diff --git a/.gitignore b/.gitignore index a9b6be08742..7808c3bbd61 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,9 @@ .idea .iml +# XCode +xcuserdata/ + # Eclipse *.pydevproject .project diff --git a/VERSION.txt b/VERSION.txt index fe4c608dd35..e4fcb02a56f 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -Sage version 6.3.beta4, released 2014-06-19 +Sage version 6.3.beta6, released 2014-07-19 diff --git a/build/deps b/build/deps index d0dc158e74b..d13bd97fa4b 100644 --- a/build/deps +++ b/build/deps @@ -336,7 +336,7 @@ $(INST)/$(SAGETEX): $(INST)/$(PYTHON) \ $(INST)/$(SETUPTOOLS): $(INST)/$(PYTHON) +$(PIPE) "$(SAGE_SPKG) $(SETUPTOOLS) 2>&1" "tee -a $(SAGE_LOGS)/$(SETUPTOOLS).log" -$(INST)/$(SINGULAR): $(INST)/$(MPIR) $(INST)/$(NTL) \ +$(INST)/$(SINGULAR): $(INST)/$(MPIR) $(INST)/$(NTL) $(INST)/$(FLINT) \ $(INST)/$(READLINE) $(INST)/$(MPFR) +$(PIPE) "$(SAGE_SPKG) $(SINGULAR) 2>&1" "tee -a $(SAGE_LOGS)/$(SINGULAR).log" diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 97ed2219c62..fe665dab861 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=100d46c2358df82a93feb63bc339620f413caa1b -md5=d10af4cfec628f992109225444e68ba3 -cksum=553182482 +sha1=3265062e02ba06ebe5897596eea08fb04cb3c554 +md5=f3a75a886044f86dab6565b399141bde +cksum=666076577 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index a7873645902..7facc89938b 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -34 +36 diff --git a/build/pkgs/libgap/checksums.ini b/build/pkgs/libgap/checksums.ini index 46267a893ac..a575c3e6351 100644 --- a/build/pkgs/libgap/checksums.ini +++ b/build/pkgs/libgap/checksums.ini @@ -1,4 +1,4 @@ tarball=libgap-VERSION.tar.gz -sha1=4ae83174267adbe615be9cab0d1f4a3c53bddb5e -md5=e69513efa609ac1fa095db981b6c7c2b -cksum=2720542483 +sha1=6880c1c36a59051a1e94f4866848a37f4d79cb7c +md5=f797ca3b8d3232cb24b5fe3be704a08a +cksum=1046627126 diff --git a/build/pkgs/libgap/package-version.txt b/build/pkgs/libgap/package-version.txt index 70bc9a9f64c..b37b05b50a9 100644 --- a/build/pkgs/libgap/package-version.txt +++ b/build/pkgs/libgap/package-version.txt @@ -1 +1 @@ -4.7.5 +4.7.5.1 diff --git a/build/pkgs/lrcalc/SPKG.txt b/build/pkgs/lrcalc/SPKG.txt index e2f888541bc..328549280df 100644 --- a/build/pkgs/lrcalc/SPKG.txt +++ b/build/pkgs/lrcalc/SPKG.txt @@ -29,42 +29,3 @@ Anders S. Buch (asbuch@math.rutgers.edu) spkg-src and the patch patches/build.diff. So, whenever you update the sources, you should use/modify the spkg-src script. -== Changelog == - -=== lrcalc-1.1.6.p0 (Jeroen Demeyer, 8 May 2013) === - * Trac #14487: fix various build and packaging issues, add spkg-src - and patches/build.diff (to be applied at packaging time). - -=== lrcalc-1.1.6 (Jean-Pierre Flori, December 2012 - February 2013) === - - * Trac #13839: - * Updated to lrcalc-sage-1.1.6: let lrcalc build a shared library on Cygwin. - * Stop tracking the src directory which is now tracked upstream. - -=== lrcalc-1.1.6beta1 (Nicolas M. Thiéry, June 2012) === - - * Updated to lrcalc-1.1.6beta1: - * Fixed segmentation fault on Open Solaris (name conflict with hash_insert) - * Added quantum and fusion calculations in the README and testsuite - * Don't install the lrcalc binaries, only the libraries and headers - -=== lrcalc-1.1.6beta.p0 (Nicolas M. Thiéry, June 2012) === - - * Fixed make -> $MAKE - -=== lrcalc-1.1.6beta (Nicolas M. Thiéry, January 2012) === - - * Upgrade to lrcalc 1.1.6beta which combines: - * lrcalc-1.1.5.tar.gz - * schmult-1.1.5.tar.gz - * an autotools build system - - * First release as optional spkg (see #10333) - -=== lrcalc-1.1.5b (Nicolas M. Thiéry, May 2011) === - - * An alpha prerelease of 1.1.6beta - -=== lrcalc-1.1.4 (Mike Hansen, May 2010) === - - * Initial version diff --git a/build/pkgs/lrcalc/checksums.ini b/build/pkgs/lrcalc/checksums.ini index 6ee89d5d190..75590e1b2b4 100644 --- a/build/pkgs/lrcalc/checksums.ini +++ b/build/pkgs/lrcalc/checksums.ini @@ -1,4 +1,4 @@ -tarball=lrcalc-VERSION.tar.bz2 -sha1=b36a8bdfab9f8ca0cfc7f922b4f4b0064d364464 -md5=e41f12d2f1f04e868c5d651951f9b0ff -cksum=3021882451 +tarball=lrcalc-VERSION.tar.gz +sha1=8149a4c676f4b21ce9dc23145b272a061a04240a +md5=a1d85113d9c915b41ba38fcf1741e637 +cksum=4041239855 diff --git a/build/pkgs/lrcalc/package-version.txt b/build/pkgs/lrcalc/package-version.txt index 856e05a167d..2bf1ca5f549 100644 --- a/build/pkgs/lrcalc/package-version.txt +++ b/build/pkgs/lrcalc/package-version.txt @@ -1 +1 @@ -1.1.6.p0 +1.1.7 diff --git a/build/pkgs/lrcalc/patches/lrcalc-1.1.7-jump.patch b/build/pkgs/lrcalc/patches/lrcalc-1.1.7-jump.patch new file mode 100644 index 00000000000..d21eeaefa45 --- /dev/null +++ b/build/pkgs/lrcalc/patches/lrcalc-1.1.7-jump.patch @@ -0,0 +1,138 @@ +diff -Naur lrcalc-sage-1.1.7-orig/lrcoef/coprod.c lrcalc-sage-1.1.7/lrcoef/coprod.c +--- lrcalc-sage-1.1.7-orig/lrcoef/coprod.c 2013-06-09 16:12:42.000000000 +1200 ++++ lrcalc-sage-1.1.7/lrcoef/coprod.c 2014-06-29 09:51:37.000000000 +1200 +@@ -11,7 +11,7 @@ + #include + + #include "symfcn.h" +- ++#include "lrcalc_jump.h" + + void print_usage() + { +diff -Naur lrcalc-sage-1.1.7-orig/lrcoef/lrcalc.c lrcalc-sage-1.1.7/lrcoef/lrcalc.c +--- lrcalc-sage-1.1.7-orig/lrcoef/lrcalc.c 2013-06-09 16:12:42.000000000 +1200 ++++ lrcalc-sage-1.1.7/lrcoef/lrcalc.c 2014-06-29 09:52:25.000000000 +1200 +@@ -12,7 +12,7 @@ + + #include "symfcn.h" + #include "maple.h" +- ++#include "lrcalc_jump.h" + + #define MULT_USAGE \ + "lrcalc mult [-mz] [-r rows] [-q rows,cols] [-f rows,level] part1 - part2\n" +diff -Naur lrcalc-sage-1.1.7-orig/lrcoef/lrcoef.c lrcalc-sage-1.1.7/lrcoef/lrcoef.c +--- lrcalc-sage-1.1.7-orig/lrcoef/lrcoef.c 2013-06-09 16:12:42.000000000 +1200 ++++ lrcalc-sage-1.1.7/lrcoef/lrcoef.c 2014-06-29 09:52:50.000000000 +1200 +@@ -14,7 +14,7 @@ + + #include "symfcn.h" + #include "maple.h" +- ++#include "lrcalc_jump.h" + + void print_usage() + { +diff -Naur lrcalc-sage-1.1.7-orig/lrcoef/lrskew.c lrcalc-sage-1.1.7/lrcoef/lrskew.c +--- lrcalc-sage-1.1.7-orig/lrcoef/lrskew.c 2013-06-09 16:12:42.000000000 +1200 ++++ lrcalc-sage-1.1.7/lrcoef/lrskew.c 2014-06-29 09:53:16.000000000 +1200 +@@ -11,7 +11,7 @@ + #include + + #include "symfcn.h" +- ++#include "lrcalc_jump.h" + + void print_usage() + { +diff -Naur lrcalc-sage-1.1.7-orig/lrcoef/mult.c lrcalc-sage-1.1.7/lrcoef/mult.c +--- lrcalc-sage-1.1.7-orig/lrcoef/mult.c 2013-06-09 16:12:42.000000000 +1200 ++++ lrcalc-sage-1.1.7/lrcoef/mult.c 2014-06-29 09:53:45.000000000 +1200 +@@ -12,7 +12,7 @@ + + #include "symfcn.h" + #include "maple.h" +- ++#include "lrcalc_jump.h" + + void print_usage() + { +diff -Naur lrcalc-sage-1.1.7-orig/lrcoef/sat.c lrcalc-sage-1.1.7/lrcoef/sat.c +--- lrcalc-sage-1.1.7-orig/lrcoef/sat.c 2013-06-09 16:12:42.000000000 +1200 ++++ lrcalc-sage-1.1.7/lrcoef/sat.c 2014-06-29 09:54:08.000000000 +1200 +@@ -10,7 +10,7 @@ + #include + + #include "symfcn.h" +- ++#include "lrcalc_jump.h" + + #define NUM_PRIMES 50 + +diff -Naur lrcalc-sage-1.1.7-orig/lrcoef/skew.c lrcalc-sage-1.1.7/lrcoef/skew.c +--- lrcalc-sage-1.1.7-orig/lrcoef/skew.c 2013-06-09 16:12:42.000000000 +1200 ++++ lrcalc-sage-1.1.7/lrcoef/skew.c 2014-06-29 09:54:40.000000000 +1200 +@@ -13,7 +13,7 @@ + + #include "symfcn.h" + #include "maple.h" +- ++#include "lrcalc_jump.h" + + void print_usage() + { +diff -Naur lrcalc-sage-1.1.7-orig/mathlib/alloc.c lrcalc-sage-1.1.7/mathlib/alloc.c +--- lrcalc-sage-1.1.7-orig/mathlib/alloc.c 2013-06-09 16:12:17.000000000 +1200 ++++ lrcalc-sage-1.1.7/mathlib/alloc.c 2014-06-29 09:55:08.000000000 +1200 +@@ -8,6 +8,7 @@ + #include + + #include "alloc.h" ++#include "lrcalc_jump.h" + + #if 0 + #define DEBUG_MEMORY_PRINT +diff -Naur lrcalc-sage-1.1.7-orig/mathlib/alloc.h lrcalc-sage-1.1.7/mathlib/alloc.h +--- lrcalc-sage-1.1.7-orig/mathlib/alloc.h 2013-06-09 16:12:17.000000000 +1200 ++++ lrcalc-sage-1.1.7/mathlib/alloc.h 2014-06-29 09:50:13.000000000 +1200 +@@ -2,13 +2,6 @@ + #define _ALLOC_H + + #include +-#include +- +-/* Programs using the lrcalc library should set lrcalc_panic_frame +- * with setjmp(lrcalc_panic_frame). The lrcalc library will call +- * longjmp(lrcalc_panic_frame, 1) if an "out of memory" event occurs. +- */ +-jmp_buf lrcalc_panic_frame; + + void *amalloc(size_t size); + void *acalloc(size_t num, size_t size); +diff -Naur lrcalc-sage-1.1.7-orig/mathlib/lrcalc_jump.h lrcalc-sage-1.1.7/mathlib/lrcalc_jump.h +--- lrcalc-sage-1.1.7-orig/mathlib/lrcalc_jump.h 1970-01-01 12:00:00.000000000 +1200 ++++ lrcalc-sage-1.1.7/mathlib/lrcalc_jump.h 2014-06-29 09:49:42.000000000 +1200 +@@ -0,0 +1,11 @@ ++#ifndef _JUMP_H ++ ++#include ++ ++/* Programs using the lrcalc library should set lrcalc_panic_frame ++ * with setjmp(lrcalc_panic_frame). The lrcalc library will call ++ * longjmp(lrcalc_panic_frame, 1) if an "out of memory" event occurs. ++ */ ++jmp_buf lrcalc_panic_frame; ++ ++#endif +diff -Naur lrcalc-sage-1.1.7-orig/mathlib/salloc.c lrcalc-sage-1.1.7/mathlib/salloc.c +--- lrcalc-sage-1.1.7-orig/mathlib/salloc.c 2013-06-09 16:12:17.000000000 +1200 ++++ lrcalc-sage-1.1.7/mathlib/salloc.c 2014-06-29 09:55:34.000000000 +1200 +@@ -13,6 +13,7 @@ + #include + + #include "alloc.h" ++#include "lrcalc_jump.h" + + typedef struct mlink { + struct mlink *next; diff --git a/build/pkgs/lrcalc/spkg-install b/build/pkgs/lrcalc/spkg-install index b074fba4875..ffc3856777d 100755 --- a/build/pkgs/lrcalc/spkg-install +++ b/build/pkgs/lrcalc/spkg-install @@ -8,6 +8,15 @@ fi cd src +for patch in ../patches/*.patch; do + [ -r "$patch" ] || continue # Skip non-existing or non-readable patches + patch -p1 <"$patch" + if [ $? -ne 0 ]; then + echo >&2 "Error applying '$patch'" + exit 1 + fi +done + ./configure --prefix="$SAGE_LOCAL" if [ $? -ne 0 ]; then echo "Error configuring lrcalc." diff --git a/build/pkgs/openssl/SPKG.txt b/build/pkgs/openssl/SPKG.txt index 47ea62f0b9a..c2cb3baf813 100644 --- a/build/pkgs/openssl/SPKG.txt +++ b/build/pkgs/openssl/SPKG.txt @@ -22,27 +22,5 @@ library in a variety of computer languages are available. === Patches === - * src/Configure: Sage's gcc doesn't recognize the '-arch' option, - while Apple's gcc does, so we remove this flag. * src/config: patched to fix a problem on Solaris. -== Changelog == - -=== openssl-1.0.1c.p0 (John Palmieri, 17 June 2012) === - * Trac #13126: Update source to 1.0.1c - * Created hg repository - * Cleaned up spkg-install - * Created spkg-check - * Patch to build on OS X Lion - * Patch to build on Solaris - -=== openssl-1.0.1a (Mariah Lenox, 23 April 2012) === - * upgraded source - -=== openssl-1.0.0.p0 (William Stein, June 3, 2010) === - * Fix issue with openssl libraries getting installed to lib64, - as recommended by Mariah (the referee). - -=== openssl-1.0.0 (William Stein, April 27, 2010) === - * Initial version in modernized format - diff --git a/build/pkgs/openssl/checksums.ini b/build/pkgs/openssl/checksums.ini index f92cfbec1bc..bdded2bab89 100644 --- a/build/pkgs/openssl/checksums.ini +++ b/build/pkgs/openssl/checksums.ini @@ -1,4 +1,4 @@ tarball=openssl-VERSION.tar.gz -sha1=3f1b1223c9e8189bfe4e186d86449775bd903460 -md5=66bf6f10f060d561929de96f9dfe5b8c -cksum=4124470397 +sha1=b2239599c8bf8f7fc48590a55205c26abe560bf8 +md5=8d6d684a9430d5cc98a62a5d8fbda8cf +cksum=3773835410 diff --git a/build/pkgs/openssl/package-version.txt b/build/pkgs/openssl/package-version.txt index 2d615642512..36beb0f0e4e 100644 --- a/build/pkgs/openssl/package-version.txt +++ b/build/pkgs/openssl/package-version.txt @@ -1 +1 @@ -1.0.1e +1.0.1h diff --git a/build/pkgs/openssl/patches/Configure.patch b/build/pkgs/openssl/patches/Configure.patch deleted file mode 100644 index 7a9a385f336..00000000000 --- a/build/pkgs/openssl/patches/Configure.patch +++ /dev/null @@ -1,20 +0,0 @@ -diff -ru src/Configure b/Configure ---- src/Configure 2012-03-14 15:20:40.000000000 -0700 -+++ b/Configure 2012-06-20 13:42:25.000000000 -0700 -@@ -575,11 +575,11 @@ - - ##### MacOS X (a.k.a. Rhapsody or Darwin) setup - "rhapsody-ppc-cc","cc:-O3 -DB_ENDIAN::(unknown):MACOSX_RHAPSODY::BN_LLONG RC4_CHAR RC4_CHUNK DES_UNROLL BF_PTR:${no_asm}::", --"darwin-ppc-cc","cc:-arch ppc -O3 -DB_ENDIAN -Wa,-force_cpusubtype_ALL::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:BN_LLONG RC4_CHAR RC4_CHUNK DES_UNROLL BF_PTR:${ppc32_asm}:osx32:dlfcn:darwin-shared:-fPIC -fno-common:-arch ppc -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", --"darwin64-ppc-cc","cc:-arch ppc64 -O3 -DB_ENDIAN::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:SIXTY_FOUR_BIT_LONG RC4_CHAR RC4_CHUNK DES_UNROLL BF_PTR:${ppc64_asm}:osx64:dlfcn:darwin-shared:-fPIC -fno-common:-arch ppc64 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", --"darwin-i386-cc","cc:-arch i386 -O3 -fomit-frame-pointer -DL_ENDIAN::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:BN_LLONG RC4_INT RC4_CHUNK DES_UNROLL BF_PTR:".eval{my $asm=$x86_asm;$asm=~s/cast\-586\.o//;$asm}.":macosx:dlfcn:darwin-shared:-fPIC -fno-common:-arch i386 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", --"debug-darwin-i386-cc","cc:-arch i386 -g3 -DL_ENDIAN::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:BN_LLONG RC4_INT RC4_CHUNK DES_UNROLL BF_PTR:${x86_asm}:macosx:dlfcn:darwin-shared:-fPIC -fno-common:-arch i386 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", --"darwin64-x86_64-cc","cc:-arch x86_64 -O3 -DL_ENDIAN -Wall::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:SIXTY_FOUR_BIT_LONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL:".eval{my $asm=$x86_64_asm;$asm=~s/rc4\-[^:]+//;$asm}.":macosx:dlfcn:darwin-shared:-fPIC -fno-common:-arch x86_64 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", -+"darwin-ppc-cc","cc:-O3 -DB_ENDIAN -Wa,-force_cpusubtype_ALL::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:BN_LLONG RC4_CHAR RC4_CHUNK DES_UNROLL BF_PTR:${ppc32_asm}:osx32:dlfcn:darwin-shared:-fPIC -fno-common:-dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", -+"darwin64-ppc-cc","cc:-O3 -DB_ENDIAN::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:SIXTY_FOUR_BIT_LONG RC4_CHAR RC4_CHUNK DES_UNROLL BF_PTR:${ppc64_asm}:osx64:dlfcn:darwin-shared:-fPIC -fno-common:-dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", -+"darwin-i386-cc","cc:-O3 -fomit-frame-pointer -DL_ENDIAN::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:BN_LLONG RC4_INT RC4_CHUNK DES_UNROLL BF_PTR:".eval{my $asm=$x86_asm;$asm=~s/cast\-586\.o//;$asm}.":macosx:dlfcn:darwin-shared:-fPIC -fno-common:-dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", -+"debug-darwin-i386-cc","cc:-g3 -DL_ENDIAN::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:BN_LLONG RC4_INT RC4_CHUNK DES_UNROLL BF_PTR:${x86_asm}:macosx:dlfcn:darwin-shared:-fPIC -fno-common:-dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", -+"darwin64-x86_64-cc","cc:-O3 -DL_ENDIAN -Wall::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:SIXTY_FOUR_BIT_LONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL:".eval{my $asm=$x86_64_asm;$asm=~s/rc4\-[^:]+//;$asm}.":macosx:dlfcn:darwin-shared:-fPIC -fno-common:-dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", - "debug-darwin-ppc-cc","cc:-DBN_DEBUG -DREF_CHECK -DCONF_DEBUG -DCRYPTO_MDEBUG -DB_ENDIAN -g -Wall -O::-D_REENTRANT:MACOSX::BN_LLONG RC4_CHAR RC4_CHUNK DES_UNROLL BF_PTR:${ppc32_asm}:osx32:dlfcn:darwin-shared:-fPIC:-dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", - # iPhoneOS/iOS - "iphoneos-cross","llvm-gcc:-O3 -isysroot \$(CROSS_TOP)/SDKs/\$(CROSS_SDK) -fomit-frame-pointer -fno-common::-D_REENTRANT:iOS:-Wl,-search_paths_first%:BN_LLONG RC4_CHAR RC4_CHUNK DES_UNROLL BF_PTR:${no_asm}:dlfcn:darwin-shared:-fPIC -fno-common:-dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", diff --git a/build/pkgs/openssl/patches/openssl-1.0.1c-pod_syntax_error.patch b/build/pkgs/openssl/patches/openssl-1.0.1c-pod_syntax_error.patch deleted file mode 100644 index db11bba65d0..00000000000 --- a/build/pkgs/openssl/patches/openssl-1.0.1c-pod_syntax_error.patch +++ /dev/null @@ -1,36 +0,0 @@ -diff -uNr openssl-1.0.1c-orig/crypto/des/des.pod openssl-1.0.1c-new/crypto/des/des.pod ---- openssl-1.0.1c-orig/crypto/des/des.pod 2013-01-23 11:17:32.000000000 +0100 -+++ openssl-1.0.1c-new/crypto/des/des.pod 2013-01-23 11:17:36.000000000 +0100 -@@ -181,6 +181,8 @@ - output. If there is no name specified after the B<-u>, the name text.des - will be embedded in the header. - -+=back -+ - =head1 SEE ALSO - - ps(1), -diff -uNr openssl-1.0.1c-orig/doc/crypto/X509_STORE_CTX_get_error.pod openssl-1.0.1c-new/doc/crypto/X509_STORE_CTX_get_error.pod ---- openssl-1.0.1c-orig/doc/crypto/X509_STORE_CTX_get_error.pod 2013-01-23 11:17:41.000000000 +0100 -+++ openssl-1.0.1c-new/doc/crypto/X509_STORE_CTX_get_error.pod 2013-01-23 11:17:45.000000000 +0100 -@@ -278,6 +278,8 @@ - an application specific error. This will never be returned unless explicitly - set by an application. - -+=back -+ - =head1 NOTES - - The above functions should be used instead of directly referencing the fields -diff -uNr openssl-1.0.1c-orig/doc/ssl/SSL_CTX_set_client_CA_list.pod openssl-1.0.1c-new/doc/ssl/SSL_CTX_set_client_CA_list.pod ---- openssl-1.0.1c-orig/doc/ssl/SSL_CTX_set_client_CA_list.pod 2001-04-12 18:02:34.000000000 +0200 -+++ openssl-1.0.1c-new/doc/ssl/SSL_CTX_set_client_CA_list.pod 2013-01-23 11:44:24.000000000 +0100 -@@ -70,7 +70,7 @@ - - The operation succeeded. - --=item 0 -+=item 2 - - A failure while manipulating the STACK_OF(X509_NAME) object occurred or - the X509_NAME could not be extracted from B. Check the error stack diff --git a/build/pkgs/python/checksums.ini b/build/pkgs/python/checksums.ini index 9d5d10e5e99..7a51b3a186c 100644 --- a/build/pkgs/python/checksums.ini +++ b/build/pkgs/python/checksums.ini @@ -1,4 +1,4 @@ tarball=python-VERSION.tar.gz -sha1=1db01d7f325d8ceaf986976800106018b82ae45a -md5=cf842800b67841d64e7fb3cd8acb5663 -cksum=559226099 +sha1=511960dd78451a06c9df76509635aeec05b2051a +md5=d4bca0159acb0b44a781292b5231936f +cksum=3938213866 diff --git a/build/pkgs/python/package-version.txt b/build/pkgs/python/package-version.txt index 1f7da99d4e1..6a81b4c8379 100644 --- a/build/pkgs/python/package-version.txt +++ b/build/pkgs/python/package-version.txt @@ -1 +1 @@ -2.7.7 +2.7.8 diff --git a/build/pkgs/r/SPKG.txt b/build/pkgs/r/SPKG.txt index fbd87b09743..fa537ed514d 100644 --- a/build/pkgs/r/SPKG.txt +++ b/build/pkgs/r/SPKG.txt @@ -46,4 +46,4 @@ much code written for S runs unaltered under R. libintl library. * large_address_aware.patch: don't pass --large-address-aware to ld on Cygwin64. - + * 3.1 : Upstream now handles some of these issues. Some examples changed. diff --git a/build/pkgs/r/checksums.ini b/build/pkgs/r/checksums.ini index e0a97c2b2ad..ef7ba2e249c 100644 --- a/build/pkgs/r/checksums.ini +++ b/build/pkgs/r/checksums.ini @@ -1,4 +1,4 @@ -tarball=r-VERSION.tar.bz2 -sha1=0ba9c6e5ceda749cccf5bd9cf5b535ff163deefe -md5=9247263d8a7d1e2e6794ba41f265eda4 -cksum=1117391428 +tarball=r-3.1.0.tar.gz +sha1=a9d13932c739cc12667c6a17fabd9361624a1708 +md5=a1ee52446bee81820409661e6d114ab1 +cksum=3701745191 diff --git a/build/pkgs/r/package-version.txt b/build/pkgs/r/package-version.txt index 254cffbc732..018c6c94a4d 100644 --- a/build/pkgs/r/package-version.txt +++ b/build/pkgs/r/package-version.txt @@ -1 +1 @@ -3.0.2.p1 +3.1.0.p0 diff --git a/build/pkgs/r/patches/configure.patch b/build/pkgs/r/patches/configure.patch index d475b617610..e07b7c8bcae 100644 --- a/build/pkgs/r/patches/configure.patch +++ b/build/pkgs/r/patches/configure.patch @@ -1,7 +1,7 @@ diff -ru src/configure src.configure/configure --- src/configure 2011-10-24 00:05:54.000000000 +0200 +++ src.configure/configure 2012-03-30 16:31:51.409247321 +0200 -@@ -22492,7 +22492,7 @@ +@@ -22876,7 +22492,7 @@ if ac_fn_f77_try_compile "$LINENO"; then : ac_cv_prog_f77_v= # Try some options frequently used verbose output @@ -10,7 +10,7 @@ diff -ru src/configure src.configure/configure cat > conftest.$ac_ext <<_ACEOF program main -@@ -22808,7 +22808,7 @@ +@@ -23232,7 +22808,7 @@ if ac_fn_c_try_compile "$LINENO"; then : r_cv_prog_c_v= # Try some options frequently used verbose output diff --git a/build/pkgs/r/patches/install.R-arm.patch b/build/pkgs/r/patches/install.R-arm.patch deleted file mode 100644 index 6ca5128ddf1..00000000000 --- a/build/pkgs/r/patches/install.R-arm.patch +++ /dev/null @@ -1,31 +0,0 @@ -diff --git a/src/library/tools/R/install.R b/src/library/tools/R/install.R -index 5d55ca8..b654cd6 100644 ---- a/src/library/tools/R/install.R -+++ b/src/library/tools/R/install.R -@@ -110,7 +110,11 @@ - SHLIB_EXT <- if (WINDOWS) ".dll" else { - ## can we do better? - mconf <- file.path(R.home(), paste0("etc", rarch), "Makeconf") -- sub(".*= ", "", grep("^SHLIB_EXT", readLines(mconf), value = TRUE)) -+ -+ if (substr( Sys.info()["machine"], 1, 3) == "arm") # arm has broken regexps in libc -+ sub(".*= ", "", grep("^SHLIB_EXT", readLines(mconf), value = TRUE), perl = TRUE) -+ else -+ sub(".*= ", "", grep("^SHLIB_EXT", readLines(mconf), value = TRUE)) - } - - options(warn = 1) -@@ -1676,8 +1680,13 @@ - mconf <- readLines(file.path(R.home(), - paste0("etc", Sys.getenv("R_ARCH")), - "Makeconf")) -+ if (substr( Sys.info()["machine"], 1, 3) == "arm") { # arm has broken regexps in libc -+ SHLIB_EXT <- sub(".*= ", "", grep("^SHLIB_EXT", mconf, value = TRUE), perl = TRUE) -+ SHLIB_LIBADD <- sub(".*= ", "", grep("^SHLIB_LIBADD", mconf, value = TRUE), perl = TRUE) -+ } else { - SHLIB_EXT <- sub(".*= ", "", grep("^SHLIB_EXT", mconf, value = TRUE)) - SHLIB_LIBADD <- sub(".*= ", "", grep("^SHLIB_LIBADD", mconf, value = TRUE)) -+ } - MAKE <- Sys.getenv("MAKE") - rarch <- Sys.getenv("R_ARCH") - } else { diff --git a/build/pkgs/r/patches/large_address_aware.patch b/build/pkgs/r/patches/large_address_aware.patch index ecf8657bd9c..48bfa8f8023 100644 --- a/build/pkgs/r/patches/large_address_aware.patch +++ b/build/pkgs/r/patches/large_address_aware.patch @@ -1,7 +1,7 @@ diff -druN r-3.0.2.orig/configure.ac r-3.0.2/configure.ac --- r-3.0.2.orig/configure.ac 2013-08-26 15:05:06.000000000 -0700 +++ r-3.0.2/configure.ac 2014-01-18 09:35:57.516091309 -0800 -@@ -1310,7 +1310,11 @@ +@@ -1310,7 +1321,11 @@ SHLIB_EXT=".dll" dylib_undefined_allowed=no is_cygwin=yes @@ -17,7 +17,7 @@ diff -druN r-3.0.2.orig/configure.ac r-3.0.2/configure.ac diff -druN r-3.0.2.orig/configure r-3.0.2/configure --- r-3.0.2.orig/configure 2013-09-17 15:06:13.000000000 -0700 +++ r-3.0.2/configure 2014-01-18 09:38:03.426103900 -0800 -@@ -26247,7 +26247,11 @@ +@@ -26313,7 +26247,11 @@ SHLIB_EXT=".dll" dylib_undefined_allowed=no is_cygwin=yes diff --git a/build/pkgs/r/patches/libintl-visibility.patch b/build/pkgs/r/patches/libintl-visibility.patch deleted file mode 100644 index e652708acde..00000000000 --- a/build/pkgs/r/patches/libintl-visibility.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff -druN src.orig/src/extra/intl/dngettext.c src/src/extra/intl/dngettext.c ---- src.orig/src/extra/intl/dngettext.c 2010-03-17 15:43:05.000000000 +0100 -+++ src/src/extra/intl/dngettext.c 2013-11-12 14:07:19.134450433 +0100 -@@ -46,6 +46,9 @@ - - /* Look up MSGID in the DOMAINNAME message catalog of the current - LC_MESSAGES locale and skip message according to the plural form. */ -+#ifdef HAVE_VISIBILITY_ATTRIBUTE -+__attribute__ ((visibility ("default"))) -+#endif - char * - DNGETTEXT (const char *domainname, - const char *msgid1, const char *msgid2, unsigned long int n) diff --git a/build/pkgs/r/spkg-src b/build/pkgs/r/spkg-src new file mode 100644 index 00000000000..89e6f1563c3 --- /dev/null +++ b/build/pkgs/r/spkg-src @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +## What do we want ? +VERSION=$(cat package-version.txt | sed -re "s/^(.*)\\.p.+$/\\1/") +TARGET_TARBALL=r-$VERSION.tar.gz +SOURCE_TARBALL=R-$VERSION.tar.gz + +mv $SOURCE_TARBALL $TARGET_TARBALL diff --git a/build/pkgs/singular/SPKG.txt b/build/pkgs/singular/SPKG.txt index 1df072d061f..4d8c68b9855 100644 --- a/build/pkgs/singular/SPKG.txt +++ b/build/pkgs/singular/SPKG.txt @@ -23,7 +23,12 @@ http://www.singular.uni-kl.de/ == Dependencies == -GNU patch, GMP/MPIR, NTL, Termcap, Readline and MPFR +* GNU patch +* readline +* GMP/MPIR +* MPFR +* NTL +* FLINT == Special Update/Build Instructions == @@ -46,6 +51,7 @@ See spkg-src. * osx_link.patch: #14415: Fixes linker problems on OS X PPC. * sanitize_gmp_header_hack.patch: Fix and simplify generation of `factory/cf_gmp.h` (cf. #14737). + * flint.patch: avoid underlinking FLINT in libsingular. * exeext.patch: Add $(EXEEXT) to some executable names. * cygwin64.path: Support for Cywgin64. * singular-3.1.6-fix_mpoly_factor_segfault.patch: @@ -53,6 +59,9 @@ See spkg-src. nomials. Collection / subset of upstream commits, latest being 28f4fe9464722511718050dfab7cd61d29898968. (Cf. #16462 and Singular trac #621.) + * singular-3.1.6-no_return_type.patch: + State return type of main functions so that Singular builds + with clang. Other notes * The option '--without-dynamic-kernel' is used on *all* @@ -69,421 +78,8 @@ Other notes * Due to http://www.singular.uni-kl.de:8002/trac/ticket/438, we currently always build Singular with its debug code. Change --with-debug to $WITH_DEBUG in the configure call if this is fixed. - * We build Singular without FLINT support because Singular requires - FLINT 2.x. If a sufficiently recent version of FLINT is ever added - to Sage, we should make Singular use it - (configure --with-flint="$SAGE_LOCAL"). * If the environment variable SAGE_DEBUG is set to "yes", then omalloc will be replaced by xalloc. The resulting Singular executable and libsingular library will be slower than with omalloc, but allow for easier debugging of memory corruptions. -== ChangeLog == - -=== singular-3.1.6.p2 (Leif Leonhardy, June 9th 2014) === - * #16462 Add patch to fix segfault in factorization of multivariate - polynomials. (Cherry-picked from upstream; cf. Singular trac #621. - The pull request / commit referenced there isn't sufficient, as it - isn't based on Singular 3.1.6.) - -=== singular-3-1-6.p1 (Jean-Pierre Flori, 31 December 2013) === - * #14876: update patch for NTL compatibility. - -=== singular-3-1-6.p0 (Jeroen Demeyer, 26 August 2013) === - * Remove the following patches, which were fixed upstream: - - NTL_negate.patch - - install_table.patch - - pullrequest215.patch - - sage_trac_12089.patch - - singular_15435.patch - - singular_part_of_changeset_baadc0f7.patch - - singular_trac_439.patch - - singular_trac_440.patch - - singular_trac_441.patch - - singular_trac_443.patch - * Removed cygwin-makefile.patch, which is hopefully not needed anymore. - * Rebased some other patches. - * Add configure_comma.patch. - -=== singular-3-1-5.p9 (Paul Zimmermann, Jeroen Demeyer, 26 August 2013) === - * #13770: Add upstream pull request 215 for multivariate factorisation. - * spkg-install: split apply_patches step in 2 steps: choose_patches and - apply_patches; add a few small fixes. - * Remove install-sh which is no longer needed. - -=== singular-3-1-5.p8 (Leif Leonhardy, Volker Braun, 10 July 2013) === - * #14737: Fix and simplify generation of `factory/cf_gmp.h` - (by adding `patches/sanitize_gmp_header_hack.patch`). - * Add spkg-src and track all files - -=== singular-3-1-5.p7 (Jeroen Demeyer, 9 April 2013) === - * #14429: On sparc processors, build with --with-align=8. - -=== singular-3-1-5.p6 (Jeroen Demeyer, 5 April 2013) === - * #14415: Add osx_link.patch to fix linker problems on OS X 10.4 PPC - with GCC 4.7.2. - -=== singular-3-1-5.p5 (Leif Leonhardy, March 18th 2013) === - * #14295: Singular fails to build with GCC 4.7.x on Solaris. - On Solaris, with `__cplusplus >= 199711L`, `floor()` and `log10()` are - overloaded functions which get pulled into the global namespace, - such that calling them with an `int` gets ambiguous. - Patch `kernel/bigintmat.cc` (`patches/sage_trac_14295.patch`) - to cast parameters to `floor()` and `log10()` from `int` to - `double`, making the calls unambiguous. - -=== singular-3-1-5.p4 (Jean-Pierre Flori, 12 February 2013) === - * Trac #14033: prevent Singular from always linking to ncurses on - Cygwin. - -=== singular-3-1-5.p3 (Volker Braun, 28 December 2012) === - * Trac #13876: Rename SINGULAR_XALLOC to SAGE_DEBUG - * Build fixes for the Sage library with SAGE_DEBUG=yes - -=== singular-3-1-5.p2 (Simon King, 03 December 2012) === - * Trac #13731 - * Optionally replace omalloc with xalloc (a thin compatibility layer - on top of malloc) so that debugging of memory corruptions becomes - easier. - * Include two upstream fixes for memory corruptions. - -=== singular-3-1-5.p1 (Simon King, 29 August 2012) === - * Trac #13237: add patch install_table.patch - - singular_15435.patch - - singular_part_of_changeset_baadc0f7.patch - * If SINGULAR_XALLOC is "yes", then omalloc is replaced - by xalloc (a thin compatibility layer on top of malloc), - so that debugging of memory corruptions becomes easier. - -=== singular-3-1-5.p0 (Jeroen Demeyer, Alexander Dreyer, 10 August 2012) === - * Trac #13237: Upgrade to version 3-1-5. - * Removed patches which are now upstreamed: - - patches/Singular.Makefile.in.shared.patch - - patches/Singular.configure.patch - - patches/factory.GNUmakefile.in.patch - - patches/factory_configure - - patches/make_parallel.patch - - patches/os_x_ppc.patch - * In spkg-install, simplify apply_patches(), automatically apply all - patches in patches/*.patch, move conditional patches to - patches/conditional - * Rename some patches such that they all have the extension '.patch' - * Put the two patches for SAGE_DEBUG (Singular.Makefile.in.debug.patch - and kernel.Makefile.in.debug.patch) into one file - conditional/sage_debug.patch. - * Add several patches (see above): - - NTL_negate.patch - - singular_trac_439.patch - - singular_trac_440.patch - - singular_trac_441.patch - - singular_trac_443.patch - - sage_trac_12089.patch - - slibdir.patch - * When building Singular, don't first make install and then - make install-nolns. Instead, only do the latter. - * Don't create the LIB->lib symlink, which is no longer needed. - * In spkg-install, remove distclean() step; merge clean_headers() and - part of the old distclean() into remove_old_version(). - * Only unset LD on Darwin. - * Remove the unsetting of TMPDIR (bug fixed upstream). - * Do not override user-set CFLAGS and CXXFLAGS. - * Echo all error messages to stderr instead of stdout. - * Fix various ./configure options and remove some unsupported options. - * Always configure --with-debug, as --without-debug doesn't work, see - http://www.singular.uni-kl.de:8002/trac/ticket/438 - * Don't create sage_singular symlink, which wasn't used anyway. - * Replace the $SAGE_LOCAL/bin/Singular script by a symlink. - * Remove workaround for GCC-4.0.x on Darwin (obsolete by the GCC spkg). - * Fix formatting of spkg-install (consistent indentation, no TABs). - -=== singular-3-1-3-3.p6 (Leif Leonhardy, March 17th 2012) === - * #12680: Fix hardcoded 'g++' (and two typos) in factory/GNUmakefile.in. - -=== singular-3-1-3-3.p5 (Jeroen Demeyer, 22 February 2012) === - * Trac #12562: Disable -pipe on SunOS, as the Sun assembler chokes on - large files passed through a pipe. - * Trac #12311: Don't specify an explicit path for testcc.sh. - -=== singular-3-1-3-3.p4 (Jeroen Demeyer, 19 January 2012) === - * Trac #12304: Regenerate factory/configure using autoconf-2.68 to fix - a build problem on OS X 10.4. - * Change spkg-changes to compress omalloc/Misc/dlmalloc/malloc.ps - using gzip -9. Redownloaded upstream sources and applied - spkg-changes. - -=== singular-3-1-3-3.p3 (Julien Puydt, 7 January 2012) === - * Trac #12110: Add patch to use the -shared switch by default on unknown - platforms -- ARM is unknown and doesn't compile without that switch ; - upstream has better support for the arch so it's not interesting to make - a better fix. - -=== singular-3-1-3-3.p2 (Jeroen Demeyer, 9 December 2011) === - * Trac #12137: Add patch make_parallel.patch to fix parallel building - by patching various Makefile.in's - * Use $MAKE instead of make - -=== singular-3-1-3-3.p1 (Jeroen Demeyer, 10 October 2011) === - * Trac #10903: Add a patch os_x_ppc.patch to fix building on OS X PPC. - * Make all patches apply at -p1 level and apply some patches for Cygwin - on all systems for consistency. - * Remove unused files in patches/: Singular.configure, Singular.configure.in, - Singular.configure.in.patch - * Exit when `patch` fails. We do not print an error message, patch is - verbose enough by itself. - -=== singular-3-1-3-3.p0 (Volker Braun, 2 October 2011) === - * Trac #10903: Solaris fixes - -=== singular-3-1-3-3 (Martin Albrecht, Burcin Erocal, Volker Braun, 28 September 2011) === - * Trac #10903: new upstream release - * SAGE_DEBUG now builds Singular with lots of checks - -=== singular-3-1-1-4.p13 (Karl-Dieter Crisman, 12 August 2011) === - * Trac #11550: For Cygwin, we need to include time.h in - Singular/Minor.h - -=== singular-3-1-1-4.p12 (Leif Leonhardy, Jeroen Demeyer, 10 August 2011) === - * Trac #11645: Fix installation of gftables on Solaris - by applying a patch to Singular/Makefile.in - -=== singular-3-1-1-4.p11 (Jeroen Demeyer, 9 August 2011) === - * Trac #11663: Make all files world-readable - * Use `cp -p` in spkg-install to preserve permissions - * In spkg-install, remove chmod for the Singular library - * Made the following files inside src/ non-executable: -Singular/LIB/phindex.lib -Singular/LIB/findifs.lib -Singular/LIB/dmodvar.lib -Singular/LIB/dmod.lib -Singular/LIB/jacobson.lib -Singular/LIB/bfun.lib -Singular/wrapper.cc -Singular/janet.cc -Singular/janet.h -doc/README_download.plural.texi -doc/INSTALL_unix.plural.texi -doc/singular.dic -doc/plural.doc -doc/letterplace.doc -doc/sca.doc -doc/NEWS.plural.texi -doc/INSTALL_win.plural.texi -doc/HOWTO.ispell -doc/COPYING.plural.texi -factory/cf_gcd_smallp.cc - -=== singular-3-1-1-4.p9 (Jeroen Demeyer, 3 May 2011) === -* #11278: When compiling with gcc version 4.0.x, build with -O3 - optimization instead of -O2 to fix build errors on older Mac OS X - systems. - -=== singular-3-1-1-4.p8 (Jeroen Demeyer, Alexander Dreyer, David Kirkby, 19th April 2011) === -* #11084. Drop optimisation level to -O2 since that is what upstream - mostly uses. -* Remove the special case for building on ia64 systems (the - optimization level used to be set to -O0 on such systems). - Testing on iras showed that it now works with -O2. -* When building with SAGE64 set, *append* -m64 to the various flags - instead of overriding. -* Merged changes from Sage's Trac #11084 into the package from #9497 - -=== singular-3-1-1-4.p6 (John Palmieri, 26th March 2011) === -* change "$RM" to "rm" in spkg-install - -=== singular-3-1-1-4.p5 (Martin Albrecht, 26th March 2011) === -* enable parallel make - -=== singular-3-1-1-4.p4 (Volker Braun, 4th February 2011) === -* compile fix for OSX on 64-bit kernels. -* removed dist/ directory. - -=== singular-3-1-1-4.p3 (Francois Bissey, Alexander Dreyer, 21th September 2010) === -* Patch by Francois Bissey: #9946: Added another dependency - -=== singular-3-1-1-4.p2 (Alexander Dreyer, 17th September 2010) === -* #9733, comment comment 41 ff: Added another dependency - -=== singular-3-1-1-4.p1 (Alexander Dreyer, 10th September 2010) === -* #9733: Restore parallel build, hopefully found all dependencies. - -=== singular-3-1-1-4.p0 (Mitesh Patel, 10th August 2010) === - * #8059: For now, restore building the package serially, to avoid - parallel build problems. - -=== singular-3-1-1-4 (Martin Albrecht, 15th July 2010) === - * new upstream release - * updated SPKG.txt - -=== singular-3-1-1-3 (Martin Albrecht, 10th July 2010) === - * new upstream release - * updated SPKG.txt - * reverting name to reflect actual Singular version format - -=== singular-3.1.0.4.p8 (David Kirkby, 30th June 2010) === - * #9397 "Resolve corrupted patches to permit Singular to build on Solaris x86/x64" - A previous patch made to add a Singular target x86-SunOS has been overwritten - by a later patch. I resolved these two issues, so the patch contained both changes - A patch file, and a diff made from it were removed, so the patches directory has two - less files than before. - * #9397 Set CC="$CC -m64" and CXX="$CXX-m64" to force the -m64 flag to fully propogate - thoughtout the Singular build process - otherwise it fails to do so. Setting - CFALGS/CXXFLAGS is insufficient. - -=== singular-3.1.0.4.p7 (Mitesh Patel, 8th June 2010) === - * #9185: Set an empty MAKEFLAGS variable before "make". On OS X, at - least, this fixes building multiple spkgs in parallel (cf. #8306). - -=== singular-3.1.0.4.p6 (David Kirkby and Willem Jan Palenstijn, 6th June 2010) === - * All changes relate to ticket #9160 - * Added sections to SPKG.txt which the Sage Developers Guide should be - present, but were not. These include Description, License, - PKG Maintainers, Upstream Contact, Dependencies and - Special Update/Build Instructions. - * Added a couple of comments in the new "Special Update/Build - Instructions" section about Solaris and the use of - --without-dynamic-kernel option and CONFIG_SHELL. - * Changed the name of package to singular-$version.$patchversion, - as is common Sage practice, and not have a date of any - update. Since this should be patch level 6, I've called it that. - * Touched the file src/Singular/libparse.cc - (Willem Jan Palenstijn) - * Touched the file src/factory/configure - * Removed the restriction that the OS needs to be OS X before - a 64-bit build is attempted. (This still does not fully build - 64-bit on OpenSolaris, though it is closer to doing so.) - * Move code associated with SAGE64 outside of the code for debugging. - * Ensured $SAGE_LOCAL/include is the first CPPFLAG, so the - Sage include files are included before others. - * Removed code which attempted to disable the dynamic kernel on - OS X in 64-bit mode, since the dynamic kernel was already disabled - on all platforms except Linux. It was pointless doing it twice. - -=== singular-3-1-0-4-20100214 (William Stein, February 14, 2010) === - * patch for Cygwin (trac 7338) - -=== singular-3-1-0-4-20100120 (Martin Albrecht, January 20th, 2010) === - * installing attrib.h from Singular - -=== singular-3-1-0-4-20090818.p2 (Martin Albrecht, November 18th, 2009) === - * installing lists.h from Singular (#7194) - -=== singular-3-1-0-4-20090818.p1 () === - * we forgot to update this :( - -=== singular-3-1-0-4-20090818.p0 (Georg S. Weber, September 24th, 2009) === - * added three missing /patches/*.diff files - * change -O2 to -O0 on ia64 (see trac #6360 and #6240), - original change: singular-3-1-0-2-20090618 (Craig Citro, June 18th, 2009) - * Make a copy of install-sh and put in the same directory as spkg-install, - SPKG.txt etc. For some reason, I believe one of the makefiles is looking - in the wrong directory for install-sh. There are about 5 identical copies - of this in the source. I'm not sure why there needs to be so many, and why - even with 5 or so copile, one can't be found. - Original change: singular-3-1-0-2-20090620.p0 (David Kirkby, July 19th, 2009) - * (don't include singular-3-1-0-2-20090620 patch, since no longer necessary: - Andrzej Giniewicz' fix GCC 4.4 compilation problem) - -=== singular-3-1-0-4-20090818 (Martin Albrecht, August 18th, 2009) === - * more includes, GCC 4.4 fixes - -=== singular-3-1-0-4-20090726 (Martin Albrecht, July 26th, 2009) === - * more includes in libsingular.h - -=== singular-3-1-0-4-20090611 (Martin Albrecht, June 11th, 2009) === - * new upstream release which makes some of our fixes redundant - -=== singular-3-1-0-2-20090512 (Martin Albrecht, May 12th, 2009) === - * new upstream release - -=== singular-3-0-4-4-20090511 (Martin Albrecht, May 11th, 2009) === - * fixed #5862 - * update SPKG.txt - -=== singular-3-0-4-4-20080711.p4 (Michael Abshoff, January 23rd, 2009) === - * Integrate two patches by Georg Weber (#4181 and #5344) - * update SPKG.txt - * delete MacOSX junk added in singular-3-0-4-4-20080711.p3 - -=== singular-3-0-4-4-20080711.p3 (Michael Abshoff, January 20th, 2009) === - * use "--with-malloc=system" for 64 bit OSX - -=== singular-3-0-4-4-20080711.p2 (Michael Abshoff, November 30th, 2008) === - * Fix header permission problem (#4668) - * delete old singular headers in $SAGE_LOCAL/include/singular - -=== singular-3-0-4-4-20080711.p1 (Michael Abshoff, September 1st, 2008) === - * Fix three Solaris issues: libsingular build options, tail POSIX issue and install missing on Solaris - -=== singular-3-0-4-4-20080711.p0 (Michael Abshoff, August 19th, 2008) === - * add 64 bit OSX support - * Fix mv-factoprization bug reported by Tom Boothby - -=== singular-3-0-4-4-20080711 (Martin Albrecht, July 11th, 2008) === - * new upstream release - -=== singular-3-0-4-2-20080405.p2 (Michael Abshoff, May 10th, 2008) === - * Default to "-O2" on Linux/Itanium (fixes #2983) - * Do not require flex to build Singular (fixes #3158) - -=== singular-3-0-4-2-20080405.p1 (Michael Abshoff, April 19th, 2008) === - * import Tim Abbott's Debian build fix (#2966) - -=== singular-3-0-4-2-20080405.p0 (Martin Albrecht) === - * Update Singular to the latest snapshot - -=== singular-3-0-4-1-20071209.p3 (Michael Abshoff) === - * fix memleak in kernel/longrat.cc - -* 20080105 William Stein -- Deleted LIB/surfex and modified LIB/all to not have surfex in it. -We should *not* ship and install precompiled java without thinking it through carefully and -understanding and testing it. - -* 20070823 Juan M. Bello Rivas - + dropped dependencies on flex and bison - -* 20070802 Martin Albrecht - + new upstream release (3-0-3) - + adapted new spkg structure (all changes in patches subdirectory) - -* 20070506 Martin Albrecht - + build script improvements - + changes to libsingular.h - -* 20070326 Martin Albrecht - + rewrote spkg-install - + added first alpha of libsingular.so - + removed stand-alone libfactory - -* 20070105 William Stein - + included Singular/Singular.rc.in which was missing for some - reason and is needed by the cygwin build. - -* 20060825 Martin Albrecht - + removed hannes-sprueche.txt (not funny anyway) - + removed ntl subdirectory (we ship NTL already) - + removed modules subdirectory (not needed) - + removed Tst subdirectory (not needed) - + wrote (very simple) spkg-install from scratch - -* 20060831 William Stein and Justin Walker -- fine tuning - for OS X. - -* 20060831 William Stein -- put a lot of the workarounds from the previous - Singular spkg-install script in here, since they are in fact needed - in order for Singular to work after you type, e.g., make clean, or if - you move the install directory. - -* Martin Albrecht -- add fix to libcf: -Yes, It's a (new?) bug in libcf I've fixed just yesterday but got no word from -the Singular people yet. They free a char ptr they are not supposed to free -if I understand the ostrstring docs correctly. I've attached the -one-line-fixed int_poly.cc which belongs in the factory subdirectory of -Singular. - - * Martin Albrecht -- 2006-09-17: -This looks to me as if I didn't remove all references to boost: -/usr/include/boost/dynamic_bitset/dynamic_bitset.hpp:1098: error: exception -I've attached an updated configure.in and configure (created with autoconf -2.60) with Boost checks disabled which should fix the problem. These belong -in the Singular subdirectory: - singular-3-0-2-2006-09-09/Singular diff --git a/build/pkgs/singular/package-version.txt b/build/pkgs/singular/package-version.txt index 4c600c1442e..14e03557af2 100644 --- a/build/pkgs/singular/package-version.txt +++ b/build/pkgs/singular/package-version.txt @@ -1 +1 @@ -3.1.6.p2 +3.1.6.p3 diff --git a/build/pkgs/singular/patches/flint.patch b/build/pkgs/singular/patches/flint.patch new file mode 100644 index 00000000000..db79d93157f --- /dev/null +++ b/build/pkgs/singular/patches/flint.patch @@ -0,0 +1,22 @@ +diff -druN latest.orig/Singular/configure latest/Singular/configure +--- latest.orig/Singular/configure 2012-12-19 13:01:16.000000000 -0800 ++++ latest/Singular/configure 2014-02-19 05:46:21.061190243 -0800 +@@ -8989,6 +8989,7 @@ + if test "x$flint_found" = "xyes"; then + includedir= "${FLINT_HOME}/include ${includedir}" + LDFLAGS="${LDFLAGS} ${FLINT_LIBS}" ++ SLDFLAGS="${SLDFLAGS} ${FLINT_LIBS}" + NEED_LIBS="-lflint -lmpfr ${NEED_LIBS}" + fi + +diff -druN latest.orig/Singular/configure.in latest/Singular/configure.in +--- latest.orig/Singular/configure.in 2012-12-19 13:01:16.000000000 -0800 ++++ latest/Singular/configure.in 2014-02-19 05:45:48.911187028 -0800 +@@ -1406,6 +1406,7 @@ + if test "x$flint_found" = "xyes"; then + includedir= "${FLINT_HOME}/include ${includedir}" + LDFLAGS="${LDFLAGS} ${FLINT_LIBS}" ++ SLDFLAGS="${SLDFLAGS} ${FLINT_LIBS}" + NEED_LIBS="-lflint -lmpfr ${NEED_LIBS}" + fi + diff --git a/build/pkgs/singular/patches/singular-3.1.6-no_return_type.patch b/build/pkgs/singular/patches/singular-3.1.6-no_return_type.patch new file mode 100644 index 00000000000..82ff99b3866 --- /dev/null +++ b/build/pkgs/singular/patches/singular-3.1.6-no_return_type.patch @@ -0,0 +1,22 @@ +--- latest/Singular/libparse.l 2012-12-19 22:01:16.000000000 +0100 ++++ latest/Singular/libparse.l 2014-05-17 18:24:37.707569492 +0200 +@@ -963,7 +963,7 @@ + } + + #ifdef STANDALONE_PARSER +-main( int argc, char *argv[] ) ++int main( int argc, char *argv[] ) + { + lib_style_types lib_style; + main_init(argc, argv); +--- latest/Singular/libparse.cc 2012-12-19 22:01:16.000000000 +0100 ++++ latest/Singular/libparse.cc 2014-05-17 18:45:54.543560513 +0200 +@@ -3504,7 +3504,7 @@ + } + + #ifdef STANDALONE_PARSER +-main( int argc, char *argv[] ) ++int main( int argc, char *argv[] ) + { + lib_style_types lib_style; + main_init(argc, argv); diff --git a/build/pkgs/singular/spkg-install b/build/pkgs/singular/spkg-install index aee6a5b1175..7914c5084a9 100755 --- a/build/pkgs/singular/spkg-install +++ b/build/pkgs/singular/spkg-install @@ -136,10 +136,10 @@ config() --with-apint=gmp \ --with-malloc=system \ --with-NTL \ + --with-flint="$SAGE_LOCAL" \ --without-MP \ --without-lex \ --without-Boost \ - --without-flint \ --enable-Singular \ --enable-factory \ --enable-libfac \ diff --git a/build/pkgs/tides/SPKG.txt b/build/pkgs/tides/SPKG.txt new file mode 100644 index 00000000000..4b99507b806 --- /dev/null +++ b/build/pkgs/tides/SPKG.txt @@ -0,0 +1,30 @@ += TIDES = + +== Description == + +TIDES is a library for integration of ODE's with high precission. + +== License == + +GPLv3+ + +== SPKG Maintainers == + +* Miguel Marco + +== Upstream Contact == + +* Marcos Rodriguez (marcos@unizar.es) + +== Dependencies == + +* gcc +* mpfr +* gmp + +== Special Update/Build Instructions == + + +minc_tides.patch changes the size of the name of the temporal files, so there is +no problem in systems that use long names. Also solves a bug in the inverse +function \ No newline at end of file diff --git a/build/pkgs/tides/checksums.ini b/build/pkgs/tides/checksums.ini new file mode 100644 index 00000000000..f75423db3a7 --- /dev/null +++ b/build/pkgs/tides/checksums.ini @@ -0,0 +1,4 @@ +tarball=tides-VERSION.tar.gz +sha1=2a70d8e08c364abff3314b9f15022aba2b62c1e7 +md5=200db1104c40f0c4f7bce806c773c87f +cksum=2240108013 diff --git a/build/pkgs/tides/package-version.txt b/build/pkgs/tides/package-version.txt new file mode 100644 index 00000000000..cd5ac039d67 --- /dev/null +++ b/build/pkgs/tides/package-version.txt @@ -0,0 +1 @@ +2.0 diff --git a/build/pkgs/tides/patches/minc_tides.patch b/build/pkgs/tides/patches/minc_tides.patch new file mode 100644 index 00000000000..c8c8d301b42 --- /dev/null +++ b/build/pkgs/tides/patches/minc_tides.patch @@ -0,0 +1,25 @@ +--- libTIDES/minc_tides.c 2014-07-04 14:37:20.861839294 +0200 ++++ b/libTIDES/minc_tides.c 2014-07-04 14:33:03.891862654 +0200 +@@ -40,7 +40,7 @@ + int dense_output = -1, coef_output = 0; + int accepted_steps = 0, rejected_steps = 0; + int ipos = 1; +-char ofname[20]="", cfname[20]=""; ++char ofname[500]="", cfname[500]=""; + FILE *fd, *fc; + + +@@ -347,11 +347,11 @@ + exit(EXIT_FAILURE); + } + if(k == 0) +- ww = 1.e0; ++ ww = p; + else + for(j = 0; j < k; j++) ww -= (u[k-j] *w[j]); + ww /= u[0]; +- return ww*p; ++ return ww; + } + + double exp_mc(double* u, double* v, int k) diff --git a/build/pkgs/tides/spkg-check b/build/pkgs/tides/spkg-check new file mode 100644 index 00000000000..35f04fc8862 --- /dev/null +++ b/build/pkgs/tides/spkg-check @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +cd src +$MAKE check diff --git a/build/pkgs/tides/spkg-install b/build/pkgs/tides/spkg-install new file mode 100644 index 00000000000..cc2938c47f5 --- /dev/null +++ b/build/pkgs/tides/spkg-install @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +cd src + + +for patch in ../patches/*.patch; do + [ -r "$patch" ] || continue # Skip non-existing or non-readable patches + echo "applying '$patch'" + patch -p1 <"$patch" + if [ $? -ne 0 ]; then + echo >&2 "Error applying '$patch'" + exit 1 + fi +done + +./configure --prefix="$SAGE_LOCAL" --libdir="$SAGE_LOCAL/lib" +if [ $? -ne 0 ]; then + echo >&2 "Error configuring TIDES." + exit 1 +fi + +$MAKE +if [ $? -ne 0 ]; then + echo >&2 "Error building TIDES." + exit 1 +fi + +$MAKE -j1 install +if [ $? -ne 0 ]; then + echo >&2 "Error installing TIDES." + exit 1 +fi diff --git a/src/bin/sage-banner b/src/bin/sage-banner index 146899e5bd1..424effdf028 100644 --- a/src/bin/sage-banner +++ b/src/bin/sage-banner @@ -1,5 +1,5 @@ ┌────────────────────────────────────────────────────────────────────┐ -│ Sage Version 6.3.beta4, Release Date: 2014-06-19 │ +│ Sage Version 6.3.beta6, Release Date: 2014-07-19 │ │ Type "notebook()" for the browser-based notebook interface. │ │ Type "help()" for help. │ └────────────────────────────────────────────────────────────────────┘ diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index c9ac6fe4f5e..7cd997b942a 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.3.beta4' -SAGE_RELEASE_DATE='2014-06-19' +SAGE_VERSION='6.3.beta6' +SAGE_RELEASE_DATE='2014-07-19' diff --git a/src/doc/common/conf.py b/src/doc/common/conf.py index ff9efe3e680..28a9402bda4 100644 --- a/src/doc/common/conf.py +++ b/src/doc/common/conf.py @@ -1,5 +1,6 @@ import sys, os, sphinx from sage.env import SAGE_DOC +from datetime import date def get_doc_abspath(path): """ @@ -37,7 +38,7 @@ def get_doc_abspath(path): # General information about the project. project = u"" -copyright = u'2005--2011, The Sage Development Team' +copyright = u"2005--{}, The Sage Development Team".format(date.today().year) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/src/doc/de/tutorial/tour_algebra.rst b/src/doc/de/tutorial/tour_algebra.rst index c7bea0327dc..47b34686658 100644 --- a/src/doc/de/tutorial/tour_algebra.rst +++ b/src/doc/de/tutorial/tour_algebra.rst @@ -156,7 +156,7 @@ berechnen. Die Gleichung :math:`x'+x-1=0` berechnen Sie wie folgt: sage: x = function('x',t) # definiere x als Funktion dieser Variablen sage: DE = diff(x, t) + x - 1 sage: desolve(DE, [x,t]) - (c + e^t)*e^(-t) + (_C + e^t)*e^(-t) Dies benutzt Sages Schnittstelle zu Maxima [Max]_, daher kann sich die Ausgabe ein wenig von anderen Ausgaben in Sage unterscheiden. In diff --git a/src/doc/en/constructions/algebraic_geometry.rst b/src/doc/en/constructions/algebraic_geometry.rst index 9966873979b..f7eff32e57a 100644 --- a/src/doc/en/constructions/algebraic_geometry.rst +++ b/src/doc/en/constructions/algebraic_geometry.rst @@ -144,8 +144,10 @@ Other methods sage: # Here you have all the points : sage: print L [1]: - _[1]=y^2+y+1 - _[2]=x+1 + _[1]=y+1 # 32-bit + _[2]=x+1 # 32-bit + _[1]=y # 64-bit + _[2]=x # 64-bit ... - Another way to compute rational points is to use Singular's diff --git a/src/doc/en/constructions/calculus.rst b/src/doc/en/constructions/calculus.rst index dde6b4bd0dd..2116913bec6 100644 --- a/src/doc/en/constructions/calculus.rst +++ b/src/doc/en/constructions/calculus.rst @@ -99,8 +99,8 @@ Taylor series: sage: g = f0/sinh(k*x)^4 sage: g.taylor(x, 0, 3) -62/945*f0*k^2*x^2 + 11/45*f0 - 2/3*f0/(k^2*x^2) + f0/(k^4*x^4) - sage: maxima(g).powerseries('x',0) - 16*f0*('sum((2^(2*i1-1)-1)*bern(2*i1)*k^(2*i1-1)*x^(2*i1-1)/factorial(2*i1),i1,0,inf))^4 + sage: maxima(g).powerseries('_SAGE_VAR_x',0) # TODO: write this without maxima + 16*_SAGE_VAR_f0*('sum((2^(2*i1-1)-1)*bern(2*i1)*_SAGE_VAR_k^(2*i1-1)*_SAGE_VAR_x^(2*i1-1)/factorial(2*i1),i1,0,inf))^4 Of course, you can view the LaTeX-ed version of this using ``view(g.powerseries('x',0))``. @@ -115,8 +115,8 @@ The Maclaurin and power series of -1/467775*x^10 - 1/37800*x^8 - 1/2835*x^6 - 1/180*x^4 - 1/6*x^2 sage: [bernoulli(2*i) for i in range(1,7)] [1/6, -1/30, 1/42, -1/30, 5/66, -691/2730] - sage: maxima(f).powerseries(x,0) - 'sum((-1)^i2*2^(2*i2-1)*bern(2*i2)*x^(2*i2)/(i2*factorial(2*i2)),i2,1,inf) + sage: maxima(f).powerseries(x,0) # TODO: write this without maxima + 'sum((-1)^i2*2^(2*i2-1)*bern(2*i2)*_SAGE_VAR_x^(2*i2)/(i2*factorial(2*i2)),i2,1,inf) .. index:: pair: calculus; integration @@ -283,9 +283,9 @@ An example, how to solve ODE's symbolically in Sage using the Maxima interface sage: y=function('y',x); desolve(diff(y,x,2) + 3*x == y, dvar = y, ics = [1,1,1]) 3*x - 2*e^(x - 1) sage: desolve(diff(y,x,2) + 3*x == y, dvar = y) - k2*e^(-x) + k1*e^x + 3*x + _K2*e^(-x) + _K1*e^x + 3*x sage: desolve(diff(y,x) + 3*x == y, dvar = y) - (3*(x + 1)*e^(-x) + c)*e^x + (3*(x + 1)*e^(-x) + _C)*e^x sage: desolve(diff(y,x) + 3*x == y, dvar = y, ics = [1,1]).expand() 3*x - 5*e^(x - 1) + 3 diff --git a/src/doc/en/developer/coding_in_other.rst b/src/doc/en/developer/coding_in_other.rst index adbc1a8a5d6..ceb1709ae22 100644 --- a/src/doc/en/developer/coding_in_other.rst +++ b/src/doc/en/developer/coding_in_other.rst @@ -437,11 +437,9 @@ interface to Singular:: 0 [2]: [1]: - 2 # 32-bit - -2 # 64-bit + -2 [2]: - 2 # 32-bit - 1 # 64-bit + -1 [3]: 1 ... diff --git a/src/doc/en/developer/walk_through.rst b/src/doc/en/developer/walk_through.rst index c21d7e7ef34..e52f9bdab86 100644 --- a/src/doc/en/developer/walk_through.rst +++ b/src/doc/en/developer/walk_through.rst @@ -15,6 +15,10 @@ We also have a handy `one-page "cheat sheet" `_ of commonly used git commands that you can print out and leave on your desk. +You can alternatively fork and create a pull request at +`github `_ which will automatically fetch +your code and open a ticket on our trac server. + .. _section-walkthrough-setup-git: diff --git a/src/doc/en/reference/combinat/designs.rst b/src/doc/en/reference/combinat/designs.rst index b18fd7373d4..9fbb579f291 100644 --- a/src/doc/en/reference/combinat/designs.rst +++ b/src/doc/en/reference/combinat/designs.rst @@ -20,6 +20,7 @@ Constructions ../sage/combinat/designs/steiner_quadruple_systems ../sage/combinat/designs/latin_squares ../sage/combinat/designs/orthogonal_arrays + ../sage/combinat/designs/orthogonal_arrays_recursive ../sage/combinat/designs/difference_family ../sage/combinat/designs/database diff --git a/src/doc/en/reference/combinat/index.rst b/src/doc/en/reference/combinat/index.rst index 681522c304f..da6bad48c9d 100644 --- a/src/doc/en/reference/combinat/index.rst +++ b/src/doc/en/reference/combinat/index.rst @@ -26,6 +26,7 @@ Combinatorics sage/combinat/alternating_sign_matrix sage/combinat/composition sage/combinat/core + designs sage/combinat/knutson_tao_puzzles sage/combinat/gelfand_tsetlin_patterns sage/combinat/necklace @@ -190,7 +191,6 @@ Combinatorics root_systems crystals rigged_configurations - designs species **Developer Tools** diff --git a/src/doc/en/reference/functions/index.rst b/src/doc/en/reference/functions/index.rst index 5323981388d..675b352f983 100644 --- a/src/doc/en/reference/functions/index.rst +++ b/src/doc/en/reference/functions/index.rst @@ -13,6 +13,7 @@ Functions sage/functions/orthogonal_polys sage/functions/other sage/functions/special + sage/functions/hypergeometric sage/functions/jacobi sage/functions/bessel sage/functions/exp_integral diff --git a/src/doc/en/reference/game_theory/conf.py b/src/doc/en/reference/game_theory/conf.py new file mode 100644 index 00000000000..ae3b7eaf67f --- /dev/null +++ b/src/doc/en/reference/game_theory/conf.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# +# Sage documentation build configuration file, created by +# sphinx-quickstart on Thu Aug 21 20:15:55 2008. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os +sys.path.append(os.environ['SAGE_DOC']) +from common.conf import * + +# settings for the intersphinx extension: + +ref_src = os.path.join(SAGE_DOC, 'en', 'reference') +ref_out = os.path.join(SAGE_DOC, 'output', 'html', 'en', 'reference') +intersphinx_mapping[ref_out] = None + +for doc in os.listdir(ref_src): + if os.path.exists(os.path.join(ref_src, doc, 'index.rst')): + intersphinx_mapping[os.path.join(ref_out, doc)] = None + +# We use the main document's title, if we can find it. +rst_file = open('index.rst', 'r') +rst_lines = rst_file.read().splitlines() +rst_file.close() + +title = u'' +for i in xrange(len(rst_lines)): + if rst_lines[i].startswith('==') and i > 0: + title = rst_lines[i-1].strip() + break + +# Otherwise, we use this directory's name. +name = os.path.basename(os.path.abspath('.')) +if not title: + title = name.capitalize() +title = title.replace(u'`', u'$') + +# General information about the project. +project = u'Sage Reference Manual: ' + title + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = u'Sage Reference Manual v' + release + ': ' + title + +# A shorter title for the navigation bar. Default is the same as html_title. +html_short_title = title + +# HTML theme (e.g., 'default', 'sphinxdoc'). The pages for the +# reference manual use a custom theme, a slight variant on the 'sage' +# theme, to set the links in the top line. +html_theme = 'sageref' + +# Output file base name for HTML help builder. +htmlhelp_basename = name + +# Grouping the document tree into LaTeX files. List of tuples (source +# start file, target name, title, author, document class +# [howto/manual]). +latex_documents = [ +('index', name + '.tex', project, u'The Sage Development Team', 'manual') +] + +#Ignore all .rst in the _sage subdirectory +exclude_trees = exclude_trees + ['_sage'] + +multidocs_is_master = False diff --git a/src/doc/en/reference/game_theory/index.rst b/src/doc/en/reference/game_theory/index.rst new file mode 100644 index 00000000000..292ce80e82b --- /dev/null +++ b/src/doc/en/reference/game_theory/index.rst @@ -0,0 +1,9 @@ +Game Theory +=========== + +.. toctree:: + :maxdepth: 2 + + sage/game_theory/cooperative_game + +.. include:: ../footer.txt \ No newline at end of file diff --git a/src/doc/en/reference/graphs/index.rst b/src/doc/en/reference/graphs/index.rst index 5beb0896d28..579c664638c 100644 --- a/src/doc/en/reference/graphs/index.rst +++ b/src/doc/en/reference/graphs/index.rst @@ -49,8 +49,7 @@ Hypergraphs :maxdepth: 1 sage/graphs/hypergraph_generators - sage/graphs/hypergraph - + sage/combinat/designs/incidence_structures Libraries of algorithms diff --git a/src/doc/en/reference/groups/index.rst b/src/doc/en/reference/groups/index.rst index 3aeb5b20c14..080f84dfb99 100644 --- a/src/doc/en/reference/groups/index.rst +++ b/src/doc/en/reference/groups/index.rst @@ -12,6 +12,7 @@ Groups sage/groups/finitely_presented sage/groups/finitely_presented_named sage/groups/braid + sage/groups/indexed_free_group sage/groups/raag sage/groups/abelian_gps/abelian_group sage/groups/abelian_gps/values diff --git a/src/doc/en/reference/index.rst b/src/doc/en/reference/index.rst index 8e6c2436734..0472866713b 100644 --- a/src/doc/en/reference/index.rst +++ b/src/doc/en/reference/index.rst @@ -96,6 +96,7 @@ Miscellaneous Mathematics * :doc:`Statistics ` * :doc:`Quantitative Finance ` * :doc:`Coding Theory ` +* :doc:`Game Theory ` Doctesting, Interfaces, Databases, Miscellany --------------------------------------------- diff --git a/src/doc/en/reference/monoids/index.rst b/src/doc/en/reference/monoids/index.rst index 9f984286142..05b8c558d65 100644 --- a/src/doc/en/reference/monoids/index.rst +++ b/src/doc/en/reference/monoids/index.rst @@ -12,6 +12,7 @@ finite number of indeterminates. sage/monoids/free_monoid_element sage/monoids/free_abelian_monoid sage/monoids/free_abelian_monoid_element + sage/monoids/indexed_free_monoid sage/monoids/string_monoid_element sage/monoids/string_monoid diff --git a/src/doc/en/reference/numerical/index.rst b/src/doc/en/reference/numerical/index.rst index b331ca23637..911c20a9961 100644 --- a/src/doc/en/reference/numerical/index.rst +++ b/src/doc/en/reference/numerical/index.rst @@ -8,6 +8,7 @@ Numerical Optimization sage/numerical/mip sage/numerical/linear_functions sage/numerical/optimize + sage/numerical/interactive_simplex_method LP Solver backends ------------------ diff --git a/src/doc/en/reference/structure/index.rst b/src/doc/en/reference/structure/index.rst index b276bdce7f8..f7149a821eb 100644 --- a/src/doc/en/reference/structure/index.rst +++ b/src/doc/en/reference/structure/index.rst @@ -21,6 +21,7 @@ Basic Structures sage/structure/mutability sage/structure/sequence sage/structure/element_wrapper + sage/structure/indexed_generators sage/structure/global_options sage/sets/cartesian_product diff --git a/src/doc/en/thematic_tutorials/lie/affine_finite_crystals.rst b/src/doc/en/thematic_tutorials/lie/affine_finite_crystals.rst index ee56d5293b4..a3e0cd141a0 100644 --- a/src/doc/en/thematic_tutorials/lie/affine_finite_crystals.rst +++ b/src/doc/en/thematic_tutorials/lie/affine_finite_crystals.rst @@ -120,7 +120,7 @@ In Sage this can be obtained via:: sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) sage: G = K.digraph() - sage: view(G, pdflatex=True, tightpage=True) # optional - dot2tex graphviz + sage: view(G, tightpage=True) # optional - dot2tex graphviz Types `D_n^{(1)}`, `B_n^{(1)}`, `A_{2n-1}^{(2)}` @@ -168,7 +168,8 @@ only holds in the ranges `1\le r\le n-2` for type `D_n^{(1)}`, and sage: K = crystals.KirillovReshetikhin(['D',6,1],4,2) sage: K.classical_decomposition() - The crystal of tableaux of type ['D', 6] and shape(s) [[], [1, 1], [1, 1, 1, 1], [2, 2], [2, 2, 1, 1], [2, 2, 2, 2]] + The crystal of tableaux of type ['D', 6] and shape(s) + [[], [1, 1], [1, 1, 1, 1], [2, 2], [2, 2, 1, 1], [2, 2, 2, 2]] For type `B_n^{(1)}` and `r=n`, one needs to be aware that `\omega_n` is a spin weight and hence corresponds in the partition language to a @@ -471,6 +472,32 @@ crystal are labelled by tuples which specify their nonzero `\phi_i(b)` and :align: center +Single column KR crystals +------------------------- + +A single column KR crystal is `B^{r,1}` for any `r \in I_0`. + +In [LNSSS14I]_ and [LNSSS14II]_, it was shown that single column KR +crystals can be constructed by projecting level 0 crystals of LS paths onto +the classical weight lattice. We first verify that we do get an isomorphic +crystal for `B^{1,1}` in type `E_6^{(1)}`:: + + sage: K = crystals.KirillovReshetikhin(['E',6,1], 1,1) + sage: K2 = crystals.kirillov_reshetikhin.LSPaths(['E',6,1], 1,1) + sage: K.digraph().is_isomorphic(K2.digraph(), edge_labels=True) + True + +Here is an example in `E_8^{(1)}` and we calculate its +classical decomposition:: + + sage: K = crystals.kirillov_reshetikhin.LSPaths(['E',8,1], 8,1) + sage: K.cardinality() + 249 + sage: L = [x for x in K if x.is_highest_weight([1,2,3,4,5,6,7,8])] + sage: map(lambda x: x.weight(), L) + [-2*Lambda[0] + Lambda[8], 0] + + Applications ------------ diff --git a/src/doc/en/thematic_tutorials/lie/affine_hw_crystals.rst b/src/doc/en/thematic_tutorials/lie/affine_hw_crystals.rst index e7bb2a66d2c..770234ac9d8 100644 --- a/src/doc/en/thematic_tutorials/lie/affine_hw_crystals.rst +++ b/src/doc/en/thematic_tutorials/lie/affine_hw_crystals.rst @@ -45,11 +45,6 @@ associated digraph:: :scale: 50 :align: center -REFERENCES: - -.. [L1995] P. Littelmann. *Paths and root operators in representation theory*. - Ann. of Math. (2) 142 (1995), no. 3, 499-525. - The Littelmann path model also lends itself as a model for level zero crystals which are bi-infinite. To cut out a slice of these crystals, one can use the direction option in subcrystal:: @@ -67,3 +62,44 @@ can use the direction option in subcrystal:: :scale: 50 :align: center +Modified Nakajima monomials +--------------------------- + +Modified Nakajima monomials have also been implemented in Sage and models +highest weight crystals in all symmetrizable types. The elements are given +in terms of commuting variables `Y_i(n)` where `i \in I` and +`n \in \ZZ_{\geq 0}`. For more information on the modified Nakajima +monomials, see [KKS2007]_. + +We give an example in affine type and verify that up to depth 3, it agrees +with the Littelmann path model:: + + sage: La = RootSystem(['C',3,1]).weight_space().fundamental_weights() + sage: LS = crystals.LSPaths(2*La[1]+La[2]) + sage: SL = LS.subcrystal(max_depth=3) + sage: GL = LS.digraph(subset=SL) + + sage: La = RootSystem(['C',3,1]).weight_lattice().fundamental_weights() + sage: M = crystals.NakajimaMonomials(['C',3,1], 2*La[1]+La[2]) + sage: SM = M.subcrystal(max_depth=3) + sage: GM = M.digraph(subset=SM) + sage: GL.is_isomorphic(GM, edge_labels=True) + True + +Now we do an example of a simply-laced (so symmetrizable) hyperbolic +type `H_1^{(4)}`, which comes from the complete graph on 4 vertices:: + + sage: CM = CartanMatrix([[2, -1, -1,-1],[-1,2,-1,-1],[-1,-1,2,-1],[-1,-1,-1,2]]); CM + [ 2 -1 -1 -1] + [-1 2 -1 -1] + [-1 -1 2 -1] + [-1 -1 -1 2] + sage: La = RootSystem(CM).weight_lattice().fundamental_weights() + sage: M = crystals.NakajimaMonomials(CM, La[0]) + sage: SM = M.subcrystal(max_depth=4) + sage: GM = M.digraph(subset=SM) # long time + +.. image:: ../media/hyperbolic_La0.png + :scale: 20 + :align: center + diff --git a/src/doc/en/thematic_tutorials/lie/bibliography.rst b/src/doc/en/thematic_tutorials/lie/bibliography.rst index 2e27e4a37fd..0af8551304b 100644 --- a/src/doc/en/thematic_tutorials/lie/bibliography.rst +++ b/src/doc/en/thematic_tutorials/lie/bibliography.rst @@ -2,6 +2,9 @@ Bibliography ============ +.. [Bourbaki46] Nicolas Bourbaki. *Lie Groups and Lie Algebras: Chapters 4-6*. + Springer, reprint edition, 1998. + .. [BumpNakasuji2010] D. Bump and M. Nakasuji. Casselman's basis of Iwahori vectors and the Bruhat order. arXiv:1002.2996, http://arxiv.org/abs/1002.2996. @@ -66,6 +69,9 @@ Bibliography Affine structures and a tableau model for E_6 crystals *J. Algebra*, 324:2512-2542, 2010 +.. [Kac] Victor G. Kac. *Infinite Dimensional Lie algebras* + Cambridge University Press, third edition, 1994. + .. [Kashiwara1995] M. Kashiwara. On crystal bases. Representations of groups (Banff, AB, 1994), 155--197, CMS Conference Proceedings, 16, American Mathematical Society, Providence, RI, 1995. @@ -90,10 +96,25 @@ Bibliography Affine crystals and vertex models. *Int. J. Mod. Phys.* A 7 (suppl. 1A): 449--484, 1992. +.. [LNSSS14I] C. Lenart, S. Naito, D. Sagaki, A. Schilling, and M. Shimozono. + A uniform model for for Kirillov-Reshetikhin crystals I: Lifting the + parabolic quantum Bruhat graph. (2014) :arxiv:`1211.2042` + +.. [LNSSS14II] C. Lenart, S. Naito, D. Sagaki, A. Schilling, and M. Shimozono. + A uniform model for for Kirillov-Reshetikhin crystals II: Alcove model, + path model, and `P = X`. (2014) :arxiv:`1402.2203` + +.. [L1995] P. Littelmann. *Paths and root operators in representation theory*. + Ann. of Math. (2) 142 (1995), no. 3, 499-525. + .. [McKayPatera1981] W. G. McKay and J. Patera. *Tables of Dimensions, Indices and Branching Rules for Representations of Simple Lie Algebras*. Marcel Dekker, 1981. +.. [KKS2007] S.-J. Kang, J.-A. Kim, and D.-U. Shin. + *Modified Nakajima monomials and the crystal* `B(\infty)`. + J. Algebra, **308** (2007), 524-535. + .. [OkadoSchilling2008] M. Okado, A.Schilling. Existence of crystal bases for Kirillov--Reshetikhin crystals for nonexceptional types. *Representation Theory* 12:186--207, 2008. @@ -118,4 +139,4 @@ Bibliography J. Algebra 122 (1989), no. 2, 299–322. .. [Testerman1992] Testerman, Donna M. The construction of the maximal A1's in - the exceptional algebraic groups. Proc. Amer. Math. Soc. 116 (1992), no. 3, 635–644. \ No newline at end of file + the exceptional algebraic groups. Proc. Amer. Math. Soc. 116 (1992), no. 3, 635–644. diff --git a/src/doc/en/thematic_tutorials/lie/branching_rules.rst b/src/doc/en/thematic_tutorials/lie/branching_rules.rst index ddca38c6668..96eedbfb939 100644 --- a/src/doc/en/thematic_tutorials/lie/branching_rules.rst +++ b/src/doc/en/thematic_tutorials/lie/branching_rules.rst @@ -1,5 +1,7 @@ .. linkall +.. _branch_rules: + ------------------------------------- Maximal Subgroups and Branching Rules ------------------------------------- @@ -54,7 +56,7 @@ the branching rule that we may create as follows:: The name "symmetric" of this branching rule will be explained further later, but it means that `Sp(4)` is the fixed subgroup of an involution of `Sl(4)`. -Here ``A3`` and ``C2`` are the Cartan Types of the groups +Here ``A3`` and ``C2`` are the Cartan types of the groups `G=SL(4)` and `H=Sp(4)`. Now we may see how representations of `SL(4)` decompose @@ -89,7 +91,7 @@ factors through `Sp(4)\times Sp(4)`, while the other factors through `SL(4)`. To check that the embeddings are not conjugate, we branch a (randomly chosen) representation. Observe that we do not have to build the intermediate -WeylCharacterRings. +:class:`Weyl character rings `. :: @@ -182,7 +184,7 @@ Maximal subgroups Sage has a database of maximal subgroups for every simple Cartan type of rank `\le 8`. You may access this with the -``maximal_subgroups`` method of the WeylCharacter Ring:: +``maximal_subgroups`` method of the Weyl character ring:: sage: E7=WeylCharacterRing("E7",style="coroots") sage: E7.maximal_subgroups() @@ -250,6 +252,8 @@ rule, we compose the given one with this automorphism:: sage: b1=branching_rule("E6","E6","automorphic")*b; b1 composite branching rule E6 => (automorphic) E6 => (miscellaneous) A2xG2 +.. _levi_branch_rules: + Levi subgroups -------------- @@ -268,9 +272,9 @@ For example, here is the A3 Dynkin diagram: 1 2 3 A3 -We see that we may remove the node 3 and obtain A2, or the node 2 and -obtain A1xA1. These correspond to the Levi subgroups `GL(3)` and -`GL(2) \times GL(2)` of `GL(4)`. +We see that we may remove the node 3 and obtain `A_2`, or the node 2 +and obtain `A_1 \times A_1`. These correspond to the Levi subgroups +`GL(3)` and `GL(2) \times GL(2)` of `GL(4)`. Let us construct the irreducible representations of `GL(4)` and branch them down to these down to @@ -315,11 +319,12 @@ versus SL(4) \to SL(2) \times SL(2). -Consider the representation `A3(0,1,0)`, which is the six dimensional exterior -square. In the coroot notation, the restriction contained two copies of the -trivial representation, ``2*A1xA1(0,0)``. The other way, we had instead three -distinct representations in the restriction, namely ``A1xA1(1,1,0,0)`` and -``A1xA1(0,0,1,1)``, that is, `\det \otimes 1` and `1 \otimes \det`. +Consider the representation ``A3(0,1,0)``, which is the six dimensional +exterior square. In the coroot notation, the restriction contained two +copies of the trivial representation, ``2*A1xA1(0,0)``. The other way, +we had instead three distinct representations in the restriction, namely +``A1xA1(1,1,0,0)`` and ``A1xA1(0,0,1,1)``, that is, +`\det \otimes 1` and `1 \otimes \det`. The Levi subgroup ``A1xA1`` is actually not maximal. Indeed, we may factor the embedding: @@ -342,8 +347,8 @@ we could accomplish the branching in two steps, thus:: [A1xA1(1,0) + A1xA1(0,1), 2*A1xA1(0,0) + A1xA1(1,1), A1xA1(1,0) + A1xA1(0,1)] As you can see, we've redone the branching rather circuitously this -way, making use of the branching rules ``A3->C2`` and ``B2->D2``, and -two accidental isomorphisms ``C2=B2`` and ``D2=A1xA1``. It is much +way, making use of the branching rules ``A3 -> C2`` and ``B2 -> D2``, and +two accidental isomorphisms ``C2 = B2`` and ``D2 = A1xA1``. It is much easier to go in one step using ``rule="levi"``, but reassuring that we get the same answer! @@ -360,10 +365,10 @@ diagram that we obtain the Dynkin diagram of a subgroup. For example:: 1 2 0 G2~ -Observe that by removing the 1 node that we obtain an A2 Dynkin -diagram. Therefore the exceptional group G2 contains a copy of -`SL(3)`. We branch the two representations of G2 corresponding to the -fundamental weights to this copy of A2:: +Observe that by removing the 1 node that we obtain an `A_2` Dynkin +diagram. Therefore the exceptional group `G_2` contains a copy of +`SL(3)`. We branch the two representations of `G_2` corresponding to the +fundamental weights to this copy of `A_2`:: sage: G2 = WeylCharacterRing("G2", style="coroots") sage: A2 = WeylCharacterRing("A2", style="coroots") @@ -372,7 +377,7 @@ fundamental weights to this copy of A2:: sage: [G2(f).branch(A2, rule="extended") for f in G2.fundamental_weights()] [A2(0,0) + A2(0,1) + A2(1,0), A2(0,1) + A2(1,0) + A2(1,1)] -The two representations of G2, of degrees 7 and 14 respectively, are +The two representations of `G_2`, of degrees 7 and 14 respectively, are the action on the octonions of trace zero and the adjoint representation. @@ -410,7 +415,8 @@ handled as follows:: sage: branching_rule("D4","B3",rule="symmetric") symmetric branching rule D4 => B3 -If `G = \hbox{SO}(r+s)` then `G` has a subgroup `\hbox{SO}(r) \times \hbox{SO}(s)`. This +If `G = \hbox{SO}(r+s)` then `G` has a subgroup +`\hbox{SO}(r) \times \hbox{SO}(s)`. This lifts to an embedding of the universal covering groups .. MATH:: @@ -468,7 +474,7 @@ Similarly we have embeddings:: ['D',k] x ['B',l] --> ['B',k+l] These are also of extended type. For example consider the embedding of -``D3xB2->B5``. Here is the ``B5`` extended Dynkin diagram:: +``D3xB2 -> B5``. Here is the ``B5`` extended Dynkin diagram:: O 0 | @@ -517,7 +523,7 @@ may or may not be implemented in Sage. However if it is not implemented, it may be constructed as a composition of two branching rules. -For example, prior to Sage-6.1 ``branching_rule("E6","A5","levi") +For example, prior to Sage-6.1 ``branching_rule("E6","A5","levi")`` returned a not-implemented error and the advice to branch to ``A5xA1``. And we can see from the extended Dynkin diagram of `E_6` that indeed `A_5` is not a maximal subgroup, since removing node 2 @@ -556,7 +562,7 @@ construct the branching rule to `A_5` we may proceed as follows:: For more detailed information use verbose=True -Note that it is not necessary to construct the WeylCharacterRing +Note that it is not necessary to construct the Weyl character ring for the intermediate group ``A5xA1``. This last example illustrates another common problem: @@ -655,7 +661,7 @@ fall between the cracks. Mostly these involve maximal subgroups of fairly small rank. The rule ``rule="plethysm"`` is a powerful rule that includes any -branching rule from types A, B, C or D as a special case. Thus it +branching rule from types `A`, `B`, `C` or `D` as a special case. Thus it could be used in place of the above rules and would give the same results. However, it is most useful when branching from `G` to a maximal subgroup `H` such that `rank(H) < rank(G)-1`. @@ -681,7 +687,7 @@ homomorphism by invoking its character, to be called ``chi``:: This confirms that the character has degree 6 and is symplectic, so it corresponds to a homomorphism `SL(2) \to Sp(6)`, and there is a -corresponding branching rule ``C3 => A1``:: +corresponding branching rule ``C3 -> A1``:: sage: A1 = WeylCharacterRing("A1", style="coroots") sage: C3 = WeylCharacterRing("C3", style="coroots") @@ -768,10 +774,10 @@ the other hand, the assumption of characteristic `p` is not important for Theorems G.1 and A.1, which describe the torus embeddings, hence contain enough information to compute the branching rule. There -are other ways of embedding ``G_2`` or ``A_2`` into -``E_6``. These may embeddings be characterized by the +are other ways of embedding `G_2` or `A_2` into +`E_6`. These may embeddings be characterized by the condition that the two 27-dimensional representations of -``E_6`` restrict irreducibly to ``G_2`` or ``A_2``. +`E_6` restrict irreducibly to `G_2` or `A_2`. Their images are maximal subgroups. The remaining rules come about as follows. Let `G` be @@ -786,7 +792,7 @@ of subgroups Then the centralizer of `H` is `A_1`, `A_2`, `C_3`, `F_4` (if `H=G_2`) or `A_1` (if `G=E_7` and `H=F_4`). This gives us five of the cases. -Regarding the branching rule ``E_6\to G_2\times A_2``, Rubenthaler +Regarding the branching rule `E_6 \to G_2 \times A_2`, Rubenthaler [Rubenthaler2008]_ describes the embedding and applies it in an interesting way. @@ -902,13 +908,13 @@ you can write your own branching rules. As an example, let us consider how to implement the branching rule ``A3 -> C2``. Here ``H = C2 = Sp(4)`` embedded as a subgroup in -``A3 = GL(4).`` The Cartan subalgebra `\hbox{Lie}(U)` consists of +``A3 = GL(4)``. The Cartan subalgebra `\hbox{Lie}(U)` consists of diagonal matrices with eigenvalues ``u1, u2, -u2, -u1``. Then ``C2.space()`` is the two dimensional vector spaces consisting of the linear functionals ``u1`` and ``u2`` on ``U``. On the other hand `Lie(T) = \mathbf{R}^4`. A convenient way to see the restriction is to think of it as the adjoint of the map ``[u1,u2] -> [u1,u2,-u2,-u1]``, -that is, ``[x0,x1,x2,x3] -> [x0-x3,x1-x2].`` Hence we may encode the +that is, ``[x0,x1,x2,x3] -> [x0-x3,x1-x2]``. Hence we may encode the rule:: def brule(x): @@ -929,7 +935,7 @@ Let us check that this agrees with the built-in rule:: C2(0,0) + C2(1,1) Although this works, it is better to make the rule -into an element of the BranchingRule class, as follows. +into an element of the :class:`BranchingRule` class, as follows. :: @@ -956,7 +962,7 @@ effect may be computed using the branching rule code:: sage: A4(1,0,1,0).branch(A4,rule="automorphic") A4(0,1,0,1) -In the special case where `G=D4`, the Dynkin diagram has +In the special case where ``G=D4``, the Dynkin diagram has extra symmetries, and these correspond to outer automorphisms of the group. These are implemented as the ``"triality"`` branching rule:: @@ -1003,9 +1009,10 @@ permuted by triality:: D4(0,0,0,1) By contrast, ``rule="automorphic"`` simply interchanges the two -spin representations, as it always does in Type D:: +spin representations, as it always does in type `D`:: sage: D4(0,0,0,1).branch(D4,rule="automorphic") D4(0,0,1,0) sage: D4(0,0,1,0).branch(D4,rule="automorphic") D4(0,0,0,1) + diff --git a/src/doc/en/thematic_tutorials/lie/crystals.rst b/src/doc/en/thematic_tutorials/lie/crystals.rst index 7591d78afa8..a9c2f25858b 100644 --- a/src/doc/en/thematic_tutorials/lie/crystals.rst +++ b/src/doc/en/thematic_tutorials/lie/crystals.rst @@ -2,6 +2,11 @@ Classical Crystals ================== +A classical crystal is one coming from the finite (classical) types +`A_r, B_r, C_r, D_r, E_{6,7,8}, F_4`, and `G_2`. Here we +describe some background before going into the general theory of crystals +and the type dependent combinatorics. + Tableaux and representations of `GL(n)` --------------------------------------- @@ -22,8 +27,8 @@ columns are strictly increasing. Thus :scale: 75 :align: center -is a semistandard Young tableau. Sage has a Tableau class, and you may -create this tableau as follows:: +is a semistandard Young tableau. Sage has a :class:`Tableau` class, +and you may create this tableau as follows:: sage: T = Tableau([[1,2,2], [2,3]]); T [[1, 2, 2], [2, 3]] @@ -43,9 +48,9 @@ with `\lambda = (-1, \dots, -1)`, which is a dominant weight but not a partition, and the character is not a polynomial function on `\hbox{Mat}_n(\mathbf{C})`. -**Theorem** (Littlewood) If `\lambda` is a partition, then the number -of Semi-Standard Young Tableaux with shape `\lambda` and entries in -`{1,2,\dots,r+1}` is the dimension of `\pi_\lambda`. +**Theorem** [Littlewood] If `\lambda` is a partition, then the number +of semi-standard Young tableaux with shape `\lambda` and entries in +`\{1,2,\ldots,r+1\}` is the dimension of `\pi_\lambda`. For example, if `\lambda = (3,2)` and `r = 2`, then we find 15 tableaux with shape `\lambda` and entries in `\{1,2,3\}`: @@ -76,7 +81,7 @@ in the tableaux. Then the multiplicity of `\mu` in the character \chi_\lambda(\mathbf{z}) = \sum_T \mathbf{z}^{\hbox{wt}(T)} where the sum is over all semi-standard Young tableaux of shape -`\lambda` that have entries in `{1,2,\dots,r+1}`. +`\lambda` that have entries in `\{1, 2, \ldots, r+1\}`. Frobenius-Schur Duality @@ -119,7 +124,7 @@ Let us say that a semistandard Young tableau `T` of shape `\lambda\vdash k` is *standard* if `T` contains each entry `1,2,\dots,k` exactly once. Thus both rows and columns are strictly increasing. -**Theorem** (Young, 1927) The degree of `\pi_\lambda` is the number of +**Theorem** [Young, 1927] The degree of `\pi_\lambda` is the number of standard tableaux of shape `\lambda`. Now let us consider the implications of Frobenius-Schur duality. @@ -196,8 +201,8 @@ Both the combinatorial fact and the decomposition of `\mathbf{C}[S_k]` show that the number of pairs of standard tableaux of size `k` and the same shape equals `k!`. -- The third combinatorial fact is analogous to the decomposition of the ring of polynomial - functions on `\hbox{Mat}(n, \mathbf{C})` on which +- The third combinatorial fact is analogous to the decomposition of the + ring of polynomial functions on `\hbox{Mat}(n, \mathbf{C})` on which `GL(n, \mathbf{C}) \times GL(n, \mathbf{C})` acts by `(g_1, g_2)f(X) = f({^t g_1}X g_2)`. The polynomial ring decomposes into the direct sum of @@ -212,8 +217,8 @@ Taking traces gives the *Cauchy identity*: where `x_i` are the eigenvalues of `g_1` and `y_j` are the eigenvalues of `g_2`. The sum is over all partitions `\lambda`. -- This is analogous to the decomposition of the exterior algebra over - `\hbox{Mat}(n, \mathbf{C})`. +- The last combinatorial fact is analogous to the decomposition of + the exterior algebra over `\hbox{Mat}(n, \mathbf{C})`. Taking traces gives the *dual Cauchy identity*: @@ -256,17 +261,14 @@ a limiting structure can still be detected. This is the Kashiwara's crystal bases have a combinatorial structure that sheds light even on purely combinatorial constructions on tableaux that predated quantum groups. It gives a good generalization to other -Cartan types. - -We will not make the most general definition of a crystal. See the -references for a more general definition. Let `\Lambda` be the weight -lattice of a classical Cartan type. +Cartan types (or more generally to Kac-Moody algebras). +Let `\Lambda` be the weight lattice of a Cartan type with root system `\Phi`. We now define a *crystal* of type `\Phi`. Let `\mathcal{B}` be a set, and let `0 \notin \mathcal{B}` be an auxiliary element. For each index `1 \le i \le r` we assume there given maps `e_i, f_i : \mathcal{B} \longrightarrow \mathcal{B} \cup \{0\}`, maps -`\varepsilon_i, \phi_i : \mathcal{B} \longrightarrow \mathbf{Z}` and a +`\varepsilon_i, \varphi_i : \mathcal{B} \longrightarrow \mathbf{Z}` and a map `\hbox{wt} : \mathcal{B} \longrightarrow \Lambda` satisfying certain assumptions, which we now describe. It is assumed that if `x, y \in \mathcal{B}` then `e_i (x) = y` if and only if @@ -277,13 +279,13 @@ certain assumptions, which we now describe. It is assumed that if \hbox{wt} (y) = \hbox{wt} (x) + \alpha_i, \qquad \varepsilon_i (x) = \varepsilon_i (y) + 1, - \qquad \phi_i (x) = \phi_i (y) - 1. + \qquad \varphi_i (x) = \varphi_i (y) - 1. Moreover, we assume that .. MATH:: - \phi_i (x) - \varepsilon_i (x) + \varphi_i (x) - \varepsilon_i (x) = \left\langle \hbox{wt} (x), \alpha^{\vee}_i \right\rangle @@ -292,9 +294,9 @@ for all `x \in \mathcal{B}`. We call a crystal *regular* if it satisfies the additional assumption that `\varepsilon_i(v)` is the number of times that `e_i` may be applied to `v`, and that `\phi_i(v)` is the number of times that `f_i` may be applied. That is, -`\phi_i (x) = \max \{k | f_i^k x \neq 0\}` and `\varepsilon_i (x) = \max \{k -| e_i^k (x) \neq 0\}`. -Kashiwara also allows `\varepsilon_i` and `\phi_i` to take the value `-\infty`. +`\varphi_i (x) = \max \{k | f_i^k x \neq 0\}` and `\varepsilon_i (x) = +\max \{k | e_i^k (x) \neq 0\}`. Kashiwara also allows `\varepsilon_i` +and `\varphi_i` to take the value `-\infty`. .. NOTE:: @@ -314,7 +316,8 @@ the irreducible character with highest weight `\lambda`, as in :ref:`representations`. The crystal `\mathcal{B}_\lambda` is not uniquely characterized by the -properties that we have stated so far. For Cartan types `A, D, E` it may +properties that we have stated so far. For Cartan types `A, D, E` +(more generally, any simply-laced type) it may be characterized by these properties together with certain other *Stembridge axioms*. We will take it for granted that there is a unique "correct" crystal `\mathcal{B}_\lambda` and discuss how these @@ -446,7 +449,7 @@ For each of the classical Cartan types there is a *standard crystal* up by taking tensor products and extracting constituent irreducible crystals. This procedure is sufficient for Cartan types `A_r` and `C_r`. For types `B_r` and `D_r` the standard crystal must be -supplemented with a *spin crystal*. See [KashiwaraNakashima1994]_ or +supplemented with *spin crystals*. See [KashiwaraNakashima1994]_ or [HongKang2002]_ for further details. Here is the standard crystal of type `A_r`. @@ -455,7 +458,7 @@ Here is the standard crystal of type `A_r`. :scale: 75 :align: center -You may create the crystal, and work with it as follows:: +You may create the crystal and work with it as follows:: sage: C = crystals.Letters("A6") sage: v0 = C.highest_weight_vector(); v0 @@ -479,7 +482,7 @@ There is, additionally, a spin crystal for `B_r`, corresponding to the `2^r`-dimensional spin representation. We will not draw it, but we will describe it. Its elements are vectors `\epsilon_1\otimes\cdots\otimes\epsilon_r`, where each ``spin`` -`\epsilon_i=\pm 1`. +`\epsilon_i=\pm`. If `i \varepsilon_i (y)$},\\ - x \otimes f_i (y) & \text{if $\phi_i (x) \le \varepsilon_i (y)$}, + f_i (x) \otimes y & \text{if $\varphi_i (x) > \varepsilon_i (y)$},\\ + x \otimes f_i (y) & \text{if $\varphi_i (x) \le \varepsilon_i (y)$}, \end{cases} and @@ -596,23 +598,24 @@ and e_i (x \otimes y) = \begin{cases} - e_i (x) \otimes y & \text{if $\phi_i (x) \ge \varepsilon_i (y)$},\\ - x \otimes e_i (y) & \text{if $\phi_i (x) < \varepsilon_i (y)$}. + e_i (x) \otimes y & \text{if $\varphi_i (x) \ge \varepsilon_i (y)$},\\ + x \otimes e_i (y) & \text{if $\varphi_i (x) < \varepsilon_i (y)$}. \end{cases} It is understood that `x \otimes 0 = 0 \otimes x = 0`. We also define: .. MATH:: - \phi_i (x \otimes y) + \varphi_i (x \otimes y) = - \max (\phi_i (y), \phi_i (x) + \phi_i (y) - \varepsilon_i (y)), + \max (\varphi_i (y), \varphi_i (x) + \varphi_i (y) - \varepsilon_i (y)), .. MATH:: \varepsilon_i (x \otimes y) = - \max (\varepsilon_i (x), \varepsilon_i (x) + \varepsilon_i (y) - \phi_i (x)) . + \max (\varepsilon_i (x), \varepsilon_i (x) + \varepsilon_i (y) + - \varphi_i (x)) . Alternative definition @@ -629,8 +632,8 @@ we denote the ordered pair `(y, x)` with `y \in \mathcal{B}` and f_i (x \otimes y) = \begin{cases} - f_i (x) \otimes y & \text{if $\phi_i (y) \le \varepsilon_i (x)$},\\ - x \otimes f_i (y) & \text{if $\phi_i (y) > \varepsilon_i (x)$}, + f_i (x) \otimes y & \text{if $\varphi_i (y) \le \varepsilon_i (x)$},\\ + x \otimes f_i (y) & \text{if $\varphi_i (y) > \varepsilon_i (x)$}, \end{cases} and @@ -640,23 +643,24 @@ and e_i (x \otimes y) = \begin{cases} - e_i (x) \otimes y & \text{if $\phi_i (y) < \varepsilon_i (x)$},\\ - x \otimes e_i (y) & \text{if $\phi_i (y) \ge \varepsilon_i (x)$}. + e_i (x) \otimes y & \text{if $\varphi_i (y) < \varepsilon_i (x)$},\\ + x \otimes e_i (y) & \text{if $\varphi_i (y) \ge \varepsilon_i (x)$}. \end{cases} It is understood that `y \otimes 0 = 0 \otimes y = 0`. We also define .. MATH:: - \phi_i (x \otimes y) + \varphi_i (x \otimes y) = - \max (\phi_i (x), \phi_i (y) + \phi_i (x) - \varepsilon_i (x)), + \max (\varphi_i (x), \varphi_i (y) + \varphi_i (x) - \varepsilon_i (x)), .. MATH:: \varepsilon_i (x \otimes y) = - \max (\varepsilon_i (y), \varepsilon_i (y) + \varepsilon_i (x) - \phi_i (y)). + \max (\varepsilon_i (y), \varepsilon_i (y) + \varepsilon_i (x) + - \varphi_i (y)). The tensor product is associative: `(x \otimes y) \otimes z \mapsto x \otimes(y \otimes z)` is an @@ -675,6 +679,13 @@ tensor product construction is *a priori* asymmetrical, both constructions produce isomorphic crystals, and in particular Sage's crystals of tableaux are identical to Kashiwara's. +.. NOTE:: + + Using abstract crystals (i.e. they satisfy the axioms but do not arise + from a representation of `U_q(\mathfrak{g})`), we can construct crystals + `\mathcal{B}, \mathcal{C}` such that `\mathcal{B} \otimes \mathcal{C} + \neq \mathcal{C} \otimes \mathcal{B}` (of course, using the + same convention). Tensor products of crystals in Sage ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -751,8 +762,8 @@ Crystals of tableaux as tensor products of crystals Sage implements the :class:`~sage.combinat.crystals.tensor_product.CrystalOfTableaux` as a subcrystal of a tensor product of the -:class:`~sage.combinat.crystals.letters.CrystalOfLetters`. You can see how -its done as follows:: +:class:`~sage.combinat.crystals.letters.ClassicalCrystalOfLetters`. +You can see how its done as follows:: sage: T = crystals.Tableaux("A4",shape=[3,2]) sage: v = T.highest_weight_vector().f(1).f(2).f(3).f(2).f(1).f(4).f(2).f(3); v @@ -762,8 +773,8 @@ its done as follows:: We've looked at the internal representation of `v`, where it is represented as an element of the fourth tensor power of the -:class:`~sage.combinat.crystals.letters.CrystalOfLetters`. We see -that the tableau: +:class:`~sage.combinat.crystals.letters.ClassicalCrystalOfLetters`. +We see that the tableau: .. MATH:: @@ -959,6 +970,9 @@ We see that the type `D_4` crystal indeed decomposes into two type `A_3` compone :scale: 75 :align: center +For more on branching rules, see :ref:`branch_rules` or +:ref:`levi_branch_rules` for specifics on the Levi subgroups. + Subcrystals ----------- diff --git a/src/doc/en/thematic_tutorials/lie/iwahori_hecke_algebra.rst b/src/doc/en/thematic_tutorials/lie/iwahori_hecke_algebra.rst index 3e559be46be..4083f029d66 100644 --- a/src/doc/en/thematic_tutorials/lie/iwahori_hecke_algebra.rst +++ b/src/doc/en/thematic_tutorials/lie/iwahori_hecke_algebra.rst @@ -2,13 +2,13 @@ Iwahori Hecke Algebras ---------------------- -The Iwahori Hecke algebra is defined in [Iwahori1964]_. In that -original paper, the algebra occurs as the convolution ring of -functions on a `p`-adic group that are compactly supported and -invariant both left and right by the Iwahori subgroup. However Iwahori -determined its structure in terms of generators and relations, and it -turns out to be a deformation of the group algebra of the affine Weyl -group. +The :class:`Iwahori Hecke algebra ` is defined +in [Iwahori1964]_. In that original paper, the algebra occurs as the +convolution ring of functions on a `p`-adic group that are compactly +supported and invariant both left and right by the Iwahori subgroup. +However Iwahori determined its structure in terms of generators and +relations, and it turns out to be a deformation of the group algebra +of the affine Weyl group. Once the presentation is found, the Iwahori Hecke algebra can be defined for any Coxeter group. It depends on a parameter `q` which in @@ -48,13 +48,14 @@ varieties, where they are used in the definition of the Kazhdan-Lusztig polynomials. They appear in connection with quantum groups, and in Jones's original paper on the Jones polynomial. -Here is how to create an Iwahori Hecke algebra:: +Here is how to create an Iwahori Hecke algebra (in the `T` basis):: sage: R. = PolynomialRing(ZZ) - sage: H = IwahoriHeckeAlgebra("B3",q).T(); H + sage: H = IwahoriHeckeAlgebra("B3",q) + sage: T = H.T(); T Iwahori-Hecke algebra of type B3 in q,-1 over Univariate Polynomial Ring in q over Integer Ring in the T-basis - sage: T1,T2,T3 = H.algebra_generators() + sage: T1,T2,T3 = T.algebra_generators() sage: T1*T1 (q-1)*T[1] + q @@ -66,7 +67,8 @@ You may coerce a Weyl group element into the Iwahori Hecke algebra:: sage: W = WeylGroup("G2",prefix="s") sage: [s1,s2] = W.simple_reflections() sage: P. = LaurentPolynomialRing(QQ) - sage: H = IwahoriHeckeAlgebra(W,q).T() - sage: H(s1*s2) + sage: H = IwahoriHeckeAlgebra("B3",q) + sage: T = H.T() + sage: T(s1*s2) T[1,2] diff --git a/src/doc/en/thematic_tutorials/lie/kazhdan_lusztig_polynomials.rst b/src/doc/en/thematic_tutorials/lie/kazhdan_lusztig_polynomials.rst index 691d22e62e6..020a4c8f976 100644 --- a/src/doc/en/thematic_tutorials/lie/kazhdan_lusztig_polynomials.rst +++ b/src/doc/en/thematic_tutorials/lie/kazhdan_lusztig_polynomials.rst @@ -19,7 +19,7 @@ Kazhdan-Lusztig polynomials as follows:: sage: KL.P(s2, s2*s1*s3*s2) 1 + q -Thus we have the Kazhdan-Lusztig R and P polynomials. +Thus we have the Kazhdan-Lusztig `R` and `P` polynomials. Known algorithms for computing Kazhdan-Lusztig polynomials are highly recursive, and caching of intermediate results is necessary for the diff --git a/src/doc/en/thematic_tutorials/lie/lie_basics.rst b/src/doc/en/thematic_tutorials/lie/lie_basics.rst index 80983fde8b3..1e60a4126b0 100644 --- a/src/doc/en/thematic_tutorials/lie/lie_basics.rst +++ b/src/doc/en/thematic_tutorials/lie/lie_basics.rst @@ -7,15 +7,15 @@ Goals of this section --------------------- Since we must be brief here, this is not really a place to learn about -Lie groups. Rather, the point of this section is to outline what you -need to know to use Sage effectively for Lie computations, and to fix -ideas and notations. +Lie groups or Lie algebras. Rather, the point of this section is to outline +what you need to know to use Sage effectively for Lie computations, and +to fix ideas and notations. Semisimple and reductive groups ------------------------------- -If `g \in GL(n,\mathbf{C})`, then `g` may be uniquely factored as +If `g \in GL(n,\CC)`, then `g` may be uniquely factored as `g_1 g_2` where `g_1` and `g_2` commute, with `g_1` semisimple (diagonalizable) and `g_2` unipotent (all its eigenvalues equal to 1). This follows from the Jordan canonical form. If `g = g_1` then `g` @@ -32,13 +32,13 @@ the notion of being semisimple or unipotent is intrinsic. Examples: - Complex analytic groups with analytic representations -- Algebraic groups over `\mathbf{R}` with algebraic representations. +- Algebraic groups over `\RR` with algebraic representations. A subgroup of `G` is called *unipotent* if it is connected and all its elements are unipotent. It is called a *torus* if it is connected, abelian, and all its elements are semisimple. The group `G` is called *reductive* if it has no nontrivial normal unipotent subgroup. For -example, `GL(2,\mathbf{C})` is reductive, but its subgroup: +example, `GL(2,\CC)` is reductive, but its subgroup: .. MATH:: @@ -65,7 +65,7 @@ A group has a unique largest normal unipotent subgroup, called the radical is trivial. A Lie group is called *semisimple* it is reductive and furthermore has -no nontrivial normal tori. For example `GL(2,\mathbf{C})` is reductive +no nontrivial normal tori. For example `GL(2,\CC)` is reductive but not semisimple because it has a normal torus: .. MATH:: @@ -77,7 +77,7 @@ but not semisimple because it has a normal torus: \end{array} \right)\right\}. -The group `SL(2,\mathbf{C})` is semisimple. +The group `SL(2,\CC)` is semisimple. Fundamental group and center @@ -103,7 +103,7 @@ maximal normal unipotent subgroup or *unipotent radical* `P` and a reductive subgroup `M`, which is determined up to conjugacy. The subgroup `M` is called a *Levi subgroup*. -**Example:** Let `G = GL_n(\mathbf{C})` and let `r_1, \dots, r_k` be +**Example:** Let `G = GL_n(\CC)` and let `r_1, \ldots, r_k` be integers whose sum is `n`. Then we may consider matrices of the form: .. MATH:: @@ -115,7 +115,7 @@ integers whose sum is `n`. Then we may consider matrices of the form: &&& g_r \end{array}\right) -where `g_i \in GL(r_i,\mathbf{C}`. The unipotent radical consists of +where `g_i \in GL(r_i,\CC)`. The unipotent radical consists of the subgroup in which all `g_i = I_{r_i}`. The Levi subgroup (determined up to conjugacy) is: @@ -133,7 +133,7 @@ the subgroup in which all `g_i = I_{r_i}`. The Levi subgroup \right)\right\}, and is isomorphic to -`M = GL(r_1,\mathbf{C}) \times \cdots \times GL(r_k,\mathbf{C})`. +`M = GL(r_1,\CC) \times \cdots \times GL(r_k,\CC)`. Therefore `M` is a Levi subgroup. The notion of a Levi subgroup can be extended to compact Lie @@ -148,12 +148,12 @@ Cartan types Semisimple Lie groups are classified by their *Cartan types*. There are both reducible and irreducible Cartan types in Sage. Let us start with the irreducible types. Such a type is implemented in Sage as a -pair ``['X',r]`` where 'X' is one of A, B, C, D, E, F or G and `r` is a +pair ``['X', r]`` where 'X' is one of A, B, C, D, E, F or G and `r` is a positive integer. If 'X' is 'D' then we must have `r > 1` and if 'X' is one of the *exceptional types* 'E', 'F' or 'G' then `r` is limited to only a few possibilities. The exceptional types are:: - ['G',2], ['F',4], ['E',6], ['E',7] or ['E',8]. + ['G', 2], ['F', 4], ['E', 6], ['E', 7] or ['E', 8]. A simply-connected semisimple group is a direct product of simple Lie groups, which are given by the following table. The integer `r` is @@ -164,13 +164,13 @@ Here are the Lie groups corresponding to the classical types: +---------------+-------------------------+-------------+ | compact group | complex analytic group | Cartan type | +===============+=========================+=============+ -| `SU(r+1)` | `SL(r+1,\mathbf{C})` | `A_r` | +| `SU(r+1)` | `SL(r+1,\CC)` | `A_r` | +---------------+-------------------------+-------------+ -| `spin(2r+1)` | `spin(2r+1,\mathbf{C})` | `B_r` | +| `spin(2r+1)` | `spin(2r+1,\CC)` | `B_r` | +---------------+-------------------------+-------------+ -| `Sp(2r)` | `Sp(2r,\mathbf{C})` | `C_r` | +| `Sp(2r)` | `Sp(2r,\CC)` | `C_r` | +---------------+-------------------------+-------------+ -| `spin(2r)` | `spin(2r,\mathbf{C})` | `D_r` | +| `spin(2r)` | `spin(2r,\CC)` | `D_r` | +---------------+-------------------------+-------------+ You may create these Cartan types and their Dynkin diagrams as follows:: @@ -181,6 +181,7 @@ You may create these Cartan types and their Dynkin diagrams as follows:: Here ``"D5"`` is an abbreviation for ``['D',5]``. The group `spin(n)` is the simply-connected double cover of the orthogonal group `SO(n)`. + Dual Cartan types ------------------ @@ -189,8 +190,8 @@ Every Cartan type has a dual, which you can get from within Sage:: sage: CartanType("B4").dual() ['C', 4] -Types other than ``B`` and ``C`` are self-dual in the sense that the -dual is isomorphic to the original type; however the isomorphism of a +Types other than `B_r` and `C_r` for `r > 2` are self-dual in the sense that +the dual is isomorphic to the original type; however the isomorphism of a Cartan type with its dual might relabel the vertices. We can see this as follows:: @@ -210,7 +211,7 @@ Reducible Cartan types ---------------------- If `G` is a Lie group of finite index in `G_1 \times G_2`, where `G_1` -and `G_2` are Lie groups of dimension `>0`, then `G` is called +and `G_2` are Lie groups of positive dimension, then `G` is called *reducible*. In this case, the root system of `G` is the disjoint union of the root systems of `G_1` and `G_2`, which lie in orthogonal subspaces of the ambient space of the weight space of `G`. The Cartan @@ -232,18 +233,18 @@ There are some isomorphisms that occur in low degree. +-------------+------------+-----------------+---------------------+ | Cartan Type | Group | Equivalent Type | Isomorphic Group | +=============+============+=================+=====================+ -| B2 | `spin(5)` | C2 | `Sp(4)` | +| `B_2` | `spin(5)` | `C_2` | `Sp(4)` | +-------------+------------+-----------------+---------------------+ -| D3 | `spin(6)` | A3 | `SL(4)` | +| `D_3` | `spin(6)` | `A_3` | `SL(4)` | +-------------+------------+-----------------+---------------------+ -| D2 | `spin(4)` | A1xA1 | `SL(2)\times SL(2)` | +| `D_2` | `spin(4)` | `A1 \times A_1` | `SL(2)\times SL(2)` | +-------------+------------+-----------------+---------------------+ -| B1 | `spin(3)` | A1 | `SL(2)` | +| `B_1` | `spin(3)` | `A_1` | `SL(2)` | +-------------+------------+-----------------+---------------------+ -| C1 | `Sp(2)` | A1 | `SL(2)` | +| `C_1` | `Sp(2)` | `A_1` | `SL(2)` | +-------------+------------+-----------------+---------------------+ -Sometimes the redundant Cartan types such as D3 and D2 are excluded +Sometimes the redundant Cartan types such as `D_3` and `D_2` are excluded from the list of Cartan types. However Sage allows them since excluding them leads to exceptions having to be made in algorithms. A better approach, which is followed by Sage, is to allow the redundant Cartan types, but to implement @@ -260,53 +261,14 @@ consider it to be `SL(2)`, `spin(2)` or `Sp(2)`:: Finite family {1: (2)} -Affine Cartan types -------------------- - -There are also affine Cartan types, which correspond to (infinite) -affine Lie algebras. There is an affine Cartan type of the of the -form ``[`X`,r,1]`` if ``X=A,B,C,D,E,F,G`` and ``[`X`,r]`` is an ordinary -Cartan type. There are also *twisted affine types* of the form ``[X,r,k]`` -where `k = 2` or 3 if the Dynkin diagram of the ordinary Cartan type -``[X,r]`` has an automorphism of degree `k`. - -Illustrating some of the methods available for the untwisted affine -Cartan type ``['A',4,1]``:: - - sage: ct = CartanType(['A',4,1]); ct - ['A', 4, 1] - sage: ct.dual() - ['A', 4, 1] - sage: ct.classical() - ['A', 4] - sage: ct.dynkin_diagram() - 0 - O-----------+ - | | - | | - O---O---O---O - 1 2 3 4 - A4~ - -The twisted affine Cartan types are relabeling of the duals of certain -untwisted Cartan types:: - - sage: CartanType(['A',3,2]) - ['B', 2, 1]^* - sage: CartanType(['D',4,3]) - ['G', 2, 1]^* relabelled by {0: 0, 1: 2, 2: 1} - - Relabeled Cartan types ---------------------- -By default Sage uses the labeling of the Dynkin Diagram from Bourbaki, -*Lie Groups and Lie Algebras* Chapters 4,5,6. There is another -labeling of the vertices due to Dynkin. Most of the literature follows -Bourbaki, though Kac's book *Infinite Dimensional Lie algebras* -follows Dynkin. +By default Sage uses the labeling of the Dynkin diagram from [Bourbaki46]_. +There is another labeling of the vertices due to Dynkin. +Most of the literature follows [Bourbaki46]_, though [Kac]_ follows Dynkin. -If you need to use Dynkin's labeling you should be aware that Sage +If you need to use Dynkin's labeling, you should be aware that Sage does support relabeled Cartan types. See the documentation in ``sage.combinat.root_system.type_relabel`` for further information. @@ -316,16 +278,16 @@ does support relabeled Cartan types. See the documentation in Standard realizations of the ambient spaces ------------------------------------------- -These realizations follow the Appendix in Bourbaki, *Lie Groups and -Lie Algebras, Chapters 4-6*. See the :ref:`Root system plot tutorial -` for how to visualize them. +These realizations follow the Appendix in [Bourbaki46]_. See the +:ref:`Root system plot tutorial ` +for how to visualize them. Type A ^^^^^^ For type `A_r` we use an `r+1` dimensional ambient space. This means -that we are modeling the Lie group `U(r+1)` or `GL(r+1,\mathbf{C})` -rather than `SU(r+1)` or `SL(r+1,\mathbf{C})`. The ambient space is +that we are modeling the Lie group `U(r+1)` or `GL(r+1,\CC)` +rather than `SU(r+1)` or `SL(r+1,\CC)`. The ambient space is identified with `\mathbf{Q}^{r+1}`:: sage: RootSystem("A3").ambient_space().simple_roots() @@ -404,6 +366,10 @@ The dominant weights consist of `r`-tuples of integers `\lambda = (\lambda_1,\dots,\lambda_{r+1})` such that `\lambda_1 \ge \cdots \ge \lambda_{r-1} \ge |\lambda_r|`. + +Exceptional Types +^^^^^^^^^^^^^^^^^ + We leave the reader to examine the exceptional types. You can use Sage to list the fundamental dominant weights and simple roots. @@ -413,11 +379,11 @@ Weights and the ambient space Let `G` be a reductive complex analytic group. Let `T` be a maximal torus, `\Lambda = X^{\ast} (T)` be its group of analytic -characters. Then `T \cong (\mathbf{C}^{\times})^r` for some `r` and -`\Lambda \cong \mathbf{Z}^r`. +characters. Then `T \cong (\CC^{\times})^r` for some `r` and +`\Lambda \cong \ZZ^r`. -**Example 1:** Let `G = \hbox{GL}_{r+1} (\mathbf{C})`. Then `T` is the -diagonal subgroup and `X^{\ast} (T) \cong \mathbf{Z}^{r+1}`. If +**Example 1:** Let `G = \hbox{GL}_{r+1} (\CC)`. Then `T` is the +diagonal subgroup and `X^{\ast} (T) \cong \ZZ^{r+1}`. If `\lambda = (\lambda_1, \dots, \lambda_n)` then `\lambda` is identified with the rational character @@ -432,11 +398,11 @@ with the rational character \end{array}\right) \longmapsto \prod t_i^{\lambda_i}. -**Example 2:** Let `G = \hbox{SL}_{r+1} (\mathbf{C})`. Again `T` is +**Example 2:** Let `G = \hbox{SL}_{r+1} (\CC)`. Again `T` is the diagonal subgroup but now if -`\lambda \in \mathbf{Z}^{\Delta} = \{(d, \cdots, d) | d \in \mathbf{Z}\} \subseteq \mathbf{Z}^{r+1}` +`\lambda \in \ZZ^{\Delta} = \{(d, \cdots, d) | d \in \ZZ\} \subseteq \ZZ^{r+1}` then `\prod t_i^{\lambda_i} = \det ({\bf t})^d = 1`, so -`X^{\ast} (T) \cong \mathbf{Z}^{r+1} /\mathbf{Z}^{\Delta} \cong \mathbf{Z}^r`. +`X^{\ast} (T) \cong \ZZ^{r+1} /\ZZ^{\Delta} \cong \ZZ^r`. - Elements of `\Lambda` are called *weights*. @@ -448,15 +414,15 @@ then `\prod t_i^{\lambda_i} = \det ({\bf t})^d = 1`, so - The nonzero weights of the adjoint representation are called *roots*. -- The *ambient space* of `\Lambda` is `\mathbf{Q} \otimes \Lambda`. +- The *ambient space* of `\Lambda` is `\QQ \otimes \Lambda`. The root system --------------- As we have mentioned, `G` acts on its complexified Lie algebra -`\mathfrak{g}_{\mathbf{C}}` by the adjoint representation. The zero -weight space `\mathfrak{g}_{\mathbf{C}}(0)` is just the Lie algebra of +`\mathfrak{g}_{\CC}` by the adjoint representation. The zero +weight space `\mathfrak{g}_{\CC}(0)` is just the Lie algebra of `T` itself. The other nonzero weights each appear with multiplicity one and form an interesting configuration of vectors called the *root system* `\Phi`. @@ -464,9 +430,9 @@ one and form an interesting configuration of vectors called the It is convenient to partition `\Phi` into two sets `\Phi^+` and `\Phi^-` such that `\Phi^+` consists of all roots lying on one side of a hyperplane. Often we arrange things so that `G` is embedded in -`GL(n,\mathbf{C})` in such a way that the positive weights correspond +`GL(n,\CC)` in such a way that the positive weights correspond to upper triangular matrices. Thus if `\alpha` is a positive root, its -weight space `\mathfrak{g}_{\mathbf{C}}(\alpha)` is spanned by a +weight space `\mathfrak{g}_{\CC}(\alpha)` is spanned by a vector `X_\alpha`, and the exponential of this eigenspace in `G` is a one-parameter subgroup of unipotent matrices. It is always possible to arrange that this one-parameter subgroup consists of upper triangular @@ -475,7 +441,7 @@ matrices. If `\alpha` is a positive root that cannot be decomposed as a sum of other positive roots, then `\alpha` is called a *simple root*. If `G` is semisimple of rank `r`, then `r` is the number of positive -roots. Let `\alpha_1,\dots,\alpha_r` be these. +roots. Let `\alpha_1, \ldots, \alpha_r` be these. The Weyl group @@ -485,22 +451,22 @@ Let `G` be a complex analytic group. Let `T` be a maximal torus, and let `N(T)` be its normalizer. Let `W = N(T)/T` be the *Weyl group*. It acts on `T` by conjugation; therefore it acts on the weight lattice `\Lambda` and its ambient space. The ambient space admits an inner -product that is invariant under this action. Let `\left` +product that is invariant under this action. Let `(v | w)` denote this inner product. If `\alpha` is a root let `r_\alpha` denote the reflection in the hyperplane of the ambient space that is perpendicular to `\alpha`. If `\alpha = \alpha_i` is a simple root, then we use the notation `s_i` to denote `r_\alpha`. -Then `s_1,\dots,s_r` generate `W`, which is a *Coxeter group*. This +Then `s_1, \ldots, s_r` generate `W`, which is a *Coxeter group*. This means that it is generated by elements `s_i` of order two and that if `m(i,j)` is the order of `s_i s_j`, then .. MATH:: - W = \left + W = \left\langle s_i \mid s_i^2=1, (s_i s_j)^{m(i,j)} = 1 \right\rangle -is a presentation. An important function `l: W \to \mathbf{Z}` is the -*length* function, where `l(w)` is the length of the shortest +is a presentation. An important function `\ell : W \to \ZZ` is the +*length* function, where `\ell(w)` is the length of the shortest decomposition of `w` into a product of simple reflections. @@ -509,7 +475,7 @@ The dual root system The *coroots* are certain linear functionals on the ambient space that also form a root system. Since the ambient space admits a -`W`-invariant inner product, they may be identified with elements +`W`-invariant inner product `(\ |\ )`, they may be identified with elements of the ambient space itself. Then they are proportional to the roots, though if the roots have different lengths, long roots correspond to short coroots and conversely. The coroot corresponding @@ -517,7 +483,16 @@ to the root `\alpha` is .. MATH:: - \alpha^\vee = \frac{2\alpha}{\left<\alpha,\alpha\right>}. + \alpha^\vee = \frac{2\alpha}{(\alpha | \alpha)}. + +We can also describe the natural pairing between coroots and roots using +this invariant inner product as + +.. MATH:: + + \langle \alpha^{\vee}, \beta \rangle + = + 2 \frac{(\alpha | \beta)}{(\alpha | \alpha)}. The Dynkin diagram @@ -525,7 +500,7 @@ The Dynkin diagram The Dynkin diagram is a graph whose vertices are in bijection with the set simple roots. We connect the vertices corresponding to roots that -are not orthogonal. Usually two such vertices make an angle of +are not orthogonal. Usually two such roots (vertices) make an angle of `2\pi/3`, in which case we connect them with a single bond. Occasionally they may make an angle of `3\pi/4` in which case we connect them with a double bond, or `5\pi/6` in which case we connect @@ -533,10 +508,9 @@ them with a triple bond. If the bond is single, the roots have the same length with respect to the inner product on the ambient space. In the case of a double or triple bond, the two simple roots in questions have different length, and the bond is drawn as an arrow from the long -root to the short root. Only the exceptional group `G_2` has a triple -bond. +root to the short root. Only the exceptional group `G_2` has a triple bond. -There are various ways to get the Dynkin diagram:: +There are various ways to get the Dynkin diagram in Sage:: sage: DynkinDiagram("D5") O 5 @@ -567,58 +541,36 @@ There are various ways to get the Dynkin diagram:: G2 -The affine root and the extended Dynkin diagram ------------------------------------------------ +The Cartan matrix +----------------- -For the extended Dynkin diagram, we add one negative root -`\alpha_0`. This is the root whose negative is the highest weight in -the adjoint representation. Sometimes this is called the -*affine root*. We make the Dynkin diagram as before by measuring the -angles between the roots. This extended Dynkin diagram is useful for -many purposes, such as finding maximal subgroups and for describing -the affine Weyl group. +Consider the natural pairing `\langle\ ,\ \rangle` between coroots and +roots, then the defining matrix of this pairing is called the +*Cartan matrix*. That is to say, the Cartan matrix `A = (a_{ij})_{ij}` +is given by -The extended Dynkin diagram may be obtained as the Dynkin diagram of -the corresponding untwisted affine type:: +.. MATH:: - sage: ct = CartanType("E6"); ct - ['E', 6] - sage: ct.affine() - ['E', 6, 1] - sage: ct.affine() == CartanType(['E',6,1]) - True - sage: ct.affine().dynkin_diagram() - O 0 - | - | - O 2 - | - | - O---O---O---O---O - 1 3 4 5 6 - E6~ + a_{ij} = \langle \alpha_i^{\vee}, \alpha_j \rangle. -The extended Dynkin diagram is also a method of the ``WeylCharacterRing``:: +This uniquely corresponds to a root system/Dynkin diagram/Lie group. - sage: WeylCharacterRing("E7").extended_dynkin_diagram() - O 2 - | - | - O---O---O---O---O---O---O - 0 1 3 4 5 6 7 - E7~ +We note that we have made a convention choice, and the opposite convention +corresponds to taking the transpose of the Cartan matrix. Fundamental weights and the Weyl vector --------------------------------------- -There are certain weights `\omega_1,\dots,\omega_r` that: +There are certain weights `\omega_1, \ldots, \omega_r` that: .. MATH:: - \frac{2\left<\alpha_i,\omega_j\right>}{\left<\alpha_i,\alpha_i\right>} + \langle \omega_j, \alpha_i \rangle + = + 2 \frac{( \alpha_i | \omega_j )}{( \alpha_i | \alpha_i ) } = - \delta(i,j). + \delta_{ij}. If `G` is semisimple then these are uniquely determined, whereas if `G` is reductive but not semisimple we may choose them conveniently. @@ -631,7 +583,7 @@ we make a different choice, then `\rho` is altered by a vector that is orthogonal to all roots. This is a harmless change for many purposes such as the Weyl character formula. -In Sage, this issue arises only for Cartan type A. See :ref:`SLvsGL`. +In Sage, this issue arises only for Cartan type `A_r`. See :ref:`SLvsGL`. .. _representations: @@ -639,23 +591,23 @@ In Sage, this issue arises only for Cartan type A. See :ref:`SLvsGL`. Representations and characters ------------------------------ -Let `\Lambda = X^{\ast} (T)` be the group of rational characters. Then -`\Lambda \cong \mathbf{Z}^r`. +Let `T` be a maximal torus and `\Lambda = X^{\ast} (T)` be the group +of rational characters. Then `\Lambda \cong \ZZ^r`. -- Recall that elements of `\Lambda \cong \mathbf{Z}^r` are called *weights*. +- Recall that elements of `\Lambda \cong \ZZ^r` are called *weights*. - The Weyl group `W = N(T)/T` acts on `T`, hence on `\Lambda` and its ambient space by conjugation. -- The ambient space `\mathbf{Q} \otimes X^{\ast} (T) \cong \mathbf{Q}^r` +- The ambient space `\QQ \otimes X^{\ast} (T) \cong \QQ^r` has a fundamental domain `\mathcal{C}^+` for the Weyl group `W` called the *positive Weyl chamber*. Weights in `\mathcal{C}^+` are called *dominant*. - Then `\mathcal{C}^+` consists of all vectors such that - `\left<\alpha,v\right> \ge 0` for all positive roots `\alpha`. + `(\alpha | v) \geq 0` for all positive roots `\alpha`. -- It is useful to embed `\Lambda` in `\mathbf{R}^r` and consider +- It is useful to embed `\Lambda` in `\RR^r` and consider weights as lattice points. - If `(\pi, V)` is a representation then restricting to `T`, the @@ -707,7 +659,7 @@ Representations: an example :scale: 75 :align: center -In this example, `G = \hbox{SL}(3,\mathbf{C})`. We have drawn the +In this example, `G = \hbox{SL}(3,\CC)`. We have drawn the weights of an irreducible representation with highest weight `\lambda`. The shaded region is `\mathcal{C}^+`. `\lambda` is a dominant weight, and the labeled vertices are the weights with positive multiplicity in @@ -720,8 +672,8 @@ while the six interior weights (with double circles) have `m(\mu) = 2`. Partitions and Schur polynomials -------------------------------- -The considerations of this section are particular to type A. We review -the relationship between characters of `GL(n,\mathbf{C})` and +The considerations of this section are particular to type `A`. We review +the relationship between characters of `GL(n,\CC)` and symmetric function theory. A *partition* `\lambda` is a sequence of descending nonnegative @@ -750,13 +702,13 @@ dominant weight of ``['A',r]`` is a partition of length `\le n`, where `n = r+1`. Let `\lambda` be a dominant weight, and let `\chi_\lambda` be the -character of `GL(n,\mathbf{C})` with highest weight `\lambda`. If `k` +character of `GL(n,\CC)` with highest weight `\lambda`. If `k` is any integer we may consider the weight `\mu = (\lambda_1+k,\dots,\lambda_n+k)` obtained by adding `k` to each entry. Then `\chi_{\mu} = \det^k \otimes \chi_\lambda`. Clearly by choosing `k` large enough, we may make `\mu` effective. -So the characters of irreducible representations of `GL(n,\mathbf{C})` +So the characters of irreducible representations of `GL(n,\CC)` do not all correspond to partitions, but the characters indexed by partitions (effective dominant weights) are enough that we can write any character `\det^{-k}\chi_{\mu}` where `\mu` is a @@ -777,7 +729,140 @@ This means that if & \ddots \\ && z_n \end{array}\right) - \in GL(n,\mathbf{C}) + \in GL(n,\CC) then `\chi_\lambda(g)` is a polynomial in the eigenvalues of `g`. -This is the *Schur polynomial* `s_\lambda(z_1,\dots,z_n)`. +This is the *Schur polynomial* `s_\lambda(z_1, \ldots, z_n)`. + + +Affine Cartan types +------------------- + +There are also affine Cartan types, which correspond to (infinite dimensional) +affine Lie algebras. There are affine Cartan types of the +form ``[`X`, r, 1]`` if ``X=A,B,C,D,E,F,G`` and ``[`X`, r]`` is an ordinary +Cartan type. There are also *twisted affine types* of the form ``[X, r, k]``, +where `k = 2` or `3` if the Dynkin diagram of the ordinary Cartan type +``[X, r]`` has an automorphism of degree `k`. When `k = 1`, the affine Cartan +type is said to be *untwisted*. + +Illustrating some of the methods available for the untwisted affine +Cartan type ``['A', 4, 1]``:: + + sage: ct = CartanType(['A',4,1]); ct + ['A', 4, 1] + sage: ct.dual() + ['A', 4, 1] + sage: ct.classical() + ['A', 4] + sage: ct.dynkin_diagram() + 0 + O-----------+ + | | + | | + O---O---O---O + 1 2 3 4 + A4~ + +The twisted affine Cartan types are relabeling of the duals of certain +untwisted Cartan types:: + + sage: CartanType(['A',3,2]) + ['B', 2, 1]^* + sage: CartanType(['D',4,3]) + ['G', 2, 1]^* relabelled by {0: 0, 1: 2, 2: 1} + + +The affine root and the extended Dynkin diagram +----------------------------------------------- + +For the extended Dynkin diagram, we add one negative root +`\alpha_0`. For the untwisted types, this is the root whose negative +is the highest weight in the adjoint representation. Sometimes this is +called the *affine root*. We make the Dynkin diagram as before by +measuring the angles between the roots. This extended Dynkin diagram +is useful for many purposes, such as finding maximal subgroups +and for describing the affine Weyl group. + +In particular, the hyperplane for the reflection `r_0`, used in generating +the affine Weyl group is translated off the origin (so it becomes an affine +hyperplane). Now the root system is not described as linear transformations +on an Euclidean space, but instead by *affine* transformations. Thus the +dominant chamber has finite volume and tiles the Eucledian space. Moreover, +each such tile corresponds to a unique element in the affine Weyl group. + +The extended Dynkin diagram may be obtained as the Dynkin diagram of +the corresponding untwisted affine type:: + + sage: ct = CartanType("E6"); ct + ['E', 6] + sage: ct.affine() + ['E', 6, 1] + sage: ct.affine() == CartanType(['E',6,1]) + True + sage: ct.affine().dynkin_diagram() + O 0 + | + | + O 2 + | + | + O---O---O---O---O + 1 3 4 5 6 + E6~ + +The extended Dynkin diagram is also a method of the ``WeylCharacterRing``:: + + sage: WeylCharacterRing("E7").extended_dynkin_diagram() + O 2 + | + | + O---O---O---O---O---O---O + 0 1 3 4 5 6 7 + E7~ + +We note the following important distinctions from the classical cases: + +- The affine Weyl groups are all infinte. +- Type `A_1^{(1)}` has two anti-parallel roots with distinct reflections. + The Dynkin diagram in this case is represented by a double bond with + arrows going in both directions. + + +Twisted affine root systems +--------------------------- + +For the construction of `\alpha_0` in the twisted types, we refer the +reader to Chaper 8 of [Kac]_. As mentioned above, most twisted types can +be constructed by the taking the dual root system of an untwisted type. +However the type `A_{2n}^{(2)}` root system which can only be constructed by +the twisting procedure defined in [Kac]_. It has the following properties: + +- The Dynkin diagram of type `A_2^{(2)}` has a quadruple bond with an arrow + pointing from the short root to the long root. +- Type `A_{2n}^{(2)}` for `n > 1` has 3 different root lengths. + + +Further Generalizations +----------------------- + +If a root system (on an Euclidean space) has only the angles +`\pi/2, 2\pi/3, 3\pi/4, 5\pi/6` between its roots, then we call the +root system *crystallographic* (on :wikipedia:`Root_system`, this +condition is called integrality since for any two roots we have +`\langle \beta, \alpha \rangle \in \ZZ`). So if we look at the reflection +group generated by the roots (this is not a Weyl group), we get general +:wikipedia:`Coxeter groups ` (with non-infinite labels) +and non-crystallographic Coxeter groups are not connected with Lie theory. + +However we can generalize Dynkin diagrams (equivalently Cartan matrices) +to have all its edges labelled by `(a, b)` where `a, b \in \ZZ_{>0}` and +corresponds to having `a` arrows point one way and `b` arrows pointing +the other. For example in type `A_{1}^{(1)}`, we have one edge of `(2, 2)`, +or in type `A_{2}^{(2)}`, we have one edge of `(1, 4)` (equivalently +`(4, 1)`). These edge label between `i` and `j` corresponds to the entries +`a_{ij}` and `a_{ji}` in the Cartan matrix. These are used to construct +a class of (generally infinite dimensional) Lie algebras called +Kac-Moody (Lie) algebras, which in turn are used to construct quantum groups. +We refer the reader to [Kac]_ and [HongKang2002]_ for more information. + diff --git a/src/doc/en/thematic_tutorials/lie/weight_ring.rst b/src/doc/en/thematic_tutorials/lie/weight_ring.rst index 4d738da4406..2fab01a1a2b 100644 --- a/src/doc/en/thematic_tutorials/lie/weight_ring.rst +++ b/src/doc/en/thematic_tutorials/lie/weight_ring.rst @@ -6,25 +6,26 @@ Weight Rings You may wish to work directly with the weights of a representation. -``WeylCharacterRingElements`` are represented internally by a +Weyl character ring elements are represented internally by a dictionary of their weights with multiplicities. However these are subject to a constraint: the coefficients must be invariant under the action of the Weyl group. -The ``WeightRing`` is also a ring whose elements are represented +The :class:`WeightRing` is also a ring whose elements are represented internally by a dictionary of their weights with multiplicities, but it is not subject to this constraint of Weyl group invariance. The weights are allowed to be fractional, that is, elements of the ambient space. In other words, the weight ring is the group algebra over the ambient space of the weight lattice. -To create a ``WeightRing`` first construct the Weyl Character Ring, -then create the ``WeightRing`` as follows:: +To create a :class:`WeightRing` first construct the +:class:`WeylCharacterRing`, then create the +:class:`WeightRing` as follows:: sage: A2 = WeylCharacterRing(['A',2]) sage: a2 = WeightRing(A2) -You may coerce elements of the ``WeylCharacterRing`` into the weight +You may coerce elements of the :class:`WeylCharacterRing` into the weight ring. For example, if you want to see the weights of the adjoint representation of `GL(3)`, you may use the method ``mlist``, but another way is to coerce it into the weight ring:: @@ -76,7 +77,8 @@ Let us confirm the Weyl denominator formula for ``A2``:: Note that we have to be careful to use the right value of `\rho`. The reason for this is explained in :ref:`SLvsGL`. -We have seen that elements of the ``WeylCharacterRing`` can be coerced -into the ``WeightRing``. Elements of the ``WeightRing`` can be coerced -into the ``WeylCharacterRing`` *provided* they are invariant under the -Weyl group. +We have seen that elements of the :class:`WeylCharacterRing` can be coerced +into the :class:`WeightRing`. Elements of the :class:`WeightRing` can be +coerced into the :class:`WeylCharacterRing` *provided* they are invariant +under the Weyl group. + diff --git a/src/doc/en/thematic_tutorials/lie/weyl_groups.rst b/src/doc/en/thematic_tutorials/lie/weyl_groups.rst index 919776b483d..f6d43d31bce 100644 --- a/src/doc/en/thematic_tutorials/lie/weyl_groups.rst +++ b/src/doc/en/thematic_tutorials/lie/weyl_groups.rst @@ -203,10 +203,10 @@ that represents `u`, then `u \le v`. The Bruhat order is implemented in Sage as a method of Coxeter groups, and so it is available for Weyl groups, classical or affine. -If `u`, `v \in W` then ``u.bruhat_le(v)`` returns true of +If `u`, `v \in W` then ``u.bruhat_le(v)`` returns ``True`` if `u \le v` in the Bruhat order. -If `u \le v` then The *Bruhat interval* `[u,v]` is defined to be the +If `u \le v` then the *Bruhat interval* `[u,v]` is defined to be the set of all `t` such that `u \le t \le v`. One might try to implement this as follows:: @@ -242,10 +242,10 @@ References: - [BumpNakasuji2010]_ -The *Bruhat Graph* is a structure on the Bruhat interval. Suppose that +The *Bruhat graph* is a structure on the Bruhat interval. Suppose that `u \le v`. The vertices of the graph are `x` with `u \le x \le v`. -There is a vertex connecting `x,y \in [x,y]` if `x = y.r` where `r` is -a reflection. If this is true then either `x < y` or `y < x`. +There is a vertex connecting `x,y \in [x,y]` if `x = y \cdot r` where +`r` is a reflection. If this is true then either `x < y` or `y < x`. If `W` is a classical Weyl group the Bruhat graph is implemented in Sage:: @@ -259,12 +259,12 @@ The Bruhat graph has interesting regularity properties that were investigated by Carrell and Peterson. It is a regular graph if both the Kazhdan Lusztig polynomials `P_{u,v}` and `P_{w_0v,w_0u}` are 1, where `w_0` is the long Weyl group element. It is closely related to -the *Deodhar conjecture* which was proved by Deodhar, Carrell and +the *Deodhar conjecture*, which was proved by Deodhar, Carrell and Peterson, Dyer and Polo. Deodhar proved that if `u < v` then the Bruhat interval `[u,v]` contains as many elements of odd length as it does of even length. We -observe that often this can be strengthened: if there exists a +observe that often this can be strengthened: If there exists a reflection `r` such that left (or right) multiplication by `r` takes the Bruhat interval `[u,v]` to itself, then this gives an explicit bijection between the elements of odd and even length in `[u,v]`. @@ -295,5 +295,5 @@ all pairs `(u,v)` with `u < v` except the following two: precisely the pairs such that `u\prec v` in the notation of Kazhdan and Lusztig, and `l(v)-l(u) > 1`. One should not rashly suppose that this is a general characterization of the pairs `(u,v)` such that no -reflection stabilizes the Bruhat interval, for this is not true, but +reflection stabilizes the Bruhat interval, for this is not true. However it does suggest that the question is worthy of further investigation. diff --git a/src/doc/en/thematic_tutorials/linear_programming.rst b/src/doc/en/thematic_tutorials/linear_programming.rst index 809b2bbae3e..e7e896c7caf 100644 --- a/src/doc/en/thematic_tutorials/linear_programming.rst +++ b/src/doc/en/thematic_tutorials/linear_programming.rst @@ -80,15 +80,14 @@ Let us ask Sage to solve the following LP: \text{Max: } & x + y - 3z\\ \text{Such that: } & x + 2y \leq 4\\ \text{} & 5z - y \leq 8\\ + \text{} & x,y,z \geq 0\\ To achieve it, we need to define a corresponding ``MILP`` object, along with 3 -variables ``x,y`` and ``z`` (which by default can only take **non-negative -values**, see next section):: +variables ``x,y`` and ``z``:: sage: p = MixedIntegerLinearProgram() - sage: x, y, z = p['x'], p['y'], p['z'] - doctest:839: DeprecationWarning: The default behaviour of new_variable() will soon change ! It will return 'real' variables instead of nonnegative ones. Please be explicit and call new_variable(nonnegative=True) instead. - See http://trac.sagemath.org/15521 for details. + sage: v = p.new_variable(real=True, nonnegative=True) + sage: x, y, z = v['x'], v['y'], v['z'] Next, we set the objective function @@ -135,21 +134,16 @@ We can read the optimal assignation found by the solver for `x, y` and Variables ^^^^^^^^^ -In the previous example, we obtained variables through ``p['x'], p['y']`` and -``p['z']``, which is a convenient shortcut when our LP is defined over a small -number of variables. This being said, larger LP/MILP will require us to -associate a LP variable to many Sage objects, which can be integers, strings, or -even the vertices and edges of a graph. We use in this case an alternative -syntax. - -If we need 15 variables `x_1, \dots, x_{15}`, we will first define a dictionary -of variables with the ``new_variable`` method +In the previous example, we obtained variables through ``v['x'], v['y']`` and +``v['z']``. This being said, larger LP/MILP will require us to associate a LP +variable to many Sage objects, which can be integers, strings, or even the +vertices and edges of a graph. For example: .. link :: - sage: x = p.new_variable() + sage: x = p.new_variable(real=True, nonnegative=True) With this new object ``x`` we can now write constraints using ``x[1],...,x[15]``. @@ -185,16 +179,14 @@ And because any immutable object can be used as a key, doubly indexed variables Typed variables and bounds """""""""""""""""""""""""" -By default, all the LP variables represent **non-negative reals**. - **Types :** If you want a variable to assume only integer or binary values, use the ``integer=True`` or ``binary=True`` arguments of the ``new_variable`` method. Alternatively, call the ``set_integer`` and ``set_binary`` methods. -**Bounds :** By default all variables represent **non-negative reals**. If you -want a variable to represent arbitrary reals (or arbitrary integers), you should -redefine its lower bound using the ``set_min`` method. If you want to set an -upper bound on a variable, use the ``set_max`` method. +**Bounds :** If you want your variables to only take nonnegative values, you can +say so when calling ``new_variable`` with the argument ``nonnegative=True``. If +you want to set a different upper/lower bound on a variable, add a constraint or +use the use the ``set_min``, ``set_max`` methods. Basic linear programs --------------------- @@ -409,7 +401,7 @@ graph, in which all the edges have a capacity of 1:: :: sage: p = MixedIntegerLinearProgram() - sage: f = p.new_variable() + sage: f = p.new_variable(real=True, nonnegative=True) sage: s, t = 0, 2 .. link diff --git a/src/doc/en/thematic_tutorials/media/hyperbolic_La0.png b/src/doc/en/thematic_tutorials/media/hyperbolic_La0.png new file mode 100644 index 00000000000..a710f53a50b Binary files /dev/null and b/src/doc/en/thematic_tutorials/media/hyperbolic_La0.png differ diff --git a/src/doc/en/tutorial/tour_algebra.rst b/src/doc/en/tutorial/tour_algebra.rst index 48ede49a6fa..94cde69a09e 100644 --- a/src/doc/en/tutorial/tour_algebra.rst +++ b/src/doc/en/tutorial/tour_algebra.rst @@ -154,7 +154,7 @@ solve the equation :math:`x'+x-1=0`: sage: x = function('x',t) # define x to be a function of that variable sage: DE = diff(x, t) + x - 1 sage: desolve(DE, [x,t]) - (c + e^t)*e^(-t) + (_C + e^t)*e^(-t) This uses Sage's interface to Maxima [Max]_, and so its output may be a bit different from other Sage output. In this case, this says diff --git a/src/doc/en/tutorial/tour_coercion.rst b/src/doc/en/tutorial/tour_coercion.rst index 2b8d526b2fb..3e41361f13f 100644 --- a/src/doc/en/tutorial/tour_coercion.rst +++ b/src/doc/en/tutorial/tour_coercion.rst @@ -145,18 +145,17 @@ cached: Types versus parents -------------------- -The type ``RingElement`` should not be confused with the mathematical -notion of a ring element; for practical reasons, sometimes an object -is an instance of ``RingElement`` although it does not belong to a -ring: +The type ``RingElement`` does not correspond perfectly to the +mathematical notion of a ring element. For example, although square +matrices belong to a ring, they are not instances of ``RingElement``: :: - sage: M = Matrix(ZZ,2,3); M - [0 0 0] - [0 0 0] + sage: M = Matrix(ZZ,2,2); M + [0 0] + [0 0] sage: isinstance(M, RingElement) - True + False While *parents* are unique, equal *elements* of a parent in Sage are not necessarily identical. This is in contrast to the behaviour of Python diff --git a/src/doc/fr/tutorial/tour_algebra.rst b/src/doc/fr/tutorial/tour_algebra.rst index c639f8571de..222a4c0e470 100644 --- a/src/doc/fr/tutorial/tour_algebra.rst +++ b/src/doc/fr/tutorial/tour_algebra.rst @@ -133,12 +133,12 @@ ordinaires. Pour résoudre l'équation :math:`x'+x-1=0` : x(t) sage: DE = lambda y: diff(y,t) + y - 1 sage: desolve(DE(x(t)), [x(t),t]) - (c + e^t)*e^(-t) + (_C + e^t)*e^(-t) Ceci utilise l'interface de Sage vers Maxima [Max]_, aussi il se peut que la sortie diffère un peu des sorties habituelles de Sage. Dans notre cas, le résultat indique que la solution générale à l'équation -différentielle est :math:`x(t) = e^{-t}(e^{t}+c)`. +différentielle est :math:`x(t) = e^{-t}(e^{t}+C)`. Il est aussi possible de calculer des transformées de Laplace. La transformée de Laplace de :math:`t^2e^t -\sin(t)` s'obtient comme suit : diff --git a/src/doc/fr/tutorial/tour_coercion.rst b/src/doc/fr/tutorial/tour_coercion.rst index 4f863887d73..2d7b2fb884d 100644 --- a/src/doc/fr/tutorial/tour_coercion.rst +++ b/src/doc/fr/tutorial/tour_coercion.rst @@ -146,17 +146,17 @@ est construit, il est conservé en cache et réutilisé par la suite : Types et parents ---------------- -Il ne faut pas confondre le type ``RingElement`` avec la notion mathématique -d'élément d'anneau : il peut arriver que pour des raisons pratiques, un objet -soit de type ``RingElement`` alors qu'il n'appartient pas à un anneau : +Le type ``RingElement`` ne correspond pas parfaitement à la notion +mathématique d'élément d'anneau. Par exemple, bien que les matrices carrées +appartiennent à un anneau, elles ne sont pas de type ``RingElement`` : :: - sage: M = Matrix(ZZ,2,3); M - [0 0 0] - [0 0 0] + sage: M = Matrix(ZZ,2,2); M + [0 0] + [0 0] sage: isinstance(M, RingElement) - True + False Si les *parents* sont censés être uniques, des *éléments* égaux d'un parent ne sont pas nécessairement identiques. Le comportement de Sage diffère ici de diff --git a/src/doc/ru/tutorial/tour_algebra.rst b/src/doc/ru/tutorial/tour_algebra.rst index 5725792128a..c3761a4ba33 100644 --- a/src/doc/ru/tutorial/tour_algebra.rst +++ b/src/doc/ru/tutorial/tour_algebra.rst @@ -150,12 +150,12 @@ Sage может использоваться для решения диффер sage: x = function('x',t) # определение функции x зависящей от t sage: DE = diff(x, t) + x - 1 sage: desolve(DE, [x,t]) - (c + e^t)*e^(-t) + (_C + e^t)*e^(-t) Для этого используется интерфейс Maxima [Max]_, поэтому результат может быть выведен в виде, отличном от обычного вывода Sage. В данном случае общее решение для данного дифференциального уравнения - -:math:`x(t) = e^{-t}(e^{t}+c)`. +:math:`x(t) = e^{-t}(e^{t}+C)`. Преобразования Лапласа также могут быть вычислены. Преобразование Лапласа для :math:`t^2e^t -\sin(t)` вычисляется следующим образом: diff --git a/src/mac-app/AppController.h b/src/mac-app/AppController.h index 47c4c56fe54..f6b2c1a2c27 100644 --- a/src/mac-app/AppController.h +++ b/src/mac-app/AppController.h @@ -61,6 +61,7 @@ -(IBAction)showPreferences:(id)sender; +-(void)ensureReadWrite; -(void)setupPaths; // Quit diff --git a/src/mac-app/AppController.m b/src/mac-app/AppController.m index df3244a1005..37af4ad1901 100644 --- a/src/mac-app/AppController.m +++ b/src/mac-app/AppController.m @@ -33,6 +33,7 @@ - (void) awakeFromNib{ // Find sageBinary etc. [self setupPaths]; + [self ensureReadWrite]; // Initialize the StatusItem if desired. // If we are on Tiger, then showing in the dock doesn't work @@ -344,6 +345,33 @@ -(void)setupPaths{ } } +-(void)ensureReadWrite { + NSFileManager *filemgr = [NSFileManager defaultManager]; + NSLog(@"Checking if sageBinary (%@) is writeable.",sageBinary); + if ( ! [filemgr isWritableFileAtPath:sageBinary] ) { + NSAlert *alert = [NSAlert alertWithMessageText:@"Read-only Sage" + defaultButton:@"Quit" + alternateButton:@"Preferences" + otherButton:@"Continue" + informativeTextWithFormat:@"You are attempting to run Sage.app with a read-only copy of Sage " + "(most likely due to running it from the disk image). " + "Unfortunately, this is not supported for technical reasons. \n" + "Please drag Sage.app to your hard-drive and run it from there, " + "or choose a different executable in Preferences."]; + [alert setAlertStyle:NSWarningAlertStyle]; + NSModalResponse resp = [alert runModal]; + if (resp == NSModalResponseOK) {// Quit + NSLog(@"Quitting after a read-only Sage warning."); + [NSApp terminate:self]; + } else if ( resp == NSModalResponseCancel ) { // Continue + NSLog(@"Preferences after a read-only Sage warning."); + [self showPreferences:self]; + } else { + NSLog(@"Continuing from read-only Sage warning."); + } + } +} + -(IBAction)revealInFinder:(id)sender{ if ( [[sender title] isEqualToString:@"Reveal in Shell"] ) { [self terminalRun:[NSString stringWithFormat:@"cd '%@' && $SHELL", diff --git a/src/mac-app/AppDelegate.m b/src/mac-app/AppDelegate.m index e0e7c7e9958..ee3d295641e 100644 --- a/src/mac-app/AppDelegate.m +++ b/src/mac-app/AppDelegate.m @@ -35,7 +35,7 @@ - (void)applicationWillFinishLaunching:(NSNotification *)aNotification{ if( returnCode != 0 ) { // According to http://www.cocoadev.com/index.pl?TransformProcessType // TransformProcessType is available since 10.3, but doen't work for our case until 10.5 - NSLog(@"Could not show Sage.app in the dock. Error %ld", returnCode); + NSLog(@"Could not show Sage.app in the dock. Error %d", (int)returnCode); // It's forbidden to showInDock since it doesn't work [defaults setBool:NO forKey:@"myShowInDock"]; [defaults synchronize]; diff --git a/src/mac-app/Sage.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/src/mac-app/Sage.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..17080955d9c --- /dev/null +++ b/src/mac-app/Sage.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/src/mac-app/Sage.xcodeproj/project.xcworkspace/xcshareddata/Sage.xccheckout b/src/mac-app/Sage.xcodeproj/project.xcworkspace/xcshareddata/Sage.xccheckout new file mode 100644 index 00000000000..caf5cd0ac1e --- /dev/null +++ b/src/mac-app/Sage.xcodeproj/project.xcworkspace/xcshareddata/Sage.xccheckout @@ -0,0 +1,41 @@ + + + + + IDESourceControlProjectFavoriteDictionaryKey + + IDESourceControlProjectIdentifier + E0D9769F-5D54-4AC1-A951-4D318D9DD866 + IDESourceControlProjectName + Sage + IDESourceControlProjectOriginsDictionary + + 14FAB7F0-79B5-47C3-8938-EE51EF938D3B + git://github.com/sagemath/sage.git + + IDESourceControlProjectPath + src/mac-app/Sage.xcodeproj/project.xcworkspace + IDESourceControlProjectRelativeInstallPathDictionary + + 14FAB7F0-79B5-47C3-8938-EE51EF938D3B + ../../../.. + + IDESourceControlProjectURL + git://github.com/sagemath/sage.git + IDESourceControlProjectVersion + 110 + IDESourceControlProjectWCCIdentifier + 14FAB7F0-79B5-47C3-8938-EE51EF938D3B + IDESourceControlProjectWCConfigurations + + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + 14FAB7F0-79B5-47C3-8938-EE51EF938D3B + IDESourceControlWCCName + sage + + + + diff --git a/src/module_list.py b/src/module_list.py index 6267d39b995..ad3e98ccfae 100755 --- a/src/module_list.py +++ b/src/module_list.py @@ -42,7 +42,8 @@ numpy_depends = [SAGE_LOCAL + '/lib/python/site-packages/numpy/core/include/numpy/_numpyconfig.h'] flint_depends = [SAGE_INC + '/flint/flint.h'] -singular_depends = [SAGE_INC + '/libsingular.h', SAGE_INC + '/givaro/givconfig.h'] +singular_depends = [SAGE_INC + '/libsingular.h'] +givaro_depends = [SAGE_INC + '/givaro/givconfig.h'] ######################################################### ### M4RI flags @@ -57,8 +58,7 @@ m4ri_extra_compile_args.extend( [flag.strip() for flag in m4ri_sse2_cflags.split(" ") if flag.strip()] ) break -singular_libs = ['m', 'readline', 'singular', 'givaro', 'ntl', 'gmpxx', 'gmp'] - +singular_libs = ['singular', 'flint', 'ntl', 'gmpxx', 'gmp', 'readline', 'm'] ######################################################### ### Givaro flags @@ -701,7 +701,7 @@ def uname_specific(name, value, alternative): 'stdc++', 'givaro', 'mpfr', 'gmp', 'gmpxx', BLAS, BLAS2], language = 'c++', extra_compile_args = givaro_extra_compile_args, - depends = [SAGE_INC + '/givaro/givconfig.h']), + depends = givaro_depends), Extension('sage.libs.lcalc.lcalc_Lfunction', sources = ['sage/libs/lcalc/lcalc_Lfunction.pyx'], @@ -761,10 +761,10 @@ def uname_specific(name, value, alternative): Extension('sage.libs.singular.singular', sources = ['sage/libs/singular/singular.pyx'], - libraries = singular_libs, + libraries = ['givaro'] + singular_libs, language="c++", include_dirs = [SAGE_INC + '/singular', SAGE_INC + '/factory'], - depends = singular_depends, + depends = singular_depends + givaro_depends, extra_compile_args = givaro_extra_compile_args), Extension('sage.libs.singular.polynomial', @@ -772,24 +772,21 @@ def uname_specific(name, value, alternative): libraries = singular_libs, language="c++", include_dirs = [SAGE_INC + '/singular', SAGE_INC + '/factory'], - depends = singular_depends, - extra_compile_args = givaro_extra_compile_args), + depends = singular_depends), Extension('sage.libs.singular.ring', sources = ['sage/libs/singular/ring.pyx'], libraries = singular_libs, language="c++", include_dirs = [SAGE_INC + '/singular', SAGE_INC + '/factory'], - depends = singular_depends, - extra_compile_args = givaro_extra_compile_args), + depends = singular_depends), Extension('sage.libs.singular.groebner_strategy', sources = ['sage/libs/singular/groebner_strategy.pyx'], libraries = singular_libs, language="c++", include_dirs = [SAGE_INC + '/singular', SAGE_INC + '/factory'], - depends = singular_depends, - extra_compile_args = givaro_extra_compile_args), + depends = singular_depends), Extension('sage.libs.singular.function', sources = ['sage/libs/singular/function.pyx'], @@ -804,8 +801,7 @@ def uname_specific(name, value, alternative): libraries = singular_libs, language="c++", include_dirs = [SAGE_INC + '/singular', SAGE_INC + '/factory'], - depends = singular_depends, - extra_compile_args = givaro_extra_compile_args), + depends = singular_depends), Extension('sage.libs.symmetrica.symmetrica', sources = ["sage/libs/symmetrica/%s"%s for s in ["symmetrica.pyx"]], @@ -1099,8 +1095,7 @@ def uname_specific(name, value, alternative): libraries = singular_libs, language="c++", include_dirs = [SAGE_INC + '/singular', SAGE_INC + '/factory'], - depends = singular_depends, - extra_compile_args = givaro_extra_compile_args), + depends = singular_depends), Extension('sage.matrix.matrix_rational_dense', sources = ['sage/matrix/matrix_rational_dense.pyx'], @@ -1786,8 +1781,7 @@ def uname_specific(name, value, alternative): libraries = singular_libs, language="c++", include_dirs = [SAGE_INC + '/singular', SAGE_INC + '/factory'], - depends = singular_depends, - extra_compile_args = givaro_extra_compile_args), + depends = singular_depends), Extension('sage.rings.polynomial.plural', sources = ['sage/rings/polynomial/plural.pyx'], @@ -1802,8 +1796,7 @@ def uname_specific(name, value, alternative): libraries = singular_libs, language="c++", include_dirs = [SAGE_INC + '/singular', SAGE_INC + '/factory'], - depends = singular_depends, - extra_compile_args = givaro_extra_compile_args), + depends = singular_depends), Extension('sage.rings.polynomial.multi_polynomial_ring_generic', sources = ['sage/rings/polynomial/multi_polynomial_ring_generic.pyx']), diff --git a/src/sage/algebras/free_algebra.py b/src/sage/algebras/free_algebra.py index a70223c4111..7fecb1f460e 100644 --- a/src/sage/algebras/free_algebra.py +++ b/src/sage/algebras/free_algebra.py @@ -434,7 +434,7 @@ def one_basis(self): sage: F.one_basis().parent() Free monoid on 2 generators (x, y) """ - return self._basis_keys.one() + return self._indices.one() def is_field(self, proof=True): """ @@ -585,7 +585,7 @@ def _element_constructor_(self, x): P = x.parent() if self.has_coerce_map_from(P): # letterplace versus generic ngens = P.ngens() - M = self._basis_keys + M = self._indices def exp_to_monomial(T): out = [] for i in xrange(len(T)): @@ -601,7 +601,7 @@ def exp_to_monomial(T): return self(sage_eval(x, locals=d)) R = self.base_ring() # coercion from free monoid - if isinstance(x, FreeMonoidElement) and x.parent() is self._basis_keys: + if isinstance(x, FreeMonoidElement) and x.parent() is self._indices: return self.element_class(self, {x: R.one()}) # coercion from the PBW basis if isinstance(x, PBWBasisOfFreeAlgebra.Element) \ @@ -678,7 +678,7 @@ def _coerce_map_from_(self, R): sage: F.1 + (z+1) * L.2 b + (z+1)*c """ - if self._basis_keys.has_coerce_map_from(R): + if self._indices.has_coerce_map_from(R): return True # free algebras in the same variable over any base that coerces in: @@ -703,7 +703,7 @@ def gen(self, i): if i < 0 or not i < self.__ngens: raise IndexError("Argument i (= {}) must be between 0 and {}.".format(i, self.__ngens-1)) R = self.base_ring() - F = self._basis_keys + F = self._indices return self.element_class(self, {F.gen(i): R.one()}) @cached_method @@ -805,7 +805,7 @@ def monoid(self): sage: F.monoid() Free monoid on 3 generators (x, y, z) """ - return self._basis_keys + return self._indices def g_algebra(self, relations, names=None, order='degrevlex', check=True): """ @@ -964,7 +964,7 @@ def lie_polynomial(self, w): """ if not w: return self.one() - M = self._basis_keys + M = self._indices if len(w) == 1: return self(M(w)) @@ -1187,7 +1187,7 @@ def one_basis(self): sage: PBW.one_basis().parent() Free monoid on 2 generators (x, y) """ - return self._basis_keys.one() + return self._indices.one() def algebra_generators(self): """ @@ -1201,7 +1201,7 @@ def algebra_generators(self): sage: all(g.parent() is PBW for g in gens) True """ - return tuple(self.monomial(x) for x in self._basis_keys.gens()) + return tuple(self.monomial(x) for x in self._indices.gens()) gens = algebra_generators diff --git a/src/sage/algebras/group_algebra.py b/src/sage/algebras/group_algebra.py index 5195f6b71a9..4df7e70e36b 100644 --- a/src/sage/algebras/group_algebra.py +++ b/src/sage/algebras/group_algebra.py @@ -51,7 +51,7 @@ def __init__(self, group, base_ring = IntegerRing()): EXAMPLES:: sage: from sage.algebras.group_algebra import GroupAlgebra - doctest:1: DeprecationWarning:... + doctest:...: DeprecationWarning:... sage: GroupAlgebra(GL(3, GF(7))) Group algebra of group "General Linear Group of degree 3 over Finite Field of size 7" over base ring Integer Ring diff --git a/src/sage/algebras/steenrod/steenrod_algebra.py b/src/sage/algebras/steenrod/steenrod_algebra.py index fd1c74e9cea..9a2c91a88d1 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra.py +++ b/src/sage/algebras/steenrod/steenrod_algebra.py @@ -2194,7 +2194,7 @@ def basis(self, d=None): """ from sage.sets.family import Family if d is None: - return Family(self._basis_keys, self.monomial) + return Family(self._indices, self.monomial) else: return Family([self.monomial(tuple(a)) for a in self._basis_fcn(d)]) diff --git a/src/sage/all.py b/src/sage/all.py index f4bd4f7f76d..ade355dac4e 100644 --- a/src/sage/all.py +++ b/src/sage/all.py @@ -171,6 +171,8 @@ from sage.matroids.all import * +from sage.game_theory.all import * + # Lazily import notebook functions and interacts (#15335) lazy_import('sagenb.notebook.notebook_object', 'notebook') lazy_import('sagenb.notebook.notebook_object', 'inotebook') diff --git a/src/sage/calculus/calculus.py b/src/sage/calculus/calculus.py index 42429c906e3..194e4de49bd 100644 --- a/src/sage/calculus/calculus.py +++ b/src/sage/calculus/calculus.py @@ -468,6 +468,41 @@ def symbolic_sum(expression, v, a, b, algorithm='maxima'): sage: symbolic_sum(1/k^5, k, 1, oo) zeta(5) + .. WARNING:: + + This function only works with symbolic expressions. To sum any + other objects like list elements or function return values, + please use python summation, see + http://docs.python.org/library/functions.html#sum + + In particular, this does not work:: + + sage: n = var('n') + sage: list=[1,2,3,4,5] + sage: sum(list[n],n,0,3) + Traceback (most recent call last): + ... + TypeError: unable to convert x (=n) to an integer + + Use python ``sum()`` instead:: + + sage: sum(list[n] for n in range(4)) + 10 + + Also, only a limited number of functions are recognized in symbolic sums:: + + sage: sum(valuation(n,2),n,1,5) + Traceback (most recent call last): + ... + AttributeError: 'sage.symbolic.expression.Expression' object has no attribute 'valuation' + + Again, use python ``sum()``:: + + sage: sum(valuation(n+1,2) for n in range(5)) + 3 + + (now back to the Sage ``sum`` examples) + A well known binomial identity:: sage: symbolic_sum(binomial(n,k), k, 0, n) @@ -1048,8 +1083,9 @@ def limit(ex, dir=None, taylor=False, algorithm='maxima', **argv): Traceback (most recent call last): ... ValueError: Computation failed since Maxima requested additional - constraints; using the 'assume' command before limit evaluation - *may* help (see `assume?` for more details) + constraints; using the 'assume' command before evaluation + *may* help (example of legal syntax is 'assume(a>0)', see + `assume?` for more details) Is a positive, negative or zero? With this example, Maxima is looking for a LOT of information:: @@ -1059,16 +1095,18 @@ def limit(ex, dir=None, taylor=False, algorithm='maxima', **argv): Traceback (most recent call last): ... ValueError: Computation failed since Maxima requested additional - constraints; using the 'assume' command before limit evaluation - *may* help (see `assume?` for more details) + constraints; using the 'assume' command before evaluation *may* help + (example of legal syntax is 'assume(a>0)', see `assume?` for + more details) Is a an integer? sage: assume(a,'integer') sage: limit(x^a,x=0) Traceback (most recent call last): ... ValueError: Computation failed since Maxima requested additional - constraints; using the 'assume' command before limit evaluation - *may* help (see `assume?` for more details) + constraints; using the 'assume' command before evaluation *may* help + (example of legal syntax is 'assume(a>0)', see `assume?` for + more details) Is a an even number? sage: assume(a,'even') sage: limit(x^a,x=0) @@ -1692,16 +1730,19 @@ def _inverse_laplace_latex_(self, *args): ####################################################### -symtable = {'%pi':'pi', '%e': 'e', '%i':'I', '%gamma':'euler_gamma'} +# Conversion dict for special maxima objects +# c,k1,k2 are from ode2() +symtable = {'%pi':'pi', '%e': 'e', '%i':'I', '%gamma':'euler_gamma',\ + '%c' : '_C', '%k1' : '_K1', '%k2' : '_K2', + 'e':'_e', 'i':'_i', 'I':'_I'} -from sage.misc.multireplace import multiple_replace import re maxima_tick = re.compile("'[a-z|A-Z|0-9|_]*") maxima_qp = re.compile("\?\%[a-z|A-Z|0-9|_]*") # e.g., ?%jacobi_cd -maxima_var = re.compile("\%[a-z|A-Z|0-9|_]*") # e.g., ?%jacobi_cd +maxima_var = re.compile("[a-z|A-Z|0-9|_\%]*") # e.g., %jacobi_cd sci_not = re.compile("(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]\d+)") @@ -1709,6 +1750,8 @@ def _inverse_laplace_latex_(self, *args): maxima_polygamma = re.compile("psi\[(\d*)\]\(") # matches psi[n]( where n is a number +maxima_hyper = re.compile("\%f\[\d+,\d+\]") # matches %f[m,n] + def symbolic_expression_from_maxima_string(x, equals_sub=False, maxima=maxima): """ Given a string representation of a Maxima expression, parse it and @@ -1761,11 +1804,39 @@ def symbolic_expression_from_maxima_string(x, equals_sub=False, maxima=maxima): sage: sefms("x # 3") == SR(x != 3) True sage: solve([x != 5], x) - #0: solve_rat_ineq(ineq=x # 5) + #0: solve_rat_ineq(ineq=_SAGE_VAR_x # 5) [[x - 5 != 0]] sage: solve([2*x==3, x != 5], x) [[x == (3/2), (-7/2) != 0]] + Make sure that we don't accidentally pick up variables in the maxima namespace (trac #8734):: + + sage: sage.calculus.calculus.maxima('my_new_var : 2') + 2 + sage: var('my_new_var').full_simplify() + my_new_var + + ODE solution constants are treated differently (:trac:`16007`):: + + sage: from sage.calculus.calculus import symbolic_expression_from_maxima_string as sefms + sage: sefms('%k1*x + %k2*y + %c') + _K1*x + _K2*y + _C + + Check that some hypothetical variables don't end up as special constants (:trac:`6882`):: + + sage: from sage.calculus.calculus import symbolic_expression_from_maxima_string as sefms + sage: sefms('%i')^2 + -1 + sage: ln(sefms('%e')) + 1 + sage: sefms('i')^2 + _i^2 + sage: sefms('I')^2 + _I^2 + sage: sefms('ln(e)') + ln(_e) + sage: sefms('%inf') + +Infinity """ syms = sage.symbolic.pynac.symbol_table.get('maxima', {}).copy() @@ -1777,6 +1848,7 @@ def symbolic_expression_from_maxima_string(x, equals_sub=False, maxima=maxima): #r = maxima._eval_line('listofvars(_tmp_);')[1:-1] s = maxima._eval_line('_tmp_;') + s = s.replace("_SAGE_VAR_","") formal_functions = maxima_tick.findall(s) if len(formal_functions) > 0: @@ -1794,15 +1866,28 @@ def symbolic_expression_from_maxima_string(x, equals_sub=False, maxima=maxima): pass else: syms[X[2:]] = function_factory(X[2:]) - s = s.replace("?%","") + s = s.replace("?%", "") + + s = maxima_hyper.sub('hypergeometric', s) + + # Look up every variable in the symtable keys and fill a replacement list. + cursor = 0 + l = [] + for m in maxima_var.finditer(s): + if symtable.has_key(m.group(0)): + l.append(s[cursor:m.start()]) + l.append(symtable.get(m.group(0))) + cursor = m.end() + if cursor > 0: + l.append(s[cursor:]) + s = "".join(l) - s = polylog_ex.sub('polylog(\\1,',s) - s = multiple_replace(symtable, s) s = s.replace("%","") s = s.replace("#","!=") # a lot of this code should be refactored somewhere... - s = maxima_polygamma.sub('psi(\g<1>,',s) # this replaces psi[n](foo) with psi(n,foo), ensuring that derivatives of the digamma function are parsed properly below + s = polylog_ex.sub('polylog(\\1,', s) + s = maxima_polygamma.sub('psi(\g<1>,', s) # this replaces psi[n](foo) with psi(n,foo), ensuring that derivatives of the digamma function are parsed properly below if equals_sub: s = s.replace('=','==') diff --git a/src/sage/calculus/desolvers.py b/src/sage/calculus/desolvers.py index 8b8e5c6249a..d61fc1aa6a6 100644 --- a/src/sage/calculus/desolvers.py +++ b/src/sage/calculus/desolvers.py @@ -3,8 +3,14 @@ This file contains functions useful for solving differential equations which occur commonly in a 1st semester differential equations -course. For another numerical solver see :meth:`ode_solver` function -and optional package Octave. +course. For another numerical solver see the :meth:`ode_solver` function +and the optional package Octave. + +Solutions from the Maxima package can contain the three constants +``_C``, ``_K1``, and ``_K2`` where the underscore is used to distinguish +them from symbolic variables that the user might have used. You can +substitute values for them, and make them into accessible usable +symbolic variables, for example with ``var("_C")``. Commands: @@ -123,7 +129,7 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) sage: x = var('x') sage: y = function('y', x) sage: desolve(diff(y,x) + y - 1, y) - (c + e^x)*e^(-x) + (_C + e^x)*e^(-x) :: @@ -140,7 +146,7 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) sage: y = function('y', x) sage: de = diff(y,x,2) - y == x sage: desolve(de, y) - k2*e^(-x) + k1*e^x - x + _K2*e^(-x) + _K1*e^x - x :: @@ -162,7 +168,7 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) sage: de = diff(y,x,2) + y == 0 sage: desolve(de, y) - k2*cos(x) + k1*sin(x) + _K2*cos(x) + _K1*sin(x) :: @@ -172,12 +178,12 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) :: sage: desolve(y*diff(y,x)+sin(x)==0,y) - -1/2*y(x)^2 == c - cos(x) + -1/2*y(x)^2 == _C - cos(x) Clairaut equation: general and singular solutions:: sage: desolve(diff(y,x)^2+x*diff(y,x)-y==0,y,contrib_ode=True,show_method=True) - [[y(x) == c^2 + c*x, y(x) == -1/4*x^2], 'clairault'] + [[y(x) == _C^2 + _C*x, y(x) == -1/4*x^2], 'clairault'] For equations involving more variables we specify an independent variable:: @@ -194,7 +200,7 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) Higher order equations, not involving independent variable:: sage: desolve(diff(y,x,2)+y*(diff(y,x,1))^3==0,y).expand() - 1/6*y(x)^3 + k1*y(x) == k2 + x + 1/6*y(x)^3 + _K1*y(x) == _K2 + x :: @@ -209,12 +215,12 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) Separable equations - Sage returns solution in implicit form:: sage: desolve(diff(y,x)*sin(y) == cos(x),y) - -cos(y(x)) == c + sin(x) + -cos(y(x)) == _C + sin(x) :: sage: desolve(diff(y,x)*sin(y) == cos(x),y,show_method=True) - [-cos(y(x)) == c + sin(x), 'separable'] + [-cos(y(x)) == _C + sin(x), 'separable'] :: @@ -224,12 +230,12 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) Linear equation - Sage returns the expression on the right hand side only:: sage: desolve(diff(y,x)+(y) == cos(x),y) - 1/2*((cos(x) + sin(x))*e^x + 2*c)*e^(-x) + 1/2*((cos(x) + sin(x))*e^x + 2*_C)*e^(-x) :: sage: desolve(diff(y,x)+(y) == cos(x),y,show_method=True) - [1/2*((cos(x) + sin(x))*e^x + 2*c)*e^(-x), 'linear'] + [1/2*((cos(x) + sin(x))*e^x + 2*_C)*e^(-x), 'linear'] :: @@ -241,16 +247,16 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) into `e^{x}e^{y}`:: sage: desolve(diff(y,x)==exp(x-y),y,show_method=True) - [-e^x + e^y(x) == c, 'exact'] + [-e^x + e^y(x) == _C, 'exact'] You can solve Bessel equations, also using initial conditions, but you cannot put (sometimes desired) the initial condition at x=0, since this point is a singular point of the equation. Anyway, if the solution should be bounded at x=0, then - k2=0.:: + _K2=0.:: sage: desolve(x^2*diff(y,x,x)+x*diff(y,x)+(x^2-4)*y==0,y) - k1*bessel_J(2, x) + k2*bessel_Y(2, x) + _K1*bessel_J(2, x) + _K2*bessel_Y(2, x) Example of difficult ODE producing an error:: @@ -266,12 +272,12 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) Some more types of ODE's:: sage: desolve(x*diff(y,x)^2-(1+x*y)*diff(y,x)+y==0,y,contrib_ode=True,show_method=True) - [[y(x) == c + log(x), y(x) == c*e^x], 'factor'] + [[y(x) == _C*e^x, y(x) == _C + log(x)], 'factor'] :: sage: desolve(diff(y,x)==(x+y)^2,y,contrib_ode=True,show_method=True) - [[[x == c - arctan(sqrt(t)), y(x) == -x - sqrt(t)], [x == c + arctan(sqrt(t)), y(x) == -x + sqrt(t)]], 'lagrange'] + [[[x == _C - arctan(sqrt(t)), y(x) == -x - sqrt(t)], [x == _C + arctan(sqrt(t)), y(x) == -x + sqrt(t)]], 'lagrange'] These two examples produce an error (as expected, Maxima 5.18 cannot solve equations from initial conditions). Maxima 5.18 @@ -292,12 +298,12 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) Second order linear ODE:: sage: desolve(diff(y,x,2)+2*diff(y,x)+y == cos(x),y) - (k2*x + k1)*e^(-x) + 1/2*sin(x) + (_K2*x + _K1)*e^(-x) + 1/2*sin(x) :: sage: desolve(diff(y,x,2)+2*diff(y,x)+y == cos(x),y,show_method=True) - [(k2*x + k1)*e^(-x) + 1/2*sin(x), 'variationofparameters'] + [(_K2*x + _K1)*e^(-x) + 1/2*sin(x), 'variationofparameters'] :: @@ -322,12 +328,12 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) :: sage: desolve(diff(y,x,2)+2*diff(y,x)+y == 0,y) - (k2*x + k1)*e^(-x) + (_K2*x + _K1)*e^(-x) :: sage: desolve(diff(y,x,2)+2*diff(y,x)+y == 0,y,show_method=True) - [(k2*x + k1)*e^(-x), 'constcoeff'] + [(_K2*x + _K1)*e^(-x), 'constcoeff'] :: @@ -357,7 +363,7 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) sage: sage.calculus.calculus.maxima('domain:real') # needed since Maxima 5.26.0 to get the answer as below real sage: desolve(x*diff(y,x)-x*sqrt(y^2+x^2)-y == 0, y, contrib_ode=True) - [x - arcsinh(y(x)/x) == c] + [x - arcsinh(y(x)/x) == _C] Trac #10682 updated Maxima to 5.26, and it started to show a different solution in the complex domain for the ODE above:: @@ -368,7 +374,7 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) [1/2*(2*x^2*sqrt(x^(-2)) - 2*x*sqrt(x^(-2))*arcsinh(y(x)/sqrt(x^2)) - 2*x*sqrt(x^(-2))*arcsinh(y(x)^2/(x*sqrt(y(x)^2))) + log(4*(2*x^2*sqrt((x^2*y(x)^2 + y(x)^4)/x^2)*sqrt(x^(-2)) + x^2 + - 2*y(x)^2)/x^2))/(x*sqrt(x^(-2))) == c] + 2*y(x)^2)/x^2))/(x*sqrt(x^(-2))) == _C] Trac #6479 fixed:: @@ -395,7 +401,7 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) sage: x=var('x'); f=function('f',x); k=var('k'); assume(k>0) sage: desolve(diff(f,x,2)/f==k,f,ivar=x) - k1*e^(sqrt(k)*x) + k2*e^(-sqrt(k)*x) + _K1*e^(sqrt(k)*x) + _K2*e^(-sqrt(k)*x) AUTHORS: @@ -420,13 +426,13 @@ def desolve(de, dvar, ics=None, ivar=None, show_method=False, contrib_ode=False) if len(ivars) != 1: raise ValueError("Unable to determine independent variable, please specify.") ivar = ivars[0] - def sanitize_var(exprs): - return exprs.replace("'"+dvar_str+"("+ivar_str+")",dvar_str) de00 = de._maxima_() P = de00.parent() dvar_str=P(dvar.operator()).str() ivar_str=P(ivar).str() de00 = de00.str() + def sanitize_var(exprs): + return exprs.replace("'"+dvar_str+"("+ivar_str+")",dvar_str) de0 = sanitize_var(de00) ode_solver="ode2" cmd="(TEMP:%s(%s,%s,%s), if TEMP=false then TEMP else substitute(%s=%s(%s),TEMP))"%(ode_solver,de0,dvar_str,ivar_str,dvar_str,dvar_str,ivar_str) @@ -660,11 +666,14 @@ def desolve_laplace(de, dvar, ics=None, ivar=None): ivar = ivars[0] ## verbatim copy from desolve - end + dvar_str = str(dvar) def sanitize_var(exprs): # 'y(x) -> y(x) - return exprs.replace("'"+str(dvar),str(dvar)) + return exprs.replace("'"+dvar_str,dvar_str) de0=de._maxima_() P = de0.parent() - cmd = sanitize_var("desolve("+de0.str()+","+str(dvar)+")") + i = dvar_str.find('(') + dvar_str = dvar_str[:i+1] + '_SAGE_VAR_' + dvar_str[i+1:] + cmd = sanitize_var("desolve("+de0.str()+","+dvar_str+")") soln=P(cmd).rhs() if str(soln).strip() == 'false': raise NotImplementedError("Maxima was unable to solve this ODE.") @@ -836,16 +845,17 @@ def desolve_system_strings(des,vars,ics=None): maxima.eval(cmd) desstr = "[" + ",".join(dess) + "]" d = len(vars) - varss = list("'" + vars[i] + "(" + vars[0] + ")" for i in range(1,d)) + varss = list("'" + vars[i] + "(_SAGE_VAR_" + vars[0] + ")" for i in range(1,d)) varstr = "[" + ",".join(varss) + "]" if ics is not None: #d = len(ics) ## must be same as len(des) for i in range(1,d): - ic = "atvalue('" + vars[i] + "("+vars[0] + ")," + str(vars[0]) + "=" + str(ics[0]) + "," + str(ics[i]) + ")" + ic = "atvalue('" + vars[i] + "(_SAGE_VAR_"+vars[0] + ")," + "_SAGE_VAR_"\ + + str(vars[0]) + "=" + str(ics[0]) + "," + str(ics[i]) + ")" maxima.eval(ic) cmd = "desolve(" + desstr + "," + varstr + ");" soln = maxima(cmd) - return [f.rhs()._maxima_init_() for f in soln] + return [f.rhs()._maxima_init_().replace("_SAGE_VAR_"+vars[0],vars[0]) for f in soln] @rename_keyword(deprecation=6094, method="algorithm") def eulers_method(f,x0,y0,h,x1,algorithm="table"): @@ -1223,13 +1233,13 @@ def desolve_rk4(de, dvar, ics=None, ivar=None, end_points=None, step=0.1, output sol_1, sol_2 = [],[] if lower_boundics[0]: cmd="rk(%s,%s,%s,[%s,%s,%s,%s])\ - "%(de0.str(),str(dummy_dvar),str(ics[1]),str(ivar),str(ics[0]),upper_bound,step) + "%(de0.str(),'_SAGE_VAR_'+str(dummy_dvar),str(ics[1]),'_SAGE_VAR_'+str(ivar),str(ics[0]),upper_bound,step) sol_2=maxima(cmd).sage() sol_2.pop(0) sol=sol_1 @@ -1343,13 +1353,13 @@ def desolve_system_rk4(des, vars, ics=None, ivar=None, end_points=None, step=0.1 sol_1, sol_2 = [],[] if lower_boundics[0]: cmd="rk(%s,%s,%s,[%s,%s,%s,%s])\ - "%(desstr,varstr,icstr,str(ivar),str(x0),upper_bound,step) + "%(desstr,varstr,icstr,'_SAGE_VAR_'+str(ivar),str(x0),upper_bound,step) sol_2=maxima(cmd).sage() sol_2.pop(0) sol=sol_1 diff --git a/src/sage/calculus/functional.py b/src/sage/calculus/functional.py index e2c06b36c99..9317e7e058a 100644 --- a/src/sage/calculus/functional.py +++ b/src/sage/calculus/functional.py @@ -243,7 +243,7 @@ def integral(f, *args, **kwds): Traceback (most recent call last): ... ValueError: Computation failed since Maxima requested additional - constraints; using the 'assume' command before integral evaluation + constraints; using the 'assume' command before evaluation *may* help (example of legal syntax is 'assume(a>0)', see `assume?` for more details) Is a positive, negative or zero? diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index 36200017d0d..02cc4375e14 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -2872,9 +2872,9 @@ def _without_axioms(self, named=False): Traceback (most recent call last): ... ValueError: This join category isn't built by adding axioms to a single category - sage: C = Monoids().Commutative() + sage: C = Monoids().Infinite() sage: C._repr_(as_join=True) - 'Join of Category of monoids and Category of commutative magmas' + 'Join of Category of monoids and Category of infinite sets' sage: C._without_axioms() Category of magmas sage: C._without_axioms(named=True) @@ -2922,7 +2922,7 @@ def _repr_object_names(self): EXAMPLES:: sage: Groups().Finite().Commutative()._repr_(as_join=True) - 'Join of Category of finite groups and Category of commutative magmas' + 'Join of Category of finite groups and Category of commutative groups' sage: Groups().Finite().Commutative()._repr_object_names() 'finite commutative groups' diff --git a/src/sage/categories/commutative_rings.py b/src/sage/categories/commutative_rings.py index 8986c2530c9..b3978cea797 100644 --- a/src/sage/categories/commutative_rings.py +++ b/src/sage/categories/commutative_rings.py @@ -25,7 +25,7 @@ class CommutativeRings(CategoryWithAxiom): sage: C = CommutativeRings(); C Category of commutative rings sage: C.super_categories() - [Category of rings, Category of commutative magmas] + [Category of rings, Category of commutative monoids] TESTS:: diff --git a/src/sage/categories/fields.py b/src/sage/categories/fields.py index 0a1de703129..197333fccf9 100644 --- a/src/sage/categories/fields.py +++ b/src/sage/categories/fields.py @@ -6,7 +6,7 @@ # William Stein # 2008 Teresa Gomez-Diaz (CNRS) # 2008-2009 Nicolas M. Thiery -# 2012 Julian Rueth +# 2012-2014 Julian Rueth # # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ @@ -182,7 +182,6 @@ def is_field( self, proof=True ): def is_integrally_closed(self): r""" - Return ``True``, as per :meth:`IntegralDomain.is_integrally_closed`: for every field `F`, `F` is its own field of fractions, hence every element of `F` is integral over `F`. @@ -200,6 +199,25 @@ def is_integrally_closed(self): """ return True + def is_perfect(self): + r""" + Return whether this field is perfect, i.e., its characteristic is + `p=0` or every element has a `p`-th root. + + EXAMPLES:: + + sage: QQ.is_perfect() + True + sage: GF(2).is_perfect() + True + sage: FunctionField(GF(2), 'x').is_perfect() + False + + """ + if self.characteristic() == 0: + return True + else: raise NotImplementedError + def _test_characteristic_fields(self, **options): """ Run generic tests on the method :meth:`.characteristic`. @@ -241,6 +259,75 @@ def fraction_field(self): """ return self + def _squarefree_decomposition_univariate_polynomial(self, f): + r""" + Return the square-free decomposition of ``f`` over this field. + + This is a helper method for + :meth:`sage.rings.polynomial.squarefree_decomposition`. + + INPUT: + + - ``f`` -- a univariate non-zero polynomial over this field + + ALGORITHM: For rings of characteristic zero, we use the algorithm + descriped in [Yun]_. Other fields may provide their own + implementation by overriding this method. + + EXAMPLES:: + + sage: x = polygen(QQ) + sage: p = 37 * (x-1)^3 * (x-2)^3 * (x-1/3)^7 * (x-3/7) + sage: p.squarefree_decomposition() + (37*x - 111/7) * (x^2 - 3*x + 2)^3 * (x - 1/3)^7 + sage: p = 37 * (x-2/3)^2 + sage: p.squarefree_decomposition() + (37) * (x - 2/3)^2 + sage: x = polygen(GF(3)) + sage: x.squarefree_decomposition() + x + sage: f = QQbar['x'](1) + sage: f.squarefree_decomposition() + 1 + + REFERENCES: + + .. [Yun] Yun, David YY. On square-free decomposition algorithms. + In Proceedings of the third ACM symposium on Symbolic and algebraic + computation, pp. 26-35. ACM, 1976. + + """ + from sage.structure.factorization import Factorization + if f.degree() == 0: + return Factorization([], unit=f[0]) + if self.characteristic() != 0: + raise NotImplementedError("square-free decomposition not implemented for this polynomial.") + + factors = [] + cur = f + f = [f] + while cur.degree() > 0: + cur = cur.gcd(cur.derivative()) + f.append(cur) + + g = [] + for i in range(len(f) - 1): + g.append(f[i] // f[i+1]) + + a = [] + for i in range(len(g) - 1): + a.append(g[i] // g[i+1]) + a.append(g[-1]) + + unit = f[-1] + for i in range(len(a)): + if a[i].degree() > 0: + factors.append((a[i], i+1)) + else: + unit = unit * a[i].constant_coefficient() ** (i + 1) + + return Factorization(factors, unit=unit, sort=False) + def __pow__(self, n): r""" Returns the vector space of dimension `n` over ``self``. diff --git a/src/sage/categories/finite_posets.py b/src/sage/categories/finite_posets.py index b674a34e02c..dddf0d8a878 100644 --- a/src/sage/categories/finite_posets.py +++ b/src/sage/categories/finite_posets.py @@ -1191,9 +1191,9 @@ def birational_rowmotion(self, labelling): ....: # send order ideal `I` to a `T`-labelling of `P`. ....: dct = {v: TT(v in I) for v in P} ....: return (TT, dct, TT(1), TT(0)) - sage: all( indicator_labelling(P.rowmotion(I)) - ....: == P.birational_rowmotion(indicator_labelling(I)) - ....: for I in P.order_ideals_lattice() ) + sage: all(indicator_labelling(P.rowmotion(I)) + ....: == P.birational_rowmotion(indicator_labelling(I)) + ....: for I in P.order_ideals_lattice(facade=True)) True TESTS: @@ -1361,7 +1361,7 @@ def toggling_orbits(self, vs, element_constructor = set): """ # TODO: implement a generic function taking a set and # bijections on this set, and returning the orbits. - OI = set(self.order_ideals_lattice()) + OI = set(self.order_ideals_lattice(facade=True)) orbits = [] while OI: A = OI.pop() @@ -1669,7 +1669,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): + def order_ideals_lattice(self, as_ideals=True, facade=False): r""" Return the lattice of order ideals of a poset ``self``, ordered by inclusion. @@ -1725,17 +1725,19 @@ def order_ideals_lattice(self, as_ideals=True): if as_ideals: from sage.misc.misc import attrcall from sage.sets.set import Set - ideals = [Set( self.order_ideal(antichain) ) for antichain in self.antichains()] - return LatticePoset((ideals,attrcall("issubset"))) + ideals = [Set(self.order_ideal(antichain)) + for antichain in self.antichains()] + return LatticePoset((ideals, attrcall("issubset")), + facade=facade) else: from sage.misc.cachefunc import cached_function antichains = [tuple(a) for a in self.antichains()] @cached_function - def is_above(a,xb): - return any(self.is_lequal(xa,xb) for xa in a) - def cmp(a,b): - return all(is_above(a,xb) for xb in b) - return LatticePoset((antichains,cmp)) + def is_above(a, xb): + return any(self.is_lequal(xa, xb) for xa in a) + def cmp(a, b): + return all(is_above(a, xb) for xb in b) + return LatticePoset((antichains, cmp), facade=facade) @abstract_method(optional = True) def antichains(self): diff --git a/src/sage/categories/graded_modules_with_basis.py b/src/sage/categories/graded_modules_with_basis.py index 65a4cdee7ff..9bb99fc5a95 100644 --- a/src/sage/categories/graded_modules_with_basis.py +++ b/src/sage/categories/graded_modules_with_basis.py @@ -65,9 +65,9 @@ def basis(self, d=None): """ from sage.sets.family import Family if d is None: - return Family(self._basis_keys, self.monomial) + return Family(self._indices, self.monomial) else: - return Family(self._basis_keys.subset(size=d), self.monomial) + return Family(self._indices.subset(size=d), self.monomial) class ElementMethods: diff --git a/src/sage/categories/groups.py b/src/sage/categories/groups.py index 2811e6e1e33..fb41d4fc6fc 100644 --- a/src/sage/categories/groups.py +++ b/src/sage/categories/groups.py @@ -45,6 +45,45 @@ def example(self): from sage.groups.matrix_gps.linear import GL return GL(4,QQ) + @staticmethod + def free(index_set=None, names=None, **kwds): + r""" + Return the free group. + + INPUT: + + - ``index_set`` -- (optional) an index set for the generators; if + an integer, then this represents `\{0, 1, \ldots, n-1\}` + + - ``names`` -- a string or list/tuple/iterable of strings + (default: ``'x'``); the generator names or name prefix + + When the index set is an integer or only variable names are given, + this returns :class:`~sage.groups.free_group.FreeGroup_class`, which + currently has more features due to the interface with GAP than + :class:`~sage.groups.indexed_free_group.IndexedFreeGroup`. + + EXAMPLES:: + + sage: Groups.free(index_set=ZZ) + Free group indexed by Integer Ring + sage: Groups().free(ZZ) + Free group indexed by Integer Ring + sage: Groups().free(5) + Free Group on generators {x0, x1, x2, x3, x4} + sage: F. = Groups().free(); F + Free Group on generators {x, y, z} + """ + from sage.rings.all import ZZ + if index_set in ZZ or (index_set is None and names is not None): + from sage.groups.free_group import FreeGroup + if names is None: + return FreeGroup(index_set, **kwds) + return FreeGroup(index_set, names, **kwds) + + from sage.groups.indexed_free_group import IndexedFreeGroup + return IndexedFreeGroup(index_set, **kwds) + class ParentMethods: def group_generators(self): @@ -364,6 +403,57 @@ class ElementMethods: Finite = LazyImport('sage.categories.finite_groups', 'FiniteGroups') #Algebras = LazyImport('sage.categories.group_algebras', 'GroupAlgebras') + class Commutative(CategoryWithAxiom): + """ + Category of commutative (abelian) groups. + + A group `G` is *commutative* if `xy = yx` for all `x,y \in G`. + """ + @staticmethod + def free(index_set=None, names=None, **kwds): + r""" + Return the free commutative group. + + INPUT: + + - ``index_set`` -- (optional) an index set for the generators; if + an integer, then this represents `\{0, 1, \ldots, n-1\}` + + - ``names`` -- a string or list/tuple/iterable of strings + (default: ``'x'``); the generator names or name prefix + + EXAMPLES:: + + sage: Groups.Commutative.free(index_set=ZZ) + Free abelian group indexed by Integer Ring + sage: Groups().Commutative().free(ZZ) + Free abelian group indexed by Integer Ring + sage: Groups().Commutative().free(5) + Multiplicative Abelian group isomorphic to Z x Z x Z x Z x Z + sage: F. = Groups().Commutative().free(); F + Multiplicative Abelian group isomorphic to Z x Z x Z + """ + from sage.rings.all import ZZ + if names is not None: + if isinstance(names, str): + if ',' not in names and index_set in ZZ: + names = [names + repr(i) for i in range(index_set)] + else: + names = names.split(',') + names = tuple(names) + if index_set is None: + index_set = ZZ(len(names)) + if index_set in ZZ: + from sage.groups.abelian_gps.abelian_group import AbelianGroup + return AbelianGroup(index_set, names=names, **kwds) + + if index_set in ZZ: + from sage.groups.abelian_gps.abelian_group import AbelianGroup + return AbelianGroup(index_set, **kwds) + + from sage.groups.indexed_free_group import IndexedFreeAbelianGroup + return IndexedFreeAbelianGroup(index_set, names=names, **kwds) + class Algebras(AlgebrasCategory): r""" The category of group algebras over a given base ring. diff --git a/src/sage/categories/homset.py b/src/sage/categories/homset.py index 79dbcd7cf33..f63e972e416 100644 --- a/src/sage/categories/homset.py +++ b/src/sage/categories/homset.py @@ -668,7 +668,7 @@ def __hash__(self): sage: E = EllipticCurve('37a') sage: H = E(0).parent(); H Abelian group of points on Elliptic Curve defined by y^2 + y = x^3 - x over Rational Field - sage: hash(H) + sage: hash(H) # random output -1145411691 # 32-bit -8446824869798451307 # 64-bit """ diff --git a/src/sage/categories/modules_with_basis.py b/src/sage/categories/modules_with_basis.py index 33621d6226f..32ac456cce5 100644 --- a/src/sage/categories/modules_with_basis.py +++ b/src/sage/categories/modules_with_basis.py @@ -201,7 +201,7 @@ def basis(self): [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] """ from sage.combinat.family import Family - return Family(self._basis_keys, self.monomial) + return Family(self._indices, self.monomial) def module_morphism(self, on_basis = None, diagonal = None, triangular = None, **keywords): r""" diff --git a/src/sage/categories/monoids.py b/src/sage/categories/monoids.py index dfe4f4ce828..8b1c698c5c6 100644 --- a/src/sage/categories/monoids.py +++ b/src/sage/categories/monoids.py @@ -65,6 +65,48 @@ class Monoids(CategoryWithAxiom): Finite = LazyImport('sage.categories.finite_monoids', 'FiniteMonoids', at_startup=True) Inverse = LazyImport('sage.categories.groups', 'Groups', at_startup=True) + @staticmethod + def free(index_set=None, names=None, **kwds): + r""" + Return a free monoid on `n` generators or with the generators + indexed by a set `I`. + + A free monoid is constructed by specifing either: + + - the number of generators and/or the names of the generators + - the indexing set for the generators + + INPUT: + + - ``index_set`` -- (optional) an index set for the generators; if + an integer, then this represents `\{0, 1, \ldots, n-1\}` + + - ``names`` -- a string or list/tuple/iterable of strings + (default: ``'x'``); the generator names or name prefix + + EXAMPLES:: + + sage: Monoids.free(index_set=ZZ) + Free monoid indexed by Integer Ring + sage: Monoids().free(ZZ) + Free monoid indexed by Integer Ring + sage: F. = Monoids().free(); F + Free monoid indexed by {'x', 'y', 'z'} + """ + if names is not None: + if isinstance(names, str): + from sage.rings.all import ZZ + if ',' not in names and index_set in ZZ: + names = [names + repr(i) for i in range(index_set)] + else: + names = names.split(',') + names = tuple(names) + if index_set is None: + index_set = names + + from sage.monoids.indexed_free_monoid import IndexedFreeMonoid + return IndexedFreeMonoid(index_set, names=names, **kwds) + class ParentMethods: def one_element(self): @@ -194,6 +236,54 @@ def _pow_naive(self, n): result *= self return result + class Commutative(CategoryWithAxiom): + """ + Category of commutative (abelian) monoids. + + A monoid `M` is *commutative* if `xy = yx` for all `x,y \in M`. + """ + @staticmethod + def free(index_set=None, names=None, **kwds): + r""" + Return a free abelian monoid on `n` generators or with + the generators indexed by a set `I`. + + A free monoid is constructed by specifing either: + + - the number of generators and/or the names of the generators, or + - the indexing set for the generators. + + INPUT: + + - ``index_set`` -- (optional) an index set for the generators; if + an integer, then this represents `\{0, 1, \ldots, n-1\}` + + - ``names`` -- a string or list/tuple/iterable of strings + (default: ``'x'``); the generator names or name prefix + + EXAMPLES:: + + sage: Monoids.Commutative.free(index_set=ZZ) + Free abelian monoid indexed by Integer Ring + sage: Monoids().Commutative().free(ZZ) + Free abelian monoid indexed by Integer Ring + sage: F. = Monoids().Commutative().free(); F + Free abelian monoid indexed by {'x', 'y', 'z'} + """ + if names is not None: + if isinstance(names, str): + from sage.rings.all import ZZ + if ',' not in names and index_set in ZZ: + names = [names + repr(i) for i in range(index_set)] + else: + names = names.split(',') + names = tuple(names) + if index_set is None: + index_set = names + + from sage.monoids.indexed_free_monoid import IndexedFreeAbelianMonoid + return IndexedFreeAbelianMonoid(index_set, names=names, **kwds) + class WithRealizations(WithRealizationsCategory): class ParentMethods: diff --git a/src/sage/categories/posets.py b/src/sage/categories/posets.py index 2b32d8918b9..1d2f389e5a4 100644 --- a/src/sage/categories/posets.py +++ b/src/sage/categories/posets.py @@ -404,18 +404,18 @@ def order_ideal_toggle(self, I, v): sage: P.order_ideal_toggle(I, 4) {1, 2, 4} sage: P4 = Posets(4) - sage: all( all( all( P.order_ideal_toggle(P.order_ideal_toggle(I, i), i) == I - ....: for i in range(4) ) - ....: for I in P.order_ideals_lattice() ) - ....: for P in P4 ) + sage: all(all(all(P.order_ideal_toggle(P.order_ideal_toggle(I, i), i) == I + ....: for i in range(4)) + ....: for I in P.order_ideals_lattice(facade=True)) + ....: for P in P4) True """ if not v in I: - if all( u in I for u in self.lower_covers(v) ): + if all(u in I for u in self.lower_covers(v)): from sage.sets.set import Set return I.union(Set({v})) else: - if all( u not in I for u in self.upper_covers(v) ): + if all(u not in I for u in self.upper_covers(v)): from sage.sets.set import Set return I.difference(Set({v})) return I diff --git a/src/sage/categories/primer.py b/src/sage/categories/primer.py index 62176f48924..99a864f5054 100644 --- a/src/sage/categories/primer.py +++ b/src/sage/categories/primer.py @@ -348,7 +348,7 @@ Category of unique factorization domains, Category of gcd domains, Category of integral domains, Category of domains, Category of commutative rings, Category of rings, ... - Category of magmas and additive magmas, + Category of magmas and additive magmas, ... Category of monoids, Category of semigroups, Category of commutative magmas, Category of unital magmas, Category of magmas, Category of commutative additive groups, ..., Category of additive magmas, diff --git a/src/sage/coding/code_constructions.py b/src/sage/coding/code_constructions.py index 48d9b629cd0..4992099da19 100644 --- a/src/sage/coding/code_constructions.py +++ b/src/sage/coding/code_constructions.py @@ -980,7 +980,7 @@ def HammingCode(r,F): ``HammingCode`` is still available in the global namespace:: sage: HammingCode(3,GF(2)) - doctest:1: DeprecationWarning: This method soon will not be available in that way anymore. To use it, you can now call it by typing codes.HammingCode + doctest:...: DeprecationWarning: This method soon will not be available in that way anymore. To use it, you can now call it by typing codes.HammingCode See http://trac.sagemath.org/15445 for details. Linear code of length 7, dimension 4 over Finite Field of size 2 @@ -1296,7 +1296,7 @@ def ReedSolomonCode(n,k,F,pts = None): ``ReedSolomonCode`` is still available in the global namespace:: sage: ReedSolomonCode(6,4,GF(7)) - doctest:1: DeprecationWarning: This method soon will not be available in that way anymore. To use it, you can now call it by typing codes.ReedSolomonCode + doctest:...: DeprecationWarning: This method soon will not be available in that way anymore. To use it, you can now call it by typing codes.ReedSolomonCode See http://trac.sagemath.org/15445 for details. Linear code of length 6, dimension 4 over Finite Field of size 7 diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index 4c27445c5fb..c71456a3985 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -3,10 +3,9 @@ CombinatorialObject, CombinatorialClass, FilteredCombinatorialClass, \ UnionCombinatorialClass, MapCombinatorialClass, \ InfiniteAbstractCombinatorialClass, \ - number_of_combinations, number_of_arrangements, \ - derangements, number_of_derangements, tuples, number_of_tuples, \ + tuples, number_of_tuples, \ unordered_tuples, number_of_unordered_tuples, permutations, \ - permutations_iterator, number_of_permutations, cyclic_permutations, \ + cyclic_permutations, \ cyclic_permutations_iterator, bell_polynomial, fibonacci_sequence, \ fibonacci_xrange, bernoulli_polynomial diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index 0b61ebe8924..56b9aa6eb2b 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -2158,76 +2158,6 @@ def __iter__(self): ##################################################### #### combinatorial sets/lists -def number_of_combinations(mset,k): - """ - Returns the size of combinations(mset,k). IMPLEMENTATION: Wraps - GAP's NrCombinations. - - EXAMPLES:: - - sage: mset = [1,1,2,3,4,4,5] - sage: number_of_combinations(mset,2) - doctest:1: DeprecationWarning: Use Combinations(mset,k).cardinality() instead. - See http://trac.sagemath.org/14138 for details. - 12 - """ - from sage.combinat.combination import Combinations - from sage.misc.superseded import deprecation - deprecation(14138, 'Use Combinations(mset,k).cardinality() instead.') - return Combinations(mset,k).cardinality() - -def number_of_arrangements(mset,k): - """ - Returns the size of arrangements(mset,k). - - EXAMPLES:: - - sage: mset = [1,1,2,3,4,4,5] - sage: number_of_arrangements(mset,2) - doctest:1: DeprecationWarning: Use Arrangements(mset,k).cardinality() instead. - See http://trac.sagemath.org/14138 for details. - 22 - """ - from sage.combinat.permutation import Arrangements - from sage.misc.superseded import deprecation - deprecation(14138, 'Use Arrangements(mset,k).cardinality() instead.') - return Arrangements(mset, k).cardinality() - -def derangements(mset): - """ - This is deprecated in :trac:`9005`. Use instead :class:`Derangements`. - - EXAMPLES:: - - sage: mset = [1,2,3,4] - sage: D = derangements(mset); D - doctest:1: DeprecationWarning: derangements() is deprecated. Use Derangements instead. - See http://trac.sagemath.org/9005 for details. - Derangements of the set [1, 2, 3, 4] - """ - from sage.misc.superseded import deprecation - deprecation(9005,'derangements() is deprecated. Use Derangements instead.') - from sage.combinat.derangements import Derangements - return Derangements(mset) - -def number_of_derangements(mset): - """ - This is deprecated in :trac:`9005`. Use :meth:`Derangements.cardinality()` - instead. - - EXAMPLES:: - - sage: mset = [1,2,3,4] - sage: number_of_derangements(mset) - doctest:1: DeprecationWarning: number_of_derangements() is deprecated. Use Derangements.cardinality() instead. - See http://trac.sagemath.org/9005 for details. - 9 - """ - from sage.misc.superseded import deprecation - deprecation(9005,'number_of_derangements() is deprecated. Use Derangements.cardinality() instead.') - from sage.combinat.derangements import Derangements - return Derangements(mset).cardinality() - def tuples(S,k): """ An ordered tuple of length k of set is an ordered selection with @@ -2378,100 +2308,6 @@ def permutations(mset): ans = Permutations(mset) return ans.list() -def permutations_iterator(mset,n=None): - """ - Do not use this function. It is deprecated in :trac:`14138`. - Use Permutations instead. For example, instead of - - ``for p in permutations_iterator(range(1, m+1), n)`` - - use - - ``for p in Permutations(m, n)``. - - Note that :class:`Permutations`, unlike this function, treats repeated - elements as identical. - - If you insist on using this now: - - Returns an iterator (http://docs.python.org/lib/typeiter.html) - which can be used in place of permutations(mset) if all you need it - for is a 'for' loop. - - Posted by Raymond Hettinger, 2006/03/23, to the Python Cookbook: - http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/474124 - - Note- This function considers repeated elements as different - entries, so for example:: - - sage: from sage.combinat.combinat import permutations, permutations_iterator - sage: mset = [1,2,2] - sage: permutations(mset) - doctest:...: DeprecationWarning: Use the Permutations object instead. - See http://trac.sagemath.org/14772 for details. - [[1, 2, 2], [2, 1, 2], [2, 2, 1]] - sage: for p in permutations_iterator(mset): print p - doctest:...: DeprecationWarning: Use the Permutations object instead. - See http://trac.sagemath.org/14138 for details. - [1, 2, 2] - [2, 1, 2] - [2, 2, 1] - [2, 1, 2] - [2, 2, 1] - - EXAMPLES:: - - sage: X = permutations_iterator(range(3),2) - sage: [x for x in X] - [[0, 1], [0, 2], [1, 0], [1, 2], [2, 0], [2, 1]] - """ - from sage.misc.superseded import deprecation - deprecation(14138, 'Use the Permutations object instead.') - items = mset - if n is None: - n = len(items) - from sage.combinat.permutation import Permutations - for i in range(len(items)): - v = items[i:i+1] - if n == 1: - yield v - else: - rest = items[:i] + items[i+1:] - for p in Permutations(rest, n-1): - yield v + list(p) - -def number_of_permutations(mset): - """ - Do not use this function. It was deprecated in :trac:`14138`. - Use :class:`Permutations` instead. For example, instead of - - ``number_of_permutations(mset)`` - - use - - ``Permutations(mset).cardinality()``. - - If you insist on using this now: - - Returns the size of permutations(mset). - - AUTHORS: - - - Robert L. Miller - - EXAMPLES:: - - sage: mset = [1,1,2,2,2] - sage: number_of_permutations(mset) - doctest:...: DeprecationWarning: Use the Permutations object instead. - See http://trac.sagemath.org/14138 for details. - 10 - """ - from sage.misc.superseded import deprecation - deprecation(14138, 'Use the Permutations object instead.') - from sage.combinat.permutation import Permutations - return Permutations(mset).cardinality() - def cyclic_permutations(mset): """ This function is deprecated in :trac:`14772`. Use instead diff --git a/src/sage/combinat/combinatorial_algebra.py b/src/sage/combinat/combinatorial_algebra.py index 2f1fba5b469..60b48eb39fc 100644 --- a/src/sage/combinat/combinatorial_algebra.py +++ b/src/sage/combinat/combinatorial_algebra.py @@ -38,7 +38,7 @@ sage: ps(2) 2*p[] -The important things to define are ._basis_keys which +The important things to define are ._indices which specifies the combinatorial class that indexes the basis elements, ._one which specifies the identity element in the algebra, ._name which specifies the name of the algebra, .print_options is used to set @@ -191,8 +191,9 @@ def __init__(self, R, cc = None, element_class = None, category = None): category = AlgebrasWithBasis(R) # for backward compatibility - if cc is None and hasattr(self, "_basis_keys"): - cc = self._basis_keys + if cc is None: + if hasattr(self, "_indices"): + cc = self._indices assert(cc is not None) CombinatorialFreeModule.__init__(self, R, cc, element_class, category = category) diff --git a/src/sage/combinat/composition.py b/src/sage/combinat/composition.py index 96f1bb36918..70b4d09a02a 100644 --- a/src/sage/combinat/composition.py +++ b/src/sage/combinat/composition.py @@ -1484,7 +1484,7 @@ class Compositions(Parent, UniqueRepresentation): preferably consider using ``IntegerVectors`` instead:: sage: Compositions(2, length=3, min_part=0).list() - doctest:... RuntimeWarning: Currently, setting min_part=0 produces Composition objects which violate internal assumptions. Calling methods on these objects may produce errors or WRONG results! + doctest:...: RuntimeWarning: Currently, setting min_part=0 produces Composition objects which violate internal assumptions. Calling methods on these objects may produce errors or WRONG results! [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] sage: list(IntegerVectors(2, 3)) @@ -1956,7 +1956,7 @@ def from_descents(descents, nps=None): sage: [x-1 for x in Composition([1, 1, 3, 4, 3]).to_subset()] [0, 1, 4, 8] sage: sage.combinat.composition.from_descents([1,0,4,8],12) - doctest:1: DeprecationWarning: from_descents is deprecated. Use Compositions().from_descents instead. + doctest:...: DeprecationWarning: from_descents is deprecated. Use Compositions().from_descents instead. See http://trac.sagemath.org/14063 for details. [1, 1, 3, 4, 3] """ @@ -1973,7 +1973,7 @@ def composition_from_subset(S, n): sage: from sage.combinat.composition import composition_from_subset sage: composition_from_subset([2,1,5,9], 12) - doctest:1: DeprecationWarning: composition_from_subset is deprecated. Use Compositions().from_subset instead. + doctest:...: DeprecationWarning: composition_from_subset is deprecated. Use Compositions().from_subset instead. See http://trac.sagemath.org/14063 for details. [1, 1, 3, 4, 3] """ @@ -1992,7 +1992,7 @@ def from_code(code): sage: Composition([4,1,2,3,5]).to_code() [1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0] sage: composition.from_code(_) - doctest:1: DeprecationWarning: from_code is deprecated. Use Compositions().from_code instead. + doctest:...: DeprecationWarning: from_code is deprecated. Use Compositions().from_code instead. See http://trac.sagemath.org/14063 for details. [4, 1, 2, 3, 5] """ diff --git a/src/sage/combinat/designs/all.py b/src/sage/combinat/designs/all.py index c6a04de444d..fc3b63f2563 100644 --- a/src/sage/combinat/designs/all.py +++ b/src/sage/combinat/designs/all.py @@ -5,6 +5,8 @@ from incidence_structures import (IncidenceStructure, IncidenceStructureFromMatrix) +from incidence_structures import IncidenceStructure as Hypergraph + from covering_design import (CoveringDesign, schonheim, trivial_covering_design) @@ -19,8 +21,7 @@ ["ProjectiveGeometryDesign", "AffineGeometryDesign", "WittDesign", - "HadamardDesign", - "BlockDesign_generic"], + "HadamardDesign"], ("This method soon will not be available in that " "way anymore. To use it, you can now call it by " "typing designs.%(name)s")) diff --git a/src/sage/combinat/designs/bibd.py b/src/sage/combinat/designs/bibd.py index b36333a9e94..feda44eb4e6 100644 --- a/src/sage/combinat/designs/bibd.py +++ b/src/sage/combinat/designs/bibd.py @@ -6,16 +6,16 @@ * Steiner Triple Systems, i.e. `(v,3,1)`-BIBD. * `K_4`-decompositions of `K_v`, i.e. `(v,4,1)`-BIBD. -These BIBD can be obtained through the :meth:`BalancedIncompleteBlockDesign` -method, available in Sage as ``designs.BalancedIncompleteBlockDesign``. +These BIBD can be obtained through the :func:`balanced_incomplete_block_design` +method, available in Sage as ``designs.balanced_incomplete_block_design``. EXAMPLES:: - sage: designs.BalancedIncompleteBlockDesign(7,3) + sage: designs.balanced_incomplete_block_design(7,3) Incidence structure with 7 points and 7 blocks - sage: designs.BalancedIncompleteBlockDesign(7,3).blocks() + sage: designs.balanced_incomplete_block_design(7,3).blocks() [[0, 1, 3], [0, 2, 4], [0, 5, 6], [1, 2, 6], [1, 4, 5], [2, 3, 5], [3, 4, 6]] - sage: designs.BalancedIncompleteBlockDesign(13,4).blocks() + sage: designs.balanced_incomplete_block_design(13,4).blocks() [[0, 1, 2, 12], [0, 3, 6, 9], [0, 4, 8, 10], [0, 5, 7, 11], [1, 3, 8, 11], [1, 4, 7, 9], [1, 5, 6, 10], [2, 3, 7, 10], [2, 4, 6, 11], [2, 5, 8, 9], [3, 4, 5, 12], [6, 7, 8, 12], [9, 10, 11, 12]] @@ -26,7 +26,7 @@ Decompositions of `K_v` into `K_4` (i.e. `(v,4,1)`-BIBD) are built following Douglas Stinson's construction as presented in [Stinson2004]_ page 167. It is based upon the construction of `(v\{4,5,8,9,12\})`-PBD (see the doc of -:meth:`PBD_4_5_8_9_12`), knowing that a `(v\{4,5,8,9,12\})`-PBD on `v` points +:func:`PBD_4_5_8_9_12`), knowing that a `(v\{4,5,8,9,12\})`-PBD on `v` points can always be transformed into a `((k-1)v+1,4,1)`-BIBD, which covers all possible cases of `(v,4,1)`-BIBD. @@ -52,7 +52,7 @@ from sage.rings.arith import binomial from sage.rings.arith import is_prime_power -def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): +def balanced_incomplete_block_design(v,k,existence=False,use_LJCR=False): r""" Returns a BIBD of parameters `v,k`. @@ -82,7 +82,7 @@ def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): - ``use_LJCR`` (boolean) -- whether to query the La Jolla Covering Repository for the design when Sage does not know how to build it (see - :meth:`~sage.combinat.designs.covering_design.best_known_covering_design_www`). This + :func:`~sage.combinat.designs.covering_design.best_known_covering_design_www`). This requires internet. .. SEEALSO:: @@ -98,9 +98,9 @@ def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): EXAMPLES:: - sage: designs.BalancedIncompleteBlockDesign(7,3).blocks() + sage: designs.balanced_incomplete_block_design(7,3).blocks() [[0, 1, 3], [0, 2, 4], [0, 5, 6], [1, 2, 6], [1, 4, 5], [2, 3, 5], [3, 4, 6]] - sage: B = designs.BalancedIncompleteBlockDesign(21,5, use_LJCR=True) # optional - internet + sage: B = designs.balanced_incomplete_block_design(21,5, use_LJCR=True) # optional - internet sage: B # optional - internet Incidence structure with 21 points and 21 blocks sage: B.blocks() # optional - internet @@ -111,57 +111,57 @@ def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): [2, 7, 8, 13, 19], [3, 4, 10, 13, 18], [3, 5, 8, 14, 17], [3, 6, 9, 12, 19], [3, 7, 11, 15, 16], [4, 5, 6, 7, 20], [8, 9, 10, 11, 20], [12, 13, 14, 15, 20], [16, 17, 18, 19, 20]] - sage: designs.BalancedIncompleteBlockDesign(20,5, use_LJCR=True) # optional - internet + sage: designs.balanced_incomplete_block_design(20,5, use_LJCR=True) # optional - internet Traceback (most recent call last): ... ValueError: No such design exists ! - sage: designs.BalancedIncompleteBlockDesign(16,6) + sage: designs.balanced_incomplete_block_design(16,6) Traceback (most recent call last): ... NotImplementedError: I don't know how to build a (16,6,1)-BIBD! TESTS:: - sage: designs.BalancedIncompleteBlockDesign(85,5,existence=True) + sage: designs.balanced_incomplete_block_design(85,5,existence=True) True - sage: _ = designs.BalancedIncompleteBlockDesign(85,5) + sage: _ = designs.balanced_incomplete_block_design(85,5) A BIBD from a Finite Projective Plane:: - sage: _ = designs.BalancedIncompleteBlockDesign(21,5) + sage: _ = designs.balanced_incomplete_block_design(21,5) Some trivial BIBD:: - sage: designs.BalancedIncompleteBlockDesign(10,10) + sage: designs.balanced_incomplete_block_design(10,10) Incidence structure with 10 points and 1 blocks - sage: designs.BalancedIncompleteBlockDesign(1,10) + sage: designs.balanced_incomplete_block_design(1,10) Incidence structure with 1 points and 0 blocks Existence of BIBD with `k=3,4,5`:: - sage: [v for v in xrange(50) if designs.BalancedIncompleteBlockDesign(v,3,existence=True)] + sage: [v for v in xrange(50) if designs.balanced_incomplete_block_design(v,3,existence=True)] [1, 3, 7, 9, 13, 15, 19, 21, 25, 27, 31, 33, 37, 39, 43, 45, 49] - sage: [v for v in xrange(100) if designs.BalancedIncompleteBlockDesign(v,4,existence=True)] + sage: [v for v in xrange(100) if designs.balanced_incomplete_block_design(v,4,existence=True)] [1, 4, 13, 16, 25, 28, 37, 40, 49, 52, 61, 64, 73, 76, 85, 88, 97] - sage: [v for v in xrange(150) if designs.BalancedIncompleteBlockDesign(v,5,existence=True)] + sage: [v for v in xrange(150) if designs.balanced_incomplete_block_design(v,5,existence=True)] [1, 5, 21, 25, 41, 45, 61, 65, 81, 85, 101, 105, 121, 125, 141, 145] For `k > 5` there are currently very few constructions:: - sage: [v for v in xrange(150) if designs.BalancedIncompleteBlockDesign(v,6,existence=True) is True] + sage: [v for v in xrange(150) if designs.balanced_incomplete_block_design(v,6,existence=True) is True] [1, 6, 31, 91] - sage: [v for v in xrange(150) if designs.BalancedIncompleteBlockDesign(v,6,existence=True) is Unknown] + sage: [v for v in xrange(150) if designs.balanced_incomplete_block_design(v,6,existence=True) is Unknown] [16, 21, 36, 46, 51, 61, 66, 76, 81, 96, 106, 111, 121, 126, 136, 141] """ if v == 1: if existence: return True - return BlockDesign(v, [], test=False) + return BlockDesign(v, [], check=False) if k == v: if existence: return True - return BlockDesign(v, [range(v)], test=False) + return BlockDesign(v, [range(v)], check=False) if v < k or k < 2 or (v-1) % (k-1) != 0 or (v*(v-1)) % (k*(k-1)) != 0: if existence: @@ -172,7 +172,7 @@ def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): if existence: return True from itertools import combinations - return BlockDesign(v, combinations(range(v),2), test = False) + return BlockDesign(v, combinations(range(v),2), check=False) if k == 3: if existence: return v%6 == 1 or v%6 == 3 @@ -180,18 +180,23 @@ def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): if k == 4: if existence: return v%12 == 1 or v%12 == 4 - return BlockDesign(v, v_4_1_BIBD(v), test = False) + return BlockDesign(v, v_4_1_BIBD(v), check=False) if k == 5: if existence: return v%20 == 1 or v%20 == 5 - return BlockDesign(v, v_5_1_BIBD(v), test = False) + return BlockDesign(v, v_5_1_BIBD(v), check=False) from difference_family import difference_family + from database import BIBD_constructions + if (v,k,1) in BIBD_constructions: + if existence: + return True + return BlockDesign(v,BIBD_constructions[(v,k,1)]()) if BIBD_from_TD(v,k,existence=True): if existence: return True - return BlockDesign(v, BIBD_from_TD(v,k)) + return BlockDesign(v, BIBD_from_TD(v,k), check=False) if v == (k-1)**2+k and is_prime_power(k-1): if existence: return True @@ -201,7 +206,7 @@ def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): if existence: return True G,D = difference_family(v,k) - return BlockDesign(v, BIBD_from_difference_family(G,D,check=False), test=False) + return BlockDesign(v, BIBD_from_difference_family(G,D,check=False), check=False) if use_LJCR: from covering_design import best_known_covering_design_www B = best_known_covering_design_www(v,k,2) @@ -213,7 +218,7 @@ def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): return False raise EmptySetError("No such design exists !") B = B.incidence_structure() - if len(B.blcks) == expected_n_of_blocks: + if B.num_blocks() == expected_n_of_blocks: if existence: return True else: @@ -253,12 +258,14 @@ def steiner_triple_system(n): sage: sts Incidence structure with 9 points and 12 blocks sage: list(sts) - [[0, 1, 5], [0, 2, 4], [0, 3, 6], [0, 7, 8], [1, 2, 3], [1, 4, 7], [1, 6, 8], [2, 5, 8], [2, 6, 7], [3, 4, 8], [3, 5, 7], [4, 5, 6]] + [[0, 1, 5], [0, 2, 4], [0, 3, 6], [0, 7, 8], [1, 2, 3], + [1, 4, 7], [1, 6, 8], [2, 5, 8], [2, 6, 7], [3, 4, 8], + [3, 5, 7], [4, 5, 6]] As any pair of vertices is covered once, its parameters are :: - sage: sts.parameters(t=2) - (2, 9, 3, 1) + sage: sts.is_t_design(return_parameters=True) + (True, (2, 9, 3, 1)) An exception is raised for invalid values of ``n`` :: @@ -359,21 +366,21 @@ def BIBD_from_TD(v,k,existence=False): sage: from sage.combinat.designs.bibd import BIBD_from_TD sage: BIBD_from_TD(25,5,existence=True) True - sage: _ = BlockDesign(25,BIBD_from_TD(25,5)) + sage: _ = designs.BlockDesign(25,BIBD_from_TD(25,5)) Second construction:: sage: from sage.combinat.designs.bibd import BIBD_from_TD sage: BIBD_from_TD(21,5,existence=True) True - sage: _ = BlockDesign(21,BIBD_from_TD(21,5)) + sage: _ = designs.BlockDesign(21,BIBD_from_TD(21,5)) Third construction:: sage: from sage.combinat.designs.bibd import BIBD_from_TD sage: BIBD_from_TD(85,5,existence=True) True - sage: _ = BlockDesign(85,BIBD_from_TD(85,5)) + sage: _ = designs.BlockDesign(85,BIBD_from_TD(85,5)) No idea:: @@ -389,14 +396,14 @@ def BIBD_from_TD(v,k,existence=False): # First construction if (v%k == 0 and - BalancedIncompleteBlockDesign(v//k,k,existence=True) and + balanced_incomplete_block_design(v//k,k,existence=True) and transversal_design(k,v//k,existence=True)): if existence: return True v = v//k - BIBDvk = BalancedIncompleteBlockDesign(v,k) + BIBDvk = balanced_incomplete_block_design(v,k) TDkv = transversal_design(k,v,check=False) BIBD = TDkv @@ -405,14 +412,14 @@ def BIBD_from_TD(v,k,existence=False): # Second construction elif ((v-1)%k == 0 and - BalancedIncompleteBlockDesign((v-1)//k+1,k,existence=True) and + balanced_incomplete_block_design((v-1)//k+1,k,existence=True) and transversal_design(k,(v-1)//k,existence=True)): if existence: return True v = (v-1)//k - BIBDv1k = BalancedIncompleteBlockDesign(v+1,k) + BIBDv1k = balanced_incomplete_block_design(v+1,k) TDkv = transversal_design(k,v,check=False) inf = v*k @@ -422,14 +429,14 @@ def BIBD_from_TD(v,k,existence=False): # Third construction elif ((v-k)%k == 0 and - BalancedIncompleteBlockDesign((v-k)//k+k,k,existence=True) and + balanced_incomplete_block_design((v-k)//k+k,k,existence=True) and transversal_design(k,(v-k)//k,existence=True)): if existence: return True v = (v-k)//k - BIBDvpkk = BalancedIncompleteBlockDesign(v+k,k) + BIBDvpkk = balanced_incomplete_block_design(v+k,k) TDkv = transversal_design(k,v,check=False) inf = v*k BIBD = TDkv @@ -523,14 +530,14 @@ def v_4_1_BIBD(v, check=True): A `(v,4,1)`-BIBD is an edge-decomposition of the complete graph `K_v` into copies of `K_4`. For more information, see - :meth:`BalancedIncompleteBlockDesign`. It exists if and only if `v\equiv 1,4 + :func:`balanced_incomplete_block_design`. It exists if and only if `v\equiv 1,4 \pmod {12}`. See page 167 of [Stinson2004]_ for the construction details. .. SEEALSO:: - * :meth:`BalancedIncompleteBlockDesign` + * :func:`balanced_incomplete_block_design` INPUT: @@ -638,7 +645,7 @@ def BIBD_from_PBD(PBD,v,k,check=True,base_cases={}): n = len(X) N = (k-1)*n+1 if not (n,k) in base_cases: - base_cases[n,k] = _relabel_bibd(BalancedIncompleteBlockDesign(N,k).blcks,N) + base_cases[n,k] = _relabel_bibd(balanced_incomplete_block_design(N,k), N) for XX in base_cases[n,k]: if N-1 in XX: @@ -655,7 +662,10 @@ def BIBD_from_PBD(PBD,v,k,check=True,base_cases={}): def _check_pbd(B,v,S): r""" - Checks that ``B`` is a PBD on `v` points with given block sizes. + Checks that ``B`` is a PBD on ``v`` points with given block sizes ``S``. + + The points of the balanced incomplete block design are implicitely assumed + to be `\{0, ..., v-1\}`. INPUT: @@ -667,7 +677,7 @@ def _check_pbd(B,v,S): EXAMPLE:: - sage: designs.BalancedIncompleteBlockDesign(40,4).blocks() # indirect doctest + sage: designs.balanced_incomplete_block_design(40,4).blocks() # indirect doctest [[0, 1, 2, 12], [0, 3, 6, 9], [0, 4, 8, 10], [0, 5, 7, 11], [0, 13, 26, 39], [0, 14, 25, 28], [0, 15, 27, 38], [0, 16, 22, 32], [0, 17, 23, 34], @@ -677,32 +687,63 @@ def _check_pbd(B,v,S): Traceback (most recent call last): ... RuntimeError: All integers of S must be >=2 + + TESTS:: + + sage: _check_pbd([[1,2]],2,[2]) + Traceback (most recent call last): + ... + RuntimeError: The PBD covers a point 2 which is not in {0, 1} + sage: _check_pbd([[1,2]]*2,2,[2]) + Traceback (most recent call last): + ... + RuntimeError: The pair (1,2) is covered more than once + sage: _check_pbd([],2,[2]) + Traceback (most recent call last): + ... + RuntimeError: The pair (0,1) is not covered + sage: _check_pbd([[1,2],[1]],2,[2]) + Traceback (most recent call last): + ... + RuntimeError: A block has size 1 while S=[2] """ from itertools import combinations from sage.graphs.graph import Graph - if not all(len(X) in S for X in B): - raise RuntimeError("Some block has wrong size: this is not a nice honest PBD from the good old days !") + for X in B: + if len(X) not in S: + raise RuntimeError("A block has size {} while S={}".format(len(X),S)) if any(x < 2 for x in S): raise RuntimeError("All integers of S must be >=2") if v == 0 or v == 1: if B: - raise RuntimeError("This is not a nice honest PBD from the good old days!") - else: - return + raise RuntimeError("A PBD with v<=1 is expected to be empty.") g = Graph() + g.add_vertices(range(v)) m = 0 for X in B: - g.add_edges(list(combinations(X,2))) - if g.size() != m+binomial(len(X),2): - raise RuntimeError("This is not a nice honest PBD from the good old days !") - m = g.size() - - if not (g.is_clique() and g.vertices() == range(v)): - raise RuntimeError("This is not a nice honest PBD from the good old days !") + for i,j in combinations(X,2): + g.add_edge(i,j) + m_tmp = g.size() + if m_tmp != m+1: + raise RuntimeError("The pair ({},{}) is covered more than once".format(i,j)) + m = m_tmp + + if g.vertices() != range(v): + from sage.sets.integer_range import IntegerRange + p = (set(g.vertices())-set(range(v))).pop() + raise RuntimeError("The PBD covers a point {} which is not in {}".format(p,IntegerRange(v))) + + if not g.is_clique(): + for p1 in g: + if g.degree(p1) != v-1: + break + neighbors = g.neighbors(p1)+[p1] + p2 = (set(g.vertices())-set(neighbors)).pop() + raise RuntimeError("The pair ({},{}) is not covered".format(p1,p2)) return B @@ -722,7 +763,7 @@ def _relabel_bibd(B,n,p=None): EXAMPLE:: - sage: designs.BalancedIncompleteBlockDesign(40,4).blocks() # indirect doctest + sage: designs.balanced_incomplete_block_design(40,4).blocks() # indirect doctest [[0, 1, 2, 12], [0, 3, 6, 9], [0, 4, 8, 10], [0, 5, 7, 11], [0, 13, 26, 39], [0, 14, 25, 28], [0, 15, 27, 38], [0, 16, 22, 32], [0, 17, 23, 34], @@ -763,7 +804,7 @@ def PBD_4_5_8_9_12(v, check=True): EXAMPLES:: - sage: designs.BalancedIncompleteBlockDesign(40,4).blocks() # indirect doctest + sage: designs.balanced_incomplete_block_design(40,4).blocks() # indirect doctest [[0, 1, 2, 12], [0, 3, 6, 9], [0, 4, 8, 10], [0, 5, 7, 11], [0, 13, 26, 39], [0, 14, 25, 28], [0, 15, 27, 38], [0, 16, 22, 32], [0, 17, 23, 34], @@ -836,13 +877,13 @@ def _PBD_4_5_8_9_12_closure(B): r""" Makes sure all blocks of `B` have size in `\{4,5,8,9,12\}`. - This is a helper function for :meth:`PBD_4_5_8_9_12`. Given that + This is a helper function for :func:`PBD_4_5_8_9_12`. Given that `\{4,5,8,9,12\}` is PBD-closed, any block of size not in `\{4,5,8,9,12\}` can be decomposed further. EXAMPLES:: - sage: designs.BalancedIncompleteBlockDesign(40,4).blocks() # indirect doctest + sage: designs.balanced_incomplete_block_design(40,4).blocks() # indirect doctest [[0, 1, 2, 12], [0, 3, 6, 9], [0, 4, 8, 10], [0, 5, 7, 11], [0, 13, 26, 39], [0, 14, 25, 28], [0, 15, 27, 38], [0, 16, 22, 32], [0, 17, 23, 34], @@ -928,7 +969,7 @@ def v_5_1_BIBD(v, check=True): .. SEEALSO:: - * :meth:`BalancedIncompleteBlockDesign` + * :func:`balanced_incomplete_block_design` EXAMPLES:: diff --git a/src/sage/combinat/designs/block_design.py b/src/sage/combinat/designs/block_design.py index 21e31d6c81e..041c13a3d33 100644 --- a/src/sage/combinat/designs/block_design.py +++ b/src/sage/combinat/designs/block_design.py @@ -1,23 +1,10 @@ """ Block designs. -A module to help with constructions and computations of block -designs and other incidence structures. - -A block design is an incidence structure consisting of a set of points `P` and a -set of blocks `B`, where each block is considered as a subset of `P`. More -precisely, a *block design* `B` is a class of `k`-element subsets of `P` such -that the number `r` of blocks that contain any point `x` in `P` is independent -of `x`, and the number `\lambda` of blocks that contain any given `t`-element -subset `T` is independent of the choice of `T` (see [1]_ for more). Such a block -design is also called a `t-(v,k,\lambda)`-design, and `v` (the number of -points), `b` (the number of blocks), `k`, `r`, and `\lambda` are the parameters -of the design. (In Python, ``lambda`` is reserved, so we sometimes use ``lmbda`` -or ``L`` instead.) - -In Sage, sets are replaced by (ordered) lists and the standard representation of -a block design uses `P = [0,1,..., v-1]`, so a block design is specified by -`(v,B)`. +A *block design* is a set together with a family of subsets (repeated subsets +are allowed) whose members are chosen to satisfy some set of properties that are +deemed useful for a particular application. See :wikipedia:`Block_design`. It is +an object equivalent to an incidence structure. REFERENCES: @@ -64,14 +51,17 @@ #*************************************************************************** from sage.modules.free_module import VectorSpace +from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ from sage.rings.arith import binomial, integer_floor -from sage.combinat.designs.incidence_structures import IncidenceStructure, IncidenceStructureFromMatrix +from incidence_structures import IncidenceStructure from sage.misc.decorators import rename_keyword from sage.rings.finite_rings.constructor import FiniteField from sage.categories.sets_cat import EmptySetError from sage.misc.unknown import Unknown +BlockDesign = IncidenceStructure + ### utility functions ------------------------------------------------------- def tdesign_params(t, v, k, L): @@ -81,7 +71,7 @@ def tdesign_params(t, v, k, L): EXAMPLES:: - sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD = designs.BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: from sage.combinat.designs.block_design import tdesign_params sage: tdesign_params(2,7,3,1) (2, 7, 7, 3, 3, 1) @@ -94,7 +84,7 @@ def tdesign_params(t, v, k, L): r = integer_floor(L * x/y) return (t, v, b, r, k, L) -def ProjectiveGeometryDesign(n, d, F, algorithm=None): +def ProjectiveGeometryDesign(n, d, F, algorithm=None, check=True): """ Returns a projective geometry design. @@ -126,26 +116,22 @@ def ProjectiveGeometryDesign(n, d, F, algorithm=None): sage: designs.ProjectiveGeometryDesign(2, 1, GF(2)) Incidence structure with 7 points and 7 blocks sage: BD = designs.ProjectiveGeometryDesign(2, 1, GF(2), algorithm="gap") # optional - gap_packages (design package) - sage: BD.is_block_design() # optional - gap_packages (design package) - (True, [2, 7, 3, 1]) + sage: BD.is_t_design(return_parameters=True) # optional - gap_packages (design package) + (True, (2, 7, 3, 1)) """ q = F.order() - from sage.interfaces.gap import gap, GapElement - from sage.sets.set import Set if algorithm is None: V = VectorSpace(F, n+1) - points = list(V.subspaces(1)) - flats = list(V.subspaces(d+1)) - blcks = [] - for p in points: + points = {p:i for i,p in enumerate(V.subspaces(1))} + blocks = [] + for s in V.subspaces(d+1): b = [] - for i in range(len(flats)): - if p.is_subspace(flats[i]): - b.append(i) - blcks.append(b) - v = (q**(n+1)-1)/(q-1) - return BlockDesign(v, blcks, name="ProjectiveGeometryDesign") + for bb in s.subspaces(1): + b.append(points[bb]) + blocks.append(b) + return BlockDesign(len(points), blocks, name="ProjectiveGeometryDesign", check=check) if algorithm == "gap": # Requires GAP's Design + from sage.interfaces.gap import gap gap.load_package("design") gap.eval("D := PGPointFlatBlockDesign( %s, %s, %d )"%(n,q,d)) v = eval(gap.eval("D.v")) @@ -153,7 +139,7 @@ def ProjectiveGeometryDesign(n, d, F, algorithm=None): gB = [] for b in gblcks: gB.append([x-1 for x in b]) - return BlockDesign(v, gB, name="ProjectiveGeometryDesign") + return BlockDesign(v, gB, name="ProjectiveGeometryDesign", check=check) def DesarguesianProjectivePlaneDesign(n, check=True): r""" @@ -231,7 +217,7 @@ def DesarguesianProjectivePlaneDesign(n, check=True): # the line at infinity "z = 0" blcks.append(range(n2,n2+n+1)) - return BlockDesign(n2+n+1, blcks, name="Desarguesian projective plane of order %d"%n, test=check) + return BlockDesign(n2+n+1, blcks, name="Desarguesian projective plane of order %d"%n, check=check) def projective_plane_to_OA(pplane, pt=None, check=True): r""" @@ -254,12 +240,6 @@ def projective_plane_to_OA(pplane, pt=None, check=True): guys), you may want to disable it whenever you want speed. Set to ``True`` by default. - .. SEEALSO: - - The function :func:`OA_to_projective_plane` does the reverse operation. - For more on orthogonal arrays, you may have a look at - :func:`~sage.combinat.designs.orthogonal_arrays.orthogonal_array` - EXAMPLES:: sage: from sage.combinat.designs.block_design import projective_plane_to_OA @@ -284,7 +264,7 @@ def projective_plane_to_OA(pplane, pt=None, check=True): sage: _ = projective_plane_to_OA(pp, pt=7) """ from bibd import _relabel_bibd - pplane = pplane.blcks + pplane = pplane.blocks() n = len(pplane[0]) - 1 if pt is None: @@ -304,47 +284,6 @@ def projective_plane_to_OA(pplane, pt=None, check=True): return OA -def OA_to_projective_plane(OA, check=True): - r""" - Return the projective plane associated to an `OA(n+1,n,2)`. - - .. SEEALSO:: - - :func:`projective_plane_to_OA` for the function that goes the other way - around. - - EXAMPLES:: - - sage: from sage.combinat.designs.block_design import projective_plane_to_OA - sage: from sage.combinat.designs.block_design import OA_to_projective_plane - sage: p3 = designs.DesarguesianProjectivePlaneDesign(3) - sage: OA3 = projective_plane_to_OA(p3) - sage: OA_to_projective_plane(OA3) - Incidence structure with 13 points and 13 blocks - - sage: p4 = designs.DesarguesianProjectivePlaneDesign(4) - sage: OA4 = projective_plane_to_OA(p4) - sage: OA_to_projective_plane(OA4) - Incidence structure with 21 points and 21 blocks - """ - n = len(OA[0])-1 - n2 = n**2 - - assert len(OA) == n2, "the orthogonal array does not have parameters k=n+1,t=2" - - blcks = [] - - # add the n^2 lines that correspond to transversals - for l in OA: - blcks.append([i+(n+1)*j for i,j in enumerate(l)]) - - # add the n+1 lines that correspond to transversals - for i in xrange(n+1): - blcks.append(range(i*n, (i+1)*n)) - blcks[-1].append(n2+n) - - return BlockDesign(n2+n+1, blcks, name="Projective plane of order %d (built from an OA(%d,%d,2))"%(n,n+1,n), test=check) - def projective_plane(n, check=True, existence=False): r""" Returns a projective plane of order ``n`` as a 2-design. @@ -374,7 +313,7 @@ def projective_plane(n, check=True, existence=False): sage: designs.projective_plane(6) Traceback (most recent call last): ... - EmptySetError: By the Ryser-Chowla theorem, no projective plane of order 6 exists. + EmptySetError: By the Bruck-Ryser theorem, no projective plane of order 6 exists. sage: designs.projective_plane(10) Traceback (most recent call last): ... @@ -386,7 +325,7 @@ def projective_plane(n, check=True, existence=False): sage: designs.projective_plane(14) Traceback (most recent call last): ... - EmptySetError: By the Ryser-Chowla theorem, no projective plane of order 14 exists. + EmptySetError: By the Bruck-Ryser theorem, no projective plane of order 14 exists. TESTS:: @@ -399,7 +338,8 @@ def projective_plane(n, check=True, existence=False): sage: designs.projective_plane(12, existence=True) Unknown """ - from sage.rings.arith import is_prime_power, two_squares + from sage.rings.arith import is_prime_power + from sage.rings.sum_of_squares import is_sum_of_two_squares_pyx if n <= 1: if existence: @@ -413,14 +353,11 @@ def projective_plane(n, check=True, existence=False): "projective planes of order 10\" (1989), Canad. J. Math.") raise EmptySetError("No projective plane of order 10 exists by %s"%ref) - if (n%4) in [1,2]: - try: - two_squares(n) - except ValueError: - if existence: - return False - raise EmptySetError("By the Ryser-Chowla theorem, no projective" - " plane of order "+str(n)+" exists.") + if (n%4) in [1,2] and not is_sum_of_two_squares_pyx(n): + if existence: + return False + raise EmptySetError("By the Bruck-Ryser theorem, no projective" + " plane of order {} exists.".format(n)) if not is_prime_power(n): if existence: @@ -433,7 +370,6 @@ def projective_plane(n, check=True, existence=False): else: return DesarguesianProjectivePlaneDesign(n, check=check) - def AffineGeometryDesign(n, d, F): r""" Returns an Affine Geometry Design. @@ -464,27 +400,17 @@ def AffineGeometryDesign(n, d, F): EXAMPLES:: sage: BD = designs.AffineGeometryDesign(3, 1, GF(2)) - sage: BD.parameters(t=2) - (2, 8, 2, 1) - sage: BD.is_block_design() - (True, [2, 8, 2, 1]) + sage: BD.is_t_design(return_parameters=True) + (True, (2, 8, 2, 1)) sage: BD = designs.AffineGeometryDesign(3, 2, GF(2)) - sage: BD.parameters(t=3) - (3, 8, 4, 1) - sage: BD.is_block_design() - (True, [3, 8, 4, 1]) - - A 3-design:: - - sage: D = IncidenceStructure(range(32),designs.steiner_quadruple_system(32)) - sage: D.is_block_design() - (True, [3, 32, 4, 1]) + sage: BD.is_t_design(return_parameters=True) + (True, (3, 8, 4, 1)) With an integer instead of a Finite Field:: sage: BD = designs.AffineGeometryDesign(3, 2, 4) - sage: BD.parameters(t=2) - (2, 64, 16, 5) + sage: BD.is_t_design(return_parameters=True) + (True, (2, 64, 16, 5)) """ try: q = int(F) @@ -523,16 +449,13 @@ def WittDesign(n): EXAMPLES:: - sage: BD = designs.WittDesign(9) # optional - gap_packages (design package) - sage: BD.is_block_design() # optional - gap_packages (design package) - (True, [2, 9, 3, 1]) - sage: BD # optional - gap_packages (design package) + sage: BD = designs.WittDesign(9) # optional - gap_packages (design package) + sage: BD.is_t_design(return_parameters=True) # optional - gap_packages (design package) + (True, (2, 9, 3, 1)) + sage: BD # optional - gap_packages (design package) Incidence structure with 9 points and 12 blocks - sage: print BD # optional - gap_packages (design package) + sage: print BD # optional - gap_packages (design package) WittDesign - sage: BD = designs.WittDesign(12) # optional - gap_packages (design package) - sage: BD.is_block_design() # optional - gap_packages (design package) - (True, [5, 12, 6, 1]) """ from sage.interfaces.gap import gap, GapElement gap.load_package("design") @@ -542,7 +465,7 @@ def WittDesign(n): gB = [] for b in gblcks: gB.append([x-1 for x in b]) - return BlockDesign(v, gB, name="WittDesign", test=True) + return BlockDesign(v, gB, name="WittDesign", check=True) def HadamardDesign(n): """ @@ -588,7 +511,7 @@ def HadamardDesign(n): MS = J.parent() A = MS((H2+J)/2) # convert -1's to 0's; coerce entries to ZZ # A is the incidence matrix of the block design - return IncidenceStructureFromMatrix(A,name="HadamardDesign") + return IncidenceStructure(incidence_matrix=A,name="HadamardDesign") def Hadamard3Design(n): """ @@ -648,38 +571,4 @@ def Hadamard3Design(n): A1 = (H1+J)/2 A2 = (J-H1)/2 A = block_matrix(1, 2, [A1, A2]) #the incidence matrix of the design. - return IncidenceStructureFromMatrix(A, name="HadamardThreeDesign") - -def BlockDesign(max_pt, blks, name=None, test=True): - """ - Returns an instance of the :class:`IncidenceStructure` class. - - Requires each B in blks to be contained in range(max_pt). Does not check if - the result is a block design. - - EXAMPLES:: - - sage: BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]], name="Fano plane") - Incidence structure with 7 points and 7 blocks - sage: print BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]], name="Fano plane") - Fano plane - """ - nm = name - if nm is None and test: - nm = "BlockDesign" - BD = BlockDesign_generic( range(max_pt), blks, name=nm, test=test ) - if not test: - return BD - else: - pars = BD.parameters(t=2) - if BD.block_design_checker(pars[0],pars[1],pars[2],pars[3]): - return BD - else: - raise TypeError("parameters are not those of a block design.") - -# Possibly in the future there will be methods which apply to block designs and -# not incidence structures. None have been implemented yet though. The class -# name BlockDesign_generic is reserved in the name space in case more -# specialized methods are implemented later. In that case, BlockDesign_generic -# should inherit from IncidenceStructure. -BlockDesign_generic = IncidenceStructure + return IncidenceStructure(incidence_matrix=A, name="HadamardThreeDesign") diff --git a/src/sage/combinat/designs/covering_design.py b/src/sage/combinat/designs/covering_design.py index 78d21fe2ebf..db1bec0f030 100644 --- a/src/sage/combinat/designs/covering_design.py +++ b/src/sage/combinat/designs/covering_design.py @@ -439,7 +439,7 @@ def incidence_structure(self): sage: from sage.combinat.designs.covering_design import CoveringDesign sage: C=CoveringDesign(7,3,2,7,range(7),[[0, 1, 2], [0, 3, 4], [0, 5, 6], [1, 3, 5], [1, 4, 6], [2, 3, 6], [2, 4, 5]],0, 'Projective Plane') sage: D = C.incidence_structure() - sage: D.points() + sage: D.ground_set() [0, 1, 2, 3, 4, 5, 6] sage: D.blocks() [[0, 1, 2], [0, 3, 4], [0, 5, 6], [1, 3, 5], [1, 4, 6], [2, 3, 6], [2, 4, 5]] diff --git a/src/sage/combinat/designs/database.py b/src/sage/combinat/designs/database.py index bb5fcd66049..8ad082baa6b 100644 --- a/src/sage/combinat/designs/database.py +++ b/src/sage/combinat/designs/database.py @@ -54,6 +54,8 @@ :func:`OA(11,80) `, :func:`OA(10,82) `, :func:`OA(10,100) `, + :func:`OA(9,120) `, + :func:`OA(9,135) `, :func:`OA(12,144) `, :func:`OA(10,154) `, :func:`OA(12,210) `, @@ -70,6 +72,18 @@ :func:`four MOLS of order 15 `, :func:`three MOLS of order 18 ` +- :func:`DF(21,5,1) ` + :func:`DF(25,4,1) ` + :func:`DF(37,4,1) ` + :func:`DF(81,5,1) ` + :func:`DF(91,6,1) ` + :func:`DF(121,5,1) ` + :func:`DF(141,5,1) ` + :func:`DF(161,5,1) ` + :func:`DF(201,5,1) ` + :func:`DF(221,5,1) ` + +- :func:`RBIBD(120,8,1) ` **Dictionaries** @@ -163,7 +177,7 @@ def _MOLS_from_string(s,k): EXAMPLES:: - sage: _ = designs.mutually_orthogonal_latin_squares(10,2) # indirect doctest + sage: _ = designs.mutually_orthogonal_latin_squares(2,10) # indirect doctest """ from sage.matrix.constructor import Matrix matrices = [[] for _ in range(k)] @@ -189,7 +203,7 @@ def MOLS_10_2(): The design is available from the general constructor:: - sage: designs.mutually_orthogonal_latin_squares(10,2,existence=True) + sage: designs.mutually_orthogonal_latin_squares(2,10,existence=True) True """ from sage.matrix.constructor import Matrix @@ -262,7 +276,7 @@ def MOLS_14_4(): The design is available from the general constructor:: - sage: designs.mutually_orthogonal_latin_squares(14,4,existence=True) + sage: designs.mutually_orthogonal_latin_squares(4,14,existence=True) True """ M = """ @@ -300,7 +314,7 @@ def MOLS_15_4(): The design is available from the general constructor:: - sage: designs.mutually_orthogonal_latin_squares(15,4,existence=True) + sage: designs.mutually_orthogonal_latin_squares(4,15,existence=True) True """ M = """ @@ -339,7 +353,7 @@ def MOLS_18_3(): The design is available from the general constructor:: - sage: designs.mutually_orthogonal_latin_squares(18,3,existence=True) + sage: designs.mutually_orthogonal_latin_squares(3,18,existence=True) True """ M = """ @@ -369,7 +383,7 @@ def MOLS_18_3(): # # Associates to n the pair (k,f) where f() is a function that returns k MOLS of order n # -# This dictionary is used by designs.mutually_orthogonal_latin_squares(n,k). +# This dictionary is used by designs.mutually_orthogonal_latin_squares(k,n). MOLS_constructions = { 10 : (2, MOLS_10_2), @@ -2310,6 +2324,140 @@ def OA_10_100(): M = OA_from_Vmt(8,11,[0,1,6,56,22,35,47,23,60]) return M +def OA_9_120(): + r""" + Returns an OA(9,120) + + Construction shared by Julian R. Abel: + + From a resolvable `(120,8,1)-BIBD`, one can obtain 7 `MOLS(120)` or a + resolvable `TD(8,120)` by forming a resolvable `TD(8,8) - 8.TD(8,1)` on + `I_8 \times B` for each block `B` in the BIBD. This gives a `TD(8,120) + - 120 TD(8,1)` (which is resolvable as the BIBD is resolvable). + + .. SEEALSO:: + + :func:`RBIBD_120_8_1` + + EXAMPLES:: + + sage: from sage.combinat.designs.designs_pyx import is_orthogonal_array + sage: from sage.combinat.designs.database import OA_9_120 + sage: OA = OA_9_120() + sage: print is_orthogonal_array(OA,9,120,2) + True + + The design is available from the general constructor:: + + sage: designs.orthogonal_array(9,120,existence=True) + True + """ + from incidence_structures import IncidenceStructure + RBIBD_120 = RBIBD_120_8_1() + equiv = [RBIBD_120[i*15:(i+1)*15] for i in range(17)] + + OA8 = orthogonal_array(9,8) + assert all( (len(set(B[:-1])) == 1) == (B[-1] == 0) for B in OA8) + OA = [] + + for i,classs in enumerate(equiv): + for S in classs: + for B in OA8: + if B[-1] != 0: + OA.append([S[x] for x in B[:-1]]+[i*7+B[-1]]) + + for i in range(120): + OA.append([i]*8+[0]) + + return OA + +def OA_9_135(): + r""" + Returns an OA(9,135) + + Construction shared by Julian R. Abel: + + This design can be built by Wilson's method (`135 = 8.16 + 7`) applied + to an Orthogonal Array `OA(9+7,16)` with 7 groups truncated to size 1 in + such a way that a block contain 0, 1 or 3 points of the truncated + groups. + + This is possible, because `PG(2,2)` (the projective plane over `GF(2)`) + is a subdesign in `PG(2,16)` (the projective plane over `GF(16)`); in a + cyclic `PG(2,16)` or `BIBD(273,17,1)` the points `\equiv 0 + \pmod{39}` form such a subdesign (note that `273=16^2 + 16 +1` and + `273 = 39 \times 7` and `7 = 2^2 + 2 + 1`). + + EXAMPLES:: + + sage: from sage.combinat.designs.designs_pyx import is_orthogonal_array + sage: from sage.combinat.designs.database import OA_9_135 + sage: OA = OA_9_135() + sage: print is_orthogonal_array(OA,9,135,2) + True + + The design is available from the general constructor:: + + sage: designs.orthogonal_array(9,135,existence=True) + True + + As this orthogonal array requires a `(273,17,1)` cyclic difference set, we check that + it is available:: + + sage: G,D = designs.difference_family(273,17,1) + sage: G + Ring of integers modulo 273 + """ + from bibd import BIBD_from_difference_family + G,B = CDF_273_17_1() + PG16 = BIBD_from_difference_family(G,B) + + n = 273 + + # PG2 is a (7,3,1)-design (fano plane) contained in PG16. It is a set of 7 + # points that any block of PG16 intersect on 0,1, or 3 points. + # + # We build it, then check that it works + PG2 = set([x*39 for x in range(7)]) + traces = [[x for x in B if x%39 == 0] for B in PG16] + assert set(map(len,traces)) == set([0,1,3]) + + # We now build an OA(17,16) from our PG16, in such a way that all points of + # PG2 are in different columns. For this, we need to find a point p that is + # not located on any of the lines defined by the points of PG2 + + lines = [B for B in PG16 if len([x for x in B if x%39 == 0]) == 3] + union_of_the_lines = set(sum(lines,[])) + p = (set(range(237))-union_of_the_lines).pop() + + # We can now build a TD from our PG16 by removing p. + for B in PG16: + B.sort(key=lambda x:int(x not in PG2)) + PG16.sort(key=lambda B:sum(x for x in B if x in PG2)) + + r = {} + for B in PG16: + if p in B: + for x in B: + if x != p: + r[x] = len(r) + r[p] = n-1 + + # The columns containing points from PG2 will be the last 7 + assert all(r[x] >= (n-1)-16*7 for x in PG2) + # Those points are the first of each column + assert all(r[x]%16 == 0 for x in PG2) + + PG = [sorted([r[x] for x in B]) for B in PG16] + OA = [[x%16 for x in B] for B in PG if n-1 not in B] + + # We truncate the last 7 columns to size 1. We also drop the first column + truncated_OA = [B[1:-7]+[x if x==0 else None for x in B[-7:]] for B in OA] + + # And call Wilson's construction + from orthogonal_arrays import wilson_construction + return wilson_construction(truncated_OA, 9, 16, 8,7,(1,)*7,check=False) + def OA_12_144(): r""" Returns an OA(12,144) @@ -2609,6 +2757,8 @@ def OA_33_993(): 80 : (11 , OA_11_80), 82 : (10 , OA_10_82), 100 : (10 , OA_10_100), + 120 : (9 , OA_9_120), + 135 : (9 , OA_9_135), 144 : (12 , OA_12_144), 154 : (10 , OA_10_154), 210 : (12 , OA_12_210), @@ -2849,7 +2999,27 @@ def CDF_221_5_1(): from sage.rings.finite_rings.integer_mod_ring import Zmod return Zmod(221), D -# Index of the (right now cyclic) difference families constructions +def CDF_273_17_1(): + r""" + A cyclic `(273,17,1)`-difference set. + + EXAMPLES:: + + sage: from sage.combinat.designs.database import CDF_273_17_1 + sage: from sage.combinat.designs.difference_family import is_difference_family + sage: G,D = CDF_273_17_1() + sage: is_difference_family(G,D,273,17,1) + True + + The difference family is available from the constructor:: + + sage: _ = designs.difference_family(273,17,1) + """ + from sage.rings.finite_rings.integer_mod_ring import Zmod + D = [(1,2,4,8,16,32,64,91,117,128,137,182,195,205,234,239,256)] + return Zmod(273), D + +# Index of the (right now cyclic or Abelian) difference families constructions # # Associates to triple (v,k,lambda) a function that return a # (n,k,lambda)-difference family. @@ -2866,6 +3036,96 @@ def CDF_221_5_1(): (141,5,1): CDF_141_5_1, (161,5,1): CDF_161_5_1, (201,5,1): CDF_201_5_1, - (221,5,1): CDF_221_5_1 + (221,5,1): CDF_221_5_1, + (273,17,1): CDF_273_17_1, } +def RBIBD_120_8_1(): + r""" + Returns a resolvable `BIBD(120,8,1)` + + This function output a list ``L`` of `17\times 15` blocks such that + ``L[i*15:(i+1)*15]`` is a partition of `120`. + + Construction shared by Julian R. Abel: + + Seiden's method: Start with a cyclic `(273,17,1)-BIBD` and let `B` be an + hyperoval, i.e. a set which intersects any block of the BIBD in either 0 + (153 blocks) or 2 points (120 blocks). Dualise this design and take + these last 120 blocks as points in the design; blocks in the design will + correspond to the `273-18=255` non-hyperoval points. + + The design is also resolvable. In the original `PG(2,16)` take any + point `T` in the hyperoval and consider a block `B1` containing `T`. + The `15` points in `B1` that do not belong to the hyperoval correspond + to `15` blocks forming a parallel class in the dualised design. The + other `16` parallel classes come in a similar way, by using point `T` + and the other `16` blocks containing `T`. + + .. SEEALSO:: + + :func:`OA_9_120` + + EXAMPLES:: + + sage: from sage.combinat.designs.database import RBIBD_120_8_1 + sage: from sage.combinat.designs.bibd import _check_pbd + sage: RBIBD = RBIBD_120_8_1() + sage: _ = _check_pbd(RBIBD,120,[8]) + + It is indeed resolvable, and the parallel classes are given by 17 slices of + consecutive 15 blocks:: + + sage: for i in range(17): + ....: assert len(set(sum(RBIBD[i*15:(i+1)*15],[]))) == 120 + + The BIBD is available from the constructor:: + + sage: _ = designs.balanced_incomplete_block_design(120,8) + """ + from incidence_structures import IncidenceStructure + n=273 + + # Base block of a cyclic BIBD(273,16,1) + B = [1,2,4,8,16,32,64,91,117,128,137,182,195,205,234,239,256] + BIBD = [[(x+c)%n for x in B] for c in range(n)] + + # A (precomputed) set that every block of the BIBD intersects on 0 or 2 points + hyperoval = [128, 192, 194, 4, 262, 140, 175, 48, 81, 180, 245, 271, 119, 212, 249, 189, 62, 255] + #for B in BIBD: + # len_trace = sum(x in hyperoval for x in B) + # assert len_trace == 0 or len_trace == 2 + + # Equivalence classes + p = hyperoval[0] + equiv = [] + new_BIBD = [] + for B in BIBD: + if any(x in hyperoval for x in B): + if p in B: + equiv.append([x for x in B if x not in hyperoval]) + else: + new_BIBD.append([x for x in B]) + + BIBD = new_BIBD + + r = {v:i for i,v in enumerate(x for x in range(n) if x not in hyperoval)} + BIBD = [[r[x] for x in B] for B in BIBD ] + equiv = [[r[x] for x in B] for B in equiv] + + BIBD = IncidenceStructure(range(255),BIBD) + M = BIBD.incidence_matrix() + + equiv = [[M.nonzero_positions_in_row(x) for x in S] for S in equiv] + return [B for S in equiv for B in S] + +# Index of the BIBD constructions +# +# Associates to triple (v,k,lambda) a function that return a +# (n,k,lambda)-BIBD family. +# +# This dictionary is used by designs.BalancedIncompleteBlockDesign + +BIBD_constructions = { + (120,8,1): RBIBD_120_8_1, +} diff --git a/src/sage/combinat/designs/design_catalog.py b/src/sage/combinat/designs/design_catalog.py index 63176682328..ffa0fdeecfa 100644 --- a/src/sage/combinat/designs/design_catalog.py +++ b/src/sage/combinat/designs/design_catalog.py @@ -39,7 +39,7 @@ :delim: | :meth:`~sage.combinat.designs.block_design.ProjectiveGeometryDesign` - :meth:`~sage.combinat.designs.block_design.ProjectivePlaneDesign` + :meth:`~sage.combinat.designs.block_design.DesarguesianProjectivePlaneDesign` :meth:`~sage.combinat.designs.bibd.BalancedIncompleteBlockDesign` :meth:`~sage.combinat.designs.block_design.AffineGeometryDesign` :meth:`~sage.combinat.designs.block_design.WittDesign` @@ -50,6 +50,7 @@ :meth:`~sage.combinat.designs.orthogonal_arrays.orthogonal_array` :meth:`~sage.combinat.designs.bibd.steiner_triple_system` :meth:`~sage.combinat.designs.steiner_quadruple_systems.steiner_quadruple_system` + :meth:`~sage.combinat.designs.block_design.projective_plane` And the :meth:`designs.best_known_covering_design_from_LJCR ` function @@ -64,7 +65,8 @@ .. [1] La Jolla Covering Repository, http://www.ccrwest.org/cover.html """ -from sage.combinat.designs.block_design import (ProjectiveGeometryDesign, +from sage.combinat.designs.block_design import (BlockDesign, + ProjectiveGeometryDesign, DesarguesianProjectivePlaneDesign, projective_plane, AffineGeometryDesign, @@ -80,6 +82,14 @@ from sage.combinat.designs.orthogonal_arrays import transversal_design, orthogonal_array, incomplete_orthogonal_array -from sage.combinat.designs.bibd import BalancedIncompleteBlockDesign, steiner_triple_system from sage.combinat.designs.difference_family import difference_family + +from sage.combinat.designs.incidence_structures import IncidenceStructure +BlockDesign = IncidenceStructure # just an alias +from sage.combinat.designs.bibd import balanced_incomplete_block_design, steiner_triple_system + +# deprecated in june 2014 (#16446) +from sage.misc.superseded import deprecated_function_alias +BalancedIncompleteBlockDesign = deprecated_function_alias(16446, + balanced_incomplete_block_design) diff --git a/src/sage/combinat/designs/designs_pyx.pyx b/src/sage/combinat/designs/designs_pyx.pyx index 862272a8dad..d211f3af79c 100644 --- a/src/sage/combinat/designs/designs_pyx.pyx +++ b/src/sage/combinat/designs/designs_pyx.pyx @@ -95,6 +95,9 @@ def is_orthogonal_array(OA, int k, int n, int t=2, verbose=False, terminology="O "MOLS" : "All squares do not have dimension n^2={}^2".format(n)}[terminology] return False + if n == 0: + return True + cdef int i,j,l # A copy of OA diff --git a/src/sage/combinat/designs/ext_rep.py b/src/sage/combinat/designs/ext_rep.py index 6c1fc5a6d4d..1e0b1e75c8e 100644 --- a/src/sage/combinat/designs/ext_rep.py +++ b/src/sage/combinat/designs/ext_rep.py @@ -1016,8 +1016,10 @@ def designs_from_XML(fname): sage: d = BlockDesign(v, blocks) sage: d.blocks() [[0, 1], [0, 1]] - sage: d.parameters(t=2) - (2, 2, 2, 2) + sage: d.is_t_design(t=2) + True + sage: d.is_t_design(return_parameters=True) + (True, (2, 2, 2, 2)) """ proc = XTreeProcessor() diff --git a/src/sage/combinat/designs/incidence_structures.py b/src/sage/combinat/designs/incidence_structures.py index d9a99df92a9..f35de38520d 100644 --- a/src/sage/combinat/designs/incidence_structures.py +++ b/src/sage/combinat/designs/incidence_structures.py @@ -1,5 +1,5 @@ """ -Incidence structures. +Incidence structures (i.e. hypergraphs, i.e. set systems) An incidence structure is specified by a list of points, blocks, and an incidence matrix ([1]_, [2]_). @@ -19,9 +19,10 @@ This is a significantly modified form of part of the module block_design.py (version 0.6) written by Peter Dobcsanyi peter@designtheory.org. +- Vincent Delecroix (2014): major rewrite -Classes and methods -------------------- +Methods +------- """ #*************************************************************************** # Copyright (C) 2007 # @@ -36,37 +37,20 @@ # http://www.gnu.org/licenses/ # #*************************************************************************** -from sage.rings.integer_ring import ZZ -from sage.rings.arith import binomial -### utility functions ------------------------------------------------------- +from sage.misc.superseded import deprecated_function_alias +from sage.misc.cachefunc import cached_method -def coordinatewise_product(L): - """ - Returns the coordinatewise product of a list of vectors. - - INPUT: - - - ``L`` is a list of `n`-vectors or lists all of length `n` with a common - parent. This returns the vector whose `i`-th coordinate is the product of - the `i`-th coordinates of the vectors. - - EXAMPLES:: - - sage: from sage.combinat.designs.incidence_structures import coordinatewise_product - sage: L = [[1,2,3],[-1,-1,-1],[5,7,11]] - sage: coordinatewise_product(L) - [-5, -14, -33] - """ - n = len(L[0]) - ans = [1]*n - for x in L: - ans = [ans[i]*x[i] for i in range(n)] - return ans +from sage.rings.all import ZZ +from sage.rings.integer import Integer +from sage.misc.latex import latex +from sage.sets.set import Set def IncidenceStructureFromMatrix(M, name=None): """ - Builds and incidence structure from a matrix. + Deprecated function that builds an incidence structure from a matrix. + + You should now use ``designs.IncidenceStructure(incidence_matrix=M)``. INPUT: @@ -75,103 +59,232 @@ def IncidenceStructureFromMatrix(M, name=None): EXAMPLES:: - sage: from sage.combinat.designs.block_design import BlockDesign - sage: BD1 = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD1 = designs.IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: M = BD1.incidence_matrix() sage: BD2 = IncidenceStructureFromMatrix(M) + doctest:...: DeprecationWarning: IncidenceStructureFromMatrix is deprecated. + Please use designs.IncidenceStructure(incidence_matrix=M) instead. + See http://trac.sagemath.org/16553 for details. sage: BD1 == BD2 True """ - nm = name - v = len(M.rows()) - b = len(M.columns()) - #points = range(v) - blocks = [] - for i in range(b): - B = [] - for j in range(v): - if M[j, i] != 0: - B.append(j) - blocks.append(B) - return IncidenceStructure(range(v), blocks, name=nm) + from sage.misc.superseded import deprecation + deprecation(16553, 'IncidenceStructureFromMatrix is deprecated. Please use designs.IncidenceStructure(incidence_matrix=M) instead.') + return IncidenceStructure(incidence_matrix=M, name=name) class IncidenceStructure(object): - """ - This the base class for block designs. - """ - def __init__(self, pts, blks, inc_mat=None, name=None, test=True): - """ - INPUT: + r""" + A base class for incidence structures (i.e. hypergraphs, i.e. set systems) - - ``pts, blks`` -- a list of points, and a list of lists (list of blocks). + An incidence structure (i.e. hypergraph, i.e. set system) can be defined + from a collection of blocks (i.e. sets, i.e. edges), optionally with an + explicit ground set (i.e. point set, i.e. vertex set). Alternatively they + can be defined from a binary incidence matrix. - If each `B` in ``blks`` is contained in ``pts`` then the incidence - matrix ` inc_mat`` need not (and should not) be given. Otherwise, - ``inc_mat`` should be the ``ptsxblks`` `(0,1)`-matrix `A` for which - `A_i,j=1` iff ``blks[j]`` is incident with ``pts[i]``. + INPUT: - - ``inc_mat`` (for giving the `(0,1)`-incidence matrix) + - ``points`` -- (i.e. ground set, i.e. vertex set) the underlying set. If + ``points`` is an integer `v`, then the set is considered to be `\{0, ..., + v-1\}`. - - ``name`` (a string, such as "Fano plane"). + .. NOTE:: - - ``test`` (boolean) - if ``True``, then each block must be a list of pts. + The following syntax, where ``points`` is ommitted, automatically + defines the ground set as the union of the blocks:: - EXAMPLES:: + sage: H = IncidenceStructure([['a','b','c'],['c','d','e']]) + sage: H.ground_set() + ['a', 'b', 'c', 'd', 'e'] - sage: IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) - Incidence structure with 7 points and 7 blocks + - ``blocks`` -- (i.e. edges, i.e. sets) the blocks defining the incidence + structure. Can be any iterable. + + - ``incidence_matrix`` -- a binary incidence matrix. Each column represents + a set. + + - ``name`` (a string, such as "Fano plane"). + + - ``check`` -- whether to check the input + + - ``copy`` -- (use with caution) if set to ``False`` then ``blocks`` must be + a list of lists of integers. The list will not be copied but will be + modified in place (each block is sorted, and the whole list is + sorted). Your ``blocks`` object will become the + :class:`IncidenceStructure` instance's internal data. + + EXAMPLES: + + An incidence structure can be constructed by giving the number of points and + the list of blocks:: + + sage: designs.IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + Incidence structure with 7 points and 7 blocks + + Only providing the set of blocks is sufficient. In this case, the ground set + is defined as the union of the blocks:: + + sage: IncidenceStructure([[1,2,3],[2,3,4]]) + Incidence structure with 4 points and 2 blocks - Points are sorted :: + Or by its adjacency matrix (a `\{0,1\}`-matrix in which rows are indexed by + points and columns by blocks):: - sage: BD1 = IncidenceStructure([4,6,0,3,2,5,1],[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) - sage: BD1.points() - [0, 1, 2, 3, 4, 5, 6] + sage: m = matrix([[0,1,0],[0,0,1],[1,0,1],[1,1,1]]) + sage: designs.IncidenceStructure(m) + Incidence structure with 4 points and 3 blocks - TESTS: + The points can be any (hashable) object:: - The following shows that :trac:`11333` is fixed. :: + sage: V = [(0,'a'),(0,'b'),(1,'a'),(1,'b')] + sage: B = [(V[0],V[1],V[2]), (V[1],V[2]), (V[0],V[2])] + sage: I = designs.IncidenceStructure(V, B) + sage: I.ground_set() + [(0, 'a'), (0, 'b'), (1, 'a'), (1, 'b')] + sage: I.blocks() + [[(0, 'a'), (0, 'b'), (1, 'a')], [(0, 'a'), (1, 'a')], [(0, 'b'), (1, 'a')]] - sage: A = IncidenceStructure([0,1],[[0]]) - sage: B = IncidenceStructure([1,0],[[0]]) - sage: B == A + The order of the points and blocks does not matter as they are sorted on + input (see :trac:`11333`):: + + sage: A = designs.IncidenceStructure([0,1,2], [[0],[0,2]]) + sage: B = designs.IncidenceStructure([1,0,2], [[0],[2,0]]) + sage: B == A + True + + sage: C = designs.BlockDesign(2, [[0], [1,0]]) + sage: D = designs.BlockDesign(2, [[0,1], [0]]) + sage: C == D + True + + If you care for speed, you can set ``copy`` to ``False``, but in that + case, your input must be a list of lists and the ground set must be `{0, + ..., v-1}`:: + + sage: blocks = [[0,1],[2,0],[1,2]] # a list of lists of integers + sage: I = designs.IncidenceStructure(3, blocks, copy=False) + sage: I.blocks(copy=False) is blocks + True + """ + def __init__(self, points=None, blocks=None, incidence_matrix=None, + name=None, check=True, test=None, copy=True): + r""" + TESTS:: + + sage: designs.IncidenceStructure(3, [[4]]) + Traceback (most recent call last): + ... + ValueError: Block [4] is not contained in the point set + + sage: designs.IncidenceStructure(3, [[0,1],[0,2]], test=True) + doctest:...: DeprecationWarning: the keyword test is deprecated, + use check instead + See http://trac.sagemath.org/16553 for details. + Incidence structure with 3 points and 2 blocks + + sage: designs.IncidenceStructure(2, [[0,1,2,3,4,5]], test=False) + Incidence structure with 2 points and 1 blocks + + We avoid to convert to integers when the points are not (but compare + equal to integers because of coercion):: + + sage: V = GF(5) + sage: e0,e1,e2,e3,e4 = V + sage: [e0,e1,e2,e3,e4] == range(5) # coercion makes them equal True + sage: blocks = [[e0,e1,e2],[e0,e1],[e2,e4]] + sage: I = designs.IncidenceStructure(V, blocks) + sage: type(I.ground_set()[0]) + + sage: type(I.blocks()[0][0]) + + """ + if test is not None: + from sage.misc.superseded import deprecation + deprecation(16553, "the keyword test is deprecated, use check instead") + check = test + + from sage.matrix.constructor import matrix + from sage.structure.element import Matrix + + # Reformatting input + if isinstance(points, Matrix): + assert incidence_matrix is None, "'incidence_matrix' cannot be defined when 'points' is a matrix" + assert blocks is None, "'blocks' cannot be defined when 'points' is a matrix" + incidence_matrix = points + points = blocks = None + elif points and blocks is None: + blocks = points + points = set().union(*blocks) + if points: + assert incidence_matrix is None, "'incidence_matrix' cannot be defined when 'points' is defined" + + if incidence_matrix: + M = matrix(incidence_matrix) + v = M.nrows() + self._points = range(v) + self._point_to_index = None + self._blocks = sorted(M.nonzero_positions_in_column(i) for i in range(M.ncols())) + + else: + if isinstance(points, (int,Integer)): + self._points = range(points) + self._point_to_index = None + else: + self._points = sorted(points) + if self._points == range(len(points)) and all(isinstance(x,(int,Integer)) for x in self._points): + self._point_to_index = None + else: + self._point_to_index = {e:i for i,e in enumerate(self._points)} + + if check: + for block in blocks: + if any(x not in self._points for x in block): + raise ValueError("Block {} is not contained in the point set".format(block)) + if len(block) != len(set(block)): + raise ValueError("Repeated element in block {}".format(block)) + + if self._point_to_index: + # translate everything to integers between 0 and v-1 + blocks = [sorted(self._point_to_index[e] for e in block) for block in blocks] + elif copy: + # create a new list made of sorted blocks + blocks = [sorted(block) for block in blocks] + else: + # sort the data but avoid copying it + for b in blocks: + b.sort() + + blocks.sort() + self._blocks = blocks - REFERENCES: - - - E. Assmus, J. Key, Designs and their codes, CUP, 1992. - """ - bs = [] - self.pnts = pts - self.pnts.sort() - v, blocks = len(pts), blks - for block in blocks: - if test: - for x in block: - if not(x in self.pnts): - raise ValueError('Point %s is not in the base set.' % x) - try: - y = sorted(block[:]) - bs.append(y) - except Exception: - bs.append(block) - bs.sort(cmp) - self.v = v - self.blcks = bs - self.name = name - self._incidence_matrix = inc_mat + self._name = str(name) if name is not None else 'IncidenceStructure' def __iter__(self): """ Iterator over the blocks. - EXAMPLE:: + Note that it is faster to call for the method ``.blocks(copy=True)`` + (but in that case the output should not be modified). + + EXAMPLES:: sage: sts = designs.steiner_triple_system(9) sage: list(sts) - [[0, 1, 5], [0, 2, 4], [0, 3, 6], [0, 7, 8], [1, 2, 3], [1, 4, 7], [1, 6, 8], [2, 5, 8], [2, 6, 7], [3, 4, 8], [3, 5, 7], [4, 5, 6]] + [[0, 1, 5], [0, 2, 4], [0, 3, 6], [0, 7, 8], [1, 2, 3], [1, 4, 7], + [1, 6, 8], [2, 5, 8], [2, 6, 7], [3, 4, 8], [3, 5, 7], [4, 5, 6]] + + sage: b = designs.IncidenceStructure('ab', ['a','ab']) + sage: it = iter(b) + sage: it.next() + ['a'] + sage: it.next() + ['a', 'b'] """ - - return iter(self.blcks) + if self._point_to_index is None: + for b in self._blocks: yield b[:] + else: + for b in self._blocks: + yield [self._points[i] for i in b] def __repr__(self): """ @@ -179,13 +292,12 @@ def __repr__(self): EXAMPLES:: - sage: from sage.combinat.designs.block_design import BlockDesign - sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD = designs.IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: BD Incidence structure with 7 points and 7 blocks """ - repr = 'Incidence structure with %s points and %s blocks' % (len(self.pnts), len(self.blcks)) - return repr + return 'Incidence structure with {} points and {} blocks'.format( + self.num_points(), self.num_blocks()) def __str__(self): """ @@ -193,185 +305,204 @@ def __str__(self): EXAMPLES:: - sage: from sage.combinat.designs.block_design import BlockDesign - sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD = designs.IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: print BD - BlockDesign - sage: BD = IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + IncidenceStructure + sage: BD = designs.IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: print BD IncidenceStructure """ - if self.name: - repr = '%s' % (self.name, self.pnts, - self.blcks) - else: - repr = 'IncidenceStructure' % (self.pnts, - self.blcks) - return repr + return '{}'.format( + self._name, self.ground_set(), self.blocks()) - def automorphism_group(self): + def __eq__(self, other): """ - Returns the subgroup of the automorphism group of the incidence graph - which respects the P B partition. It is (isomorphic to) the automorphism - group of the block design, although the degrees differ. + Tests is the two incidence structures are equal - EXAMPLES:: + TESTS:: - sage: P = designs.DesarguesianProjectivePlaneDesign(2); P - Incidence structure with 7 points and 7 blocks - sage: G = P.automorphism_group() - sage: G.is_isomorphic(PGL(3,2)) + sage: blocks = [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]] + sage: BD1 = designs.IncidenceStructure(7, blocks) + sage: M = BD1.incidence_matrix() + sage: BD2 = designs.IncidenceStructure(incidence_matrix=M) + sage: BD1 == BD2 True - sage: G - Permutation Group with generators [(2,3)(4,5), (2,4)(3,5), (1,2)(4,6), (0,1)(4,5)] - - A non self-dual example:: - sage: from sage.combinat.designs.incidence_structures import IncidenceStructure - sage: IS = IncidenceStructure(range(4), [[0,1,2,3],[1,2,3]]) - sage: IS.automorphism_group().cardinality() - 6 - sage: IS.dual_design().automorphism_group().cardinality() - 1 + sage: e1 = frozenset([0,1]) + sage: e2 = frozenset([2]) + sage: sorted([e1,e2]) == [e1,e2] + True + sage: sorted([e2,e1]) == [e2,e1] + True + sage: I1 = designs.IncidenceStructure([e1,e2], [[e1],[e1,e2]]) + sage: I2 = designs.IncidenceStructure([e1,e2], [[e2,e1],[e1]]) + sage: I3 = designs.IncidenceStructure([e2,e1], [[e1,e2],[e1]]) + sage: I1 == I2 and I2 == I1 and I1 == I3 and I3 == I1 and I2 == I3 and I3 == I2 + True """ - from sage.groups.perm_gps.partn_ref.refinement_matrices import MatrixStruct - from sage.groups.perm_gps.permgroup import PermutationGroup - from sage.groups.perm_gps.permgroup_named import SymmetricGroup - M1 = self.incidence_matrix().transpose() - M2 = MatrixStruct(M1) - M2.run() - gens = M2.automorphism_group()[0] - return PermutationGroup(gens, domain=range(self.v)) + # We are extra careful in this method since we cannot assume that a + # total order is defined on the point set. + if not isinstance(other, IncidenceStructure): + return False - def block_design_checker(self, t, v, k, lmbda, type=None): - """ - This is *not* a wrapper for GAP Design's IsBlockDesign. The GAP - Design function IsBlockDesign - http://www.gap-system.org/Manuals/pkg/design/htm/CHAP004.htm - apparently simply checks the record structure and no mathematical - properties. Instead, the function below checks some necessary (but - not sufficient) "easy" identities arising from the identity. + if self._points == other._points: + return self._blocks == other._blocks - INPUT: + if (self.num_points() != other.num_points() or + self.num_blocks() != other.num_blocks()): + return False - - ``t`` - the t as in "t-design" + p_to_i = self._point_to_index if self._point_to_index else range(self.num_points()) - - ``v`` - the number of points + if any(p not in p_to_i for p in other.ground_set()): + return False - - ``k`` - the number of blocks incident to a point + other_blocks = sorted(sorted(p_to_i[p] for p in b) for b in other.blocks()) + return self._blocks == other_blocks - - ``lmbda`` - each t-tuple of points should be incident with - lmbda blocks + def __ne__(self, other): + r""" + Difference test. - - ``type`` - can be 'simple' or 'binary' or 'connected' - Depending on the option, this wraps IsBinaryBlockDesign, - IsSimpleBlockDesign, or IsConnectedBlockDesign. + EXAMPLES:: - - Binary: no block has a repeated element. + sage: BD1 = designs.IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: M = BD1.incidence_matrix() + sage: BD2 = designs.IncidenceStructure(incidence_matrix=M) + sage: BD1 != BD2 + False + """ + return not self.__eq__(other) - - Simple: no block is repeated. + def ground_set(self, copy=True): + r""" + Return the ground set (i.e the list of points). - - Connected: its incidence graph is a connected graph. + INPUT: - WARNING: This is very fast but can return false positives. + - ``copy`` (boolean) -- ``True`` by default. When set to ``False``, a + pointer toward the object's internal data is given. Set it to + ``False`` only if you know what you are doing. EXAMPLES:: - sage: from sage.combinat.designs.block_design import BlockDesign - sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) - sage: BD.is_block_design() - (True, [2, 7, 3, 1]) - sage: BD.block_design_checker(2, 7, 3, 1) - True - sage: BD.block_design_checker(2, 7, 3, 1,"binary") - True - sage: BD.block_design_checker(2, 7, 3, 1,"connected") - True - sage: BD.block_design_checker(2, 7, 3, 1,"simple") - True + sage: designs.IncidenceStructure(3, [[0,1],[0,2]]).ground_set() + [0, 1, 2] """ - from sage.sets.set import Set - if not(v == len(self.points())): - return False - b = lmbda*binomial(v, t)/binomial(k, t) - r = int(b*k/v) - if not(b == len(self.blocks())): - return False - if not(ZZ(v).divides(b*k)): - return False - A = self.incidence_matrix() - #k = sum(A.columns()[0]) - #r = sum(A.rows()[0]) - for i in range(b): - if not(sum(A.columns()[i]) == k): - return False - for i in range(v): - if not(sum(A.rows()[i]) == r): - return False - if type is None: - return True - if type == "binary": - for b in self.blocks(): - if len(b) != len(Set(b)): - return False - return True - if type == "simple": - B = self.blocks() - for b in B: - if B.count(b) > 1: - return False - return True - if type == "connected": - Gamma = self.incidence_graph() - if Gamma.is_connected(): - return True - else: - return False + if copy: + return self._points[:] + return self._points + + def num_points(self): + r""" + The number of points in that design. - def blocks(self): + EXAMPLES:: + + sage: designs.DesarguesianProjectivePlaneDesign(2).num_points() + 7 + sage: B = designs.IncidenceStructure(4, [[0,1],[0,2],[0,3],[1,2], [1,2,3]]) + sage: B.num_points() + 4 """ - Return the list of blocks. + return len(self._points) + + def num_blocks(self): + r""" + The number of blocks. EXAMPLES:: - sage: from sage.combinat.designs.block_design import BlockDesign - sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: designs.DesarguesianProjectivePlaneDesign(2).num_blocks() + 7 + sage: B = designs.IncidenceStructure(4, [[0,1],[0,2],[0,3],[1,2], [1,2,3]]) + sage: B.num_blocks() + 5 + """ + return len(self._blocks) + + def blocks(self, copy=True): + """Return the list of blocks. + + INPUT: + + - ``copy`` (boolean) -- ``True`` by default. When set to ``False``, a + pointer toward the object's internal data is given. Set it to + ``False`` only if you know what you are doing. + + EXAMPLES:: + + sage: BD = designs.IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: BD.blocks() [[0, 1, 2], [0, 3, 4], [0, 5, 6], [1, 3, 5], [1, 4, 6], [2, 3, 6], [2, 4, 5]] + + What you should pay attention to:: + + sage: blocks = BD.blocks(copy=False) + sage: del blocks[0:6] + sage: BD + Incidence structure with 7 points and 1 blocks + """ - B = sorted(self.blcks) - return B + if copy: + if self._point_to_index is None: + from copy import deepcopy + return deepcopy(self._blocks) + else: + return [[self._points[i] for i in b] for b in self._blocks] + else: + return self._blocks - def __eq__(self, other): + def block_sizes(self): + r""" + Return the set of block sizes. + + EXAMPLES:: + + sage: BD = designs.IncidenceStructure(8, [[0,1,3],[1,4,5,6],[1,2],[5,6,7]]) + sage: BD.block_sizes() + [3, 2, 4, 3] + sage: BD = designs.IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD.block_sizes() + [3, 3, 3, 3, 3, 3, 3] """ - Returns true if their points and blocks are equal (resp.). + return map(len, self._blocks) + + def is_connected(self): + r""" + Test whether the design is connected. EXAMPLES:: - sage: from sage.combinat.designs.block_design import BlockDesign - sage: BD1 = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) - sage: M = BD1.incidence_matrix() - sage: BD2 = IncidenceStructureFromMatrix(M) - sage: BD1 == BD2 + sage: designs.IncidenceStructure(3, [[0,1],[0,2]]).is_connected() True + sage: designs.IncidenceStructure(4, [[0,1],[2,3]]).is_connected() + False """ - bool1 = self.points() == other.points() - bool2 = self.blocks() == other.blocks() - return (bool1 and bool2) + return self.incidence_graph().is_connected() - def block_sizes(self): - """ - Return a list of block's sizes. + def is_simple(self): + r""" + Test whether this design is simple (i.e. no repeated block). EXAMPLES:: - sage: from sage.combinat.designs.block_design import BlockDesign - sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) - sage: BD.block_sizes() - [3, 3, 3, 3, 3, 3, 3] + sage: designs.IncidenceStructure(3, [[0,1],[1,2],[0,2]]).is_simple() + True + sage: designs.IncidenceStructure(3, [[0],[0]]).is_simple() + False + + sage: V = [(0,'a'),(0,'b'),(1,'a'),(1,'b')] + sage: B = [[V[0],V[1]], [V[1],V[2]]] + sage: I = designs.IncidenceStructure(V, B) + sage: I.is_simple() + True + sage: I2 = designs.IncidenceStructure(V, B*2) + sage: I2.is_simple() + False """ - self._block_sizes = map(len, self.blocks()) - return self._block_sizes + B = self._blocks + return all(B[i] != B[i+1] for i in xrange(len(B)-1)) def _gap_(self): """ @@ -379,23 +510,274 @@ def _gap_(self): EXAMPLES:: - sage: from sage.combinat.designs.block_design import BlockDesign - sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD = designs.IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: BD._gap_() 'BlockDesign(7,[[1, 2, 3], [1, 4, 5], [1, 6, 7], [2, 4, 6], [2, 5, 7], [3, 4, 7], [3, 5, 6]])' """ B = self.blocks() - v = len(self.points()) - gB = [] - for b in B: - gB.append([x+1 for x in b]) + v = self.num_points() + gB = [[x+1 for x in b] for b in self._blocks] return "BlockDesign("+str(v)+","+str(gB)+")" - def dual_incidence_structure(self, algorithm=None): + def incidence_matrix(self): + r""" + Return the incidence matrix `A` of the design. A is a `(v \times b)` + matrix defined by: ``A[i,j] = 1`` if ``i`` is in block ``B_j`` and 0 + otherwise. + + EXAMPLES:: + + sage: BD = designs.IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD.block_sizes() + [3, 3, 3, 3, 3, 3, 3] + sage: BD.incidence_matrix() + [1 1 1 0 0 0 0] + [1 0 0 1 1 0 0] + [1 0 0 0 0 1 1] + [0 1 0 1 0 1 0] + [0 1 0 0 1 0 1] + [0 0 1 1 0 0 1] + [0 0 1 0 1 1 0] + + sage: I = designs.IncidenceStructure('abc', ('ab','abc','ac','c')) + sage: I.incidence_matrix() + [1 1 1 0] + [1 1 0 0] + [0 1 1 1] """ - Returns the dual of the incidence structure. + from sage.matrix.constructor import Matrix + from sage.rings.all import ZZ + A = Matrix(ZZ, self.num_points(), self.num_blocks(), sparse=True) + for j, b in enumerate(self._blocks): + for i in b: + A[i, j] = 1 + return A + + def incidence_graph(self): + """ + Returns the incidence graph of the design, where the incidence + matrix of the design is the adjacency matrix of the graph. + + EXAMPLE:: + + sage: BD = designs.IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD.incidence_graph() + Bipartite graph on 14 vertices + sage: A = BD.incidence_matrix() + sage: Graph(block_matrix([[A*0,A],[A.transpose(),A*0]])) == BD.incidence_graph() + True + + REFERENCE: + + - Sage Reference Manual on Graphs + """ + from sage.graphs.bipartite_graph import BipartiteGraph + A = self.incidence_matrix() + return BipartiteGraph(A) + + ##################### + # real computations # + ##################### + + def is_t_design(self, t=None, v=None, k=None, l=None, return_parameters=False): + r""" + Test whether ``self`` is a `t-(v,k,l)` design. + + A `t-(v,k,\lambda)` (sometimes called `t`-design for short) is a block + design in which: + + - the underlying set has cardinality `v` + - the blocks have size `k` + - each `t`-subset of points is covered by `\lambda` blocks + + INPUT: + + - ``t,v,k,l`` (integers) -- their value is set to ``None`` by + default. The function tests whether the design is a ``t-(v,k,l)`` + design using the provided values and guesses the others. Note that + `l`` cannot be specified if ``t`` is not. + + - ``return_parameters`` (boolean)-- whether to return the parameters of + the `t`-design. If set to ``True``, the function returns a pair + ``(boolean_answer,(t,v,k,l))``. + + EXAMPLES:: + + sage: fano_blocks = [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]] + sage: BD = designs.IncidenceStructure(7, fano_blocks) + sage: BD.is_t_design() + True + sage: BD.is_t_design(return_parameters=True) + (True, (2, 7, 3, 1)) + sage: BD.is_t_design(2, 7, 3, 1) + True + sage: BD.is_t_design(1, 7, 3, 3) + True + sage: BD.is_t_design(0, 7, 3, 7) + True + + sage: BD.is_t_design(0,6,3,7) or BD.is_t_design(0,7,4,7) or BD.is_t_design(0,7,3,8) + False + + sage: BD = designs.AffineGeometryDesign(3, 1, GF(2)) + sage: BD.is_t_design(1) + True + sage: BD.is_t_design(2) + True + + Steiner triple and quadruple systems are other names for `2-(v,3,1)` and + `3-(v,4,1)` designs:: + + sage: S3_9 = designs.steiner_triple_system(9) + sage: S3_9.is_t_design(2,9,3,1) + True + + sage: blocks = designs.steiner_quadruple_system(8) + sage: S4_8 = designs.IncidenceStructure(8, blocks) + sage: S4_8.is_t_design(3,8,4,1) + True + + sage: blocks = designs.steiner_quadruple_system(14) + sage: S4_14 = designs.IncidenceStructure(14, blocks) + sage: S4_14.is_t_design(3,14,4,1) + True + + Some examples of Witt designs that need the gap database:: + + sage: BD = designs.WittDesign(9) # optional - gap_packages + sage: BD.is_t_design(2,9,3,1) # optional - gap_packages + True + sage: W12 = designs.WittDesign(12) # optional - gap_packages + sage: W12.is_t_design(5,12,6,1) # optional - gap_packages + True + sage: W12.is_t_design(4) # optional - gap_packages + True + + Further examples:: + + sage: D = designs.IncidenceStructure(4,[[],[]]) + sage: D.is_t_design(return_parameters=True) + (True, (0, 4, 0, 2)) + + sage: D = designs.IncidenceStructure(4, [[0,1],[0,2],[0,3]]) + sage: D.is_t_design(return_parameters=True) + (True, (0, 4, 2, 3)) + + sage: D = designs.IncidenceStructure(4, [[0],[1],[2],[3]]) + sage: D.is_t_design(return_parameters=True) + (True, (1, 4, 1, 1)) + + sage: D = designs.IncidenceStructure(4,[[0,1],[2,3]]) + sage: D.is_t_design(return_parameters=True) + (True, (1, 4, 2, 1)) + + sage: D = designs.IncidenceStructure(4, [range(4)]) + sage: D.is_t_design(return_parameters=True) + (True, (4, 4, 4, 1)) + + TESTS:: + + sage: blocks = designs.steiner_quadruple_system(8) + sage: S4_8 = designs.IncidenceStructure(8, blocks) + sage: R = range(15) + sage: [(v,k,l) for v in R for k in R for l in R if S4_8.is_t_design(3,v,k,l)] + [(8, 4, 1)] + sage: [(v,k,l) for v in R for k in R for l in R if S4_8.is_t_design(2,v,k,l)] + [(8, 4, 3)] + sage: [(v,k,l) for v in R for k in R for l in R if S4_8.is_t_design(1,v,k,l)] + [(8, 4, 7)] + sage: [(v,k,l) for v in R for k in R for l in R if S4_8.is_t_design(0,v,k,l)] + [(8, 4, 14)] + sage: A = designs.AffineGeometryDesign(3, 1, GF(2)) + sage: A.is_t_design(return_parameters=True) + (True, (2, 8, 2, 1)) + sage: A = designs.AffineGeometryDesign(4, 2, GF(2)) + sage: A.is_t_design(return_parameters=True) + (True, (3, 16, 4, 1)) + sage: I = designs.IncidenceStructure(2, []) + sage: I.is_t_design(return_parameters=True) + (True, (0, 2, 0, 0)) + sage: I = designs.IncidenceStructure(2, [[0],[0,1]]) + sage: I.is_t_design(return_parameters=True) + (False, (0, 0, 0, 0)) + """ + from sage.rings.arith import binomial - Note that the dual of a block design may not be a block design. + # Missing parameters ? + if v is None: + v = self.num_points() + + if k is None: + k = len(self._blocks[0]) if self._blocks else 0 + + if l is not None and t is None: + raise ValueError("t must be set when l=None") + + b = self.num_blocks() + + # Trivial wrong answers + if (any(len(block) != k for block in self._blocks) or # non k-uniform + v != self.num_points()): + return (False, (0,0,0,0)) if return_parameters else False + + # Trivial case t>k + if (t is not None and t>k): + if (l is None or l == 0): + return (True, (t,v,k,0)) if return_parameters else True + else: + return (False, (0,0,0,0)) if return_parameters else False + + # Trivial case k=0 + if k==0: + if (l is None or l == 0): + return (True, (0,v,k,b)) if return_parameters else True + else: + return (False, (0,0,0,0)) if return_parameters else False + + # Trivial case k=v (includes v=0) + if k == v: + if t is None: + t = v + if l is None or b == l: + return (True, (t,v,k,b)) if return_parameters else True + else: + return (True, (0,0,0,0)) if return_parameters else False + + # Handbook of combinatorial design theorem II.4.8: + # + # a t-(v,k,l) is also a t'-(v,k,l') + # for t' < t and l' = l* binomial(v-t',t-t') / binomial(k-t',t-t') + # + # We look for the largest t such that self is a t-design + from itertools import combinations + for tt in (range(1,k+1) if t is None else [t]): + # is lambda an integer? + if (b*binomial(k,tt)) % binomial(v,tt) != 0: + tt -= 1 + break + + s = {} + for block in self._blocks: + for i in combinations(block,tt): + s[i] = s.get(i,0) + 1 + + if len(set(s.values())) != 1: + tt -= 1 + break + + ll = b*binomial(k,tt) // binomial(v,tt) + + if ((t is not None and t!=tt) or + (l is not None and l!=ll)): + return (False, (0,0,0,0)) if return_parameters else False + else: + if tt == 0: + ll = b + return (True, (tt,v,k,ll)) if return_parameters else True + + def dual(self, algorithm=None): + """ + Returns the dual of the incidence structure. INPUT: @@ -407,35 +789,30 @@ def dual_incidence_structure(self, algorithm=None): The ``algorithm="gap"`` option requires GAP's Design package (included in the gap_packages Sage spkg). - Also can be called with ``dual_design``. - EXAMPLES: The dual of a projective plane is a projective plane:: sage: PP = designs.DesarguesianProjectivePlaneDesign(4) - sage: PP.dual_design().is_block_design() - (True, [2, 21, 5, 1]) - sage: PP = designs.DesarguesianProjectivePlaneDesign(4) # optional - gap_packages - sage: PP.dual_design(algorithm="gap").is_block_design() # optional - gap_packages - (True, [2, 21, 5, 1]) + sage: PP.dual().is_t_design(return_parameters=True) + (True, (2, 21, 5, 1)) TESTS:: - sage: from sage.combinat.designs.block_design import BlockDesign - sage: D = BlockDesign(4, [[0,2],[1,2,3],[2,3]], test=False) + sage: D = designs.IncidenceStructure(4, [[0,2],[1,2,3],[2,3]]) sage: D Incidence structure with 4 points and 3 blocks - sage: D.dual_design() + sage: D.dual() Incidence structure with 3 points and 4 blocks - sage: print D.dual_design(algorithm="gap") # optional - gap_packages + sage: print D.dual(algorithm="gap") # optional - gap_packages IncidenceStructure - sage: BD = IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]], name="FanoPlane") + sage: blocks = [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]] + sage: BD = designs.IncidenceStructure(7, blocks, name="FanoPlane"); sage: BD Incidence structure with 7 points and 7 blocks - sage: print BD.dual_design(algorithm="gap") # optional - gap_packages + sage: print BD.dual(algorithm="gap") # optional - gap_packages IncidenceStructure - sage: BD.dual_incidence_structure() + sage: BD.dual() Incidence structure with 7 points and 7 blocks REFERENCE: @@ -453,219 +830,311 @@ def dual_incidence_structure(self, algorithm=None): gB = [] for b in gblcks: gB.append([x-1 for x in b]) - return IncidenceStructure(range(v), gB, name=None, test=False) + return IncidenceStructure(range(v), gB, name=None, check=False) else: - M = self.incidence_matrix() - new_blocks = [list(r.dict(copy=False)) for r in M.rows()] - return IncidenceStructure(range(M.ncols()), new_blocks, name=None, test=False) - - dual_design = dual_incidence_structure # to preserve standard terminology + return IncidenceStructure( + incidence_matrix=self.incidence_matrix().transpose(), + check=False) - def incidence_matrix(self): - """ - Return the incidence matrix `A` of the design. A is a `(v \times b)` - matrix defined by: ``A[i,j] = 1`` if ``i`` is in block ``B_j`` and 0 - otherwise. + def automorphism_group(self): + r""" + Returns the subgroup of the automorphism group of the incidence graph + which respects the P B partition. It is (isomorphic to) the automorphism + group of the block design, although the degrees differ. EXAMPLES:: - sage: BD = IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) - sage: BD.block_sizes() - [3, 3, 3, 3, 3, 3, 3] - sage: BD.incidence_matrix() - [1 1 1 0 0 0 0] - [1 0 0 1 1 0 0] - [1 0 0 0 0 1 1] - [0 1 0 1 0 1 0] - [0 1 0 0 1 0 1] - [0 0 1 1 0 0 1] - [0 0 1 0 1 1 0] - """ - if not self._incidence_matrix is None: - return self._incidence_matrix - else: - from sage.matrix.constructor import Matrix - v = len(self.points()) - blks = self.blocks() - b = len(blks) - A = Matrix(ZZ, v, b, sparse=True) - for j, b in enumerate(blks): - for i in b: - A[i, j] = 1 - self._incidence_matrix = A - return A + sage: P = designs.DesarguesianProjectivePlaneDesign(2); P + Incidence structure with 7 points and 7 blocks + sage: G = P.automorphism_group() + sage: G.is_isomorphic(PGL(3,2)) + True + sage: G + Permutation Group with generators [(2,3)(4,5), (2,4)(3,5), (1,2)(4,6), (0,1)(4,5)] - def incidence_graph(self): + A non self-dual example:: + + sage: IS = designs.IncidenceStructure(range(4), [[0,1,2,3],[1,2,3]]) + sage: IS.automorphism_group().cardinality() + 6 + sage: IS.dual().automorphism_group().cardinality() + 1 + + An example with points other than integers:: + + sage: I = designs.IncidenceStructure('abc', ('ab','ac','bc')) + sage: I.automorphism_group() + Permutation Group with generators [('b','c'), ('a','b')] """ - Returns the incidence graph of the design, where the incidence - matrix of the design is the adjacency matrix of the graph. + from sage.groups.perm_gps.partn_ref.refinement_matrices import MatrixStruct + from sage.groups.perm_gps.permgroup import PermutationGroup + from sage.groups.perm_gps.permgroup_named import SymmetricGroup + M1 = self.incidence_matrix().transpose() + M2 = MatrixStruct(M1) + M2.run() + gens = M2.automorphism_group()[0] + if self._point_to_index: + gens = [[self._points[i] for i in p] for p in gens] + return PermutationGroup(gens, domain=self._points) - EXAMPLE:: + ############### + # Deprecation # + ############### - sage: BD = IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) - sage: BD.incidence_graph() - Bipartite graph on 14 vertices - sage: A = BD.incidence_matrix() - sage: Graph(block_matrix([[A*0,A],[A.transpose(),A*0]])) == BD.incidence_graph() - True + def parameters(self): + r""" + Deprecated function. You should use :meth:`is_t_design` instead. - REFERENCE: + EXAMPLES:: - - Sage Reference Manual on Graphs + sage: I = designs.IncidenceStructure('abc', ['ab','ac','bc']) + sage: I.parameters() + doctest:...: DeprecationWarning: .parameters() is deprecated. Use + `is_t_design` instead + See http://trac.sagemath.org/16553 for details. + (2, 3, 2, 1) """ - from sage.graphs.bipartite_graph import BipartiteGraph - A = self.incidence_matrix() - return BipartiteGraph(A) + from sage.misc.superseded import deprecation + deprecation(16553, ".parameters() is deprecated. Use `is_t_design` instead") + return self.is_t_design(return_parameters=True)[1] - def is_block_design(self, verbose=False): + dual_design = deprecated_function_alias(16553, dual) + dual_incidence_structure = deprecated_function_alias(16553, dual) + is_block_design = deprecated_function_alias(16553, is_t_design) + points = deprecated_function_alias(16553, ground_set) + + def block_design_checker(self, t, v, k, lmbda, type=None): """ - Returns a pair ``True, pars`` if the incidence structure is a - `t`-design, for some `t`, where ``pars`` is the list of parameters `(t, - v, k, lmbda)`. The largest possible `t` is returned, provided `t=10`. + This method is deprecated and will soon be removed (see :trac:`16553`). + You could use :meth:`is_t_design` instead. + + This is *not* a wrapper for GAP Design's IsBlockDesign. The GAP + Design function IsBlockDesign + http://www.gap-system.org/Manuals/pkg/design/htm/CHAP004.htm + apparently simply checks the record structure and no mathematical + properties. Instead, the function below checks some necessary (but + not sufficient) "easy" identities arising from the identity. INPUT: - - ``verbose`` (boolean) -- prints useful information when the answer is - negative. + - ``t`` - the t as in "t-design" + + - ``v`` - the number of points + + - ``k`` - the number of blocks incident to a point + + - ``lmbda`` - each t-tuple of points should be incident with + lmbda blocks + + - ``type`` - can be 'simple' or 'binary' or 'connected' + Depending on the option, this wraps IsBinaryBlockDesign, + IsSimpleBlockDesign, or IsConnectedBlockDesign. + + - Binary: no block has a repeated element. + + - Simple: no block is repeated. + + - Connected: its incidence graph is a connected graph. + + WARNING: This is very fast but can return false positives. EXAMPLES:: - sage: BD = IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) - sage: BD.is_block_design() - (True, [2, 7, 3, 1]) + sage: BD = designs.IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) + sage: BD.is_t_design(return_parameters=True) + (True, (2, 7, 3, 1)) sage: BD.block_design_checker(2, 7, 3, 1) + doctest:...: DeprecationWarning: .block_design_checker(v,t,k,lmbda) is deprecated; please use + .is_t_design(v,t,k,lmbda) instead + See http://trac.sagemath.org/16553 for details. True - sage: BD = designs.WittDesign(9) # optional - gap_packages (design package) - sage: BD.is_block_design() # optional - gap_packages (design package) - (True, [2, 9, 3, 1]) - sage: BD = designs.WittDesign(12) # optional - gap_packages (design package) - sage: BD.is_block_design() # optional - gap_packages (design package) - (True, [5, 12, 6, 1]) - sage: BD = designs.AffineGeometryDesign(3, 1, GF(2)) - sage: BD.is_block_design() - (True, [2, 8, 2, 1]) - """ - from sage.rings.arith import binomial - from itertools import combinations - v = len(self.points()) - b = len(self.blcks) - # Definition and consistency of 'k' and 'r' - # - # r_list stores the degree of each point - k = len(self.blcks[0]) - r_list = [0]*v - for block in self.blcks: - if len(block) != k: - if verbose: - print "All blocks do not have the same size" - return False - for x in block: - r_list[x] += 1 - - r = r_list[0] - if any(x!=r for x in r_list): - if verbose: - print "All points do not have the same degree" - return False + sage: BD.block_design_checker(2, 7, 3, 1,"binary") + doctest:...: DeprecationWarning: .block_design_checker(type='binary') is + deprecated; use .is_binary() instead + See http://trac.sagemath.org/16553 for details. + True - # Definition and consistency of 'l' (lambda) and 't' - t_found_yet = False + sage: BD.block_design_checker(2, 7, 3, 1,"connected") + doctest:...: DeprecationWarning: block_design_checker(type='connected') is + deprecated, please use .is_connected() instead + See http://trac.sagemath.org/16553 for details. + True - for t in range(2,min(v,k+1)): - # Is lambda an integer ? - if (b*binomial(k,t)) % binomial(v,t) == 0: - l = (b*binomial(k,t))/binomial(v,t) - else: - continue + sage: BD.block_design_checker(2, 7, 3, 1,"simple") + doctest:...: DeprecationWarning: .block_design_checker(type='simple') + is deprecated; all designs here are simple! + See http://trac.sagemath.org/16553 for details. + True + """ + from sage.misc.superseded import deprecation - # Associates to every t-subset of [v] the number of its occurrences - # as a subset of a block - t_counts = {} - for block in self.blcks: - for t_set in combinations(sorted(block),t): - t_counts[t_set] = t_counts.get(t_set,0)+1 + ans = self.is_t_design(t,v,k,lmbda) - # Checking the consistency of l - l_values = t_counts.values() + if type is None: + deprecation(16553, ".block_design_checker(v,t,k,lmbda) is deprecated; please use .is_t_design(v,t,k,lmbda) instead") + return ans - if all(l == x for x in l_values): - t_found_yet = True - t_lambda = t,l + if type == "binary": + deprecation(16553, ".block_design_checker(type='binary') is deprecated; use .is_binary() instead") + return True + if type == "simple": + deprecation(16553, ".block_design_checker(type='simple') is deprecated; all designs here are simple!") + return True + if type == "connected": + deprecation(16553, "block_design_checker(type='connected') is deprecated, please use .is_connected() instead") + return self.incidence_graph().is_connected() - if t_found_yet: - t,l = t_lambda - return (True, [t,v,k,l]) - else: - return (False, [0,0,0,0]) + def edge_coloring(self): + r""" + Compute a proper edge-coloring. - def parameters(self, t=None): - """ - Returns `(t,v,k,lambda)`. Does not check if the input is a block - design. + A proper edge-coloring is an assignment of colors to the sets of the + incidence structure such that two sets with non-empty intersection + receive different colors. The coloring returned minimizes the number of + colors. - INPUT: + OUTPUT: - - ``t`` -- `t` such that the design is a `t`-design. + A partition of the sets into color classes. EXAMPLES:: - sage: from sage.combinat.designs.block_design import BlockDesign - sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]], name="FanoPlane") - sage: BD.parameters(t=2) - (2, 7, 3, 1) - sage: BD.parameters(t=3) - (3, 7, 3, 0) + sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H + Incidence structure with 6 points and 4 blocks + sage: C = H.edge_coloring() + sage: C # random + [[[3, 4, 5]], [[2, 3, 4]], [[4, 5, 6], [1, 2, 3]]] + sage: Set(map(Set,sum(C,[]))) == Set(map(Set,H.blocks())) + True """ - if t is None: - from sage.misc.superseded import deprecation - deprecation(15664, "the 't' argument will become mandatory soon. 2"+ - " is used when none is provided.") - t = 2 + from sage.graphs.graph import Graph + blocks = self.blocks() + blocks_sets = map(frozenset,blocks) + g = Graph([range(self.num_blocks()),lambda x,y : len(blocks_sets[x]&blocks_sets[y])],loops = False) + return [[blocks[i] for i in C] for C in g.coloring(algorithm="MILP")] - v = len(self.points()) - blks = self.blocks() - k = len(blks[int(0)]) - b = len(blks) - #A = self.incidence_matrix() - #r = sum(A.rows()[0]) - lmbda = int(b/(binomial(v, t)/binomial(k, t))) - return (t, v, k, lmbda) + def _spring_layout(self): + r""" + Return a spring layout for the points. - def points(self): - """ - Returns the list of points. + The layout is computed by creating a graph `G` on the points *and* sets + of the incidence structure. Each set is then made adjacent in `G` with + all points it contains before a spring layout is computed for this + graph. The position of the points in the graph gives the position of the + points in the final drawing. + + .. NOTE:: + + This method also returns the position of the "fake" points, + i.e. those representing the sets. EXAMPLES:: - sage: from sage.combinat.designs.block_design import BlockDesign - sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) - sage: BD.points() - [0, 1, 2, 3, 4, 5, 6] + sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H + Incidence structure with 6 points and 4 blocks + sage: L = H._spring_layout() + sage: L # random + {1: (0.238, -0.926), + 2: (0.672, -0.518), + 3: (0.449, -0.225), + 4: (0.782, 0.225), + 5: (0.558, 0.518), + 6: (0.992, 0.926), + {3, 4, 5}: (0.504, 0.173), + {2, 3, 4}: (0.727, -0.173), + {4, 5, 6}: (0.838, 0.617), + {1, 2, 3}: (0.393, -0.617)} + sage: all(v in L for v in H.ground_set()) + True + sage: all(v in L for v in map(Set,H.blocks())) + True """ - return self.pnts + from sage.graphs.graph import Graph - def points_from_gap(self): - """ - Literally pushes this block design over to GAP and returns the - points of that. Other than debugging, usefulness is unclear. + g = Graph() + for s in map(Set,self.blocks()): + for x in s: + g.add_edge(s,x) + + _ = g.plot(iterations = 50000,save_pos=True) + + # The values are rounded as TikZ does not like accuracy. + return {k:(round(x,3),round(y,3)) for k,(x,y) in g.get_pos().items()} - REQUIRES: GAP's Design package. + def _latex_(self): + r""" + Return a TikZ representation of the incidence structure EXAMPLES:: - sage: from sage.combinat.designs.block_design import BlockDesign - sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) - sage: BD.points_from_gap() # optional - gap_packages (design package) - doctest:1: DeprecationWarning: Unless somebody protests this method will be removed, as nobody seems to know why it is there. - See http://trac.sagemath.org/14499 for details. - [1, 2, 3, 4, 5, 6, 7] + sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H + Incidence structure with 6 points and 4 blocks + sage: view(H) # not tested + + With sets of size 4:: + + sage: g = graphs.Grid2dGraph(5,5) + sage: C4 = graphs.CycleGraph(4) + sage: sets = Set(map(Set,list(g.subgraph_search_iterator(C4)))) + sage: H = Hypergraph(sets) + sage: view(H) # not tested """ - from sage.misc.superseded import deprecation - deprecation(14499, ('Unless somebody protests this method will be ' - 'removed, as nobody seems to know why it is there.')) - from sage.interfaces.gap import gap - gap.load_package("design") - gD = self._gap_() - gP = gap.eval("BlockDesignPoints("+gD+")").replace("..", ",") - return range(eval(gP)[0], eval(gP)[1]+1) + from sage.rings.integer import Integer + from sage.functions.trig import arctan2 + + from sage.misc.misc import warn + warn("\nThe hypergraph is drawn as a set of closed curves. The curve " + "representing a set S go **THROUGH** the points contained " + "in S.\n A point which is encircled by a curve but is not located " + "on its boundary is **NOT** included in the corresponding set.\n" + "\n" + "The colors are picked for readability and have no other meaning.") + + latex.add_package_to_preamble_if_available("tikz") + latex.add_to_mathjax_avoid_list("tikz") + + if not latex.has_file("tikz.sty"): + raise RuntimeError("You must have TikZ installed in order " + "to draw a hypergraph.") + + domain = self.ground_set() + pos = self._spring_layout() + tex = "\\begin{tikzpicture}[scale=3]\n" + + colors = ["black", "red", "green", "blue", "cyan", "magenta", "yellow","pink","brown"] + colored_sets = [(s,i) for i,S in enumerate(self.edge_coloring()) for s in S] + + # Prints each set with its color + for s,i in colored_sets: + current_color = colors[i%len(colors)] + + if len(s) == 2: + s = list(s) + tex += ("\\draw[color="+str(current_color)+","+ + "line width=.1cm,opacity = .6] "+ + str(pos[s[0]])+" -- "+str(pos[s[1]])+";\n") + continue + + tex += ("\\draw[color="+str(current_color)+"," + "line width=.1cm,opacity = .6," + "line cap=round," + "line join=round]" + "plot [smooth cycle,tension=1] coordinates {") + + # Reorders the vertices of s according to their angle with the + # "center", i.e. the vertex representing the set s + cx, cy = pos[Set(s)] + s = map(lambda x: pos[x], s) + s = sorted(s, key = lambda x_y: arctan2(x_y[0] - cx, x_y[1] - cy)) + + for x in s: + tex += str(x)+" " + tex += "};\n" + + # Prints each vertex + for v in domain: + tex += "\\draw node[fill,circle,scale=.5,label={90:$"+latex(v)+"$}] at "+str(pos[v])+" {};\n" + + tex += "\\end{tikzpicture}" + return tex diff --git a/src/sage/combinat/designs/latin_squares.py b/src/sage/combinat/designs/latin_squares.py index d237b2f94a4..f679522e4bc 100644 --- a/src/sage/combinat/designs/latin_squares.py +++ b/src/sage/combinat/designs/latin_squares.py @@ -2,50 +2,95 @@ r""" Mutually Orthogonal Latin Squares (MOLS) -A Latin square is an `n\times n` array filled with `n` different symbols, each -occurring exactly once in each row and exactly once in each column. For Sage's -methods related to Latin Squares, see the module -:mod:`sage.combinat.matrices.latin`. +This module gathers Sage's functions related to Mutually Orthogonal Latin +Squares. Its main function is :func:`mutually_orthogonal_latin_squares` which +can be used to generate MOLS:: -This module gathers constructions of Mutually Orthogonal Latin Squares, which -are equivalent to Transversal Designs and specific Orthogonal Arrays. + sage: MOLS = designs.mutually_orthogonal_latin_squares(4,8) For more information on MOLS, see the :wikipedia:`Wikipedia entry on MOLS -`. +`. If you are only +interested by latin squares, see :mod:`~sage.combinat.matrices.latin`. The following table prints the maximum number of MOLS that Sage can build for -every order `n<300`, similarly to the `table of MOLS +every order `n<600`, similarly to the `table of MOLS `_ -from the Handbook of Combinatorial Designs. +from the Handbook of Combinatorial Designs 2ed [DesignHandbook]_. :: - sage: def MOLS_table(number_of_lines): - ....: print " "+join(['%3s'%str(i) for i in range(20)]) - ....: print " "+"_"*80 - ....: for i in range(20*15): - ....: if i%20==0: - ....: print "\n"+'%3s'%str(i)+"|", - ....: print '%3s'%str(designs.mutually_orthogonal_latin_squares(i,None,existence=True) if i>1 else "+oo"), - sage: MOLS_table(15) # long time + sage: from sage.combinat.designs.latin_squares import MOLS_table + sage: MOLS_table(30) # long time 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ________________________________________________________________________________ - 0| +oo +oo 1 2 3 4 1 6 7 8 2 10 5 12 4 4 15 16 5 18 20| 4 5 3 22 7 24 4 26 5 28 4 30 31 5 4 5 8 36 4 5 - 40| 7 40 5 42 5 6 4 46 8 48 6 5 5 52 5 6 7 7 2 58 + 40| 7 40 5 42 5 6 4 46 8 48 6 5 5 52 5 6 7 7 5 58 60| 5 60 5 6 63 7 5 66 5 6 6 70 7 72 5 7 6 6 6 78 - 80| 9 80 8 82 6 6 6 3 7 88 4 6 6 4 3 6 7 96 6 8 - 100| 8 100 6 102 7 7 5 106 5 108 4 6 7 112 3 7 5 8 4 6 - 120| 6 120 5 6 5 124 6 126 127 7 6 130 6 6 6 6 7 136 4 138 - 140| 6 7 6 10 10 7 6 7 5 148 6 150 7 8 8 5 5 156 4 6 - 160| 7 7 6 162 5 7 4 166 7 168 6 8 6 172 6 6 10 9 6 178 - 180| 6 180 6 6 7 8 6 10 6 6 6 190 7 192 6 7 6 196 6 198 - 200| 7 7 6 7 6 6 6 8 12 11 10 210 6 7 6 7 7 8 6 10 - 220| 6 12 6 222 7 8 6 226 6 228 6 6 7 232 6 7 6 6 5 238 - 240| 7 240 6 242 6 7 6 12 7 7 5 250 6 10 7 7 255 256 4 7 - 260| 6 8 7 262 7 8 6 10 6 268 6 270 15 16 5 10 10 276 6 8 - 280| 7 280 6 282 6 12 6 7 15 288 6 6 5 292 6 6 7 10 10 12 + 80| 9 80 8 82 6 6 6 6 7 88 6 7 6 6 6 6 7 96 6 8 + 100| 8 100 6 102 7 7 6 106 6 108 6 6 7 112 6 7 6 8 6 6 + 120| 7 120 6 6 6 124 6 126 127 7 6 130 6 7 6 7 7 136 6 138 + 140| 6 7 6 10 10 7 6 7 6 148 6 150 7 8 8 7 6 156 7 6 + 160| 7 7 6 162 6 7 6 166 7 168 6 8 6 172 6 6 10 9 6 178 + 180| 6 180 6 6 7 8 6 10 6 7 6 190 7 192 6 7 6 196 6 198 + 200| 7 7 6 7 6 7 6 8 12 11 10 210 6 7 6 7 7 8 6 10 + 220| 6 12 6 222 7 8 6 226 6 228 6 7 7 232 6 7 6 7 6 238 + 240| 7 240 6 242 6 7 6 12 7 7 6 250 6 10 7 7 255 256 6 12 + 260| 6 8 7 262 7 8 7 10 7 268 7 270 15 16 6 13 10 276 6 9 + 280| 7 280 6 282 6 12 6 7 15 288 6 6 6 292 6 6 7 10 10 12 + 300| 7 7 7 7 15 15 6 306 7 7 7 310 7 312 7 10 7 316 7 10 + 320| 15 15 6 16 8 12 6 7 7 9 6 330 7 8 7 6 7 336 6 7 + 340| 6 10 10 342 7 7 6 346 6 348 8 12 10 352 6 9 7 7 6 358 + 360| 7 360 6 7 7 7 6 366 15 15 7 15 7 372 7 15 7 13 7 378 + 380| 7 12 7 382 15 15 7 15 7 388 7 16 7 7 7 7 8 396 7 7 + 400| 15 400 7 15 11 8 7 15 7 408 7 13 8 12 10 9 15 15 7 418 + 420| 7 420 7 15 7 16 6 7 7 7 6 430 15 432 6 15 6 18 7 438 + 440| 7 15 7 442 7 13 7 11 15 448 7 15 7 7 7 15 7 456 7 16 + 460| 7 460 7 462 15 15 7 466 8 8 7 15 7 15 10 18 7 15 6 478 + 480| 15 15 6 15 8 7 6 486 7 15 6 490 6 16 6 7 15 15 6 498 + 500| 7 8 9 502 7 15 6 15 7 508 6 15 511 18 6 15 8 12 8 15 + 520| 7 520 6 522 7 15 8 16 15 528 7 15 8 12 7 15 8 15 10 15 + 540| 12 540 7 15 16 7 7 546 7 8 7 18 7 7 7 7 7 556 7 12 + 560| 7 7 7 562 7 7 6 7 7 568 6 570 7 7 15 22 8 576 7 7 + 580| 7 8 7 10 7 8 7 586 7 18 17 7 15 592 8 15 7 7 8 598 + + +Comparison with the results from the Handbook of Combinatorial Designs (2ed) +[DesignHandbook]_:: + + sage: MOLS_table(30,compare=True) # long time + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + ________________________________________________________________________________ + 0| + + + 20| + 40| + 60| + + 80| + 100| - + 120| + 140| + 160| - - + 180| - - + 200| - - - + 220| - + 240| - - + 260| - + 280| + 300| + 320| - + 340| - - + 360| - - + 380| - + 400| - - + 420| - + 440| + 460| + 480| + 500| - - + 520| - - - + 540| - + 560| - + 580| TODO: @@ -95,7 +140,7 @@ def are_mutually_orthogonal_latin_squares(l, verbose=False): Squares 0 and 2 are not orthogonal False - sage: m = designs.mutually_orthogonal_latin_squares(8,7) + sage: m = designs.mutually_orthogonal_latin_squares(7,8) sage: are_mutually_orthogonal_latin_squares(m) True @@ -142,22 +187,20 @@ def are_mutually_orthogonal_latin_squares(l, verbose=False): from designs_pyx import is_orthogonal_array return is_orthogonal_array(zip(*[[x for R in M for x in R] for M in l]),k,n, verbose=verbose, terminology="MOLS") -def mutually_orthogonal_latin_squares(n,k, partitions = False, check = True, existence=False, who_asked=tuple()): +def mutually_orthogonal_latin_squares(k,n, partitions = False, check = True, existence=False): r""" Returns `k` Mutually Orthogonal `n\times n` Latin Squares (MOLS). - For more information on Latin Squares and MOLS, see - :mod:`~sage.combinat.designs.latin_squares` or the :wikipedia:`Latin_square`, - or even the - :wikipedia:`Wikipedia entry on MOLS `. + For more information on Mutually Orthogonal Latin Squares, see + :mod:`~sage.combinat.designs.latin_squares`. INPUT: - - ``n`` (integer) -- size of the latin square. - - ``k`` (integer) -- number of MOLS. If ``k=None`` it is set to the largest value available. + - ``n`` (integer) -- size of the latin square. + - ``partition`` (boolean) -- a Latin Square can be seen as 3 partitions of the `n^2` cells of the array into `n` sets of size `n`, respectively : @@ -193,14 +236,9 @@ def mutually_orthogonal_latin_squares(n,k, partitions = False, check = True, exi guys), you may want to disable it whenever you want speed. Set to ``True`` by default. - - ``who_asked`` (internal use only) -- because of the equivalence between - OA/TD/MOLS, each of the three constructors calls the others. We must keep - track of who calls who in order to avoid infinite loops. ``who_asked`` is - the tuple of the other functions that were called before this one. - EXAMPLES:: - sage: designs.mutually_orthogonal_latin_squares(5,4) + sage: designs.mutually_orthogonal_latin_squares(4,5) [ [0 2 4 1 3] [0 3 1 4 2] [0 4 3 2 1] [0 1 2 3 4] [4 1 3 0 2] [3 1 4 2 0] [2 1 0 4 3] [4 0 1 2 3] @@ -209,7 +247,7 @@ def mutually_orthogonal_latin_squares(n,k, partitions = False, check = True, exi [1 3 0 2 4], [2 0 3 1 4], [3 2 1 0 4], [1 2 3 4 0] ] - sage: designs.mutually_orthogonal_latin_squares(7,3) + sage: designs.mutually_orthogonal_latin_squares(3,7) [ [0 2 4 6 1 3 5] [0 3 6 2 5 1 4] [0 4 1 5 2 6 3] [6 1 3 5 0 2 4] [5 1 4 0 3 6 2] [4 1 5 2 6 3 0] @@ -220,7 +258,7 @@ def mutually_orthogonal_latin_squares(n,k, partitions = False, check = True, exi [1 3 5 0 2 4 6], [2 5 1 4 0 3 6], [3 0 4 1 5 2 6] ] - sage: designs.mutually_orthogonal_latin_squares(5,2,partitions=True) + sage: designs.mutually_orthogonal_latin_squares(2,5,partitions=True) [[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14], @@ -244,7 +282,7 @@ def mutually_orthogonal_latin_squares(n,k, partitions = False, check = True, exi What is the maximum number of MOLS of size 8 that Sage knows how to build?:: - sage: designs.mutually_orthogonal_latin_squares(8,None,existence=True) + sage: designs.mutually_orthogonal_latin_squares(None,8,existence=True) 7 If you only want to know if Sage is able to build a given set of MOLS, just @@ -252,7 +290,7 @@ def mutually_orthogonal_latin_squares(n,k, partitions = False, check = True, exi sage: designs.mutually_orthogonal_latin_squares(5, 5, existence=True) False - sage: designs.mutually_orthogonal_latin_squares(6, 4, existence=True) + sage: designs.mutually_orthogonal_latin_squares(4,6, existence=True) Unknown If you ask for such a MOLS then you will respecively get an informative @@ -262,7 +300,7 @@ def mutually_orthogonal_latin_squares(n,k, partitions = False, check = True, exi Traceback (most recent call last): ... EmptySetError: There exist at most n-1 MOLS of size n if n>=2. - sage: designs.mutually_orthogonal_latin_squares(6, 4) + sage: designs.mutually_orthogonal_latin_squares(4,6) Traceback (most recent call last): ... NotImplementedError: I don't know how to build 4 MOLS of order 6 @@ -271,17 +309,17 @@ def mutually_orthogonal_latin_squares(n,k, partitions = False, check = True, exi The special case `n=1`:: - sage: designs.mutually_orthogonal_latin_squares(1, 3) + sage: designs.mutually_orthogonal_latin_squares(3, 1) [[0], [0], [0]] - sage: designs.mutually_orthogonal_latin_squares(1, None, existence=True) + sage: designs.mutually_orthogonal_latin_squares(None,1, existence=True) +Infinity - sage: designs.mutually_orthogonal_latin_squares(1, None) + sage: designs.mutually_orthogonal_latin_squares(None, 1) Traceback (most recent call last): ... - ValueError: there are no bound on k when n=1. - sage: designs.mutually_orthogonal_latin_squares(10,2,existence=True) + ValueError: there are no bound on k when 0<=n<=1 + sage: designs.mutually_orthogonal_latin_squares(2,10,existence=True) True - sage: designs.mutually_orthogonal_latin_squares(10,2) + sage: designs.mutually_orthogonal_latin_squares(2,10) [ [1 8 9 0 2 4 6 3 5 7] [1 7 6 5 0 9 8 2 3 4] [7 2 8 9 0 3 5 4 6 1] [8 2 1 7 6 0 9 3 4 5] @@ -295,23 +333,28 @@ def mutually_orthogonal_latin_squares(n,k, partitions = False, check = True, exi [4 5 6 7 1 2 3 9 0 8], [7 1 2 3 4 5 6 9 8 0] ] """ - from sage.combinat.designs.orthogonal_arrays import orthogonal_array + from sage.combinat.designs.orthogonal_arrays import orthogonal_array, _OA_cache_set, _OA_cache_get, _OA_cache_construction_available from sage.matrix.constructor import Matrix from sage.rings.arith import factor from database import MOLS_constructions # Is k is None we find the largest available if k is None: - if n == 1: + if n == 0 or n == 1: if existence: from sage.rings.infinity import Infinity return Infinity - raise ValueError("there are no bound on k when n=1.") + raise ValueError("there are no bound on k when 0<=n<=1") k = orthogonal_array(None,n,existence=True) - 2 if existence: return k + if existence and _OA_cache_get(k+2,n) is not None: + return _OA_cache_get(k+2,n) + + may_be_available = _OA_cache_construction_available(k+2,n) is not False + if n == 1: if existence: return True @@ -322,18 +365,17 @@ def mutually_orthogonal_latin_squares(n,k, partitions = False, check = True, exi return False raise EmptySetError("There exist at most n-1 MOLS of size n if n>=2.") - elif n in MOLS_constructions and k <= MOLS_constructions[n][0]: + elif may_be_available and n in MOLS_constructions and k <= MOLS_constructions[n][0]: + _OA_cache_set(MOLS_constructions[n][0]+2,n,True) if existence: return True _, construction = MOLS_constructions[n] matrices = construction()[:k] - elif (orthogonal_array not in who_asked and - orthogonal_array(k+2,n,existence=True,who_asked = who_asked+(mutually_orthogonal_latin_squares,)) is not Unknown): - + elif orthogonal_array(k+2,n,existence=True) is not Unknown: # Forwarding non-existence results - if orthogonal_array(k+2,n,existence=True,who_asked = who_asked+(mutually_orthogonal_latin_squares,)): + if orthogonal_array(k+2,n,existence=True): if existence: return True else: @@ -341,7 +383,7 @@ def mutually_orthogonal_latin_squares(n,k, partitions = False, check = True, exi return False raise EmptySetError("There does not exist {} MOLS of order {}!".format(k,n)) - OA = orthogonal_array(k+2,n,check=False, who_asked = who_asked+(mutually_orthogonal_latin_squares,)) + OA = orthogonal_array(k+2,n,check=False) OA.sort() # make sure that the first two columns are "11, 12, ..., 1n, 21, 22, ..." # We first define matrices as lists of n^2 values @@ -398,7 +440,7 @@ def latin_square_product(M,N,*others): EXAMPLES:: sage: from sage.combinat.designs.latin_squares import latin_square_product - sage: m=designs.mutually_orthogonal_latin_squares(4,3)[0] + sage: m=designs.mutually_orthogonal_latin_squares(3,4)[0] sage: latin_square_product(m,m,m) 64 x 64 sparse matrix over Integer Ring (use the '.str()' method to see the entries) """ @@ -420,3 +462,112 @@ def latin_square_product(M,N,*others): return latin_square_product(P, others[0],*others[1:]) else: return P + + +def MOLS_table(number_of_lines,compare=False): + r""" + Prints the MOLS table that Sage can produce. + + INPUT: + + - ``compare`` (boolean) -- if sets to ``True`` the MOLS displays + with `+` and `-` entries its difference with the table from the + Handbook of Combinatorial Designs (2ed). + + EXAMPLES:: + + sage: from sage.combinat.designs.latin_squares import MOLS_table + sage: MOLS_table(5) + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + ________________________________________________________________________________ + 0| +oo +oo 1 2 3 4 1 6 7 8 2 10 5 12 4 4 15 16 5 18 + 20| 4 5 3 22 7 24 4 26 5 28 4 30 31 5 4 5 8 36 4 5 + 40| 7 40 5 42 5 6 4 46 8 48 6 5 5 52 5 6 7 7 5 58 + 60| 5 60 5 6 63 7 5 66 5 6 6 70 7 72 5 7 6 6 6 78 + 80| 9 80 8 82 6 6 6 6 7 88 6 7 6 6 6 6 7 96 6 8 + sage: MOLS_table(5,compare=True) + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + ________________________________________________________________________________ + 0| + + + 20| + 40| + 60| + + 80| + """ + global Handbook_2ed_table + hb = Handbook_2ed_table + from string import join + print " " + join('%3d'%i for i in range(20)) + print " " + "_"*80, + for i in range(20*number_of_lines): + if i%20==0: + print "\n%3d|"%i, + k = mutually_orthogonal_latin_squares(None,i,existence=True) + if compare: + if i < 2 or hb[i] == k: + c = "" + elif hb[i] < k: + c = "+" + else: + c = "-" + else: + if i < 2: + c = "+oo" + else: + c = k + print '{:>3}'.format(c), + +# MOLS table from the Handbook of Combinatorial Designs 2ed (page 176) +Handbook_2ed_table = [ + None,None, + 1, 2, 3, 4, 1, 6, 7, 8, 2, 10, 5, 12, 3, 4, 15, 16, 3, 18, + 4, 5, 3, 22, 7, 24, 4, 26, 5, 28, 4, 30, 31, 5, 4, 5, 8, 36, 4, 5, + 7, 40, 5, 42, 5, 6, 4, 46, 8, 48, 6, 5, 5, 52, 5, 6, 7, 7, 5, 58, + 4, 60, 5, 6, 63, 7, 5, 66, 5, 6, 6, 70, 7, 72, 5, 7, 6, 6, 6, 78, + 9, 80, 8, 82, 6, 6, 6, 6, 7, 88, 6, 7, 6, 6, 6, 6, 7, 96, 6, 8, + 8,100, 6,102, 7, 7, 6,106, 6,108, 6, 6, 13,112, 6, 7, 6, 8, 6, 6, + 7,120, 6, 6, 6,124, 6,126,127, 7, 6,130, 6, 7, 6, 7, 7,136, 6,138, + 6, 7, 6, 10, 10, 7, 6, 7, 6,148, 6,150, 7, 8, 8, 7, 6,156, 7, 6, + 9, 7, 6,162, 6, 7, 6,166, 7,168, 6, 8, 6,172, 6, 6, 14, 9, 6,178, + 6,180, 6, 6, 7, 9, 6, 10, 6, 8, 6,190, 7,192, 6, 7, 6,196, 6,198, + 7, 8, 6, 7, 6, 8, 6, 8, 14, 11, 10,210, 6, 7, 6, 7, 7, 8, 6, 10, + 6, 12, 6,222, 13, 8, 6,226, 6,228, 6, 7, 7,232, 6, 7, 6, 7, 6,238, + 7,240, 6,242, 6, 7, 6, 12, 7, 7, 6,250, 6, 12, 9, 7,255,256, 6, 12, + 6, 8, 8,262, 7, 8, 7, 10, 7,268, 7,270, 15, 16, 6, 13, 10,276, 6, 9, + 7,280, 6,282, 6, 12, 6, 7, 15,288, 6, 6, 6,292, 6, 6, 7, 10, 10, 12, + 7, 7, 7, 7, 15, 15, 6,306, 7, 7, 7,310, 7,312, 7, 10, 7,316, 7, 10, + 15, 15, 6, 16, 8, 12, 6, 7, 7, 9, 6,330, 7, 8, 7, 6, 8,336, 6, 7, + 6, 10, 10,342, 7, 7, 6,346, 6,348, 8, 12, 18,352, 6, 9, 7, 9, 6,358, + 8,360, 6, 7, 7, 10, 6,366, 15, 15, 7, 15, 7,372, 7, 15, 7, 13, 7,378, + 7, 12, 7,382, 15, 15, 7, 15, 7,388, 7, 16, 7, 8, 7, 7, 8,396, 7, 7, + 15,400, 7, 15, 11, 8, 7, 15, 8,408, 7, 13, 8, 12, 10, 9, 18, 15, 7,418, + 7,420, 7, 15, 7, 16, 6, 7, 7, 10, 6,430, 15,432, 6, 15, 6, 18, 7,438, + 7, 15, 7,442, 7, 13, 7, 11, 15,448, 7, 15, 7, 7, 7, 15, 7,456, 7, 16, + 7,460, 7,462, 15, 15, 7,466, 8, 8, 7, 15, 7, 15, 10, 18, 7, 15, 6,478, + 15, 15, 6, 15, 8, 7, 6,486, 7, 15, 6,490, 6, 16, 6, 7, 15, 15, 6,498, + 7, 12, 9,502, 7, 15, 6, 15, 7,508, 6, 15,511, 18, 7, 15, 8, 12, 8, 15, + 8,520, 10,522, 12, 15, 8, 16, 15,528, 7, 15, 8, 12, 7, 15, 8, 15, 10, 15, + 12,540, 7, 15, 18, 7, 7,546, 7, 8, 7, 18, 7, 7, 7, 7, 7,556, 7, 12, + 15, 7, 7,562, 7, 7, 6, 7, 7,568, 6,570, 7, 7, 15, 22, 8,576, 7, 7, + 7, 8, 7, 10, 7, 8, 7,586, 7, 18, 17, 7, 15,592, 8, 15, 7, 7, 8,598, + 14,600, 12, 15, 7, 15, 16,606, 18, 15, 7, 15, 8,612, 8, 15, 7,616, 7,618, + 8, 22, 8, 15, 15,624, 7, 8, 8, 16, 7,630, 7, 8, 7, 8, 7, 12, 7, 8, + 9,640, 7,642, 7, 7, 7,646, 8, 10, 7, 7, 7,652, 7, 7, 15, 15, 7,658, + 7,660, 7, 15, 7, 15, 7, 22, 7, 15, 7, 15, 15,672, 7, 24, 8,676, 7, 15, + 7, 15, 7,682, 8, 15, 7, 15, 15, 15, 7,690, 8, 15, 7, 15, 7, 16, 7, 15, + 8,700, 7, 18, 15, 15, 7, 15, 8,708, 7, 15, 7, 22, 21, 15, 7, 15, 8,718, + 15, 9, 8, 12, 10, 24, 12,726, 7,728, 16, 16, 18,732, 7, 7, 22, 10, 8,738, + 7, 7, 7,742, 7, 15, 7, 8, 7, 10, 7,750, 15, 15, 8, 15, 8,756, 8, 15, + 7,760, 8, 15, 8, 15, 8, 15, 15,768, 8, 15, 8,772, 8, 24, 23, 15, 8, 18, + 8, 18, 7, 26, 15, 15, 10,786, 12, 15, 7, 15, 20, 15, 18, 15, 8,796, 22, 16, + 24, 15, 8, 15, 8, 15, 8, 15, 8,808, 8,810, 8, 15, 8, 15, 15, 18, 8, 8, + 8,820, 8,822, 8, 15, 8,826, 8,828, 8, 15, 12, 16, 7, 8, 7, 26, 25,838, + 8,840, 8, 20, 8, 10, 8, 16, 15, 15, 12, 22, 7,852, 16, 15, 22,856, 7,858, + 22, 15, 24,862, 26, 15, 7, 15, 8, 15, 9, 15, 7, 15, 7, 15, 7,876, 8, 15, + 15,880, 8,882, 8, 15, 7,886, 7, 15, 8, 15, 10, 18, 8, 15, 13, 15, 8, 28, + 27, 16, 8, 8, 8, 22, 8,906, 8, 18, 10,910, 15, 14, 8, 15, 16, 10, 18,918, + 24, 8, 22, 12, 24, 24, 26, 8, 28,928, 7, 18, 7, 7, 7, 14, 7,936, 7, 15, + 7,940, 7, 22, 15, 15, 7,946, 7, 12, 12, 15, 7,952, 7, 15, 7, 15, 8, 15, + 15,960, 29, 15, 8, 15, 8,966, 8, 15, 8,970, 10, 18, 12, 15, 15,976, 16, 18, + 18, 15, 7,982, 27, 15, 24, 15, 26, 22, 28,990, 31, 31, 7, 15, 8,996, 25, 26 +] diff --git a/src/sage/combinat/designs/orthogonal_arrays.py b/src/sage/combinat/designs/orthogonal_arrays.py index f4ad4570c27..94ca0323c7c 100644 --- a/src/sage/combinat/designs/orthogonal_arrays.py +++ b/src/sage/combinat/designs/orthogonal_arrays.py @@ -6,8 +6,6 @@ .. TODO:: - - Implement an improvement of Wilson's construction for u=1,2 in [CD96]_ - - A resolvable `OA(k,n)` is equivalent to a `OA(k+1,n)`. Sage should be able to return resolvable OA, with sorted rows (so that building the decomposition is easy. @@ -29,7 +27,8 @@ from sage.rings.infinity import Infinity from designs_pyx import is_orthogonal_array -def transversal_design(k,n,check=True,existence=False, who_asked=tuple()): + +def transversal_design(k,n,check=True,existence=False): r""" Return a transversal design of parameters `k,n`. @@ -73,12 +72,6 @@ def transversal_design(k,n,check=True,existence=False, who_asked=tuple()): When ``k=None`` and ``existence=True`` the function returns an integer, i.e. the largest `k` such that we can build a `TD(k,n)`. - - ``who_asked`` (internal use only) -- because of the equivalence between - OA/TD/MOLS, each of the three constructors calls the others. We must keep - track of who calls who in order to avoid infinite loops. ``who_asked`` is - the tuple of the other functions that were called before this one on the - same input `k,n`. - OUTPUT: The kind of output depends on the input: @@ -187,7 +180,7 @@ def transversal_design(k,n,check=True,existence=False, who_asked=tuple()): sage: designs.transversal_design(None, 30, existence=True) 6 sage: designs.transversal_design(None, 120, existence=True) - 8 + 9 TESTS: @@ -265,20 +258,25 @@ def transversal_design(k,n,check=True,existence=False, who_asked=tuple()): sage: designs.transversal_design(None, 1) Traceback (most recent call last): ... - ValueError: there are no bound on k when n=1. + ValueError: there is no upper bound on k when 0<=n<=1 """ # Is k is None we find the largest available if k is None: - if n == 1: + if n == 0 or n == 1: if existence: from sage.rings.infinity import Infinity return Infinity - raise ValueError("there are no bound on k when n=1.") + raise ValueError("there is no upper bound on k when 0<=n<=1") k = orthogonal_array(None,n,existence=True) if existence: return k + if existence and _OA_cache_get(k,n) is not None: + return _OA_cache_get(k,n) + + may_be_available = _OA_cache_construction_available(k,n) is not False + if n == 1: if existence: return True @@ -290,30 +288,17 @@ def transversal_design(k,n,check=True,existence=False, who_asked=tuple()): raise EmptySetError("No Transversal Design exists when k>=n+2 if n>=2") elif n == 12 and k <= 6: + _OA_cache_set(6,12,True) if existence: return True from sage.combinat.designs.database import TD_6_12 TD = [l[:k] for l in TD_6_12()] - elif TD_find_product_decomposition(k,n): - if existence: - return True - n1,n2 = TD_find_product_decomposition(k,n) - TD1 = transversal_design(k,n1, check = False) - TD2 = transversal_design(k,n2, check = False) - TD = TD_product(k,TD1,n1,TD2,n2, check = False) - - elif find_wilson_decomposition(k,n): - if existence: - return True - TD = wilson_construction(*find_wilson_decomposition(k,n), check = False) - # Section 6.6 of [Stinson2004] - elif (orthogonal_array not in who_asked and - orthogonal_array(k, n, existence=True, who_asked = who_asked + (transversal_design,)) is not Unknown): + elif orthogonal_array(k, n, existence=True) is not Unknown: # Forwarding non-existence results - if orthogonal_array(k, n, existence=True, who_asked = who_asked + (transversal_design,)): + if orthogonal_array(k, n, existence=True): if existence: return True else: @@ -321,7 +306,7 @@ def transversal_design(k,n,check=True,existence=False, who_asked=tuple()): return False raise EmptySetError("There exists no TD({},{})!".format(k,n)) - OA = orthogonal_array(k,n, check = False, who_asked = who_asked + (transversal_design,)) + OA = orthogonal_array(k,n, check = False) TD = [[i*n+c for i,c in enumerate(l)] for l in OA] else: @@ -367,182 +352,101 @@ def is_transversal_design(B,k,n, verbose=False): """ return is_orthogonal_array([[x%n for x in R] for R in B],k,n,verbose=verbose) -@cached_function -def find_wilson_decomposition(k,n): +def wilson_construction(OA,k,r,m,n_trunc,u,check=True): r""" - Finds a wilson decomposition of `k,n` - - This method looks for possible integers `m,t,u` satisfying that `mt+u=n` and - such that Sage knows how to build a `TD(k,m), TD(k,m+1),TD(k+1,t)` and a - `TD(k,u)`. These can then be used to feed :func:`wilson_construction`. - - INPUT: + Returns a `OA(k,rm+u)` from a truncated `OA(k+s,r)` by Wilson's construction. - - `k,n` (integers) + Let `OA` be a truncated `OA(k+s,r)` with `s` truncated columns of sizes + `u_1,...,u_s`, whose blocks have sizes in `\{k+b_1,...,k+b_t\}`. If there + exist: - OUTPUT: - - Returns a 4-tuple `(n, m, t, u)` if it is found, and ``False`` otherwise. - - EXAMPLES:: + - An `OA(k,m+b_i) - b_i.OA(k,1)` for every `1\leq i\leq t` - sage: from sage.combinat.designs.orthogonal_arrays import find_wilson_decomposition - sage: find_wilson_decomposition(4,38) - (4, 7, 5, 3) - sage: find_wilson_decomposition(4,20) - False - """ - # If there exists a TD(k+1,t) then k+1 < t+2, i.e. k <= t - for t in range(max(1,k),n-1): - u = n%t - # We ensure that 1<=u, and that there can exists a TD(k,u), i.e k1 and k >= u+2): - continue - - m = n//t - # If there exists a TD(k,m) then k= m+2: - break - - if (transversal_design(k ,m , existence=True) and - transversal_design(k ,m+1, existence=True) and - transversal_design(k+1,t , existence=True) and - transversal_design(k ,u , existence=True)): - return k,m,t,u - - return False - -def wilson_construction(k,m,t,u, check = True): - r""" - Returns a `TD(k,mt+u)` by Wilson's construction. + - An `OA(k,u_i)` for every `1\leq i\leq s` - Wilson's construction builds a `TD(k,mt+u)` from the following designs : + Then there exists an `OA(k,rm+\sum u_i)`. The construction is a + generalization of Lemma 3.16 in [HananiBIBD]_. - * A `TD(k,m)` - * A `TD(k,m+1)` - * A `TD(k+1,t)` - * A `TD(k,u)` + INPUT: - For more information, see page 147 of [Stinson2004]_. + - ``OA`` -- an incomplete orthogonal array with ``k+n_trunc`` columns. The + elements of a column of size `c` must belong to `\{0,...,c\}`. The missing + entries of a block are represented by ``None`` values. - INPUT: + - ``k,r,m,n_trunc`` (integers) - - ``k,m,t,u`` -- integers with `k\geq 2` and `1\leq u\leq t`. + - ``u`` (list) -- a list of length ``n_trunc`` such that column ``k+i`` has + size ``u[i]``. - ``check`` (boolean) -- whether to check that output is correct before returning it. As this is expected to be useless (but we are cautious guys), you may want to disable it whenever you want speed. Set to ``True`` by default. + REFERENCE: + + .. [HananiBIBD] Balanced incomplete block designs and related designs, + Haim Hanani, + Discrete Mathematics 11.3 (1975) pages 255-369. + EXAMPLES:: sage: from sage.combinat.designs.orthogonal_arrays import wilson_construction - sage: from sage.combinat.designs.orthogonal_arrays import find_wilson_decomposition + sage: from sage.combinat.designs.orthogonal_arrays import OA_relabel + sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_wilson_decomposition_with_one_truncated_group sage: total = 0 sage: for k in range(3,8): ....: for n in range(1,30): - ....: if find_wilson_decomposition(k,n): + ....: if find_wilson_decomposition_with_one_truncated_group(k,n): ....: total += 1 - ....: k,m,t,u = find_wilson_decomposition(k,n) - ....: _ = wilson_construction(k,m,t,u, check=True) + ....: f, args = find_wilson_decomposition_with_one_truncated_group(k,n) + ....: _ = f(*args) sage: print total 41 """ - # Raises a NotImplementedError if one of them does not exist. - TDkm = transversal_design(k,m,check=False) - TDkm1 = transversal_design(k,m+1,check=False) - TDk1t = transversal_design(k+1,t,check=False) - TDku = transversal_design(k,u,check=False) + n = r*m+sum(u) + master_design = OA - # Truncaed TDk1t - truncated_TDk1t = [[x for x in B if x= min_unknown or k <= max_unknown): + elif min_unknown is not None and (k >= min_unknown and k <= max_unknown): return Unknown elif k >= min_false: return False return None -def orthogonal_array(k,n,t=2,check=True,existence=False,who_asked=tuple()): +def _OA_cache_construction_available(k,n): + r""" + Tests if a construction is implemented using the cache's information + + INPUT: + + - ``k,n`` (integers) + + EXAMPLES:: + + sage: from sage.combinat.designs.orthogonal_arrays import _OA_cache_construction_available + sage: _OA_cache_construction_available(5,10) + Unknown + sage: designs.orthogonal_array(5,10,existence=True) + Unknown + sage: _OA_cache_construction_available(5,10) + False + """ + ans = _OA_cache.get(n,None) + if ans is not None: + max_true, min_unknown, max_unknown, min_false = ans + if k <= max_true: + return True + if min_unknown is not None and k >= min_unknown: + return False + else: + return Unknown + else: + return Unknown + +def orthogonal_array(k,n,t=2,check=True,existence=False): r""" Return an orthogonal array of parameters `k,n,t`. @@ -725,12 +657,6 @@ def orthogonal_array(k,n,t=2,check=True,existence=False,who_asked=tuple()): When ``k=None`` and ``existence=True`` the function returns an integer, i.e. the largest `k` such that we can build a `TD(k,n)`. - - ``who_asked`` (internal use only) -- because of the equivalence between - OA/TD/MOLS, each of the three constructors calls the others. We must keep - track of who calls who in order to avoid infinite loops. ``who_asked`` is - the tuple of the other functions that were called before this one on the - same input `k,n`. - OUTPUT: The kind of output depends on the input: @@ -785,7 +711,7 @@ def orthogonal_array(k,n,t=2,check=True,existence=False,who_asked=tuple()): sage: designs.orthogonal_array(4,2) Traceback (most recent call last): ... - EmptySetError: No Orthogonal Array exists when k>=n+t except when n=1 + EmptySetError: No Orthogonal Array exists when k>=n+t except when n<=1 sage: designs.orthogonal_array(12,20) Traceback (most recent call last): ... @@ -801,45 +727,72 @@ def orthogonal_array(k,n,t=2,check=True,existence=False,who_asked=tuple()): TESTS: - The special case `n=1`:: + The special cases `n=0,1`:: + sage: designs.orthogonal_array(3,0) + [] sage: designs.orthogonal_array(3,1) [[0, 0, 0]] + sage: designs.orthogonal_array(None,0,existence=True) + +Infinity sage: designs.orthogonal_array(None,1,existence=True) +Infinity sage: designs.orthogonal_array(None,1) Traceback (most recent call last): ... - ValueError: there are no bound on k when n=1. - sage: designs.orthogonal_array(None,14,existence=True) - 6 + ValueError: there is no upper bound on k when 0<=n<=1 + sage: designs.orthogonal_array(None,0) + Traceback (most recent call last): + ... + ValueError: there is no upper bound on k when 0<=n<=1 + sage: designs.orthogonal_array(16,0) + [] sage: designs.orthogonal_array(16,1) [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] + + when `t>2` and `k=None`:: + + sage: t = 3 + sage: designs.orthogonal_array(None,5,t=t,existence=True) == t + True + sage: _ = designs.orthogonal_array(t,5,t) """ from latin_squares import mutually_orthogonal_latin_squares - from database import OA_constructions + from database import OA_constructions, MOLS_constructions from block_design import projective_plane, projective_plane_to_OA + from orthogonal_arrays_recursive import find_recursive_construction + + assert n>=0 # If k is set to None we find the largest value available if k is None: - if n == 1: + if n == 0 or n == 1: if existence: from sage.rings.infinity import Infinity return Infinity - raise ValueError("there are no bound on k when n=1.") - - for k in range(1,n+2): - if not orthogonal_array(k+1,n,existence=True): - break + raise ValueError("there is no upper bound on k when 0<=n<=1") + elif t == 2 and projective_plane(n,existence=True): + k = n+1 + else: + for k in range(t-1,n+2): + if not orthogonal_array(k+1,n,t=t,existence=True): + break if existence: return k - if k < 2: - raise ValueError("undefined for k less than 2") + if k < t: + raise ValueError("undefined for k= n+t: # When t=2 then k=n+t except when n=1") + raise EmptySetError("No Orthogonal Array exists when k>=n+t except when n<=1") - elif k == t: + elif k <= t: if existence: return True from itertools import product - OA = map(list, product(range(n), repeat=k)) + return map(list, product(range(n), repeat=k)) - elif n in OA_constructions and k <= OA_constructions[n][0]: + elif t != 2: if existence: - return True - _, construction = OA_constructions[n] + return Unknown + raise NotImplementedError("Only trivial orthogonal arrays are implemented for t>=2") - OA = OA_from_wider_OA(construction(),k) + elif k <= 3: + if existence: + return True + return [[i,j,(i+j)%n] for i in xrange(n) for j in xrange(n)] # projective spaces are equivalent to OA(n+1,n,2) - elif (t == 2 and - (projective_plane(n, existence=True) or - (k == n+1 and projective_plane(n, existence=True) is False))): + elif (projective_plane(n, existence=True) or + (k == n+1 and projective_plane(n, existence=True) is False)): + _OA_cache_set(n+1,n,projective_plane(n, existence=True)) if k == n+1: if existence: return projective_plane(n, existence=True) @@ -880,49 +836,48 @@ def orthogonal_array(k,n,t=2,check=True,existence=False,who_asked=tuple()): OA = [l[:k] for l in projective_plane_to_OA(p, check=False)] # Constructions from the database - elif n in OA_constructions and k <= OA_constructions[n][0]: + elif may_be_available and n in OA_constructions and k <= OA_constructions[n][0]: + _OA_cache_set(OA_constructions[n][0],n,True) if existence: return True _, construction = OA_constructions[n] OA = OA_from_wider_OA(construction(),k) - elif (t == 2 and transversal_design not in who_asked and - transversal_design(k,n,existence=True,who_asked=who_asked+(orthogonal_array,)) is not Unknown): + # Constructions from the database II + elif may_be_available and k <= 6 and n == 12: + _OA_cache_set(6,12,True) - # forward existence - if transversal_design(k,n,existence=True,who_asked=who_asked+(orthogonal_array,)): - if existence: - return True - else: - TD = transversal_design(k,n,check=False,who_asked=who_asked+(orthogonal_array,)) - OA = [[x%n for x in R] for R in TD] - - # forward non-existence + if existence: + return True else: - if existence: - return False - raise EmptySetError("There exists no OA({},{})!".format(k,n)) + from database import TD_6_12 + TD = TD_6_12() + OA = [[x%n for x in R] for R in TD] + # Constructions from the database III # Section 6.5.1 from [Stinson2004] - elif (t == 2 and mutually_orthogonal_latin_squares not in who_asked and - mutually_orthogonal_latin_squares(n,k-2, existence=True,who_asked=who_asked+(orthogonal_array,)) is not Unknown): + elif may_be_available and n in MOLS_constructions and k-2 <= MOLS_constructions[n][0]: + _OA_cache_set(MOLS_constructions[n][0]+2,n,True) - # forward existence - if mutually_orthogonal_latin_squares(n,k-2, existence=True,who_asked=who_asked+(orthogonal_array,)): - if existence: - return True - else: - mols = mutually_orthogonal_latin_squares(n,k-2,who_asked=who_asked+(orthogonal_array,)) - OA = [[i,j]+[m[i,j] for m in mols] - for i in range(n) for j in range(n)] - # forward non-existence + if existence: + return True else: - if existence: - return False - raise EmptySetError("There exists no OA({},{})!".format(k,n)) + construction = MOLS_constructions[n][1] + mols = construction() + OA = [[i,j]+[m[i,j] for m in mols] + for i in range(n) for j in range(n)] + OA = OA_from_wider_OA(OA,k) + + elif may_be_available and find_recursive_construction(k,n): + _OA_cache_set(k,n,True) + if existence: + return True + f,args = find_recursive_construction(k,n) + OA = f(*args) else: + _OA_cache_set(k,n,Unknown) if existence: return Unknown raise NotImplementedError("I don't know how to build an OA({},{})!".format(k,n)) @@ -1446,7 +1401,7 @@ def OA_from_PBD(k,n,PBD, check=True): sage: _ = OA_from_PBD(3,6,pbd) Traceback (most recent call last): ... - RuntimeError: This is not a nice honest PBD from the good old days ! + RuntimeError: The PBD covers a point 8 which is not in {0, ..., 5} """ # Size of the sets of the PBD K = set(map(len,PBD)) diff --git a/src/sage/combinat/designs/orthogonal_arrays_recursive.py b/src/sage/combinat/designs/orthogonal_arrays_recursive.py new file mode 100644 index 00000000000..757ab5b82eb --- /dev/null +++ b/src/sage/combinat/designs/orthogonal_arrays_recursive.py @@ -0,0 +1,926 @@ +r""" +Orthogonal arrays (Recursive constructions) + +This module implements several functions to find recursive constructions of +:mod:`Orthogonal Arrays ` using +Wilson's construction. To this end, they compute and truncate OA in specific +ways. + +The main function of this module, i.e. :func:`find_recursive_construction`, +queries all implemented recursive constructions of designs. It is used by +Sage's function +:func:`~sage.combinat.designs.orthogonal_arrays.orthogonal_array`. + +REFERENCES: + +.. [AC07] Concerning eight mutually orthogonal latin squares + Julian R. Abel, Nicholas Cavenagh + Journal of Combinatorial Designs + Vol. 15, n.3, pp. 255-261 + 2007 + +Functions +--------- +""" +from sage.misc.cachefunc import cached_function +from orthogonal_arrays import orthogonal_array +from designs_pyx import is_orthogonal_array + +@cached_function +def find_recursive_construction(k,n): + r""" + Find a recursive construction of a `OA(k,n)` + + This determines whether an `OA(k,n)` can be built through the following + constructions: + + - :func:`simple_wilson_construction` (0, 1 or 2 truncated columns) + - :func:`construction_3_3` + - :func:`construction_3_4` + - :func:`construction_3_5` + - :func:`construction_3_6` + - :func:`construction_q_x` + + INPUT: + + - ``k,n`` (integers) + + OUTPUT: + + Returns a pair ``f,args`` such that ``f(*args)`` returns the requested `OA` + if possible, and ``False`` otherwise. + + EXAMPLES:: + + sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_recursive_construction + sage: from sage.combinat.designs.orthogonal_arrays import is_orthogonal_array + sage: count = 0 + sage: for n in range(10,150): + ....: k = designs.orthogonal_array(None,n,existence=True) + ....: if find_recursive_construction(k,n): + ....: count = count + 1 + ....: f,args = find_recursive_construction(k,n) + ....: OA = f(*args) + ....: assert is_orthogonal_array(OA,k,n,2,verbose=True) + sage: print count + 54 + """ + assert k > 3 + + for find_c in [find_product_decomposition, + find_wilson_decomposition_with_one_truncated_group, + find_wilson_decomposition_with_two_truncated_groups, + find_construction_3_3, + find_construction_3_4, + find_construction_3_5, + find_construction_3_6, + find_q_x]: + res = find_c(k,n) + if res: + return res + return False + +def find_product_decomposition(k,n): + r""" + Look for a factorization of `n` in order to build an `OA(k,n)`. + + If Sage can build a `OA(k,n_1)` and a `OA(k,n_2)` such that `n=n_1\times + n_2` then a `OA(k,n)` can be built by a product construction (which + correspond to Wilson's construction with no truncated column). This + function look for a pair of integers `(n_1,n_2)` with `n1 \leq n_2`, `n_1 + \times n_2 = n` and such that both an `OA(k,n_1)` and an `OA(k,n_2)` are + available. + + INPUT: + + - ``k,n`` (integers) -- see above. + + OUTPUT: + + A pair ``f,args`` such that ``f(*args)`` is an `OA(k,n)` or ``False`` if no + product decomposition was found. + + EXAMPLES:: + + sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_product_decomposition + sage: f,args = find_product_decomposition(6, 84) + sage: args + (6, 7, 12, ()) + sage: _ = f(*args) + """ + from sage.rings.arith import divisors + for n1 in divisors(n)[1:-1]: # we ignore 1 and n + n2 = n//n1 # n2 is decreasing along the loop + if n2 < n1: + break + if orthogonal_array(k, n1, existence=True) and orthogonal_array(k, n2, existence=True): + return simple_wilson_construction, (k,n1,n2,()) + return False + +def find_wilson_decomposition_with_one_truncated_group(k,n): + r""" + Helper function for Wilson's construction with one truncated column. + + This function looks for possible integers `m,t,u` satisfying that `mt+u=n` and + such that Sage knows how to build a `OA(k,m), OA(k,m+1),OA(k+1,t)` and a + `OA(k,u)`. + + INPUT: + + - ``k,n`` (integers) -- see above + + OUTPUT: + + A pair `f,args` such that `f(*args)` is an `OA(k,n)` or ``False`` if no + decomposition with one truncated block was found. + + EXAMPLES:: + + sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_wilson_decomposition_with_one_truncated_group + sage: f,args = find_wilson_decomposition_with_one_truncated_group(4,38) + sage: args + (4, 5, 7, (3,)) + sage: _ = f(*args) + + sage: find_wilson_decomposition_with_one_truncated_group(4,20) + False + """ + # If there exists a TD(k+1,t) then k+1 < t+2, i.e. k <= t + for r in range(max(1,k),n-1): + u = n%r + # We ensure that 1<=u, and that there can exists a TD(k,u), i.e k1 and k >= u+2): + continue + + m = n//r + # If there exists a TD(k,m) then k= m+2: + break + + if (orthogonal_array(k ,m , existence=True) and + orthogonal_array(k ,m+1, existence=True) and + orthogonal_array(k+1,r , existence=True) and + orthogonal_array(k ,u , existence=True)): + return simple_wilson_construction, (k,r,m,(u,)) + + return False + +def find_wilson_decomposition_with_two_truncated_groups(k,n): + r""" + Helper function for Wilson's construction with two trucated columns. + + Look for integers `r,m,r_1,r_2` satisfying `n=rm+r_1+r_2` and `1\leq r_1,r_2= k+1 + if not orthogonal_array(k+2,r,existence=True): + continue + m_min = (n - (2*r-2))//r + m_max = (n - 2)//r + if m_min > 1: + m_values = range(max(m_min,k-1), m_max+1) + else: + m_values = [1] + range(k-1, m_max+1) + for m in m_values: + r1_p_r2 = n-r*m # the sum of r1+r2 + # it is automatically >= 2 since m <= m_max + if (r1_p_r2 > 2*r-2 or + not orthogonal_array(k,m ,existence=True) or + not orthogonal_array(k,m+1,existence=True) or + not orthogonal_array(k,m+2,existence=True)): + continue + + r1_min = r1_p_r2 - (r-1) + r1_max = min(r-1, r1_p_r2) + if r1_min > 1: + r1_values = range(max(k-1,r1_min), r1_max+1) + else: + r1_values = [1] + range(k-1, r1_max+1) + for r1 in r1_values: + if not orthogonal_array(k,r1,existence=True): + continue + r2 = r1_p_r2-r1 + if orthogonal_array(k,r2,existence=True): + assert n == r*m+r1+r2 + return simple_wilson_construction, (k,r,m,(r1,r2)) + return False + +def simple_wilson_construction(k,r,m,u): + r""" + Return an `OA(k,r*m + \sum u_i)` from Wilson construction. + + INPUT: + + - ``k,r,m`` -- integers + + - ``u`` -- list of positive integers + + .. TODO:: + + As soon as wilson construction accepts an empty master design we should + remove this intermediate functions. + + EXAMPLES:: + + sage: from sage.combinat.designs.orthogonal_arrays_recursive import simple_wilson_construction + sage: from sage.combinat.designs.designs_pyx import is_orthogonal_array + + sage: OA = simple_wilson_construction(6,7,12,()) + sage: is_orthogonal_array(OA,6,84) + True + + sage: OA = simple_wilson_construction(4,5,7,(3,)) + sage: is_orthogonal_array(OA,4,38) + True + + sage: OA = simple_wilson_construction(5,7,7,(4,5)) + sage: is_orthogonal_array(OA,5,58) + True + """ + from sage.combinat.designs.orthogonal_arrays import wilson_construction, OA_relabel + + n = r*m + sum(u) + n_trunc = len(u) + OA = orthogonal_array(k+n_trunc,r,check=False) + matrix = [range(r)]*k + for uu in u: + matrix.append(range(uu)+[None]*(r-uu)) + OA = OA_relabel(OA,k+n_trunc,r,matrix=matrix) + + return wilson_construction(OA,k,r,m,n_trunc,u,False) + +def find_construction_3_3(k,n): + r""" + Finds a decomposition for construction 3.3 from [AC07]_ + + INPUT: + + - ``k,n`` (integers) + + .. SEEALSO:: + + :func:`construction_3_3` + + OUTPUT: + + A pair ``f,args`` such that ``f(*args)`` returns the requested OA. + + EXAMPLES:: + + sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_construction_3_3 + sage: find_construction_3_3(11,177)[1] + (11, 11, 16, 1) + sage: find_construction_3_3(12,11) + """ + for mm in range(k-1,n//2+1): + if (not orthogonal_array(k ,mm , existence=True) or + not orthogonal_array(k ,mm+1, existence=True)): + continue + + for nn in range(2,n//mm+1): + i = n-nn*mm + if i<=0: + continue + + if (orthogonal_array(k+i, nn , existence=True) and + orthogonal_array(k , mm+i, existence=True)): + return construction_3_3, (k,nn,mm,i) + +def construction_3_3(k,n,m,i): + r""" + Returns an `OA(k,nm+i)`. + + This is Wilson's construction with `i` truncated columns of size 1 and such + that a block `B_0` of the incomplete OA intersects all truncated columns. As + a consequence, all other blocks intersect only `0` or `1` of the last `i` + columns. This allow to consider the block `B_0` only up to its first `k` + coordinates and then use a `OA(k,i)` instead of a `OA(k,m+i) - i.OA(k,1)`. + + This is construction 3.3 from [AC07]_. + + INPUT: + + - ``k,n,m,i`` (integers) such that the following designs are available : + `OA(k,n),OA(k,m),OA(k,m+1),OA(k,r)`. + + .. SEEALSO:: + + :func:`find_construction_3_3` + + EXAMPLES:: + + sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_construction_3_3 + sage: from sage.combinat.designs.orthogonal_arrays_recursive import construction_3_3 + sage: from sage.combinat.designs.orthogonal_arrays import is_orthogonal_array + sage: k=11;n=177 + sage: is_orthogonal_array(construction_3_3(*find_construction_3_3(k,n)[1]),k,n,2) + True + """ + from orthogonal_arrays import wilson_construction, OA_relabel, incomplete_orthogonal_array + # Builds an OA(k+i,n) containing a block [0]*(k+i) + OA = incomplete_orthogonal_array(k+i,n,(1,)) + OA = [[(x+1)%n for x in B] for B in OA] + + # Truncated version + OA = [B[:k]+[0 if x == 0 else None for x in B[k:]] for B in OA] + + OA = wilson_construction(OA,k,n,m,i,[1]*i,check=False)[:-i] + matrix = [range(m)+range(n*m,n*m+i)]*k + OA.extend(OA_relabel(orthogonal_array(k,m+i),k,m+i,matrix=matrix)) + assert is_orthogonal_array(OA,k,n*m+i) + return OA + +def find_construction_3_4(k,n): + r""" + Finds a decomposition for construction 3.4 from [AC07]_ + + INPUT: + + - ``k,n`` (integers) + + .. SEEALSO:: + + :func:`construction_3_4` + + OUTPUT: + + A pair ``f,args`` such that ``f(*args)`` returns the requested OA. + + EXAMPLES:: + + sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_construction_3_4 + sage: find_construction_3_4(8,196)[1] + (8, 25, 7, 12, 9) + sage: find_construction_3_4(9,24) + """ + for mm in range(k-1,n//2+1): + if (not orthogonal_array(k,mm+0,existence=True) or + not orthogonal_array(k,mm+1,existence=True) or + not orthogonal_array(k,mm+2,existence=True)): + continue + + for nn in range(2,n//mm+1): + i = n-nn*mm + if i<=0: + continue + + for s in range(1,min(i,nn)): + r = i-s + if (orthogonal_array(k+r+1,nn,existence=True) and + orthogonal_array(k , s,existence=True) and + (orthogonal_array(k,mm+r,existence=True) or orthogonal_array(k,mm+r+1,existence=True))): + return construction_3_4, (k,nn,mm,r,s) + +def construction_3_4(k,n,m,r,s): + r""" + Returns a `OA(k,nm+rs)`. + + This is Wilson's construction applied to a truncated `OA(k+r+1,n)` with `r` + columns of size `1` and one column of size `s`. + + The unique elements of the `r` truncated columns are picked so that a block + `B_0` contains them all. + + - If there exists an `OA(k,m+r+1)` the column of size `s` is truncated in + order to intersect `B_0`. + + - Otherwise, if there exists an `OA(k,m+r)`, the last column must not + intersect `B_0` + + This is construction 3.4 from [AC07]_. + + INPUT: + + - ``k,n,m,r,s`` (integers) -- we assume that `s= n or + not orthogonal_array(k,mm+1,existence=True) or + not orthogonal_array(k,mm+2,existence=True) or + not orthogonal_array(k,mm+3,existence=True)): + continue + + for nn in range(2,n//mm+1): + i = n-nn*mm + if i<=0: + continue + + if not orthogonal_array(k+3,nn,existence=True): + continue + + for r,s,t in IntegerListsLex(i,length=3,ceiling=[nn-1,nn-1,nn-1]): + if (r <= s and + (nn-r-1)*(nn-s) < t and + (r==0 or orthogonal_array(k,r,existence=True)) and + (s==0 or orthogonal_array(k,s,existence=True)) and + (t==0 or orthogonal_array(k,t,existence=True))): + return construction_3_5, (k,nn,mm,r,s,t) + +def construction_3_5(k,n,m,r,s,t): + r""" + Returns an `OA(k,nm+r+s+t)`. + + This is exactly Wilson's construction with three truncated groups + except we make sure that all blocks have size `>k`, so we don't + need a `OA(k,m+0)` but only `OA(k,m+1),OA(k,m+2),OA(k,m+3)`. + + This is construction 3.5 from [AC07]_. + + INPUT: + + - ``k,n,m`` (integers) + + - ``r,s,t`` (integers) -- sizes of the three truncated groups, + such that `r\leq s` and `(q-r-1)(q-s) \geq (q-s-1)*(q-r)`. + + The following designs must be available : + `OA(k,n),OA(k,r),OA(k,s),OA(k,t),OA(k,m+1),OA(k,m+2),OA(k,m+3)`. + + .. SEEALSO:: + + :func:`find_construction_3_5` + + EXAMPLES:: + + sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_construction_3_5 + sage: from sage.combinat.designs.orthogonal_arrays_recursive import construction_3_5 + sage: from sage.combinat.designs.orthogonal_arrays import is_orthogonal_array + sage: k=8;n=111 + sage: is_orthogonal_array(construction_3_5(*find_construction_3_5(k,n)[1]),k,n,2) + True + """ + from orthogonal_arrays import wilson_construction, OA_relabel + assert r <= s + q = n + assert (q-r-1)*(q-s) >= (q-s-1)*(q-r) + master_design = orthogonal_array(k+3,q) + + # group k+1 has cardinality r + # group k+2 has cardinality s + # group k+3 has cardinality t + + # Taking q-s blocks going through 0 in the last block + blocks_crossing_0 = [B[-3:] for B in master_design if B[-1] == 0][:q-s] + + # defining the undeleted points of the groups k+1,k+2 + group_k_1 = [x[0] for x in blocks_crossing_0] + group_k_1 = [x for x in range(q) if x not in group_k_1][:r] + + group_k_2 = [x[1] for x in blocks_crossing_0] + group_k_2 = [x for x in range(q) if x not in group_k_2][:s] + + # All blocks that have a deleted point in groups k+1 and k+2 MUST contain a + # point in group k+3 + group_k_3 = [B[-1] for B in master_design if B[-3] not in group_k_1 and B[-2] not in group_k_2] + group_k_3 = list(set(group_k_3)) + assert len(group_k_3) <= t + group_k_3.extend([x for x in range(q) if x not in group_k_3]) + group_k_3 = group_k_3[:t] + + # Relabelling the OA + r1 = [None]*q + r2 = [None]*q + r3 = [None]*q + for i,x in enumerate(group_k_1): + r1[x] = i + for i,x in enumerate(group_k_2): + r2[x] = i + for i,x in enumerate(group_k_3): + r3[x] = i + + OA = OA_relabel(master_design, k+3,q, matrix=[range(q)]*k+[r1,r2,r3]) + OA = wilson_construction(OA,k,q,m,3,[r,s,t], check=False) + return OA + +def find_construction_3_6(k,n): + r""" + Finds a decomposition for construction 3.6 from [AC07]_ + + INPUT: + + - ``k,n`` (integers) + + .. SEEALSO:: + + :func:`construction_3_6` + + OUTPUT: + + A pair ``f,args`` such that ``f(*args)`` returns the requested OA. + + EXAMPLES:: + + sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_construction_3_6 + sage: find_construction_3_6(8,95)[1] + (8, 13, 7, 4) + sage: find_construction_3_6(8,98) + """ + from sage.rings.arith import is_prime_power + + for mm in range(k-1,n//2+1): + if (not orthogonal_array(k,mm+0,existence=True) or + not orthogonal_array(k,mm+1,existence=True) or + not orthogonal_array(k,mm+2,existence=True)): + continue + + for nn in range(2,n//mm+1): + i = n-nn*mm + if i<=0: + continue + + if (is_prime_power(nn) and + orthogonal_array(k+i,nn,existence=True)): + return construction_3_6, (k,nn,mm,i) + +def construction_3_6(k,n,m,i): + r""" + Returns a `OA(k,nm+i)` + + This is Wilson's construction with `r` columns of order `1`, in which each + block intersects at most two truncated columns. Such a design exists when + `n` is a prime power and is returned by :func:`OA_and_oval`. + + INPUT: + + - ``k,n,m,i`` (integers) -- `n` must be a prime power. The following designs + must be available: `OA(k+r,q),OA(k,m),OA(k,m+1),OA(k,m+2)`. + + This is construction 3.6 from [AC07]_. + + .. SEEALSO:: + + - :func:`construction_3_6` + - :func:`OA_and_oval` + + EXAMPLES:: + + sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_construction_3_6 + sage: from sage.combinat.designs.orthogonal_arrays_recursive import construction_3_6 + sage: from sage.combinat.designs.orthogonal_arrays import is_orthogonal_array + sage: k=8;n=95 + sage: is_orthogonal_array(construction_3_6(*find_construction_3_6(k,n)[1]),k,n,2) + True + """ + from orthogonal_arrays import wilson_construction + OA = OA_and_oval(n) + OA = [B[:k+i] for B in OA] + OA = [B[:k] + [x if x==0 else None for x in B[k:]] for B in OA] + OA = wilson_construction(OA,k,n,m,i,[1]*i) + assert is_orthogonal_array(OA,k,n*m+i) + return OA + +def OA_and_oval(q): + r""" + Returns a `OA(q+1,q)` whose blocks contains `\leq 2` zeroes in the last `q` + columns. + + This `OA` is build from a projective plane of order `q`, in which there + exists an oval `O` of size `q+1` (i.e. a set of `q+1` points no three of which + are [colinear/contained in a common set of the projective plane]). + + Removing an element `x\in O` and all sets that contain it, we obtain a + `TD(q+1,q)` in which `O` intersects all columns except one. As `O` is an + oval, no block of the `TD` intersects it more than twice. + + INPUT: + + - ``q`` -- a prime power + + .. NOTE:: + + This function is called by :func:`construction_3_6`, an + implementation of Construction 3.6 from [AC07]_. + + EXAMPLES:: + + sage: from sage.combinat.designs.orthogonal_arrays_recursive import OA_and_oval + sage: _ = OA_and_oval + + """ + from sage.rings.arith import is_prime_power + from sage.combinat.designs.block_design import projective_plane + from orthogonal_arrays import OA_relabel + + assert is_prime_power(q) + B = projective_plane(q, check=False) + + # We compute the oval with a linear program + from sage.numerical.mip import MixedIntegerLinearProgram + p = MixedIntegerLinearProgram() + b = p.new_variable(binary=True) + V = B.ground_set() + p.add_constraint(p.sum([b[i] for i in V]) == q+1) + for bl in B: + p.add_constraint(p.sum([b[i] for i in bl]) <= 2) + p.solve() + b = p.get_values(b) + oval = [x for x,i in b.items() if i] + assert len(oval) == q+1 + + # We remove one element from the oval + x = oval.pop() + oval.sort() + + # We build the TD by relabelling the point set, and removing those which + # contain x. + r = {} + B = list(B) + # (this is to make sure that the first set containing x in B is the one + # which contains no other oval point) + + B.sort(key=lambda b:int(any([xx in oval for xx in b]))) + BB = [] + for b in B: + if x in b: + for xx in b: + if xx == x: + continue + r[xx] = len(r) + else: + BB.append(b) + + assert len(r) == (q+1)*q # all points except x have an image + assert len(set(r.values())) == len(r) # the images are different + + # Relabelling/sorting the blocks and the oval + BB = [[r[xx] for xx in b] for b in BB] + oval = [r[xx] for xx in oval] + + for b in BB: + b.sort() + oval.sort() + + # Turning the TD into an OA + BB = [[xx%q for xx in b] for b in BB] + oval = [xx%q for xx in oval] + assert len(oval) == q + + # We relabel the "oval" as relabelled as [0,...,0] + OA = OA_relabel(BB+([[0]+oval]),q+1,q,blocks=[[0]+oval]) + OA = [[(x+1)%q for x in B] for B in OA] + OA.remove([0]*(q+1)) + + assert all(sum([xx == 0 for xx in b[1:]]) <= 2 for b in OA) + return OA + +def construction_q_x(k,q,x,check=True): + r""" + Returns an `OA(k,(q-1)*(q-x)+x+2)` using the `q-x` construction. + + Let `v=(q-1)*(q-x)+x+2`. If there exists a projective plane of order `q` + (e.g. when `q` is a prime power) and `0] (v0) edge[loop above] node {$0$} (); \end{tikzpicture} -We can turn this into a graphical representation. Before doing this, -we have to :func:`setup the latex preamble ` and -make sure that TikZ pictures are not rendered by mathjax, but by -actually running LaTeX. +We can turn this into a graphical representation. :: - sage: sage.combinat.finite_state_machine.setup_latex_preamble() - sage: latex.mathjax_avoid_list('tikzpicture') sage: view(NAF) # not tested To actually see this, use the live documentation in the Sage notebook @@ -268,7 +263,7 @@ where two successive values differ in only one bit, cf. the :wikipedia:`Gray_code`. The Gray code of an integer `n` is obtained by a bitwise xor between the binary expansion of `n` and the binary -expansion of `\\lfloor n/2\\rfloor`; the latter corresponds to a +expansion of `\lfloor n/2\rfloor`; the latter corresponds to a shift by one position in binary. The purpose of this example is to construct a transducer converting the @@ -279,7 +274,7 @@ the left-most position. Note that it is easier to shift everything to the right first, i.e., multiply by `2` instead of building -`\\lfloor n/2\\rfloor`. Then, we take the input xor with the right +`\lfloor n/2\rfloor`. Then, we take the input xor with the right shift of the input and forget the first letter. We first construct a transducer shifting the binary expansion to the @@ -374,6 +369,14 @@ sage: product_transducer = shift_right_transducer.cartesian_product(transducers.Identity([0, 1])) sage: sage.combinat.finite_state_machine.FSMOldCodeTransducerCartesianProduct = True sage: Gray_transducer = xor_transducer(product_transducer) + +We use :meth:`~FiniteStateMachine.construct_final_word_out` to make sure that all output +is written; otherwise, we would have to make sure that a sufficient number of trailing +zeros is read. + +:: + + sage: Gray_transducer.construct_final_word_out([0]) sage: Gray_transducer.transitions() [Transition from (('I', 0), 0) to ((0, 0), 0): 0|-, Transition from (('I', 0), 0) to ((1, 0), 0): 1|-, @@ -401,14 +404,12 @@ True Finally, we check that this indeed computes the Gray code of the first -10 non-negative integers. Note that we add a trailing zero at the most -significant position of the input in order to flush all output digits. -This is due to the left shift which delays its output. +10 non-negative integers. :: sage: for n in srange(10): - ....: Gray_transducer(n.bits() + [0]) + ....: Gray_transducer(n.bits()) [] [1] [1, 1] @@ -562,6 +563,7 @@ from sage.rings.real_mpfr import RR from sage.symbolic.ring import SR from sage.calculus.var import var +from sage.misc.cachefunc import cached_function from sage.misc.latex import latex from sage.misc.misc import verbose from sage.functions.trig import cos, sin, atan2 @@ -643,6 +645,35 @@ def full_group_by(l, key=lambda x: x): elements[s].append(item) return [(original_keys[s], values ) for (s, values) in elements.items()] + +def startswith(list, prefix): + """ + Determine whether list starts with the given prefix. + + INPUT: + + - ``list`` -- list + - ``prefix`` -- list representing the prefix + + OUTPUT: + + ``True`` or ``False``. + + Similar to :meth:`str.startswith`. + + EXAMPLES:: + + sage: from sage.combinat.finite_state_machine import startswith + sage: startswith([1, 2, 3], [1, 2]) + True + sage: startswith([1], [1, 2]) + False + sage: startswith([1, 3, 2], [1, 2]) + False + """ + + return list[:len(prefix)] == prefix + #***************************************************************************** FSMEmptyWordSymbol = '-' @@ -743,22 +774,7 @@ class FSMState(SageObject): - ``final_word_out`` -- (default: ``None``) a word that is written when the state is reached as the last state of some input; only for final - states - - .. WARNING:: - - ``final_word_out`` is not implemented everywhere. Currently it is - implemented in :meth:`FiniteStateMachine.process`, - in the LaTeX output, - :meth:`FiniteStateMachine.composition`, - :meth:`FiniteStateMachine.output_projection`, - :meth:`FiniteStateMachine.prepone_output`, - :meth:`Transducer.cartesian_product`, but not in - :meth:`FiniteStateMachine.determine_alphabets`, - :meth:`FiniteStateMachine.equivalence_classes`, - :meth:`FiniteStateMachine.transposition`, - :meth:`Transducer.intersection` and - :meth:`Transducer.simplification`. + states. - ``hook`` -- (default: ``None``) A function which is called when the state is reached during processing input. @@ -1161,7 +1177,9 @@ def __copy__(self): 'A' """ new = FSMState(self.label(), self.word_out, - self.is_initial, self.is_final) + self.is_initial, self.is_final, + color=self.color, + final_word_out=self.final_word_out) if hasattr(self, 'hook'): new.hook = self.hook return new @@ -2808,21 +2826,26 @@ def __contains__(self, item): return False - def is_Markov_chain(self): + def is_Markov_chain(self, is_zero=None): """ Checks whether ``self`` is a Markov chain where the transition probabilities are modeled as input labels. INPUT: - Nothing. + - ``is_zero`` -- by default (``is_zero=None``), checking for + zero is simply done by + :meth:`~sage.structure.element.Element.is_zero`. This + parameter can be used to provide a more sophisticated check + for zero, e.g. in the case of symbolic probabilities, see + the examples below. OUTPUT: - True or False. + ``True`` or ``False``. - ``on_duplicate_transition`` must be - ``duplicate_transition_add_input`` and the sum of the input + :attr:`on_duplicate_transition` must be + :func:`duplicate_transition_add_input` and the sum of the input weights of the transitions leaving a state must add up to 1. EXAMPLES:: @@ -2834,7 +2857,8 @@ def is_Markov_chain(self): sage: F.is_Markov_chain() True - ``on_duplicate_transition`` must be ``duplicate_transition_add_input``:: + :attr:`on_duplicate_transition` must be + :func:`duplicate_transition_add_input`:: sage: F = Transducer([[0, 0, 1/4, 0], [0, 1, 3/4, 1], ....: [1, 0, 1/2, 0], [1, 1, 1/2, 1]]) @@ -2848,12 +2872,46 @@ def is_Markov_chain(self): ....: on_duplicate_transition=duplicate_transition_add_input) sage: F.is_Markov_chain() False + + If the probabilities are variables in the symbolic ring, + :func:`~sage.symbolic.assumptions.assume` will do the trick:: + + sage: var('p q') + (p, q) + sage: F = Transducer([(0, 0, p, 1), (0, 0, q, 0)], + ....: on_duplicate_transition=duplicate_transition_add_input) + sage: assume(p + q == 1) + sage: (p + q - 1).is_zero() + True + sage: F.is_Markov_chain() + True + sage: forget() + sage: del(p, q) + + If the probabilities are variables in some polynomial ring, + the parameter ``is_zero`` can be used:: + + sage: R. = PolynomialRing(QQ) + sage: def is_zero_polynomial(polynomial): + ....: return polynomial in (p + q - 1)*R + sage: F = Transducer([(0, 0, p, 1), (0, 0, q, 0)], + ....: on_duplicate_transition=duplicate_transition_add_input) + sage: F.is_Markov_chain() + False + sage: F.is_Markov_chain(is_zero_polynomial) + True """ + def default_is_zero(expression): + return expression.is_zero() + + is_zero_function = default_is_zero + if is_zero is not None: + is_zero_function = is_zero if self.on_duplicate_transition != duplicate_transition_add_input: return False - return all((sum(t.word_in[0] for t in state.transitions) - 1).is_zero() + return all(is_zero_function(sum(t.word_in[0] for t in state.transitions) - 1) for state in self.states()) @@ -3155,9 +3213,6 @@ def latex_options(self, means, it can be combined with directly setting some attributes as outlined above. - See also :func:`setup_latex_preamble` or the example below on - how to setup the LaTeX environment. - EXAMPLES: See also the section on :ref:`finite_state_machine_LaTeX_output` @@ -3165,9 +3220,6 @@ def latex_options(self, :: - sage: from sage.combinat.finite_state_machine import setup_latex_preamble - sage: setup_latex_preamble() - sage: latex.mathjax_avoid_list('tikzpicture') sage: T = Transducer(initial_states=['I'], ....: final_states=[0, 3]) sage: for j in srange(4): @@ -3461,6 +3513,8 @@ def label_rotation(angle, both_directions): anchor_label = "north" return "rotate=%.2f, anchor=%s" % (angle_label, anchor_label) + setup_latex_preamble() + options = ["auto", "initial text=", ">=latex"] nonempty_final_word_out = False @@ -3815,16 +3869,22 @@ def determine_alphabets(self, reset=True): After this operation the input alphabet and the output alphabet of self are a list of letters. + .. TODO:: + + At the moment, the letters of the alphabets need to be hashable. + EXAMPLES:: sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1), ....: (2, 2, 1, 1), (2, 2, 0, 0)], + ....: final_states=[1], ....: determine_alphabets=False) + sage: T.state(1).final_word_out = [1, 4] sage: (T.input_alphabet, T.output_alphabet) (None, None) sage: T.determine_alphabets() sage: (T.input_alphabet, T.output_alphabet) - ([0, 1, 2], [0, 1]) + ([0, 1, 2], [0, 1, 4]) """ if reset: ain = set() @@ -3838,6 +3898,9 @@ def determine_alphabets(self, reset=True): ain.add(letter) for letter in t.word_out: aout.add(letter) + for s in self.iter_final_states(): + for letter in s.final_word_out: + aout.add(letter) self.input_alphabet = list(ain) self.output_alphabet = list(aout) @@ -4463,10 +4526,6 @@ def process(self, *args, **kwargs): processing (in the case the finite state machine runs as transducer). - Note that in the case the finite state machine is not - deterministic, one possible path is gone. This means, that in - this case the output can be wrong. - EXAMPLES:: sage: from sage.combinat.finite_state_machine import FSMState @@ -4489,6 +4548,32 @@ def process(self, *args, **kwargs): sage: [NAF.process(w)[0] for w in [[0], [0, 1], [1, 1], [0, 1, 0, 1], ....: [0, 1, 1, 1, 0], [1, 0, 0, 1, 1]]] [True, True, False, True, False, False] + + Non-deterministic finite state machines cannot be handeled. + + :: + + sage: T = Transducer([(0, 1, 0, 0), (0, 2, 0, 0)], + ....: initial_states=[0]) + sage: T.process([0]) + Traceback (most recent call last): + ... + NotImplementedError: Non-deterministic path encountered when processing input. + sage: T = Transducer([(0, 1, [0, 0], 0), (0, 2, [0, 0, 1], 0), + ....: (0, 1, 1, 2), (1, 0, [], 1), (1, 1, 1, 3)], + ....: initial_states=[0], final_states=[0, 1]) + sage: T.process([0]) + (False, None, None) + sage: T.process([0, 0]) + Traceback (most recent call last): + ... + NotImplementedError: Non-deterministic path encountered when processing input. + sage: T.process([1]) + (True, 1, [2]) + sage: T.process([1, 1]) + Traceback (most recent call last): + ... + NotImplementedError: process cannot handle epsilon transition leaving state 1. """ it = self.iter_process(*args, **kwargs) for _ in it: @@ -5343,6 +5428,7 @@ def composition(self, other, algorithm=None, The input alphabet of self has to be specified. This is a very limited implementation of composition. + WARNING: The output of ``other`` is fed into ``self``. If algorithm is ``None``, then the algorithm is chosen @@ -5431,11 +5517,39 @@ def composition(self, other, algorithm=None, sage: H.transitions()[0].word_out is H.transitions()[1].word_out True + In the explorative algorithm, transducers with non-empty final + output words are currently not implemented:: + + sage: A = transducers.GrayCode() + sage: B = transducers.abs([0, 1]) + sage: A.composition(B, algorithm='explorative') + Traceback (most recent call last): + ... + NotImplementedError: Explorative composition is not + implemented for transducers with non-empty final output + words. Try the direct algorithm instead. + + Similarly, the explorative algorithm cannot handle + non-deterministic finite state machines:: + + sage: A = Transducer([(0, 0, 0, 0), (0, 1, 0, 0)]) + sage: B = transducers.Identity([0]) + sage: A.composition(B, algorithm='explorative') + Traceback (most recent call last): + ... + NotImplementedError: Explorative composition is currently + not implemented for non-deterministic transducers. + sage: B.composition(A, algorithm='explorative') + Traceback (most recent call last): + ... + NotImplementedError: Explorative composition is currently + not implemented for non-deterministic transducers. + TESTS: Due to the limitations of the two algorithms the following (examples from above, but different algorithm used) does not - give a full answer or does not work + give a full answer or does not work. In the following, ``algorithm='explorative'`` is inadequate, as ``F`` has more than one initial state:: @@ -5555,10 +5669,10 @@ def _composition_explorative_(self, other): sage: B.determinisation() Automaton with 1 states - TODO: + .. TODO:: - The explorative algorithm should be re-implemented using the - process iterators of both finite state machines. + The explorative algorithm should be re-implemented using the + process iterators of both finite state machines. """ def composition_transition(state, input): (state1, state2) = state @@ -5584,6 +5698,18 @@ def composition_transition(state, input): output += transition2.word_out return ((new_state1, new_state2), output) + if any(s.final_word_out for s in self.iter_final_states()) or \ + any(s.final_word_out for s in other.iter_final_states()): + raise NotImplementedError("Explorative composition is not " + "implemented for transducers with " + "non-empty final output words. Try " + "the direct algorithm instead.") + + if not self.is_deterministic() or not other.is_deterministic(): + raise NotImplementedError("Explorative composition is " + "currently not implemented for " + "non-deterministic transducers.") + F = other.empty_copy() new_initial_states = [(other.initial_states()[0], self.initial_states()[0])] F.add_from_transition_function(composition_transition, @@ -5761,25 +5887,46 @@ def transposition(self): ....: initial_states=['1'], final_states=['1', '2']) sage: aut.transposition().initial_states() ['1', '2'] + + + TESTS: + + If a final state of ``self`` has a non-empty final output word, + transposition is not implemented:: + + sage: T = Transducer([('1', '1', 1, 0), ('1', '2', 0, 1), + ....: ('2', '2', 0, 2)], + ....: initial_states=['1'], + ....: final_states=['1', '2']) + sage: T.state('1').final_word_out = [2, 5] + sage: T.transposition() + Traceback (most recent call last): + ... + NotImplementedError: Transposition for transducers with + final output words is not implemented. """ transposition = self.empty_copy() - for state in self.states(): + for state in self.iter_states(): transposition.add_state(deepcopy(state)) - for transition in self.transitions(): + for transition in self.iter_transitions(): transposition.add_transition( transition.to_state.label(), transition.from_state.label(), transition.word_in, transition.word_out) - for initial in self.initial_states(): + for initial in self.iter_initial_states(): state = transposition.state(initial.label()) if not initial.is_final: state.is_final = True state.is_initial = False - for final in self.final_states(): + for final in self.iter_final_states(): state = transposition.state(final.label()) + if final.final_word_out: + raise NotImplementedError("Transposition for transducers " + "with final output words is not " + "implemented.") if not final.is_initial: state.is_final = False state.is_initial = True @@ -5945,7 +6092,7 @@ def prepone_output(self): obtained:: sage: C = Transducer([(0,1,0,0)]) - sage: C.prepone_output() # doctest: +ELLIPSIS + sage: C.prepone_output() verbose 0 (...: finite_state_machine.py, prepone_output) All transitions leaving state 0 have an output label with prefix 0. However, there is no inbound transition and it @@ -6081,7 +6228,8 @@ def equivalence_classes(self): - `p_a.\mathit{word}_\mathit{in}=p_b.\mathit{word}_\mathit{in}`, - `p_a.\mathit{word}_\mathit{out}=p_b.\mathit{word}_\mathit{out}`, - `a'` and `b'` have the same output label, and - - `a'` and `b'` are both final or both non-final. + - `a'` and `b'` are both final or both non-final and have the + same final output word. The function :meth:`.equivalence_classes` returns a list of the equivalence classes to this equivalence relation. @@ -6098,7 +6246,19 @@ def equivalence_classes(self): ....: ("B", "C", 0, 0), ("B", "C", 1, 1), ....: ("C", "D", 0, 1), ("C", "D", 1, 0), ....: ("D", "A", 0, 0), ("D", "A", 1, 1)]) - sage: fsm.equivalence_classes() + sage: sorted(fsm.equivalence_classes()) + [['A', 'C'], ['B', 'D']] + sage: fsm.state("A").is_final = True + sage: sorted(fsm.equivalence_classes()) + [['A'], ['B'], ['C'], ['D']] + sage: fsm.state("C").is_final = True + sage: sorted(fsm.equivalence_classes()) + [['A', 'C'], ['B', 'D']] + sage: fsm.state("A").final_word_out = 1 + sage: sorted(fsm.equivalence_classes()) + [['A'], ['B'], ['C'], ['D']] + sage: fsm.state("C").final_word_out = 1 + sage: sorted(fsm.equivalence_classes()) [['A', 'C'], ['B', 'D']] """ @@ -6123,7 +6283,8 @@ def equivalence_classes(self): # initialize with 0-equivalence classes_previous = [] - key_0 = lambda state: (state.is_final, state.color, state.word_out) + key_0 = lambda state: (state.is_final, state.color, state.word_out, + state.final_word_out) states_grouped = full_group_by(self.states(), key=key_0) classes_current = [equivalence_class for (key,equivalence_class) in states_grouped] @@ -6179,8 +6340,8 @@ def quotient(self, classes): Non-initial states may be merged with initial states, the resulting state is an initial state. - All states in a class must have the same ``is_final`` and - ``word_out`` values. + All states in a class must have the same ``is_final``, + ``final_word_out`` and ``word_out`` values. EXAMPLES:: @@ -6211,6 +6372,22 @@ def quotient(self, classes): Traceback (most recent call last): ... AssertionError: Transitions of state 'A' and 'B' are incompatible. + + TESTS:: + + sage: fsm = FiniteStateMachine([("A", "B", 0, 1), ("A", "B", 1, 0), + ....: ("B", "C", 0, 0), ("B", "C", 1, 1), + ....: ("C", "D", 0, 1), ("C", "D", 1, 0), + ....: ("D", "A", 0, 0), ("D", "A", 1, 1)], + ....: final_states=["A", "C"]) + sage: fsm.state("A").final_word_out = 1 + sage: fsm.state("C").final_word_out = 2 + sage: fsmq = fsm.quotient([[fsm.state("A"), fsm.state("C")], + ....: [fsm.state("B"), fsm.state("D")]]) + Traceback (most recent call last): + ... + AssertionError: Class ['A', 'C'] mixes + final states with different final output words. """ new = self.empty_copy() state_mapping = {} @@ -6249,6 +6426,9 @@ def quotient(self, classes): [(state_mapping[t.to_state], t.word_in, t.word_out) for t in state.transitions]), \ "Transitions of state %s and %s are incompatible." % (c[0], state) + assert new_state.final_word_out == state.final_word_out, \ + "Class %s mixes final states with different " \ + "final output words." % (c,) return new @@ -6900,13 +7080,6 @@ def asymptotic_moments(self, variable=SR.symbol('n')): see [HKW2014]_, Theorem 2 for details. If those exponents are integers, we can use a polynomial ring. - .. WARNING:: - - If not all states are final, we only print a warning. This is - for a transitional period to accomodate subsequential - transducers while those are not yet implemented - (cf. :trac:`16191`). - EXAMPLES: #. A trivial example: write the negative of the input:: @@ -6931,7 +7104,9 @@ def asymptotic_moments(self, variable=SR.symbol('n')): following agrees with the results in [HP2007]_. We first use the transducer to convert the standard binary - expansion to the NAF given in [HP2007]_:: + expansion to the NAF given in [HP2007]_. We use the parameter + ``with_final_word_out`` such that we do not have to add + sufficiently many trailing zeros:: sage: NAF = Transducer([(0, 0, 0, 0), ....: (0, '.1', 1, None), @@ -6940,19 +7115,18 @@ def asymptotic_moments(self, variable=SR.symbol('n')): ....: (1, 1, 1, 0), ....: (1, '.1', 0, None)], ....: initial_states=[0], - ....: final_states=[0]) + ....: final_states=[0], + ....: with_final_word_out=[0]) As an example, we compute the NAF of `27` by this - transducer. Note that we have to add two trailing (at the - most significant positions) digits `0` in order to be sure - to reach the final state. + transducer. :: sage: binary_27 = 27.bits() sage: binary_27 [1, 1, 0, 1, 1] - sage: NAF_27 = NAF(binary_27+[0, 0]) + sage: NAF_27 = NAF(binary_27) sage: NAF_27 [-1, 0, -1, 0, 0, 1, 0] sage: ZZ(NAF_27, base=2) @@ -6970,9 +7144,29 @@ def asymptotic_moments(self, variable=SR.symbol('n')): ....: input_alphabet=[-1, 0, 1], ....: initial_states=[0], ....: final_states=[0]) + + At the moment, we can not use composition with ``NAF``, + because it has non-empty final output words:: + + sage: NAFweight = weight_transducer.composition( + ....: NAF, + ....: algorithm='explorative') + Traceback (most recent call last): + ... + NotImplementedError: Explorative composition is not + implemented for transducers with non-empty final output + words. Try the direct algorithm instead. + + + Thus, we change ``NAF``, then compose and again construct + the final output words:: + + sage: for s in NAF.final_states(): + ....: s.final_word_out = [] sage: NAFweight = weight_transducer.composition( ....: NAF, ....: algorithm='explorative').relabeled() + sage: NAFweight.construct_final_word_out(0) sage: sorted(NAFweight.transitions()) [Transition from 0 to 0: 0|0, Transition from 0 to 1: 1|-, @@ -6986,8 +7180,6 @@ def asymptotic_moments(self, variable=SR.symbol('n')): Now, we actually compute the asymptotic moments:: sage: moments = NAFweight.asymptotic_moments() - verbose 0 (...) Not all states are final. Proceeding - under the assumption that you know what you are doing. sage: moments['expectation'] 1/3*n + Order(1) sage: moments['variance'] @@ -6995,11 +7187,6 @@ def asymptotic_moments(self, variable=SR.symbol('n')): sage: moments['covariance'] Order(1) - In this case, we can ignore the warning: we could have all - states as final states if we would have a final output - label, i.e., a subsequential transducer. However, this is - not yet implemented in this package, cf. :trac:`16191`. - #. This is Example 3.1 in [HKW2014]_, where a transducer with variable output labels is given. There, the aim was to choose the output labels of this very simple transducer such @@ -7013,7 +7200,7 @@ def asymptotic_moments(self, variable=SR.symbol('n')): sage: T = Transducer([[0, 0, 0, a_1], [0, 1, 1, a_3], ....: [1, 0, 0, a_4], [1, 1, 1, a_2]], ....: initial_states=[0], final_states=[0, 1]) - sage: moments = T.asymptotic_moments() # doctest: +ELLIPSIS + sage: moments = T.asymptotic_moments() verbose 0 (...) Non-integer output weights lead to significant performance degradation. sage: moments['expectation'] @@ -7031,8 +7218,6 @@ def asymptotic_moments(self, variable=SR.symbol('n')): `):: sage: moments = transducers.GrayCode().asymptotic_moments() - verbose 0 (...) Not all states are final. Proceeding - under the assumption that you know what you are doing. sage: moments['expectation'] 1/2*n + Order(1) sage: moments['variance'] @@ -7040,11 +7225,6 @@ def asymptotic_moments(self, variable=SR.symbol('n')): sage: moments['covariance'] Order(1) - Also in this case, we can ignore the warning: we could have - all states as final states if we would have a final output - label, i.e., a subsequential transducer. However, this is - not yet implemented in this package, cf. :trac:`16191`. - #. This is the first part of Example 6.3 in [HKW2014]_, counting the number of 10 blocks in the standard binary expansion. The least significant digit is at the left-most @@ -7191,7 +7371,7 @@ def asymptotic_moments(self, variable=SR.symbol('n')): sage: T = Transducer([[0, 0, 0, 0], [0, 0, 1, -1/2]], ....: initial_states=[0], final_states=[0]) - sage: moments = T.asymptotic_moments() # doctest: +ELLIPSIS + sage: moments = T.asymptotic_moments() verbose 0 (...) Non-integer output weights lead to significant performance degradation. sage: moments['expectation'] @@ -7233,11 +7413,19 @@ def asymptotic_moments(self, variable=SR.symbol('n')): sage: s = FSMState(0, word_out=2/3) sage: T = Transducer([(s, s, 0, 1/2)], ....: initial_states=[s], final_states=[s]) - sage: T.asymptotic_moments()['expectation'] # doctest: +ELLIPSIS + sage: T.asymptotic_moments()['expectation'] verbose 0 (...) Non-integer output weights lead to significant performance degradation. 7/6*n + Order(1) + #. All states of ``self`` have to be final:: + + sage: T = Transducer([(0, 1, 1, 4)], initial_states=[0]) + sage: T.asymptotic_moments() + Traceback (most recent call last): + ... + ValueError: Not all states are final. + ALGORITHM: See [HKW2014]_, Theorem 2. @@ -7264,10 +7452,8 @@ def asymptotic_moments(self, variable=SR.symbol('n')): if len(self.initial_states()) != 1: raise ValueError("A unique initial state is required.") - if len(self.final_states()) != len(self.states()): - verbose("Not all states are final. Proceeding under the " - "assumption that you know what you are doing.", - level=0) + if not all(state.is_final for state in self.iter_states()): + raise ValueError("Not all states are final.") if not self.is_complete(): raise NotImplementedError("This finite state machine is " @@ -7641,7 +7827,10 @@ def determinisation(self): sage: auto.is_deterministic() False sage: auto.process(list('aaab')) - (False, 'A') + Traceback (most recent call last): + ... + NotImplementedError: Non-deterministic path encountered + when processing input. sage: auto.states() ['A', 'C', 'B'] sage: Ddet = auto.determinisation() @@ -7858,12 +8047,6 @@ def process(self, *args, **kwargs): could not be processed, i.e., when at one point no transition to go could be found.). - Note that in the case the automaton is not - deterministic, one possible path is gone. This means that in - this case the output can be wrong. Use - :meth:`.determinisation` to get a deterministic automaton - machine and try again. - By setting ``FSMOldProcessOutput`` to ``False`` the new desired output is produced. @@ -8068,15 +8251,13 @@ def intersection(self, other, only_accessible_components=True): ....: ('2', '2', 1, 0), ....: ('2', '2', 0, 1)], ....: initial_states=['1'], - ....: final_states=['2'], - ....: determine_alphabets=True) + ....: final_states=['2']) sage: transducer2 = Transducer([('A', 'A', 1, 0), ....: ('A', 'B', 0, 0), ....: ('B', 'B', 0, 1), ....: ('B', 'A', 1, 1)], ....: initial_states=['A'], - ....: final_states=['B'], - ....: determine_alphabets=True) + ....: final_states=['B']) sage: res = transducer1.intersection(transducer2) sage: res.transitions() [Transition from ('1', 'A') to ('2', 'A'): 1|0, @@ -8096,20 +8277,35 @@ def intersection(self, other, only_accessible_components=True): ....: (0, 1, None, 'c'), ....: (1, 1, None, 'c')], ....: initial_states=[0], - ....: final_states=[0, 1], - ....: determine_alphabets=True) + ....: final_states=[0, 1]) sage: t2 = Transducer([('A', 'A', None, 'b'), ....: ('A', 'B', 'a', 'c'), ....: ('B', 'B', 'a', 'c')], ....: initial_states=['A'], - ....: final_states=['A', 'B'], - ....: determine_alphabets=True) + ....: final_states=['A', 'B']) sage: t2.intersection(t1) Traceback (most recent call last): ... ValueError: An epsilon-transition (with empty input or output) was found. + TESTS:: + + sage: transducer1 = Transducer([('1', '2', 1, 0)], + ....: initial_states=['1'], + ....: final_states=['2']) + sage: transducer2 = Transducer([('A', 'B', 1, 0)], + ....: initial_states=['A'], + ....: final_states=['B']) + sage: res = transducer1.intersection(transducer2) + sage: res.final_states() + [('2', 'B')] + sage: transducer1.state('2').final_word_out = 1 + sage: transducer2.state('B').final_word_out = 2 + sage: res = transducer1.intersection(transducer2) + sage: res.final_states() + [] + REFERENCES: .. [BaWo2012] Javier Baliosian and Dina Wonsever, *Finite State @@ -8131,10 +8327,20 @@ def function(transition1, transition2): else: raise LookupError - return self.product_FiniteStateMachine( - other, - function, - only_accessible_components=only_accessible_components) + new = self.product_FiniteStateMachine( + other, + function, + only_accessible_components=only_accessible_components, + final_function=lambda s1, s2: s1.final_word_out) + + for state in new.iter_final_states(): + state0 = self.state(state.label()[0]) + state1 = other.state(state.label()[1]) + if state0.final_word_out != state1.final_word_out: + state.final_word_out = None + state.is_final = False + + return new def cartesian_product(self, other, only_accessible_components=True): @@ -8191,7 +8397,7 @@ def cartesian_product(self, other, only_accessible_components=True): ....: final_states=[1], ....: determine_alphabets=True) sage: result = transducer1.cartesian_product(transducer2) - doctest:1: DeprecationWarning: The output of + doctest:...: DeprecationWarning: The output of Transducer.cartesian_product will change. Please use Transducer.intersection for the original output. See http://trac.sagemath.org/16061 for details. @@ -8460,10 +8666,6 @@ def process(self, *args, **kwargs): - the third gives a list of the output labels used during processing. - Note that in the case the transducer is not - deterministic, one possible path is gone. This means, that in - this case the output can be wrong. - By setting ``FSMOldProcessOutput`` to ``False`` the new desired output is produced. @@ -8767,12 +8969,27 @@ def next(self): try: while not found: next_word.append(self.read_letter()) + if len(next_word) == 1 and any(not t.word_in + for t in self.current_state.transitions): + raise NotImplementedError( + "process cannot handle epsilon transition " + "leaving state %s." % self.current_state.label()) try: transition = self.get_next_transition( next_word) found = True except ValueError: pass + + if found and any( + t is not transition and startswith(t.word_in, + next_word) + for t in self.current_state.transitions): + raise NotImplementedError("Non-deterministic " + "path encountered " + "when processing " + "input.") + except StopIteration: # this means input tape is finished if len(next_word) > 0: @@ -8917,8 +9134,9 @@ def get_next_transition(self, word_in): #***************************************************************************** +@cached_function def setup_latex_preamble(): - """ + r""" This function adds the package ``tikz`` with support for automata to the preamble of Latex so that the finite state machines can be drawn nicely. @@ -8931,9 +9149,6 @@ def setup_latex_preamble(): Nothing. - In the Sage notebook, you probably want to use - ``latex.mathjax_avoid_list('tikzpicture')`` such that - :func:`~sage.misc.latex.view` actually shows the result. See the section on :ref:`finite_state_machine_LaTeX_output` in the introductory examples of this module. @@ -8941,10 +9156,13 @@ def setup_latex_preamble(): sage: from sage.combinat.finite_state_machine import setup_latex_preamble sage: setup_latex_preamble() - sage: latex.mathjax_avoid_list('tikzpicture') + sage: ("\usepackage{tikz}" in latex.extra_preamble()) == latex.has_file("tikz.sty") + True """ latex.add_package_to_preamble_if_available('tikz') - latex.add_to_preamble('\\usetikzlibrary{automata}') + latex.add_to_mathjax_avoid_list("tikz") + if latex.has_file("tikz.sty"): + latex.add_to_preamble(r'\usetikzlibrary{automata}') #***************************************************************************** diff --git a/src/sage/combinat/finite_state_machine_generators.py b/src/sage/combinat/finite_state_machine_generators.py index b5aa35a63d0..529fbedd525 100644 --- a/src/sage/combinat/finite_state_machine_generators.py +++ b/src/sage/combinat/finite_state_machine_generators.py @@ -24,9 +24,12 @@ :meth:`~TransducerGenerators.Identity` | Returns a transducer realizing the identity map. :meth:`~TransducerGenerators.abs` | Returns a transducer realizing absolute value. :meth:`~TransducerGenerators.operator` | Returns a transducer realizing a binary operation. + :meth:`~TransducerGenerators.all` | Returns a transducer realizing logical ``and``. + :meth:`~TransducerGenerators.any` | Returns a transducer realizing logical ``or``. :meth:`~TransducerGenerators.add` | Returns a transducer realizing addition. :meth:`~TransducerGenerators.sub` | Returns a transducer realizing subtraction. :meth:`~TransducerGenerators.CountSubblockOccurrences` | Returns a transducer counting the occurrences of a subblock. + :meth:`~TransducerGenerators.Wait` | Returns a transducer writing ``False`` until first (or k-th) true input is read. :meth:`~TransducerGenerators.weight` | Returns a transducer realizing the Hamming weight :meth:`~TransducerGenerators.GrayCode` | Returns a transducer realizing binary Gray code. @@ -74,9 +77,12 @@ class TransducerGenerators(object): - :meth:`~Identity` - :meth:`~abs` - :meth:`~TransducerGenerators.operator` + - :meth:`~all` + - :meth:`~any` - :meth:`~add` - :meth:`~sub` - :meth:`~CountSubblockOccurrences` + - :meth:`~Wait` - :meth:`~GrayCode` """ @@ -240,6 +246,49 @@ def transition_function(read, input): s.is_final = True return T + def Wait(self, input_alphabet, threshold=1): + r""" + Writes ``False`` until reading the ``threshold``-th occurrence + of a true input letter; then writes ``True``. + + INPUT: + + - ``input_alphabet`` -- a list or other iterable. + + - ``threshold`` -- a positive integer specifying how many + occurrences of ``True`` inputs are waited for. + + OUTPUT: + + A transducer writing ``False`` until the ``threshold``-th true + (Python's standard conversion to boolean is used to convert the + actual input to boolean) input is read. Subsequently, the + transducer writes ``True``. + + EXAMPLES:: + + sage: T = transducers.Wait([0, 1]) + sage: T([0, 0, 1, 0, 1, 0]) + [False, False, True, True, True, True] + sage: T2 = transducers.Wait([0, 1], threshold=2) + sage: T2([0, 0, 1, 0, 1, 0]) + [False, False, False, False, True, True] + """ + def transition(state, input): + if state == threshold: + return (threshold, True) + if not input: + return (state, False) + return (state + 1, state + 1 == threshold) + + T = Transducer(transition, + input_alphabet=input_alphabet, + initial_states=[0]) + for s in T.iter_states(): + s.is_final = True + + return T + def operator(self, operator, input_alphabet, number_of_operands=2): r""" @@ -317,7 +366,112 @@ def transition_function(state, operands): final_states=[0]) - def add(self, input_alphabet): + def all(self, input_alphabet, number_of_operands=2): + """ + Returns a transducer which realizes logical ``and`` over the given + input alphabet. + + INPUT: + + - ``input_alphabet`` -- a list or other iterable. + + - ``number_of_operands`` -- (default: `2`) specifies the number + of input arguments for the ``and`` operation. + + OUTPUT: + + A transducer mapping an input word + `(i_{01}, \ldots, i_{0d})\ldots (i_{k1}, \ldots, i_{kd})` to the word + `(i_{01} \land \cdots \land i_{0d})\ldots (i_{k1} \land \cdots \land i_{kd})`. + + The input alphabet of the generated transducer is the cartesian + product of ``number_of_operands`` copies of ``input_alphabet``. + + EXAMPLE: + + The following transducer realizes letter-wise + logical ``and``:: + + sage: T = transducers.all([False, True]) + sage: T.transitions() + [Transition from 0 to 0: (False, False)|False, + Transition from 0 to 0: (False, True)|False, + Transition from 0 to 0: (True, False)|False, + Transition from 0 to 0: (True, True)|True] + sage: T.input_alphabet + [(False, False), (False, True), (True, False), (True, True)] + sage: T.initial_states() + [0] + sage: T.final_states() + [0] + sage: T([(False, False), (False, True), (True, False), (True, True)]) + [False, False, False, True] + + More than two operands and other input alphabets (with + conversion to boolean) are also possible:: + + sage: T3 = transducers.all([0, 1], number_of_operands=3) + sage: T3([(0, 0, 0), (1, 0, 0), (1, 1, 1)]) + [False, False, True] + """ + return self.operator(lambda *args: all(args), + input_alphabet, number_of_operands) + + + def any(self, input_alphabet, number_of_operands=2): + """ + Returns a transducer which realizes logical ``or`` over the given + input alphabet. + + INPUT: + + - ``input_alphabet`` -- a list or other iterable. + + - ``number_of_operands`` -- (default: `2`) specifies the number + of input arguments for the ``or`` operation. + + OUTPUT: + + A transducer mapping an input word + `(i_{01}, \ldots, i_{0d})\ldots (i_{k1}, \ldots, i_{kd})` to the word + `(i_{01} \lor \cdots \lor i_{0d})\ldots (i_{k1} \lor \cdots \lor i_{kd})`. + + The input alphabet of the generated transducer is the cartesian + product of ``number_of_operands`` copies of ``input_alphabet``. + + EXAMPLE: + + The following transducer realizes letter-wise + logical ``or``:: + + sage: T = transducers.any([False, True]) + sage: T.transitions() + [Transition from 0 to 0: (False, False)|False, + Transition from 0 to 0: (False, True)|True, + Transition from 0 to 0: (True, False)|True, + Transition from 0 to 0: (True, True)|True] + sage: T.input_alphabet + [(False, False), (False, True), (True, False), (True, True)] + sage: T.initial_states() + [0] + sage: T.final_states() + [0] + sage: T([(False, False), (False, True), (True, False), (True, True)]) + [False, True, True, True] + + More than two operands and other input alphabets (with + conversion to boolean) are also possible:: + + sage: T3 = transducers.any([0, 1], number_of_operands=3) + sage: T3([(0, 0, 0), (1, 0, 0), (1, 1, 1)]) + [False, True, True] + """ + return self.operator(lambda *args: any(args), + input_alphabet, number_of_operands) + + + + def add(self, input_alphabet, number_of_operands=2): """ Returns a transducer which realizes addition on pairs over the given input alphabet. @@ -326,13 +480,17 @@ def add(self, input_alphabet): - ``input_alphabet`` -- a list or other iterable. + - ``number_of_operands`` -- (default: `2`) it specifies the number + of input arguments the operator takes. + OUTPUT: - A transducer mapping an input word `(i_0, i'_0)\ldots (i_k, i'_k)` - to the word `(i_0 + i'_0)\ldots (i_k + i'_k)`. + A transducer mapping an input word + `(i_{01}, \ldots, i_{0d})\ldots (i_{k1}, \ldots, i_{kd})` to the word + `(i_{01} + \cdots + i_{0d})\ldots (i_{k1} + \cdots + i_{kd})`. The input alphabet of the generated transducer is the cartesian - product of two copies of ``input_alphabet``. + product of ``number_of_operands`` copies of ``input_alphabet``. EXAMPLE: @@ -353,9 +511,19 @@ def add(self, input_alphabet): [0] sage: T([(0, 0), (0, 1), (1, 0), (1, 1)]) [0, 1, 1, 2] + + More than two operands can also be handled:: + + sage: T3 = transducers.add([0, 1], number_of_operands=3) + sage: T3.input_alphabet + [(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), + (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)] + sage: T3([(0, 0, 0), (0, 1, 0), (0, 1, 1), (1, 1, 1)]) + [0, 1, 2, 3] """ - import operator - return self.operator(operator.add, input_alphabet) + return self.operator(lambda *args: sum(args), + input_alphabet, + number_of_operands=number_of_operands) def sub(self, input_alphabet): @@ -525,7 +693,7 @@ def GrayCode(self): Transducer with 3 states sage: sage.combinat.finite_state_machine.FSMOldProcessOutput = False sage: for v in srange(0, 10): - ....: print v, G(v.digits(base=2) + [0]) + ....: print v, G(v.digits(base=2)) 0 [] 1 [1] 2 [1, 1] @@ -553,7 +721,8 @@ def GrayCode(self): [2, 1, z, o], [2, 2, o, z]], initial_states=[0], - final_states=[1]) + final_states=[1], + with_final_word_out=[0]) # Easy access to the transducer generators from the command line: diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index ef8092d07f9..e3be0f2c9ac 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -13,6 +13,7 @@ from sage.structure.element import Element from sage.structure.parent import Parent from sage.structure.sage_object import have_same_parent +from sage.structure.indexed_generators import IndexedGenerators from sage.modules.free_module_element import vector from sage.misc.misc import repr_lincomb from sage.modules.module import Module @@ -180,17 +181,17 @@ def _sorted_items_for_printing(self): sage: f = B['a'] + 2*B['c'] + 3 * B['b'] sage: f._sorted_items_for_printing() [('a', 1), ('b', 3), ('c', 2)] - sage: F.print_options(monomial_cmp = lambda x,y: -cmp(x,y)) + sage: F.print_options(generator_cmp = lambda x,y: -cmp(x,y)) sage: f._sorted_items_for_printing() [('c', 2), ('b', 3), ('a', 1)] - sage: F.print_options(monomial_cmp=cmp) #reset to original state + sage: F.print_options(generator_cmp=cmp) #reset to original state .. seealso:: :meth:`_repr_`, :meth:`_latex_`, :meth:`print_options` """ print_options = self.parent().print_options() v = self._monomial_coefficients.items() try: - v.sort(cmp = print_options['monomial_cmp'], + v.sort(cmp = print_options['generator_cmp'], key = lambda monomial_coeff: monomial_coeff[0]) except Exception: # Sorting the output is a plus, but if we can't, no big deal pass @@ -217,13 +218,13 @@ def _repr_(self): function on elements of the support:: sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c'], - ... monomial_cmp = lambda x,y: cmp(y,x)) + ... generator_cmp = lambda x,y: cmp(y,x)) sage: e = F.basis() sage: e['a'] + 3*e['b'] + 2*e['c'] 2*B['c'] + 3*B['b'] + B['a'] sage: F = CombinatorialFreeModule(QQ, ['ac', 'ba', 'cb'], - ... monomial_cmp = lambda x,y: cmp(x[1],y[1])) + ... generator_cmp = lambda x,y: cmp(x[1],y[1])) sage: e = F.basis() sage: e['ac'] + 3*e['ba'] + 2*e['cb'] 3*B['ba'] + 2*B['cb'] + B['ac'] @@ -324,13 +325,13 @@ def _latex_(self): function on elements of the support:: sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c'], - ... monomial_cmp = lambda x,y: cmp(y,x)) + ... generator_cmp = lambda x,y: cmp(y,x)) sage: e = F.basis() sage: latex(e['a'] + 3*e['b'] + 2*e['c']) 2B_{c} + 3B_{b} + B_{a} sage: F = CombinatorialFreeModule(QQ, ['ac', 'ba', 'cb'], - ... monomial_cmp = lambda x,y: cmp(x[1],y[1])) + ... generator_cmp = lambda x,y: cmp(x[1],y[1])) sage: e = F.basis() sage: latex(e['ac'] + 3*e['ba'] + 2*e['cb']) 3B_{ba} + 2B_{cb} + B_{ac} @@ -586,7 +587,7 @@ def coefficient(self, m): """ # NT: coefficient_fast should be the default, just with appropriate assertions # that can be turned on or off - C = self.parent()._basis_keys + C = self.parent()._indices assert m in C, "%s should be an element of %s"%(m, C) if hasattr(C, "element_class") and not isinstance(m, C.element_class): m = C(m) @@ -974,7 +975,7 @@ def _divide_if_possible(x, y): else: return q -class CombinatorialFreeModule(UniqueRepresentation, Module): +class CombinatorialFreeModule(UniqueRepresentation, Module, IndexedGenerators): r""" Class for free modules with a named basis @@ -994,67 +995,13 @@ class CombinatorialFreeModule(UniqueRepresentation, Module): default None, in which case use the "category of modules with basis" over the base ring ``R``) - Options controlling the printing of elements: - - - ``prefix`` - string, prefix used for printing elements of this - module (optional, default 'B'). With the default, a monomial - indexed by 'a' would be printed as ``B['a']``. - - - ``latex_prefix`` - string or None, prefix used in the LaTeX - representation of elements (optional, default None). If this is - anything except the empty string, it prints the index as a - subscript. If this is None, it uses the setting for ``prefix``, - so if ``prefix`` is set to "B", then a monomial indexed by 'a' - would be printed as ``B_{a}``. If this is the empty string, then - don't print monomials as subscripts: the monomial indexed by 'a' - would be printed as ``a``, or as ``[a]`` if ``latex_bracket`` is - True. - - - ``bracket`` - None, bool, string, or list or tuple of - strings (optional, default None): if None, use the value of the - attribute ``self._repr_option_bracket``, which has default value - True. (``self._repr_option_bracket`` is available for backwards - compatibility. Users should set ``bracket`` instead. If - ``bracket`` is set to anything except None, it overrides - the value of ``self._repr_option_bracket``.) If False, do not - include brackets when printing elements: a monomial indexed by - 'a' would be printed as ``B'a'``, and a monomial indexed by - (1,2,3) would be printed as ``B(1,2,3)``. If True, use "[" and - "]" as brackets. If it is one of "[", "(", or "{", use it and - its partner as brackets. If it is any other string, use it as - both brackets. If it is a list or tuple of strings, use the - first entry as the left bracket and the second entry as the - right bracket. - - - ``latex_bracket`` - bool, string, or list or tuple of strings - (optional, default False): if False, do not include brackets in - the LaTeX representation of elements. This option is only - relevant if ``latex_prefix`` is the empty string; otherwise, - brackets are not used regardless. If True, use "\\left[" and - "\\right]" as brackets. If this is one of "[", "(", "\\{", "|", - or "||", use it and its partner, prepended with "\\left" and - "\\right", as brackets. If this is any other string, use it as - both brackets. If this is a list or tuple of strings, use the - first entry as the left bracket and the second entry as the - right bracket. - - - ``scalar_mult`` - string to use for scalar multiplication in - the print representation (optional, default "*") - - - ``latex_scalar_mult`` - string or None (optional, default None), - string to use for scalar multiplication in the latex - representation. If None, use the empty string if ``scalar_mult`` - is set to "*", otherwise use the value of ``scalar_mult``. - - - ``tensor_symbol`` - string or None (optional, default None), - string to use for tensor product in the print representation. If - None, use the ``sage.categories.tensor.symbol``. - - - ``monomial_cmp`` - a comparison function (optional, default cmp), - to use for sorting elements in the output of elements - - .. note:: These print options may also be accessed and modified using the - :meth:`print_options` method, after the module has been defined. + For the options controlling the printing of elements, see + :class:`~sage.structure.indexed_generators.IndexedGenerators`. + + .. NOTE:: + + These print options may also be accessed and modified using the + :meth:`print_options` method, after the module has been defined. EXAMPLES: @@ -1158,7 +1105,10 @@ class CombinatorialFreeModule(UniqueRepresentation, Module): sage: F = CombinatorialFreeModule(QQ, ['a','b'], prefix='x') sage: original_print_options = F.print_options() sage: sorted(original_print_options.items()) - [('bracket', None), ('latex_bracket', False), ('latex_prefix', None), ('latex_scalar_mult', None), ('monomial_cmp', ), ('prefix', 'x'), ('scalar_mult', '*'), ('tensor_symbol', None)] + [('bracket', None), ('generator_cmp', ), + ('latex_bracket', False), ('latex_prefix', None), + ('latex_scalar_mult', None), ('prefix', 'x'), + ('scalar_mult', '*'), ('tensor_symbol', None)] sage: e = F.basis() sage: e['a'] - 3 * e['b'] @@ -1174,7 +1124,7 @@ class CombinatorialFreeModule(UniqueRepresentation, Module): sage: latex(e['a'] - 3 * e['b']) y_{a} - 3 y_{b} - sage: F.print_options(monomial_cmp = lambda x,y: -cmp(x,y)) + sage: F.print_options(generator_cmp = lambda x,y: -cmp(x,y)) sage: e['a'] - 3 * e['b'] -3 x{'b'} + x{'a'} sage: F.print_options(**original_print_options) # reset print options @@ -1217,7 +1167,7 @@ class CombinatorialFreeModule(UniqueRepresentation, Module): """ @staticmethod - def __classcall_private__(cls, base_ring, basis_keys, category = None, **keywords): + def __classcall_private__(cls, base_ring, basis_keys, category = None, prefix="B", **keywords): """ TESTS:: @@ -1259,11 +1209,11 @@ def __classcall_private__(cls, base_ring, basis_keys, category = None, **keyword latex_bracket = keywords.get('latex_bracket', None) if isinstance(latex_bracket, list): keywords['latex_bracket'] = tuple(latex_bracket) - return super(CombinatorialFreeModule, cls).__classcall__(cls, base_ring, basis_keys, category = category, **keywords) + return super(CombinatorialFreeModule, cls).__classcall__(cls, base_ring, basis_keys, category = category, prefix=prefix, **keywords) Element = CombinatorialFreeModuleElement - def __init__(self, R, basis_keys, element_class = None, category = None, **kwds): + def __init__(self, R, basis_keys, element_class = None, category = None, prefix="B", **kwds): r""" TESTS:: @@ -1287,7 +1237,7 @@ def __init__(self, R, basis_keys, element_class = None, category = None, **kwds) TESTS: - Regression test for #10127: ``self._basis_keys`` needs to be + Regression test for :trac:10127`: ``self._indices`` needs to be set early enough, in case the initialization of the categories use ``self.basis().keys()``. This occured on several occasions in non trivial constructions. In the following example, @@ -1327,38 +1277,42 @@ def __init__(self, R, basis_keys, element_class = None, category = None, **kwds) # the classcall and passes lists as basis_keys if isinstance(basis_keys, (list, tuple)): basis_keys = FiniteEnumeratedSet(basis_keys) - self._basis_keys = basis_keys # Needs to be done early: #10127 + + # ignore the optional 'key' since it only affects CachedRepresentation + kwds.pop('key', None) + # This needs to be first as per #10127 + if 'monomial_cmp' in kwds: + kwds['generator_cmp'] = kwds['monomial_cmp'] + del kwds['monomial_cmp'] + IndexedGenerators.__init__(self, basis_keys, prefix, **kwds) Parent.__init__(self, base = R, category = category, # Could we get rid of this? element_constructor = self._element_constructor_) - self._order = None - # printing options for elements (set when initializing self). - # This includes self._repr_option_bracket (kept for backwards - # compatibility, declared to be True by default, needs to be - # overridden explicitly). - self._print_options = {'prefix': "B", - 'bracket': None, - 'latex_bracket': False, - 'latex_prefix': None, - 'scalar_mult': "*", - 'latex_scalar_mult': None, - 'tensor_symbol': None, - 'monomial_cmp': cmp} - # 'bracket': its default value here is None, meaning that - # the value of self._repr_option_bracket is used; the default - # value of that attribute is True -- see immediately before - # the method _repr_term. If 'bracket' is any value - # except None, then it overrides the value of - # self._repr_option_bracket. Future users might consider - # using 'bracket' instead of _repr_option_bracket. + # For backwards compatibility + _repr_term = IndexedGenerators._repr_generator + _latex_term = IndexedGenerators._latex_generator - # ignore the optional 'key' since it only affects CachedRepresentation - kwds.pop('key', None) - self.print_options(**kwds) + def _ascii_art_term(self, m): + r""" + Return an ascii art representation of the term indexed by ``m``. + + TESTS:: + + sage: R = NonCommutativeSymmetricFunctions(QQ).R() + sage: ascii_art(R.one()) + 1 + """ + from sage.misc.ascii_art import AsciiArt + try: + if m == self.one_basis(): + return AsciiArt(["1"]) + except StandardError: + pass + return IndexedGenerators._ascii_art_generator(self, m) # mostly for backward compatibility @lazy_attribute @@ -1559,13 +1513,13 @@ def _element_constructor_(self, x): raise TypeError("do not know how to make x (= %s) an element of %s"%(x, self)) #x is an element of the basis enumerated set; # This is a very ugly way of testing this - elif ((hasattr(self._basis_keys, 'element_class') and - isinstance(self._basis_keys.element_class, type) and - isinstance(x, self._basis_keys.element_class)) - or (sage.structure.element.parent(x) == self._basis_keys)): + elif ((hasattr(self._indices, 'element_class') and + isinstance(self._indices.element_class, type) and + isinstance(x, self._indices.element_class)) + or (sage.structure.element.parent(x) == self._indices)): return self.monomial(x) - elif x in self._basis_keys: - return self.monomial(self._basis_keys(x)) + elif x in self._indices: + return self.monomial(self._indices(x)) else: if hasattr(self, '_coerce_end'): try: @@ -1609,7 +1563,7 @@ def dimension(self): sage: s.dimension() +Infinity """ - return self._basis_keys.cardinality() + return self._indices.cardinality() def gens(self): """ @@ -1705,294 +1659,6 @@ def from_vector(self, vector): cc = self.get_order() return self._from_dict(dict( (cc[index], coeff) for (index,coeff) in vector.iteritems())) - def prefix(self): - """ - Returns the prefix used when displaying elements of self. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) - sage: F.prefix() - 'B' - - :: - - sage: X = SchubertPolynomialRing(QQ) - sage: X.prefix() - 'X' - """ - return self._print_options['prefix'] - - def print_options(self, **kwds): - """ - Return the current print options, or set an option. - - INPUT: all of the input is optional; if present, it should be - in the form of keyword pairs, such as - ``latex_bracket='('``. The allowable keywords are: - - - ``prefix`` - - ``latex_prefix`` - - ``bracket`` - - ``latex_bracket`` - - ``scalar_mult`` - - ``latex_scalar_mult`` - - ``tensor_symbol`` - - ``monomial_cmp`` - - See the documentation for :class:`CombinatorialFreeModule` for - descriptions of the effects of setting each of these options. - - OUTPUT: if the user provides any input, set the appropriate - option(s) and return nothing. Otherwise, return the - dictionary of settings for print and LaTeX representations. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2,3], prefix='x') - sage: F.print_options() - {...'prefix': 'x'...} - sage: F.print_options(bracket='(') - sage: F.print_options() - {...'bracket': '('...} - - TESTS:: - - sage: sorted(F.print_options().items()) - [('bracket', '('), ('latex_bracket', False), ('latex_prefix', None), ('latex_scalar_mult', None), ('monomial_cmp', ), ('prefix', 'x'), ('scalar_mult', '*'), ('tensor_symbol', None)] - sage: F.print_options(bracket='[') # reset - """ - # don't just use kwds.get(...) because I want to distinguish - # between an argument like "option=None" and the option not - # being there altogether. - if kwds: - for option in kwds: - if option in ['prefix', 'latex_prefix', 'bracket', 'latex_bracket', - 'scalar_mult', 'latex_scalar_mult', 'tensor_symbol', - 'monomial_cmp' - ]: - self._print_options[option] = kwds[option] - else: - raise ValueError('%s is not a valid print option.' % option) - else: - return self._print_options - - _repr_option_bracket = True - - def _repr_term(self, m): - """ - Returns a string representing the basis element indexed by m. - - The output can be customized by setting any of the following - options when initializing the module: - - - prefix - - bracket - - scalar_mult - - Alternatively, one can use the :meth:`print_options` method - to achieve the same effect. To modify the bracket setting, - one can also set ``self._repr_option_bracket`` as long as one - has *not* set the ``bracket`` option: if the - ``bracket`` option is anything but ``None``, it overrides - the value of ``self._repr_option_bracket``. - - See the documentation for :class:`CombinatorialFreeModule` for - details on the initialization options. - - .. todo:: rename to ``_repr_monomial`` - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) - sage: e = F.basis() - sage: e['a'] + 2*e['b'] # indirect doctest - B['a'] + 2*B['b'] - - sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c'], prefix="F") - sage: e = F.basis() - sage: e['a'] + 2*e['b'] # indirect doctest - F['a'] + 2*F['b'] - - sage: QS3 = CombinatorialFreeModule(QQ, Permutations(3), prefix="") - sage: original_print_options = QS3.print_options() - sage: a = 2*QS3([1,2,3])+4*QS3([3,2,1]) - sage: a # indirect doctest - 2*[[1, 2, 3]] + 4*[[3, 2, 1]] - - sage: QS3.print_options(bracket = False) - sage: a # indirect doctest - 2*[1, 2, 3] + 4*[3, 2, 1] - - sage: QS3.print_options(prefix='') - sage: a # indirect doctest - 2*[1, 2, 3] + 4*[3, 2, 1] - - sage: QS3.print_options(bracket="|", scalar_mult=" *@* ") - sage: a # indirect doctest - 2 *@* |[1, 2, 3]| + 4 *@* |[3, 2, 1]| - - sage: QS3.print_options(**original_print_options) # reset - - TESTS:: - - sage: F = CombinatorialFreeModule(QQ, [('a', 'b'), ('c','d')]) - sage: e = F.basis() - sage: e[('a','b')] + 2*e[('c','d')] # indirect doctest - B[('a', 'b')] + 2*B[('c', 'd')] - """ - bracket = self._print_options.get('bracket', None) - bracket_d = {"{": "}", "[": "]", "(": ")"} - if bracket is None: - bracket = self._repr_option_bracket - if bracket is True: - left = "[" - right = "]" - elif bracket is False: - left = "" - right = "" - elif isinstance(bracket, (tuple, list)): - left = bracket[0] - right = bracket[1] - elif bracket in bracket_d: - left = bracket - right = bracket_d[bracket] - else: - left = bracket - right = bracket - return self.prefix() + left + repr(m) + right # mind the (m), to accept a tuple for m - - def _ascii_art_term(self, el): - r""" - Return an ascii art representing of the term. - - TESTS:: - - sage: R = NonCommutativeSymmetricFunctions(QQ).R() - sage: ascii_art(R[1,2,2,4]) - R - **** - ** - ** - * - sage: Partitions.global_options(diagram_str="#", convention="french") - sage: ascii_art(R[1,2,2,4]) - R - # - ## - ## - #### - """ - from sage.misc.ascii_art import ascii_art - try: - if el == self.one_basis(): - return AsciiArt(["1"]) - except Exception: - pass - pref = AsciiArt([self.prefix()]) - r = pref * (AsciiArt([" "**Integer(len(pref))]) + ascii_art(el)) - r._baseline = r._h - 1 - return r - - def _latex_term(self, m): - r""" - Returns a string for the LaTeX code for the basis element - indexed by m. - - The output can be customized by setting any of the following - options when initializing the module: - - - prefix - - latex_prefix - - latex_bracket - - (Alternatively, one can use the :meth:`print_options` method - to achieve the same effect.) - - See the documentation for :class:`CombinatorialFreeModule` for - details on the initialization options. - - .. todo:: rename to ``_latex_monomial`` - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) - sage: e = F.basis() - sage: latex(e['a'] + 2*e['b']) # indirect doctest - B_{a} + 2B_{b} - - sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c'], prefix="C") - sage: e = F.basis() - sage: latex(e['a'] + 2*e['b']) # indirect doctest - C_{a} + 2C_{b} - - sage: QS3 = CombinatorialFreeModule(QQ, Permutations(3), prefix="", scalar_mult="*") - sage: original_print_options = QS3.print_options() - sage: a = 2*QS3([1,2,3])+4*QS3([3,2,1]) - sage: latex(a) # indirect doctest - 2[1, 2, 3] + 4[3, 2, 1] - sage: QS3.print_options(latex_bracket=True) - sage: latex(a) # indirect doctest - 2\left[ [1, 2, 3] \right] + 4\left[ [3, 2, 1] \right] - sage: QS3.print_options(latex_bracket="(") - sage: latex(a) # indirect doctest - 2\left( [1, 2, 3] \right) + 4\left( [3, 2, 1] \right) - sage: QS3.print_options(latex_bracket=('\\myleftbracket', '\\myrightbracket')) - sage: latex(a) # indirect doctest - 2\myleftbracket [1, 2, 3] \myrightbracket + 4\myleftbracket [3, 2, 1] \myrightbracket - sage: QS3.print_options(**original_print_options) # reset - - TESTS:: - - sage: F = CombinatorialFreeModule(QQ, [('a', 'b'), (0,1,2)]) - sage: e = F.basis() - sage: latex(e[('a','b')]) # indirect doctest - B_{('a', 'b')} - sage: latex(2*e[(0,1,2)]) # indirect doctest - 2B_{\left(0, 1, 2\right)} - sage: F = CombinatorialFreeModule(QQ, [('a', 'b'), (0,1,2)], prefix="") - sage: e = F.basis() - sage: latex(2*e[(0,1,2)]) # indirect doctest - 2\left(0, 1, 2\right) - """ - from sage.misc.latex import latex - - s = latex(m) - if s.find('\\text{\\textt') != -1: - # m contains "non-LaTeXed" strings, use string representation - s = str(m) - - # dictionary with left-right pairs of "brackets". put pairs - # in here accept \\left and \\right as prefixes. - bracket_d = {"{": "\\}", "[": "]", "(": ")", "\\{": "\\}", - "|": "|", "||": "||"} - bracket = self._print_options.get('latex_bracket', False) - if bracket is True: - left = "\\left[" - right = "\\right]" - elif bracket is False: - left = "" - right = "" - elif isinstance(bracket, (tuple, list)): - left = bracket[0] - right = bracket[1] - elif bracket in bracket_d: - left = bracket - right = bracket_d[bracket] - if left == "{": - left = "\\{" - left = "\\left" + left - right = "\\right" + right - else: - left = bracket - right = bracket - prefix = self._print_options.get('latex_prefix') - if prefix is None: - prefix = self._print_options.get('prefix') - if prefix == "": - return left + s + right - return "%s_{%s}" % (prefix, s) - def __cmp__(self, other): """ EXAMPLES:: @@ -2199,7 +1865,7 @@ def monomial(self): Term map from {'a', 'b', 'c'} to Free module generated by {'a', 'b', 'c'} over Rational Field """ # Should use a real Map, as soon as combinatorial_classes are enumerated sets, and therefore parents - return PoorManMap(self._monomial, domain = self._basis_keys, codomain = self, name = "Term map") + return PoorManMap(self._monomial, domain=self._indices, codomain=self, name="Term map") def _sum_of_monomials(self, indices): """ diff --git a/src/sage/combinat/ncsf_qsym/generic_basis_code.py b/src/sage/combinat/ncsf_qsym/generic_basis_code.py index fbca8418ca6..7427f5257c9 100644 --- a/src/sage/combinat/ncsf_qsym/generic_basis_code.py +++ b/src/sage/combinat/ncsf_qsym/generic_basis_code.py @@ -117,9 +117,9 @@ def __getitem__(self, c, *rest): assert len(rest) == 0 else: if len(rest) > 0 or isinstance(c, (int, Integer)): - c = self._basis_keys([c] + list(rest)) + c = self._indices([c] + list(rest)) else: - c = self._basis_keys(list(c)) + c = self._indices(list(c)) return self.monomial(c) # could go to Algebras(...).Graded().Connected() or Modules(...).Graded().Connected() @@ -297,7 +297,7 @@ def sum_of_partition_rearrangements(self, par): sage: elementary.sum_of_partition_rearrangements(Partition([])) L[] """ - return self.sum_of_monomials( self._basis_keys(comp) for comp in Permutations(par) ) + return self.sum_of_monomials( self._indices(comp) for comp in Permutations(par) ) def _comp_to_par(self, comp): """ diff --git a/src/sage/combinat/ncsf_qsym/ncsf.py b/src/sage/combinat/ncsf_qsym/ncsf.py index 26db35e3107..48eefd3f4ef 100644 --- a/src/sage/combinat/ncsf_qsym/ncsf.py +++ b/src/sage/combinat/ncsf_qsym/ncsf.py @@ -804,7 +804,7 @@ def verschiebung(self, n): # componentwise, then convert back. parent = self.parent() S = parent.realization_of().S() - C = parent._basis_keys + C = parent._indices dct = {C(map(lambda i: i // n, I)): coeff for (I, coeff) in S(self) if all(i % n == 0 for i in I)} return parent(S._from_dict(dct)) @@ -1636,7 +1636,7 @@ def algebra_generators(self): """ from sage.sets.family import Family from sage.sets.positive_integers import PositiveIntegers - return Family(PositiveIntegers(), lambda i: self.monomial(self._basis_keys([i]))) + return Family(PositiveIntegers(), lambda i: self.monomial(self._indices([i]))) def product_on_basis(self, composition1, composition2): """ @@ -1891,7 +1891,7 @@ def coproduct_on_generators(self, i): """ if i < 1: raise ValueError("Not a positive integer: {}".format(i)) - def C(i): return self._basis_keys([i]) if i else self._basis_keys([]) + def C(i): return self._indices([i]) if i else self._indices([]) T = self.tensor_square() return T.sum_of_monomials( (C(j), C(i-j)) for j in range(0,i+1) ) @@ -2144,8 +2144,8 @@ def product_on_basis(self, I, J): elif not J._list: return self.monomial(I) else: - return self.monomial(self._basis_keys(I[:] + J[:])) + \ - self.monomial(self._basis_keys(I[:-1] + [I[-1]+J[0]] + J[1:])) + return self.monomial(self._indices(I[:] + J[:])) + \ + self.monomial(self._indices(I[:-1] + [I[-1]+J[0]] + J[1:])) def antipode_on_basis(self, composition): """ @@ -2339,7 +2339,7 @@ def verschiebung(self, n): True """ parent = self.parent() - C = parent._basis_keys + C = parent._indices def ribbon_mapper(I, coeff): # return \mathbf{V}_n ( coeff * R_I ) as pair # (composition, coefficient) @@ -3002,7 +3002,7 @@ def verschiebung(self, n): True """ parent = self.parent() - C = parent._basis_keys + C = parent._indices return parent.sum_of_terms([(C([i // n for i in I]), coeff * (-1) ** (sum(I) * (n-1) // n)) for (I, coeff) in self @@ -3283,7 +3283,7 @@ def _from_complete_on_generators(self, n): """ # Equation (58) of NCSF I article one = self.base_ring().one() - I = self._basis_keys([n]) + I = self._indices([n]) # TODO: I being trivial, there is no refinement going on here, so # one can probably be a bit more explicit / fast return self.sum_of_terms( ( (J, one/coeff_pi(J,I)) for J in Compositions(n) ), @@ -3676,7 +3676,7 @@ def verschiebung(self, n): True """ parent = self.parent() - C = parent._basis_keys + C = parent._indices return parent.sum_of_terms([(C([i // n for i in I]), coeff * (n ** len(I))) for (I, coeff) in self @@ -3949,7 +3949,7 @@ def verschiebung(self, n): True """ parent = self.parent() - C = parent._basis_keys + C = parent._indices return parent.sum_of_terms([(C([i // n for i in I]), coeff * (n ** len(I))) for (I, coeff) in self @@ -4213,7 +4213,7 @@ def _from_psi_on_basis(self, I): for J in Compositions(I.size()): if I.is_finer(J): len_of_J = len(J) - p = [0] + self._basis_keys(I).refinement_splitting_lengths(J).partial_sums() + p = [0] + self._indices(I).refinement_splitting_lengths(J).partial_sums() sum_of_elements += prod( (len_of_J - k)**(p[k+1]-p[k]) for k in range(len_of_J) ) * M(J) return sum_of_elements diff --git a/src/sage/combinat/ncsf_qsym/qsym.py b/src/sage/combinat/ncsf_qsym/qsym.py index d631c2080f9..37192dfee48 100644 --- a/src/sage/combinat/ncsf_qsym/qsym.py +++ b/src/sage/combinat/ncsf_qsym/qsym.py @@ -1116,7 +1116,7 @@ def frobenius(self, n): # then convert back. parent = self.parent() M = parent.realization_of().M() - C = parent._basis_keys + C = parent._indices dct = {C(map(lambda i: n * i, I)): coeff for (I, coeff) in M(self)} result_in_M_basis = M._from_dict(dct) @@ -1594,8 +1594,8 @@ def coproduct_on_basis(self, compo): sage: M.coproduct_on_basis(Composition([])) M[] # M[] """ - return self.tensor_square().sum_of_monomials((self._basis_keys(compo[:i]), - self._basis_keys(compo[i:])) + return self.tensor_square().sum_of_monomials((self._indices(compo[:i]), + self._indices(compo[i:])) for i in range(0,len(compo)+1)) def lambda_of_monomial(self, I, n): @@ -1703,7 +1703,7 @@ def lambda_of_monomial(self, I, n): QQ_result = QQM.zero() for lam in Partitions(n): coeff = QQ((-1) ** len(lam)) / lam.centralizer_size() - QQ_result += coeff * QQM.prod([QQM(self._basis_keys([k * i for i in I])) + QQ_result += coeff * QQM.prod([QQM(self._indices([k * i for i in I])) for k in lam]) QQ_result *= (-1) ** n # QQ_result is now \lambda^n(M_I) over QQ. @@ -2985,7 +2985,7 @@ def _precompute_cache(self, n, to_self_cache, from_self_cache, transition_matric # Handle the n == 0 case separately if n == 0: - part = self._basis_keys([]) + part = self._indices([]) to_self_cache[ part ] = { part: base_ring(1) } from_self_cache[ part ] = { part: base_ring(1) } transition_matrices[n] = matrix(base_ring, [[1]]) @@ -3013,7 +3013,7 @@ def _precompute_cache(self, n, to_self_cache, from_self_cache, transition_matric # M_coeffs will be M(self[I])._monomial_coefficients M_coeffs = {} - self_I_in_M_basis = M.prod([from_self_gen_function(self._basis_keys(list(J))) + self_I_in_M_basis = M.prod([from_self_gen_function(self._indices(list(J))) for J in Word(I).lyndon_factorization()]) j = 0 @@ -3237,5 +3237,5 @@ def product_on_basis(self, I, J): # either some i satisfies a_i > b_i and (a_j == b_j for all # j < i), or we have n > m and all i <= m satisfy a_i == b_i. new_factors = sorted(I_factors + J_factors, reverse=True) - return self.monomial(self._basis_keys(flatten(new_factors))) + return self.monomial(self._indices(flatten(new_factors))) diff --git a/src/sage/combinat/parking_functions.py b/src/sage/combinat/parking_functions.py index 4f64c89c286..d99efa8ed25 100644 --- a/src/sage/combinat/parking_functions.py +++ b/src/sage/combinat/parking_functions.py @@ -3,31 +3,36 @@ INFORMALLY (reference [Beck]_): -Imagine a one-way cul-de-sac with `n` parking spots. We'll give the first parking spot the -number 1, the next one number 2, etc., down to the last one, number `n`. Initially they're -all free, but there are `n` cars approaching the street, and they'd all like to park -there. To make life interesting, every car has a parking preference, and we record the -preferences in a sequence; For example, if `n = 3`, the sequence `(2, 1, 1)` means that -the first car would like to park at spot number 2, the second car prefers parking spot -number 1, and the last car would also like to part at number 1. The street is very narrow, -so there is no way to back up. Now each car enters the street and approaches its preferred -parking spot; if it is free, it parks there, and if not, it moves down the street to the -first available spot. We call a sequence a parking function (of length `n`) if all cars -end up finding a parking spot. For example, the sequence `(2, 1, 1)` is a parking sequence -(of length 3), whereas the sequence `(2, 3, 2)` is not. +Imagine a one-way cul-de-sac with `n` parking spots. We will give the +first parking spot the number 1, the next one number 2, etc., down to +the last one, number `n`. Initially they are all free, but there are +`n` cars approaching the street, and they would all like to park there. +To make life interesting, every car has a parking preference, and we +record the preferences in a sequence; For example, if `n = 3`, the +sequence `(2, 1, 1)` means that the first car would like to park at +spot number 2, the second car prefers parking spot number 1, and the +last car would also like to part at number 1. The street is very +narrow, so there is no way to back up. Now each car enters the street +and approaches its preferred parking spot; if it is free, it parks +there, and if not, it moves down the street to the first available +spot. We call a sequence a parking function (of length `n`) if all +cars end up finding a parking spot. For example, the sequence `(2, 1, +1)` is a parking sequence (of length 3), whereas the sequence `(2, 3, +2)` is not. FORMALLY: -A parking function of size `n` is a sequence `(a_1, \ldots, a_n)` of positive integers -such that if `b_1 \leq b_2 \leq \cdots \leq b_n` is the increasing rearrangement -of `a_1, \ldots, a_n`, then `b_i \leq i`. +A parking function of size `n` is a sequence `(a_1, \ldots, a_n)` of +positive integers such that if `b_1 \leq b_2 \leq \cdots \leq b_n` is +the increasing rearrangement of `a_1, \ldots, a_n`, then `b_i \leq i`. -A parking function of size `n` is a pair `(L, D)` of two sequences `L` and `D` -where `L` is a permutation and `D` is an area sequence of a Dyck path of size n such -that `D[i] \geq 0`, `D[i+1] \leq D[i]+1` and if `D[i+1] = D[i]+1` then `L[i+1] > L[i]`. +A parking function of size `n` is a pair `(L, D)` of two sequences `L` +and `D` where `L` is a permutation and `D` is an area sequence of a +Dyck path of size n such that `D[i] \geq 0`, `D[i+1] \leq D[i]+1` and +if `D[i+1] = D[i]+1` then `L[i+1] > L[i]`. -The number of parking functions of size `n` is equal to the number of rooted forest -on `n` vertices and is equal to `(n+1)^{n-1}`. +The number of parking functions of size `n` is equal to the number of +rooted forests on `n` vertices and is equal to `(n+1)^{n-1}`. REFERENCES: @@ -62,14 +67,17 @@ from sage.rings.all import QQ from copy import copy from sage.combinat.combinat import (CombinatorialClass, CombinatorialObject, - InfiniteAbstractCombinatorialClass) + InfiniteAbstractCombinatorialClass) from sage.combinat.permutation import Permutation, Permutations from sage.combinat.dyck_word import DyckWord from sage.combinat.combinatorial_map import combinatorial_map +from sage.misc.prandom import randint +from sage.rings.finite_rings.integer_mod_ring import Zmod + def ParkingFunctions(n=None): r""" - Returns the combinatorial class of Parking Functions. + Return the combinatorial class of Parking Functions. A *parking function* of size `n` is a sequence `(a_1, \ldots,a_n)` of positive integers such that if `b_1 \leq b_2 \leq \cdots \leq b_n` is @@ -80,8 +88,8 @@ def ParkingFunctions(n=None): of a Dyck Path of size n such that `D[i] \geq 0`, `D[i+1] \leq D[i]+1` and if `D[i+1] = D[i]+1` then `L[i+1] > L[i]`. - The number of parking functions of size `n` is equal to the number of rooted forest - on `n` vertices and is equal to `(n+1)^{n-1}`. + The number of parking functions of size `n` is equal to the number + of rooted forests on `n` vertices and is equal to `(n+1)^{n-1}`. EXAMPLES: @@ -93,10 +101,8 @@ def ParkingFunctions(n=None): [1, 2, 2], [2, 1, 2], [2, 2, 1], [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] - If no size is specified, then ParkingFunctions returns the combinatorial class of - all parking functions. - - :: + If no size is specified, then ParkingFunctions returns the + combinatorial class of all parking functions. :: sage: PF = ParkingFunctions(); PF Parking functions @@ -149,15 +155,18 @@ def ParkingFunctions(n=None): """ if n is None: return ParkingFunctions_all() - else: - if not isinstance(n, (Integer, int)) or n<0: - raise ValueError("%s is not a non-negative integer." % n) - return ParkingFunctions_n(n) + + if not isinstance(n, (Integer, int)) or n < 0: + raise ValueError("%s is not a non-negative integer." % n) + return ParkingFunctions_n(n) + def is_a(x, n=None): - """ - Checks whether a list is a parking function. If a size `n` is specified, checks if a - list is a parking function of size `n`. + r""" + Check whether a list is a parking function. + + If a size `n` is specified, checks if a list is a parking function + of size `n`. TESTS:: @@ -177,6 +186,7 @@ def is_a(x, n=None): from sage.combinat.non_decreasing_parking_function import is_a return is_a(A, n) + class ParkingFunctions_all(InfiniteAbstractCombinatorialClass): def __init__(self): """ @@ -231,6 +241,7 @@ def _infinite_cclass_slice(self, n): """ return ParkingFunctions_n(n) + class ParkingFunctions_n(CombinatorialClass): r""" The combinatorial class of parking functions of size `n`. @@ -244,8 +255,8 @@ class ParkingFunctions_n(CombinatorialClass): of a Dyck Path of size `n` such that `D[i] \geq 0`, `D[i+1] \leq D[i]+1` and if `D[i+1] = D[i]+1` then `L[i+1] > L[i]`. - The number of parking functions of size `n` is equal to the number of rooted forest - on `n` vertices and is equal to `(n+1)^{n-1}`. + The number of parking functions of size `n` is equal to the number + of rooted forests on `n` vertices and is equal to `(n+1)^{n-1}`. EXAMPLES:: @@ -260,8 +271,8 @@ class ParkingFunctions_n(CombinatorialClass): .. warning:: - The precise order in which the parking function are generated or - listed is not fixed, and may change in the future. + The precise order in which the parking function are generated or + listed is not fixed, and may change in the future. """ def __init__(self, n): """ @@ -280,7 +291,7 @@ def __repr__(self): sage: repr(ParkingFunctions(3)) 'Parking functions of size 3' """ - return "Parking functions of size %s"%(self.n) + return "Parking functions of size %s" % self.n def __contains__(self, x): """ @@ -307,24 +318,25 @@ def __contains__(self, x): def cardinality(self): r""" - Returns the number of parking functions of size ``n``. The cardinality is equal - to `(n+1)^{n-1}`. + Return the number of parking functions of size ``n``. + + The cardinality is equal to `(n+1)^{n-1}`. EXAMPLES:: sage: [ParkingFunctions(i).cardinality() for i in range(6)] [1, 1, 3, 16, 125, 1296] """ - return Integer((self.n+1)**(self.n-1)) + return Integer((self.n + 1) ** (self.n - 1)) def __iter__(self): """ - Returns an iterator for parking functions of size `n`. + Return an iterator for parking functions of size `n`. .. warning:: - The precise order in which the parking function are generated is not fixed, - and may change in the future. + The precise order in which the parking function are + generated is not fixed, and may change in the future. EXAMPLES:: @@ -360,12 +372,14 @@ def iterator_rec(n): sage: [e for e in PF] # indirect doctest [[1, 1], [1, 2], [2, 1]] """ - if n==0: - yield [ ]; return - if n==1: - yield [1]; return - for res1 in iterator_rec(n-1): - for i in range(res1[-1], n+1): + if n == 0: + yield [] + return + if n == 1: + yield [1] + return + for res1 in iterator_rec(n - 1): + for i in range(res1[-1], n + 1): res = copy(res1) res.append(i) yield res @@ -375,9 +389,41 @@ def iterator_rec(n): yield ParkingFunction(list(pi)) return -def ParkingFunction(pf=None, labelling=None, area_sequence=None, labelled_dyck_word = None): + def random_element(self): + r""" + Return a random parking function of size `n`. + + The algorithm uses a circular parking space with `n+1` + spots. Then all `n` cars can park and there remains one empty + spot. Spots are then renumbered so that the empty spot is `0`. + + The probability distribution is uniform on the set of + `(n+1)^{n-1}` parking functions of size `n`. + + EXAMPLES:: + + sage: pf = ParkingFunctions(8) + sage: a = pf.random_element(); a # random + [5, 7, 2, 4, 2, 5, 1, 3] + sage: a in pf + True + """ + n = self.n + Zm = Zmod(n + 1) + fun = [Zm(randint(0, n)) for i in range(n)] + free = [Zm(j) for j in range(n + 1)] + for car in fun: + position = car + while not(position in free): + position += Zm.one() + free.remove(position) + return ParkingFunction([(i - free[0]).lift() for i in fun]) + + +def ParkingFunction(pf=None, labelling=None, area_sequence=None, + labelled_dyck_word=None): r""" - Returns the combinatorial class of Parking Functions. + Return the combinatorial class of Parking Functions. A *parking function* of size `n` is a sequence `(a_1, \ldots,a_n)` of positive integers such that if `b_1 \leq b_2 \leq \cdots \leq b_n` is @@ -388,8 +434,8 @@ def ParkingFunction(pf=None, labelling=None, area_sequence=None, labelled_dyck_w of a Dyck Path of size `n` such that `D[i] \geq 0`, `D[i+1] \leq D[i]+1` and if `D[i+1] = D[i]+1` then `L[i+1] > L[i]`. - The number of parking functions of size `n` is equal to the number of rooted forests - on `n` vertices and is equal to `(n+1)^{n-1}`. + The number of parking functions of size `n` is equal to the number + of rooted forests on `n` vertices and is equal to `(n+1)^{n-1}`. INPUT: @@ -439,18 +485,20 @@ def ParkingFunction(pf=None, labelling=None, area_sequence=None, labelled_dyck_w elif labelling is not None: if (area_sequence is None): raise ValueError("must also provide area sequence along with labelling.") - if (len(area_sequence)!=len(labelling)): - raise ValueError("%s must be the same size as the labelling %s"%(area_sequence,labelling)) - if any(area_sequence[i]labelling[i+1] for i in range(len(labelling)-1)): - raise ValueError("%s is not a valid labeling of area sequence %s"%(labelling, area_sequence)) - return from_labelling_and_area_sequence( labelling, area_sequence ) + if (len(area_sequence) != len(labelling)): + raise ValueError("%s must be the same size as the labelling %s" % (area_sequence, labelling)) + if any(area_sequence[i] < area_sequence[i+1] and labelling[i] > labelling[i + 1] for i in range(len(labelling) - 1)): + raise ValueError("%s is not a valid labeling of area sequence %s" % (labelling, area_sequence)) + return from_labelling_and_area_sequence(labelling, area_sequence) elif labelled_dyck_word is not None: return from_labelled_dyck_word(labelled_dyck_word) elif area_sequence is not None: DW = DyckWord(area_sequence) - return ParkingFunction(labelling=range(1,DW.size()+1), area_sequence=DW) - else: - raise ValueError("did not manage to make this into a parking function") + return ParkingFunction(labelling=range(1, DW.size() + 1), + area_sequence=DW) + + raise ValueError("did not manage to make this into a parking function") + class ParkingFunction_class(CombinatorialObject): def __init__(self, lst): @@ -466,12 +514,13 @@ def __init__(self, lst): def __getitem__(self, n): """ - Returns the `n^{th}` item in the underlying list. + Return the `n^{th}` item in the underlying list. .. NOTE:: - Note that this is different than the image of ``n`` under function. It is - "off by one" in that it agrees with sage indexing starting at 0. + Note that this is different than the image of ``n`` under + function. It is "off by one" in that it agrees with sage + indexing starting at 0. EXAMPLES:: @@ -485,7 +534,7 @@ def __getitem__(self, n): def __call__(self, n): """ - Returns the image of ``n`` under the parking function. + Return the image of ``n`` under the parking function. EXAMPLES:: @@ -499,7 +548,7 @@ def __call__(self, n): def diagonal_reading_word(self): r""" - Returns a diagonal word of the labelled Dyck path corresponding to parking + Return a diagonal word of the labelled Dyck path corresponding to parking function (see [Hag08]_ p. 75). INPUT: @@ -538,18 +587,20 @@ def diagonal_reading_word(self): L = self.to_labelling_permutation() D = self.to_area_sequence() m = max(D) - return Permutation([L[-j-1] for i in range(m+1) for j in range(len(L)) if D[-j-1]==m-i]) + return Permutation([L[-j - 1] for i in range(m + 1) + for j in range(len(L)) if D[-j - 1] == m - i]) diagonal_word = diagonal_reading_word def parking_permutation(self): # indices are cars, entries are parking spaces r""" - Returns the sequence of parking spots that are taken by cars 1 through `n` - and corresponding to the parking function. - For example, ``parking_permutation(PF) = [6, 1, 5, 2, 3, 4, 7]`` - means that spot 6 is taken by car 1, spot 1 by car 2, spot 5 by car 3, spot 2 is - taken by car 4, spot 3 is taken by car 5, spot 4 is taken by car 6 and spot 7 - is taken by car 7. + Return the sequence of parking spots that are taken by cars 1 + through `n` and corresponding to the parking function. + + For example, ``parking_permutation(PF) = [6, 1, 5, 2, 3, 4, + 7]`` means that spot 6 is taken by car 1, spot 1 by car 2, + spot 5 by car 3, spot 2 is taken by car 4, spot 3 is taken by + car 5, spot 4 is taken by car 6 and spot 7 is taken by car 7. INPUT: @@ -557,8 +608,9 @@ def parking_permutation(self): # indices are cars, entries are parking space OUTPUT: - - returns the permutation of parking spots that corresponds to the parking - function and which is the same size as parking function + - the permutation of parking spots that corresponds to + the parking function and which is the same size as parking + function EXAMPLES:: @@ -580,8 +632,9 @@ def parking_permutation(self): # indices are cars, entries are parking space @combinatorial_map(name='to car permutation') def cars_permutation(self): # indices are parking spaces, entries are car labels r""" - Returns the sequence of cars that take parking spots 1 through `n` + Return the sequence of cars that take parking spots 1 through `n` and corresponding to the parking function. + For example, ``cars_permutation(PF) = [2, 4, 5, 6, 3, 1, 7]`` means that car 2 takes spots 1, car 4 takes spot 2, ..., car 1 takes spot 6 and car 7 takes spot 7. @@ -592,7 +645,7 @@ def cars_permutation(self): # indices are parking spaces, entries are car la OUTPUT: - - returns the permutation of cars corresponding to the parking function + - the permutation of cars corresponding to the parking function and which is the same size as parking function EXAMPLES:: @@ -612,15 +665,16 @@ def cars_permutation(self): # indices are parking spaces, entries are car la """ out = {} for i in range(len(self)): - j=0 - while self[i]+j in out.keys(): - j+=1 - out[self[i]+j] = i - return Permutation([out[i+1]+1 for i in range(len(self))]) + j = 0 + while self[i] + j in out.keys(): + j += 1 + out[self[i] + j] = i + return Permutation([out[i + 1] + 1 for i in range(len(self))]) def jump_list(self): # cars displacements r""" - Returns the displacements of cars that corresponds to the parking function. + Return the displacements of cars that corresponds to the parking function. + For example, ``jump_list(PF) = [0, 0, 0, 0, 1, 3, 2]`` means that car 1 through 4 parked in their preferred spots, car 5 had to park one spot farther (jumped or was displaced by one spot), @@ -632,7 +686,7 @@ def jump_list(self): # cars displacements OUTPUT: - - returns the displacements sequence of parked cars which corresponds + - the displacements sequence of parked cars which corresponds to the parking function and which is the same size as parking function EXAMPLES:: @@ -656,10 +710,12 @@ def jump_list(self): # cars displacements out.append(pi[i] - self[i]) return out - def jump(self): #sum of all jumps, sum of all dispalcements + def jump(self): # sum of all jumps, sum of all displacements r""" - Returns the sum of the differences between the parked and preferred parking spots - (see [Shin]_ p. 18). + Return the sum of the differences between the parked and + preferred parking spots. + + See [Shin]_ p. 18. INPUT: @@ -667,7 +723,7 @@ def jump(self): #sum of all jumps, sum of all dispalcements OUTPUT: - - returns the sum of the differences between the parked and preferred parking + - the sum of the differences between the parked and preferred parking spots EXAMPLES:: @@ -689,7 +745,7 @@ def jump(self): #sum of all jumps, sum of all dispalcements def lucky_cars(self): # the set of cars that can park in their preferred spots r""" - Returns the cars that can park in their preferred spots. For example, + Return the cars that can park in their preferred spots. For example, ``lucky_cars(PF) = [1, 2, 7]`` means that cars 1, 2 and 7 parked in their preferred spots and all the other cars did not. @@ -699,7 +755,7 @@ def lucky_cars(self): # the set of cars that can park in their preferred spo OUTPUT: - - returns the cars that can park in their preferred spots + - the cars that can park in their preferred spots EXAMPLES:: @@ -717,12 +773,11 @@ def lucky_cars(self): # the set of cars that can park in their preferred spo [1, 2, 3] """ w = self.jump_list() - return [i+1 for i in range(len(w)) if w[i]==0] - + return [i + 1 for i in range(len(w)) if w[i] == 0] def luck(self): # the number of lucky cars r""" - Returns the number of cars that parked in their preferred parking spots + Return the number of cars that parked in their preferred parking spots (see [Shin]_ p. 33). INPUT: @@ -731,7 +786,7 @@ def luck(self): # the number of lucky cars OUTPUT: - - returns the number of cars that parked in their preferred parking spots + - the number of cars that parked in their preferred parking spots EXAMPLES:: @@ -752,7 +807,7 @@ def luck(self): # the number of lucky cars def primary_dinversion_pairs(self): r""" - Returns the primary descent inversion pairs of a labelled Dyck path corresponding + Return the primary descent inversion pairs of a labelled Dyck path corresponding to the parking function. INPUT: @@ -761,7 +816,7 @@ def primary_dinversion_pairs(self): OUTPUT: - - returns the pairs `(i, j)` such that `i < j`, and `i^{th}` area = `j^{th}` area, + - the pairs `(i, j)` such that `i < j`, and `i^{th}` area = `j^{th}` area, and `i^{th}` label < `j^{th}` label EXAMPLES:: @@ -781,11 +836,12 @@ def primary_dinversion_pairs(self): """ L = self.to_labelling_permutation() D = self.to_area_sequence() - return [(i,j) for j in range(len(D)) for i in range(j) if D[i] == D[j] and L[i] < L[j]] + return [(i, j) for j in range(len(D)) for i in range(j) + if D[i] == D[j] and L[i] < L[j]] def secondary_dinversion_pairs(self): r""" - Returns the secondary descent inversion pairs of a labelled Dyck path + Return the secondary descent inversion pairs of a labelled Dyck path corresponding to the parking function. INPUT: @@ -794,7 +850,7 @@ def secondary_dinversion_pairs(self): OUTPUT: - - returns the pairs `(i, j)` such that `i < j`, and `i^{th}` area = `j^{th}` area +1, + - the pairs `(i, j)` such that `i < j`, and `i^{th}` area = `j^{th}` area +1, and `i^{th}` label > `j^{th}` label EXAMPLES:: @@ -814,12 +870,13 @@ def secondary_dinversion_pairs(self): """ L = self.to_labelling_permutation() D = self.to_area_sequence() - return [(i,j) for j in range(len(D)) for i in range(j) if D[i] == D[j] + 1 and L[i] > L[j]] + return [(i, j) for j in range(len(D)) for i in range(j) + if D[i] == D[j] + 1 and L[i] > L[j]] def dinversion_pairs(self): r""" - Returns the descent inversion pairs of a labelled Dyck path corresponding - to the parking function. + Return the descent inversion pairs of a labelled Dyck path + corresponding to the parking function. INPUT: @@ -827,7 +884,7 @@ def dinversion_pairs(self): OUTPUT: - - returns the primary and secondary diversion pairs + - the primary and secondary diversion pairs EXAMPLES:: @@ -848,9 +905,10 @@ def dinversion_pairs(self): def dinv(self): r""" - Returns the number of inversions of a labelled Dyck path corresponding - to the parking function (see [Hag08]_ p. 74). Same as the cardinality of - :meth:`dinversion_pairs`. + Return the number of inversions of a labelled Dyck path corresponding + to the parking function (see [Hag08]_ p. 74). + + Same as the cardinality of :meth:`dinversion_pairs`. INPUT: @@ -858,7 +916,7 @@ def dinv(self): OUTPUT: - - returns the number of dinversion pairs + - the number of dinversion pairs EXAMPLES:: @@ -879,7 +937,8 @@ def dinv(self): def area(self): r""" - Returns the area of the labelled Dyck path corresponding to the parking function. + Return the area of the labelled Dyck path corresponding to the + parking function. INPUT: @@ -887,7 +946,7 @@ def area(self): OUTPUT: - - returns the sum of squares under and over the main diagonal the Dyck Path, + - the sum of squares under and over the main diagonal the Dyck Path, corresponding to the parking function EXAMPLES:: @@ -910,12 +969,15 @@ def area(self): @combinatorial_map(name='to ides composition') def ides_composition(self): r""" - Return the :meth:`~sage.combinat.permutation.Permutation.descents_composition` - of the inverse of the :meth:`diagonal_reading_word` of corresponding parking - function. For example, ``ides_composition(PF) = [4, 2, 1]`` - means that the descents of the inverse of the permutation - :meth:`diagonal_reading_word` of the parking function with word ``PF`` are at - the 4th and 6th positions. + Return the + :meth:`~sage.combinat.permutation.Permutation.descents_composition` + of the inverse of the :meth:`diagonal_reading_word` of + corresponding parking function. + + For example, ``ides_composition(PF) = [4, 2, 1]`` means that + the descents of the inverse of the permutation + :meth:`diagonal_reading_word` of the parking function with + word ``PF`` are at the 4th and 6th positions. INPUT: @@ -947,6 +1009,7 @@ def ides(self): r""" Return the :meth:`~sage.combinat.permutation.Permutation.descents` sequence of the inverse of the :meth:`diagonal_reading_word` of ``self``. + For example, ``ides(PF) = [1, 2, 3, 5]`` means that descents are at the 2nd, 3rd, 4th and 6th positions in the inverse of the :meth:`diagonal_reading_word` of the parking function (see [GXZ]_ p. 2). @@ -979,10 +1042,12 @@ def ides(self): def touch_points(self): r""" - Returns the sequence of touch points which corresponds to the labelled Dyck path - after initial step. For example, ``touch_points(PF) = [4, 7]`` means that after - the initial step, the path touches the main diagonal at points `(4, 4)` and - `(7, 7)`. + Return the sequence of touch points which corresponds to the labelled Dyck path + after initial step. + + For example, ``touch_points(PF) = [4, 7]`` means that after + the initial step, the path touches the main diagonal at points + `(4, 4)` and `(7, 7)`. INPUT: @@ -990,7 +1055,7 @@ def touch_points(self): OUTPUT: - - returns the sequence of touch points after the initial step of the + - the sequence of touch points after the initial step of the labelled Dyck path that corresponds to the parking function EXAMPLES:: @@ -1010,13 +1075,15 @@ def touch_points(self): """ return self.to_dyck_word().touch_points() - @combinatorial_map(name = 'to touch composition') + @combinatorial_map(name='to touch composition') def touch_composition(self): r""" - Returns the composition of the labelled Dyck path corresponding to the - parking function. For example, ``touch_composition(PF) = [4, 3]`` - means that the first touch is four diagonal units from the starting point, and - the second is three units further (see [GXZ]_ p. 2). + Return the composition of the labelled Dyck path corresponding + to the parking function. + + For example, ``touch_composition(PF) = [4, 3]`` means that the + first touch is four diagonal units from the starting point, + and the second is three units further (see [GXZ]_ p. 2). INPUT: @@ -1024,7 +1091,7 @@ def touch_composition(self): OUTPUT: - - returns the length between the corresponding touch points which + - the length between the corresponding touch points which of the labelled Dyck path that corresponds to the parking function EXAMPLES:: @@ -1046,10 +1113,10 @@ def touch_composition(self): diagonal_composition = touch_composition - @combinatorial_map(name = 'to labelling permutation') + @combinatorial_map(name='to labelling permutation') def to_labelling_permutation(self): r""" - Returns the labelling of the support Dyck path of the parking function. + Return the labelling of the support Dyck path of the parking function. INPUT: @@ -1057,7 +1124,7 @@ def to_labelling_permutation(self): OUTPUT: - - returns the labelling of the Dyck path + - the labelling of the Dyck path EXAMPLES:: @@ -1079,7 +1146,8 @@ def to_labelling_permutation(self): def to_area_sequence(self): r""" - Returns the area sequence of the support Dyck path of the parking function. + Return the area sequence of the support Dyck path of the + parking function. INPUT: @@ -1087,7 +1155,7 @@ def to_area_sequence(self): OUTPUT: - - returns area sequence of the Dyck path + - the area sequence of the Dyck path EXAMPLES:: @@ -1109,8 +1177,9 @@ def to_area_sequence(self): def to_labelling_area_sequence_pair(self): r""" - Returns a pair consisting of a labelling and an area sequence of a Dyck path - which corresponds to the given parking function. + Return a pair consisting of a labelling and an area sequence + of a Dyck path which corresponds to the given parking + function. INPUT: @@ -1143,7 +1212,7 @@ def to_labelling_area_sequence_pair(self): @combinatorial_map(name='to dyck word') def to_dyck_word(self): r""" - Returns the support Dyck word of the parking function. + Return the support Dyck word of the parking function. INPUT: @@ -1151,7 +1220,7 @@ def to_dyck_word(self): OUTPUT: - - returns the Dyck word of the corresponding parking function + - the Dyck word of the corresponding parking function .. SEEALSO:: :meth:`DyckWord` @@ -1174,9 +1243,11 @@ def to_dyck_word(self): def to_labelled_dyck_word(self): r""" - Returns the labelled Dyck word corresponding to the parking function. - This is a representation of the parking function as a list where the entries of - 1 in the Dyck word are replaced with the corresponding label. + Return the labelled Dyck word corresponding to the parking function. + + This is a representation of the parking function as a list + where the entries of 1 in the Dyck word are replaced with the + corresponding label. INPUT: @@ -1184,7 +1255,7 @@ def to_labelled_dyck_word(self): OUTPUT: - - returns the labelled Dyck word of the corresponding parking function + - the labelled Dyck word of the corresponding parking function which is twice the size of parking function word EXAMPLES:: @@ -1204,15 +1275,15 @@ def to_labelled_dyck_word(self): """ dw = self.to_dyck_word() out = list(copy(self.to_labelling_permutation())) - for i in range(2*len(out)): + for i in range(2 * len(out)): if dw[i] == 0: out.insert(i, 0) return out def to_labelling_dyck_word_pair(self): r""" - Returns the pair ``(L, D)`` where ``L`` is a labelling and ``D`` is the Dyck - word of the parking function. + Return the pair ``(L, D)`` where ``L`` is a labelling and + ``D`` is the Dyck word of the parking function. INPUT: @@ -1220,7 +1291,7 @@ def to_labelling_dyck_word_pair(self): OUTPUT: - - returns the pair ``(L, D)``, where ``L`` is the labelling and ``D`` is + - the pair ``(L, D)``, where ``L`` is the labelling and ``D`` is the Dyck word of the parking function .. SEEALSO:: :meth:`DyckWord` @@ -1242,11 +1313,11 @@ def to_labelling_dyck_word_pair(self): """ return (self.to_labelling_permutation(), self.to_dyck_word()) - @combinatorial_map(name = 'to non-decreasing parking function') + @combinatorial_map(name='to non-decreasing parking function') def to_NonDecreasingParkingFunction(self): r""" - Returns the non-decreasing parking function which underlies the parking - function. + Return the non-decreasing parking function which underlies the + parking function. INPUT: @@ -1254,7 +1325,7 @@ def to_NonDecreasingParkingFunction(self): OUTPUT: - - returns a sorted parking function + - a sorted parking function .. SEEALSO:: :meth:`NonDecreasingParkingFunction` @@ -1277,19 +1348,24 @@ def to_NonDecreasingParkingFunction(self): """ return ParkingFunction(sorted(self)) - def characteristic_quasisymmetric_function(self, q=None, R=QQ['q','t'].fraction_field()): + def characteristic_quasisymmetric_function(self, q=None, + R=QQ['q', 't'].fraction_field()): r""" - The characteristic function of the Parking Function is the sum over all permutation - labelling of the Dyck path `q^{dinv(PF)} F_{ides(PF)}` where `ides(PF)` is - :meth:`ides_composition` is the descent composition of diagonal reading word of - the parking function. + Return the characteristic quasisymmetric function of ``self``. + + The characteristic function of the Parking Function is the sum + over all permutation labellings of the Dyck path `q^{dinv(PF)} + F_{ides(PF)}` where `ides(PF)` (:meth:`ides_composition`) is + the descent composition of diagonal reading word of the + parking function. INPUT: - - ``q`` -- (default: ``q = R('q')``) a parameter for the generating function power + - ``q`` -- (default: ``q = R('q')``) a parameter for the + generating function power - - ``R`` -- (default: ``R = QQ['q','t'].fraction_field()``) the base ring to do - the calculations over + - ``R`` -- (default: ``R = QQ['q','t'].fraction_field()``) the + base ring to do the calculations over OUTPUT: @@ -1297,7 +1373,7 @@ def characteristic_quasisymmetric_function(self, q=None, R=QQ['q','t'].fraction_ EXAMPLES:: - sage: R=QQ['q','t'].fraction_field() + sage: R = QQ['q','t'].fraction_field() sage: (q,t) = R.gens() sage: cqf = sum(t**PF.area()*PF.characteristic_quasisymmetric_function() for PF in ParkingFunctions(3)); cqf (q^3+q^2*t+q*t^2+t^3+q*t)*F[1, 1, 1] + (q^2+q*t+t^2+q+t)*F[1, 2] + (q^2+q*t+t^2+q+t)*F[2, 1] + F[3] @@ -1318,23 +1394,25 @@ def characteristic_quasisymmetric_function(self, q=None, R=QQ['q','t'].fraction_ """ from sage.combinat.ncsf_qsym.qsym import QuasiSymmetricFunctions if q is None: - q=R('q') + q = R('q') else: if not q in R: - raise ValueError("q=%s must be an element of the base ring %s"%(q,R)) + raise ValueError("q=%s must be an element of the base ring %s" % (q, R)) F = QuasiSymmetricFunctions(R).Fundamental() - return q**self.dinv()*F(self.ides_composition()) + return q ** self.dinv() * F(self.ides_composition()) def pretty_print(self, underpath=True): r""" - Displays a parking function as a lattice path consisting of a Dyck path - and a labelling with the labels displayed along the edges of the Dyck path. + Displays a parking function as a lattice path consisting of a + Dyck path and a labelling with the labels displayed along the + edges of the Dyck path. INPUT: - - ``underpath`` - if the length of the parking function is less than or - equal to 9 then display the labels under the path if ``underpath`` is True - otherwise display them to the right of the path (default: True) + - ``underpath`` -- if the length of the parking function is + less than or equal to 9 then display the labels under the + path if ``underpath`` is True otherwise display them to the + right of the path (default: ``True``) EXAMPLES:: @@ -1414,16 +1492,19 @@ def pretty_print(self, underpath=True): L = self.to_labelling_permutation() dw = self.to_dyck_word() if len(L) <= 9: - dw.pretty_print(labelling=L, underpath = underpath) + dw.pretty_print(labelling=L, underpath=underpath) else: - dw.pretty_print(labelling=L, underpath = False) + dw.pretty_print(labelling=L, underpath=False) #****************************************************************************** # CONSTRUCTIONS #****************************************************************************** + + def from_labelling_and_area_sequence(L, D): r""" - Returns the parking function corresponding to the labelling area sequence pair. + Return the parking function corresponding to the labelling area + sequence pair. INPUT: @@ -1433,7 +1514,7 @@ def from_labelling_and_area_sequence(L, D): OUTPUT: - - returns the parking function corresponding the labelling permutation ``L`` + - the parking function corresponding the labelling permutation ``L`` and ``D`` an area sequence of the corresponding Dyck path EXAMPLES:: @@ -1453,11 +1534,13 @@ def from_labelling_and_area_sequence(L, D): sage: from_labelling_and_area_sequence([1, 2, 4, 3], [0, 1, 2, 1]) [1, 1, 3, 1] """ - return ParkingFunction_class([L.index(i)+1-D[L.index(i)] for i in range(1,len(L)+1)]) + return ParkingFunction_class([L.index(i) + 1 - D[L.index(i)] + for i in range(1, len(L) + 1)]) + def from_labelled_dyck_word(LDW): r""" - Returns the parking function corresponding to the labelled Dyck word. + Return the parking function corresponding to the labelled Dyck word. INPUT: @@ -1465,8 +1548,8 @@ def from_labelled_dyck_word(LDW): OUTPUT: - - returns the parking function corresponding to the labelled Dyck word that is - half the size of ``LDW`` + - the parking function corresponding to the labelled Dyck + word that is half the size of ``LDW`` EXAMPLES:: @@ -1484,6 +1567,6 @@ def from_labelled_dyck_word(LDW): sage: from_labelled_dyck_word([2, 4, 0, 1, 0, 0, 3, 0]) [2, 1, 4, 1] """ - L = [ell for ell in LDW if ell!=0] + L = [ell for ell in LDW if ell != 0] D = DyckWord(map(lambda x: Integer(not x.is_zero()), LDW)) return from_labelling_and_area_sequence(L, D.to_area_sequence()) diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 745e93b975f..72e9dbf9871 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -419,7 +419,7 @@ def from_frobenius_coordinates(coords): EXAMPLES:: sage: sage.combinat.partition.from_frobenius_coordinates(([],[])) - doctest:1: DeprecationWarning: from_frobenius_coordinates is deprecated. Use Partitions().from_frobenius_coordinates instead. + doctest:...: DeprecationWarning: from_frobenius_coordinates is deprecated. Use Partitions().from_frobenius_coordinates instead. See http://trac.sagemath.org/13605 for details. [] """ @@ -435,7 +435,7 @@ def from_beta_numbers(beta): EXAMPLES:: sage: sage.combinat.partition.from_beta_numbers([0,1,2,4,5,8]) - doctest:1: DeprecationWarning: from_beta_numbers is deprecated. Use Partitions().from_beta_numbers instead. + doctest:...: DeprecationWarning: from_beta_numbers is deprecated. Use Partitions().from_beta_numbers instead. See http://trac.sagemath.org/13605 for details. [3, 1, 1] """ @@ -451,7 +451,7 @@ def from_exp(exp): EXAMPLES:: sage: sage.combinat.partition.from_exp([2,2,1]) - doctest:1: DeprecationWarning: from_exp is deprecated. Use Partitions().from_exp instead. + doctest:...: DeprecationWarning: from_exp is deprecated. Use Partitions().from_exp instead. See http://trac.sagemath.org/13605 for details. [3, 2, 2, 1, 1] """ @@ -467,7 +467,7 @@ def from_core_and_quotient(core, quotient): EXAMPLES:: sage: sage.combinat.partition.from_core_and_quotient([2,1], [[2,1],[3],[1,1,1]]) - doctest:1: DeprecationWarning: from_core_and_quotient is deprecated. Use Partitions().from_core_and_quotient instead. + doctest:...: DeprecationWarning: from_core_and_quotient is deprecated. Use Partitions().from_core_and_quotient instead. See http://trac.sagemath.org/13605 for details. [11, 5, 5, 3, 2, 2, 2] """ @@ -2746,7 +2746,7 @@ def dominate(self, rows=None): EXAMPLES:: sage: Partition([3,2,1]).dominate() - doctest:1: DeprecationWarning: dominate is deprecated. Use dominated_partitions instead. + doctest:...: DeprecationWarning: dominate is deprecated. Use dominated_partitions instead. See http://trac.sagemath.org/13605 for details. [[3, 2, 1], [3, 1, 1, 1], [2, 2, 2], [2, 2, 1, 1], [2, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1]] """ @@ -6466,7 +6466,7 @@ def PartitionsInBox_hw(h, w): EXAMPLES:: sage: sage.combinat.partition.PartitionsInBox_hw(3, 2) - doctest:1: DeprecationWarning: this class is deprecated. Use PartitionsInBox instead + doctest:...: DeprecationWarning: this class is deprecated. Use PartitionsInBox instead See http://trac.sagemath.org/13605 for details. Integer partitions which fit in a 3 x 2 box """ @@ -6652,7 +6652,7 @@ def OrderedPartitions_nk(n, k): EXAMPLES:: sage: sage.combinat.partition.OrderedPartitions_nk(3, 2) - doctest:1: DeprecationWarning: this class is deprecated. Use OrderedPartitions instead + doctest:...: DeprecationWarning: this class is deprecated. Use OrderedPartitions instead See http://trac.sagemath.org/13605 for details. Ordered partitions of 3 of length 2 """ @@ -6726,7 +6726,7 @@ def PartitionsGreatestLE_nk(n, k): EXAMPLES:: sage: sage.combinat.partition.PartitionsGreatestLE_nk(10, 2) - doctest:1: DeprecationWarning: this class is deprecated. Use PartitionsGreatestLE instead + doctest:...: DeprecationWarning: this class is deprecated. Use PartitionsGreatestLE instead See http://trac.sagemath.org/13605 for details. Partitions of 10 having parts less than or equal to 2 """ @@ -6798,7 +6798,7 @@ def PartitionsGreatestEQ_nk(n, k): EXAMPLES:: sage: sage.combinat.partition.PartitionsGreatestEQ_nk(10, 2) - doctest:1: DeprecationWarning: this class is deprecated. Use PartitionsGreatestEQ instead + doctest:...: DeprecationWarning: this class is deprecated. Use PartitionsGreatestEQ instead See http://trac.sagemath.org/13605 for details. Partitions of 10 having greatest part equal to 2 """ @@ -6839,7 +6839,7 @@ def RestrictedPartitions(n, S, k=None): EXAMPLES:: sage: RestrictedPartitions(5,[3,2,1]) - doctest:1: DeprecationWarning: RestrictedPartitions is deprecated; use Partitions with the parts_in keyword instead. + doctest:...: DeprecationWarning: RestrictedPartitions is deprecated; use Partitions with the parts_in keyword instead. See http://trac.sagemath.org/13072 for details. doctest:...: DeprecationWarning: RestrictedPartitions_nsk is deprecated; use Partitions with the parts_in keyword instead. See http://trac.sagemath.org/13072 for details. @@ -6867,7 +6867,7 @@ def __init__(self, n, S, k=None): TESTS:: sage: r = RestrictedPartitions(5,[3,2,1]) - doctest:1: DeprecationWarning: RestrictedPartitions is deprecated; use Partitions with the parts_in keyword instead. + doctest:...: DeprecationWarning: RestrictedPartitions is deprecated; use Partitions with the parts_in keyword instead. See http://trac.sagemath.org/13072 for details. sage: r == loads(dumps(r)) True @@ -6909,7 +6909,7 @@ def _repr_(self): EXAMPLES:: sage: RestrictedPartitions(5,[3,2,1]).__repr__() - doctest:1: DeprecationWarning: RestrictedPartitions is deprecated; use Partitions with the parts_in keyword instead. + doctest:...: DeprecationWarning: RestrictedPartitions is deprecated; use Partitions with the parts_in keyword instead. See http://trac.sagemath.org/13072 for details. 'Partitions of 5 restricted to the values [1, 2, 3]' """ diff --git a/src/sage/combinat/partition_algebra.py b/src/sage/combinat/partition_algebra.py index c5826c8baa1..fbd5156efca 100644 --- a/src/sage/combinat/partition_algebra.py +++ b/src/sage/combinat/partition_algebra.py @@ -1415,7 +1415,7 @@ def __init__(self, R, cclass, n, k, name=None, prefix=None): """ self.k = k self.n = n - self._basis_keys = cclass + self._indices = cclass self._name = "Generic partition algebra with k = %s and n = %s and basis %s"%( self.k, self.n, cclass) if name is None else name self._one = identity(ceil(self.k)) self._prefix = "" if prefix is None else prefix diff --git a/src/sage/combinat/root_system/cartan_matrix.py b/src/sage/combinat/root_system/cartan_matrix.py index 11b8dbd650b..2bb763642ec 100644 --- a/src/sage/combinat/root_system/cartan_matrix.py +++ b/src/sage/combinat/root_system/cartan_matrix.py @@ -735,7 +735,7 @@ def cartan_matrix(t): EXAMPLES:: sage: cartan_matrix(['A', 4]) - doctest:1: DeprecationWarning: cartan_matrix() is deprecated. Use CartanMatrix() instead + doctest:...: DeprecationWarning: cartan_matrix() is deprecated. Use CartanMatrix() instead See http://trac.sagemath.org/14137 for details. [ 2 -1 0 0] [-1 2 -1 0] diff --git a/src/sage/combinat/root_system/root_lattice_realizations.py b/src/sage/combinat/root_system/root_lattice_realizations.py index 37aa8e2eb76..d463b76de21 100644 --- a/src/sage/combinat/root_system/root_lattice_realizations.py +++ b/src/sage/combinat/root_system/root_lattice_realizations.py @@ -1051,6 +1051,103 @@ def root_poset(self, restricted=False, facade=False): rels.append((root,root_cover)) return Poset((pos_roots,rels),cover_relations=True,facade=facade) + def nonnesting_partition_lattice(self, facade=False): + r""" + Return the lattice of nonnesting partitions + + This is the lattice of order ideals of the root poset. + + This has been defined by Postnikov, see Remark 2 in [Reiner97]_. + + .. SEEALSO:: + + :meth:`generalized_nonnesting_partition_lattice`, :meth:`root_poset` + + EXAMPLES:: + + sage: R = RootSystem(['A', 3]) + sage: RS = R.root_lattice() + sage: P = RS.nonnesting_partition_lattice(); P + Finite lattice containing 14 elements + sage: P.coxeter_transformation()**10 == 1 + True + + sage: R = RootSystem(['B', 3]) + sage: RS = R.root_lattice() + sage: P = RS.nonnesting_partition_lattice(); P + Finite lattice containing 20 elements + sage: P.coxeter_transformation()**7 == 1 + True + + REFERENCES: + + .. [Reiner97] Victor Reiner. *Non-crossing partitions for + classical reflection groups*. Discrete Mathematics 177 (1997) + .. [Arm06] Drew Armstrong. *Generalized Noncrossing Partitions and + Combinatorics of Coxeter Groups*. :arxiv:`math/0611106` + """ + return self.root_poset(facade=facade).order_ideals_lattice(facade=facade) + + def generalized_nonnesting_partition_lattice(self, m, facade=False): + r""" + Return the lattice of `m`-nonnesting partitions + + This has been defined by Athanasiadis, see chapter 5 of [Arm06]_. + + INPUT: + + - `m` -- integer + + .. SEEALSO:: + + :meth:`nonnesting_partition_lattice` + + EXAMPLES:: + + sage: R = RootSystem(['A', 2]) + sage: RS = R.root_lattice() + sage: P = RS.generalized_nonnesting_partition_lattice(2); P + Finite lattice containing 12 elements + sage: P.coxeter_transformation()**20 == 1 + True + """ + from sage.combinat.multichoose_nk import MultichooseNK + Phi_plus = self.positive_roots() + L = self.nonnesting_partition_lattice(facade=True) + chains = [chain for chain in L.chains().list() if len(chain) <= m] + multichains = [] + for chain in chains: + for multilist in MultichooseNK(len(chain), m): + if len(set(multilist)) == len(chain): + multichains.append(tuple([chain[i] for i in multilist])) + def is_saturated_chain(chain): + for i in range(1, m + 1): + for j in range(1, m - i + 1): + for alpha in chain[i - 1]: + for beta in chain[j - 1]: + gamma = alpha + beta + if gamma in Phi_plus and gamma not in chain[i+j-1]: + return False + cochain = [[beta for beta in Phi_plus if beta not in ideal] + for ideal in chain] + for i in range(1, m + 1): + for j in range(1, m + 1): + for alpha in cochain[i - 1]: + for beta in cochain[j - 1]: + gamma = alpha + beta + if gamma in Phi_plus and gamma not in cochain[min(m - 1, i + j - 1)]: + return False + return True + + def is_componentwise_subset(chain1, chain2): + return all(chain1[i].issubset(chain2[i]) + for i in range(len(chain1))) + from sage.combinat.posets.lattices import LatticePoset + saturated_chains = [multichain for multichain in multichains + if is_saturated_chain(multichain)] + return LatticePoset((saturated_chains, is_componentwise_subset), + facade=facade) + def almost_positive_roots(self): r""" Returns the almost positive roots of ``self`` diff --git a/src/sage/combinat/set_partition.py b/src/sage/combinat/set_partition.py index 1dc165ae421..ecd6d9af95c 100644 --- a/src/sage/combinat/set_partition.py +++ b/src/sage/combinat/set_partition.py @@ -1592,7 +1592,7 @@ def inf(s,t): sage: sp2 = Set([Set([1,3]), Set([2,4])]) sage: s = Set([ Set([2,4]), Set([3]), Set([1])]) #{{2, 4}, {3}, {1}} sage: sage.combinat.set_partition.inf(sp1, sp2) == s - doctest:1: DeprecationWarning: inf(s, t) is deprecated. Use s.inf(t) instead. + doctest:...: DeprecationWarning: inf(s, t) is deprecated. Use s.inf(t) instead. See http://trac.sagemath.org/14140 for details. True """ @@ -1612,7 +1612,7 @@ def sup(s,t): sage: sp2 = Set([Set([1,3]), Set([2,4])]) sage: s = Set([ Set([1,2,3,4]) ]) sage: sage.combinat.set_partition.sup(sp1, sp2) == s - doctest:1: DeprecationWarning: sup(s, t) is deprecated. Use s.sup(t) instead. + doctest:...: DeprecationWarning: sup(s, t) is deprecated. Use s.sup(t) instead. See http://trac.sagemath.org/14140 for details. True """ @@ -1632,7 +1632,7 @@ def standard_form(sp): EXAMPLES:: sage: map(sage.combinat.set_partition.standard_form, SetPartitions(4, [2,2])) - doctest:1: DeprecationWarning: standard_form(sp) is deprecated. Use sp.standard_form() instead. + doctest:...: DeprecationWarning: standard_form(sp) is deprecated. Use sp.standard_form() instead. See http://trac.sagemath.org/14140 for details. [[[1, 2], [3, 4]], [[1, 3], [2, 4]], [[1, 4], [2, 3]]] """ @@ -1649,7 +1649,7 @@ def less(s, t): sage: z = SetPartitions(3).list() sage: sage.combinat.set_partition.less(z[0], z[1]) - doctest:1: DeprecationWarning: less(s, t) is deprecated. Use SetPartitions.is_less_tan(s, t) instead. + doctest:...: DeprecationWarning: less(s, t) is deprecated. Use SetPartitions.is_less_tan(s, t) instead. See http://trac.sagemath.org/14140 for details. False """ diff --git a/src/sage/combinat/sf/k_dual.py b/src/sage/combinat/sf/k_dual.py index 43991509f31..cfe5b1cfd61 100644 --- a/src/sage/combinat/sf/k_dual.py +++ b/src/sage/combinat/sf/k_dual.py @@ -551,8 +551,8 @@ def _element_constructor_(self, x): else: raise TypeError("do not know how to make x (= %s) an element of %s"%(x, self)) #x is an element of the basis enumerated set; - elif x in self._basis_keys: - return self.monomial(self._basis_keys(x)) + elif x in self._indices: + return self.monomial(self._indices(x)) raise TypeError("do not know how to make x (= %s) an element of self (=%s)"%(x,self)) def ambient(self): diff --git a/src/sage/combinat/sf/new_kschur.py b/src/sage/combinat/sf/new_kschur.py index 3a6f5a2cf46..40b65a3b98a 100644 --- a/src/sage/combinat/sf/new_kschur.py +++ b/src/sage/combinat/sf/new_kschur.py @@ -328,8 +328,8 @@ def _element_constructor_(self, x): else: raise TypeError("do not know how to make x (= %s) an element of %s"%(x, self)) #x is an element of the basis enumerated set; - elif x in self._basis_keys: - return self.monomial(self._basis_keys(x)) + elif x in self._indices: + return self.monomial(self._indices(x)) raise TypeError("do not know how to make x (= %s) an element of self (=%s)"%(x,self)) def _convert_map_from_(self,Q): @@ -389,7 +389,7 @@ def __getitem__(self, c, *rest): else: c = Partition(list(c)) - if c not in self._basis_keys: + if c not in self._indices: raise TypeError("do not know how to make %s an element of %s"%(c,self)) return self.monomial(c) diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index b9a2a5819c7..232bb954f69 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -2023,11 +2023,11 @@ def set_print_style(self, ps): sage: s.set_print_style('lex') """ if ps == 'lex': - self.print_options(monomial_cmp = lambda x,y: cmp(x,y)) + self.print_options(generator_cmp = lambda x,y: cmp(x,y)) elif ps == 'length': - self.print_options(monomial_cmp = lambda x,y: cmp(len(x), len(y))) + self.print_options(generator_cmp = lambda x,y: cmp(len(x), len(y))) elif ps == 'maximal_part': - self.print_options(monomial_cmp = lambda x,y: cmp(_lmax(x), _lmax(y))) + self.print_options(generator_cmp = lambda x,y: cmp(_lmax(x), _lmax(y))) else: raise ValueError("the print style must be one of lex, length, or maximal_part ") self._print_style = ps diff --git a/src/sage/combinat/skew_partition.py b/src/sage/combinat/skew_partition.py index b1eed9d5da6..024921208f8 100644 --- a/src/sage/combinat/skew_partition.py +++ b/src/sage/combinat/skew_partition.py @@ -1142,7 +1142,7 @@ def from_row_and_column_length(rowL, colL): EXAMPLES:: sage: sage.combinat.skew_partition.from_row_and_column_length([3,1,2,2],[2,3,1,1,1]) - doctest:1: DeprecationWarning: from_row_and_column_length is deprecated. Use SkewPartitions().from_row_and_column_length instead. + doctest:...: DeprecationWarning: from_row_and_column_length is deprecated. Use SkewPartitions().from_row_and_column_length instead. See http://trac.sagemath.org/14101 for details. [5, 2, 2, 2] / [2, 1] """ diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index 775104a3bea..0dce946378b 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -640,7 +640,7 @@ def algebra_generators(self): a[1] = 1 b = range(2, self.n+2) b[self.n-1] = 1 - return [self.monomial(self._basis_keys(a)), self.monomial(self._basis_keys(b))] + return [self.monomial(self._indices(a)), self.monomial(self._indices(b))] def _conjugacy_classes_representatives_underlying_group(self): r""" @@ -962,7 +962,7 @@ def jucys_murphy(self, k): p = range(1, self.n+1) p[i-1] = k p[k-1] = i - res += self.monomial(self._basis_keys(p)) + res += self.monomial(self._indices(p)) return res @@ -1979,9 +1979,9 @@ def __init__(self, R, n, q=None): Hecke algebra of the symmetric group of order 3 with q=1 on the T basis over Rational Field """ self.n = n - self._basis_keys = Permutations(n) + self._indices = Permutations(n) self._name = "Hecke algebra of the symmetric group of order {}".format(n) - self._one = self._basis_keys(range(1,n+1)) + self._one = self._indices(range(1,n+1)) if q is None: q = PolynomialRing(R, 'q').gen() @@ -2025,7 +2025,7 @@ def _coerce_start(self, x): if x == []: return self.one() if len(x) < self.n and x in Permutations(): - return self.monomial(self._basis_keys( list(x) + range(len(x)+1, self.n+1) )) + return self.monomial(self._indices( list(x) + range(len(x)+1, self.n+1) )) raise TypeError class HeckeAlgebraSymmetricGroup_t(HeckeAlgebraSymmetricGroup_generic): @@ -2072,7 +2072,7 @@ def t_action_on_basis(self, perm, i): # -- Darij, 19 Nov 2013 if perm[i-1] < perm[i]: - return self.monomial(self._basis_keys(perm_i)) + return self.monomial(self._indices(perm_i)) else: #Ti^2 = (q - q^(-1))*Ti - q1*q2 q = self.q() @@ -2201,7 +2201,7 @@ def jucys_murphy(self, k): raise ValueError("k (= %(k)d) must be between 1 and n (= %(n)d)" % {'k': k, 'n': self.n}) q = self.q() - P = self._basis_keys + P = self._indices v = self.sum_of_terms( ( ( P(range(1, l) + [k] + range(l+1, k) + [l]), q ** l - q ** (l-1) ) for l in range(1, k) ), diff --git a/src/sage/combinat/tableau.py b/src/sage/combinat/tableau.py index 8a94d92289f..1f5643d492b 100644 --- a/src/sage/combinat/tableau.py +++ b/src/sage/combinat/tableau.py @@ -857,23 +857,6 @@ def to_word(self): """ return self.to_word_by_row() - - def to_permutation(self): - """ - Deprecated in :trac:`14724`. Use :meth:`reading_word_permutation()` - instead. - - EXAMPLES:: - - sage: Tableau([[1,2],[3,4]]).to_permutation() - doctest:...: DeprecationWarning: to_permutation() is deprecated. Use instead reading_word_permutation() - See http://trac.sagemath.org/14724 for details. - [3, 4, 1, 2] - """ - from sage.misc.superseded import deprecation - deprecation(14724, 'to_permutation() is deprecated. Use instead reading_word_permutation()') - return self.reading_word_permutation() - def attacking_pairs(self): """ Deprecated in :trac:`15327`. Use ``T.shape().attacking_pairs()`` diff --git a/src/sage/combinat/words/alphabet.py b/src/sage/combinat/words/alphabet.py index db9f0baee76..c52979a4241 100644 --- a/src/sage/combinat/words/alphabet.py +++ b/src/sage/combinat/words/alphabet.py @@ -285,7 +285,7 @@ class OrderedAlphabet(object): sage: from sage.combinat.words.alphabet import OrderedAlphabet sage: A = OrderedAlphabet('ab'); A - doctest:1: DeprecationWarning: OrderedAlphabet is deprecated; use Alphabet instead. + doctest:...: DeprecationWarning: OrderedAlphabet is deprecated; use Alphabet instead. See http://trac.sagemath.org/8920 for details. {'a', 'b'} sage: type(A) @@ -297,7 +297,7 @@ def __new__(self, alphabet=None, name=None): sage: from sage.combinat.words.alphabet import OrderedAlphabet sage: A = OrderedAlphabet('ab'); A # indirect doctest - doctest:1: DeprecationWarning: OrderedAlphabet is deprecated; use Alphabet instead. + doctest:...: DeprecationWarning: OrderedAlphabet is deprecated; use Alphabet instead. See http://trac.sagemath.org/8920 for details. {'a', 'b'} """ @@ -330,7 +330,7 @@ def __getattr__(self, name): sage: from sage.combinat.words.alphabet import OrderedAlphabet sage: O = OrderedAlphabet() - doctest:1: DeprecationWarning: OrderedAlphabet is deprecated; use Alphabet instead. + doctest:...: DeprecationWarning: OrderedAlphabet is deprecated; use Alphabet instead. See http://trac.sagemath.org/8920 for details. sage: O._alphabet = ['a', 'b'] sage: O._elements diff --git a/src/sage/databases/cremona.py b/src/sage/databases/cremona.py index 67dfde890b9..754ea2d535a 100644 --- a/src/sage/databases/cremona.py +++ b/src/sage/databases/cremona.py @@ -771,6 +771,123 @@ def curves(self, N): ret['h3'] = [[1,-1,1,-1568,-4669],int(1),int(6)] return ret + def coefficients_and_data(self, label): + """ + Return the Weierstrass coefficients and other data for the + curve with given label. + + EXAMPLES:: + + sage: c, d = CremonaDatabase().coefficients_and_data('144b1') + sage: c + [0, 0, 0, 6, 7] + sage: d['conductor'] + 144 + sage: d['cremona_label'] + '144b1' + sage: d['rank'] + 0 + sage: d['torsion_order'] + 2 + """ + # There are two possible strings: the Cremona label and the LMFDB label. + # They are distinguished by the presence of a period. + if label.find('.') == -1: + cremona_label = label + lmfdb_label = None + else: + cremona_label = lmfdb_to_cremona(label) + lmfdb_label = label + + N, iso, num = parse_cremona_label(cremona_label) + label = str(N)+iso+str(num) + if self.get_skeleton() == _miniCremonaSkeleton: + q = self.__connection__.cursor().execute("SELECT eqn,rank,tors " \ + + 'FROM t_curve,t_class USING(class) WHERE curve=?', (label,)) + else: + q = self.__connection__.cursor().execute("SELECT eqn,rank,tors," \ + + "deg,gens,cp,om,L,reg,sha FROM t_curve,t_class " \ + + "USING(class) WHERE curve=?",(label,)) + try: + c = q.next() + except StopIteration: + if N < self.largest_conductor(): + message = "There is no elliptic curve with label " + label \ + + " in the database" + elif is_package_installed('database_cremona_ellcurve'): + message = "There is no elliptic curve with label " + label \ + + " in the currently available databases" + else: + message = "There is no elliptic curve with label " \ + + label + " in the default database; try installing " \ + + "the optional package database_cremona_ellcurve which " \ + + "contains the complete Cremona database" + raise ValueError(message) + ainvs = eval(c[0]) + data = {'cremona_label': label, + 'rank': c[1], + 'torsion_order': c[2], + 'conductor': N} + if lmfdb_label: + data['lmfdb_label'] = lmfdb_label + if len(c) > 3: + if num == 1: + data['modular_degree'] = (c[3]) + data['gens'] = eval(c[4]) + data['db_extra'] = list(c[5:]) + elif c[1] == 0: + # we know the rank is 0, so the gens are empty + data['gens'] = [] + return ainvs, data + + def data_from_coefficients(self, ainvs): + """ + Return elliptic curve data for the curve with given + Weierstrass coefficients. + + EXAMPLES:: + + sage: d = CremonaDatabase().data_from_coefficients([1, -1, 1, 31, 128]) + sage: d['conductor'] + 1953 + sage: d['cremona_label'] + '1953c1' + sage: d['rank'] + 1 + sage: d['torsion_order'] + 2 + """ + ainvs = str(list(ainvs)) + if self.get_skeleton() == _miniCremonaSkeleton: + q = self.__connection__.cursor().execute("SELECT curve,rank,tors " + + 'FROM t_curve,t_class USING(class) WHERE eqn=?', + (ainvs.replace(' ', ''),)) + else: + q = self.__connection__.cursor().execute("SELECT curve,rank,tors," + + "deg,gens,cp,om,L,reg,sha FROM t_curve,t_class " + + "USING(class) WHERE eqn=?", + (ainvs.replace(' ', ''),)) + try: + c = q.next() + except StopIteration: + raise RuntimeError("There is no elliptic curve with coefficients " + + ainvs + " in the database") + label = str(c[0]) + N, iso, num = parse_cremona_label(label) + data = {'cremona_label': label, + 'rank': c[1], + 'torsion_order': c[2], + 'conductor': N} + if len(c) > 3: + if num == 1: + data['modular_degree'] = (c[3]) + data['gens'] = eval(c[4]) + data['db_extra'] = list(c[5:]) + elif c[1] == 0: + # we know the rank is 0, so the gens are empty + data['gens'] = [] + return data + def elliptic_curve_from_ainvs(self, ainvs): """ Returns the elliptic curve in the database of with minimal @@ -804,13 +921,8 @@ def elliptic_curve_from_ainvs(self, ainvs): ... ValueError: There is no elliptic curve with label 10a1 in the database """ - q = self.__connection__.cursor().execute("SELECT curve FROM t_curve " \ - + "WHERE eqn=?",(str(ainvs).replace(' ',''),)) - try: - return self.elliptic_curve(q.next()[0]) - except StopIteration: - raise RuntimeError("No elliptic curve with ainvs (=%s) "%ainvs \ - + "in the database.") + data = self.data_from_coefficients(ainvs) + return elliptic.EllipticCurve(ainvs, **data) def elliptic_curve(self, label): """ @@ -846,55 +958,8 @@ def elliptic_curve(self, label): sage: c.elliptic_curve('462.f3') Elliptic Curve defined by y^2 + x*y = x^3 - 363*x + 1305 over Rational Field """ - # There are two possible strings: the Cremona label and the LMFDB label. - # They are distinguished by the presence of a period. - if label.find('.') == -1: - cremona_label = label - lmfdb_label = None - else: - cremona_label = lmfdb_to_cremona(label) - lmfdb_label = label - - N, iso, num = parse_cremona_label(cremona_label) - label = str(N)+iso+str(num) - if self.get_skeleton() == _miniCremonaSkeleton: - q = self.__connection__.cursor().execute("SELECT eqn,rank,tors " \ - + 'FROM t_curve,t_class USING(class) WHERE curve=?', (label,)) - else: - q = self.__connection__.cursor().execute("SELECT eqn,rank,tors," \ - + "deg,gens,cp,om,L,reg,sha FROM t_curve,t_class " \ - + "USING(class) WHERE curve=?",(label,)) - try: - c = q.next() - F = elliptic.EllipticCurve(eval(c[0])) - F._set_cremona_label(label) - F._set_rank(c[1]) - F._set_torsion_order(c[2]) - F._set_conductor(N) - if lmfdb_label: - F._lmfdb_label = lmfdb_label - if len(c) > 3: - if num == 1: - F._set_modular_degree(c[3]) - F._set_gens(eval(c[4])) - F.db_extra = list(c[5:]) - elif c[1] == 0: - # we know the rank is 0, so the gens are empty - F._set_gens([]) - return F - except StopIteration: - if N < self.largest_conductor(): - message = "There is no elliptic curve with label " + label \ - + " in the database" - elif is_package_installed('database_cremona_ellcurve'): - message = "There is no elliptic curve with label " + label \ - + " in the currently available databases" - else: - message = "There is no elliptic curve with label " \ - + label + " in the default database; try installing " \ - + "the optional package database_cremona_ellcurve which " \ - + "contains the complete Cremona database" - raise ValueError(message) + ainvs, data = self.coefficients_and_data(label) + return elliptic.EllipticCurve(ainvs, **data) def iter(self, conductors): """ diff --git a/src/sage/databases/sloane.py b/src/sage/databases/sloane.py index 6788c60546b..91df768bb32 100644 --- a/src/sage/databases/sloane.py +++ b/src/sage/databases/sloane.py @@ -379,7 +379,7 @@ def parse_sequence(text=''): TESTS:: sage: from sage.databases.sloane import parse_sequence sage: parse_sequence() - doctest:1: DeprecationWarning: The function parse_sequence is not used anymore (2012-01-01). + doctest:...: DeprecationWarning: The function parse_sequence is not used anymore (2012-01-01). See http://trac.sagemath.org/10358 for details. """ deprecation(10358, "The function parse_sequence is not used anymore (2012-01-01).") @@ -394,7 +394,7 @@ def sloane_sequence(number=1, verbose=True): TESTS:: sage: sloane_sequence(123) - doctest:1: DeprecationWarning: The function sloane_sequence is deprecated. Use oeis() instead (2012-01-01). + doctest:...: DeprecationWarning: The function sloane_sequence is deprecated. Use oeis() instead (2012-01-01). See http://trac.sagemath.org/10358 for details. """ deprecation(10358, @@ -411,7 +411,7 @@ def sloane_find(list=[], nresults=30, verbose=True): TESTS:: sage: sloane_find([1,2,3]) - doctest:1: DeprecationWarning: The function sloane_find is deprecated. Use oeis() instead (2012-01-01). + doctest:...: DeprecationWarning: The function sloane_find is deprecated. Use oeis() instead (2012-01-01). See http://trac.sagemath.org/10358 for details. """ deprecation(10358, diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 2273d434a82..4d3407ac8ad 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -135,7 +135,7 @@ def warning_function(file): sage: wrn("bad stuff", UserWarning, "myfile.py", 0) sage: F.seek(0) sage: F.read() - 'doctest:0: UserWarning: bad stuff\n' + 'doctest:...: UserWarning: bad stuff\n' """ def doctest_showwarning(message, category, filename, lineno, file=file, line=None): try: @@ -1205,13 +1205,13 @@ def report_unexpected_exception(self, out, test, example, exc_info): sage: _ = sage0.eval("DTR = sdf.SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)") sage: sage0._prompt = r"\(Pdb\) " sage: sage0.eval("DTR.run(DT, clear_globs=False)") # indirect doctest - '... ArithmeticError("Invariants %s define a singular curve."%ainvs)' + '... ArithmeticError("invariants " + str(ainvs) + " define a singular curve")' sage: sage0.eval("l") '...if self.discriminant() == 0:...raise ArithmeticError...' sage: sage0.eval("u") - '...EllipticCurve_field.__init__(self, [field(x) for x in ainvs])' + '...EllipticCurve_field.__init__(self, K, ainvs)' sage: sage0.eval("p ainvs") - '[0, 0]' + '(0, 0, 0, 0, 0)' sage: sage0._prompt = "sage: " sage: sage0.eval("quit") 'TestResults(failed=1, attempted=1)' @@ -2105,6 +2105,8 @@ def __call__(self, options, outtmpfile=None, msgfile=None, result_queue=None): if extras['tab']: results.err = 'tab' results.tab_linenos = extras['tab'] + if extras['line_number']: + results.err = 'line_number' results.optionals = extras['optionals'] # We subtract 1 to remove the sig_on_count() tests result = (sum([max(0,len(test.examples) - 1) for test in doctests]), results) diff --git a/src/sage/doctest/reporting.py b/src/sage/doctest/reporting.py index fb8be8e5472..295183289ee 100644 --- a/src/sage/doctest/reporting.py +++ b/src/sage/doctest/reporting.py @@ -14,7 +14,7 @@ - 32: TAB character found - 64: Internal error in the doctesting framework - 128: Testing interrupted, not all tests run - +- 256: Doctest contains explicit source line number AUTHORS: @@ -375,6 +375,10 @@ def report(self, source, timeout, return_code, results, output, pid=None): log(" Error: TAB character found at line%s"%(tabs)) postscript['lines'].append(cmd + " # Tab character found") self.error_status |= 32 + elif result_dict.err == 'line_number': + log(" Error: Source line number found") + postscript['lines'].append(cmd + " # Source line number found") + self.error_status |= 256 elif result_dict.err is not None: # This case should not occur if result_dict.err is True: diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index ce91290292d..6b55ce3afd4 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -52,6 +52,9 @@ sagestart = re.compile(r"^\s*(>>> |sage: )\s*[^#\s]") untested = re.compile("(not implemented|not tested)") +# Source line number in warning output +doctest_line_number = re.compile(r"^\s*doctest:[0-9]") + def get_basename(path): """ @@ -230,6 +233,8 @@ def _create_doctests(self, namespace, tab_okay=None): 40 sage: extras['tab'] False + sage: extras['line_number'] + False """ if tab_okay is None: tab_okay = isinstance(self,TexSource) @@ -244,7 +249,10 @@ def _create_doctests(self, namespace, tab_okay=None): doc = [] start = None tab_locations = [] + contains_line_number = False for lineno, line in self: + if doctest_line_number.search(line) is not None: + contains_line_number = True if "\t" in line: tab_locations.append(str(lineno+1)) if "SAGE_DOCTEST_ALLOW_TABS" in line: @@ -293,8 +301,9 @@ def _create_doctests(self, namespace, tab_okay=None): if unparsed_doc: self._process_doc(doctests, doc, namespace, start) - extras = dict(tab = not tab_okay and tab_locations, - optionals = self.parser.optionals) + extras = dict(tab=not tab_okay and tab_locations, + line_number=contains_line_number, + optionals=self.parser.optionals) if self.options.randorder is not None and self.options.randorder is not False: # we want to randomize even when self.randorder = 0 random.seed(self.options.randorder) @@ -340,6 +349,20 @@ class StringDocTestSource(DocTestSource): 1 sage: extras['tab'] [] + sage: extras['line_number'] + False + + sage: s = "'''\n\tsage: 2 + 2\n\t4\n'''" + sage: PSS = PythonStringSource('', s, DocTestDefaults(), 'runtime') + sage: dt, extras = PSS.create_doctests({}) + sage: extras['tab'] + ['2', '3'] + + sage: s = "'''\n sage: import warnings; warnings.warn('foo')\n doctest:1: UserWarning: foo \n'''" + sage: PSS = PythonStringSource('', s, DocTestDefaults(), 'runtime') + sage: dt, extras = PSS.create_doctests({}) + sage: extras['line_number'] + True """ def __init__(self, basename, source, options, printpath, lineno_shift=0): r""" diff --git a/src/sage/ext/fast_callable.pyx b/src/sage/ext/fast_callable.pyx index f9fd01225ca..f4a0fff3524 100644 --- a/src/sage/ext/fast_callable.pyx +++ b/src/sage/ext/fast_callable.pyx @@ -446,8 +446,10 @@ def fast_callable(x, domain=None, vars=None, from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing if is_PolynomialRing(x.parent()) or is_MPolynomialRing(x.parent()): vars = x.parent().variable_names() + etb = ExpressionTreeBuilder(vars=vars, domain=domain) et = x._fast_callable_(etb) + if isinstance(domain, RealField_class): import sage.ext.interpreters.wrapper_rr builder = sage.ext.interpreters.wrapper_rr.Wrapper_rr @@ -507,6 +509,9 @@ def function_name(fn): sage: function_name(factorial) '{factorial}' """ + from sage.structure.dynamic_class import DynamicMetaclass + if isinstance(type(fn), DynamicMetaclass): + return "{%r}" % fn builtins = get_builtin_functions() if fn in builtins: return builtins[fn] diff --git a/src/sage/functions/all.py b/src/sage/functions/all.py index 9c8d2bd9a9d..dd2650ad4a3 100644 --- a/src/sage/functions/all.py +++ b/src/sage/functions/all.py @@ -76,3 +76,5 @@ sin_integral, cos_integral, Si, Ci, sinh_integral, cosh_integral, Shi, Chi, exponential_integral_1, Ei) + +from hypergeometric import hypergeometric diff --git a/src/sage/functions/bessel.py b/src/sage/functions/bessel.py index 00e5e7b1fd0..be1b36113a4 100644 --- a/src/sage/functions/bessel.py +++ b/src/sage/functions/bessel.py @@ -260,18 +260,14 @@ class Function_Bessel_J(BuiltinFunction): sage: bessel_J(1.0, 1.0) - A[0] < 1e-15 True - Currently, integration is not supported (directly) since we cannot - yet convert hypergeometric functions to and from Maxima:: + Integration is supported directly and through Maxima:: sage: f = bessel_J(2, x) sage: f.integrate(x) - Traceback (most recent call last): - ... - TypeError: cannot coerce arguments: no canonical coercion from to Symbolic Ring - + 1/24*x^3*hypergeometric((3/2,), (5/2, 3), -1/4*x^2) sage: m = maxima(bessel_J(2, x)) sage: m.integrate(x) - hypergeometric([3/2],[5/2,3],-x^2/4)*x^3/24 + hypergeometric([3/2],[5/2,3],-_SAGE_VAR_x^2/4)*_SAGE_VAR_x^3/24 Visualization:: @@ -912,10 +908,10 @@ def Bessel(*args, **kwds): sage: x,y = var('x,y') sage: f = maxima(Bessel(typ='K')(x,y)) - sage: f.derivative('x') - %pi*csc(%pi*x)*('diff(bessel_i(-x,y),x,1)-'diff(bessel_i(x,y),x,1))/2-%pi*bessel_k(x,y)*cot(%pi*x) - sage: f.derivative('y') - -(bessel_k(x+1,y)+bessel_k(x-1,y))/2 + sage: f.derivative('_SAGE_VAR_x') + %pi*csc(%pi*_SAGE_VAR_x)*('diff(bessel_i(-_SAGE_VAR_x,_SAGE_VAR_y),_SAGE_VAR_x,1)-'diff(bessel_i(_SAGE_VAR_x,_SAGE_VAR_y),_SAGE_VAR_x,1))/2-%pi*bessel_k(_SAGE_VAR_x,_SAGE_VAR_y)*cot(%pi*_SAGE_VAR_x) + sage: f.derivative('_SAGE_VAR_y') + -(bessel_k(_SAGE_VAR_x+1,_SAGE_VAR_y)+bessel_k(_SAGE_VAR_x-1,_SAGE_VAR_y))/2 Compute the particular solution to Bessel's Differential Equation that satisfies `y(1) = 1` and `y'(1) = 1`, then verify the initial conditions diff --git a/src/sage/functions/hyperbolic.py b/src/sage/functions/hyperbolic.py index f2249c1b2fb..54a26d5b296 100644 --- a/src/sage/functions/hyperbolic.py +++ b/src/sage/functions/hyperbolic.py @@ -608,7 +608,7 @@ def _eval_numpy_(self, x): sage: import numpy sage: a = numpy.linspace(0,1,3) sage: arcsech(a) - doctest:614: RuntimeWarning: divide by zero encountered in divide + doctest:...: RuntimeWarning: divide by zero encountered in divide array([ inf, 1.3169579, 0. ]) """ return arccosh(1.0 / x) @@ -658,7 +658,7 @@ def _eval_numpy_(self, x): sage: import numpy sage: a = numpy.linspace(0,1,3) sage: arccsch(a) - doctest:664: RuntimeWarning: divide by zero encountered in divide + doctest:...: RuntimeWarning: divide by zero encountered in divide array([ inf, 1.44363548, 0.88137359]) """ return arcsinh(1.0 / x) diff --git a/src/sage/functions/hypergeometric.py b/src/sage/functions/hypergeometric.py new file mode 100644 index 00000000000..0fed0f157af --- /dev/null +++ b/src/sage/functions/hypergeometric.py @@ -0,0 +1,855 @@ +r""" +Hypergeometric Functions + +This module implements manipulation of infinite hypergeometric series +represented in standard parametric form (as $\,_pF_q$ functions). + +AUTHORS: + +- Fredrik Johansson (2010): initial version + +- Eviatar Bach (2013): major changes + +EXAMPLES: + +Examples from :trac:`9908`:: + + sage: maxima('integrate(bessel_j(2, x), x)').sage() + 1/24*x^3*hypergeometric((3/2,), (5/2, 3), -1/4*x^2) + sage: sum(((2*I)^x/(x^3 + 1)*(1/4)^x), x, 0, oo) + hypergeometric((1, 1, -1/2*I*sqrt(3) - 1/2, 1/2*I*sqrt(3) - 1/2),... + (2, -1/2*I*sqrt(3) + 1/2, 1/2*I*sqrt(3) + 1/2), 1/2*I) + sage: sum((-1)^x/((2*x + 1)*factorial(2*x + 1)), x, 0, oo) + hypergeometric((1/2,), (3/2, 3/2), -1/4) + +Simplification (note that ``simplify_full`` does not yet call +``simplify_hypergeometric``):: + + sage: hypergeometric([-2], [], x).simplify_hypergeometric() + x^2 - 2*x + 1 + sage: hypergeometric([], [], x).simplify_hypergeometric() + e^x + sage: a = hypergeometric((hypergeometric((), (), x),), (), + ....: hypergeometric((), (), x)) + sage: a.simplify_hypergeometric() + 1/((-e^x + 1)^e^x) + sage: a.simplify_hypergeometric(algorithm='sage') + (-e^x + 1)^(-e^x) + +Equality testing:: + + sage: bool(hypergeometric([], [], x).derivative(x) == + ....: hypergeometric([], [], x)) # diff(e^x, x) == e^x + True + sage: bool(hypergeometric([], [], x) == hypergeometric([], [1], x)) + False + +Computing terms and series:: + + sage: z = var('z') + sage: hypergeometric([], [], z).series(z, 0) + Order(1) + sage: hypergeometric([], [], z).series(z, 1) + 1 + Order(z) + sage: hypergeometric([], [], z).series(z, 2) + 1 + 1*z + Order(z^2) + sage: hypergeometric([], [], z).series(z, 3) + 1 + 1*z + 1/2*z^2 + Order(z^3) + + sage: hypergeometric([-2], [], z).series(z, 3) + 1 + (-2)*z + 1*z^2 + sage: hypergeometric([-2], [], z).series(z, 6) + 1 + (-2)*z + 1*z^2 + sage: hypergeometric([-2], [], z).series(z, 6).is_terminating_series() + True + sage: hypergeometric([-2], [], z).series(z, 2) + 1 + (-2)*z + Order(z^2) + sage: hypergeometric([-2], [], z).series(z, 2).is_terminating_series() + False + + sage: hypergeometric([1], [], z).series(z, 6) + 1 + 1*z + 1*z^2 + 1*z^3 + 1*z^4 + 1*z^5 + Order(z^6) + sage: hypergeometric([], [1/2], -z^2/4).series(z, 11) + 1 + (-1/2)*z^2 + 1/24*z^4 + (-1/720)*z^6 + 1/40320*z^8 +... + (-1/3628800)*z^10 + Order(z^11) + + sage: hypergeometric([1], [5], x).series(x, 5) + 1 + 1/5*x + 1/30*x^2 + 1/210*x^3 + 1/1680*x^4 + Order(x^5) + + sage: sum(hypergeometric([1, 2], [3], 1/3).terms(6)).n() + 1.29788359788360 + sage: hypergeometric([1, 2], [3], 1/3).n() + 1.29837194594696 + sage: hypergeometric([], [], x).series(x, 20)(x=1).n() == e.n() + True + +Plotting:: + + sage: plot(hypergeometric([1, 1], [3, 3, 3], x), x, -30, 30) + sage: complex_plot(hypergeometric([x], [], 2), (-1, 1), (-1, 1)) + +Numeric evaluation:: + + sage: hypergeometric([1], [], 1/10).n() # geometric series + 1.11111111111111 + sage: hypergeometric([], [], 1).n() # e + 2.71828182845905 + sage: hypergeometric([], [], 3., hold=True) + hypergeometric((), (), 3.00000000000000) + sage: hypergeometric([1, 2, 3], [4, 5, 6], 1/2).n() + 1.02573619590134 + sage: hypergeometric([1, 2, 3], [4, 5, 6], 1/2).n(digits=30) + 1.02573619590133865036584139535 + sage: hypergeometric([5 - 3*I], [3/2, 2 + I, sqrt(2)], 4 + I).n() + 5.52605111678805 - 7.86331357527544*I + sage: hypergeometric((10, 10), (50,), 2.) + -1705.75733163554 - 356.749986056024*I + +Conversions:: + + sage: maxima(hypergeometric([1, 1, 1], [3, 3, 3], x)) + hypergeometric([1,1,1],[3,3,3],_SAGE_VAR_x) + sage: hypergeometric((5, 4), (4, 4), 3)._sympy_() + hyper((5, 4), (4, 4), 3) + sage: hypergeometric((5, 4), (4, 4), 3)._mathematica_init_() + 'HypergeometricPFQ[{5,4},{4,4},3]' + +Arbitrary level of nesting for conversions:: + + sage: maxima(nest(lambda y: hypergeometric([y], [], x), 3, 1)) + 1/(1-_SAGE_VAR_x)^(1/(1-_SAGE_VAR_x)^(1/(1-_SAGE_VAR_x))) + sage: maxima(nest(lambda y: hypergeometric([y], [3], x), 3, 1))._sage_() + hypergeometric((hypergeometric((hypergeometric((1,), (3,), x),), (3,),... + x),), (3,), x) + sage: nest(lambda y: hypergeometric([y], [], x), 3, 1)._mathematica_init_() + 'HypergeometricPFQ[{HypergeometricPFQ[{HypergeometricPFQ[{1},{},x]},... +""" +#***************************************************************************** +# Copyright (C) 2010 Fredrik Johansson +# Copyright (C) 2013 Eviatar Bach +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.rings.infinity import Infinity +from sage.rings.arith import (binomial, rising_factorial, factorial) +from sage.functions.other import sqrt, gamma, real_part +from sage.functions.log import exp, log +from sage.functions.trig import cos, sin +from sage.functions.hyperbolic import cosh, sinh +from sage.functions.other import erf +from sage.symbolic.constants import pi +from sage.symbolic.all import I +from sage.symbolic.function import BuiltinFunction, is_inexact +from sage.symbolic.ring import SR +from sage.structure.element import get_coercion_model +from sage.misc.latex import latex +from sage.misc.misc_c import prod +from sage.libs.mpmath import utils as mpmath_utils +from sage.symbolic.expression import Expression +from sage.calculus.functional import derivative + + +def rational_param_as_tuple(x): + """ + Utility function for converting rational pFq parameters to + tuples (which mpmath handles more efficiently). + + EXAMPLES:: + + sage: from sage.functions.hypergeometric import rational_param_as_tuple + sage: rational_param_as_tuple(1/2) + (1, 2) + sage: rational_param_as_tuple(3) + 3 + sage: rational_param_as_tuple(pi) + pi + + """ + try: + x = x.pyobject() + except AttributeError: + pass + try: + if x.parent() is QQ: + p = int(x.numer()) + q = int(x.denom()) + return p, q + except AttributeError: + pass + return x + + +class Hypergeometric(BuiltinFunction): + r""" + Represents a (formal) generalized infinite hypergeometric series. It is + defined as `\,{}_pF_q(a_1,\ldots,a_p;b_1,\ldots,b_q;z) = \sum_{n=0}^\infty + \frac{(a_1)_n\dots(a_p)_n}{(b_1)_n\dots(b_q)_n} \, \frac{z^n}{n!},` + + where `(x)_n` is the rising factorial. + """ + def __init__(self): + """ + Initialize class. + + EXAMPLES:: + + sage: maxima(hypergeometric) + hypergeometric + """ + BuiltinFunction.__init__(self, 'hypergeometric', nargs=3, + conversions={'mathematica': + 'HypergeometricPFQ', + 'maxima': 'hypergeometric', + 'sympy': 'hyper'}) + + def __call__(self, a, b, z, **kwargs): + """ + Return symbolic hypergeometric function expression. + + INPUT: + + - ``a`` -- a list or tuple of parameters + - ``b`` -- a list or tuple of parameters + - ``z`` -- a number or symbolic expression + + EXAMPLES:: + + sage: hypergeometric([], [], 1) + hypergeometric((), (), 1) + sage: hypergeometric([], [1], 1) + hypergeometric((), (1,), 1) + sage: hypergeometric([2, 3], [1], 1) + hypergeometric((2, 3), (1,), 1) + sage: hypergeometric([], [], x) + hypergeometric((), (), x) + sage: hypergeometric([x], [], x^2) + hypergeometric((x,), (), x^2) + + The only simplification that is done automatically is returning 1 if ``z`` + is 0. For other simplifications use the ``simplify_hypergeometric`` method. + """ + return BuiltinFunction.__call__(self, + SR._force_pyobject(a), + SR._force_pyobject(b), + z, **kwargs) + + def _print_latex_(self, a, b, z): + r""" + TESTS:: + + sage: latex(hypergeometric([1, 1], [2], -1)) + \,_2F_1\left(\begin{matrix} 1,1 \\ 2 \end{matrix} ; -1 \right) + + """ + aa = ",".join(latex(c) for c in a) + bb = ",".join(latex(c) for c in b) + z = latex(z) + return (r"\,_{}F_{}\left(\begin{{matrix}} {} \\ {} \end{{matrix}} ; " + r"{} \right)").format(len(a), len(b), aa, bb, z) + + def _eval_(self, a, b, z, **kwargs): + """ + EXAMPLES:: + + sage: hypergeometric([], [], 0) + 1 + """ + if not isinstance(a,tuple) or not isinstance(b,tuple): + raise ValueError('First two parameters must be of type list.') + coercion_model = get_coercion_model() + co = reduce(lambda x, y: coercion_model.canonical_coercion(x, y)[0], + a + b + (z,)) + if is_inexact(co) and not isinstance(co, Expression): + from sage.structure.coerce import parent + return self._evalf_(a, b, z, parent=parent(co)) + if not isinstance(z, Expression) and z == 0: # Expression is excluded + return Integer(1) # to avoid call to Maxima + return + + def _evalf_(self, a, b, z, parent, algorithm=None): + """ + TESTS:: + + sage: hypergeometric([1, 1], [2], -1).n() + 0.693147180559945 + sage: hypergeometric([], [], RealField(100)(1)) + 2.7182818284590452353602874714 + + """ + if not isinstance(a,tuple) or not isinstance(b,tuple): + raise ValueError('First two parameters must be of type list.') + from mpmath import hyper + aa = [rational_param_as_tuple(c) for c in a] + bb = [rational_param_as_tuple(c) for c in b] + return mpmath_utils.call(hyper, aa, bb, z, parent=parent) + + def _tderivative_(self, a, b, z, *args, **kwargs): + """ + EXAMPLES:: + + sage: hypergeometric([1/3, 2/3], [5], x^2).diff(x) + 4/45*x*hypergeometric((4/3, 5/3), (6,), x^2) + sage: hypergeometric([1, 2], [x], 2).diff(x) + Traceback (most recent call last): + ... + NotImplementedError: derivative of hypergeometric function with... + respect to parameters. Try calling .simplify_hypergeometric()... + first. + sage: hypergeometric([1/3, 2/3], [5], 2).diff(x) + 0 + """ + diff_param = kwargs['diff_param'] + if diff_param in hypergeometric(a, b, 1).variables(): # ignore z + raise NotImplementedError("derivative of hypergeometric function " + "with respect to parameters. Try calling" + " .simplify_hypergeometric() first.") + t = (reduce(lambda x, y: x * y, a, 1) * + reduce(lambda x, y: x / y, b, Integer(1))) + return (t * derivative(z, diff_param) * + hypergeometric([c + 1 for c in a], [c + 1 for c in b], z)) + + class EvaluationMethods: + def _fast_float_(cls, self, *args): + """ + Do not support the old ``fast_float`` + + OUTPUT: + + This method raises ``NotImplementedError``; use the newer + ``fast_callable`` implementation + + EXAMPLES:: + + sage: f = hypergeometric([], [], x) + sage: f._fast_float_() + Traceback (most recent call last): + ... + NotImplementedError + """ + raise NotImplementedError + + def _fast_callable_(cls, self, a, b, z, etb): + """ + Override the ``fast_callable`` method + + OUTPUT: + + A :class:`~sage.ext.fast_callable.ExpressionCall` representing the + hypergeometric function in the expression tree + + EXAMPLES:: + + sage: h = hypergeometric([], [], x) + sage: from sage.ext.fast_callable import ExpressionTreeBuilder + sage: etb = ExpressionTreeBuilder(vars=['x']) + sage: h._fast_callable_(etb) + {hypergeometric((), (), x)}(v_0) + + sage: y = var('y') + sage: fast_callable(hypergeometric([y], [], x), + ....: vars=[x, y])(3, 4) + hypergeometric((4,), (), 3) + """ + return etb.call(self, *map(etb.var, etb._vars)) + + def sorted_parameters(cls, self, a, b, z): + """ + Return with parameters sorted in a canonical order. + + EXAMPLES:: + + sage: hypergeometric([2, 1, 3], [5, 4], + ....: 1/2).sorted_parameters() + hypergeometric((1, 2, 3), (4, 5), 1/2) + + """ + return hypergeometric(sorted(a), sorted(b), z) + + def eliminate_parameters(cls, self, a, b, z): + """ + Eliminate repeated parameters by pairwise cancellation of identical + terms in ``a`` and ``b``. + + EXAMPLES:: + + sage: hypergeometric([1, 1, 2, 5], [5, 1, 4], + ....: 1/2).eliminate_parameters() + hypergeometric((1, 2), (4,), 1/2) + sage: hypergeometric([x], [x], x).eliminate_parameters() + hypergeometric((), (), x) + sage: hypergeometric((5, 4), (4, 4), 3).eliminate_parameters() + hypergeometric((5,), (4,), 3) + + """ + aa = list(a) # tuples are immutable + bb = list(b) + p = pp = len(aa) + q = qq = len(bb) + i = 0 + while i < qq and aa: + bbb = bb[i] + if bbb in aa: + aa.remove(bbb) + bb.remove(bbb) + pp -= 1 + qq -= 1 + else: + i += 1 + if (pp, qq) != (p, q): + return hypergeometric(aa, bb, z) + return self + + def is_termwise_finite(cls, self, a, b, z): + """ + Determine whether all terms of self are finite. Any infinite + terms or ambiguous terms beyond the first zero, if one exists, + are ignored. + + Ambiguous cases (where a term is the product of both zero + and an infinity) are not considered finite. + + EXAMPLES:: + + sage: hypergeometric([2], [3, 4], 5).is_termwise_finite() + True + sage: hypergeometric([2], [-3, 4], 5).is_termwise_finite() + False + sage: hypergeometric([-2], [-3, 4], 5).is_termwise_finite() + True + sage: hypergeometric([-3], [-3, 4], + ....: 5).is_termwise_finite() # ambiguous + False + + sage: hypergeometric([0], [-1], 5).is_termwise_finite() + True + sage: hypergeometric([0], [0], + ....: 5).is_termwise_finite() # ambiguous + False + sage: hypergeometric([1], [2], Infinity).is_termwise_finite() + False + sage: (hypergeometric([0], [0], Infinity) + ....: .is_termwise_finite()) # ambiguous + False + sage: (hypergeometric([0], [], Infinity) + ....: .is_termwise_finite()) # ambiguous + False + + """ + if z == 0: + return 0 not in b + if abs(z) == Infinity: + return False + if abs(z) == Infinity: + return False + for bb in b: + if bb in ZZ and bb <= 0: + if any((aa in ZZ) and (bb < aa <= 0) for aa in a): + continue + return False + return True + + def is_terminating(cls, self, a, b, z): + """ + Determine whether the series represented by self terminates + after a finite number of terms, i.e. whether any of the + numerator parameters are nonnegative integers (with no + preceding nonnegative denominator parameters), or z = 0. + + If terminating, the series represents a polynomial of z. + + EXAMPLES:: + + sage: hypergeometric([1, 2], [3, 4], x).is_terminating() + False + sage: hypergeometric([1, -2], [3, 4], x).is_terminating() + True + sage: hypergeometric([1, -2], [], x).is_terminating() + True + + """ + if z == 0: + return True + for aa in a: + if (aa in ZZ) and (aa <= 0): + return self.is_termwise_finite() + return False + + def is_absolutely_convergent(cls, self, a, b, z): + """ + Determine whether self converges absolutely as an infinite series. + False is returned if not all terms are finite. + + EXAMPLES: + + Degree giving infinite radius of convergence:: + + sage: hypergeometric([2, 3], [4, 5], + ....: 6).is_absolutely_convergent() + True + sage: hypergeometric([2, 3], [-4, 5], + ....: 6).is_absolutely_convergent() # undefined + False + sage: (hypergeometric([2, 3], [-4, 5], Infinity) + ....: .is_absolutely_convergent()) # undefined + False + + Ordinary geometric series (unit radius of convergence):: + + sage: hypergeometric([1], [], 1/2).is_absolutely_convergent() + True + sage: hypergeometric([1], [], 2).is_absolutely_convergent() + False + sage: hypergeometric([1], [], 1).is_absolutely_convergent() + False + sage: hypergeometric([1], [], -1).is_absolutely_convergent() + False + sage: hypergeometric([1], [], -1).n() # Sum still exists + 0.500000000000000 + + Degree $p = q+1$ (unit radius of convergence):: + + sage: hypergeometric([2, 3], [4], 6).is_absolutely_convergent() + False + sage: hypergeometric([2, 3], [4], 1).is_absolutely_convergent() + False + sage: hypergeometric([2, 3], [5], 1).is_absolutely_convergent() + False + sage: hypergeometric([2, 3], [6], 1).is_absolutely_convergent() + True + sage: hypergeometric([-2, 3], [4], + ....: 5).is_absolutely_convergent() + True + sage: hypergeometric([2, -3], [4], + ....: 5).is_absolutely_convergent() + True + sage: hypergeometric([2, -3], [-4], + ....: 5).is_absolutely_convergent() + True + sage: hypergeometric([2, -3], [-1], + ....: 5).is_absolutely_convergent() + False + + Degree giving zero radius of convergence:: + + sage: hypergeometric([1, 2, 3], [4], + ....: 2).is_absolutely_convergent() + False + sage: hypergeometric([1, 2, 3], [4], + ....: 1/2).is_absolutely_convergent() + False + sage: (hypergeometric([1, 2, -3], [4], 1/2) + ....: .is_absolutely_convergent()) # polynomial + True + + """ + p, q = len(a), len(b) + if not self.is_termwise_finite(): + return False + if p <= q: + return True + if self.is_terminating(): + return True + if p == q + 1: + if abs(z) < 1: + return True + if abs(z) == 1: + if real_part(sum(b) - sum(a)) > 0: + return True + return False + + def terms(cls, self, a, b, z, n=None): + """ + Generate the terms of self (optionally only n terms). + + EXAMPLES:: + + sage: list(hypergeometric([-2, 1], [3, 4], x).terms()) + [1, -1/6*x, 1/120*x^2] + sage: list(hypergeometric([-2, 1], [3, 4], x).terms(2)) + [1, -1/6*x] + sage: list(hypergeometric([-2, 1], [3, 4], x).terms(0)) + [] + + """ + if n is None: + n = Infinity + t = Integer(1) + k = 1 + while k <= n: + yield t + for aa in a: + t *= (aa + k - 1) + for bb in b: + t /= (bb + k - 1) + t *= z + if t == 0: + break + t /= k + k += 1 + + def deflated(cls, self, a, b, z): + """ + Rewrite as a linear combination of functions of strictly lower + degree by eliminating all parameters ``a[i]`` and ``b[j]`` such + that ``a[i]`` = ``b[i]`` + ``m`` for nonnegative integer ``m``. + + EXAMPLES:: + + sage: x = hypergeometric([6, 1], [3, 4, 5], 10) + sage: y = x.deflated() + sage: y + 1/252*hypergeometric((4,), (7, 8), 10) + 1/12*hypergeometric((3,), (6, 7), 10) + 1/2*hypergeometric((2,), (5, 6), 10) + hypergeometric((1,), (4, 5), 10) + sage: x.n(); y.n() + 2.87893612686782 + 2.87893612686782 + + sage: x = hypergeometric([6, 7], [3, 4, 5], 10) + sage: y = x.deflated() + sage: y + 25/27216*hypergeometric((), (11,), 10) + 25/648*hypergeometric((), (10,), 10) + 265/504*hypergeometric((), (9,), 10) + 181/63*hypergeometric((), (8,), 10) + 19/3*hypergeometric((), (7,), 10) + 5*hypergeometric((), (6,), 10) + hypergeometric((), (5,), 10) + sage: x.n(); y.n() + 63.0734110716969 + 63.0734110716969 + + """ + return sum(map(prod, self._deflated())) + + def _deflated(cls, self, a, b, z): + """ + Private helper to return list of deflated terms. + + EXAMPLES:: + + sage: x = hypergeometric([5], [4], 3) + sage: y = x.deflated() + sage: y + 7/4*hypergeometric((), (), 3) + sage: x.n(); y.n() + 35.1496896155784 + 35.1496896155784 + """ + new = self.eliminate_parameters() + aa = new.operands()[0].operands() + bb = new.operands()[1].operands() + for i, aaa in enumerate(aa): + for j, bbb in enumerate(bb): + m = aaa - bbb + if m in ZZ and m > 0: + aaaa = aa[:i] + aa[i + 1:] + bbbb = bb[:j] + bb[j + 1:] + terms = [] + for k in xrange(m + 1): + # TODO: could rewrite prefactors as recurrence + term = binomial(m, k) + for c in aaaa: + term *= rising_factorial(c, k) + for c in bbbb: + term /= rising_factorial(c, k) + term *= z ** k + term /= rising_factorial(aaa - m, k) + F = hypergeometric([c + k for c in aaaa], + [c + k for c in bbbb], z) + unique = [] + counts = [] + for c, f in F._deflated(): + if f in unique: + counts[unique.index(f)] += c + else: + unique.append(f) + counts.append(c) + Fterms = zip(counts, unique) + terms += [(term * termG, G) for (termG, G) in + Fterms] + return terms + return ((1, new),) + +hypergeometric = Hypergeometric() + + +def closed_form(hyp): + """ + Try to evaluate self in closed form using elementary + (and other simple) functions. + + It may be necessary to call :meth:`deflated` first to + find some closed forms. + + EXAMPLES:: + + sage: from sage.functions.hypergeometric import closed_form + sage: var('a b c z') + (a, b, c, z) + sage: closed_form(hypergeometric([1], [], 1 + z)) + -1/z + sage: closed_form(hypergeometric([], [], 1 + z)) + e^(z + 1) + sage: closed_form(hypergeometric([], [1/2], 4)) + cosh(4) + sage: closed_form(hypergeometric([], [3/2], 4)) + 1/4*sinh(4) + sage: closed_form(hypergeometric([], [5/2], 4)) + 3/16*cosh(4) - 3/64*sinh(4) + sage: closed_form(hypergeometric([], [-3/2], 4)) + 19/3*cosh(4) - 4*sinh(4) + sage: closed_form(hypergeometric([-3, 1], [var('a')], z)) + -3*z/a + 6*z^2/((a + 1)*a) - 6*z^3/((a + 2)*(a + 1)*a) + 1 + sage: closed_form(hypergeometric([-3, 1/3], [-4], z)) + 7/162*z^3 + 1/9*z^2 + 1/4*z + 1 + sage: closed_form(hypergeometric([], [], z)) + e^z + sage: closed_form(hypergeometric([a], [], z)) + (-z + 1)^(-a) + sage: closed_form(hypergeometric([1, 1, 2], [1, 1], z)) + (z - 1)^(-2) + sage: closed_form(hypergeometric([2, 3], [1], x)) + -1/(x - 1)^3 + 3*x/(x - 1)^4 + sage: closed_form(hypergeometric([1/2], [3/2], -5)) + 1/10*sqrt(5)*sqrt(pi)*erf(sqrt(5)) + sage: closed_form(hypergeometric([2], [5], 3)) + 4 + sage: closed_form(hypergeometric([2], [5], 5)) + 48/625*e^5 + 612/625 + sage: closed_form(hypergeometric([1/2, 7/2], [3/2], z)) + 1/5*z^2/(-z + 1)^(5/2) + 2/3*z/(-z + 1)^(3/2) + 1/sqrt(-z + 1) + sage: closed_form(hypergeometric([1/2, 1], [2], z)) + -2*(sqrt(-z + 1) - 1)/z + sage: closed_form(hypergeometric([1, 1], [2], z)) + -log(-z + 1)/z + sage: closed_form(hypergeometric([1, 1], [3], z)) + -2*((z - 1)*log(-z + 1)/z - 1)/z + sage: closed_form(hypergeometric([1, 1, 1], [2, 2], x)) + hypergeometric((1, 1, 1), (2, 2), x) + """ + if hyp.is_terminating(): + return sum(hyp.terms()) + + new = hyp.eliminate_parameters() + + def _closed_form(hyp): + a, b, z = hyp.operands() + a, b = a.operands(), b.operands() + p, q = len(a), len(b) + + if z == 0: + return Integer(1) + if p == q == 0: + return exp(z) + if p == 1 and q == 0: + return (1 - z) ** (-a[0]) + + if p == 0 and q == 1: + # TODO: make this require only linear time + def _0f1(b, z): + F12 = cosh(2 * sqrt(z)) + F32 = sinh(2 * sqrt(z)) / (2 * sqrt(z)) + if 2 * b == 1: + return F12 + if 2 * b == 3: + return F32 + if 2 * b > 3: + return ((b - 2) * (b - 1) / z * (_0f1(b - 2, z) - + _0f1(b - 1, z))) + if 2 * b < 1: + return (_0f1(b + 1, z) + z / (b * (b + 1)) * + _0f1(b + 2, z)) + raise ValueError + # Can evaluate 0F1 in terms of elementary functions when + # the parameter is a half-integer + if 2 * b[0] in ZZ and b[0] not in ZZ: + return _0f1(b[0], z) + + # Confluent hypergeometric function + if p == 1 and q == 1: + aa, bb = a[0], b[0] + if aa * 2 == 1 and bb * 2 == 3: + t = sqrt(-z) + return sqrt(pi) / 2 * erf(t) / t + if a == 1 and b == 2: + return (exp(z) - 1) / z + n, m = aa, bb + if n in ZZ and m in ZZ and m > 0 and n > 0: + rf = rising_factorial + if m <= n: + return (exp(z) * sum(rf(m - n, k) * (-z) ** k / + factorial(k) / rf(m, k) for k in + xrange(n - m + 1))) + else: + T = sum(rf(n - m + 1, k) * z ** k / + (factorial(k) * rf(2 - m, k)) for k in + xrange(m - n)) + U = sum(rf(1 - n, k) * (-z) ** k / + (factorial(k) * rf(2 - m, k)) for k in + xrange(n)) + return (factorial(m - 2) * rf(1 - m, n) * + z ** (1 - m) / factorial(n - 1) * + (T - exp(z) * U)) + + if p == 2 and q == 1: + R12 = QQ('1/2') + R32 = QQ('3/2') + + def _2f1(a, b, c, z): + """ + Evaluation of 2F1(a, b, c, z), assuming a, b, c positive + integers or half-integers + """ + if b == c: + return (1 - z) ** (-a) + if a == c: + return (1 - z) ** (-b) + if a == 0 or b == 0: + return Integer(1) + if a > b: + a, b = b, a + if b >= 2: + F1 = _2f1(a, b - 1, c, z) + F2 = _2f1(a, b - 2, c, z) + q = (b - 1) * (z - 1) + return (((c - 2 * b + 2 + (b - a - 1) * z) * F1 + + (b - c - 1) * F2) / q) + if c > 2: + # how to handle this case? + if a - c + 1 == 0 or b - c + 1 == 0: + raise NotImplementedError + F1 = _2f1(a, b, c - 1, z) + F2 = _2f1(a, b, c - 2, z) + r1 = (c - 1) * (2 - c - (a + b - 2 * c + 3) * z) + r2 = (c - 1) * (c - 2) * (1 - z) + q = (a - c + 1) * (b - c + 1) * z + return (r1 * F1 + r2 * F2) / q + + if (a, b, c) == (R12, 1, 2): + return (2 - 2 * sqrt(1 - z)) / z + if (a, b, c) == (1, 1, 2): + return -log(1 - z) / z + if (a, b, c) == (1, R32, R12): + return (1 + z) / (1 - z) ** 2 + if (a, b, c) == (1, R32, 2): + return 2 * (1 / sqrt(1 - z) - 1) / z + if (a, b, c) == (R32, 2, R12): + return (1 + 3 * z) / (1 - z) ** 3 + if (a, b, c) == (R32, 2, 1): + return (2 + z) / (2 * (sqrt(1 - z) * (1 - z) ** 2)) + if (a, b, c) == (2, 2, 1): + return (1 + z) / (1 - z) ** 3 + raise NotImplementedError + aa, bb = a + cc, = b + if z == 1: + return (gamma(cc) * gamma(cc - aa - bb) / gamma(cc - aa) / + gamma(cc - bb)) + if ((aa * 2) in ZZ and (bb * 2) in ZZ and (cc * 2) in ZZ and + aa > 0 and bb > 0 and cc > 0): + try: + return _2f1(aa, bb, cc, z) + except NotImplementedError: + pass + return hyp + return sum([coeff * _closed_form(pfq) for coeff, pfq in new._deflated()]) diff --git a/src/sage/functions/orthogonal_polys.py b/src/sage/functions/orthogonal_polys.py index 7b5adcb1ba0..3c9a1e6daa7 100644 --- a/src/sage/functions/orthogonal_polys.py +++ b/src/sage/functions/orthogonal_polys.py @@ -721,9 +721,9 @@ def _maxima_init_evaled_(self, n, x): sage: var('n, x') (n, x) sage: chebyshev_T._maxima_init_evaled_(1,x) - 'x' + '_SAGE_VAR_x' sage: maxima(chebyshev_T(n, chebyshev_T(n, x))) - chebyshev_t(n,chebyshev_t(n,x)) + chebyshev_t(_SAGE_VAR_n,chebyshev_t(_SAGE_VAR_n,_SAGE_VAR_x)) """ return maxima.eval('chebyshev_t({0},{1})'.format(n._maxima_init_(), x._maxima_init_())) @@ -1030,11 +1030,11 @@ def _maxima_init_evaled_(self, n, x): sage: var('n, x') (n, x) sage: maxima(chebyshev_U(5,x)) - 32*x^5-32*x^3+6*x + 32*_SAGE_VAR_x^5-32*_SAGE_VAR_x^3+6*_SAGE_VAR_x sage: maxima(chebyshev_U(n,x)) - chebyshev_u(n,x) + chebyshev_u(_SAGE_VAR_n,_SAGE_VAR_x) sage: maxima(chebyshev_U(2,x)) - 4*x^2-1 + 4*_SAGE_VAR_x^2-1 """ return maxima.eval('chebyshev_u({0},{1})'.format(n._maxima_init_(), x._maxima_init_())) diff --git a/src/sage/functions/other.py b/src/sage/functions/other.py index ed917719340..980738f8571 100644 --- a/src/sage/functions/other.py +++ b/src/sage/functions/other.py @@ -243,7 +243,7 @@ def _derivative_(self, x, diff_param=None): sage: derivative(erf(c*x),x) 2*c*e^(-c^2*x^2)/sqrt(pi) sage: erf(c*x).diff(x)._maxima_init_() - '((%pi)^(-1/2))*(c)*(exp(((c)^(2))*((x)^(2))*(-1)))*(2)' + '((%pi)^(-1/2))*(_SAGE_VAR_c)*(exp(((_SAGE_VAR_c)^(2))*((_SAGE_VAR_x)^(2))*(-1)))*(2)' """ return 2*exp(-x**2)/sqrt(pi) @@ -1276,7 +1276,7 @@ def __init__(self): sage: factorial._maxima_init_() 'factorial' sage: maxima(factorial(z)) - factorial(z) + factorial(_SAGE_VAR_z) sage: _.sage() factorial(z) sage: k = var('k') @@ -1428,7 +1428,7 @@ def __init__(self): sage: n,k = var('n,k') sage: maxima(binomial(n,k)) - binomial(n,k) + binomial(_SAGE_VAR_n,_SAGE_VAR_k) sage: _.sage() binomial(n, k) sage: binomial._maxima_init_() @@ -1796,7 +1796,7 @@ def __init__(self): sage: latex(arg(x)) {\rm arg}\left(x\right) sage: maxima(arg(x)) - atan2(0,x) + atan2(0,_SAGE_VAR_x) sage: maxima(arg(2+i)) atan(1/2) sage: maxima(arg(sqrt(2)+i)) diff --git a/src/sage/functions/trig.py b/src/sage/functions/trig.py index 38a6c2fdba6..b0825c04602 100644 --- a/src/sage/functions/trig.py +++ b/src/sage/functions/trig.py @@ -36,6 +36,9 @@ def __init__(self): sage: conjugate(sin(x)) sin(conjugate(x)) + sage: sin(complex(1,1)) # rel tol 1e-15 + (1.2984575814159773+0.6349639147847361j) + """ GinacFunction.__init__(self, "sin", latex_name=r"\sin", conversions=dict(maxima='sin',mathematica='Sin')) @@ -73,6 +76,9 @@ def __init__(self): sage: conjugate(cos(x)) cos(conjugate(x)) + sage: cos(complex(1,1)) # rel tol 1e-15 + (0.8337300251311491-0.9888977057628651j) + """ GinacFunction.__init__(self, "cos", latex_name=r"\cos", conversions=dict(maxima='cos',mathematica='Cos')) @@ -114,6 +120,9 @@ def __init__(self): sage: conjugate(tan(x)) tan(conjugate(x)) + sage: tan(complex(1,1)) # rel tol 1e-15 + (0.2717525853195118+1.0839233273386946j) + """ GinacFunction.__init__(self, "tan", latex_name=r"\tan") @@ -162,6 +171,14 @@ def _evalf_(self, x, parent=None, algorithm=None): 1.4142135623730950488016887242 sage: float(sec(pi/4)) 1.4142135623730951 + + TESTS: + + Test complex input:: + + sage: sec(complex(1,1)) # rel tol 1e-15 + (0.49833703055518686+0.5910838417210451j) + """ if parent is float: return 1/math.cos(x) @@ -253,6 +270,14 @@ def _evalf_(self, x, parent=None, algorithm=None): 1.4142135623730950488016887242 sage: float(csc(pi/4)) 1.4142135623730951 + + TESTS: + + Test complex input:: + + sage: csc(complex(1,1)) # rel tol 1e-15 + (0.6215180171704284-0.30393100162842646j) + """ if parent is float: return 1/math.sin(x) @@ -373,6 +398,14 @@ def _evalf_(self, x, parent=None, algorithm=None): 1.0000000000000000000000000000 sage: float(cot(1)) 0.64209261593433... + + TESTS: + + Test complex input:: + + sage: cot(complex(1,1)) # rel tol 1e-15 + (0.21762156185440273-0.8680141428959249j) + """ if parent is float: return 1/math.tan(x) @@ -598,11 +631,25 @@ def _evalf_(self, x, parent=None, algorithm=None): 1.1071487177940905030170654602 sage: float(arccot(1/2)) 1.1071487177940904 + + TESTS: + + Test complex input:: + + sage: arccot(complex(1,1)) # rel tol 1e-15 + (0.5535743588970452-0.4023594781085251j) + """ if parent is float: return math.pi/2 - math.atan(x) + from sage.symbolic.constants import pi - return parent(pi/2 - x.arctan()) + try: + return parent(pi/2 - x.arctan()) + except AttributeError: + # Usually this means that x is of type 'complex' + from sage.rings.complex_double import CDF + return complex(pi/2 - CDF(x).arctan()) def _eval_numpy_(self, x): """ @@ -666,10 +713,24 @@ def _evalf_(self, x, parent=None, algorithm=None): 0.52359877559829887307710723055 sage: float(arccsc(2)) 0.52359877559829... + + TESTS: + + Test complex input:: + + sage: arccsc(complex(1,1)) # rel tol 1e-15 + (0.45227844715119064-0.5306375309525178j) + """ if parent is float: return math.asin(1/x) - return (1/x).arcsin() + + try: + return (1/x).arcsin() + except AttributeError: + # Usually this means that x is of type 'complex' + from sage.rings.complex_double import CDF + return complex(CDF(1/x).arcsin()) def _eval_numpy_(self, x): """ @@ -728,10 +789,24 @@ def _evalf_(self, x, parent=None, algorithm=None): sage: arcsec(2).n(100) 1.0471975511965977461542144611 + + TESTS: + + Test complex input:: + + sage: arcsec(complex(1,1)) # rel tol 1e-15 + (1.118517879643706+0.5306375309525178j) + """ if parent is float: return math.acos(1/x) - return (1/x).arccos() + + try: + return (1/x).arccos() + except AttributeError: + # Usually this means that x is of type 'complex' + from sage.rings.complex_double import CDF + return complex(CDF(1/x).arccos()) def _eval_numpy_(self, x): """ diff --git a/src/sage/game_theory/__init__.py b/src/sage/game_theory/__init__.py new file mode 100644 index 00000000000..c9fecacd721 --- /dev/null +++ b/src/sage/game_theory/__init__.py @@ -0,0 +1 @@ +import all diff --git a/src/sage/game_theory/all.py b/src/sage/game_theory/all.py new file mode 100644 index 00000000000..2357d08a931 --- /dev/null +++ b/src/sage/game_theory/all.py @@ -0,0 +1 @@ +from cooperative_game import CooperativeGame diff --git a/src/sage/game_theory/cooperative_game.py b/src/sage/game_theory/cooperative_game.py new file mode 100644 index 00000000000..dcd4b9de1f1 --- /dev/null +++ b/src/sage/game_theory/cooperative_game.py @@ -0,0 +1,863 @@ +""" +Co-operative Games With Finite Players + +This module implements a class for a characteristic function cooperative +game. Methods to calculate the Shapley value (a fair way of sharing +common resources: see [CEW2011]_) as well as test properties of the game +(monotonicity, superadditivity) are also included. + +AUTHORS: + +- James Campbell and Vince Knight (06-2014): Original version +""" + +#***************************************************************************** +# Copyright (C) 2014 James Campbell james.campbell@tanti.org.uk +# +# 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 3 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from itertools import permutations, combinations +from sage.misc.misc import powerset +from sage.rings.integer import Integer +from sage.structure.sage_object import SageObject + + +class CooperativeGame(SageObject): + r""" + An object representing a co-operative game. Primarily used to compute the + Shapley value, but can also provide other information. + + INPUT: + + - ``characteristic_function`` -- a dictionary containing all possible + sets of players: + + * key - each set must be entered as a tuple. + * value - a real number representing each set of players contribution + + EXAMPLES: + + The type of game that is currently implemented is referred to as a + Characteristic function game. This is a game on a set of players + `\Omega` that is defined by a value function `v : C \to \RR` where + `C = 2^{\Omega}` is the set of all coalitions of players. + Let `N := |\Omega|`. + An example of such a game is shown below: + + .. MATH:: + + v(c) = \begin{cases} + 0 &\text{if } c = \emptyset, \\ + 6 &\text{if } c = \{1\}, \\ + 12 &\text{if } c = \{2\}, \\ + 42 &\text{if } c = \{3\}, \\ + 12 &\text{if } c = \{1,2\}, \\ + 42 &\text{if } c = \{1,3\}, \\ + 42 &\text{if } c = \{2,3\}, \\ + 42 &\text{if } c = \{1,2,3\}. \\ + \end{cases} + + The function `v` can be thought of as as a record of contribution of + individuals and coalitions of individuals. Of interest, becomes how to + fairly share the value of the grand coalition (`\Omega`)? This class + allows for such an answer to be formulated by calculating the Shapley + value of the game. + + Basic examples of how to implement a co-operative game. These functions + will be used repeatedly in other examples. :: + + sage: integer_function = {(): 0, + ....: (1,): 6, + ....: (2,): 12, + ....: (3,): 42, + ....: (1, 2,): 12, + ....: (1, 3,): 42, + ....: (2, 3,): 42, + ....: (1, 2, 3,): 42} + sage: integer_game = CooperativeGame(integer_function) + + We can also use strings instead of numbers. :: + + sage: letter_function = {(): 0, + ....: ('A',): 6, + ....: ('B',): 12, + ....: ('C',): 42, + ....: ('A', 'B',): 12, + ....: ('A', 'C',): 42, + ....: ('B', 'C',): 42, + ....: ('A', 'B', 'C',): 42} + sage: letter_game = CooperativeGame(letter_function) + + Please note that keys should be tuples. ``'1, 2, 3'`` is not a valid key, + neither is ``123``. The correct input would be ``(1, 2, 3)``. Similarly, + for coalitions containing a single element the bracket notation (which + tells Sage that it is a tuple) must be used. So ``(1)``, ``(1,)`` are + correct however simply inputting `1` is not. + + Characteristic function games can be of various types. + + A characteristic function game `G = (N, v)` is monotone if it satisfies + `v(C_2) \geq v(C_1)` for all `C_1 \subseteq C_2`. A characteristic + function game `G = (N, v)` is superadditive if it satisfies + `v(C_1 \cup C_2) \geq v(C_1) + v(C_2)` for all `C_1, C_2 \subseteq 2^{\Omega}` such + that `C_1 \cap C_2 = \emptyset`. + + We can test if a game is monotonic or superadditive. :: + + sage: letter_game.is_monotone() + True + sage: letter_game.is_superadditive() + False + + Instances have a basic representation that will display basic information + about the game:: + + sage: letter_game + A 3 player co-operative game + + It can be shown that the "fair" payoff vector, referred to as the + Shapley value is given by the following formula: + + .. MATH:: + + \phi_i(G) = \frac{1}{N!} \sum_{\pi\in\Pi_n} \Delta_{\pi}^G(i), + + where the summation is over the permutations of the players and the + marginal contributions of a player for a given permutation is given as: + + .. MATH:: + + \Delta_{\pi}^G(i) = v\bigl( S_{\pi}(i) \cup \{i\} \bigr) + - v\bigl( S_{\pi}(i) \bigr) + + where `S_{\pi}(i)` is the set of predecessors of `i` in `\pi`, i.e. + `S_{\pi}(i) = \{ j \mid \pi(i) > \pi(j) \}` (or the number of inversions + of the form `(i, j)`). + + This payoff vector is "fair" in that it has a collection of properties + referred to as: efficiency, symmetry, additivity and Null player. + Some of these properties are considered in this documentation (and tests + are implemented in the class) but for a good overview see [CEW2011]_. + + Note ([MSZ2013]_) that an equivalent formula for the Shapley value is given by: + + .. MATH:: + + \phi_i(G) = \sum_{S \subseteq \Omega} \sum_{p \in S} + \frac{(|S|-1)!(N-|S|)!}{N!} \bigl( v(S) - v(S \setminus \{p\}) \bigr) + = \sum_{S \subseteq \Omega} \sum_{p \in S} + \frac{1}{|S|\binom{N}{|S|}} \bigl( v(S) - v(S \setminus \{p\}) \bigr). + + This later formulation is implemented in Sage and + requires `2^N-1` calculations instead of `N!`. + + To compute the Shapley value in Sage is simple:: + + sage: letter_game.shapley_value() + {'A': 2, 'C': 35, 'B': 5} + + The following example implements a (trivial) 10 player characteristic + function game with `v(c) = |c|` for all `c \in 2^{\Omega}`. + + :: + + sage: def simple_characteristic_function(N): + ....: return {tuple(coalition) : len(coalition) + ....: for coalition in subsets(range(N))} + sage: g = CooperativeGame(simple_characteristic_function(10)) + sage: g.shapley_value() + {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1} + + For very large games it might be worth taking advantage of the particular + problem structure to calculate the Shapley value and there are also + various approximation approaches to obtaining the Shapley value of a game + (see [SWJ2008]_ for one such example). Implementing these would be a + worthwhile development For more information about the computational + complexity of calculating the Shapley value see [XP1994]_. + + We can test 3 basic properties of any payoff vector `\lambda`. + The Shapley value (described above) is known to be the unique + payoff vector that satisfies these and 1 other property + not implemented here (additivity). They are: + + * Efficiency - `\sum_{i=1}^N \lambda_i = v(\Omega)` + In other words, no value of the total coalition is lost. + + * The nullplayer property - If there exists an `i` such that + `v(C \cup i) = v(C)` for all `C \in 2^{\Omega}` then, `\lambda_i = 0`. + In other words: if a player does not contribute to any coalition then + that player should receive no payoff. + + * Symmetry property - If `v(C \cup i) = v(C \cup j)` for all + `C \in 2^{\Omega} \setminus \{i,j\}`, then `x_i = x_j`. + If players contribute symmetrically then they should get the same + payoff:: + + sage: payoff_vector = letter_game.shapley_value() + sage: letter_game.is_efficient(payoff_vector) + True + sage: letter_game.nullplayer(payoff_vector) + True + sage: letter_game.is_symmetric(payoff_vector) + True + + Any payoff vector can be passed to the game and these properties + can once again be tested:: + + sage: payoff_vector = {'A': 0, 'C': 35, 'B': 3} + sage: letter_game.is_efficient(payoff_vector) + False + sage: letter_game.nullplayer(payoff_vector) + True + sage: letter_game.is_symmetric(payoff_vector) + True + + TESTS: + + Check that the order within a key does not affect other functions:: + + sage: letter_function = {(): 0, + ....: ('A',): 6, + ....: ('B',): 12, + ....: ('C',): 42, + ....: ('A', 'B',): 12, + ....: ('C', 'A',): 42, + ....: ('B', 'C',): 42, + ....: ('B', 'A', 'C',): 42} + sage: letter_game = CooperativeGame(letter_function) + sage: letter_game.shapley_value() + {'A': 2, 'C': 35, 'B': 5} + sage: letter_game.is_monotone() + True + sage: letter_game.is_superadditive() + False + sage: letter_game.is_efficient({'A': 2, 'C': 35, 'B': 5}) + True + sage: letter_game.nullplayer({'A': 2, 'C': 35, 'B': 5}) + True + sage: letter_game.is_symmetric({'A': 2, 'C': 35, 'B': 5}) + True + + Any payoff vector can be passed to the game and these properties can once + again be tested. :: + + sage: letter_game.is_efficient({'A': 0, 'C': 35, 'B': 3}) + False + sage: letter_game.nullplayer({'A': 0, 'C': 35, 'B': 3}) + True + sage: letter_game.is_symmetric({'A': 0, 'C': 35, 'B': 3}) + True + + REFERENCES: + + .. [CEW2011] Georgios Chalkiadakis, Edith Elkind, and Michael Wooldridge. + *Computational Aspects of Cooperative Game Theory*. + Morgan & Claypool Publishers, (2011). + ISBN 9781608456529, :doi:`10.2200/S00355ED1V01Y201107AIM016`. + + .. [MSZ2013] Michael Maschler, Solan Eilon, and Zamir Shmuel. + *Game Theory*. + Cambridge: Cambridge University Press, (2013). + ISBN 9781107005488. + + .. [XP1994] Deng Xiaotie, and Christos Papadimitriou. + *On the complexity of cooperative solution concepts.* + Mathematics of Operations Research 19.2 (1994): 257-266. + + .. [SWJ2008] Fatima Shaheen, Michael Wooldridge, and Nicholas Jennings. + *A linear approximation method for the Shapley value.* + Artificial Intelligence 172.14 (2008): 1673-1699. + """ + def __init__(self, characteristic_function): + r""" + Initializes a co-operative game and checks the inputs. + + TESTS: + + An attempt to construct a game from an integer:: + + sage: int_game = CooperativeGame(4) + Traceback (most recent call last): + ... + TypeError: characteristic function must be a dictionary + + This test checks that an incorrectly entered singularly tuple will be + changed into a tuple. In this case ``(1)`` becomes ``(1,)``:: + + sage: tuple_function = {(): 0, + ....: (1): 6, + ....: (2,): 12, + ....: (3,): 42, + ....: (1, 2,): 12, + ....: (1, 3,): 42, + ....: (2, 3,): 42, + ....: (1, 2, 3,): 42} + sage: tuple_game = CooperativeGame(tuple_function) + + This test checks that if a key is not a tuple an error is raised:: + + sage: error_function = {(): 0, + ....: (1,): 6, + ....: (2,): 12, + ....: (3,): 42, + ....: 12: 12, + ....: (1, 3,): 42, + ....: (2, 3,): 42, + ....: (1, 2, 3,): 42} + sage: error_game = CooperativeGame(error_function) + Traceback (most recent call last): + ... + TypeError: key must be a tuple + + A test to ensure that the characteristic function is the power + set of the grand coalition (ie all possible sub-coalitions):: + + sage: incorrect_function = {(): 0, + ....: (1,): 6, + ....: (2,): 12, + ....: (3,): 42, + ....: (1, 2, 3,): 42} + sage: incorrect_game = CooperativeGame(incorrect_function) + Traceback (most recent call last): + ... + ValueError: characteristic function must be the power set + """ + if type(characteristic_function) is not dict: + raise TypeError("characteristic function must be a dictionary") + + self.ch_f = characteristic_function + for key in self.ch_f: + if len(str(key)) == 1 and type(key) is not tuple: + self.ch_f[(key,)] = self.ch_f.pop(key) + elif type(key) is not tuple: + raise TypeError("key must be a tuple") + for key in self.ch_f: + sortedkey = tuple(sorted(list(key))) + self.ch_f[sortedkey] = self.ch_f.pop(key) + + self.player_list = max(characteristic_function.keys(), key=lambda key: len(key)) + for coalition in powerset(self.player_list): + if tuple(sorted(list(coalition))) not in sorted(self.ch_f.keys()): + raise ValueError("characteristic function must be the power set") + + self.number_players = len(self.player_list) + + def shapley_value(self): + r""" + Return the Shapley value for ``self``. + + The Shapley value is the "fair" payoff vector and + is computed by the following formula: + + .. MATH:: + + \phi_i(G) = \sum_{S \subseteq \Omega} \sum_{p \in S} + \frac{1}{|S|\binom{N}{|S|}} + \bigl( v(S) - v(S \setminus \{p\}) \bigr). + + EXAMPLES: + + A typical example of computing the Shapley value:: + + sage: integer_function = {(): 0, + ....: (1,): 6, + ....: (2,): 12, + ....: (3,): 42, + ....: (1, 2,): 12, + ....: (1, 3,): 42, + ....: (2, 3,): 42, + ....: (1, 2, 3,): 42} + sage: integer_game = CooperativeGame(integer_function) + sage: integer_game.player_list + (1, 2, 3) + sage: integer_game.shapley_value() + {1: 2, 2: 5, 3: 35} + + A longer example of the Shapley value:: + + sage: long_function = {(): 0, + ....: (1,): 0, + ....: (2,): 0, + ....: (3,): 0, + ....: (4,): 0, + ....: (1, 2): 0, + ....: (1, 3): 0, + ....: (1, 4): 0, + ....: (2, 3): 0, + ....: (2, 4): 0, + ....: (3, 4): 0, + ....: (1, 2, 3): 0, + ....: (1, 2, 4): 45, + ....: (1, 3, 4): 40, + ....: (2, 3, 4): 0, + ....: (1, 2, 3, 4): 65} + sage: long_game = CooperativeGame(long_function) + sage: long_game.shapley_value() + {1: 70/3, 2: 10, 3: 25/3, 4: 70/3} + """ + payoff_vector = {} + n = Integer(len(self.player_list)) + for player in self.player_list: + weighted_contribution = 0 + for coalition in powerset(self.player_list): + if coalition: # If non-empty + k = Integer(len(coalition)) + weight = 1 / (n.binomial(k) * k) + t = tuple(p for p in coalition if p != player) + weighted_contribution += weight * (self.ch_f[tuple(coalition)] + - self.ch_f[t]) + payoff_vector[player] = weighted_contribution + + return payoff_vector + + def is_monotone(self): + r""" + Return ``True`` if ``self`` is monotonic. + + A game `G = (N, v)` is monotonic if it satisfies + `v(C_2) \geq v(C_1)` for all `C_1 \subseteq C_2`. + + EXAMPLES: + + A simple game that is monotone:: + + sage: integer_function = {(): 0, + ....: (1,): 6, + ....: (2,): 12, + ....: (3,): 42, + ....: (1, 2,): 12, + ....: (1, 3,): 42, + ....: (2, 3,): 42, + ....: (1, 2, 3,): 42} + sage: integer_game = CooperativeGame(integer_function) + sage: integer_game.is_monotone() + True + + An example when the game is not monotone:: + + sage: integer_function = {(): 0, + ....: (1,): 6, + ....: (2,): 12, + ....: (3,): 42, + ....: (1, 2,): 10, + ....: (1, 3,): 42, + ....: (2, 3,): 42, + ....: (1, 2, 3,): 42} + sage: integer_game = CooperativeGame(integer_function) + sage: integer_game.is_monotone() + False + + An example on a longer game:: + + sage: long_function = {(): 0, + ....: (1,): 0, + ....: (2,): 0, + ....: (3,): 0, + ....: (4,): 0, + ....: (1, 2): 0, + ....: (1, 3): 0, + ....: (1, 4): 0, + ....: (2, 3): 0, + ....: (2, 4): 0, + ....: (3, 4): 0, + ....: (1, 2, 3): 0, + ....: (1, 2, 4): 45, + ....: (1, 3, 4): 40, + ....: (2, 3, 4): 0, + ....: (1, 2, 3, 4): 65} + sage: long_game = CooperativeGame(long_function) + sage: long_game.is_monotone() + True + """ + return not any([set(p1) <= set(p2) and self.ch_f[p1] > self.ch_f[p2] + for p1, p2 in permutations(self.ch_f.keys(), 2)]) + + def is_superadditive(self): + r""" + Return ``True`` if ``self`` is superadditive. + + A characteristic function game `G = (N, v)` is superadditive + if it satisfies `v(C_1 \cup C_2) \geq v(C_1) + v(C_2)` for + all `C_1, C_2 \subseteq 2^{\Omega}` such that `C_1 \cap C_2 + = \emptyset`. + + EXAMPLES: + + An example that is not superadditive:: + + sage: integer_function = {(): 0, + ....: (1,): 6, + ....: (2,): 12, + ....: (3,): 42, + ....: (1, 2,): 12, + ....: (1, 3,): 42, + ....: (2, 3,): 42, + ....: (1, 2, 3,): 42} + sage: integer_game = CooperativeGame(integer_function) + sage: integer_game.is_superadditive() + False + + An example that is superadditive:: + + sage: A_function = {(): 0, + ....: (1,): 6, + ....: (2,): 12, + ....: (3,): 42, + ....: (1, 2,): 18, + ....: (1, 3,): 48, + ....: (2, 3,): 55, + ....: (1, 2, 3,): 80} + sage: A_game = CooperativeGame(A_function) + sage: A_game.is_superadditive() + True + + An example with a longer game that is superadditive:: + + sage: long_function = {(): 0, + ....: (1,): 0, + ....: (2,): 0, + ....: (3,): 0, + ....: (4,): 0, + ....: (1, 2): 0, + ....: (1, 3): 0, + ....: (1, 4): 0, + ....: (2, 3): 0, + ....: (2, 4): 0, + ....: (3, 4): 0, + ....: (1, 2, 3): 0, + ....: (1, 2, 4): 45, + ....: (1, 3, 4): 40, + ....: (2, 3, 4): 0, + ....: (1, 2, 3, 4): 65} + sage: long_game = CooperativeGame(long_function) + sage: long_game.is_superadditive() + True + + An example with a longer game that is not:: + + sage: long_function = {(): 0, + ....: (1,): 0, + ....: (2,): 0, + ....: (3,): 55, + ....: (4,): 0, + ....: (1, 2): 0, + ....: (1, 3): 0, + ....: (1, 4): 0, + ....: (2, 3): 0, + ....: (2, 4): 0, + ....: (3, 4): 0, + ....: (1, 2, 3): 0, + ....: (1, 2, 4): 45, + ....: (1, 3, 4): 40, + ....: (2, 3, 4): 0, + ....: (1, 2, 3, 4): 85} + sage: long_game = CooperativeGame(long_function) + sage: long_game.is_superadditive() + False + """ + sets = self.ch_f.keys() + for p1, p2 in combinations(sets, 2): + if not (set(p1) & set(p2)): + union = tuple(sorted(set(p1) | set(p2))) + if self.ch_f[union] < self.ch_f[p1] + self.ch_f[p2]: + return False + return True + + def _repr_(self): + r""" + Return a concise description of ``self``. + + EXAMPLES:: + + sage: letter_function = {(): 0, + ....: ('A',): 6, + ....: ('B',): 12, + ....: ('C',): 42, + ....: ('A', 'B',): 12, + ....: ('A', 'C',): 42, + ....: ('B', 'C',): 42, + ....: ('A', 'B', 'C',): 42} + sage: letter_game = CooperativeGame(letter_function) + sage: letter_game + A 3 player co-operative game + """ + return "A {} player co-operative game".format(self.number_players) + + def _latex_(self): + r""" + Return the LaTeX code representing the characteristic function. + + EXAMPLES:: + + sage: letter_function = {(): 0, + ....: ('A',): 6, + ....: ('B',): 12, + ....: ('C',): 42, + ....: ('A', 'B',): 12, + ....: ('A', 'C',): 42, + ....: ('B', 'C',): 42, + ....: ('A', 'B', 'C',): 42} + sage: letter_game = CooperativeGame(letter_function) + sage: latex(letter_game) + v(c) = \begin{cases} + 0, & \text{if } c = \emptyset \\ + 6, & \text{if } c = \{A\} \\ + 42, & \text{if } c = \{C\} \\ + 12, & \text{if } c = \{B\} \\ + 42, & \text{if } c = \{B, C\} \\ + 12, & \text{if } c = \{A, B\} \\ + 42, & \text{if } c = \{A, C\} \\ + 42, & \text{if } c = \{A, B, C\} \\ + \end{cases} + """ + cf = self.ch_f + output = "v(c) = \\begin{cases}\n" + for key in sorted(cf.keys(), key=lambda key: len(key)): + if not key: # == () + coalition = "\\emptyset" + else: + coalition = "\\{" + ", ".join(str(player) for player in key) + "\\}" + output += "{}, & \\text{{if }} c = {} \\\\\n".format(cf[key], coalition) + output += "\\end{cases}" + return output + + def is_efficient(self, payoff_vector): + r""" + Return ``True`` if ``payoff_vector`` is efficient. + + A payoff vector `v` is efficient if + `\sum_{i=1}^N \lambda_i = v(\Omega)`; + in other words, no value of the total coalition is lost. + + INPUT: + + - ``payoff_vector`` -- a dictionary where the key is the player + and the value is their payoff + + EXAMPLES: + + An efficient payoff vector:: + + sage: letter_function = {(): 0, + ....: ('A',): 6, + ....: ('B',): 12, + ....: ('C',): 42, + ....: ('A', 'B',): 12, + ....: ('A', 'C',): 42, + ....: ('B', 'C',): 42, + ....: ('A', 'B', 'C',): 42} + sage: letter_game = CooperativeGame(letter_function) + sage: letter_game.is_efficient({'A': 14, 'B': 14, 'C': 14}) + True + + sage: letter_function = {(): 0, + ....: ('A',): 6, + ....: ('B',): 12, + ....: ('C',): 42, + ....: ('A', 'B',): 12, + ....: ('A', 'C',): 42, + ....: ('B', 'C',): 42, + ....: ('A', 'B', 'C',): 42} + sage: letter_game = CooperativeGame(letter_function) + sage: letter_game.is_efficient({'A': 10, 'B': 14, 'C': 14}) + False + + A longer example:: + + sage: long_function = {(): 0, + ....: (1,): 0, + ....: (2,): 0, + ....: (3,): 0, + ....: (4,): 0, + ....: (1, 2): 0, + ....: (1, 3): 0, + ....: (1, 4): 0, + ....: (2, 3): 0, + ....: (2, 4): 0, + ....: (3, 4): 0, + ....: (1, 2, 3): 0, + ....: (1, 2, 4): 45, + ....: (1, 3, 4): 40, + ....: (2, 3, 4): 0, + ....: (1, 2, 3, 4): 65} + sage: long_game = CooperativeGame(long_function) + sage: long_game.is_efficient({1: 20, 2: 20, 3: 5, 4: 20}) + True + """ + pl = tuple(sorted(list(self.player_list))) + return sum(payoff_vector.values()) == self.ch_f[pl] + + def nullplayer(self, payoff_vector): + r""" + Return ``True`` if ``payoff_vector`` possesses the nullplayer + property. + + A payoff vector `v` has the nullplayer property if there exists + an `i` such that `v(C \cup i) = v(C)` for all `C \in 2^{\Omega}` + then, `\lambda_i = 0`. In other words: if a player does not + contribute to any coalition then that player should receive no payoff. + + INPUT: + + - ``payoff_vector`` -- a dictionary where the key is the player + and the value is their payoff + + EXAMPLES: + + A payoff vector that returns ``True``:: + + sage: letter_function = {(): 0, + ....: ('A',): 0, + ....: ('B',): 12, + ....: ('C',): 42, + ....: ('A', 'B',): 12, + ....: ('A', 'C',): 42, + ....: ('B', 'C',): 42, + ....: ('A', 'B', 'C',): 42} + sage: letter_game = CooperativeGame(letter_function) + sage: letter_game.nullplayer({'A': 0, 'B': 14, 'C': 14}) + True + + A payoff vector that returns ``False``:: + + sage: A_function = {(): 0, + ....: (1,): 0, + ....: (2,): 12, + ....: (3,): 42, + ....: (1, 2,): 12, + ....: (1, 3,): 42, + ....: (2, 3,): 55, + ....: (1, 2, 3,): 55} + sage: A_game = CooperativeGame(A_function) + sage: A_game.nullplayer({1: 10, 2: 10, 3: 25}) + False + + A longer example for nullplayer:: + + sage: long_function = {(): 0, + ....: (1,): 0, + ....: (2,): 0, + ....: (3,): 0, + ....: (4,): 0, + ....: (1, 2): 0, + ....: (1, 3): 0, + ....: (1, 4): 0, + ....: (2, 3): 0, + ....: (2, 4): 0, + ....: (3, 4): 0, + ....: (1, 2, 3): 0, + ....: (1, 2, 4): 45, + ....: (1, 3, 4): 40, + ....: (2, 3, 4): 0, + ....: (1, 2, 3, 4): 65} + sage: long_game = CooperativeGame(long_function) + sage: long_game.nullplayer({1: 20, 2: 20, 3: 5, 4: 20}) + True + + TESTS: + + Checks that the function is going through all players:: + + sage: A_function = {(): 0, + ....: (1,): 42, + ....: (2,): 12, + ....: (3,): 0, + ....: (1, 2,): 55, + ....: (1, 3,): 42, + ....: (2, 3,): 12, + ....: (1, 2, 3,): 55} + sage: A_game = CooperativeGame(A_function) + sage: A_game.nullplayer({1: 10, 2: 10, 3: 25}) + False + """ + for player in self.player_list: + results = [] + for coalit in self.ch_f: + if player in coalit: + t = tuple(sorted(set(coalit) - {player})) + results.append(self.ch_f[coalit] == self.ch_f[t]) + if all(results) and payoff_vector[player] != 0: + return False + return True + + def is_symmetric(self, payoff_vector): + r""" + Return ``True`` if ``payoff_vector`` possesses the symmetry property. + + A payoff vector possesses the symmetry property if + `v(C \cup i) = v(C \cup j)` for all + `C \in 2^{\Omega} \setminus \{i,j\}`, then `x_i = x_j`. + + INPUT: + + - ``payoff_vector`` -- a dictionary where the key is the player + and the value is their payoff + + EXAMPLES: + + A payoff pector that has the symmetry property:: + + sage: letter_function = {(): 0, + ....: ('A',): 6, + ....: ('B',): 12, + ....: ('C',): 42, + ....: ('A', 'B',): 12, + ....: ('A', 'C',): 42, + ....: ('B', 'C',): 42, + ....: ('A', 'B', 'C',): 42} + sage: letter_game = CooperativeGame(letter_function) + sage: letter_game.is_symmetric({'A': 5, 'B': 14, 'C': 20}) + True + + A payoff vector that returns ``False``:: + + sage: integer_function = {(): 0, + ....: (1,): 12, + ....: (2,): 12, + ....: (3,): 42, + ....: (1, 2,): 12, + ....: (1, 3,): 42, + ....: (2, 3,): 42, + ....: (1, 2, 3,): 42} + sage: integer_game = CooperativeGame(integer_function) + sage: integer_game.is_symmetric({1: 2, 2: 5, 3: 35}) + False + + A longer example for symmetry:: + + sage: long_function = {(): 0, + ....: (1,): 0, + ....: (2,): 0, + ....: (3,): 0, + ....: (4,): 0, + ....: (1, 2): 0, + ....: (1, 3): 0, + ....: (1, 4): 0, + ....: (2, 3): 0, + ....: (2, 4): 0, + ....: (3, 4): 0, + ....: (1, 2, 3): 0, + ....: (1, 2, 4): 45, + ....: (1, 3, 4): 40, + ....: (2, 3, 4): 0, + ....: (1, 2, 3, 4): 65} + sage: long_game = CooperativeGame(long_function) + sage: long_game.is_symmetric({1: 20, 2: 20, 3: 5, 4: 20}) + True + """ + sets = self.ch_f.keys() + element = [i for i in sets if len(i) == 1] + for c1, c2 in combinations(element, 2): + results = [] + for m in sets: + junion = tuple(sorted(set(c1) | set(m))) + kunion = tuple(sorted(set(c2) | set(m))) + results.append(self.ch_f[junion] == self.ch_f[kunion]) + if all(results) and payoff_vector[c1[0]] != payoff_vector[c2[0]]: + return False + return True + diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index 60586481f2b..5d278c3662e 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -2841,7 +2841,7 @@ def line_set(self): sage: halfplane = Cone([(1,0), (0,1), (-1,0)]) sage: halfplane.line_set() - doctest:1: DeprecationWarning: + doctest:...: DeprecationWarning: line_set(...) is deprecated, please use lines().set() instead! See http://trac.sagemath.org/12544 for details. frozenset([N(1, 0)]) @@ -3853,7 +3853,7 @@ def Hilbert_coefficients(self, point): from sage.numerical.mip import MixedIntegerLinearProgram p = MixedIntegerLinearProgram(maximization=False) p.set_objective(None) - x = p.new_variable(integer=True) + x = p.new_variable(integer=True, nonnegative=True) x = [ x[i] for i in range(0,len(basis)) ] for i in range(0,self.lattice_dim()): p.add_constraint(sum(b[i]*x[j] for j,b in enumerate(basis)) == point[i]) diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index 3eb74a7bd11..ce5135d84ba 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -2390,7 +2390,7 @@ def normal_form(self): sage: o = lattice_polytope.cross_polytope(3) sage: o.normal_form() - doctest:1: DeprecationWarning: normal_form() output will change, + doctest:...: DeprecationWarning: normal_form() output will change, please use normal_form_pc().column_matrix() instead or consider using normal_form_pc() directly! See http://trac.sagemath.org/15240 for details. @@ -3216,7 +3216,7 @@ def points(self): sage: o = lattice_polytope.cross_polytope(3) sage: o.points() - doctest:1: DeprecationWarning: points() output will change, + doctest:...: DeprecationWarning: points() output will change, please use points_pc().column_matrix() instead or consider using points_pc() directly! See http://trac.sagemath.org/15240 for details. @@ -3616,7 +3616,7 @@ def vertices(self): sage: o = lattice_polytope.cross_polytope(3) sage: o.vertices() - doctest:1: DeprecationWarning: vertices() output will change, + doctest:...: DeprecationWarning: vertices() output will change, please use vertices_pc().column_matrix() instead or consider using vertices_pc() directly! See http://trac.sagemath.org/15240 for details. @@ -5395,7 +5395,7 @@ def always_use_files(new_state=None): EXAMPLES:: sage: lattice_polytope.always_use_files() - doctest:1: DeprecationWarning: using PALP via pipes is deprecated and + doctest:...: DeprecationWarning: using PALP via pipes is deprecated and will be removed, if you have a use case for this, please email Andrey Novoseltsev See http://trac.sagemath.org/15240 for details. @@ -5545,7 +5545,7 @@ def filter_polytopes(f, polytopes, subseq=None, print_numbers=False): This filters polytopes of dimension at least 4:: sage: lattice_polytope.filter_polytopes(lambda p: p.dim() >= 4, polytopes) - doctest:1: DeprecationWarning: filter_polytopes is deprecated, + doctest:...: DeprecationWarning: filter_polytopes is deprecated, use standard tools instead See http://trac.sagemath.org/15240 for details. [2, 3, 4] @@ -5737,7 +5737,7 @@ def projective_space(dim): EXAMPLES: We construct 3- and 4-dimensional simplexes:: sage: p = lattice_polytope.projective_space(3) - doctest:1: DeprecationWarning: this function is deprecated, + doctest:...: DeprecationWarning: this function is deprecated, perhaps toric_varieties.P(n) is what you are looking for? See http://trac.sagemath.org/15240 for details. sage: p diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 461395e88ea..d5eddb6aa8d 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -441,6 +441,7 @@ def _is_subpolyhedron(self, other): def plot(self, point=None, line=None, polygon=None, # None means unspecified by the user wireframe='blue', fill='green', + projection_direction=None, **kwds): """ Return a graphical representation. @@ -465,7 +466,13 @@ def plot(self, objects in the dimension of the polytope (or of dimension 2 for higher dimensional polytopes) and ``wireframe`` is used for all lower-dimensional graphics objects - (default: 'green' for fill and 'blue' for wireframe) + (default: 'green' for ``fill`` and 'blue' for ``wireframe``) + + - ``projection_direction`` -- coordinate list/tuple/iterable + or ``None`` (default). The direction to use for the + :meth:`schlegel_projection`` of the polytope. If not + specified, no projection is used in dimensions `< 4` and + parallel projection is used in dimension `4`. - ``**kwds`` -- optional keyword parameters that are passed to all graphics objects. @@ -572,6 +579,46 @@ def plot(self, sage: for p in point.plot(wireframe=False, fill="red"): ... print p.options()['rgbcolor'], p red Point set defined by 1 point(s) + + The ``projection_direction`` option:: + + sage: line3d = Polyhedron([(-1,-1,-1), (1,1,1)]) + sage: print(line3d.plot(projection_direction=[2,3,4]).description()) + Line defined by 2 points: [(-0.00..., 0.126...), (0.131..., -1.93...)] + Point set defined by 2 point(s): [(-0.00..., 0.126...), (0.131..., -1.93...)] + + We try to draw the polytope in 2 or 3 dimensions:: + + sage: type(Polyhedron(ieqs=[(1,)]).plot()) + + sage: type(polytopes.n_cube(1).plot()) + + sage: type(polytopes.n_cube(2).plot()) + + sage: type(polytopes.n_cube(3).plot()) + + + In 4d a projection to 3d is used:: + + sage: type(polytopes.n_cube(4).plot()) + + sage: type(polytopes.n_cube(5).plot()) + Traceback (most recent call last): + ... + NotImplementedError: plotting of 5-dimensional polyhedra not implemented + + If the polyhedron is not full-dimensional, the :meth:`affine_hull` is used if necessary:: + + sage: type(Polyhedron([(0,), (1,)]).plot()) + + sage: type(Polyhedron([(0,0), (1,1)]).plot()) + + sage: type(Polyhedron([(0,0,0), (1,1,1)]).plot()) + + sage: type(Polyhedron([(0,0,0,0), (1,1,1,1)]).plot()) + + sage: type(Polyhedron([(0,0,0,0,0), (1,1,1,1,1)]).plot()) + """ def merge_options(*opts): merged = dict() @@ -593,14 +640,26 @@ def merge_options(*opts): opts = [merge_options(opt1, opt2, kwds) for opt1, opt2 in zip(opts, [point, line, polygon])] - from plot import render_2d, render_3d, render_4d - render_method = [ None, None, render_2d, render_3d, render_4d ] - if self.ambient_dim() < len(render_method): - render = render_method[self.ambient_dim()] - if render is not None: - return render(self, *opts) - raise NotImplementedError('Plotting of '+str(self.ambient_dim())+ - '-dimensional polyhedra not implemented') + def project(polyhedron): + if projection_direction is not None: + return polyhedron.schlegel_projection(projection_direction) + elif polyhedron.ambient_dim() == 4: + # There is no 4-d screen, we must project down to 3d + return polyhedron.schlegel_projection() + else: + return polyhedron.projection() + + projection = project(self) + try: + plot_method = projection.plot + except AttributeError: + projection = project(self.affine_hull()) + try: + plot_method = projection.plot + except AttributeError: + raise NotImplementedError('plotting of {0}-dimensional polyhedra not implemented' + .format(self.ambient_dim())) + return plot_method(*opts) show = plot @@ -1619,6 +1678,8 @@ def dim(self): else: return self.ambient_dim() - self.n_equations() + dimension = dim + def is_empty(self): """ Test whether the polyhedron is the empty polyhedron @@ -3308,6 +3369,15 @@ def projection(self): """ Return a projection object. + See also + :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.schlegel_projection` + for a more interesting projection. + + OUTPUT: + + The identity projection. This is useful for plotting + polyhedra. + EXAMPLES:: sage: p = polytopes.n_cube(3) @@ -3356,10 +3426,33 @@ def render_wireframe(self, **kwds): return proj.render_outline_2d(**kwds) raise ValueError("render_wireframe is only defined for 2 and 3 dimensional polyhedra.") - def schlegel_projection(self, projection_dir = None, height = 1.1): + def schlegel_projection(self, projection_dir=None, height=1.1): """ - Returns a projection object whose transformed coordinates are - a Schlegel projection of the polyhedron. + Return the Schlegel projection. + + * The polyhedron is translated such that its + :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.center` + is at the origin. + + * The vertices are then normalized to the unit sphere + + * The normalized points are stereographically projected from a + point slightly outside of the sphere. + + INPUT: + + - ``projection_direction`` -- coordinate list/tuple/iterable + or ``None`` (default). The direction of the Schlegel + projection. For a full-dimensional polyhedron, the default + is the first facet normal; Otherwise, the vector consisting + of the first n primes is chosen. + + - ``height`` -- float (default: `1.1`). How far outside of the + unit sphere the focal point is. + + OUTPUT: + + A :class:`~sage.geometry.polyhedron.plot.Projection` object. EXAMPLES:: @@ -3377,7 +3470,7 @@ def schlegel_projection(self, projection_dir = None, height = 1.1): f0 = [ v.index() for v in facet.incident() ] projection_dir = [sum([vertices[f0[i]][j]/len(f0) for i in range(len(f0))]) for j in range(self.ambient_dim())] - return proj.schlegel(projection_direction = projection_dir, height = height) + return proj.schlegel(projection_direction=projection_dir, height=height) def _volume_lrs(self, verbose=False): """ @@ -4302,6 +4395,69 @@ def edge_label_noncompact(i,j,c_ij): self._restricted_automorphism_group = group return group + def is_full_dimensional(self): + """ + Return whether the polyhedron is full dimensional. + + OUTPUT: + Boolean. Whether the polyhedron is not contained in any strict + affine subspace. + EXAMPLES:: + + sage: polytopes.n_cube(3).is_full_dimensional() + True + sage: Polyhedron(vertices=[(1,2,3)], rays=[(1,0,0)]).is_full_dimensional() + False + """ + return self.dim() == self.ambient_dim() + + def affine_hull(self): + """ + Return the affine hull. + Each polyhedron is contained in some smallest affine subspace + (possibly the entire ambient space). The affine hull is the + same polyhedron but thought of as a full-dimensional + polyhedron in this subspace. + + OUTPUT: + + A full-dimensional polyhedron. + + EXAMPLES:: + + sage: triangle = Polyhedron([(1,0,0), (0,1,0), (0,0,1)]); triangle + A 2-dimensional polyhedron in ZZ^3 defined as the convex hull of 3 vertices + sage: triangle.affine_hull() + A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices + + sage: half3d = Polyhedron(vertices=[(3,2,1)], rays=[(1,0,0)]) + sage: half3d.affine_hull().Vrepresentation() + (A ray in the direction (1), A vertex at (3)) + + TESTS:: + + sage: Polyhedron([(2,3,4)]).affine_hull() + A 0-dimensional polyhedron in ZZ^0 defined as the convex hull of 1 vertex + """ + # translate one vertex to the origin + v0 = self.vertices()[0].vector() + gens = [] + for v in self.vertices()[1:]: + gens.append(v.vector() - v0) + for r in self.rays(): + gens.append(r.vector()) + for l in self.lines(): + gens.append(l.vector()) + + # Pick subset of coordinates to coordinatize the affine span + pivots = matrix(gens, base_ring=self.base_ring()).pivots() + def pivot(indexed): + return [indexed[i] for i in pivots] + + vertices = map(pivot, self.vertices()) + rays = map(pivot, self.rays()) + lines = map(pivot, self.lines()) + return Polyhedron(vertices=vertices, rays=rays, lines=lines, base_ring=self.base_ring()) diff --git a/src/sage/geometry/polyhedron/plot.py b/src/sage/geometry/polyhedron/plot.py index 07624500ba7..b19aef483ec 100644 --- a/src/sage/geometry/polyhedron/plot.py +++ b/src/sage/geometry/polyhedron/plot.py @@ -21,7 +21,7 @@ from sage.symbolic.constants import pi from sage.structure.sequence import Sequence -from sage.plot.all import point2d, line2d, arrow, polygon2d +from sage.plot.all import Graphics, point2d, line2d, arrow, polygon2d from sage.plot.plot3d.all import point3d, line3d, arrow3d, polygon3d from sage.plot.plot3d.transform import rotate_arbitrary @@ -30,7 +30,7 @@ ############################################################# -def render_2d(projection, point_opts={}, line_opts={}, polygon_opts={}): +def render_2d(projection, *args, **kwds): """ Return 2d rendering of the projection of a polyhedron into 2-dimensional ambient space. @@ -45,32 +45,23 @@ def render_2d(projection, point_opts={}, line_opts={}, polygon_opts={}): sage: q3 = p3.projection() sage: p4 = Polyhedron(vertices=[[2,0]], rays=[[1,-1]], lines=[[1,1]]) sage: q4 = p4.projection() - sage: q1.show() + q2.show() + q3.show() + q4.show() + sage: q1.plot() + q2.plot() + q3.plot() + q4.plot() sage: from sage.geometry.polyhedron.plot import render_2d sage: q = render_2d(p1.projection()) + doctest:...: DeprecationWarning: use Projection.render_2d instead + See http://trac.sagemath.org/16625 for details. sage: q._objects [Point set defined by 1 point(s), Arrow from (1.0,1.0) to (2.0,2.0), Polygon defined by 3 points] """ + from sage.misc.superseded import deprecation + deprecation(16625, 'use Projection.render_2d instead') if is_Polyhedron(projection): projection = Projection(projection) - from sage.plot.graphics import Graphics - plt = Graphics() - if isinstance(point_opts, dict): - point_opts.setdefault('zorder', 2) - point_opts.setdefault('pointsize', 10) - plt += projection.render_points_2d(**point_opts) - if isinstance(line_opts, dict): - line_opts.setdefault('zorder', 1) - plt += projection.render_outline_2d(**line_opts) - if isinstance(polygon_opts, dict): - polygon_opts.setdefault('zorder', 0) - plt += projection.render_fill_2d(**polygon_opts) - return plt - - -def render_3d(projection, point_opts={}, line_opts={}, polygon_opts={}): + return projection.render_2d(*args, **kwds) + +def render_3d(projection, *args, **kwds): """ Return 3d rendering of a polyhedron projected into 3-dimensional ambient space. @@ -85,32 +76,24 @@ def render_3d(projection, point_opts={}, line_opts={}, polygon_opts={}): sage: p1 = Polyhedron(vertices=[[1,1,1]], rays=[[1,1,1]]) sage: p2 = Polyhedron(vertices=[[2,0,0], [0,2,0], [0,0,2]]) sage: p3 = Polyhedron(vertices=[[1,0,0], [0,1,0], [0,0,1]], rays=[[-1,-1,-1]]) - sage: p1.projection().show() + p2.projection().show() + p3.projection().show() + sage: p1.projection().plot() + p2.projection().plot() + p3.projection().plot() It correctly handles various degenerate cases:: - sage: Polyhedron(lines=[[1,0,0],[0,1,0],[0,0,1]]).show() # whole space - sage: Polyhedron(vertices=[[1,1,1]], rays=[[1,0,0]], lines=[[0,1,0],[0,0,1]]).show() # half space - sage: Polyhedron(vertices=[[1,1,1]], lines=[[0,1,0],[0,0,1]]).show() # R^2 in R^3 - sage: Polyhedron(rays=[[0,1,0],[0,0,1]], lines=[[1,0,0]]).show() # quadrant wedge in R^2 - sage: Polyhedron(rays=[[0,1,0]], lines=[[1,0,0]]).show() # upper half plane in R^3 - sage: Polyhedron(lines=[[1,0,0]]).show() # R^1 in R^2 - sage: Polyhedron(rays=[[0,1,0]]).show() # Half-line in R^3 - sage: Polyhedron(vertices=[[1,1,1]]).show() # point in R^3 + sage: Polyhedron(lines=[[1,0,0],[0,1,0],[0,0,1]]).plot() # whole space + sage: Polyhedron(vertices=[[1,1,1]], rays=[[1,0,0]], lines=[[0,1,0],[0,0,1]]).plot() # half space + sage: Polyhedron(vertices=[[1,1,1]], lines=[[0,1,0],[0,0,1]]).plot() # R^2 in R^3 + sage: Polyhedron(rays=[[0,1,0],[0,0,1]], lines=[[1,0,0]]).plot() # quadrant wedge in R^2 + sage: Polyhedron(rays=[[0,1,0]], lines=[[1,0,0]]).plot() # upper half plane in R^3 + sage: Polyhedron(lines=[[1,0,0]]).plot() # R^1 in R^2 + sage: Polyhedron(rays=[[0,1,0]]).plot() # Half-line in R^3 + sage: Polyhedron(vertices=[[1,1,1]]).plot() # point in R^3 """ + from sage.misc.superseded import deprecation + deprecation(16625, 'use Projection.render_3d instead') if is_Polyhedron(projection): projection = Projection(projection) - from sage.plot.plot3d.base import Graphics3d - plt = Graphics3d() - if isinstance(point_opts, dict): - point_opts.setdefault('width', 3) - plt += projection.render_vertices_3d(**point_opts) - if isinstance(line_opts, dict): - line_opts.setdefault('width', 3) - plt += projection.render_wireframe_3d(**line_opts) - if isinstance(polygon_opts, dict): - plt += projection.render_solid_3d(**polygon_opts) - return plt + return projection.render_3d(*args, **kwds) def render_4d(polyhedron, point_opts={}, line_opts={}, polygon_opts={}, projection_direction=None): """ @@ -140,9 +123,9 @@ def render_4d(polyhedron, point_opts={}, line_opts={}, polygon_opts={}, projecti sage: poly = polytopes.twenty_four_cell() sage: poly A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 24 vertices - sage: poly.show() - sage: poly.show(projection_direction=[2,5,11,17]) - sage: type( poly.show() ) + sage: poly.plot() + sage: poly.plot(projection_direction=[2,5,11,17]) + sage: type( poly.plot() ) TESTS:: @@ -150,10 +133,16 @@ def render_4d(polyhedron, point_opts={}, line_opts={}, polygon_opts={}, projecti sage: from sage.geometry.polyhedron.plot import render_4d sage: p = polytopes.n_cube(4) sage: q = render_4d(p) + doctest:...: DeprecationWarning: use Polyhedron.schlegel_projection instead + See http://trac.sagemath.org/16625 for details. + doctest:...: DeprecationWarning: use Projection.render_3d instead + See http://trac.sagemath.org/16625 for details. sage: tach_str = q.tachyon() sage: tach_str.count('FCylinder') 32 """ + from sage.misc.superseded import deprecation + deprecation(16625, 'use Polyhedron.schlegel_projection instead') if projection_direction is None: for ineq in polyhedron.inequality_generator(): center = [v() for v in ineq.incident() if v.is_vertex()] @@ -379,7 +368,7 @@ class ProjectionFuncSchlegel(): sage: proj(vector([1.1,1.1,1.11]))[0] 0.0302... """ - def __init__(self, projection_direction, height = 1.1): + def __init__(self, projection_direction, height=1.1, center=0): """ Initializes the projection. @@ -392,6 +381,7 @@ def __init__(self, projection_direction, height = 1.1): 0.0302... sage: TestSuite(proj).run(skip='_test_pickling') """ + self.center = center self.projection_dir = vector(RDF, projection_direction) if norm(self.projection_dir).is_zero(): raise ValueError("projection direction must be a non-zero vector.") @@ -420,7 +410,7 @@ def __call__(self, x): sage: proj.__call__([1,2,3]) (0.56162854..., 2.09602626...) """ - v = vector(RDF,x) + v = vector(RDF,x) - self.center if v.is_zero(): raise ValueError("The origin must not be a vertex.") v = v/norm(v) # normalize vertices to unit sphere @@ -468,11 +458,11 @@ def __init__(self, polyhedron, proj=projection_func_identity): The projection of a polyhedron into 2 dimensions sage: Projection(p, lambda x: [x[1],x[2]] ) # another way of doing the same projection The projection of a polyhedron into 2 dimensions - sage: _.show() # plot of the projected icosahedron in 2d + sage: _.plot() # plot of the projected icosahedron in 2d sage: proj = Projection(p) sage: proj.stereographic([1,2,3]) The projection of a polyhedron into 2 dimensions - sage: proj.show() + sage: proj.plot() sage: TestSuite(proj).run(skip='_test_pickling') """ self.parent_polyhedron = polyhedron @@ -569,7 +559,7 @@ def stereographic(self, projection_point=None): sage: proj = Projection(polytopes.buckyball()) #long time sage: proj #long time The projection of a polyhedron into 3 dimensions - sage: proj.stereographic([5,2,3]).show() #long time + sage: proj.stereographic([5,2,3]).plot() #long time sage: Projection( polytopes.twenty_four_cell() ).stereographic([2,0,0,0]) The projection of a polyhedron into 3 dimensions """ @@ -578,19 +568,29 @@ def stereographic(self, projection_point=None): return self.__call__(ProjectionFuncStereographic(projection_point)) - def schlegel(self, projection_direction=None, height = 1.1): + def schlegel(self, projection_direction=None, height=1.1): """ Return the Schlegel projection. - The vertices are normalized to the unit sphere, and - stereographically projected from a point slightly outside of - the sphere. + * The polyhedron is translated such that its + :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.center` + is at the origin. + + * The vertices are then normalized to the unit sphere + + * The normalized points are stereographically projected from a + point slightly outside of the sphere. INPUT: - - ``projection_direction`` - The direction of the Schlegel - projection. By default, the vector consisting of the first n - primes is chosen. + - ``projection_direction`` -- coordinate list/tuple/iterable + or ``None`` (default). The direction of the Schlegel + projection. For a full-dimensional polyhedron, the default + is the first facet normal; Otherwise, the vector consisting + of the first n primes is chosen. + + - ``height`` -- float (default: `1.1`). How far outside of the + unit sphere the focal point is. EXAMPLES:: @@ -598,26 +598,22 @@ def schlegel(self, projection_direction=None, height = 1.1): sage: from sage.geometry.polyhedron.plot import Projection sage: Projection(cube4).schlegel([1,0,0,0]) The projection of a polyhedron into 3 dimensions - sage: _.show() + sage: _.plot() TESTS:: sage: Projection(cube4).schlegel() The projection of a polyhedron into 3 dimensions - """ + center = self.parent_polyhedron.center() if projection_direction is None: - for poly in self.polygons: - center = sum([self.coords[i] for i in poly]) / len(poly) - print center, "\n" - if not center.is_zero(): - projection_direction = center - break - if projection_direction is None: - from sage.rings.arith import primes_first_n - projection_direction = primes_first_n(self.polyhedron_ambient_dim) - return self.__call__(ProjectionFuncSchlegel(projection_direction, height = height)) - + if self.parent_polyhedron.is_full_dimensional(): + projection_direction = self.parent_polyhedron.inequality_generator().next().A() + else: + from sage.rings.arith import primes_first_n + projection_direction = primes_first_n(self.polyhedron_ambient_dim) + return self.__call__(ProjectionFuncSchlegel( + projection_direction, height=height, center=center)) def coord_index_of(self, v): """ @@ -676,23 +672,51 @@ def _init_dimension(self): sage: from sage.geometry.polyhedron.plot import Projection, render_2d sage: p = polytopes.n_simplex(2).projection() sage: test = p._init_dimension() - sage: p.show.__doc__ == render_2d.__doc__ + sage: p.plot.__doc__ == p.render_2d.__doc__ True """ - self.dimension = len(self.transformed_coords[0]) - - if self.dimension == 2: - self.show = lambda **kwds: render_2d(self,**kwds) - self.show.__doc__ = render_2d.__doc__ + if self.transformed_coords: + self.dimension = len(self.transformed_coords[0]) + else: + self.dimension = 0 + if self.dimension == 0: + self.plot = self.render_0d + elif self.dimension == 1: + self.plot = self.render_1d + elif self.dimension == 2: + self.plot = self.render_2d elif self.dimension == 3: - self.show = lambda **kwds: render_3d(self,**kwds) - self.show.__doc__ = render_3d.__doc__ + self.plot = self.render_3d else: try: - del self.show + del self.plot except AttributeError: pass + def show(self, *args, **kwds): + from sage.misc.superseded import deprecation + deprecation(16625, 'use Projection.plot instead') + return self.plot(*args, **kwds) + + def _graphics_(self): + """ + Display projection graphically on the Sage command line. + + See :meth:`~sage.plot.graphics.Graphics._graphics_`. + + EXAMPLES:: + + sage: polytopes.n_cube(3).projection()._graphics_() + False + """ + from sage.doctest import DOCTEST_MODE + if DOCTEST_MODE: + return False + try: + self.plot().show() + return True + except AttributeError: + return False def _init_from_2d(self, polyhedron): """ @@ -931,6 +955,56 @@ def adjacent_vertices(i): self.polygons.extend( [self.coord_indices_of(p) for p in polygons] ) + def render_points_1d(self, **kwds): + """ + Return the points of a polyhedron in 1d. + + INPUT: + + - ``**kwds`` -- options passed through to + :func:`~sage.plot.point.point2d`. + + OUTPUT: + + A 2-d graphics object. + + EXAMPLES:: + + sage: cube1 = polytopes.n_cube(1) + sage: proj = cube1.projection() + sage: points = proj.render_points_1d() + sage: points._objects + [Point set defined by 2 point(s)] + """ + return point2d([c + [0] for c in self.coordinates_of(self.points)], **kwds) + + def render_line_1d(self, **kwds): + """ + Return the line of a polyhedron in 1d. + + INPUT: + + - ``**kwds`` -- options passed through to + :func:`~sage.plot.line.line2d`. + + OUTPUT: + + A 2-d graphics object. + + EXAMPLES:: + + sage: outline = polytopes.n_cube(1).projection().render_line_1d() + sage: outline._objects[0] + Line defined by 2 points + """ + if len(self.lines) == 0: + return Graphics() + elif len(self.lines) == 1: + line = self.coordinates_of(self.lines[0]) + return line2d([line[0] + [0], line[1] + [0]], **kwds) + else: + assert False # unreachable + def render_points_2d(self, **kwds): """ Return the points of a polyhedron in 2d. @@ -1021,7 +1095,6 @@ def render_wireframe_3d(self, **kwds): wireframe.append(arrow3d(a_coords[0], a_coords[1], **kwds)) return sum(wireframe) - def render_solid_3d(self, **kwds): """ Return solid 3d rendering of a 3d polytope. @@ -1036,6 +1109,131 @@ def render_solid_3d(self, **kwds): return sum([ polygon3d(self.coordinates_of(f), **kwds) for f in self.polygons ]) + def render_0d(self, point_opts={}, line_opts={}, polygon_opts={}): + """ + Return 0d rendering of the projection of a polyhedron into + 2-dimensional ambient space. + + INPUT: + + See + :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.plot`. + + OUTPUT: + + A 2-d graphics object. + + EXAMPLES:: + + sage: print(Polyhedron([]).projection().render_0d().description()) + + sage: print(Polyhedron(ieqs=[(1,)]).projection().render_0d().description()) + Point set defined by 1 point(s): [(0.0, 0.0)] + """ + if isinstance(point_opts, dict): + point_opts.setdefault('zorder', 2) + point_opts.setdefault('pointsize', 10) + if self.points: + return point2d([0,0], **point_opts) + else: + return Graphics() + + + def render_1d(self, point_opts={}, line_opts={}, polygon_opts={}): + """ + Return 1d rendering of the projection of a polyhedron into + 2-dimensional ambient space. + + INPUT: + + See + :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.plot`. + + OUTPUT: + + A 2-d graphics object. + + EXAMPLES:: + + sage: Polyhedron([(0,), (1,)]).projection().render_1d() + """ + plt = Graphics() + if isinstance(point_opts, dict): + point_opts.setdefault('zorder', 2) + point_opts.setdefault('pointsize', 10) + plt += self.render_points_1d(**point_opts) + if isinstance(line_opts, dict): + line_opts.setdefault('zorder', 1) + plt += self.render_line_1d(**line_opts) + return plt + + def render_2d(self, point_opts={}, line_opts={}, polygon_opts={}): + """ + Return 2d rendering of the projection of a polyhedron into + 2-dimensional ambient space. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[[1,1]], rays=[[1,1]]) + sage: q1 = p1.projection() + sage: p2 = Polyhedron(vertices=[[1,0], [0,1], [0,0]]) + sage: q2 = p2.projection() + sage: p3 = Polyhedron(vertices=[[1,2]]) + sage: q3 = p3.projection() + sage: p4 = Polyhedron(vertices=[[2,0]], rays=[[1,-1]], lines=[[1,1]]) + sage: q4 = p4.projection() + sage: q1.plot() + q2.plot() + q3.plot() + q4.plot() + """ + plt = Graphics() + if isinstance(point_opts, dict): + point_opts.setdefault('zorder', 2) + point_opts.setdefault('pointsize', 10) + plt += self.render_points_2d(**point_opts) + if isinstance(line_opts, dict): + line_opts.setdefault('zorder', 1) + plt += self.render_outline_2d(**line_opts) + if isinstance(polygon_opts, dict): + polygon_opts.setdefault('zorder', 0) + plt += self.render_fill_2d(**polygon_opts) + return plt + + def render_3d(self, point_opts={}, line_opts={}, polygon_opts={}): + """ + Return 3d rendering of a polyhedron projected into + 3-dimensional ambient space. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[[1,1,1]], rays=[[1,1,1]]) + sage: p2 = Polyhedron(vertices=[[2,0,0], [0,2,0], [0,0,2]]) + sage: p3 = Polyhedron(vertices=[[1,0,0], [0,1,0], [0,0,1]], rays=[[-1,-1,-1]]) + sage: p1.projection().plot() + p2.projection().plot() + p3.projection().plot() + + It correctly handles various degenerate cases:: + + sage: Polyhedron(lines=[[1,0,0],[0,1,0],[0,0,1]]).plot() # whole space + sage: Polyhedron(vertices=[[1,1,1]], rays=[[1,0,0]], + ....: lines=[[0,1,0],[0,0,1]]).plot() # half space + sage: Polyhedron(vertices=[[1,1,1]], + ....: lines=[[0,1,0],[0,0,1]]).plot() # R^2 in R^3 + sage: Polyhedron(rays=[[0,1,0],[0,0,1]], lines=[[1,0,0]]).plot() # quadrant wedge in R^2 + sage: Polyhedron(rays=[[0,1,0]], lines=[[1,0,0]]).plot() # upper half plane in R^3 + sage: Polyhedron(lines=[[1,0,0]]).plot() # R^1 in R^2 + sage: Polyhedron(rays=[[0,1,0]]).plot() # Half-line in R^3 + sage: Polyhedron(vertices=[[1,1,1]]).plot() # point in R^3 + """ + from sage.plot.plot3d.base import Graphics3d + plt = Graphics3d() + if isinstance(point_opts, dict): + point_opts.setdefault('width', 3) + plt += self.render_vertices_3d(**point_opts) + if isinstance(line_opts, dict): + line_opts.setdefault('width', 3) + plt += self.render_wireframe_3d(**line_opts) + if isinstance(polygon_opts, dict): + plt += self.render_solid_3d(**polygon_opts) + return plt + def tikz(self, view=[0,0,1], angle=0, scale=2, edge_color='blue!95!black', facet_color='blue!95!black', opacity=0.8, vertex_color='green', axis=False): diff --git a/src/sage/geometry/polyhedron/ppl_lattice_polytope.py b/src/sage/geometry/polyhedron/ppl_lattice_polytope.py index 84ea4479500..64f43c66b76 100644 --- a/src/sage/geometry/polyhedron/ppl_lattice_polytope.py +++ b/src/sage/geometry/polyhedron/ppl_lattice_polytope.py @@ -1066,9 +1066,18 @@ def lattice_automorphism_group(self, points=None, point_labels=None): ((0, 0), (1, 1), (1, 2), (2, 1), (2, 2), (3, 3)) sage: Z3square.lattice_automorphism_group(points, point_labels=(1,2,3,4,5,6)) Permutation Group with generators [(), (3,4), (1,6)(2,5), (1,6)(2,5)(3,4)] + + Point labels also work for lattice polytopes that are not + full-dimensional, see trac:`16669`:: + + sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL + sage: lp = LatticePolytope_PPL((1,0,0),(0,1,0),(-1,-1,0)) + sage: lp.lattice_automorphism_group(point_labels=(0,1,2)) + Permutation Group with generators [(), (1,2), (0,1), (0,1,2), (0,2,1), (0,2)] """ if not self.is_full_dimensional(): - return self.affine_lattice_polytope().lattice_automorphism_group() + return self.affine_lattice_polytope().lattice_automorphism_group( + point_labels=point_labels) if points is None: points = self.vertices() diff --git a/src/sage/geometry/toric_plotter.py b/src/sage/geometry/toric_plotter.py index cd2863d84f1..d3cfa8b186f 100644 --- a/src/sage/geometry/toric_plotter.py +++ b/src/sage/geometry/toric_plotter.py @@ -478,7 +478,6 @@ def plot_lattice(self): # Plot the origin anyway, otherwise rays/generators may look ugly. return self.plot_points([self.origin]) d = self.dimension - extra_options = self.extra_options if d == 1: points = ((x, 0) for x in range(ceil(self.xmin), floor(self.xmax) + 1)) @@ -604,6 +603,13 @@ def plot_walls(self, walls): sage: tp = ToricPlotter(dict(), 2, quadrant.rays()) sage: print tp.plot_walls([quadrant]) Graphics object consisting of 2 graphics primitives + + Let's also check that the truncating polyhedron is functioning + correctly:: + + sage: tp = ToricPlotter({"mode": "box"}, 2, quadrant.rays()) + sage: print tp.plot_walls([quadrant]) + Graphics object consisting of 2 graphics primitives """ result = Graphics() if not walls or not self.show_walls: @@ -622,7 +628,7 @@ def plot_walls(self, walls): ieqs = [(self.xmax, -1, 0, 0), (- self.xmin, 1, 0, 0), (self.ymax, 0, -1, 0), (- self.ymin, 0, 1, 0), (self.zmax, 0, 0, -1), (- self.zmin, 0, 0, 1)] - box = Polyhedron(ieqs=ieqs, field=RDF) + box = Polyhedron(ieqs=ieqs, base_ring=RDF) for wall, color in zip(walls, colors): result += box.intersection(wall.polyhedron()).render_solid( alpha=alpha, color=color, zorder=zorder, **extra_options) @@ -631,7 +637,7 @@ def plot_walls(self, walls): for wall, color in zip(walls, colors): vertices = [rays[i] for i in wall.ambient_ray_indices()] vertices.append(origin) - result += Polyhedron(vertices=vertices, field=RDF).render_solid( + result += Polyhedron(vertices=vertices, base_ring=RDF).render_solid( alpha=alpha, color=color, zorder=zorder, **extra_options) label_sectors = [] round = mode == "round" diff --git a/src/sage/graphs/all.py b/src/sage/graphs/all.py index 40cc181f641..57fd01e8175 100644 --- a/src/sage/graphs/all.py +++ b/src/sage/graphs/all.py @@ -6,7 +6,6 @@ from graph_database import GraphDatabase, GenericGraphQuery, GraphQuery from graph import Graph from digraph import DiGraph -from hypergraph import Hypergraph from bipartite_graph import BipartiteGraph from graph_bundle import GraphBundle import weakly_chordal diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index e53944d8ef5..8b3d1573f1c 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -1783,8 +1783,8 @@ def feedback_edge_set(self, constraint_generation= True, value_only=False, solve else: p=MixedIntegerLinearProgram(maximization=False, solver=solver) - b=p.new_variable(binary = True) - d=p.new_variable(integer = True) + b=p.new_variable(binary=True) + d=p.new_variable(integer=True, nonnegative=True) n=self.order() diff --git a/src/sage/graphs/dot2tex_utils.py b/src/sage/graphs/dot2tex_utils.py index f2b8c761eee..7054ea2da5f 100644 --- a/src/sage/graphs/dot2tex_utils.py +++ b/src/sage/graphs/dot2tex_utils.py @@ -48,18 +48,20 @@ def assert_have_dot2tex(): For support, please contact . """ - missing_error_string = """ -dot2tex not available. + import_error_string = """ +An error occured when importing dot2tex. Please see :meth:`sage.graphs.generic_graph.GenericGraph.layout_graphviz` for installation instructions. """ try: import dot2tex + except ImportError as e: + print import_error_string + raise # re-raise current exception + else: if dot2tex.dot2tex("graph {}", format = "positions") != {}: raise RuntimeError(check_error_string) - except ImportError: - raise RuntimeError(missing_error_string) def quoted_latex(x): """ diff --git a/src/sage/graphs/generators/intersection.py b/src/sage/graphs/generators/intersection.py index d89cd4cc9cf..117960c429d 100644 --- a/src/sage/graphs/generators/intersection.py +++ b/src/sage/graphs/generators/intersection.py @@ -497,3 +497,55 @@ def OrthogonalArrayBlockGraph(k,n,OA=None): g.name("OA({},{})".format(k,n)) return g + +def IntersectionGraph(S): + r""" + Returns the intersection graph of the family `S` + + The intersection graph of a family `S` is a graph `G` with `V(G)=S` such + that two elements `s_1,s_2\in S` are adjacent in `G` if and only if `s_1\cap + s_2\neq \emptyset`. + + INPUT: + + - ``S`` -- a list of sets/tuples/iterables + + .. NOTE:: + + The elements of `S` must be finite, hashable, and the elements of + any `s\in S` must be hashable too. + + EXAMPLE:: + + sage: graphs.IntersectionGraph([(1,2,3),(3,4,5),(5,6,7)]) + Intersection Graph: Graph on 3 vertices + + TESTS:: + + sage: graphs.IntersectionGraph([(1,2,[1])]) + Traceback (most recent call last): + ... + TypeError: The elements of S must be hashable, and this one is not: (1, 2, [1]) + """ + from itertools import combinations + + for s in S: + try: + hash(s) + except TypeError: + raise TypeError("The elements of S must be hashable, and this one is not: {}".format(s)) + + ground_set_to_sets = {} + for s in S: + for x in s: + if x not in ground_set_to_sets: + ground_set_to_sets[x] = [] + ground_set_to_sets[x].append(s) + + g = Graph(name="Intersection Graph") + g.add_vertices(S) + for clique in ground_set_to_sets.itervalues(): + g.add_edges((u,v) for u,v in combinations(clique,2)) + + return g + diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 08df666afef..a76f8f59437 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -6961,8 +6961,8 @@ def feedback_vertex_set(self, value_only=False, solver=None, verbose=0, constrai p = MixedIntegerLinearProgram(maximization = False, solver = solver) - b = p.new_variable(binary = True) - d = p.new_variable(integer = True) + b = p.new_variable(binary=True) + d = p.new_variable(integer=True, nonnegative=True) n = self.order() # The removed vertices cover all the back arcs ( third condition ) @@ -17950,9 +17950,16 @@ def canonical_label(self, partition=None, certify=False, verbosity=0, edge_label Graph on 5 vertices sage: G.canonical_label(edge_labels=True,certify=True) (Graph on 5 vertices, {0: 4, 1: 3, 2: 0, 3: 1, 4: 2}) + + Check for immutable graphs (:trac:`16602`):: + + sage: G = Graph([[1, 2], [2, 3]], immutable=True) + sage: C = G.canonical_label(); C + Graph on 3 vertices + sage: C.vertices() + [0, 1, 2] """ from sage.groups.perm_gps.partn_ref.refinement_graphs import search_tree - from copy import copy dig = (self.has_loops() or self._directed) if partition is None: @@ -17974,7 +17981,7 @@ def canonical_label(self, partition=None, certify=False, verbosity=0, edge_label partition = [[G_to[v] for v in cell] for cell in partition] a,b,c = search_tree(GC, partition, certify=True, dig=dig, verbosity=verbosity) # c is a permutation to the canonical label of G, which depends only on isomorphism class of self. - H = copy(self) + H = self.copy(immutable=False) c_new = {} for v in self.vertices(): c_new[v] = c[G_to[relabeling[v]]] @@ -17997,7 +18004,7 @@ def canonical_label(self, partition=None, certify=False, verbosity=0, edge_label GC = HB._cg partition = [[G_to[v] for v in cell] for cell in partition] a,b,c = search_tree(GC, partition, certify=True, dig=dig, verbosity=verbosity) - H = copy(self) + H = self.copy(immutable=False) c_new = {} for v in G_to: c_new[v] = c[G_to[v]] diff --git a/src/sage/graphs/graph_decompositions/vertex_separation.pyx b/src/sage/graphs/graph_decompositions/vertex_separation.pyx index 2af7bf5ef60..23349b1c579 100644 --- a/src/sage/graphs/graph_decompositions/vertex_separation.pyx +++ b/src/sage/graphs/graph_decompositions/vertex_separation.pyx @@ -790,15 +790,15 @@ def vertex_separation_MILP(G, integrality = False, solver = None, verbosity = 0) ... if ve != vm: ... print "The solution is not optimal!" - Comparison with Different values of the integrality parameter:: + Comparison with different values of the integrality parameter:: sage: from sage.graphs.graph_decompositions import vertex_separation sage: for i in range(10): # long time (11s on sage.math, 2012) - ... G = digraphs.RandomDirectedGNP(10, 0.2) - ... va, la = vertex_separation.vertex_separation_MILP(G, integrality = False) - ... vb, lb = vertex_separation.vertex_separation_MILP(G, integrality = True) - ... if va != vb: - ... print "The integrality parameter change the result!" + ....: G = digraphs.RandomDirectedGNP(10, 0.2) + ....: va, la = vertex_separation.vertex_separation_MILP(G, integrality=False) + ....: vb, lb = vertex_separation.vertex_separation_MILP(G, integrality=True) + ....: if va != vb: + ....: print "The integrality parameter changes the result!" Giving anything else than a DiGraph:: @@ -816,10 +816,10 @@ def vertex_separation_MILP(G, integrality = False, solver = None, verbosity = 0) p = MixedIntegerLinearProgram( maximization = False, solver = solver ) # Declaration of variables. - x = p.new_variable(binary = integrality, nonnegative=bool(not integrality)) # at least one has to be set (#15221) - u = p.new_variable(binary = integrality, nonnegative=bool(not integrality)) - y = p.new_variable(binary = True) - z = p.new_variable(integer = True) + x = p.new_variable(binary=integrality, nonnegative=True) + u = p.new_variable(binary=integrality, nonnegative=True) + y = p.new_variable(binary=True) + z = p.new_variable(integer=True, nonnegative=True) N = G.num_verts() V = G.vertices() diff --git a/src/sage/graphs/graph_generators.py b/src/sage/graphs/graph_generators.py index cf713b61862..37ac1293945 100644 --- a/src/sage/graphs/graph_generators.py +++ b/src/sage/graphs/graph_generators.py @@ -230,7 +230,8 @@ def __append_to_doc(methods): """ __append_to_doc( - ["IntervalGraph", + ["IntersectionGraph", + "IntervalGraph", "OrthogonalArrayBlockGraph", "PermutationGraph", "ToleranceGraph"]) @@ -1396,6 +1397,7 @@ def fusenes(self, hexagon_count, benzenoids=False): ########################################################################### import sage.graphs.generators.intersection IntervalGraph = staticmethod(sage.graphs.generators.intersection.IntervalGraph) + IntersectionGraph = staticmethod(sage.graphs.generators.intersection.IntersectionGraph) PermutationGraph = staticmethod(sage.graphs.generators.intersection.PermutationGraph) OrthogonalArrayBlockGraph = staticmethod(sage.graphs.generators.intersection.OrthogonalArrayBlockGraph) ToleranceGraph = staticmethod(sage.graphs.generators.intersection.ToleranceGraph) diff --git a/src/sage/graphs/hypergraph.py b/src/sage/graphs/hypergraph.py deleted file mode 100644 index df7fb87ff02..00000000000 --- a/src/sage/graphs/hypergraph.py +++ /dev/null @@ -1,316 +0,0 @@ -r""" -Hypergraphs - -This module consists in a very basic implementation of :class:`Hypergraph`, -whose only current purpose is to observe them: it can be used to compute -automorphism groups and to draw them. The latter is done at the moment through -`\LaTeX` and TikZ, and can be obtained from Sage through the ``view`` command:: - - sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H - Hypergraph on 6 vertices containing 4 sets - sage: view(H) # not tested - -Note that hypergraphs are very hard to visualize, and unless it is very small -(`\leq 10` sets) or has a very specific structure (like the following one), -Sage's drawing will only bring more confusion:: - - sage: g = graphs.Grid2dGraph(5,5) - sage: sets = Set(map(Set,list(g.subgraph_search_iterator(graphs.CycleGraph(4))))) - sage: H = Hypergraph(sets) - sage: view(H) # not tested - -.. SEEALSO:: - - :class:`Hypergraph` for information on the `\LaTeX` output - -Classes and methods -------------------- -""" -from sage.misc.latex import latex -from sage.sets.set import Set - -class Hypergraph: - r""" - A hypergraph. - - A *hypergraph* `H = (V, E)` is a set of vertices `V` and a collection `E` of - sets of vertices called *hyperedges* or edges. In particular `E \subseteq - \mathcal{P}(V)`. If all (hyper)edges contain exactly 2 vertices, then `H` is - a graph in the usual sense. - - .. rubric:: Latex output - - The `\LaTeX` for a hypergraph `H` is consists of the vertices set and a - set of closed curves. The set of vertices in each closed curve represents a - hyperedge of `H`. A vertex which is encircled by a curve but is not - located on its boundary is **NOT** included in the corresponding set. - - The colors are picked for readability and have no other meaning. - - INPUT: - - - ``sets`` -- A list of hyperedges - - EXAMPLES:: - - sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{6}]); H - Hypergraph on 6 vertices containing 4 sets - - REFERENCES: - - - :wikipedia:`Hypergraph` - """ - def __init__(self, sets): - r""" - Constructor - - EXAMPLES:: - - sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H - Hypergraph on 6 vertices containing 4 sets - """ - from sage.sets.set import Set - self._sets = map(Set, sets) - self._domain = set([]) - for s in sets: - for i in s: - self._domain.add(i) - - def __repr__(self): - r""" - Short description of ``self``. - - EXAMPLES:: - - sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H - Hypergraph on 6 vertices containing 4 sets - """ - return ("Hypergraph on "+str(len(self.domain()))+" " - "vertices containing "+str(len(self._sets))+" sets") - - def edge_coloring(self): - r""" - Compute a proper edge-coloring. - - A proper edge-coloring is an assignment of colors to the sets of the - hypergraph such that two sets with non-empty intersection receive - different colors. The coloring returned minimizes the number of colors. - - OUTPUT: - - A partition of the sets into color classes. - - EXAMPLES:: - - sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H - Hypergraph on 6 vertices containing 4 sets - sage: C = H.edge_coloring() - sage: C # random - [[{3, 4, 5}], [{4, 5, 6}, {1, 2, 3}], [{2, 3, 4}]] - sage: Set(sum(C,[])) == Set(H._sets) - True - """ - from sage.graphs.graph import Graph - g = Graph([self._sets,lambda x,y : len(x&y)],loops = False) - return g.coloring(algorithm="MILP") - - def _spring_layout(self): - r""" - Return a spring layout for the vertices. - - The layout is computed by creating a graph `G` on the vertices *and* - sets of the hypergraph. Each set is then made adjacent in `G` with all - vertices it contains before a spring layout is computed for this - graph. The position of the vertices in the hypergraph is the position of - the same vertices in the graph's layout. - - .. NOTE:: - - This method also returns the position of the "fake" vertices, - i.e. those representing the sets. - - EXAMPLES:: - - sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H - Hypergraph on 6 vertices containing 4 sets - sage: L = H._spring_layout() - sage: L # random - {1: (0.238, -0.926), - 2: (0.672, -0.518), - 3: (0.449, -0.225), - 4: (0.782, 0.225), - 5: (0.558, 0.518), - 6: (0.992, 0.926), - {3, 4, 5}: (0.504, 0.173), - {2, 3, 4}: (0.727, -0.173), - {4, 5, 6}: (0.838, 0.617), - {1, 2, 3}: (0.393, -0.617)} - sage: all(v in L for v in H.domain()) - True - sage: all(v in L for v in H._sets) - True - """ - from sage.graphs.graph import Graph - - g = Graph() - for s in self._sets: - for x in s: - g.add_edge(s,x) - - _ = g.plot(iterations = 50000,save_pos=True) - - # The values are rounded as TikZ does not like accuracy. - return {k:(round(x,3),round(y,3)) for k,(x,y) in g.get_pos().items()} - - def domain(self): - r""" - Return the set of vertices. - - EXAMPLES:: - - sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H - Hypergraph on 6 vertices containing 4 sets - sage: H.domain() - set([1, 2, 3, 4, 5, 6]) - """ - return self._domain.copy() - - def _latex_(self): - r""" - Return a TikZ representation of the hypergraph. - - EXAMPLES:: - - sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H - Hypergraph on 6 vertices containing 4 sets - sage: view(H) # not tested - - With sets of size 4:: - - sage: g = graphs.Grid2dGraph(5,5) - sage: C4 = graphs.CycleGraph(4) - sage: sets = Set(map(Set,list(g.subgraph_search_iterator(C4)))) - sage: H = Hypergraph(sets) - sage: view(H) # not tested - """ - from sage.rings.integer import Integer - from sage.functions.trig import arctan2 - - from sage.misc.misc import warn - warn("\nThe hypergraph is drawn as a set of closed curves. The curve " - "representing a set S go **THROUGH** the vertices contained " - "in S.\n A vertex which is encircled by a curve but is not located " - "on its boundary is **NOT** included in the corresponding set.\n" - "\n" - "The colors are picked for readability and have no other meaning.") - - latex.add_package_to_preamble_if_available("tikz") - latex.add_to_mathjax_avoid_list("tikz") - - if not latex.has_file("tikz.sty"): - raise RuntimeError("You must have TikZ installed in order " - "to draw a hypergraph.") - - domain = self.domain() - pos = self._spring_layout() - tex = "\\begin{tikzpicture}[scale=3]\n" - - colors = ["black", "red", "green", "blue", "cyan", "magenta", "yellow","pink","brown"] - colored_sets = [(s,i) for i,S in enumerate(self.edge_coloring()) for s in S] - - # Prints each set with its color - for s,i in colored_sets: - current_color = colors[i%len(colors)] - - if len(s) == 2: - s = list(s) - tex += ("\\draw[color="+str(current_color)+","+ - "line width=.1cm,opacity = .6] "+ - str(pos[s[0]])+" -- "+str(pos[s[1]])+";\n") - continue - - tex += ("\\draw[color="+str(current_color)+"," - "line width=.1cm,opacity = .6," - "line cap=round," - "line join=round]" - "plot [smooth cycle,tension=1] coordinates {") - - # Reorders the vertices of s according to their angle with the - # "center", i.e. the vertex representing the set s - cx, cy = pos[s] - s = map(lambda x: pos[x], s) - s = sorted(s, key = lambda x_y: arctan2(x_y[0] - cx, x_y[1] - cy)) - - for x in s: - tex += str(x)+" " - tex += "};\n" - - # Prints each vertex - for v in domain: - tex += "\\draw node[fill,circle,scale=.5,label={90:$"+latex(v)+"$}] at "+str(pos[v])+" {};\n" - - tex += "\\end{tikzpicture}" - return tex - - def to_bipartite_graph(self, with_partition=False): - r""" - Returns the associated bipartite graph - - INPUT: - - - with_partition -- boolean (default: False) - - OUTPUT: - - - a graph or a pair (graph, partition) - - EXAMPLES:: - - sage: H = designs.steiner_triple_system(7).blocks() - sage: H = Hypergraph(H) - sage: g = H.to_bipartite_graph(); g - Graph on 14 vertices - sage: g.is_regular() - True - """ - from sage.graphs.graph import Graph - - G = Graph() - domain = list(self.domain()) - G.add_vertices(domain) - for s in self._sets: - for i in s: - G.add_edge(s, i) - if with_partition: - return (G, [domain, list(self._sets)]) - else: - return G - - def automorphism_group(self): - r""" - Returns the automorphism group. - - For more information on the automorphism group of a hypergraph, see the - :wikipedia:`Hypergraph`. - - EXAMPLE:: - - sage: H = designs.steiner_triple_system(7).blocks() - sage: H = Hypergraph(H) - sage: g = H.automorphism_group(); g - Permutation Group with generators [(2,4)(5,6), (2,5)(4,6), (1,2)(3,4), (1,3)(5,6), (0,1)(2,5)] - sage: g.is_isomorphic(groups.permutation.PGL(3,2)) - True - """ - from sage.groups.perm_gps.permgroup import PermutationGroup - - G, part = self.to_bipartite_graph(with_partition=True) - - domain = part[0] - - ag = G.automorphism_group(partition=part) - - gens = [[tuple(c) for c in g.cycle_tuples() if c[0] in domain] - for g in ag.gens()] - - return PermutationGroup(gens = gens, domain = domain) diff --git a/src/sage/graphs/hypergraph_generators.py b/src/sage/graphs/hypergraph_generators.py index 0498189270b..79f25372b86 100644 --- a/src/sage/graphs/hypergraph_generators.py +++ b/src/sage/graphs/hypergraph_generators.py @@ -6,7 +6,7 @@ isomorphism. """ -class HyperGraphGenerators(): +class HypergraphGenerators(): r""" A class consisting of constructors for common hypergraphs. """ @@ -159,4 +159,4 @@ 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)) -hypergraphs = HyperGraphGenerators() +hypergraphs = HypergraphGenerators() diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 8eccf53adaf..6d280ea1951 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -90,8 +90,13 @@ def is_FreeGroup(x): False sage: is_FreeGroup(FreeGroup(0)) True + sage: is_FreeGroup(FreeGroup(index_set=ZZ)) + True """ - return isinstance(x, FreeGroup_class) + if isinstance(x, FreeGroup_class): + return True + from sage.groups.indexed_free_group import IndexedFreeGroup + return isinstance(x, IndexedFreeGroup) def _lexi_gen(zeroes=False): """ @@ -433,9 +438,9 @@ def __call__(self, *values): return prod( replace[gen] ** power for gen, power in self.syllables() ) -def FreeGroup(n=None, names='x'): +def FreeGroup(n=None, names='x', index_set=None, abelian=False, **kwds): """ - Construct a Free Group + Construct a Free Group. INPUT: @@ -445,6 +450,17 @@ def FreeGroup(n=None, names='x'): - ``names`` -- string or list/tuple/iterable of strings (default: ``'x'``). The generator names or name prefix. + - ``index_set`` -- (optional) an index set for the generators; if + specified then the optional keyword ``abelian`` can be used + + - ``abelian`` -- (default: ``False``) whether to construct a free + abelian group or a free group + + .. NOTE:: + + If you want to create a free group, it is currently preferential to + use ``Groups().free(...)`` as that does not load GAP. + EXAMPLES:: sage: G. = FreeGroup(); G @@ -466,6 +482,13 @@ def FreeGroup(n=None, names='x'): sage: FreeGroup() Free Group on generators {x} + We give two examples using the ``index_set`` option:: + + sage: FreeGroup(index_set=ZZ) + Free group indexed by Integer Ring + sage: FreeGroup(index_set=ZZ, abelian=True) + Free abelian group indexed by Integer Ring + TESTS:: sage: G1 = FreeGroup(2, 'a,b') @@ -490,6 +513,13 @@ def FreeGroup(n=None, names='x'): n = len(names) from sage.structure.parent import normalize_names names = tuple(normalize_names(n, names)) + if index_set is not None or abelian: + if abelian: + from sage.groups.indexed_free_group import IndexedFreeAbelianGroup + return IndexedFreeAbelianGroup(index_set, names=names, **kwds) + + from sage.groups.indexed_free_group import IndexedFreeGroup + return IndexedFreeGroup(index_set, names=names, **kwds) return FreeGroup_class(names) diff --git a/src/sage/groups/indexed_free_group.py b/src/sage/groups/indexed_free_group.py new file mode 100644 index 00000000000..d41d5491ce2 --- /dev/null +++ b/src/sage/groups/indexed_free_group.py @@ -0,0 +1,494 @@ +""" +Indexed Free Groups + +Free groups and free abelian groups implemented using an indexed set of +generators. + +AUTHORS: + +- Travis Scrimshaw (2013-10-16): Initial version +""" + +############################################################################## +# Copyright (C) 2013 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# The full text of the GPL is available at: +# +# http://www.gnu.org/licenses/ +############################################################################## + +from copy import copy +from sage.categories.groups import Groups +from sage.categories.poor_man_map import PoorManMap +from sage.groups.group import Group, AbelianGroup +from sage.monoids.indexed_free_monoid import (IndexedMonoid, + IndexedMonoidElement, IndexedFreeMonoidElement, + IndexedFreeAbelianMonoidElement) +from sage.misc.cachefunc import cached_method +from sage.combinat.dict_addition import dict_addition +from sage.rings.integer import Integer +from sage.rings.infinity import infinity +from sage.sets.family import Family + +class IndexedGroup(IndexedMonoid): + """ + Base class for free (abelian) groups whose generators are indexed + by a set. + + TESTS: + + We check finite properties:: + + sage: G = Groups().free(index_set=ZZ) + sage: G.is_finite() + False + sage: G = Groups().free(index_set='abc') + sage: G.is_finite() + False + sage: G = Groups().free(index_set=[]) + sage: G.is_finite() + True + + :: + + sage: G = Groups().Commutative().free(index_set=ZZ) + sage: G.is_finite() + False + sage: G = Groups().Commutative().free(index_set='abc') + sage: G.is_finite() + False + sage: G = Groups().Commutative().free(index_set=[]) + sage: G.is_finite() + True + """ + def order(self): + r""" + Return the number of elements of ``self``, which is `\infty` unless + this is the trivial group. + + EXAMPLES:: + + sage: G = Groups().free(index_set=ZZ) + sage: G.order() + +Infinity + sage: G = Groups().Commutative().free(index_set='abc') + sage: G.order() + +Infinity + sage: G = Groups().Commutative().free(index_set=[]) + sage: G.order() + 1 + """ + return self.cardinality() + + def rank(self): + """ + Return the rank of ``self``. + + This is the number of generators of ``self``. + + EXAMPLES:: + + sage: G = Groups().free(index_set=ZZ) + sage: G.rank() + +Infinity + sage: G = Groups().free(index_set='abc') + sage: G.rank() + 3 + sage: G = Groups().free(index_set=[]) + sage: G.rank() + 0 + + :: + + sage: G = Groups().Commutative().free(index_set=ZZ) + sage: G.rank() + +Infinity + sage: G = Groups().Commutative().free(index_set='abc') + sage: G.rank() + 3 + sage: G = Groups().Commutative().free(index_set=[]) + sage: G.rank() + 0 + """ + return self.group_generators().cardinality() + + def group_generators(self): + """ + Return the group generators of ``self``. + + EXAMPLES:: + + sage: G = Groups.free(index_set=ZZ) + sage: G.group_generators() + Lazy family (Generator map from Integer Ring to + Free group indexed by Integer Ring(i))_{i in Integer Ring} + sage: G = Groups().free(index_set='abcde') + sage: sorted(G.group_generators()) + [F['a'], F['b'], F['c'], F['d'], F['e']] + """ + if self._indices.cardinality() == infinity: + gen = PoorManMap(self.gen, domain=self._indices, codomain=self, name="Generator map") + return Family(self._indices, gen) + return Family(self._indices, self.gen) + + gens = group_generators + +class IndexedFreeGroup(IndexedGroup, Group): + """ + An indexed free group. + + EXAMPLES:: + + sage: G = Groups().free(index_set=ZZ) + sage: G + Free group indexed by Integer Ring + sage: G = Groups().free(index_set='abcde') + sage: G + Free group indexed by {'a', 'b', 'c', 'd', 'e'} + """ + def __init__(self, indices, prefix, category=None, **kwds): + """ + Initialize ``self``. + + TESTS:: + + sage: G = Groups().free(index_set=ZZ) + sage: TestSuite(G).run() + sage: G = Groups().free(index_set='abc') + sage: TestSuite(G).run() + """ + category = Groups().or_subcategory(category) + IndexedGroup.__init__(self, indices, prefix, category, **kwds) + + def _repr_(self): + """ + Return a string representation of ``self`` + + TESTS:: + + sage: Groups().free(index_set=ZZ) # indirect doctest + Free group indexed by Integer Ring + """ + return 'Free group indexed by {}'.format(self._indices) + + @cached_method + def one(self): + """ + Return the identity element of ``self``. + + EXAMPLES:: + + sage: G = Groups().free(ZZ) + sage: G.one() + 1 + """ + return self.element_class(self, ()) + + def gen(self, x): + """ + The generator indexed by ``x`` of ``self``. + + EXAMPLES:: + + sage: G = Groups().free(index_set=ZZ) + sage: G.gen(0) + F[0] + sage: G.gen(2) + F[2] + """ + if x not in self._indices: + raise IndexError("{} is not in the index set".format(x)) + try: + return self.element_class(self, ((self._indices(x),1),)) + except TypeError: # Backup (if it is a string) + return self.element_class(self, ((x,1),)) + + class Element(IndexedFreeMonoidElement): + def __lt__(self, other): + """ + Return whether ``self`` is smaller than ``y``. + + This is done by comparing lexicographically the words for + ``self`` and ``y``. In particular this assumes that the + (index of) the generators are totally ordered. + + EXAMPLES:: + + sage: G = Groups().free(index_set=ZZ) + sage: a,b,c,d,e = [G.gen(i) for i in range(5)] + sage: a < b + True + sage: a^-1*b < b^-1*a + True + sage: a*b < a*a^-1 + False + sage: a^-1*a < a^2 + True + sage: a^2*b < a*b^-1*a*b + True + """ + if not isinstance(other, IndexedMonoidElement): + return False + return self.to_word_list() < other.to_word_list() + + def __len__(self): + """ + Return the length of ``self``. + + EXAMPLES:: + + sage: G = Groups().free(index_set=ZZ) + sage: a,b,c,d,e = [G.gen(i) for i in range(5)] + sage: elt = a*c^-3*b^-2*a + sage: elt.length() + 7 + sage: len(elt) + 7 + + sage: G = Groups().free(index_set=ZZ) + sage: a,b,c,d,e = [G.gen(i) for i in range(5)] + sage: elt = a*c^-3*b^-2*a + sage: elt.length() + 7 + sage: len(elt) + 7 + """ + return sum(abs(exp) for gen,exp in self._monomial) + + length = __len__ + + def _mul_(self, other): + """ + Multiply ``self`` by ``other``. + + EXAMPLES:: + + sage: G = Groups().free(index_set=ZZ) + sage: a,b,c,d,e = [G.gen(i) for i in range(5)] + sage: a*b^2*e*d + F[0]*F[1]^2*F[4]*F[3] + sage: (a*b^2*d^2) * (d^-4*b*e) + F[0]*F[1]^2*F[3]^-2*F[1]*F[4] + sage: (a*b^-2*d^2) * (d^-2*b^2*a^-1) + 1 + """ + if not self._monomial: + return other + if not other._monomial: + return self + + ret = list(self._monomial) + rhs = list(other._monomial) + while len(ret) > 0 and len(rhs) > 0 and ret[-1][0] == rhs[0][0]: + rhs[0] = (rhs[0][0], rhs[0][1] + ret.pop()[1]) + if rhs[0][1] == 0: + rhs.pop(0) + ret += rhs + return self.__class__(self.parent(), tuple(ret)) + + def __invert__(self): + """ + Return the inverse of ``self``. + + EXAMPLES:: + + sage: G = Groups().free(index_set=ZZ) + sage: a,b,c,d,e = [G.gen(i) for i in range(5)] + sage: x = a*b^2*e^-1*d; ~x + F[3]^-1*F[4]*F[1]^-2*F[0]^-1 + sage: x * ~x + 1 + """ + return self.__class__(self.parent(), + tuple((x[0], -x[1]) for x in reversed(self._monomial))) + + def to_word_list(self): + """ + Return ``self`` as a word represented as a list whose entries + are the pairs ``(i, s)`` where ``i`` is the index and ``s`` is + the sign. + + EXAMPLES:: + + sage: G = Groups().free(index_set=ZZ) + sage: a,b,c,d,e = [G.gen(i) for i in range(5)] + sage: x = a*b^2*e*a^-1 + sage: x.to_word_list() + [(0, 1), (1, 1), (1, 1), (4, 1), (0, -1)] + """ + sign = lambda x: 1 if x > 0 else -1 # It is never 0 + return [ (k, sign(e)) for k,e in self._sorted_items() + for dummy in range(abs(e))] + +class IndexedFreeAbelianGroup(IndexedGroup, AbelianGroup): + """ + An indexed free abelian group. + + EXAMPLES:: + + sage: G = Groups().Commutative().free(index_set=ZZ) + sage: G + Free abelian group indexed by Integer Ring + sage: G = Groups().Commutative().free(index_set='abcde') + sage: G + Free abelian group indexed by {'a', 'b', 'c', 'd', 'e'} + """ + def __init__(self, indices, prefix, category=None, **kwds): + """ + Initialize ``self``. + + TESTS:: + + sage: G = Groups().Commutative().free(index_set=ZZ) + sage: TestSuite(G).run() + sage: G = Groups().Commutative().free(index_set='abc') + sage: TestSuite(G).run() + """ + category = Groups().or_subcategory(category) + IndexedGroup.__init__(self, indices, prefix, category, **kwds) + + def _repr_(self): + """ + TESTS:: + + sage: Groups.Commutative().free(index_set=ZZ) + Free abelian group indexed by Integer Ring + """ + return 'Free abelian group indexed by {}'.format(self._indices) + + def _element_constructor_(self, x=None): + """ + Create an element of ``self`` from ``x``. + + EXAMPLES:: + + sage: G = FreeAbelianMonoid(index_set=ZZ) + sage: G(G.gen(2)) + F[2] + sage: G([[1, 3], [-2, 12]]) + F[-2]^12*F[1]^3 + sage: G({1:3, -2: 12}) + F[-2]^12*F[1]^3 + sage: G(-5) + Traceback (most recent call last): + ... + ValueError: unable to convert -5, use gen() instead + """ + if isinstance(x, (list, tuple, dict)): + x = dict(x) + return IndexedGroup._element_constructor_(self, x) + + @cached_method + def one(self): + """ + Return the identity element of ``self``. + + EXAMPLES:: + + sage: G = Groups().Commutative().free(index_set=ZZ) + sage: G.one() + 1 + """ + return self.element_class(self, {}) + + def gen(self, x): + """ + The generator indexed by ``x`` of ``self``. + + EXAMPLES:: + + sage: G = Groups().Commutative().free(index_set=ZZ) + sage: G.gen(0) + F[0] + sage: G.gen(2) + F[2] + """ + if x not in self._indices: + raise IndexError("{} is not in the index set".format(x)) + try: + return self.element_class(self, {self._indices(x):1}) + except TypeError: # Backup (if it is a string) + return self.element_class(self, {x:1}) + + class Element(IndexedFreeAbelianMonoidElement, IndexedFreeGroup.Element): + def _mul_(self, other): + """ + Multiply ``self`` by ``other``. + + EXAMPLES:: + + sage: G = Groups().Commutative().free(index_set=ZZ) + sage: a,b,c,d,e = [G.gen(i) for i in range(5)] + sage: a*b^2*e^-1*d + F[0]*F[1]^2*F[3]*F[4]^-1 + sage: (a*b^2*d^2) * (d^-4*b^-2*e) + F[0]*F[3]^-2*F[4] + sage: (a*b^-2*d^2) * (d^-2*b^2*a^-1) + 1 + """ + return self.__class__(self.parent(), + dict_addition([self._monomial, other._monomial])) + + def __invert__(self): + """ + Return the inverse of ``self``. + + EXAMPLES:: + + sage: G = Groups().Commutative().free(index_set=ZZ) + sage: a,b,c,d,e = [G.gen(i) for i in range(5)] + sage: x = a*b^2*e^-1*d; ~x + F[0]^-1*F[1]^-2*F[3]^-1*F[4] + sage: x * ~x + 1 + """ + return self.__pow__(-1) + + def __floordiv__(self, a): + """ + Return the division of ``self`` by ``a``. + + EXAMPLES:: + + sage: G = Groups().Commutative().free(index_set=ZZ) + sage: a,b,c,d,e = [G.gen(i) for i in range(5)] + sage: elt = a*b*c^3*d^2; elt + F[0]*F[1]*F[2]^3*F[3]^2 + sage: elt // a + F[1]*F[2]^3*F[3]^2 + sage: elt // c + F[0]*F[1]*F[2]^2*F[3]^2 + sage: elt // (a*b*d^2) + F[2]^3 + sage: elt // a^4 + F[0]^-3*F[1]*F[2]^3*F[3]^2 + """ + return self * ~a + + def __pow__(self, n): + """ + Raise ``self`` to the power of ``n``. + + EXAMPLES:: + + sage: G = Groups().Commutative().free(index_set=ZZ) + sage: a,b,c,d,e = [G.gen(i) for i in range(5)] + sage: x = a*b^2*e^-1*d; x + F[0]*F[1]^2*F[3]*F[4]^-1 + sage: x^3 + F[0]^3*F[1]^6*F[3]^3*F[4]^-3 + sage: x^0 + 1 + sage: x^-3 + F[0]^-3*F[1]^-6*F[3]^-3*F[4]^3 + """ + if not isinstance(n, (int, long, Integer)): + raise TypeError("Argument n (= {}) must be an integer".format(n)) + if n == 1: + return self + if n == 0: + return self.parent().one() + return self.__class__(self.parent(), {k:v*n for k,v in self._monomial.iteritems()}) + diff --git a/src/sage/interfaces/expect.py b/src/sage/interfaces/expect.py index a4ddc514cad..4e47d545ecd 100644 --- a/src/sage/interfaces/expect.py +++ b/src/sage/interfaces/expect.py @@ -437,8 +437,6 @@ def _start(self, alt_message=None, block_during_init=True): os.chdir(current_path) self._expect.timeout = self.__max_startup_time - if not self._terminal_echo: - self._expect.setecho(0) #self._expect.setmaxread(self.__maxread) self._expect.maxread = self.__maxread @@ -451,6 +449,14 @@ def _start(self, alt_message=None, block_during_init=True): failed_to_start.append(self.name()) raise RuntimeError("Unable to start %s"%self.name()) self._expect.timeout = None + + # Calling tcsetattr earlier exposes bugs in various pty + # implementations, see :trac:`16474`. Since we haven't + # **written** anything so far it is safe to wait with + # switching echo off until now. + if not self._terminal_echo: + self._expect.setecho(0) + with gc_disabled(): if block_during_init: for X in self.__init_code: diff --git a/src/sage/interfaces/interface.py b/src/sage/interfaces/interface.py index 16ae4d703f2..ad29e1c6065 100644 --- a/src/sage/interfaces/interface.py +++ b/src/sage/interfaces/interface.py @@ -1082,11 +1082,11 @@ def _add_(self, right): sage: f = maxima.cos(x) sage: g = maxima.sin(x) sage: f + g - sin(x)+cos(x) + sin(_SAGE_VAR_x)+cos(_SAGE_VAR_x) sage: f + 2 - cos(x)+2 + cos(_SAGE_VAR_x)+2 sage: 2 + f - cos(x)+2 + cos(_SAGE_VAR_x)+2 """ return self._operation("+", right) @@ -1097,11 +1097,11 @@ def _sub_(self, right): sage: f = maxima.cos(x) sage: g = maxima.sin(x) sage: f - g - cos(x)-sin(x) + cos(_SAGE_VAR_x)-sin(_SAGE_VAR_x) sage: f - 2 - cos(x)-2 + cos(_SAGE_VAR_x)-2 sage: 2 - f - 2-cos(x) + 2-cos(_SAGE_VAR_x) """ return self._operation('-', right) @@ -1112,9 +1112,9 @@ def _mul_(self, right): sage: f = maxima.cos(x) sage: g = maxima.sin(x) sage: f*g - cos(x)*sin(x) + cos(_SAGE_VAR_x)*sin(_SAGE_VAR_x) sage: 2*f - 2*cos(x) + 2*cos(_SAGE_VAR_x) """ return self._operation('*', right) @@ -1125,9 +1125,9 @@ def _div_(self, right): sage: f = maxima.cos(x) sage: g = maxima.sin(x) sage: f/g - cos(x)/sin(x) + cos(_SAGE_VAR_x)/sin(_SAGE_VAR_x) sage: f/2 - cos(x)/2 + cos(_SAGE_VAR_x)/2 """ return self._operation("/", right) diff --git a/src/sage/interfaces/magma.py b/src/sage/interfaces/magma.py index bc094d19a2b..c6ee1f012ae 100644 --- a/src/sage/interfaces/magma.py +++ b/src/sage/interfaces/magma.py @@ -183,7 +183,6 @@ ... TypeError: Error evaluating Magma code. ... - Runtime error in '*': Bad argument types Argument types given: RngUPolElt[RngInt], FldRatElt diff --git a/src/sage/interfaces/maxima.py b/src/sage/interfaces/maxima.py index 91ff4469347..45b96ffc204 100644 --- a/src/sage/interfaces/maxima.py +++ b/src/sage/interfaces/maxima.py @@ -421,7 +421,7 @@ gamma(1/7) sage: del f sage: maxima(sin(x)) - sin(x) + sin(_SAGE_VAR_x) This tests to make sure we handle the case where Maxima asks if an expression is positive or zero. @@ -1102,7 +1102,7 @@ class MaximaElement(MaximaAbstractElement, ExpectElement): sage: maxima(3) 3 sage: maxima(cos(x)+e^234) - cos(x)+%e^234 + cos(_SAGE_VAR_x)+%e^234 """ def __init__(self, parent, value, is_name=False, name=None): diff --git a/src/sage/interfaces/maxima_abstract.py b/src/sage/interfaces/maxima_abstract.py index db5d412c5c1..503134322e4 100644 --- a/src/sage/interfaces/maxima_abstract.py +++ b/src/sage/interfaces/maxima_abstract.py @@ -496,7 +496,7 @@ def _equality_symbol(self): sage: var('x y') (x, y) sage: maxima(x == y) - x=y + _SAGE_VAR_x=_SAGE_VAR_y """ return '=' @@ -513,7 +513,7 @@ def _inequality_symbol(self): sage: maxima._inequality_symbol() '#' sage: maxima((x != 1)) - x#1 + _SAGE_VAR_x#1 """ return '#' @@ -1753,11 +1753,11 @@ def _latex_(self): sage: y,d = var('y,d') sage: f = function('f') sage: latex(maxima(derivative(f(x*y), x))) - \left(\left.{{{\it \partial}}\over{{\it \partial}\,{\it t_0}}}\,f \left({\it t_0}\right)\right|_{\left[ {\it t_0}=x\,y \right] } \right)\,y + \left(\left.{{{\it \partial}}\over{{\it \partial}\,{\it t_0}}}\,f \left({\it t_0}\right)\right|_{\left[ {\it t_0}=x\,y \right] } \right)\,{\it y} sage: latex(maxima(derivative(f(x,y,d), d,x,x,y))) - {{{\it \partial}^4}\over{{\it \partial}\,d\,{\it \partial}\,x^2\, {\it \partial}\,y}}\,f\left(x , y , d\right) + {{{\it \partial}^4}\over{{\it \partial}\,{\it d}\, {\it \partial}\,{\it x}^2\,{\it \partial}\, {\it y}}}\,f\left({\it x} , {\it y} , {\it d}\right) sage: latex(maxima(d/(d-2))) - {{d}\over{d-2}} + {{{\it d}}\over{{\it d}-2}} """ self._check_valid() P = self.parent() @@ -1771,7 +1771,8 @@ def _latex_(self): '\\%':'', '\\arcsin ':'\\sin^{-1} ', '\\arccos ':'\\cos^{-1} ', - '\\arctan ':'\\tan^{-1} '}, s) + '\\arctan ':'\\tan^{-1} ', + '\\_SAGE\\_VAR\\_':''}, s) # Fix a maxima bug, which gives a latex representation of multiplying # two numbers as a single space. This was really bad when 2*17^(1/3) @@ -1898,7 +1899,7 @@ def _operation(self, operation, right): sage: f = maxima.cos(x) sage: f._operation("+", f) - 2*cos(x) + 2*cos(_SAGE_VAR_x) """ P = self._check_valid() @@ -2183,25 +2184,20 @@ def _add_(self, f): sage: f+3 sin(x)+3 - :: + The Maxima variable ``x`` is different from the Sage symbolic variable:: + sage: (f+maxima.cos(x)) + cos(_SAGE_VAR_x)+sin(x) + sage: (f+maxima.cos(y)) + cos(_SAGE_VAR_y)+sin(x) + + Note that you may get unexpected results when calling symbolic expressions + and not explicitly giving the variables:: + sage: (f+maxima.cos(x))(2) - sin(2)+cos(2) - sage: (f+maxima.cos(y)) # This is a function with only ONE argument! - cos(y)+sin(x) + cos(_SAGE_VAR_x)+sin(2) sage: (f+maxima.cos(y))(2) - cos(y)+sin(2) - - :: - - sage: f = maxima.function('x','sin(x)') - sage: g = -maxima.cos(x) - sage: g+f - sin(x)-cos(x) - sage: (g+f)(2) # The sum IS a function - sin(2)-cos(2) - sage: 2+f - sin(x)+2 + cos(_SAGE_VAR_y)+sin(2) """ return self._operation("+", f) @@ -2213,20 +2209,21 @@ def _sub_(self, f): sage: x,y = var('x,y') sage: f = maxima.function('x','sin(x)') - sage: g = -maxima.cos(x) # not a function - sage: f-g - sin(x)+cos(x) - sage: (f-g)(2) - sin(2)+cos(2) - sage: (f-maxima.cos(y)) # This function only has the argument x! - sin(x)-cos(y) - sage: _(2) - sin(2)-cos(y) - - :: - - sage: g-f - -sin(x)-cos(x) + + The Maxima variable ``x`` is different from the Sage symbolic variable:: + + sage: (f-maxima.cos(x)) + sin(x)-cos(_SAGE_VAR_x) + sage: (f-maxima.cos(y)) + sin(x)-cos(_SAGE_VAR_y) + + Note that you may get unexpected results when calling symbolic expressions + and not explicitly giving the variables:: + + sage: (f-maxima.cos(x))(2) + sin(2)-cos(_SAGE_VAR_x) + sage: (f-maxima.cos(y))(2) + sin(2)-cos(_SAGE_VAR_y) """ return self._operation("-", f) diff --git a/src/sage/interfaces/maxima_lib.py b/src/sage/interfaces/maxima_lib.py index 7d90915ca29..9f9e1436ae4 100644 --- a/src/sage/interfaces/maxima_lib.py +++ b/src/sage/interfaces/maxima_lib.py @@ -255,7 +255,7 @@ def max_to_string(s): sage: from sage.interfaces.maxima_lib import maxima_lib, max_to_string sage: ecl = maxima_lib(cos(x)).ecl() sage: max_to_string(ecl) - 'cos(x)' + 'cos(_SAGE_VAR_x)' """ return maxprint(s).python()[1:-1] @@ -420,11 +420,13 @@ def _eval_line(self, line, locals=None, reformat=True, **kwds): else: statement = line[:ind_semi] line = line[ind_semi+1:] - if statement: result = ((result + '\n') if result else '') + max_to_string(maxima_eval("#$%s$"%statement)) + if statement: + result = ((result + '\n') if result else '') + max_to_string(maxima_eval("#$%s$"%statement)) else: statement = line[:ind_dollar] line = line[ind_dollar+1:] - if statement: _ = maxima_eval("#$%s$"%statement) + if statement: + _ = maxima_eval("#$%s$"%statement) if not reformat: return result return ''.join([x.strip() for x in result.split()]) @@ -561,14 +563,21 @@ def _create(self, value, name=None): sage: maxima_lib._create(c,'m') 'm' sage: maxima_lib.get('m') - 'x+cos(19)' + '_SAGE_VAR_x+cos(19)' sage: maxima_lib.clear('m') """ name = self._next_var_name() if name is None else name - if isinstance(value,EclObject): - maxima_eval([[msetq],cadadr("#$%s$#$"%name),value]) - else: - self.set(name, value) + try: + if isinstance(value,EclObject): + maxima_eval([[msetq],cadadr("#$%s$#$"%name),value]) + else: + self.set(name, value) + except RuntimeError as error: + s = str(error) + if "Is" in s: # Maxima asked for a condition + self._missing_assumption(s) + else: + raise return name def _function_class(self): @@ -651,7 +660,7 @@ def sr_integral(self,*args): Traceback (most recent call last): ... ValueError: Computation failed since Maxima requested additional - constraints; using the 'assume' command before integral evaluation + constraints; using the 'assume' command before evaluation *may* help (example of legal syntax is 'assume(a>0)', see `assume?` for more details) Is a positive or negative? @@ -664,7 +673,7 @@ def sr_integral(self,*args): Traceback (most recent call last): ... ValueError: Computation failed since Maxima requested additional - constraints; using the 'assume' command before integral evaluation + constraints; using the 'assume' command before evaluation *may* help (example of legal syntax is 'assume(n>0)', see `assume?` for more details) Is n equal to -1? @@ -733,7 +742,7 @@ def sr_integral(self,*args): 4 This definite integral returned zero (incorrectly) in at least - maxima-5.23. The correct answer is now given (:trac:`11591`):: + Maxima 5.23. The correct answer is now given (:trac:`11591`):: sage: f = (x^2)*exp(x) / (1+exp(x))^2 sage: integrate(f, (x, -infinity, infinity)) @@ -757,6 +766,15 @@ def sr_integral(self,*args): sage: maxima('radexpand: true') true + The following integral was computed incorrectly in versions of + Maxima before 5.27 (see :trac:`12947`):: + + sage: a = integrate(x*cos(x^3),(x,0,1/2)).n() + sage: a.real() + 0.124756040961038 + sage: a.imag().abs() < 3e-17 + True + """ try: return max_to_sr(maxima_eval(([max_integrate],[sr_to_max(SR(a)) for a in args]))) @@ -767,10 +785,7 @@ def sr_integral(self,*args): # if "divergent" in s or 'Principal Value' in s: raise ValueError("Integral is divergent.") elif "Is" in s: # Maxima asked for a condition - j = s.find('Is ') - s = s[j:] - k = s.find(' ', 3) - raise ValueError("Computation failed since Maxima requested additional constraints; using the 'assume' command before integral evaluation *may* help (example of legal syntax is 'assume(" + s[3:k] + ">0)', see `assume?` for more details)\n" + s) + self._missing_assumption(s) else: raise @@ -796,7 +811,7 @@ def sr_sum(self,*args): Traceback (most recent call last): ... ValueError: Computation failed since Maxima requested additional - constraints; using the 'assume' command before summation *may* help + constraints; using the 'assume' command before evaluation *may* help (example of legal syntax is 'assume(abs(q)-1>0)', see `assume?` for more details) Is abs(q)-1 positive, negative or zero? @@ -838,6 +853,13 @@ def sr_sum(self,*args): ... RuntimeError: ECL says: Error executing code in Maxima: Zero to negative power computed. + Similar situation for :trac:`12410`:: + + sage: x = var('x') + sage: sum(1/x*(-1)^x, x, 0, oo) + Traceback (most recent call last): + ... + RuntimeError: ECL says: Error executing code in Maxima: Zero to negative power computed. """ try: return max_to_sr(maxima_eval([[max_ratsimp],[[max_simplify_sum],([max_sum],[sr_to_max(SR(a)) for a in args])]])); @@ -849,10 +871,7 @@ def sr_sum(self,*args): # if "divergent" in s or 'Pole encountered' in s: raise ValueError("Sum is divergent.") elif "Is" in s: # Maxima asked for a condition - j = s.find('Is ') - s = s[j:] - k = s.find(' ', 3) - raise ValueError("Computation failed since Maxima requested additional constraints; using the 'assume' command before summation *may* help (example of legal syntax is 'assume(" + s[3:k] + ">0)', see `assume?` for more details)\n" + s) + self._missing_assumption(s) else: raise @@ -875,16 +894,15 @@ def sr_limit(self,expr,v,a,dir=None): Traceback (most recent call last): ... ValueError: Computation failed since Maxima requested additional - constraints; using the 'assume' command before limit evaluation - *may* help (see `assume?` for more details) + constraints; using the 'assume' command before evaluation + *may* help (example of legal syntax is 'assume(a>0)', see `assume?` + for more details) Is a positive, negative or zero? sage: assume(a>0) sage: limit(x^a,x=0) Traceback (most recent call last): ... - ValueError: Computation failed since Maxima requested additional - constraints; using the 'assume' command before limit evaluation - *may* help (see `assume?` for more details) + ValueError: Computation failed ... Is a an integer? sage: assume(a,'integer') sage: assume(a,'even') # Yes, Maxima will ask this too @@ -922,9 +940,7 @@ def sr_limit(self,expr,v,a,dir=None): except RuntimeError as error: s = str(error) if "Is" in s: # Maxima asked for a condition - j = s.find('Is ') - s = s[j:] - raise ValueError("Computation failed since Maxima requested additional constraints; using the 'assume' command before limit evaluation *may* help (see `assume?` for more details)\n" + s) + self._missing_assumption(s) else: raise @@ -944,7 +960,32 @@ def sr_tlimit(self,expr,v,a,dir=None): elif dir == "minus": L.append(max_minus) return max_to_sr(maxima_eval(([max_tlimit],L))) - + + def _missing_assumption(self,errstr): + """ + Helper function for unified handling of failed computation because an + assumption was missing. + + EXAMPLES:: + + sage: from sage.interfaces.maxima_lib import maxima_lib + sage: maxima_lib._missing_assumption('Is xyz a thing?') + Traceback (most recent call last): + ... + ValueError: Computation failed ... + Is xyz a thing? + """ + j = errstr.find('Is ') + errstr = errstr[j:] + jj = 2 + if errstr[3] == ' ': + jj = 3 + k = errstr.find(' ',jj+1) + + outstr = "Computation failed since Maxima requested additional constraints; using the 'assume' command before evaluation *may* help (example of legal syntax is 'assume("\ + + errstr[jj+1:k] +">0)', see `assume?` for more details)\n" + errstr + outstr = outstr.replace('_SAGE_VAR_','') + raise ValueError(outstr) def is_MaximaLibElement(x): r""" @@ -974,7 +1015,7 @@ class MaximaLibElement(MaximaAbstractElement): sage: maxima_lib(4) 4 sage: maxima_lib(log(x)) - log(x) + log(_SAGE_VAR_x) """ def ecl(self): @@ -989,7 +1030,7 @@ def ecl(self): sage: from sage.interfaces.maxima_lib import maxima_lib sage: maxima_lib(x+cos(19)).ecl() - + """ try: return self._ecl @@ -1115,6 +1156,7 @@ def reduce_load_MaximaLib(): cadadr=EclObject("cadadr") meval=EclObject("meval") NIL=EclObject("NIL") +lisp_length=EclObject("length") ## Dictionaries for standard operators sage_op_dict = { @@ -1216,14 +1258,15 @@ def sage_rat(x,y): ## Here we build dictionaries for operators needing special conversions. -ratdisrep=EclObject("ratdisrep") -mrat=EclObject("MRAT") -mqapply=EclObject("MQAPPLY") -max_li=EclObject("$LI") -max_psi=EclObject("$PSI") -max_array=EclObject("ARRAY") -mdiff=EclObject("%DERIVATIVE") -max_lambert_w=sage_op_dict[sage.functions.log.lambert_w] +ratdisrep = EclObject("ratdisrep") +mrat = EclObject("MRAT") +mqapply = EclObject("MQAPPLY") +max_li = EclObject("$LI") +max_psi = EclObject("$PSI") +max_hyper = EclObject("$%F") +max_array = EclObject("ARRAY") +mdiff = EclObject("%DERIVATIVE") +max_lambert_w = sage_op_dict[sage.functions.log.lambert_w] def mrat_to_sage(expr): r""" @@ -1247,9 +1290,9 @@ def mrat_to_sage(expr): (x, y, z) sage: c = maxima_lib((x+y^2+z^9)/x^6+z^8/y).rat() sage: c - (y*z^9+x^6*z^8+y^3+x*y)/(x^6*y) + (_SAGE_VAR_y*_SAGE_VAR_z^9+_SAGE_VAR_x^6*_SAGE_VAR_z^8+_SAGE_VAR_y^3+_SAGE_VAR_x*_SAGE_VAR_y)/(_SAGE_VAR_x^6*_SAGE_VAR_y) sage: c.ecl() - sage: mrat_to_sage(c.ecl()) (x^6*z^8 + y*z^9 + y^3 + x*y)/(x^6*y) @@ -1279,10 +1322,14 @@ def mqapply_to_sage(expr): """ if caaadr(expr) == max_li: return sage.functions.log.polylog(max_to_sr(cadadr(expr)), - max_to_sr(caddr(expr))) + max_to_sr(caddr(expr))) if caaadr(expr) == max_psi: return sage.functions.other.psi(max_to_sr(cadadr(expr)), - max_to_sr(caddr(expr))) + max_to_sr(caddr(expr))) + if caaadr(expr) == max_hyper: + return sage.functions.hypergeometric.hypergeometric(mlist_to_sage(car(cdr(cdr(expr)))), + mlist_to_sage(car(cdr(cdr(cdr(expr))))), + max_to_sr(car(cdr(cdr(cdr(cdr(expr))))))) else: op=max_to_sr(cadr(expr)) max_args=cddr(expr) @@ -1318,7 +1365,7 @@ def mlist_to_sage(expr): - ``expr`` - ECL object; a Maxima MLIST expression (i.e., a list) - OUTPUT: a python list of converted expressions. + OUTPUT: a Python list of converted expressions. EXAMPLES:: @@ -1415,7 +1462,8 @@ def dummy_integrate(expr): sage.functions.log.polylog : lambda N,X : [[mqapply],[[max_li, max_array],N],X], sage.functions.other.psi1 : lambda X : [[mqapply],[[max_psi, max_array],0],X], sage.functions.other.psi2 : lambda N,X : [[mqapply],[[max_psi, max_array],N],X], - sage.functions.log.lambert_w : lambda N,X : [[max_lambert_w], X] if N==EclObject(0) else [[mqapply],[[max_lambert_w, max_array],N],X] + sage.functions.log.lambert_w : lambda N,X : [[max_lambert_w], X] if N==EclObject(0) else [[mqapply],[[max_lambert_w, max_array],N],X], + sage.functions.hypergeometric.hypergeometric : lambda A, B, X : [[mqapply],[[max_hyper, max_array],lisp_length(A.cdr()),lisp_length(B.cdr())],A,B,X] } @@ -1537,6 +1585,8 @@ def sr_to_max(expr): return EclObject(l) elif (op in special_sage_to_max): return EclObject(special_sage_to_max[op](*[sr_to_max(o) for o in expr.operands()])) + elif op == tuple: + return EclObject( ([mlist],list(sr_to_max(op) for op in expr.operands())) ) elif not (op in sage_op_dict): # Maxima does some simplifications automatically by default # so calling maxima(expr) can change the structure of expr diff --git a/src/sage/interfaces/r.py b/src/sage/interfaces/r.py index c583dea45fa..9b52618d015 100644 --- a/src/sage/interfaces/r.py +++ b/src/sage/interfaces/r.py @@ -512,9 +512,8 @@ def _source(self, s): EXAMPLES:: - sage: print r._source("print.anova") - function (x, digits = max(getOption("digits") - 2L, 3L), signif.stars = getOption("show.signif.stars"), - ... + sage: print r._source("c") + function (..., recursive = FALSE) .Primitive("c") """ if s[-2:] == "()": s = s[-2:] @@ -532,9 +531,8 @@ def source(self, s): EXAMPLES:: - sage: print r.source("print.anova") - function (x, digits = max(getOption("digits") - 2L, 3L), signif.stars = getOption("show.signif.stars"), - ... + sage: print r.source("c") + function (..., recursive = FALSE) .Primitive("c") """ return self._source(s) @@ -701,14 +699,11 @@ def help(self, command): EXAMPLES:: - sage: r.help('print.anova') - anova package:stats R Documentation - ... - Chambers, J. M. and Hastie, T. J. (1992) _Statistical Models in - S_, Wadsworth & Brooks/Cole. + sage: r.help('c') + c package:base R Documentation ... - .. note:: + .. note:: This is similar to typing r.command?. """ diff --git a/src/sage/interfaces/tides.py b/src/sage/interfaces/tides.py new file mode 100644 index 00000000000..87451c8f031 --- /dev/null +++ b/src/sage/interfaces/tides.py @@ -0,0 +1,893 @@ +r""" +This module contains tools to write the .c files needed for TIDES [TI]_ . + +Tides is an integration engine based on the Taylor method. It is implemented +as a c library. The user must translate its initial value problem (IVP) into a +pair of .c files that will then be compiled and linked against the TIDES +library. The reulting binary will produce the desired output. The tools in this +module can be used to automate the generation of these files from the symbolic +expression of the differential equation. + +########################################################################## +# Copyright (C) 2014 Miguel Marco , Marcos Rodriguez +# +# +# Distributed under the terms of the GNU General Public License (GPL): +# +# http://www.gnu.org/licenses/ +########################################################################## + +AUTHORS: + +- Miguel Marco (06-2014) - Implementation of tides solver + +- Marcos Rodriguez (06-2014) - Implementation of tides solver + +- Alberto Abad (06-2014) - tides solver + +- Roberto Barrio (06-2014) - tides solver + +REFERENCES: + +.. [ALG924] A. Abad, R. Barrio, F. Blesa, M. Rodriguez. Algorithm 924. *ACM +Transactions on Mathematical Software*, *39*(1), 1-28. + +.. [TI](http://www.unizar.es/acz/05Publicaciones/Monografias/MonografiasPublicadas/Monografia36/IndMonogr36.htm) +A. Abad, R. Barrio, F. Blesa, M. Rodriguez. +TIDES tutorial: Integrating ODEs by using the Taylor Series Method. +""" + + + +from sage.rings.real_mpfr import RealField +import shutil +import os +from sage.calculus.all import symbolic_expression +from sage.misc.flatten import flatten +from sage.ext.fast_callable import fast_callable +from sage.rings.semirings.non_negative_integer_semiring import NN +from sage.misc.functional import N +from sage.functions.log import log +from sage.functions.other import floor, sqrt + + + +def subexpressions_list(f, pars=None): + """ + Construct the lists with the intermediate steps on the evaluation of the + function. + + INPUT: + + - ``f`` -- a symbolic function of several components. + + - ``pars`` -- a list of the parameters that appear in the function + this should be the symbolic constants that appear in f but are not + arguments. + + OTUPUT: + + - a list of the intermediate subexpressions that appear in the evaluation + of f. + + - a list with the operations used to construct each of the subexpressions. + each element of this list is a tuple, formed by a string describing the + operation made, and the operands. + + For the trigonometric functions, some extra expressions will be added. + These extra expressions will be used later to compute their derivatives. + + + EXAMPLES:: + + sage: from sage.interfaces.tides import subexpressions_list + sage: var('x,y') + (x, y) + sage: f(x,y) = [x^2+y, cos(x)/log(y)] + sage: subexpressions_list(f) + ([x^2, x^2 + y, sin(x), cos(x), log(y), cos(x)/log(y)], + [('mul', x, x), + ('add', y, x^2), + ('sin', x), + ('cos', x), + ('log', y), + ('div', log(y), cos(x))]) + + :: + + sage: f(a)=[cos(a), arctan(a)] + sage: from sage.interfaces.tides import subexpressions_list + sage: subexpressions_list(f) + ([sin(a), cos(a), a^2, a^2 + 1, arctan(a)], + [('sin', a), ('cos', a), ('mul', a, a), ('add', 1, a^2), ('atan', a)]) + + :: + + sage: from sage.interfaces.tides import subexpressions_list + sage: var('s,b,r') + (s, b, r) + sage: f(t,x,y,z)= [s*(y-x),x*(r-z)-y,x*y-b*z] + sage: subexpressions_list(f,[s,b,r]) + ([-y, + x - y, + s*(x - y), + -s*(x - y), + -z, + r - z, + (r - z)*x, + -y, + (r - z)*x - y, + x*y, + b*z, + -b*z, + x*y - b*z], + [('mul', -1, y), + ('add', -y, x), + ('mul', x - y, s), + ('mul', -1, s*(x - y)), + ('mul', -1, z), + ('add', -z, r), + ('mul', x, r - z), + ('mul', -1, y), + ('add', -y, (r - z)*x), + ('mul', y, x), + ('mul', z, b), + ('mul', -1, b*z), + ('add', -b*z, x*y)]) + + """ + from sage.functions.trig import sin, cos, arcsin, arctan, arccos + variables = f[0].arguments() + if not pars: + parameters = [] + else: + parameters = pars + varpar = list(parameters) + list(variables) + F = symbolic_expression([i(*variables) for i in f]).function(*varpar) + lis = flatten([fast_callable(i,vars=varpar).op_list() for i in F], max_level=1) + deflist = [] + stack = [] + const =[] + stackcomp=[] + detail=[] + for i in lis: + if i[0] == 'load_arg': + stack.append(varpar[i[1]]) + elif i[0] == 'ipow': + if i[1] in NN: + basis = stack[-1] + for j in range(i[1]-1): + a=stack.pop(-1) + detail.append(('mul', a, basis)) + stack.append(a*basis) + stackcomp.append(stack[-1]) + else: + detail.append(('pow',stack[-1],i[1])) + stack[-1]=stack[-1]**i[1] + stackcomp.append(stack[-1]) + + elif i[0] == 'load_const': + const.append(i[1]) + stack.append(i[1]) + elif i == 'mul': + a=stack.pop(-1) + b=stack.pop(-1) + detail.append(('mul', a, b)) + stack.append(a*b) + stackcomp.append(stack[-1]) + + elif i == 'div': + a=stack.pop(-1) + b=stack.pop(-1) + detail.append(('div', a, b)) + stack.append(b/a) + stackcomp.append(stack[-1]) + + elif i == 'add': + a=stack.pop(-1) + b=stack.pop(-1) + detail.append(('add',a,b)) + stack.append(a+b) + stackcomp.append(stack[-1]) + + elif i == 'pow': + a=stack.pop(-1) + b=stack.pop(-1) + detail.append(('pow', b, a)) + stack.append(b**a) + stackcomp.append(stack[-1]) + + elif i[0] == 'py_call' and str(i[1])=='log': + a=stack.pop(-1) + detail.append(('log', a)) + stack.append(log(a)) + stackcomp.append(stack[-1]) + + elif i[0] == 'py_call' and str(i[1])=='exp': + a=stack.pop(-1) + detail.append(('exp', a)) + stack.append(exp(a)) + stackcomp.append(stack[-1]) + + elif i[0] == 'py_call' and str(i[1])=='sin': + a=stack.pop(-1) + detail.append(('sin', a)) + detail.append(('cos', a)) + stackcomp.append(sin(a)) + stackcomp.append(cos(a)) + stack.append(sin(a)) + + elif i[0] == 'py_call' and str(i[1])=='cos': + a=stack.pop(-1) + detail.append(('sin', a)) + detail.append(('cos', a)) + stackcomp.append(sin(a)) + stackcomp.append(cos(a)) + stack.append(cos(a)) + + elif i[0] == 'py_call' and str(i[1])=='tan': + a=stack.pop(-1) + b = sin(a) + c = cos(a) + detail.append(('sin', a)) + detail.append(('cos', a)) + detail.append(('div', b, c)) + stackcomp.append(b) + stackcomp.append(c) + stackcomp.append(b/c) + stack.append(b/c) + + elif i[0] == 'py_call' and str(i[1])=='arctan': + a=stack.pop(-1) + detail.append(('mul', a, a)) + detail.append(('add', 1, a*a)) + detail.append(('atan', a)) + stackcomp.append(a*a) + stackcomp.append(1+a*a) + stackcomp.append(arctan(a)) + stack.append(arctan(a)) + + elif i[0] == 'py_call' and str(i[1])=='arcsin': + a=stack.pop(-1) + detail.append(('mul', a, a)) + detail.append(('mul', -1, a*a)) + detail.append(('add', 1, -a*a)) + detail.append(('pow', 1- a*a, 0.5)) + detail.append(('asin', a)) + stackcomp.append(a*a) + stackcomp.append(-a*a) + stackcomp.append(1-a*a) + stackcomp.append(sqrt(1-a*a)) + stackcomp.append(arcsin(a)) + stack.append(arcsin(a)) + + elif i[0] == 'py_call' and str(i[1])=='arccos': + a=stack.pop(-1) + detail.append(('mul', a, a)) + detail.append(('mul', -1, a*a)) + detail.append(('add', 1, -a*a)) + detail.append(('pow', 1- a*a, 0.5)) + detail.append(('mul', -1, sqrt(1-a*a))) + detail.append(('acos', a)) + stackcomp.append(a*a) + stackcomp.append(-a*a) + stackcomp.append(1-a*a) + stackcomp.append(sqrt(1-a*a)) + stackcomp.append(-sqrt(1-a*a)) + stackcomp.append(arccos(a)) + stack.append(arccos(a)) + + elif i[0] == 'py_call' and 'sqrt' in str(i[1]): + a=stack.pop(-1) + detail.append(('pow', a, 0.5)) + stackcomp.append(sqrt(a)) + stack.append(sqrt(a)) + + + elif i == 'neg': + a = stack.pop(-1) + detail.append(('mul', -1, a)) + stack.append(-a) + stackcomp.append(-a) + + return stackcomp,detail + + + +def remove_repeated(l1, l2): + """ + Given two lists, remove the repeated elements in l1, and the elements + in l2 that are on the same position. + positions. + + EXAMPLES:: + + sage: from sage.interfaces.tides import (subexpressions_list, remove_repeated) + sage: f(a)=[1 + a^2, arcsin(a)] + sage: l1, l2 = subexpressions_list(f) + sage: l1, l2 + ([a^2, a^2 + 1, a^2, -a^2, -a^2 + 1, sqrt(-a^2 + 1), arcsin(a)], + [('mul', a, a), + ('add', 1, a^2), + ('mul', a, a), + ('mul', -1, a^2), + ('add', 1, -a^2), + ('pow', -a^2 + 1, 0.5), + ('asin', a)]) + sage: remove_repeated(l1, l2) + sage: l1, l2 + ([a^2, a^2 + 1, -a^2, -a^2 + 1, sqrt(-a^2 + 1), arcsin(a)], + [('mul', a, a), + ('add', 1, a^2), + ('mul', -1, a^2), + ('add', 1, -a^2), + ('pow', -a^2 + 1, 0.5), + ('asin', a)]) + + + """ + for i in range(len(l1)-1): + j=i+1 + while j 0. @@ -320,7 +321,6 @@ cdef int singular_polynomial_pow(poly **ret, poly *p, long exp, ring *r) except """ cdef unsigned long v = p_GetMaxExp(p, r) v = v * exp - overflow_check(v, r) if(r != currRing): rChangeCurrRing(r) @@ -407,7 +407,7 @@ cdef object singular_polynomial_latex(poly *p, ring *r, object base, object late \left(z + 1\right) v w - z w^{2} + z v + \left(-z - 1\right) w + z + 1 """ poly = "" - cdef long e,j + cdef unsigned long e,j cdef int n = r.N cdef int atomic_repr = base._repr_option('element_is_atomic') while p: @@ -519,11 +519,41 @@ cdef int singular_vector_maximal_component(poly *v, ring *r) except -1: returns the maximal module component of the vector ``v``. INPUT: - - ``v`` - a polynomial/vector - - ``r`` - a ring + - ``v`` - a polynomial/vector + - ``r`` - a ring """ cdef int res=0 while v!=NULL: res=max(p_GetComp(v, r), res) v = pNext(v) return res + +cdef int singular_polynomial_subst(poly **p, int var_index, poly *value, ring *r) except -1: + """ + Substitute variable ``var_index`` with ``value`` in ``p``. + + INPUT: + + - ``p`` - a polynomial + - ``var_index`` - an integer < ngens (zero based indexing) + - ``value`` - a polynomial + - ``r`` - a ring + """ + + if p_IsConstant(value, r): + p[0] = pSubst(p[0], var_index+1, value) + return 0 + + cdef unsigned long exp = p_GetExp(p[0], var_index+1, r) * p_GetMaxExp(value, r) + + overflow_check(exp, r) + if(r != currRing): + rChangeCurrRing(r) + + cdef int count = singular_polynomial_length_bounded(p[0], 15) + if unlikely(count >= 15 or exp > 15): sig_on() + p[0] = pSubst(p[0], var_index+1, value) + if unlikely(count >= 15 or exp > 15): sig_off() + return 0 + + diff --git a/src/sage/libs/singular/singular.pxd b/src/sage/libs/singular/singular.pxd index d833e4d8c96..b02b53aa226 100644 --- a/src/sage/libs/singular/singular.pxd +++ b/src/sage/libs/singular/singular.pxd @@ -56,6 +56,6 @@ cdef number *sa2si(Element elem, ring * _ring) cdef int overflow_check(long e, ring *_ring) except -1 cdef init_libsingular() -cdef inline unsigned long get_max_exponent_size() + diff --git a/src/sage/libs/singular/singular.pyx b/src/sage/libs/singular/singular.pyx index 37f91e79a17..4eb807b0259 100644 --- a/src/sage/libs/singular/singular.pyx +++ b/src/sage/libs/singular/singular.pyx @@ -585,7 +585,7 @@ cdef object si2sa(number *n, ring *_ring, object base): raise ValueError, "cannot convert from SINGULAR number" cdef number *sa2si(Element elem, ring * _ring): - cdef int i + cdef int i = 0 if PY_TYPE_CHECK(elem._parent, FiniteField_prime_modn): return n_Init(int(elem),_ring) @@ -637,12 +637,9 @@ cdef extern from "dlfcn.h": cdef long RTLD_LAZY cdef long RTLD_GLOBAL -# Our attempt at avoiding exponent overflows. -cdef unsigned int max_exponent_size - cdef int overflow_check(long e, ring *_ring) except -1: """ - Raises an ``OverflowError`` if e is > ``max_exponent_size``, + Raises an ``OverflowError`` if e is > max degree per variable, or if it is not acceptable for Singular as exponent of the given ring. @@ -676,7 +673,8 @@ cdef int overflow_check(long e, ring *_ring) except -1: OverflowError: Exponent overflow (1073741824). # 32-bit """ - if unlikely(e > min(max_exponent_size,max(_ring.N,_ring.bitmask))): + # 2^31 (pPower takes ints) + if unlikely(e >= _ring.bitmask or e >= 2**31): raise OverflowError("Exponent overflow (%d)."%(e)) return 0 @@ -693,7 +691,6 @@ cdef init_libsingular(): """ global singular_options global singular_verbose_options - global max_exponent_size global WerrorS_callback global error_messages @@ -727,19 +724,10 @@ cdef init_libsingular(): On(SW_USE_EZGCD) Off(SW_USE_NTL_SORT) - if is_64_bit: - max_exponent_size = 1<<31-1; - else: - max_exponent_size = 1<<16-1; - WerrorS_callback = libsingular_error_callback error_messages = [] -cdef inline unsigned long get_max_exponent_size(): - global max_exponent_size - return max_exponent_size - # call the init routine init_libsingular() diff --git a/src/sage/matrix/benchmark.py b/src/sage/matrix/benchmark.py index 0fe5142f5af..636c5904118 100644 --- a/src/sage/matrix/benchmark.py +++ b/src/sage/matrix/benchmark.py @@ -21,6 +21,8 @@ from sage.misc.misc import alarm, cancel_alarm, cputime from sage.ext.c_lib import AlarmInterrupt +from sage.interfaces.all import magma + verbose = False timeout = 60 diff --git a/src/sage/matrix/matrix0.pyx b/src/sage/matrix/matrix0.pyx index 55fa383d426..f9c02bcd2ec 100644 --- a/src/sage/matrix/matrix0.pyx +++ b/src/sage/matrix/matrix0.pyx @@ -4666,13 +4666,12 @@ cdef class Matrix(sage.structure.element.Matrix): if PY_TYPE_CHECK(self._base_ring, CommutativeRing): return self._lmul_(left) cdef Py_ssize_t r,c - cpdef RingElement x x = self._base_ring(left) cdef Matrix ans ans = self._parent.zero_matrix().__copy__() for r from 0 <= r < self._nrows: for c from 0 <= c < self._ncols: - ans.set_unsafe(r, c, x._mul_(self.get_unsafe(r, c))) + ans.set_unsafe(r, c, x * self.get_unsafe(r, c)) return ans cpdef ModuleElement _lmul_(self, RingElement right): @@ -4709,13 +4708,12 @@ cdef class Matrix(sage.structure.element.Matrix): """ # derived classes over a commutative base *just* overload this and not _rmul_ cdef Py_ssize_t r,c - cpdef RingElement x x = self._base_ring(right) cdef Matrix ans ans = self._parent.zero_matrix().__copy__() for r from 0 <= r < self._nrows: for c from 0 <= c < self._ncols: - ans.set_unsafe(r, c, (self.get_unsafe(r, c))._mul_(x)) + ans.set_unsafe(r, c, self.get_unsafe(r, c) * x) return ans cdef sage.structure.element.Matrix _matrix_times_matrix_(self, sage.structure.element.Matrix right): @@ -5121,8 +5119,9 @@ cdef class Matrix(sage.structure.element.Matrix): """ if not self.is_square(): raise ArithmeticError("self must be a square matrix") - - return RingElement.__pow__(self, n, ignored) + if ignored is not None: + raise RuntimeError("__pow__ third argument not used") + return sage.structure.element.generic_power_c(self, n, None) ################################################### # Comparison diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index e657f6fb26b..ea8321f5603 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -35,7 +35,6 @@ from sage.misc.randstate cimport randstate, current_randstate from sage.structure.sequence import Sequence from sage.structure.element import is_Vector from sage.misc.misc import verbose, get_verbose -from sage.misc.temporary_file import graphics_filename from sage.rings.number_field.number_field_base import is_NumberField from sage.rings.integer_ring import ZZ, is_IntegerRing from sage.rings.integer import Integer @@ -5287,7 +5286,7 @@ cdef class Matrix(matrix1.Matrix): EXAMPLES:: - sage: a = matrix(QQ, 4, range(16)); a + sage: a = matrix(ZZ, 4, range(16)); a [ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11] @@ -5344,49 +5343,66 @@ cdef class Matrix(matrix1.Matrix): sage: M.eigenvalues(extend=False) [2] + The method also works for matrices over finite fields:: + + sage: M = matrix(GF(3), [[0,1,1],[1,2,0],[2,0,1]]) + sage: ev = M.eigenvalues(); ev + [2*z3, 2*z3 + 2, 2*z3 + 1] + + Similarly as in the case of QQbar, the eigenvalues belong to some + algebraic closure but they can be converted to elements of a finite + field:: + + sage: e = ev[0] + sage: e.parent() + Algebraic closure of Finite Field of size 3 + sage: e.as_finite_field_element() + (Finite Field in z3 of size 3^3, 2*z3, Ring morphism: + From: Finite Field in z3 of size 3^3 + To: Algebraic closure of Finite Field of size 3 + Defn: z3 |--> z3) """ x = self.fetch('eigenvalues') - if not x is None: + if x is not None: if not extend: - x1=Sequence([]) - for i in x: - if i in self.base_ring(): - x1.append(i) - x=x1 + x = Sequence(i for i in x if i in self.base_ring()) return x if not self.base_ring().is_exact(): from warnings import warn warn("Using generic algorithm for an inexact ring, which will probably give incorrect results due to numerical precision issues.") - from sage.rings.qqbar import QQbar - G = self.fcp() # factored charpoly of self. - V = [] - i=0 - for h, e in G: - if h.degree() == 1: - alpha = [-h[0]/h[1]] - V.extend(alpha*e) - else: - if extend: - F = h.root_field('%s%s'%('a',i)) - try: - alpha = F.gen(0).galois_conjugates(QQbar) - except AttributeError: - raise NotImplementedError, "eigenvalues() is not implemented for matrices with eigenvalues that are not in the fraction field of the base ring or in QQbar" - V.extend(alpha*e) - i+=1 - V = Sequence(V) - if extend: - self.cache('eigenvalues', V) if not extend: - V1=Sequence([]) - for i in V: - if i in self.base_ring(): - V1.append(i) - V=V1 - return V + return Sequence(r for r,m in self.charpoly().roots() for _ in xrange(m)) + # now we need to find a natural algebraic closure for the base ring + K = self.base_ring() + try: + is_field = K.is_field() + except (ValueError,AttributeError): + is_field = False + + if not is_field: + if not K.is_integral_domain(): + raise NotImplementedError("eigenvalues() not implemented for non integral domains") + K = K.fraction_field() + + try: + A = K.algebraic_closure() + except (AttributeError,ValueError): + raise NotImplementedError("algebraic closure is not implemented for %s"%K) + + res = [] + for f, e in self.charpoly().change_ring(K).factor(): + if f.degree() == 1: + res.extend([-f.constant_coefficient()]*e) + else: + for r,ee in f.change_ring(A).roots(): + res.extend([r]*(e*ee)) + + eigenvalues = Sequence(res) + self.cache('eigenvalues', eigenvalues) + return eigenvalues def eigenvectors_left(self,extend=True): @@ -7924,7 +7940,7 @@ cdef class Matrix(matrix1.Matrix): EXAMPLE:: sage: M = random_matrix(CC, 4) - sage: M.visualize_structure(os.path.join(SAGE_TMP, "matrix.png")) + sage: M.visualize_structure() """ import gd import os @@ -7979,6 +7995,7 @@ cdef class Matrix(matrix1.Matrix): setPixel((y,x), val) if filename is None: + from sage.misc.temporary_file import graphics_filename filename = graphics_filename() im.writePng(filename) @@ -11406,6 +11423,20 @@ cdef class Matrix(matrix1.Matrix): sage: isinstance(ds, tuple), isinstance(dh, tuple) (True, True) + We check that :trac:`16633` is fixed:: + + sage: A = matrix(QQ, [[ 4, -2, 4, 2], + ....: [-2, 10, -2, -7], + ....: [ 4, -2, 8, 4], + ....: [ 2, -7, 4, 7]]) + sage: A.set_immutable() + sage: L,d = A._indefinite_factorization('symmetric') + sage: A + [ 4 -2 4 2] + [-2 10 -2 -7] + [ 4 -2 8 4] + [ 2 -7 4 7] + AUTHOR: - Rob Beezer (2012-05-24) @@ -11453,6 +11484,10 @@ cdef class Matrix(matrix1.Matrix): # we need a copy no matter what, so we # (potentially) change to fraction field at the same time L = self.change_ring(F) + # The change ring doesn't necessarily return a copy if ``self`` + # is immutable and ``F`` is the same as the base ring + if L is self: + L = self.__copy__() m = L._nrows zero = F(0) one = F(1) @@ -13211,7 +13246,7 @@ cdef class Matrix(matrix1.Matrix): sage: A.eigenvalues() Traceback (most recent call last): ... - NotImplementedError: eigenvalues() is not implemented for matrices with eigenvalues that are not in the fraction field of the base ring or in QQbar + NotImplementedError: algebraic closures of finite fields are only implemented for prime fields Subdivisions are optional. :: @@ -13588,7 +13623,7 @@ cdef class Matrix(matrix1.Matrix): sage: A.eigenvalues() Traceback (most recent call last): ... - NotImplementedError: eigenvalues() is not implemented for matrices with eigenvalues that are not in the fraction field of the base ring or in QQbar + NotImplementedError: algebraic closures of finite fields are only implemented for prime fields Companion matrices may be selected as any one of four different types. See the documentation for the companion matrix constructor, diff --git a/src/sage/matrix/matrix_modn_sparse.pyx b/src/sage/matrix/matrix_modn_sparse.pyx index 3cdc57035dc..12a7bb2ca6b 100644 --- a/src/sage/matrix/matrix_modn_sparse.pyx +++ b/src/sage/matrix/matrix_modn_sparse.pyx @@ -642,12 +642,8 @@ cdef class Matrix_modn_sparse(matrix_sparse.Matrix_sparse): setPixel( (x,y), colorExact((r-delta,g-delta,b-delta)) ) if filename is None: - from sage.misc.temporary_file import graphics_filename, tmp_dir - from sage.doctest import DOCTEST_MODE + from sage.misc.temporary_file import graphics_filename filename = graphics_filename() - if DOCTEST_MODE: - import os - filename = os.path.join(tmp_dir(), filename) im.writePng(filename) diff --git a/src/sage/matroids/catalog.py b/src/sage/matroids/catalog.py index e233b51ac27..ca352a47997 100644 --- a/src/sage/matroids/catalog.py +++ b/src/sage/matroids/catalog.py @@ -1342,12 +1342,9 @@ def Block_9_4(): sage: M = matroids.named_matroids.Block_9_4() sage: M.is_valid() # long time True - sage: C = M.nonspanning_circuits() - sage: D = {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5, 'g': 6, - ....: 'h': 7, 'i': 8} - sage: B = [[D[x] for x in L] for L in C] - sage: BlockDesign(9, B).is_block_design() - (True, [2, 9, 4, 3]) + sage: BD = designs.BlockDesign(M.groundset(), M.nonspanning_circuits()) + sage: BD.is_t_design(return_parameters=True) + (True, (2, 9, 4, 3)) """ E = 'abcdefghi' CC = { @@ -1369,12 +1366,9 @@ def Block_10_5(): sage: M = matroids.named_matroids.Block_10_5() sage: M.is_valid() # long time True - sage: C = M.nonspanning_circuits() - sage: D = {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5, 'g': 6, - ....: 'h': 7, 'i': 8, 'j': 9} - sage: B = [[D[x] for x in L] for L in C] - sage: BlockDesign(10, B).is_block_design() - (True, [3, 10, 5, 3]) + sage: BD = designs.BlockDesign(M.groundset(), M.nonspanning_circuits()) + sage: BD.is_t_design(return_parameters=True) + (True, (3, 10, 5, 3)) """ E = 'abcdefghij' diff --git a/src/sage/matroids/matroid.pxd b/src/sage/matroids/matroid.pxd index 2f12ec85d66..5f433036ee2 100644 --- a/src/sage/matroids/matroid.pxd +++ b/src/sage/matroids/matroid.pxd @@ -3,6 +3,7 @@ from sage.structure.sage_object cimport SageObject cdef class Matroid(SageObject): cdef public __custom_name cdef public _custom_name + cdef public _cached_info cdef int _stored_full_rank cdef int _stored_size @@ -147,3 +148,8 @@ cdef class Matroid(SageObject): cpdef _external(self, B) cpdef tutte_polynomial(self, x=*, y=*) cpdef flat_cover(self) + + # visualization + cpdef plot(self,B=*,lineorders=*,pos_method=*,pos_dict=*,save_pos=*) + cpdef show(self,B=*,lineorders=*,pos_method=*,pos_dict=*,save_pos=*,lims=*) + cpdef _fix_positions(self,pos_dict=*,lineorders=*) diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index 472cd1f1ef4..b387cc4c993 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -113,6 +113,10 @@ additional functionality (e.g. linear extensions). - Invariants - :meth:`tutte_polynomial() ` - :meth:`flat_cover() ` + +- Visualization + - :meth:`show() ` + - :meth:`plot() ` In addition to these, all methods provided by :class:`SageObject ` are available, @@ -4890,3 +4894,151 @@ cdef class Matroid(SageObject): eps = 0.00000001 return [F for F in FF if fsol[F] > 1 - eps] + + cpdef plot(self, B=None, lineorders=None, pos_method=None,pos_dict=None,save_pos=False): + """ + Return geometric representation as a sage graphics object. + + INPUT: + + - ``B`` -- (optional) a list containing a basis. + If internal point placement is used, these elements will be placed as vertices of a triangle. + - ``lineorders`` -- (optional) A list of lists where each of the inner lists + specify ground set elements in a certain order which will be used to draw the + corresponding line in geometric representation (if it exists). + - ``pos_method`` -- An integer specifying positioning method. + - ``0``: default positioning + - ``1``: use pos_dict if it is not ``None`` + - ``2``: Force directed (Not yet implemented). + + - ``pos_dict``: A dictionary mapping ground set elements to their (x,y) positions. + - ``save_pos``: A boolean indicating that point placements (either internal or user provided) and + line orders (if provided) will be cached in the matroid (``M._cached_info``) and can be used for + reproducing the geometric representation during the same session + + OUTPUT: + + A sage graphics object of type that + corresponds to the geometric representation of the matroid + + EXAMPLES:: + + sage: M=matroids.named_matroids.Fano() + sage: G=M.plot() + sage: type(G) + + sage: G.show() + + """ + import matroids_plot_helpers + if pos_method == 1 and pos_dict != None: + # check sanity of pos_dict and add it to cached info if sane + if matroids_plot_helpers.posdict_is_sane(self, pos_dict) == True: + self._cached_info={'plot_positions':pos_dict, 'lineorders':lineorders} + # placeholder for aditional placement methods. Only need to compute positions and update self._cached_info + elif pos_method == 2: + raise NotImplementedError + + if self._cached_info == None: + self._cached_info={'plot_positions':None,'lineorders': None} + if 'plot_positions' not in self._cached_info.keys(): + self._cached_info['plot_positions'] = None + if 'lineorders' not in self._cached_info.keys(): + self._cached_info['lineorders'] = None + + if self.rank() > 3: + raise NotImplementedError + elif B == None: + B = list(self.basis()) + elif B != None and self.is_basis(B)==False: + return + lineorders2=matroids_plot_helpers.lineorders_union(self._cached_info['lineorders'],lineorders) + return matroids_plot_helpers.geomrep(self,B,lineorders2,pd=pos_dict, sp=save_pos) + + cpdef show(self,B=None,lineorders=None,pos_method=None,pos_dict=None,save_pos=False,lims=None): + """ + Show the geometric representation of the matroid. + + INPUT: + + - ``B`` -- (optional) a list containing elements of the groundset not in any particular order. + If internal point placement is used, these elements will be placed as vertices of a triangle. + - ``lineorders`` -- (optional) A list of lists where each of the inner lists + specify ground set elements in a certain order which will be used to draw the + corresponding line in geometric representation (if it exists). + - ``pos_method`` -- An integer specifying positioning method + - ``0``: default positioning + - ``1``: use pos_dict if it is not ``None`` + - ``2``: Force directed (Not yet implemented). + + - ``pos_dict`` -- A dictionary mapping ground set elements to their (x,y) positions. + - ``save_pos`` -- A boolean indicating that point placements (either internal or user provided) and + line orders (if provided) will be cached in the matroid (``M._cached_info``) and can be used for + reproducing the geometric representation during the same session + - ``lims`` -- A list of 4 elements ``[xmin,xmax,ymin,ymax]`` + + EXAMPLES:: + + sage: M=matroids.named_matroids.TernaryDowling3() + sage: M.show(B=['a','b','c']) + sage: M.show(B=['a','b','c'],lineorders=[['f','e','i']]) + sage: pos = {'a':(0,0), 'b': (0,1), 'c':(1,0), 'd':(1,1), 'e':(1,-1), 'f':(-1,1), 'g':(-1,-1),'h':(2,0), 'i':(0,2)} + sage: M.show(pos_method=1, pos_dict=pos,lims=[-3,3,-3,3]) + """ + if self.rank() > 3: + raise NotImplementedError + elif B == None: + B = list(self.basis()) + elif B != None and self.is_basis(B)==False: + return + B1=B + lineorders1=lineorders + pm=pos_method + pd=pos_dict + sp=save_pos + G=self.plot(B1,lineorders1,pm,pd,sp) + if lims == None: + G.show() + else: + G.show(xmin=lims[0], xmax=lims[1], ymin=lims[2], ymax=lims[3]) + return + + cpdef _fix_positions(self,pos_dict=None,lineorders=None): + """ + Cache point positions and line orders without actually plotting + + INPUT: + + - ``pos_dict`` -- (optional) A dictionary mapping ground set elements to their (x,y) positions. + - ``lineorders`` -- (optional) A list of lists where each of the inner lists + specify ground set elements in a certain order which will be used to draw the + corresponding line in geometric representation (if it exists). + + EXAMPLES:: + + sage: M=matroids.named_matroids.BetsyRoss() + sage: pos={} + sage: s="abcde" + sage: t="fghij" + sage: x=1.61 + sage: y=1/1.61 + sage: for i in range(5): + ....: pos[s[i]]=(RR(x*sin(2*pi*i/5)), RR(x*cos(2*pi*i/5))) + ....: pos[t[i]]=(RR(y*sin(2*pi*(i+1/2)/5)), RR(y*cos(2*pi*(i+1/2)/5))) + ....: + sage: pos['k']=(0,0) + sage: M._fix_positions(pos_dict=pos) + sage: M._cached_info['lineorders'] is None + True + sage: M._cached_info['plot_positions']['k'] + (0, 0) + """ + if self.rank() > 3: + raise NotImplementedError + # check sanity of pos_dict and add it to cached info if sane + if(pos_dict!=None): + import matroids_plot_helpers + if matroids_plot_helpers.posdict_is_sane(self,pos_dict) ==True: + self._cached_info={'plot_positions':pos_dict,'lineorders':lineorders} + return + diff --git a/src/sage/matroids/matroids_plot_helpers.py b/src/sage/matroids/matroids_plot_helpers.py new file mode 100644 index 00000000000..012af2ab410 --- /dev/null +++ b/src/sage/matroids/matroids_plot_helpers.py @@ -0,0 +1,911 @@ +r""" +Helper functions for plotting the geometric representation of matroids + + +AUTHORS: + +- Jayant Apte (2014-06-06): initial version + + .. NOTE:: + + This file provides functions that are called by ``show()`` and ``plot()`` + methods of abstract matroids class. The basic idea is to first decide + the placement of points in $\mathbb{R}^2$ and then draw lines in geometric + representation through these points. Point placement procedures such as + ``addtripts``, ``addnontripts`` together produce ``(x,y)`` tuples + corresponding to ground set of the matroid in a dictionary. + These methods provide simple but rigid point placement algorithm. + Alternatively, one can build the point placement dictionary manually or + via an optimization that gives aesthetically pleasing point placement (in + some sense. This is not yet implemented). One can then use + ``createline`` function to produce sequence of ``100`` points on a smooth + curve containing the points in the specified line which inturn uses + ``scipy.interpolate.splprep`` and ``scipy.interpolate.splev``. Then one + can use sage's graphics primitives ``line``,``point``, ``text`` and + ``points`` to produce graphics object containg points (ground set + elements) and lines (for a rank 3 matroid, these are flats of rank 2 of + size greater than equal to 3) of the geometric representation of the + matroid. Loops and parallel elements are added as per conventions in + [Oxley] using function ``addlp``. The priority order for point placement + methods used inside plot() and show() is as follows: + 1) User Specified points dictionary and lineorders + 2) cached point placement dictionary and line orders (a list of ordered + lists) in M._cached_info (a dictionary) + 3) Internal point placement and orders deciding heuristics + If a custom point placement and/or line orders is desired, then user can + simply specify the custom points dictionary as ``M.cached info = + {'plot_positions':, + 'plot_lineorders':}`` + + + +REFERENCES +========== + +.. [Oxley] James Oxley, "Matroid Theory, Second Edition". Oxford University + Press, 2011. + +EXAMPLES:: + + sage: from sage.matroids import matroids_plot_helpers + sage: M1=Matroid(ring=GF(2), matrix=[[1, 0, 0, 0, 1, 1, 1,0,1,0,1], + ....: [0, 1, 0, 1, 0, 1, 1,0,0,1,0], [0, 0, 1, 1, 1, 0, 1,0,0,0,0]]) + sage: pos_dict= {0: (0, 0), 1: (2, 0), 2: (1, 2), 3: (1.5, 1.0), + ....: 4: (0.5, 1.0), 5: (1.0, 0.0), 6: (1.0, 0.6666666666666666), + ....: 7: (3,3), 8: (4,0), 9: (-1,1), 10: (-2,-2)} + sage: M1._cached_info={'plot_positions': pos_dict, 'plot_lineorders': None} + sage: matroids_plot_helpers.geomrep(M1, sp=True) + +""" +# ***************************************************************************** +# Copyright (C) 2013 Jayant Apte +# +# 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 3 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + + +import scipy +import scipy.interpolate +import numpy as np +from sage.plot.all import Graphics, line, text, polygon2d, point, points +from sage.plot.colors import Color +from sage.sets.set import Set +from sage.matroids.advanced import newlabel + + +def it(M, B1, nB1, lps): + """ + Return points on and off the triangle and lines to be drawn for a rank 3 + matroid. + + INPUT: + + - ``M`` -- A matroid. + - ``B1``-- A list of groundset elements of ``M`` that corresponds to a + basis of matroid ``M``. + - ``nB1``-- A list of elements in the ground set of M that corresponds to + ``M.simplify.groundset() \ B1``. + - ``lps``-- A list of elements in the ground set of matroid M that are + loops. + + OUTPUT: + + A tuple containing 4 elements in this order: + + 1. A dictionary containing 2-tuple (x,y) co-ordinates with + ``M.simplify.groundset()`` elements that can be placed on the sides of + the triangle as keys. + 2. A list of 3 lists of elements of ``M.simplify.groundset()`` that can + be placed on the 3 sides of the triangle. + 3. A list of elements of `M.simplify.groundset()`` that cane be placed + inside the triangle in the geometric representation. + 4. A list of lists of elements of ``M.simplify.groundset()`` that + correspond to lines in the geometric representation other than the sides + of the triangle. + + EXAMPLES:: + + sage: from sage.matroids import matroids_plot_helpers as mph + sage: M=Matroid(ring=GF(2), matrix=[[1, 0, 0, 0, 1, 1, 1,0], + ....: [0, 1, 0, 1, 0, 1, 1,0],[0, 0, 1, 1, 1, 0, 1,0]]) + sage: N=M.simplify() + sage: B1=list(N.basis()) + sage: nB1=list(set(M.simplify().groundset())-set(B1)) + sage: pts,trilines,nontripts,curvedlines=mph.it(M, + ....: B1,nB1,M.loops()) + sage: print pts + {1: (1.0, 0.0), 2: (1.5, 1.0), 3: (0.5, 1.0), 4: (0, 0), 5: (1, 2), + 6: (2, 0)} + sage: print trilines + [[3, 4, 5], [2, 5, 6], [1, 4, 6]] + sage: print nontripts + [0] + sage: print curvedlines + [[0, 1, 5], [0, 2, 4], [0, 3, 6], [1, 2, 3], [1, 4, 6], [2, 5, 6], + [3, 4, 5]] + + .. NOTE:: + + This method does NOT do any checks. + + """ + + tripts = [(0, 0), (1, 2), (2, 0)] + pts = {} + j = 0 + for i in B1: + pts[i] = tripts[j] + j = j + 1 + pairs = [[0, 1], [1, 2], [0, 2]] + L1 = [] + L2 = [] + L3 = [] + for i in nB1: + if M.is_dependent([i, B1[pairs[0][0]], B1[pairs[0][1]]]): + # Add to L1 + L1.append(i) + elif M.is_dependent([i, B1[pairs[1][0]], B1[pairs[1][1]]]): + # Add to L2 + L2.append(i) + elif M.is_dependent([i, B1[pairs[2][0]], B1[pairs[2][1]]]): + # Add to L3 + L3.append(i) + L = [L1, L2, L3] # megalist + lines = [] # the list of lines + for i in range(1, len(L)+1): + lines.append([B1[pairs[i-1][0]]]) + lines[i-1].extend(L[i-1]) + lines[i-1].extend([B1[pairs[i-1][1]]]) + # place triangle and L1,L2,L3 + for i in L: # loop over megalist + interval = 1/float(len(i)+1) + pt1 = list(tripts[pairs[L.index(i)][0]]) + pt2 = list(tripts[pairs[L.index(i)][1]]) + for j in range(1, len(i)+1): + # loop over L1,L2,L3 + cc = interval*j + pts[i[j-1]] = (cc*pt1[0]+(1-cc)*pt2[0], cc*pt1[1]+(1-cc)*pt2[1]) + trilines = [list(set(x)) for x in lines if len(x) >= 3] + curvedlines = [list(set(list(x)).difference(set(lps))) + for x in M.flats(2) if set(list(x)) not in trilines if + len(list(x)) >= 3] + nontripts = [i for i in nB1 if i not in pts.keys()] + return pts, trilines, nontripts, curvedlines + + +def trigrid(tripts): + """ + Return a grid of 4 points inside given 3 points as a list. + + INPUT: + + - ``tripts`` -- A list of 3 lists of the form [x,y] where x and y are the + cartesian co-ordinates of a point. + + OUTPUT: + + A list of lists containing 4 points in following order: + + - 1. Barycenter of 3 input points. + - 2,3,4. Barycenters of 1. with 3 different 2-subsets of input points + respectively. + + EXAMPLES:: + + sage: from sage.matroids import matroids_plot_helpers + sage: points=matroids_plot_helpers.trigrid([[2,1],[4,5],[5,2]]) + sage: print points + [[3.6666666666666665, 2.6666666666666665], + [3.222222222222222, 2.888888888888889], + [4.222222222222222, 3.222222222222222], + [3.5555555555555554, 1.8888888888888886]] + + .. NOTE:: + + This method does NOT do any checks. + + """ + n = 0 + pairs = [[0, 1], [1, 2], [0, 2]] + cpt = list((float(tripts[0][0]+tripts[1][0]+tripts[2][0])/3, + float(tripts[0][1]+tripts[1][1]+tripts[2][1])/3)) + grid = [cpt] + for p in pairs: + pt = list((float(tripts[p[0]][0]+tripts[p[1]][0]+cpt[0])/3, + float(tripts[p[0]][1]+tripts[p[1]][1]+cpt[1])/3)) + grid.append(pt) + return grid + + +def addnontripts(tripts_labels, nontripts_labels, ptsdict): + """ + Return modified ``ptsdict`` with additional keys and values corresponding + to ``nontripts``. + + INPUT: + + - ``tripts`` -- A list of 3 ground set elements that are to be placed on + vertices of the triangle. + - ``ptsdict`` -- A dictionary (at least) containing ground set elements in + ``tripts`` as keys and their (x,y) position as values. + - ``nontripts``-- A list of ground set elements whose corresponding points + are to be placed inside the triangle. + + OUTPUT: + + A dictionary containing ground set elements in ``tripts`` as keys and + their (x,y) position as values allong with all keys and respective values + in ``ptsdict``. + + EXAMPLES:: + + sage: from sage.matroids import matroids_plot_helpers + sage: from sage.matroids.advanced import setprint + sage: ptsdict={'a':(0,0),'b':(1,2),'c':(2,0)} + sage: ptsdict_1=matroids_plot_helpers.addnontripts(['a','b','c'], + ....: ['d','e','f'],ptsdict) + sage: setprint(ptsdict_1) + {'a': [0, 0], 'b': [1, 2], 'c': [0, 2], 'd': [0.6666666666666666, 1.0], + 'e': [0.6666666666666666, 0.8888888888888888], + 'f': [0.8888888888888888, 1.3333333333333333]} + sage: ptsdict_2=matroids_plot_helpers.addnontripts(['a','b','c'], + ....: ['d','e','f','g','h'],ptsdict) + sage: setprint(ptsdict_2) + {'a': [0, 0], 'b': [1, 2], 'c': [0, 2], 'd': [0.6666666666666666, 1.0], + 'e': [0.6666666666666666, 0.8888888888888888], + 'f': [0.8888888888888888, 1.3333333333333333], + 'g': [0.2222222222222222, 1.0], + 'h': [0.5185185185185185, 0.5555555555555555]} + + .. NOTE:: + + This method does NOT do any checks. + + """ + tripts = [list(ptsdict[p]) for p in tripts_labels] + pairs = [[0, 1], [1, 2], [0, 2]] + q = [tripts] + num = len(nontripts_labels) + gridpts = [[float((tripts[0][0]+tripts[1][0]+tripts[2][0])/3), + float(tripts[0][1]+tripts[1][1]+tripts[2][1])/3]] + n = 0 + while n < num+1: + g = trigrid(q[0]) + q.extend([[g[0], q[0][pairs[0][0]], q[0][pairs[0][1]]], + [g[0], q[0][pairs[1][0]], q[0][pairs[1][1]]], + [g[0], q[0][pairs[2][0]], q[0][pairs[2][1]]]]) + q.remove(q[0]) + gridpts.extend(g[1:]) + if n == 0: + n = n + 4 + else: + n = n + 3 + j = 0 + for p in nontripts_labels: + ptsdict[p] = tuple(gridpts[j]) + j = j + 1 + return ptsdict + + +def createline(ptsdict, ll, lineorders2=None): + """ + Return ordered lists of co-ordinates of points to be traversed to draw a + 2D line. + + INPUT: + + - ``ptsdict`` -- A dictionary containing keys and their (x,y) position as + values. + - ``ll`` -- A list of keys in ``ptsdict`` through which a line is to be + drawn. + - ``lineorders2``-- (optional) A list of ordered lists of keys in + ``ptsdict`` such that if ll is setwise same as any of these then points + corresponding to values of the keys will be traversed in that order thus + overriding internal order deciding heuristic. + + OUTPUT: + + A tuple containing 4 elements in this order: + + 1. Ordered list of x-coordinates of values of keys in ``ll`` specified in + ptsdict. + 2. Ordered list of y-coordinates of values of keys in ``ll`` specified + in ptsdict. + 3. Ordered list of interpolated x-coordinates of points through which a + line can be drawn. + 4. Ordered list of interpolated y-coordinates of points through which a + line can be drawn. + + EXAMPLES:: + + sage: from sage.matroids import matroids_plot_helpers + sage: ptsdict={'a':(1,3),'b':(2,1),'c':(4,5),'d':(5,2)} + sage: x,y,x_i,y_i=matroids_plot_helpers.createline(ptsdict, + ....: ['a','b','c','d']) + sage: print [len(x),len(y),len(x_i),len(y_i)] + [4, 4, 100, 100] + sage: G = line(zip(x_i, y_i),color='black',thickness=3,zorder=1) + sage: G+=points(zip(x, y), color='black', size=300,zorder=2) + sage: G.show() + sage: x,y,x_i,y_i=matroids_plot_helpers.createline(ptsdict, + ....: ['a','b','c','d'],lineorders2=[['b','a','c','d'], + ....: ['p','q','r','s']]) + sage: print [len(x),len(y),len(x_i),len(y_i)] + [4, 4, 100, 100] + sage: G = line(zip(x_i, y_i),color='black',thickness=3,zorder=1) + sage: G+=points(zip(x, y), color='black', size=300,zorder=2) + sage: G.show() + + .. NOTE:: + + This method does NOT do any checks. + + """ + x, lo = line_hasorder(ll, lineorders2) + flip = False + if x is False: + # convert dictionary to list of lists + linepts = [list(ptsdict[i]) for i in ll] + xpts = [x[0] for x in linepts] + ypts = [y[1] for y in linepts] + xdim = (float(max(xpts))-float(min(xpts))) + ydim = (float(max(ypts))-float(min(ypts))) + if xdim > ydim: + sortedind = sorted(range(len(xpts)), key=lambda k: float(xpts[k])) + else: + sortedind = sorted(range(len(ypts)), key=lambda k: float(ypts[k])) + flip = True + sortedlinepts = [linepts[i] for i in sortedind] + sortedx = [k[0] for k in sortedlinepts] + sortedy = [k[1] for k in sortedlinepts] + else: + linepts = [list(ptsdict[i]) for i in lo] + sortedx = [k[0] for k in linepts] + sortedy = [k[1] for k in linepts] + + if flip is True: + tck, u = scipy.interpolate.splprep([sortedy, sortedx], s=0.0, k=2) + y_i, x_i = scipy.interpolate.splev(np.linspace(0, 1, 100), tck) + else: + tck, u = scipy.interpolate.splprep([sortedx, sortedy], s=0.0, k=2) + x_i, y_i = scipy.interpolate.splev(np.linspace(0, 1, 100), tck) + return sortedx, sortedy, x_i, y_i + + +def slp(M1, pos_dict=None, B=None): + """ + Return simple matroid, loops and parallel elements of given matroid. + + INPUT: + + - ``M1`` -- A matroid. + - ``pos_dict`` -- (optional) A dictionary containing non loopy elements of + ``M`` as keys and their (x,y) positions. + as keys. While simplifying the matroid, all except one element in a + parallel class that is also specified in ``pos_dict`` will be retained. + - ``B`` -- (optional) A basis of M1 that has been chosen for placement on + vertics of triangle. + + OUTPUT: + + A tuple containing 3 elements in this order: + + 1. Simple matroid corresponding to ``M1``. + 2. Loops of matroid ``M1``. + 3. Elements that are in `M1.groundset()` but not in ground set of 1 or + in 2 + + EXAMPLES:: + + sage: from sage.matroids import matroids_plot_helpers + sage: from sage.matroids.advanced import setprint + sage: M1=Matroid(ring=GF(2), matrix=[[1, 0, 0, 0, 1, 1, 1,0,1,0,1], + ....: [0, 1, 0, 1, 0, 1, 1,0,0,1,0],[0, 0, 1, 1, 1, 0, 1,0,0,0,0]]) + sage: [M,L,P]=matroids_plot_helpers.slp(M1) + sage: M.is_simple() + True + sage: setprint([L,P]) + [{7}, {8, 9, 10}] + sage: M1=Matroid(ring=GF(2), matrix=[[1, 0, 0, 0, 1, 1, 1,0,1,0,1], + ....: [0, 1, 0, 1, 0, 1, 1,0,0,1,0],[0, 0, 1, 1, 1, 0, 1,0,0,0,0]]) + sage: posdict= {8: (0, 0), 1: (2, 0), 2: (1, 2), 3: (1.5, 1.0), + ....: 4: (0.5, 1.0), 5: (1.0, 0.0), 6: (1.0, 0.6666666666666666)} + sage: [M,L,P]=matroids_plot_helpers.slp(M1,pos_dict=posdict) + sage: M.is_simple() + True + sage: setprint([L,P]) + [{7}, {0, 9, 10}] + + .. NOTE:: + + This method does NOT do any checks. + + """ + L = set(M1.loops()) + sg = sorted(M1.simplify().groundset()) + nP = L | set(M1.simplify().groundset()) + P = set(M1.groundset())-nP + if len(P) > 0: + if pos_dict is not None: + pcls = list(set([frozenset(set(M1.closure([p])) - L) + for p in list(P)])) + newP = [] + for pcl in pcls: + pcl_in_dict = [p for p in list(pcl) if p in pos_dict.keys()] + newP.extend(list(pcl-set([pcl_in_dict[0]]))) + return [M1.delete(L | set(newP)), L, set(newP)] + elif B is not None: + pcls = list(set([frozenset(set(M1.closure([p])) - L) + for p in list(P)])) + newP = [] + for pcl in pcls: + pcl_list = list(pcl) + pcl_in_basis = [p for p in pcl_list if p in B] + if len(pcl_in_basis) > 0: + newP.extend(list(pcl - set([pcl_in_basis[0]]))) + else: + newP.extend(list(pcl - set([pcl_list[0]]))) + return [M1.delete(L | set(newP)), L, set(newP)] + else: + return [M1.delete(L | P), L, P] + else: + return [M1.delete(L | P), L, P] + + +def addlp(M, M1, L, P, ptsdict, G=None, limits=None): + """ + Return a graphics object containing loops (in inset) and parallel elements + of matroid. + + INPUT: + + - ``M`` -- A matroid. + - ``M1`` -- A simple matroid corresponding to ``M``. + - ``L`` -- List of elements in ``M.groundset()`` that are loops of matroid + ``M``. + - ``P`` -- List of elements in ``M.groundset()`` not in + ``M.simplify.groundset()`` or ``L``. + - ``ptsdict`` -- A dictionary containing elements in ``M.groundset()`` not + necessarily containing elements of ``L``. + - ``G`` -- (optional) A sage graphics object to which loops and parallel + elements of matroid `M` added . + - ``limits``-- (optional) Current axes limits [xmin,xmax,ymin,ymax]. + + OUTPUT: + + A 2-tuple containing: + + 1. A sage graphics object containing loops and parallel elements of + matroid ``M`` + 2. axes limits array + + EXAMPLES:: + + sage: from sage.matroids import matroids_plot_helpers + sage: M=Matroid(ring=GF(2), matrix=[[1, 0, 0, 0, 1, 1, 1,0,1], + ....: [0, 1, 0, 1, 0, 1, 1,0,0],[0, 0, 1, 1, 1, 0, 1,0,0]]) + sage: [M1,L,P]=matroids_plot_helpers.slp(M) + sage: G,lims=matroids_plot_helpers.addlp(M,M1,L,P,{0:(0,0)}) + sage: G.show(axes=False) + + .. NOTE:: + + This method does NOT do any checks. + + """ + if G is None: + G = Graphics() + # deal with loops + if len(L) > 0: + loops = L + looptext = ", ".join([str(l) for l in loops]) + if(limits is None): + rectx = -1 + recty = -1 + else: + rectx = limits[0] + recty = limits[2]-1 + rectw = 0.5 + 0.4*len(loops) + 0.5 # controlled based on len(loops) + recth = 0.6 + G += polygon2d([[rectx, recty], [rectx, recty+recth], + [rectx+rectw, recty+recth], [rectx+rectw, recty]], + color='black', fill=False, thickness=4) + G += text(looptext, (rectx+0.5, recty+0.3), color='black', + fontsize=13) + G += point((rectx+0.2, recty+0.3), color=Color('#BDBDBD'), size=300, + zorder=2) + G += text('Loop(s)', (rectx+0.5+0.4*len(loops)+0.1, recty+0.3), + fontsize=13, color='black') + limits = tracklims(limits, [rectx, rectx+rectw], [recty, recty+recth]) + # deal with parallel elements + if len(P) > 0: + # create list of lists where inner lists are parallel classes + pcls = [] + gnd = sorted(list(M1.groundset())) + for g in gnd: + pcl = [g] + for p in P: + if M.rank([g, p]) == 1: + pcl.extend([p]) + pcls.append(pcl) + ext_gnd = list(M.groundset()) + for pcl in pcls: + if len(pcl) > 1: + basept = list(ptsdict[pcl[0]]) + if len(pcl) <= 2: + # add side by side + ptsdict[pcl[1]] = (basept[0], basept[1]-0.13) + G += points(zip([basept[0]], [basept[1]-0.13]), + color=Color('#BDBDBD'), size=300, zorder=2) + G += text(pcl[0], (float(basept[0]), + float(basept[1])), color='black', + fontsize=13) + G += text(pcl[1], (float(basept[0]), + float(basept[1])-0.13), color='black', + fontsize=13) + limits = tracklims(limits, [basept[0]], [basept[1]-0.13]) + else: + # add in a bracket + pce = sorted([str(kk) for kk in pcl]) + l = newlabel(set(ext_gnd)) + ext_gnd.append(l) + G += text(l+'={ '+", ".join(pce)+' }', (float(basept[0]), + float(basept[1]-0.2)-0.034), color='black', + fontsize=13) + G += text(l, (float(basept[0]), + float(basept[1])), color='black', + fontsize=13) + limits = tracklims(limits, [basept[0]], + [(basept[1]-0.2)-0.034]) + return G, limits + + +def line_hasorder(l, lodrs=None): + """ + Determine if an order is specified for a line + + INPUT: + + - ``l`` -- A line specified as a list of ground set elements. + - ``lordrs`` -- (optional) A list of lists each specifying an order on + a subset of ground set elements that may or may not correspond to a + line in geometric representation. + + OUTPUT: + + A tuple containing 2 elements in this order: + + 1. A boolean indicating whether there is any list in ``lordrs`` that is + setwise equal to ``l``. + 2. A list specifying an order on ``set(l)`` if 1. is True, otherwise + an empty list. + + EXAMPLES:: + + sage: from sage.matroids import matroids_plot_helpers + sage: matroids_plot_helpers.line_hasorder(['a','b','c','d'], + ....: [['a','c','d','b'],['p','q','r']]) + (True, ['a', 'c', 'd', 'b']) + sage: matroids_plot_helpers.line_hasorder(['a','b','c','d'], + ....: [['p','q','r'],['l','m','n','o']]) + (False, []) + + .. NOTE:: + + This method does NOT do any checks. + """ + if lodrs is not None: + if len(lodrs) > 0: + for i in lodrs: + if Set(i) == Set(l): + return True, i + return False, [] + + +def lineorders_union(lineorders1, lineorders2): + """ + Return a list of ordered lists of ground set elements that corresponds to + union of two sets of ordered lists of ground set elements in a sense. + + INPUT: + + - ``lineorders1`` -- A list of ordered lists specifying orders on subsets + of ground set. + - ``lineorders2`` -- A list of ordered lists specifying orders subsets of + ground set. + + OUTPUT: + + A list of ordered lists of ground set elements that are (setwise) in only + one of ``lineorders1`` or ``lineorders2`` along with the ones in + lineorder2 that are setwise equal to any list in lineorders1. + + EXAMPLES:: + + sage: from sage.matroids import matroids_plot_helpers + sage: matroids_plot_helpers.lineorders_union([['a','b','c'], + ....: ['p','q','r'],['i','j','k','l']],[['r','p','q']]) + [['a', 'b', 'c'], ['p', 'q', 'r'], ['i', 'j', 'k', 'l']] + + """ + if lineorders1 is not None and lineorders2 is not None: + lineorders = lineorders1 + for order in lineorders2: + x, lo = line_hasorder(order, lineorders1) + if x is False: + lineorders.append(order) + lineorders.remove(lo) + return lineorders + elif lineorders1 is None and lineorders2 is not None: + return lineorders2 + elif lineorders1 is not None: + return lineorders1 + else: + return None + + +def posdict_is_sane(M1, pos_dict): + """ + Return a boolean establishing sanity of ``posdict`` wrt matroid ``M``. + + INPUT: + + - ``M1`` -- A matroid. + - ``posdict`` -- A dictionary mapping ground set elements to (x,y) + positions. + + OUTPUT: + + A boolean that is ``True`` if posdict indeed has all the required elements + to plot the geometric elements, otherwise ``False``. + + EXAMPLES:: + + sage: from sage.matroids import matroids_plot_helpers + sage: M1=Matroid(ring=GF(2), matrix=[[1, 0, 0, 0, 1, 1, 1,0,1,0,1], + ....: [0, 1, 0, 1, 0, 1, 1,0,0,1,0],[0, 0, 1, 1, 1, 0, 1,0,0,0,0]]) + sage: pos_dict= {0: (0, 0), 1: (2, 0), 2: (1, 2), 3: (1.5, 1.0), + ....: 4: (0.5, 1.0), 5: (1.0, 0.0), 6: (1.0, 0.6666666666666666)} + sage: matroids_plot_helpers.posdict_is_sane(M1,pos_dict) + True + sage: pos_dict= {1: (2, 0), 2: (1, 2), 3: (1.5, 1.0), + ....: 4: (0.5, 1.0), 5: (1.0, 0.0), 6: (1.0, 0.6666666666666666)} + sage: matroids_plot_helpers.posdict_is_sane(M1,pos_dict) + False + + .. NOTE:: + + This method does NOT do any checks. ``M1`` is assumed to be a + matroid and ``posdict`` is assumed to be a dictionary. + """ + L = set(M1.loops()) + sg = sorted(M1.simplify().groundset()) + nP = L | set(M1.simplify().groundset()) + P = set(M1.groundset())-nP + pcls = list(set([frozenset(set(M1.closure([p])) - L) for p in list(P)])) + for pcl in pcls: + pcl_list = list(pcl) + if not any([x in pos_dict.keys() for x in pcl_list]): + return False + allP = [] + for pcl in pcls: + allP.extend(list(pcl)) + if not all([x in pos_dict.keys() + for x in list(set(M1.groundset()) - (L | set(allP)))]): + return False + return True + + +def tracklims(lims, x_i=[], y_i=[]): + """ + Return modified limits list. + + INPUT: + + - ``lims`` -- A list with 4 elements ``[xmin,xmax,ymin,ymax]`` + - ``x_i`` -- New x values to track + - ``y_i`` -- New y values to track + + OUTPUT: + + A list with 4 elements ``[xmin,xmax,ymin,ymax]`` + + EXAMPLES:: + + sage: from sage.matroids import matroids_plot_helpers + sage: matroids_plot_helpers.tracklims([0,5,-1,7],[1,2,3,6,-1], + ....: [-1,2,3,6]) + [-1, 6, -1, 7] + + .. NOTE:: + + This method does NOT do any checks. + """ + if lims is not None and lims[0] is not None and lims[1] is not None and \ + lims[2] is not None and lims[3] is not None: + lims = [min(min(x_i), lims[0]), max(max(x_i), lims[1]), + min(min(y_i), lims[2]), max(max(y_i), lims[3])] + else: + lims = [min(x_i), max(x_i), min(y_i), max(y_i)] + return lims + + +def geomrep(M1, B1=None, lineorders1=None, pd=None, sp=False): + """ + Return a sage graphics object containing geometric representation of + matroid M1. + + INPUT: + + - ``M1`` -- A matroid. + - ``B1`` -- (optional) A list of elements in ``M1.groundset()`` that + correspond to a basis of ``M1`` and will be placed as vertices of the + triangle in the geometric representation of ``M1``. + - ``lineorders1`` -- (optional) A list of ordered lists of elements of + ``M1.grondset()`` such that if a line in geometric representation is + setwise same as any of these then points contained will be traversed in + that order thus overriding internal order deciding heuristic. + - ``pd`` - (optional) A dictionary mapping ground set elements to their + (x,y) positions. + - ``sp`` -- (optional) If True, a positioning dictionary and line orders + will be placed in ``M._cached_info``. + + OUTPUT: + + A sage graphics object of type that + corresponds to the geometric representation of the matroid. + + EXAMPLES:: + + sage: from sage.matroids import matroids_plot_helpers + sage: M=matroids.named_matroids.P7() + sage: G=matroids_plot_helpers.geomrep(M) + sage: G.show(xmin=-2, xmax=3, ymin=-2, ymax=3) + sage: M=matroids.named_matroids.P7() + sage: G=matroids_plot_helpers.geomrep(M,lineorders1=[['f','e','d']]) + sage: G.show(xmin=-2, xmax=3, ymin=-2, ymax=3) + + .. NOTE:: + + This method does NOT do any checks. + """ + G = Graphics() + # create lists of loops and parallel elements and simplify given matroid + [M, L, P] = slp(M1, pos_dict=pd, B=B1) + if B1 is None: + B1 = list(M.basis()) + M._cached_info = M1._cached_info + + if M.rank() == 0: + limits = None + loops = L + looptext = ", ".join([str(l) for l in loops]) + rectx = -1 + recty = -1 + rectw = 0.5 + 0.4*len(loops) + 0.5 # controlled based on len(loops) + recth = 0.6 + G += polygon2d([[rectx, recty], [rectx, recty+recth], + [rectx+rectw, recty+recth], [rectx+rectw, recty]], + color='black', fill=False, thickness=4) + G += text(looptext, (rectx+0.5, recty+0.3), color='black', + fontsize=13) + G += point((rectx+0.2, recty+0.3), color=Color('#BDBDBD'), size=300, + zorder=2) + G += text('Loop(s)', (rectx+0.5+0.4*len(loops)+0.1, recty+0.3), + fontsize=13, color='black') + limits = tracklims(limits, [rectx, rectx+rectw], [recty, recty+recth]) + G.axes(False) + G.axes_range(xmin=limits[0]-0.5, xmax=limits[1]+0.5, + ymin=limits[2]-0.5, ymax=limits[3]+0.5) + return G + elif M.rank() == 1: + if M._cached_info is not None and \ + 'plot_positions' in M._cached_info.keys() and \ + M._cached_info['plot_positions'] is not None: + pts = M._cached_info['plot_positions'] + else: + pts = {} + gnd = sorted(M.groundset()) + pts[gnd[0]] = (1, float(2)/3) + G += point((1, float(2)/3), size=300, color=Color('#BDBDBD'), zorder=2) + pt = [1, float(2)/3] + if len(P) == 0: + G += text(gnd[0], (float(pt[0]), float(pt[1])), color='black', + fontsize=13) + pts2 = pts + # track limits [xmin,xmax,ymin,ymax] + pl = [list(x) for x in pts2.values()] + lims = tracklims([None, None, None, None], [pt[0] for pt in pl], + [pt[1] for pt in pl]) + elif M.rank() == 2: + nB1 = list(set(list(M.groundset())) - set(B1)) + bline = [] + for j in nB1: + if M.is_dependent([j, B1[0], B1[1]]): + bline.append(j) + interval = len(bline)+1 + if M._cached_info is not None and \ + 'plot_positions' in M._cached_info.keys() and \ + M._cached_info['plot_positions'] is not None: + pts2 = M._cached_info['plot_positions'] + else: + pts2 = {} + pts2[B1[0]] = (0, 0) + pts2[B1[1]] = (2, 0) + lpt = list(pts2[B1[0]]) + rpt = list(pts2[B1[1]]) + for k in range(len(bline)): + cc = (float(1)/interval)*(k+1) + pts2[bline[k]] = (cc*lpt[0]+(1-cc)*rpt[0], + cc*lpt[1]+(1-cc)*rpt[1]) + if sp is True: + M._cached_info['plot_positions'] = pts2 + # track limits [xmin,xmax,ymin,ymax] + pl = [list(x) for x in pts2.values()] + lims = tracklims([None, None, None, None], [pt[0] for pt in pl], + [pt[1] for pt in pl]) + bline.extend(B1) + ptsx, ptsy, x_i, y_i = createline(pts2, bline, lineorders1) + lims = tracklims(lims, x_i, y_i) + G += line(zip(x_i, y_i), color='black', thickness=3, zorder=1) + pels = [p for p in pts2.keys() if any([M1.rank([p, q]) == 1 + for q in P])] + allpts = [list(pts2[i]) for i in M.groundset()] + xpts = [float(k[0]) for k in allpts] + ypts = [float(k[1]) for k in allpts] + G += points(zip(xpts, ypts), color=Color('#BDBDBD'), size=300, + zorder=2) + for i in pts2: + if i not in pels: + pt = list(pts2[i]) + G += text(i, (float(pt[0]), float(pt[1])), color='black', + fontsize=13) + else: + if M._cached_info is None or \ + 'plot_positions' not in M._cached_info.keys() or \ + M._cached_info['plot_positions'] is None: + (pts, trilines, + nontripts, curvedlines) = it(M1, B1, + list(set(M.groundset())-set(B1)), + list(set(L) | set(P))) + pts2 = addnontripts([B1[0], B1[1], B1[2]], nontripts, pts) + trilines.extend(curvedlines) + else: + pts2 = M._cached_info['plot_positions'] + trilines = [list(set(list(x)).difference(L | P)) + for x in M1.flats(2) + if len(list(x)) >= 3] + pl = [list(x) for x in pts2.values()] + lims = tracklims([None, None, None, None], [pt[0] for pt in pl], + [pt[1] for pt in pl]) + j = 0 + for ll in trilines: + if len(ll) >= 3: + ptsx, ptsy, x_i, y_i = createline(pts2, ll, lineorders1) + lims = tracklims(lims, x_i, y_i) + G += line(zip(x_i, y_i), color='black', thickness=3, zorder=1) + pels = [p for p in pts2.keys() if any([M1.rank([p, q]) == 1 + for q in P])] + allpts = [list(pts2[i]) for i in M.groundset()] + xpts = [float(k[0]) for k in allpts] + ypts = [float(k[1]) for k in allpts] + G += points(zip(xpts, ypts), color=Color('#BDBDBD'), size=300, + zorder=2) + for i in pts2: + if i not in pels: + pt = list(pts2[i]) + G += text(i, (float(pt[0]), float(pt[1])), color='black', + fontsize=13) + if sp is True: + M1._cached_info['plot_positions'] = pts2 + M1._cached_info['plot_lineorders'] = lineorders1 + # deal with loops and parallel elements + G, lims = addlp(M1, M, L, P, pts2, G, lims) + G.axes(False) + G.axes_range(xmin=lims[0]-0.5, xmax=lims[1]+0.5, ymin=lims[2]-0.5, + ymax=lims[3]+0.5) + return G diff --git a/src/sage/misc/c3_controlled.pyx b/src/sage/misc/c3_controlled.pyx index 7012d7b4879..efdd608046c 100644 --- a/src/sage/misc/c3_controlled.pyx +++ b/src/sage/misc/c3_controlled.pyx @@ -314,9 +314,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() - 64 - sage: x.all_bases_controlled_len() 66 + sage: x.all_bases_controlled_len() + 69 sage: C = GradedHopfAlgebrasWithBasis(QQ) sage: x = HierarchyElement(C, attrcall("super_categories"), attrgetter("_cmp_key")) @@ -336,7 +336,6 @@ for any that requires the addition of some bases:: ....: if len(C._super_categories_for_classes) != len(C.super_categories())], ....: key=str) [Category of affine weyl groups, - Category of commutative rings, Category of fields, Category of finite dimensional algebras with basis over Rational Field, Category of finite dimensional hopf algebras with basis over Rational Field, diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index 898ebe9b4e6..2214a7d4ef7 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -3,14 +3,15 @@ Cached Functions and Methods AUTHORS: -- William Stein (inspired by conversation with Justin Walker). -- Mike Hansen (added doctests and made it work with class methods). -- Willem Jan Palenstijn (add CachedMethodCaller for binding cached - methods to instances). -- Tom Boothby (added DiskCachedFunction). -- Simon King (improved performance, more doctests, cython version, - CachedMethodCallerNoArgs, weak cached function, cached special methods). -- Julian Rueth (2014-03-19): added ``key`` parameter +- William Stein: initial version, (inspired by conversation with Justin Walker) +- Mike Hansen: added doctests and made it work with class methods. +- Willem Jan Palenstijn: add CachedMethodCaller for binding cached methods to + instances. +- Tom Boothby: added DiskCachedFunction. +- Simon King: improved performance, more doctests, cython version, + CachedMethodCallerNoArgs, weak cached function, cached special methods. +- Julian Rueth (2014-03-19, 2014-05-09): added ``key`` parameter, allow caching + for unhashable elements EXAMPLES: @@ -406,6 +407,60 @@ easier method:: sage: timeit("a = Q.f(2,3)") # random 625 loops, best of 3: 931 ns per loop +Some immutable objects (such as `p`-adic numbers) cannot implement a +reasonable hash function because their ``==`` operator has been +modified to return ``True`` for objects which might behave differently +in some computations:: + + sage: K. = Qq(9) + sage: b = a.add_bigoh(1) + sage: c = a + 3 + sage: b + a + O(3) + sage: c + a + 3 + O(3^20) + sage: b == c + True + sage: b == a + True + sage: c == a + False + +If such objects defined a non-trivial hash function, this would break +caching in many places. However, such objects should still be usable +in caches. This can be achieved by defining an appropriate method +``_cache_key``. + + sage: hash(b) + Traceback (most recent call last): + ... + TypeError: unhashable type: 'sage.rings.padics.padic_ZZ_pX_CR_element.pAdicZZpXCRElement' + sage: @cached_method + ....: def f(x): return x==a + sage: f(b) + True + sage: f(c) # if b and c were hashable, this would return True + False + + sage: b._cache_key() + (..., ((0, 1),), 0, 1) + sage: c._cache_key() + (..., ((0, 1), (1,)), 0, 20) + +.. NOTE:: + + This attribute will only be accessed if the object itself + is not hashable. + +An implementation must make sure that for elements ``a`` and ``b``, +if ``a != b``, then also ``a._cache_key() != b._cache_key()``. +In practice this means that the ``_cache_key`` should always include +the parent as its first argument:: + + sage: S. = Qq(4) + sage: d = a.add_bigoh(1) + sage: b._cache_key() == d._cache_key() # this would be True if the parents were not included + False """ ######################################################################## # Copyright (C) 2008 William Stein @@ -465,6 +520,50 @@ def _cached_function_unpickle(module,name): """ return getattr(__import__(module, fromlist=['']),name) +def _cache_key(o): + r""" + Helper function to return a hashable key for ``o`` which can be used for + caching. + + This function is intended for objects which are not hashable such as + `p`-adic numbers. The difference from calling an object's ``_cache_key`` + attribute directly, is that it also works for tuples and unpacks them + recursively (if necessary, i.e., if they are not hashable). + + EXAMPLES:: + + sage: from sage.misc.cachefunc import _cache_key + sage: K. = Qq(9) + sage: a = K(1); a + 1 + O(3^20) + sage: _cache_key(a) + (..., ((1,),), 0, 20) + + This function works if ``o`` is a tuple. In this case it unpacks its + entries recursively:: + + sage: o = (1, 2, (3, a)) + sage: _cache_key(o) + (1, 2, (3, (..., ((1,),), 0, 20))) + + Note that tuples are only partially unpacked if some of its entries are + hashable:: + + sage: o = (1/2, a) + sage: _cache_key(o) + (1/2, (..., ((1,),), 0, 20)) + """ + try: + hash(o) + return o + except TypeError: + if isinstance(o, sage.structure.sage_object.SageObject): + o = o._cache_key() + if isinstance(o,tuple): + return tuple(_cache_key(item) for item in o) + else: + return o + cdef class CachedFunction(object): """ Create a cached version of a function, which only recomputes @@ -487,7 +586,8 @@ cdef class CachedFunction(object): def f(...): .... - The inputs to the function must be hashable. + The inputs to the function must be hashable or they must define + :meth:`sage.structure.sage_object.SageObject._cache_key`. EXAMPLES:: @@ -543,7 +643,8 @@ cdef class CachedFunction(object): def f(...): .... - The inputs to the function must be hashable. + The inputs to the function must be hashable or they must define + :meth:`sage.structure.sage_object.SageObject._cache_key`. TESTS:: @@ -775,6 +876,24 @@ cdef class CachedFunction(object): sage: a is number_of_partitions(10^5) True + Check that :trac:`16316` has been fixed, i.e., caching works for + immutable unhashable objects which define + :meth:`sage.structure.sage_object.SageObject._cache_key`. + + sage: @cached_function + ....: def f(x): return x+x + sage: K. = Qq(4) + sage: x = K(1,1); x + 1 + O(2) + sage: y = K(1,2); y + 1 + O(2^2) + sage: x==y + True + sage: f(x) is f(x) + True + sage: f(y) is not f(x) + True + """ # We shortcut a common case of no arguments if args or kwds: @@ -790,7 +909,11 @@ cdef class CachedFunction(object): k = self._default_key = self._fix_to_pos() try: - return (self.cache)[k] + try: + return (self.cache)[k] + except TypeError: # k is not hashable + k = (_cache_key,_cache_key(k)) + return (self.cache)[k] except KeyError: w = self.f(*args, **kwds) self.cache[k] = w @@ -828,10 +951,34 @@ cdef class CachedFunction(object): 6 sage: a.f.is_in_cache(3,y=0) True + + TESTS: + + Check that :trac:`16316` has been fixed, i.e., caching works for + immutable unhashable objects which define + :meth:`sage.structure.sage_object.SageObject._cache_key`. + + sage: @cached_function + ....: def f(x): return x + sage: K. = Qq(4) + sage: x = K(1,1); x + 1 + O(2) + sage: f.is_in_cache(x) + False + sage: f(x) + 1 + O(2) + sage: f.is_in_cache(x) + True + """ if self._argument_fixer is None: self.argfix_init() - return self._fix_to_pos(*args, **kwds) in (self.cache) + k = self._fix_to_pos(*args, **kwds) + try: + return k in (self.cache) + except TypeError: # k is not hashable + k = (_cache_key,_cache_key(k)) + return k in self.cache def set_cache(self, value, *args, **kwds): """ @@ -851,6 +998,21 @@ cdef class CachedFunction(object): sage: g(5) 17 + TESTS: + + Check that :trac:`16316` has been fixed, i.e., caching works for + immutable unhashable objects which define + :meth:`sage.structure.sage_object.SageObject._cache_key`. + + sage: @cached_function + ....: def f(x): return x + sage: K. = Qq(4) + sage: x = K(1,1); x + 1 + O(2) + sage: f.set_cache(x,x) + sage: f.is_in_cache(x) + True + DEVELOPER NOTE: Is there a way to use the following intuitive syntax? @@ -863,7 +1025,15 @@ cdef class CachedFunction(object): """ if self._argument_fixer is None: self.argfix_init() - (self.cache)[self._fix_to_pos(*args, **kwds)] = value + k = self._fix_to_pos(*args, **kwds) + try: + (self.cache)[k] = value + except TypeError: # k is not hashable + k = (_cache_key, _cache_key(k)) + # to make sure that this key does not get confused with the key of + # a hashable object, such keys include _cache_key which is + # certainly not stored in the dictionary otherwise. + (self.cache)[k] = value def get_key(self, *args, **kwds): """ @@ -997,7 +1167,8 @@ cdef class WeakCachedFunction(CachedFunction): """ def __init__(self, f, classmethod=False, name=None, key=None): """ - The inputs to the function must be hashable. + The inputs to the function must be hashable or they must define + :meth:`sage.structure.sage_object.SageObject._cache_key`. The outputs to the function must be weakly referenceable. TESTS:: @@ -1057,6 +1228,26 @@ cdef class WeakCachedFunction(CachedFunction): sage: a = f() doing a computation + Check that :trac:`16316` has been fixed, i.e., caching works for + immutable unhashable objects which define + :meth:`sage.structure.sage_object.SageObject._cache_key`. + + sage: from sage.misc.cachefunc import weak_cached_function + sage: @weak_cached_function + ....: def f(x): return x+x + sage: K. = Qq(4) + sage: R. = K[] + sage: x = t + K(1,1); x + (1 + O(2^20))*t + 1 + O(2) + sage: y = t + K(1,2); y + (1 + O(2^20))*t + 1 + O(2^2) + sage: x==y + True + sage: f(x) is f(x) + True + sage: f(y) is not f(x) + True + """ # We shortcut a common case of no arguments if args or kwds: @@ -1072,7 +1263,11 @@ cdef class WeakCachedFunction(CachedFunction): k = self._default_key = self._fix_to_pos() try: - return self.cache[k] + try: + return self.cache[k] + except TypeError: # k is not hashable + k = (_cache_key,_cache_key(k)) + return self.cache[k] except KeyError: w = self.f(*args, **kwds) self.cache[k] = w @@ -1108,10 +1303,33 @@ cdef class WeakCachedFunction(CachedFunction): sage: f.is_in_cache(5) False + TESTS: + + Check that :trac:`16316` has been fixed, i.e., caching works for + immutable unhashable objects which define + :meth:`sage.structure.sage_object.SageObject._cache_key`. + + sage: from sage.misc.cachefunc import weak_cached_function + sage: @weak_cached_function + ....: def f(x): return x + sage: K. = Qq(4) + sage: R. = K[] + sage: f.is_in_cache(t) + False + sage: f(t) + (1 + O(2^20))*t + sage: f.is_in_cache(t) + True + """ if self._argument_fixer is None: self.argfix_init() - return self._fix_to_pos(*args, **kwds) in self.cache + k = self._fix_to_pos(*args, **kwds) + try: + return k in self.cache + except TypeError: # k is not hashable + k = (_cache_key,_cache_key(k)) + return k in self.cache def set_cache(self, value, *args, **kwds): """ @@ -1133,11 +1351,33 @@ cdef class WeakCachedFunction(CachedFunction): sage: f(5) Integer Ring + TESTS: + + Check that :trac:`16316` has been fixed, i.e., caching works for + immutable unhashable objects which define + :meth:`sage.structure.sage_object.SageObject._cache_key`. + + sage: from sage.misc.cachefunc import weak_cached_function + sage: @weak_cached_function + ....: def f(x): return x + sage: K. = Qq(4) + sage: R. = K[] + sage: f.set_cache(t,t) + sage: f.is_in_cache(t) + True + """ if self._argument_fixer is None: self.argfix_init() - self.cache[self._fix_to_pos(*args, **kwds)] = value - + k = self._fix_to_pos(*args, **kwds) + try: + self.cache[k] = value + except TypeError: # k is not hashable + k = (_cache_key,_cache_key(k)) + # to make sure that this key does not get confused with the key of + # a hashable object, such keys include _cache_key which is + # certainly not stored in the dictionary otherwise. + self.cache[k] = value weak_cached_function = decorator_keywords(WeakCachedFunction) @@ -1545,6 +1785,27 @@ cdef class CachedMethodCaller(CachedFunction): sage: b = Foo(3) sage: a.f(b.f) 2 + + Check that :trac:`16316` has been fixed, i.e., caching works for + immutable unhashable objects which define + :meth:`sage.structure.sage_object.SageObject._cache_key`. + + sage: K. = Qq(4) + sage: class A(object): + ....: @cached_method + ....: def f(self, x): return x+x + sage: a=A() + sage: x = K(1,1); x + 1 + O(2) + sage: y = K(1,2); y + 1 + O(2^2) + sage: x==y + True + sage: a.f(x) is a.f(x) + True + sage: a.f(y) is not a.f(x) + True + """ if self._instance is None: # cached method bound to a class @@ -1586,7 +1847,11 @@ cdef class CachedMethodCaller(CachedFunction): else: k = self._default_key = self._fix_to_pos() try: - return cache[k] + try: + return cache[k] + except TypeError: # k is not hashable + k = (_cache_key,_cache_key(k)) + return cache[k] except KeyError: w = self._cachedmethod._instance_call(self._instance, *args, **kwds) cache[k] = w @@ -2051,9 +2316,10 @@ cdef class CachedMethod(object): .. NOTE:: - For proper behavior, the method must be a pure function - (no side effects). Arguments to the method must be hashable - or transformed into something hashable using ``key``. + For proper behavior, the method must be a pure function (no side + effects). Arguments to the method must be hashable or transformed into + something hashable using ``key`` or they must define + :meth:`sage.structure.sage_object.SageObject._cache_key`. EXAMPLES:: @@ -2238,7 +2504,7 @@ cdef class CachedMethod(object): :meth:`sage.misc.superseded.deprecated_function_alias`:: sage: a.g() is a.f() - doctest:1: DeprecationWarning: g is deprecated. Please use f instead. + doctest:...: DeprecationWarning: g is deprecated. Please use f instead. See http://trac.sagemath.org/57 for details. True sage: Foo.g(a) is a.f() @@ -2415,6 +2681,8 @@ cdef class CachedSpecialMethod(CachedMethod): The hash is computed only once, subsequent calls will use the value from the cache. This was implemented in :trac:`12601`. + :: + sage: hash(c) # indirect doctest compute hash 5 @@ -2548,6 +2816,19 @@ def cached_method(f, name=None, key=None): sage: c.f(4) is c.f(4) True + Different instances have distinct caches:: + + sage: d = C() + sage: d.f(4) is c.f(4) + computing cached method + False + sage: d.f.clear_cache() + sage: c.f(4) + 8 + sage: d.f(4) + computing cached method + 8 + Using cached methods for the hash and other special methods was implemented in :trac:`12601`, by means of :class:`CachedSpecialMethod`. We show that it is used behind the scenes:: @@ -2577,15 +2858,17 @@ cdef class CachedInParentMethod(CachedMethod): This way of caching works only if - the instances *have* a parent, and - - the instances are hashable (they are part of the cache key). + - the instances are hashable (they are part of the cache key) or they + define :meth:`sage.structure.sage_object.SageObject._cache_key` NOTE: - For proper behavior, the method must be a pure function (no side - effects). If this decorator is used on a method, it will have - identical output on equal elements. This is since the element is - part of the hash key. Arguments to the method and the instance - it is assigned to must be hashable. + For proper behavior, the method must be a pure function (no side effects). + If this decorator is used on a method, it will have identical output on + equal elements. This is since the element is part of the hash key. + Arguments to the method must be hashable or define + :meth:`sage.structure.sage_object.SageObject._cache_key`. The instance it + is assigned to must be hashable. Examples can be found at :mod:`~sage.misc.cachefunc`. diff --git a/src/sage/misc/functional.py b/src/sage/misc/functional.py index 065bba9ed5e..0d631b86d6f 100644 --- a/src/sage/misc/functional.py +++ b/src/sage/misc/functional.py @@ -573,6 +573,41 @@ def symbolic_sum(expression, *args, **kwds): sage: sum(1/k^5, k, 1, oo) zeta(5) + .. WARNING:: + + This function only works with symbolic expressions. To sum any + other objects like list elements or function return values, + please use python summation, see + http://docs.python.org/library/functions.html#sum + + In particular, this does not work:: + + sage: n = var('n') + sage: list=[1,2,3,4,5] + sage: sum(list[n],n,0,3) + Traceback (most recent call last): + ... + TypeError: unable to convert x (=n) to an integer + + Use python ``sum()`` instead:: + + sage: sum(list[n] for n in range(4)) + 10 + + Also, only a limited number of functions are recognized in symbolic sums:: + + sage: sum(valuation(n,2),n,1,5) + Traceback (most recent call last): + ... + AttributeError: 'sage.symbolic.expression.Expression' object has no attribute 'valuation' + + Again, use python ``sum()``:: + + sage: sum(valuation(n+1,2) for n in range(5)) + 3 + + (now back to the Sage ``sum`` examples) + A well known binomial identity:: sage: sum(binomial(n,k), k, 0, n) @@ -1673,7 +1708,7 @@ def sqrt(x): EXAMPLES:: sage: numerical_sqrt(10.1) - doctest:1: DeprecationWarning: numerical_sqrt is deprecated, use sqrt(x, prec=n) instead + doctest:...: DeprecationWarning: numerical_sqrt is deprecated, use sqrt(x, prec=n) instead See http://trac.sagemath.org/5404 for details. 3.17804971641414 sage: numerical_sqrt(9) diff --git a/src/sage/misc/latex.py b/src/sage/misc/latex.py index ab83c4c4c04..41a1d12613d 100644 --- a/src/sage/misc/latex.py +++ b/src/sage/misc/latex.py @@ -4,6 +4,11 @@ In order to support latex formatting, an object should define a special method ``_latex_(self)`` that returns a string, which will be typeset in a mathematical mode (the exact mode depends on circumstances). + + AUTHORS: + + - William Stein: original implementation + - Joel B. Mohler: latex_variable_name() drastic rewrite and many doc-tests """ #***************************************************************************** @@ -15,7 +20,6 @@ # http://www.gnu.org/licenses/ #***************************************************************************** - EMBEDDED_MODE = False COMMON_HEADER = \ @@ -2659,9 +2663,12 @@ def latex_variable_name(x, is_fname=False): sage: latex_variable_name('x_ast') 'x_{\\ast}' - AUTHORS: - - - Joel B. Mohler: drastic rewrite and many doc-tests + TESTS:: + + sage: latex_variable_name('_C') # :trac:`16007` + 'C' + sage: latex_variable_name('_K1') + 'K_{1}' """ underscore = x.find("_") if underscore == -1: @@ -2679,6 +2686,11 @@ def latex_variable_name(x, is_fname=False): else: prefix = x[:underscore] suffix = x[underscore+1:] + if prefix == '': + from sage.calculus.calculus import symtable + for sym in symtable.values(): + if sym[0] == '_' and sym[1:] == suffix: + return latex_variable_name(suffix) if suffix and len(suffix) > 0: # handle the suffix specially because it very well might be numeric # I use strip to avoid using regex's -- It makes it a bit faster (and the code is more comprehensible to non-regex'ed people) diff --git a/src/sage/misc/misc.py b/src/sage/misc/misc.py index c87e5c25ea4..80664c62148 100644 --- a/src/sage/misc/misc.py +++ b/src/sage/misc/misc.py @@ -2370,7 +2370,7 @@ def inject_variable(name, value): sage: from warnings import warn sage: warn("blah") - doctest:1: UserWarning: blah + doctest:...: UserWarning: blah sage: warn("blah") Use with care! diff --git a/src/sage/misc/superseded.py b/src/sage/misc/superseded.py index b1ca15d0958..1fdd62f36d6 100644 --- a/src/sage/misc/superseded.py +++ b/src/sage/misc/superseded.py @@ -82,7 +82,7 @@ def deprecation(trac_number, message): sage: def foo(): ....: sage.misc.superseded.deprecation(13109, 'the function foo is replaced by bar') sage: foo() - doctest:1: DeprecationWarning: the function foo is replaced by bar + doctest:...: DeprecationWarning: the function foo is replaced by bar See http://trac.sagemath.org/13109 for details. """ _check_trac_number(trac_number) @@ -192,7 +192,7 @@ def __call__(self, *args, **kwds): sage: def bla(): return 42 sage: blo = deprecated_function_alias(13109, bla) sage: blo() - doctest:1: DeprecationWarning: blo is deprecated. Please use bla instead. + doctest:...: DeprecationWarning: blo is deprecated. Please use bla instead. See http://trac.sagemath.org/13109 for details. 42 """ diff --git a/src/sage/misc/temporary_file.py b/src/sage/misc/temporary_file.py index 59ec45bec06..2580311a1d8 100644 --- a/src/sage/misc/temporary_file.py +++ b/src/sage/misc/temporary_file.py @@ -120,7 +120,9 @@ def tmp_filename(name="tmp_", ext=""): - ``name`` -- (default: ``"tmp_"``) A prefix for the file name. - - ``ext`` -- (default: ``""``) A suffix for the file name. + - ``ext`` -- (default: ``""``) A suffix for the file name. If you + want a filename extension in the usual sense, this should start + with a dot. OUTPUT: @@ -147,14 +149,51 @@ def tmp_filename(name="tmp_", ext=""): def graphics_filename(ext='png'): """ - Return the next available canonical filename for a plot/graphics - file. + When run from the Sage notebook, return the next available canonical + filename for a plot/graphics file in the current working directory. + Otherwise, return a temporary file inside ``SAGE_TMP``. + + INPUT: + + - ``ext`` -- (default: ``"png"``) A file extension (without the dot) + for the filename. + + OUTPUT: + + The path of the temporary file created. In the notebook, this is + a filename without path in the current directory. Otherwise, this + an absolute path. + + EXAMPLES:: + + sage: from sage.misc.temporary_file import graphics_filename + sage: print graphics_filename() # random, typical filename for sagenb + sage0.png + + TESTS: + + When doctesting, this returns instead a random temporary file. + We check that it's a file inside ``SAGE_TMP`` and that the extension + is correct:: + + sage: fn = graphics_filename(ext="jpeg") + sage: fn.startswith(str(SAGE_TMP)) + True + sage: fn.endswith('.jpeg') + True """ - i = 0 - while os.path.exists('sage%d.%s'%(i,ext)): - i += 1 - filename = 'sage%d.%s'%(i,ext) - return filename + ext = '.' + ext + # Don't use this unsafe function except in the notebook, #15515 + import sage.plot.plot + if sage.plot.plot.EMBEDDED_MODE: + i = 0 + while os.path.exists('sage%d%s'%(i,ext)): + i += 1 + filename = 'sage%d%s'%(i,ext) + return filename + else: + return tmp_filename(ext=ext) + ################################################################# # write to a temporary file and move it in place diff --git a/src/sage/modules/vector_double_dense.pyx b/src/sage/modules/vector_double_dense.pyx index b7bd05fb179..09e9d46b0df 100644 --- a/src/sage/modules/vector_double_dense.pyx +++ b/src/sage/modules/vector_double_dense.pyx @@ -710,7 +710,7 @@ cdef class Vector_double_dense(free_module_element.FreeModuleElement): 0.953760808... sage: w = vector(CDF, [-1,0,1]) sage: w.norm(p=-1.6) - doctest:2097: RuntimeWarning: divide by zero encountered in power + doctest:...: RuntimeWarning: divide by zero encountered in power 0.0 Return values are in ``RDF``, or an integer when ``p = 0``. :: diff --git a/src/sage/monoids/free_abelian_monoid.py b/src/sage/monoids/free_abelian_monoid.py index 283edb909f9..2ed347a208e 100644 --- a/src/sage/monoids/free_abelian_monoid.py +++ b/src/sage/monoids/free_abelian_monoid.py @@ -57,8 +57,10 @@ from sage.structure.parent_gens import ParentWithGens, normalize_names from free_abelian_monoid_element import FreeAbelianMonoidElement from sage.rings.integer import Integer +from sage.rings.all import ZZ from sage.structure.factory import UniqueFactory +from sage.misc.decorators import rename_keyword class FreeAbelianMonoidFactory(UniqueFactory): """ @@ -101,8 +103,55 @@ def create_key(self, n, names): def create_object(self, version, key): return FreeAbelianMonoid_class(*key) -FreeAbelianMonoid = FreeAbelianMonoidFactory("FreeAbelianMonoid") +FreeAbelianMonoid_factory = FreeAbelianMonoidFactory("sage.monoids.free_abelian_monoid.FreeAbelianMonoid_factory") +@rename_keyword(deprecation=15289, n="index_set") +def FreeAbelianMonoid(index_set=None, names=None, **kwds): + """ + Return a free abelian monoid on `n` generators or with the generators + indexed by a set `I`. + + We construct free abelian monoids by specifing either: + + - the number of generators and/or the names of the generators + - the indexing set for the generators (this ignores the other two inputs) + + INPUT: + + - ``index_set`` -- an indexing set for the generators; if an integer, + then this becomes `\{0, 1, \ldots, n-1\}` + + - ``names`` -- names of generators + + OUTPUT: + + A free abelian monoid. + + EXAMPLES:: + + sage: F. = FreeAbelianMonoid(); F + Free abelian monoid on 5 generators (a, b, c, d, e) + sage: FreeAbelianMonoid(index_set=ZZ) + Free abelian monoid indexed by Integer Ring + """ + if isinstance(index_set, str): # Swap args (this works if names is None as well) + names, index_set = index_set, names + + if index_set is None and names is not None: + if isinstance(names, str): + index_set = names.count(',') + else: + index_set = len(names) + + if index_set not in ZZ: + if names is not None: + names = normalize_names(len(names), names) + from sage.monoids.indexed_free_monoid import IndexedFreeAbelianMonoid + return IndexedFreeAbelianMonoid(index_set, names=names, **kwds) + + if names is None: + raise ValueError("names must be specified") + return FreeAbelianMonoid_factory(index_set, names) def is_FreeAbelianMonoid(x): """ @@ -215,3 +264,19 @@ def ngens(self): """ return self.__ngens + def cardinality(self): + r""" + Return the cardinality of ``self``, which is `\infty`. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(3000, 'a') + sage: F.cardinality() + +Infinity + """ + if self.__ngens == 0: + from sage.rings.all import ZZ + return ZZ.one() + from sage.rings.infinity import infinity + return infinity + diff --git a/src/sage/monoids/free_monoid.py b/src/sage/monoids/free_monoid.py index 4aeeb4b42b5..8257cba1f95 100644 --- a/src/sage/monoids/free_monoid.py +++ b/src/sage/monoids/free_monoid.py @@ -40,20 +40,20 @@ from sage.structure.factory import UniqueFactory from sage.misc.cachefunc import cached_method +from sage.misc.decorators import rename_keyword +from sage.rings.all import ZZ class FreeMonoidFactory(UniqueFactory): """ - Returns a free monoid on `n` generators. + Create the free monoid in `n` generators. INPUT: - - ``n`` - integer - ``names`` - names of generators - - OUTPUT: free abelian monoid + OUTPUT: free monoid EXAMPLES:: @@ -70,12 +70,74 @@ def create_key(self, n, names): n = int(n) names = normalize_names(n, names) return (n, names) - - def create_object(self, version, key): + def create_object(self, version, key, **kwds): return FreeMonoid_class(*key) -FreeMonoid = FreeMonoidFactory("FreeMonoid") +FreeMonoid_factory = FreeMonoidFactory("sage.monoids.free_monoid.FreeMonoid_factory") + +@rename_keyword(deprecation=15289, n="index_set") +def FreeMonoid(index_set=None, names=None, commutative=False, **kwds): + r""" + Return a free monoid on `n` generators or with the generators indexed by + a set `I`. + + We construct free monoids by specifing either: + + - the number of generators and/or the names of the generators + - the indexing set for the generators + + INPUT: + + - ``index_set`` -- an indexing set for the generators; if an integer, + than this becomes `\{0, 1, \ldots, n-1\}` + + - ``names`` -- names of generators + + - ``commutative`` -- (default: ``False``) whether the free monoid is + commutative or not + + OUTPUT: + + A free monoid. + + EXAMPLES:: + + sage: F. = FreeMonoid(); F + Free monoid on 5 generators (a, b, c, d, e) + sage: FreeMonoid(index_set=ZZ) + Free monoid indexed by Integer Ring + + sage: F. = FreeMonoid(abelian=True); F + Free abelian monoid on 3 generators (x, y, z) + sage: FreeMonoid(index_set=ZZ, commutative=True) + Free abelian monoid indexed by Integer Ring + """ + if 'abelian' in kwds: + commutative = kwds['abelian'] + del kwds['abelian'] + + if commutative: + from sage.monoids.free_abelian_monoid import FreeAbelianMonoid + return FreeAbelianMonoid(index_set, names, **kwds) + if isinstance(index_set, str): # Swap args (this works if names is None as well) + names, index_set = index_set, names + + if index_set is None and names is not None: + if isinstance(names, str): + index_set = names.count(',') + else: + index_set = len(names) + + if index_set not in ZZ: + if names is not None: + names = normalize_names(len(names), names) + from sage.monoids.indexed_free_monoid import IndexedFreeMonoid + return IndexedFreeMonoid(index_set, names=names, **kwds) + + if names is None: + raise ValueError("names must be specified") + return FreeMonoid_factory(index_set, names) def is_FreeMonoid(x): """ @@ -92,8 +154,15 @@ def is_FreeMonoid(x): False sage: is_FreeMonoid(FreeAbelianMonoid(0,'')) False + sage: is_FreeMonoid(FreeMonoid(index_set=ZZ)) + True + sage: is_FreeMonoid(FreeAbelianMonoid(index_set=ZZ)) + False """ - return isinstance(x, FreeMonoid_class) + if isinstance(x, FreeMonoid_class): + return True + from sage.monoids.indexed_free_monoid import IndexedFreeMonoid + return isinstance(x, IndexedFreeMonoid) class FreeMonoid_class(Monoid_class): """ @@ -250,3 +319,20 @@ def one_element(self): 1 """ return self(1) + + def cardinality(self): + r""" + Return the cardinality of ``self``, which is `\infty`. + + EXAMPLES:: + + sage: F = FreeMonoid(2005, 'a') + sage: F.cardinality() + +Infinity + """ + if self.__ngens == 0: + from sage.rings.all import ZZ + return ZZ.one() + from sage.rings.infinity import infinity + return infinity + diff --git a/src/sage/monoids/indexed_free_monoid.py b/src/sage/monoids/indexed_free_monoid.py new file mode 100644 index 00000000000..323a8fc3d66 --- /dev/null +++ b/src/sage/monoids/indexed_free_monoid.py @@ -0,0 +1,992 @@ +""" +Indexed Monoids + +AUTHORS: + +- Travis Scrimshaw (2013-10-15) +""" + +#***************************************************************************** +# Copyright (C) 2013 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from copy import copy +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.element import MonoidElement +from sage.structure.indexed_generators import IndexedGenerators +from sage.combinat.dict_addition import dict_addition + +from sage.categories.monoids import Monoids +from sage.categories.poor_man_map import PoorManMap +from sage.rings.integer import Integer +from sage.rings.infinity import infinity +from sage.rings.all import ZZ +from sage.sets.finite_enumerated_set import FiniteEnumeratedSet +from sage.sets.family import Family + +class IndexedMonoidElement(MonoidElement): + """ + An element of an indexed monoid. + + This is an abstract class which uses the (abstract) method + :meth:`_sorted_items` for all of its functions. So to implement an + element of an indexed monoid, one just needs to implement + :meth:`_sorted_items`, which returns a list of pairs ``(i, p)`` where + ``i`` is the index and ``p`` is the corresponding power, sorted in some + order. For example, in the free monoid there is no such choice, but for + the free abelian monoid, one could want lex order or have the highest + powers first. + + Indexed monoid elements are ordered lexicographically with respect to + the result of :meth:`_sorted_items` (which for abelian free monoids is + influenced by the order on the indexing set). + """ + def __init__(self, F, x): + """ + Create the element ``x`` of an indexed free abelian monoid ``F``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: F.gen(1) + F[1] + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: x = a^2 * b^3 * a^2 * b^4; x + F[0]^4*F[1]^7 + sage: TestSuite(x).run() + + sage: F = FreeMonoid(index_set=tuple('abcde')) + sage: a,b,c,d,e = F.gens() + sage: a in F + True + sage: a*b in F + True + sage: TestSuite(a*d^2*e*c*a).run() + """ + MonoidElement.__init__(self, F) + self._monomial = x + + @abstract_method + def _sorted_items(self): + """ + Return the sorted items (i.e factors) of ``self``. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: x = a*b^2*e*d + sage: x._sorted_items() + ((0, 1), (1, 2), (4, 1), (3, 1)) + + .. SEEALSO:: + + :meth:`_repr_`, :meth:`_latex_`, :meth:`print_options` + """ + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: a*b^2*e*d + F[0]*F[1]^2*F[3]*F[4] + """ + if not self._monomial: + return '1' + + monomial = self._sorted_items() + P = self.parent() + + scalar_mult = P._print_options['scalar_mult'] + + exp = lambda v: '^{}'.format(v) if v != 1 else '' + return scalar_mult.join(P._repr_generator(g) + exp(v) for g,v in monomial) + + def _ascii_art_(self): + r""" + Return an ASCII art representation of ``self``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: ascii_art(a*e*d) + F *F *F + 0 3 4 + sage: ascii_art(a*b^2*e*d) + 2 + F *F *F *F + 0 1 3 4 + """ + from sage.misc.ascii_art import AsciiArt, ascii_art, empty_ascii_art + + if not self._monomial: + return AsciiArt(["1"]) + + monomial = self._sorted_items() + P = self.parent() + scalar_mult = P._print_options['scalar_mult'] + + if all(x[1] == 1 for x in monomial): + ascii_art_gen = lambda m: P._ascii_art_generator(m[0]) + else: + pref = AsciiArt([P.prefix()]) + def ascii_art_gen(m): + if m[1] != 1: + r = (AsciiArt([" "**Integer(len(pref))]) + ascii_art(m[1])) + else: + r = empty_ascii_art + r = r * P._ascii_art_generator(m[0]) + r._baseline = r._h - 2 + return r + b = ascii_art_gen(monomial[0]) + for x in monomial[1:]: + b = b + AsciiArt([scalar_mult]) + ascii_art_gen(x) + return b + + def _latex_(self): + r""" + Return a `\LaTeX` representation of ``self``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: latex(a*b^2*e*d) + F_{0} F_{1}^{2} F_{3} F_{4} + """ + if not self._monomial: + return '1' + + monomial = self._sorted_items() + P = self.parent() + + scalar_mult = P._print_options['latex_scalar_mult'] + if scalar_mult is None: + scalar_mult = P._print_options['scalar_mult'] + if scalar_mult == "*": + scalar_mult = " " + + exp = lambda v: '^{{{}}}'.format(v) if v != 1 else '' + return scalar_mult.join(P._latex_generator(g) + exp(v) for g,v in monomial) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: list(b*a*c^3*b) + [(F[1], 1), (F[0], 1), (F[2], 3), (F[1], 1)] + + :: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: list(b*c^3*a) + [(F[0], 1), (F[1], 1), (F[2], 3)] + """ + return ((self.parent().gen(index), exp) for (index,exp) in self._sorted_items()) + + def __eq__(self, y): + """ + Check equality. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: a == a + True + sage: a*e == a*e + True + sage: a*b*c^3*b*d == (a*b*c)*(c^2*b*d) + True + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: a == a + True + sage: a*e == e*a + True + sage: a*b*c^3*b*d == a*d*(b^2*c^2)*c + True + """ + if not isinstance(y, IndexedMonoidElement): + return y == 1 and not self._monomial + return y.parent() is self.parent() and y._monomial == self._monomial + + def __ne__(self, y): + """ + Check inequality. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: a != b + True + sage: a*b != b*a + True + sage: a*b*c^3*b*d != (a*b*c)*(c^2*b*d) + False + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: a != b + True + sage: a*b != a*a + True + sage: a*b*c^3*b*d != a*d*(b^2*c^2)*c + False + """ + return not self.__eq__(y) + + def __lt__(self, y): + """ + Check less than. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: a < b + True + sage: a*b < b*a + True + sage: a*b < a*a + False + sage: a^2*b < a*b*b + True + """ + if not isinstance(y, IndexedMonoidElement): + return False + return self.to_word_list() < y.to_word_list() + + def __gt__(self, y): + """ + Check less than. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: b > a + True + sage: a*b > b*a + False + sage: a*b > a*a + True + """ + if not isinstance(y, IndexedMonoidElement): + return False + return y.__lt__(self) + + def __le__(self, y): + """ + Check less than or equals. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: a*b <= b*a + True + """ + return self.__eq__(y) or self.__lt__(y) + + def __ge__(self, y): + """ + Check greater than or equals. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: a*b <= b*a + True + """ + return self.__eq__(y) or self.__gt__(y) + + def support(self): + """ + Return a list of the objects indexing ``self`` with + non-zero exponents. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: (b*a*c^3*b).support() + [0, 1, 2] + + :: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: (a*c^3).support() + [0, 2] + """ + supp = set([key for key, exp in self._sorted_items() if exp != 0]) + return sorted(supp) + + def leading_support(self): + """ + Return the support of the leading generator of ``self``. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: (b*a*c^3*a).leading_support() + 1 + + :: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: (b*c^3*a).leading_support() + 0 + """ + if not self: + return None + return self._sorted_items()[0][0] + + def trailing_support(self): + """ + Return the support of the trailing generator of ``self``. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: (b*a*c^3*a).trailing_support() + 0 + + :: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: (b*c^3*a).trailing_support() + 2 + """ + if not self: + return None + return self._sorted_items()[-1][0] + + def to_word_list(self): + """ + Return ``self`` as a word represented as a list whose entries + are indices of ``self``. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: (b*a*c^3*a).to_word_list() + [1, 0, 2, 2, 2, 0] + + :: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: (b*c^3*a).to_word_list() + [0, 1, 2, 2, 2] + """ + return [k for k,e in self._sorted_items() for dummy in range(e)] + +class IndexedFreeMonoidElement(IndexedMonoidElement): + """ + An element of an indexed free abelian monoid. + """ + def __init__(self, F, x): + """ + Create the element ``x`` of an indexed free abelian monoid ``F``. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=tuple('abcde')) + sage: x = F( [(1, 2), (0, 1), (3, 2), (0, 1)] ) + sage: y = F( ((1, 2), (0, 1), [3, 2], [0, 1]) ) + sage: z = F( reversed([(0, 1), (3, 2), (0, 1), (1, 2)]) ) + sage: x == y and y == z + True + sage: TestSuite(x).run() + """ + IndexedMonoidElement.__init__(self, F, tuple(map(tuple, x))) + + + def _sorted_items(self): + """ + Return the sorted items (i.e factors) of ``self``. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: x = a*b^2*e*d + sage: x._sorted_items() + ((0, 1), (1, 2), (4, 1), (3, 1)) + sage: F.print_options(generator_cmp = lambda x,y: -cmp(x,y)) + sage: x._sorted_items() + ((0, 1), (1, 2), (4, 1), (3, 1)) + sage: F.print_options(generator_cmp=cmp) # reset to original state + + .. SEEALSO:: + + :meth:`_repr_`, :meth:`_latex_`, :meth:`print_options` + """ + return self._monomial + + def _mul_(self, other): + """ + Multiply ``self`` by ``other``. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: a*b^2*e*d + F[0]*F[1]^2*F[4]*F[3] + sage: (a*b^2*d^2) * (d^4*b*e) + F[0]*F[1]^2*F[3]^6*F[1]*F[4] + """ + if not self._monomial: + return other + if not other._monomial: + return self + + ret = list(self._monomial) + rhs = list(other._monomial) + if ret[-1][0] == rhs[0][0]: + rhs[0] = (rhs[0][0], rhs[0][1] + ret.pop()[1]) + ret += rhs + return self.__class__(self.parent(), tuple(ret)) + + def __len__(self): + """ + Return the length of ``self``. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: elt = a*c^3*b^2*a + sage: elt.length() + 7 + sage: len(elt) + 7 + """ + return sum(exp for gen,exp in self._monomial) + + length = __len__ + + +class IndexedFreeAbelianMonoidElement(IndexedMonoidElement): + """ + An element of an indexed free abelian monoid. + """ + def __init__(self, F, x): + """ + Create the element ``x`` of an indexed free abelian monoid ``F``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: x = F([(0, 1), (2, 2), (-1, 2)]) + sage: y = F({0:1, 2:2, -1:2}) + sage: z = F(reversed([(0, 1), (2, 2), (-1, 2)])) + sage: x == y and y == z + True + sage: TestSuite(x).run() + """ + IndexedMonoidElement.__init__(self, F, dict(x)) + + def _sorted_items(self): + """ + Return the sorted items (i.e factors) of ``self``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: x = a*b^2*e*d + sage: x._sorted_items() + [(0, 1), (1, 2), (3, 1), (4, 1)] + sage: F.print_options(generator_cmp = lambda x,y: -cmp(x,y)) + sage: x._sorted_items() + [(4, 1), (3, 1), (1, 2), (0, 1)] + sage: F.print_options(generator_cmp=cmp) # reset to original state + + .. SEEALSO:: + + :meth:`_repr_`, :meth:`_latex_`, :meth:`print_options` + """ + print_options = self.parent().print_options() + v = self._monomial.items() + try: + v.sort(cmp = print_options['generator_cmp']) + except StandardError: # Sorting the output is a plus, but if we can't, no big deal + pass + return v + + def _mul_(self, other): + """ + Multiply ``self`` by ``other``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: a*b^2*e*d + F[0]*F[1]^2*F[3]*F[4] + """ + return self.__class__(self.parent(), + dict_addition([self._monomial, other._monomial])) + + def __pow__(self, n): + """ + Raise ``self`` to the power of ``n``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: x = a*b^2*e*d; x + F[0]*F[1]^2*F[3]*F[4] + sage: x^3 + F[0]^3*F[1]^6*F[3]^3*F[4]^3 + sage: x^0 + 1 + """ + if not isinstance(n, (int, long, Integer)): + raise TypeError("Argument n (= {}) must be an integer".format(n)) + if n < 0: + raise ValueError("Argument n (= {}) must be positive".format(n)) + if n == 1: + return self + if n == 0: + return self.parent().one() + return self.__class__(self.parent(), {k:v*n for k,v in self._monomial.iteritems()}) + + def __floordiv__(self, elt): + """ + Cancel the element ``elt`` out of ``self``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: elt = a*b*c^3*d^2; elt + F[0]*F[1]*F[2]^3*F[3]^2 + sage: elt // a + F[1]*F[2]^3*F[3]^2 + sage: elt // c + F[0]*F[1]*F[2]^2*F[3]^2 + sage: elt // (a*b*d^2) + F[2]^3 + sage: elt // a^4 + Traceback (most recent call last): + ... + ValueError: invalid cancellation + """ + d = copy(self._monomial) + for k,v in elt._monomial.iteritems(): + d[k] -= v + for k,v in d.items(): + if v < 0: + raise ValueError("invalid cancellation") + if v == 0: + del d[k] + return self.__class__(self.parent(), d) + + def __len__(self): + """ + Return the length of ``self``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: elt = a*c^3*b^2*a + sage: elt.length() + 7 + sage: len(elt) + 7 + """ + m = self._monomial + return sum(m[gen] for gen in m) + + length = __len__ + + def dict(self): + """ + Return ``self`` as a dictionary. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: (a*c^3).dict() + {0: 1, 2: 3} + """ + return copy(self._monomial) + +class IndexedMonoid(Parent, IndexedGenerators, UniqueRepresentation): + """ + Base class for monoids with an indexed set of generators. + + INPUT: + + - ``indices`` -- the indices for the generators + + For the optional arguments that control the printing, see + :class:`~sage.structure.indexed_generators.IndexedGenerators`. + """ + @staticmethod + def __classcall__(cls, indices, prefix="F", **kwds): + """ + TESTS:: + + sage: F = FreeAbelianMonoid(index_set=['a','b','c']) + sage: G = FreeAbelianMonoid(index_set=('a','b','c')) + sage: H = FreeAbelianMonoid(index_set=tuple('abc')) + sage: F is G and F is H + True + + sage: F = FreeAbelianMonoid(index_set=['a','b','c'], latex_bracket=['LEFT', 'RIGHT']) + sage: F.print_options()['latex_bracket'] + ('LEFT', 'RIGHT') + sage: F is G + False + sage: Groups.Commutative.free() + Traceback (most recent call last): + ... + ValueError: no index set specified + """ + if isinstance(indices, str): + indices = FiniteEnumeratedSet(list(indices)) + elif isinstance(indices, (list, tuple)): + indices = FiniteEnumeratedSet(indices) + elif indices is None: + if kwds.get('names', None) is None: + raise ValueError("no index set specified") + indices = FiniteEnumeratedSet(kwds['names']) + + # bracket or latex_bracket might be lists, so convert + # them to tuples so that they're hashable. + bracket = kwds.get('bracket', None) + if isinstance(bracket, list): + kwds['bracket'] = tuple(bracket) + latex_bracket = kwds.get('latex_bracket', None) + if isinstance(latex_bracket, list): + kwds['latex_bracket'] = tuple(latex_bracket) + return super(IndexedMonoid, cls).__classcall__(cls, indices, prefix, **kwds) + + def __init__(self, indices, prefix, category=None, names=None, **kwds): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: TestSuite(F).run() + sage: F = FreeMonoid(index_set=tuple('abcde')) + sage: TestSuite(F).run() + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: TestSuite(F).run() + sage: F = FreeAbelianMonoid(index_set=tuple('abcde')) + sage: TestSuite(F).run() + """ + self._indices = indices + category = Monoids().or_subcategory(category) + if indices.cardinality() == 0: + category = category.Finite() + else: + category = category.Infinite() + Parent.__init__(self, names=names, category=category) + + # ignore the optional 'key' since it only affects CachedRepresentation + kwds.pop('key', None) + IndexedGenerators.__init__(self, indices, prefix, **kwds) + + def _first_ngens(self, n): + """ + Used by the preparser for ``F. = ...``. + + EXAMPLES:: + + sage: F. = FreeMonoid(index_set=ZZ) + sage: [x, y, z] + [F[0], F[1], F[-1]] + """ + it = iter(self._indices) + return tuple(self.gen(it.next()) for i in range(n)) + + def _element_constructor_(self, x=None): + """ + Create an element of this abelian monoid from ``x``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: F(F.gen(2)) + F[2] + sage: F([[1, 3], [-2, 12]]) + F[-2]^12*F[1]^3 + sage: F(-5) + Traceback (most recent call last): + ... + ValueError: unable to convert -5, use gen() instead + """ + if x is None: + return self.one() + if x in self._indices: + raise ValueError("unable to convert {}, use gen() instead".format(x)) + # return self.gens()[x] + return self.element_class(self, x) + + def _an_element_(self): + """ + Return an element of ``self``. + + EXAMPLES:: + + sage: G = FreeAbelianMonoid(index_set=ZZ) + sage: G.an_element() + F[-1]^3*F[0]*F[1]^3 + sage: G = FreeMonoid(index_set=tuple('ab')) + sage: G.an_element() + F['a']^2*F['b']^2 + """ + x = self.one() + I = self._indices + try: + x *= self.gen(I.an_element()) + except Exception: + pass + try: + g = iter(self._indices) + for c in range(1,4): + x *= self.gen(g.next()) ** c + except Exception: + pass + return x + + def cardinality(self): + r""" + Return the cardinality of ``self``, which is `\infty` unless this is + the trivial monoid. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: F.cardinality() + +Infinity + sage: F = FreeMonoid(index_set=()) + sage: F.cardinality() + 1 + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: F.cardinality() + +Infinity + sage: F = FreeAbelianMonoid(index_set=()) + sage: F.cardinality() + 1 + """ + if self._indices.cardinality() == 0: + return ZZ.one() + return infinity + + def monoid_generators(self): + """ + Return the monoid generators of ``self``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: F.monoid_generators() + Lazy family (Generator map from Integer Ring to + Free abelian monoid indexed by Integer Ring(i))_{i in Integer Ring} + sage: F = FreeAbelianMonoid(index_set=tuple('abcde')) + sage: sorted(F.monoid_generators()) + [F['a'], F['b'], F['c'], F['d'], F['e']] + """ + if self._indices.cardinality() == infinity: + gen = PoorManMap(self.gen, domain=self._indices, codomain=self, name="Generator map") + return Family(self._indices, gen) + return Family(self._indices, self.gen) + + gens = monoid_generators + +class IndexedFreeMonoid(IndexedMonoid): + """ + Free monoid with an indexed set of generators. + + INPUT: + + - ``indices`` -- the indices for the generators + + For the optional arguments that control the printing, see + :class:`~sage.structure.indexed_generators.IndexedGenerators`. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: F.gen(15)^3 * F.gen(2) * F.gen(15) + F[15]^3*F[2]*F[15] + sage: F.gen(1) + F[1] + + Now we examine some of the printing options:: + + sage: F = FreeMonoid(index_set=ZZ, prefix='X', bracket=['|','>']) + sage: F.gen(2) * F.gen(12) + X|2>*X|12> + """ + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: FreeMonoid(index_set=ZZ) + Free monoid indexed by Integer Ring + """ + return "Free monoid indexed by {}".format(self._indices) + + Element = IndexedFreeMonoidElement + + @cached_method + def one(self): + """ + Return the identity element of ``self``. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: F.one() + 1 + """ + return self.element_class(self, ()) + + def gen(self, x): + """ + The generator indexed by ``x`` of ``self``. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: F.gen(0) + F[0] + sage: F.gen(2) + F[2] + """ + if x not in self._indices: + raise IndexError("{} is not in the index set".format(x)) + try: + return self.element_class(self, ((self._indices(x),1),)) + except TypeError: # Backup (if it is a string) + return self.element_class(self, ((x,1),)) + +class IndexedFreeAbelianMonoid(IndexedMonoid): + """ + Free abelian monoid with an indexed set of generators. + + INPUT: + + - ``indices`` -- the indices for the generators + + For the optional arguments that control the printing, see + :class:`~sage.structure.indexed_generators.IndexedGenerators`. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: F.gen(15)^3 * F.gen(2) * F.gen(15) + F[2]*F[15]^4 + sage: F.gen(1) + F[1] + + Now we examine some of the printing options:: + + sage: F = FreeAbelianMonoid(index_set=Partitions(), prefix='A', bracket=False, scalar_mult='%') + sage: F.gen([3,1,1]) * F.gen([2,2]) + A[2, 2]%A[3, 1, 1] + """ + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: FreeAbelianMonoid(index_set=ZZ) + Free abelian monoid indexed by Integer Ring + """ + return "Free abelian monoid indexed by {}".format(self._indices) + + def _element_constructor_(self, x=None): + """ + Create an element of ``self`` from ``x``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: F(F.gen(2)) + F[2] + sage: F([[1, 3], [-2, 12]]) + F[-2]^12*F[1]^3 + sage: F({1:3, -2: 12}) + F[-2]^12*F[1]^3 + """ + if isinstance(x, (list, tuple)): + x = dict(x) + return IndexedMonoid._element_constructor_(self, x) + + Element = IndexedFreeAbelianMonoidElement + + @cached_method + def one(self): + """ + Return the identity element of ``self``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: F.one() + 1 + """ + return self.element_class(self, {}) + + def gen(self, x): + """ + The generator indexed by ``x`` of ``self``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: F.gen(0) + F[0] + sage: F.gen(2) + F[2] + """ + if x not in self._indices: + raise IndexError("{} is not in the index set".format(x)) + try: + return self.element_class(self, {self._indices(x):1}) + except TypeError: # Backup (if it is a string) + return self.element_class(self, {x:1}) + diff --git a/src/sage/numerical/all.py b/src/sage/numerical/all.py index a7a55508c26..220b1e90102 100644 --- a/src/sage/numerical/all.py +++ b/src/sage/numerical/all.py @@ -9,3 +9,7 @@ minimize_constrained) from sage.numerical.mip import MixedIntegerLinearProgram from sage.numerical.backends.generic_backend import default_mip_solver + +from sage.misc.lazy_import import lazy_import +lazy_import("sage.numerical.interactive_simplex_method", + ["LPProblem", "LPProblemStandardForm"]) diff --git a/src/sage/numerical/backends/coin_backend.pyx b/src/sage/numerical/backends/coin_backend.pyx index e7632f118a8..2c50b509268 100644 --- a/src/sage/numerical/backends/coin_backend.pyx +++ b/src/sage/numerical/backends/coin_backend.pyx @@ -832,10 +832,13 @@ cdef class CoinBackend(GenericBackend): cdef double v solution = self.model.solver().getColSolution() if solution == NULL: - v = 0.0 - return v + v = 0.0 else: - return solution[variable] + v = solution[variable] + if self.is_variable_continuous(variable): + return v + else: + return round(v) cpdef int ncols(self): r""" diff --git a/src/sage/numerical/backends/glpk_backend.pyx b/src/sage/numerical/backends/glpk_backend.pyx index 49db772e41d..8743f5c902c 100644 --- a/src/sage/numerical/backends/glpk_backend.pyx +++ b/src/sage/numerical/backends/glpk_backend.pyx @@ -386,7 +386,7 @@ cdef class GLPKBackend(GenericBackend): sage: p = MixedIntegerLinearProgram(solver='GLPK') sage: x,y = p[0], p[1] - doctest:839: DeprecationWarning: The default behaviour of new_variable() will soon change ! It will return 'real' variables instead of nonnegative ones. Please be explicit and call new_variable(nonnegative=True) instead. + doctest:...: DeprecationWarning: The default value of 'nonnegative' will change, to False instead of True. You should add the explicit 'nonnegative=True'. See http://trac.sagemath.org/15521 for details. sage: p.add_constraint(2*x + 3*y, max = 6) sage: p.add_constraint(3*x + 2*y, max = 6) diff --git a/src/sage/numerical/backends/gurobi_backend.pyx b/src/sage/numerical/backends/gurobi_backend.pyx index 2fee2dd8dae..857bebee674 100644 --- a/src/sage/numerical/backends/gurobi_backend.pyx +++ b/src/sage/numerical/backends/gurobi_backend.pyx @@ -756,7 +756,10 @@ cdef class GurobiBackend(GenericBackend): cdef double value[1] check(self.env,GRBgetdblattrelement(self.model, "X", variable, value)) - return round(value[0]) if self.is_variable_binary(variable) else value[0] + if self.is_variable_continuous(variable): + return value[0] + else: + return round(value[0]) cpdef int ncols(self): """ diff --git a/src/sage/numerical/interactive_simplex_method.py b/src/sage/numerical/interactive_simplex_method.py new file mode 100644 index 00000000000..3b9baaf68c4 --- /dev/null +++ b/src/sage/numerical/interactive_simplex_method.py @@ -0,0 +1,3937 @@ +r""" +Interactive Simplex Method + +The purpose of this module is to support learning and exploring of the simplex +method. While it does allow solving Linear Programming Problems (LPPs) in a +number of ways, it may require explicit (and correct!) description of steps and +is likely to be much slower than "regular" LP solvers. Therefore, do not +use this module if all you want is the result. If, however, you want to +learn how the simplex method works and see what happens in different situations +using different strategies, but don't want to deal with tedious arithmetic, +this module is for you! + +Historically it was created to complement the Math 373 course on Mathematical +Programming and Optimization at the University of Alberta, Edmonton, Canada. + +AUTHORS: + +- Andrey Novoseltsev (2013-03-16): initial version. + +EXAMPLES: + +Most of the module functionality is demonstrated on the following problem. + +.. admonition:: Corn & Barley + + A farmer has 1000 acres available to grow corn and barley. + Corn has a net profit of 10 dollars per acre while barley has a net + profit of 5 dollars per acre. + The farmer has 1500 kg of fertilizer available with 3 kg per acre + needed for corn and 1 kg per acre needed for barley. + The farmer wants to maximize profit. + (Sometimes we also add one more constraint to make the initial dictionary + infeasible: the farmer has to use at least 40% of the available land.) + +Using variables `C` and `B` for land used to grow corn and barley respectively, +in acres, we can construct the following LP problem:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P + LP problem (use typeset mode to see details) + +It is recommended to copy-paste such examples into your own worksheet, so that +you can run these commands with typeset mode on and get + +.. MATH:: + + \begin{array}{l} + \begin{array}{lcrcrcl} + \max \!\!\!&\!\!\! \!\!\!&\!\!\! 10 C \!\!\!&\!\!\! + \!\!\!&\!\!\! 5 B \!\!\! \\ + \!\!\!&\!\!\! \!\!\!&\!\!\! C \!\!\!&\!\!\! + \!\!\!&\!\!\! B \!\!\!&\!\!\! \leq \!\!\!&\!\!\! 1000 \\ + \!\!\!&\!\!\! \!\!\!&\!\!\! 3 C \!\!\!&\!\!\! + \!\!\!&\!\!\! B \!\!\!&\!\!\! \leq \!\!\!&\!\!\! 1500 \\ + \end{array} \\ + C, B \geq 0 + \end{array} + +Since it has only two variables, we can solve it graphically:: + + sage: P.plot() + + +The simplex method can be applied only to :class:`problems in standard form +`, which can be created either directly :: + + sage: LPProblemStandardForm(A, b, c, ["C", "B"]) + LP problem (use typeset mode to see details) + +or from an already constructed problem of "general type":: + + sage: P = P.standard_form() + +In this case the problem does not require any modifications to be written in +standard form, but this step is still necessary to enable methods related to +the simplex method. + +The simplest way to use the simplex method is:: + + sage: P.run_simplex_method() + '...' + +(This method produces quite long formulas which have been omitted here.) +But, of course, it is much more fun to do most of the steps by hand. Let's start +by creating the initial dictionary:: + + sage: D = P.initial_dictionary() + sage: D + LP problem dictionary (use typeset mode to see details) + +Using typeset mode as recommended, you'll see + +.. MATH:: + + \renewcommand{\arraystretch}{1.5} + \begin{array}{|rcrcrcr|} + \hline + x_{3} \!\!\!&\!\!\! = \!\!\!&\!\!\! 1000 \!\!\!&\!\!\! - \!\!\!&\!\!\! C \!\!\!&\!\!\! - \!\!\!&\!\!\! B\\ + x_{4} \!\!\!&\!\!\! = \!\!\!&\!\!\! 1500 \!\!\!&\!\!\! - \!\!\!&\!\!\! 3 C \!\!\!&\!\!\! - \!\!\!&\!\!\! B\\ + \hline + z \!\!\!&\!\!\! = \!\!\!&\!\!\! 0 \!\!\!&\!\!\! + \!\!\!&\!\!\! 10 C \!\!\!&\!\!\! + \!\!\!&\!\!\! 5 B\\ + \hline + \end{array} + +With the initial or any other dictionary you can perform a number of checks:: + + sage: D.is_feasible() + True + sage: D.is_optimal() + False + +You can look at many of its pieces and associated data:: + + sage: D.basic_variables() + (x3, x4) + sage: D.basic_solution() + (0, 0) + sage: D.objective_value() + 0 + +Most importantly, you can perform steps of the simplex method by picking an +entering variable, a leaving variable, and updating the dictionary:: + + sage: D.enter("C") + sage: D.leave(4) + sage: D.update() + +If everything was done correctly, the new dictionary is still feasible and the +objective value did not decrease:: + + sage: D.is_feasible() + True + sage: D.objective_value() + 5000 + +If you are unsure about picking entering and leaving variables, you can use +helper methods that will try their best to tell you what are your next +options:: + + sage: D.possible_entering() + [B] + sage: D.possible_leaving() + Traceback (most recent call last): + ... + ValueError: leaving variables can be determined + for feasible dictionaries with a set entering variable + or for dual feasible dictionaries + +It is also possible to obtain :meth:`feasible sets ` +and :meth:`final dictionaries ` of +problems, work with :class:`revised dictionaries `, +and use the dual simplex method! + +.. NOTE:: + + Currently this does not have a display format for the terminal. +""" + + +#***************************************************************************** +# Copyright (C) 2013 Andrey Novoseltsev +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + + +import operator, re, sys + + +from copy import copy + + +from sage.geometry.all import Polyhedron +from sage.matrix.all import (column_matrix, + identity_matrix, + matrix, + random_matrix) +from sage.misc.all import (LatexExpr, + cached_function, + cached_method, + latex, + randint, + random) +from sage.misc.latex import EMBEDDED_MODE +from sage.misc.misc import get_main_globals +from sage.modules.all import random_vector, vector +from sage.plot.all import Graphics, arrow, line, point, rainbow, text +from sage.rings.all import Infinity, PolynomialRing, QQ, RDF, ZZ +from sage.structure.all import SageObject +from sage.symbolic.all import SR + + +# We produce rather complicated LaTeX code which needs some tweaks to be +# displayed nicely by MathJax, which make it look slightly different from real +# LaTeX. We use our own variable as it may be convenient to override it. +# Hopefully, some day there will be no need in it at all and only "if" parts +# will have to be left. +generate_real_LaTeX = not EMBEDDED_MODE + + +def _assemble_arrayl(lines, stretch=None): + r""" + Return ``lines`` assembled in a left-justified array. + + INPUT: + + - ``lines`` -- a list of strings suitable for math mode typesetting + + - ``stretch`` -- (default: None) if given, a command setting + ``\arraystretch`` to this value will be added before the array + + OUTPUT: + + - a :class:`LatexExpr` + + EXAMPLES:: + + sage: from sage.numerical.interactive_simplex_method \ + ....: import _assemble_arrayl + sage: lines = ["1 + 1", "2"] + sage: print _assemble_arrayl(lines) + \begin{array}{l} + 1 + 1\\ + 2 + \end{array} + sage: print _assemble_arrayl(lines, 1.5) + \renewcommand{\arraystretch}{1.500000} + \begin{array}{l} + 1 + 1\\ + 2 + \end{array} + """ + # Even simple LP problems tend to generate long output, so we prohibit + # truncation in the notebook cells and hope for the best! + return LatexExpr(("" if generate_real_LaTeX else "%notruncate\n") + + ("" if stretch is None else + "\\renewcommand{\\arraystretch}{%f}\n" % stretch) + + "\\begin{array}{l}\n" + + "\\\\\n".join(lines) + + "\n\\end{array}") + + +def _latex_product(coefficients, variables, + separator=None, head=None, tail=None, + drop_plus=True, allow_empty=False): + r""" + Generate LaTeX code for a linear function. + + This function is intended for internal use by LaTeX methods of LP problems + and their dictionaries. + + INPUT: + + - ``coefficients`` -- a list of coefficients + + - ``variables`` -- a list of variables + + - ``separator`` -- (default: ``"&"`` with some extra space adjustment) a + string to be inserted between elements of the generated expression + + - ``head`` -- either ``None`` (default) or a list of entries to be + added to the beginning of the output + + - ``tail`` -- either ``None`` (default) or a list of entries to be + added to the end of the output + + - ``drop_plus`` -- (default: ``True``) whether to drop the leading plus + sign or not + + - ``allow_empty`` -- (default: ``False``) whether to allow empty output or + produce at least "0" + + OUTPUT: + + - A string joining ``head``, each sign and coefficient-variable product, + and ``tail`` using ``separator``. Strings in ``head`` and ``tail`` are + used as is except for "<=", "==", and ">=", which are replaced by LaTeX + commands. Other elements in ``head`` in ``tail`` are processed by + :func:`latex`. + + TESTS:: + + sage: from sage.numerical.interactive_simplex_method import \ + ....: _latex_product + sage: var("x, y") + (x, y) + sage: print _latex_product([-1, 3], [x, y]) + - & x & + & 3 y + """ + entries = [] + for c, v in zip(coefficients, variables): + if c == 0: + entries.extend(["", ""]) + continue + sign = "+" + if latex(c).strip().startswith("-"): + sign = "-" + c = - c + if c == 1: + t = latex(v) + else: + t = latex(c) + if SR(c).operator() in [operator.add, operator.sub]: + t = r"\left( " + t + r" \right)" + t += " " + latex(v) + entries.extend([sign, t]) + if drop_plus: # Don't start with + + for i, e in enumerate(entries): + if e: # The first non-empty + if e == "+": + entries[i] = "" + break + if not (allow_empty or any(entries)): # Return at least 0 + entries[-1] = "0" + latex_relations = {"<=": r"\leq", "==": "=", ">=": r"\geq"} + if head is not None: + for e in reversed(head): + if not isinstance(e, str): + e = latex(e) + elif e in latex_relations: + e = latex_relations[e] + entries.insert(0, e) + if tail is not None: + for e in tail: + if not isinstance(e, str): + e = latex(e) + elif e in latex_relations: + e = latex_relations[e] + entries.append(e) + if separator is None: + if generate_real_LaTeX: + separator = " & " + else: + separator = r" \mspace{-6mu}&\mspace{-6mu} " + return separator.join(entries) + + +@cached_function +def variable(R, v): + r""" + Interpret ``v`` as a variable of ``R``. + + INPUT: + + - ``R`` -- a polynomial ring + + - ``v`` -- a variable of ``R`` or convertible into ``R``, a string + with the name of a variable of ``R`` or an index of a variable in ``R`` + + OUTPUT: + + - a variable of ``R`` + + EXAMPLES:: + + sage: from sage.numerical.interactive_simplex_method \ + ....: import variable + sage: R = PolynomialRing(QQ, "x3, y5, x5, y") + sage: R.inject_variables() + Defining x3, y5, x5, y + sage: variable(R, "x3") + x3 + sage: variable(R, x3) + x3 + sage: variable(R, 3) + x3 + sage: variable(R, 0) + Traceback (most recent call last): + ... + ValueError: there is no variable with the given index + sage: variable(R, 5) + Traceback (most recent call last): + ... + ValueError: the given index is ambiguous + sage: variable(R, 2 * x3) + Traceback (most recent call last): + ... + ValueError: cannot interpret given data as a variable + sage: variable(R, "z") + Traceback (most recent call last): + ... + ValueError: cannot interpret given data as a variable + """ + if v in ZZ: + v = str(v) + tail = re.compile(r"\d+$") + matches = [] + for g in R.gens(): + match = tail.search(str(g)) + if match is not None and match.group() == v: + matches.append(g) + if not matches: + raise ValueError("there is no variable with the given index") + if len(matches) > 1: + raise ValueError("the given index is ambiguous") + return matches[0] + else: + try: + v = R(v) + if v in R.gens(): + return v + except TypeError: + pass + raise ValueError("cannot interpret given data as a variable") + + +class LPProblem(SageObject): + r""" + Construct an LP (Linear Programming) problem. + + This class supports LP problems with "variables on the left" constraints. + + INPUT: + + - ``A`` -- a matrix of constraint coefficients + + - ``b`` -- a vector of constraint constant terms + + - ``c`` -- a vector of objective coefficients + + - ``x`` -- (default: ``"x"``) a vector of decision variables or a + string giving the base name + + - ``constraint_type`` -- (default: ``"<="``) a string specifying constraint + type(s): either ``"<="``, ``">="``, ``"=="``, or a list of them + + - ``variable_type`` -- (default: ``""``) a string specifying variable + type(s): either ``">="``, ``"<="``, ``""`` (the empty string), or a + list of them, corresponding, respectively, to non-negative, + non-positive, and free variables + + - ``problem_type`` -- (default: ``"max"``) a string specifying the + problem type: ``"max"``, ``"min"``, ``"-max"``, or ``"-min"`` + + - ``prefix`` -- (default: parameter ``x`` if it was given as a string, + string ``"x"`` otherwise) a string giving the base name of automatically + created variables in :meth:`standard_form` + + - ``base_ring`` -- (default: the fraction field of a common ring for all + input coefficients) a field to which all input coefficients will be + converted + + EXAMPLES: + + We will construct the following problem: + + .. MATH:: + + \begin{array}{l} + \begin{array}{lcrcrcl} + \max \!\!\!&\!\!\! \!\!\!&\!\!\! 10 C \!\!\!&\!\!\! + \!\!\!&\!\!\! 5 B \!\!\! \\ + \!\!\!&\!\!\! \!\!\!&\!\!\! C \!\!\!&\!\!\! + \!\!\!&\!\!\! B \!\!\!&\!\!\! \leq \!\!\!&\!\!\! 1000 \\ + \!\!\!&\!\!\! \!\!\!&\!\!\! 3 C \!\!\!&\!\!\! + \!\!\!&\!\!\! B \!\!\!&\!\!\! \leq \!\!\!&\!\!\! 1500 \\ + \end{array} \\ + C, B \geq 0 + \end{array} + + :: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + + Same problem, but more explicitly:: + + sage: P = LPProblem(A, b, c, ["C", "B"], + ....: constraint_type="<=", variable_type=">=") + + Even more explicitly:: + + sage: P = LPProblem(A, b, c, ["C", "B"], problem_type="max", + ....: constraint_type=["<=", "<="], variable_type=[">=", ">="]) + + Using the last form you should be able to represent any LP problem, as long + as all like terms are collected and in constraints variables and constants + are on different sides. + """ + + def __init__(self, A, b, c, x="x", + constraint_type="<=", variable_type="", problem_type="max", + prefix="x", base_ring=None): + r""" + See :class:`LPProblem` for documentation. + + TESTS:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: TestSuite(P).run() + """ + super(LPProblem, self).__init__() + A = matrix(A) + b = vector(b) + c = vector(c) + if base_ring is None: + base_ring = vector(A.list() + list(b) + list(c)).base_ring() + base_ring = base_ring.fraction_field() + A = A.change_ring(base_ring) + A.set_immutable() + m, n = A.nrows(), A.ncols() + b = b.change_ring(base_ring) + b.set_immutable() + if b.degree() != m: + raise ValueError("A and b have incompatible dimensions") + c = c.change_ring(base_ring) + c.set_immutable() + if c.degree() != n: + raise ValueError("A and c have incompatible dimensions") + if isinstance(x, str): + prefix = x + x = ["{}{:d}".format(x, i) for i in range(1, n+1)] + else: + x = map(str, x) + if len(x) != n: + raise ValueError("A and x have incompatible dimensions") + R = PolynomialRing(base_ring, x, order="neglex") + x = vector(R, R.gens()) # All variables as a vector + self._Abcx = A, b, c, x + + if constraint_type in ["<=", ">=", "=="]: + constraint_type = (constraint_type, ) * m + else: + constraint_type = tuple(constraint_type) + if any(ct not in ["<=", ">=", "=="] for ct in constraint_type): + raise ValueError("unknown constraint type") + if len(constraint_type) != m: + raise ValueError("wrong number of constraint types") + self._constraint_types = constraint_type + + if variable_type in ["<=", ">=", ""]: + variable_type = (variable_type, ) * n + else: + variable_type = tuple(variable_type) + if any(vt not in ["<=", ">=", ""] for vt in variable_type): + raise ValueError("unknown variable type") + if len(variable_type) != n: + raise ValueError("wrong number of variable types") + self._variable_types = variable_type + + if problem_type.startswith("-"): + self._is_negative = True + problem_type = problem_type[1:] + else: + self._is_negative = False + if problem_type not in ["max", "min"]: + raise ValueError("unknown problem type") + self._problem_type = problem_type + + self._prefix = prefix + + def __eq__(self, other): + r""" + Check if two LP problems are equal. + + INPUT: + + - ``other`` -- anything + + OUTPUT: + + - ``True`` if ``other`` is an :class:`LPProblem` with all details the + same as ``self``, ``False`` otherwise. + + TESTS:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P2 = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P == P2 + True + sage: P3 = LPProblem(A, c, b, ["C", "B"], variable_type=">=") + sage: P == P3 + False + """ + return (isinstance(other, LPProblem) and + self.Abcx() == other.Abcx() and + self._problem_type == other._problem_type and + self._is_negative == other._is_negative and + self._constraint_types == other._constraint_types and + self._variable_types == other._variable_types and + self._prefix == other._prefix) + + def _latex_(self): + r""" + Return a LaTeX representation of ``self``. + + OUTPUT: + + - a string + + TESTS:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: print P._latex_() + \begin{array}{l} \setlength{\arraycolsep}{0.125em} + \begin{array}{lcrcrcl} + \max & & 10 C & + & 5 B\\ + & & C & + & B & \leq & 1000 \\ + & & 3 C & + & B & \leq & 1500 \\ + \end{array} \\ + C, B \geq 0 + \end{array} + """ + A, b, c, x = self.Abcx() + lines = [] + lines.append(r"\begin{array}{l}") + if generate_real_LaTeX: + lines[-1] += r" \setlength{\arraycolsep}{0.125em}" + lines.append(r"\begin{array}{l" + "cr" * len(x) + "cl}") + head = [r"{} \{}".format("- " if self._is_negative else "", + self._problem_type)] + lines.append(_latex_product(c, x, head=head) + + (r"\\" if generate_real_LaTeX else r" \mspace{-6mu} \\")) + for Ai, ri, bi in zip(A.rows(), self._constraint_types, b): + lines.append(_latex_product(Ai, x, head=[""], tail=[ri, bi]) + + r" \\") + lines.append(r"\end{array} \\") + if set(self._variable_types) == set([">="]): + lines.append(r"{} \geq 0".format(", ".join(map(latex, x)))) + else: + lines.append(r",\ ".join(r"{} {} 0".format( + latex(xj), r"\geq" if vt == ">=" else r"\leq") + for xj, vt in zip(x, self._variable_types) if vt)) + lines.append(r"\end{array}") + return "\n".join(lines) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + OUTPUT: + + - a string + + TESTS:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: print P._repr_() + LP problem (use typeset mode to see details) + """ + return "LP problem (use typeset mode to see details)" + + @cached_method + def _solve(self): + r""" + Return an optimal solution and the optimal value of ``self``. + + OUTPUT: + + - A pair consisting of a vector and a number. If the problem is + infeasible, both components are ``None``. If the problem is + unbounded, the first component is ``None`` and the second is + `\pm \infty`. + + This function uses "brute force" solution technique of evaluating the + objective at all vertices of the feasible set and taking into account + its rays and lines. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P._solve() + ((250, 750), 6250) + """ + F = self.feasible_set() + R = self.base_ring() + A, b, c, x = self._Abcx + if F.n_vertices() == 0: + return (None, None) + elif c.is_zero(): + M, S = 0, F.vertices()[0] + elif self._problem_type == "max": + if any(c * vector(R, ray) > 0 for ray in F.rays()) or \ + any(c * vector(R, line) != 0 for line in F.lines()): + M, S = Infinity, None + else: + M, S = max((c * vector(R, v), v) for v in F.vertices()) + elif self._problem_type == "min": + if any(c * vector(R, ray) < 0 for ray in F.rays()) or \ + any(c * vector(R, line) != 0 for line in F.lines()): + M, S = -Infinity, None + else: + M, S = min((c * vector(R, v), v) for v in F.vertices()) + if self._is_negative: + M = - M + if S is not None: + S = vector(R, S) + S.set_immutable() + return S, M + + def Abcx(self): + r""" + Return `A`, `b`, `c`, and `x` of ``self`` as a tuple. + + OUTPUT: + + - a tuple + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.Abcx() + ( + [1 1] + [3 1], (1000, 1500), (10, 5), (C, B) + ) + """ + return self._Abcx + + def base_ring(self): + r""" + Return the base ring of ``self``. + + .. NOTE:: + + The base ring of LP problems is always a field. + + OUTPUT: + + - a ring + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.base_ring() + Rational Field + + sage: c = (10, 5.) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.base_ring() + Real Field with 53 bits of precision + """ + return self._Abcx[0].base_ring() + + def constant_terms(self): + r""" + Return constant terms of constraints of ``self``, i.e. `b`. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.constant_terms() + (1000, 1500) + sage: P.b() + (1000, 1500) + """ + return self._Abcx[1] + + def constraint_coefficients(self): + r""" + Return coefficients of constraints of ``self``, i.e. `A`. + + OUTPUT: + + - a matrix + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.constraint_coefficients() + [1 1] + [3 1] + sage: P.A() + [1 1] + [3 1] + """ + return self._Abcx[0] + + def decision_variables(self): + r""" + Return decision variables of ``self``, i.e. `x`. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.decision_variables() + (C, B) + sage: P.x() + (C, B) + """ + return self._Abcx[3] + + def dual(self, y=None): + r""" + Construct the dual LP problem for ``self``. + + INPUT: + + - ``y`` -- (default: ``"x"`` if the prefix of ``self`` is ``"y"``, + ``"y"`` otherwise) a vector of dual decision variables or a string + giving the base name + + OUTPUT: + + - an :class:`LPProblem` + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: DP = P.dual() + sage: DP.b() == P.c() + True + sage: DP.dual(["C", "B"]) == P + True + """ + A, c, b, x = self.Abcx() + A = A.transpose() + if y is None: + y = "x" if self._prefix == "y" else "y" + problem_type = "min" if self._problem_type == "max" else "max" + constraint_type = [] + for vt in self._variable_types: + if (vt == ">=" and problem_type == "min" or + vt == "<=" and problem_type == "max"): + constraint_type.append(">=") + elif (vt == "<=" and problem_type == "min" or + vt == ">=" and problem_type == "max"): + constraint_type.append("<=") + else: + constraint_type.append("==") + variable_type = [] + for ct in self._constraint_types: + if (ct == ">=" and problem_type == "min" or + ct == "<=" and problem_type == "max"): + variable_type.append("<=") + elif (ct == "<=" and problem_type == "min" or + ct == ">=" and problem_type == "max"): + variable_type.append(">=") + else: + variable_type.append("") + if self._is_negative: + problem_type = "-" + problem_type + return LPProblem(A, b, c, y, + constraint_type, variable_type, problem_type) + + @cached_method + def feasible_set(self): + r""" + Return the feasible set of ``self``. + + OUTPUT: + + - a :mod:`Polyhedron ` + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.feasible_set() + A 2-dimensional polyhedron in QQ^2 + defined as the convex hull of 4 vertices + """ + ieqs = [] + eqns = [] + for a, r, b in zip(self.A().rows(), self._constraint_types, self.b()): + if r == "<=": + ieqs.append([b] + list(-a)) + elif r == ">=": + ieqs.append([-b] + list(a)) + else: + eqns.append([-b] + list(a)) + for n, r in zip(identity_matrix(self.n()).rows(), self._variable_types): + if r == "<=": + ieqs.append([0] + list(-n)) + elif r == ">=": + ieqs.append([0] + list(n)) + if self.base_ring() is QQ: + R = QQ + else: + R = RDF + ieqs = [map(R, ieq) for ieq in ieqs] + eqns = [map(R, eqn) for eqn in eqns] + return Polyhedron(ieqs=ieqs, eqns=eqns, base_ring=R) + + def is_bounded(self): + r""" + Check if ``self`` is bounded. + + OUTPUT: + + - ``True`` is ``self`` is bounded, ``False`` otherwise + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.is_bounded() + True + """ + return self._solve()[0] is not None + + def is_feasible(self): + r""" + Check if ``self`` is feasible. + + OUTPUT: + + - ``True`` is ``self`` is feasible, ``False`` otherwise + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.is_feasible() + True + """ + return self._solve()[1] is not None + + def n_constraints(self): + r""" + Return the number of constraints of ``self``, i.e. `m`. + + OUTPUT: + + - an integer + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.n_constraints() + 2 + sage: P.m() + 2 + """ + return self._Abcx[0].nrows() + + def n_variables(self): + r""" + Return the number of decision variables of ``self``, i.e. `n`. + + OUTPUT: + + - an integer + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.n_variables() + 2 + sage: P.n() + 2 + """ + return self._Abcx[0].ncols() + + def objective_coefficients(self): + r""" + Return coefficients of the objective of ``self``, i.e. `c`. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.objective_coefficients() + (10, 5) + sage: P.c() + (10, 5) + """ + return self._Abcx[2] + + def optimal_solution(self): + r""" + Return **an** optimal solution of ``self``. + + OUTPUT: + + - a vector or ``None`` if there are no optimal solutions + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.optimal_solution() + (250, 750) + """ + return self._solve()[0] + + def optimal_value(self): + r""" + Return the optimal value for ``self``. + + OUTPUT: + + - a number if the problem is bounded, `\pm \infty` if it is unbounded, + or ``None`` if it is infeasible + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: P.optimal_value() + 6250 + """ + return self._solve()[1] + + def plot(self, *args, **kwds): + r""" + Return a plot for solving ``self`` graphically. + + INPUT: + + - ``xmin``, ``xmax``, ``ymin``, ``ymax`` -- bounds for the axes, if + not given, an attempt will be made to pick reasonable values + + - ``alpha`` -- (default: 0.2) determines how opaque are shadows + + OUTPUT: + + - a plot + + This only works for problems with two decision variables. On the plot + the black arrow indicates the direction of growth of the objective. The + lines perpendicular to it are level curves of the objective. If there + are optimal solutions, the arrow originates in one of them and the + corresponding level curve is solid: all points of the feasible set + on it are optimal solutions. Otherwise the arrow is placed in the + center. If the problem is infeasible or the objective is zero, a plot + of the feasible set only is returned. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: p = P.plot() + sage: p.show() + + In this case the plot works better with the following axes ranges:: + + sage: p = P.plot(0, 1000, 0, 1500) + sage: p.show() + + TESTS: + + We check that zero objective can be dealt with:: + + sage: LPProblem(A, b, (0, 0), ["C", "B"], variable_type=">=").plot() + """ + FP = self.plot_feasible_set(*args, **kwds) + c = self.c().n().change_ring(QQ) + if c.is_zero(): + return FP + xmin = FP.xmin() + xmax = FP.xmax() + ymin = FP.ymin() + ymax = FP.ymax() + xmin, xmax, ymin, ymax = map(QQ, [xmin, xmax, ymin, ymax]) + start = self.optimal_solution() + start = vector(QQ, start.n() if start is not None + else [xmin + (xmax-xmin)/2, ymin + (ymax-ymin)/2]) + length = min(xmax - xmin, ymax - ymin) / 5 + end = start + (c * length / c.norm()).n().change_ring(QQ) + result = FP + point(start, color="black", size=50, zorder=10) + result += arrow(start, end, color="black", zorder=10) + ieqs = [(xmax, -1, 0), (- xmin, 1, 0), + (ymax, 0, -1), (- ymin, 0, 1)] + box = Polyhedron(ieqs=ieqs) + d = vector([c[1], -c[0]]) + for i in range(-10, 11): + level = Polyhedron(vertices=[start + i*(end-start)], lines=[d]) + level = box.intersection(level) + if level.vertices(): + if i == 0 and self.is_bounded(): + result += line(level.vertices(), color="black", + thickness=2) + else: + result += line(level.vertices(), color="black", + linestyle="--") + result.set_axes_range(xmin, xmax, ymin, ymax) + result.axes_labels(FP.axes_labels()) #FIXME: should be preserved! + return result + + def plot_feasible_set(self, xmin=None, xmax=None, ymin=None, ymax=None, + alpha=0.2): + r""" + Return a plot of the feasible set of ``self``. + + INPUT: + + - ``xmin``, ``xmax``, ``ymin``, ``ymax`` -- bounds for the axes, if + not given, an attempt will be made to pick reasonable values + + - ``alpha`` -- (default: 0.2) determines how opaque are shadows + + OUTPUT: + + - a plot + + This only works for a problem with two decision variables. The plot + shows boundaries of constraints with a shadow on one side for + inequalities. If the :meth:`feasible_set` is not empty and at least + part of it is in the given boundaries, it will be shaded gray and `F` + will be placed in its middle. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: p = P.plot_feasible_set() + sage: p.show() + + In this case the plot works better with the following axes ranges:: + + sage: p = P.plot_feasible_set(0, 1000, 0, 1500) + sage: p.show() + """ + if self.n() != 2: + raise ValueError("only problems with 2 variables can be plotted") + A, b, c, x = self.Abcx() + if self.base_ring() is not QQ: + # Either we use QQ or crash + A = A.n().change_ring(QQ) + b = b.n().change_ring(QQ) + F = self.feasible_set() + if ymax is None: + ymax = max(map(abs, b) + [v[1] for v in F.vertices()]) + if ymin is None: + ymin = min([-ymax/4.0] + [v[1] for v in F.vertices()]) + if xmax is None: + xmax = max([1.5*ymax] + [v[0] for v in F.vertices()]) + if xmin is None: + xmin = min([-xmax/4.0] + [v[0] for v in F.vertices()]) + xmin, xmax, ymin, ymax = map(QQ, [xmin, xmax, ymin, ymax]) + pad = max(xmax - xmin, ymax - ymin) / 20 + ieqs = [(xmax, -1, 0), (- xmin, 1, 0), + (ymax, 0, -1), (- ymin, 0, 1)] + box = Polyhedron(ieqs=ieqs) + F = box.intersection(F) + result = Graphics() + colors = rainbow(self.m() + 2) + for Ai, ri, bi, color in zip(A.rows(), self._constraint_types, + b, colors[:-2]): + border = box.intersection(Polyhedron(eqns=[[-bi] + list(Ai)])) + vertices = border.vertices() + if not vertices: + continue + label = r"${}$".format(_latex_product(Ai, x, " ", tail=[ri, bi])) + result += line(vertices, color=color, legend_label=label) + if ri == "<=": + ieqs = [[bi] + list(-Ai), [-bi+pad*Ai.norm().n()] + list(Ai)] + elif ri == ">=": + ieqs = [[-bi] + list(Ai), [bi+pad*Ai.norm().n()] + list(-Ai)] + else: + continue + ieqs = map(lambda ieq: map(QQ, ieq), ieqs) + halfplane = box.intersection(Polyhedron(ieqs=ieqs)) + result += halfplane.render_solid(alpha=alpha, color=color) + # Same for variables, but no legend + for ni, ri, color in zip((QQ**2).gens(), self._variable_types, + colors[-2:]): + border = box.intersection(Polyhedron(eqns=[[0] + list(ni)])) + if not border.vertices(): + continue + if ri == "<=": + ieqs = [[0] + list(-ni), [pad] + list(ni)] + elif ri == ">=": + ieqs = [[0] + list(ni), [pad] + list(-ni)] + else: + continue + ieqs = map(lambda ieq: map(QQ, ieq), ieqs) + halfplane = box.intersection(Polyhedron(ieqs=ieqs)) + result += halfplane.render_solid(alpha=alpha, color=color) + if F.vertices(): + result += F.render_solid(alpha=alpha, color="gray") + result += text("$F$", F.center(), + fontsize=20, color="black", zorder=5) + result.set_axes_range(xmin, xmax, ymin, ymax) + result.axes_labels(map(lambda xi: "${}$".format(latex(xi)), x)) + result.legend(True) + result.set_legend_options(fancybox=True, handlelength=1.5, loc=1, + shadow=True) + result._extra_kwds["aspect_ratio"] = 1 + result.set_aspect_ratio(1) + return result + + def standard_form(self): + r""" + Construct the LP problem in standard form equivalent to ``self``. + + OUTPUT: + + - an :class:`LPProblemStandardForm` + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblem(A, b, c, ["C", "B"], variable_type=">=") + sage: DP = P.dual() + sage: DPSF = DP.standard_form() + sage: DPSF.b() + (-10, -5) + """ + A, b, c, x = self.Abcx() + if not all(ct == "<=" for ct in self._constraint_types): + newA = [] + newb = [] + for ct, Ai, bi in zip(self._constraint_types, A, b): + if ct in ["<=", "=="]: + newA.append(Ai) + newb.append(bi) + if ct in [">=", "=="]: + newA.append(-Ai) + newb.append(-bi) + A = matrix(newA) + b = vector(newb) + if not all(vt == ">=" for vt in self._variable_types): + newA = [] + newc = [] + newx = [] + for vt, Aj, cj, xj in zip(self._variable_types, A.columns(), c, x): + xj = str(xj) + if vt in [">=", ""]: + newA.append(Aj) + newc.append(cj) + if vt == ">=": + newx.append(xj) + if vt == "": + newx.append(xj + "_p") + if vt in ["<=", ""]: + newA.append(-Aj) + newc.append(-cj) + newx.append(xj + "_n") + A = column_matrix(newA) + c = vector(newc) + x = newx + is_negative = self._is_negative + if self._problem_type == "min": + is_negative = not is_negative + c = - c + problem_type = "-max" if is_negative else "max" + return LPProblemStandardForm(A, b, c, x, problem_type, + self._prefix, self._prefix + "0") + + # Aliases for the standard notation + A = constraint_coefficients + b = constant_terms + c = objective_coefficients + x = decision_variables + m = n_constraints + n = n_variables + + +class LPProblemStandardForm(LPProblem): + r""" + Construct an LP (Linear Programming) problem in standard form. + + The used standard form is: + + .. MATH:: + + \begin{array}{l} + \pm \max cx \\ + Ax \leq b \\ + x \geq 0 + \end{array} + + INPUT: + + - ``A`` -- a matrix of constraint coefficients + + - ``b`` -- a vector of constraint constant terms + + - ``c`` -- a vector of objective coefficients + + - ``x`` -- (default: ``"x"``) a vector of decision variables or a string + the base name giving + + - ``problem_type`` -- (default: ``"max"``) a string specifying the + problem type: either ``"max"`` or ``"-max"`` + + - ``slack_variables`` -- (default: same as ``x`` parameter, if it was given + as a string, otherwise string ``"x"``) a vector of slack variables or + a sting giving the base name + + - ``auxiliary_variable`` -- (default: same as ``x`` parameter with adjoined + ``"0"`` if it was given as a string, otherwise ``"x0"``) the auxiliary + name, expected to be the same as the first decision variable for + auxiliary problems + + - ``objective`` -- (default: ``"z"``) the objective variable (used for the + initial dictionary) + + - ``base_ring`` -- (default: the fraction field of a common ring for all + input coefficients) a field to which all input coefficients will be + converted + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + + Unlike :class:`LPProblem`, this class does not allow you to adjust types of + constraints (they are always ``"<="``) and variables (they are always + ``">="``), and the problem type may only be ``"max"`` or ``"-max"``. + You may give custom names to slack and auxiliary variables, but in + most cases defaults should work:: + + sage: P.decision_variables() + (x1, x2) + sage: P.slack_variables() + (x3, x4) + """ + + def __init__(self, A, b, c, x="x", problem_type="max", + slack_variables=None, auxiliary_variable=None, objective="z", + base_ring=None): + r""" + See :class:`StandardFormLPP` for documentation. + + TESTS:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: TestSuite(P).run() + """ + if problem_type not in ("max", "-max"): + raise ValueError("problems in standard form must be of (negative) " + "maximization type") + super(LPProblemStandardForm, self).__init__(A, b, c, x, + problem_type=problem_type, + constraint_type="<=", + variable_type=">=", + base_ring=base_ring) + n, m = self.n(), self.m() + if slack_variables is None: + slack_variables = self._prefix + if isinstance(slack_variables, str): + slack_variables = ["{}{:d}".format(slack_variables, i) + for i in range(n + 1, n + m + 1)] + else: + slack_variables = map(str, slack_variables) + if len(slack_variables) != m: + raise ValueError("wrong number of slack variables") + if auxiliary_variable is None: + auxiliary_variable = self._prefix + "0" + names = [str(auxiliary_variable)] + map(str, self.x()) + slack_variables + if names[0] == names[1]: + names.pop(0) + R = PolynomialRing(self.base_ring(), names, order="neglex") + self._R = R + x = vector(R.gens()[-n-m:-m]) + x.set_immutable() + self._Abcx = self._Abcx[:-1] + (x, ) + self._objective = objective + + def auxiliary_problem(self): + r""" + Construct the auxiliary problem for ``self``. + + OUTPUT: + + - an :class:`LP problem in standard form ` + + The auxiliary problem with the auxiliary variable `x_0` is + + .. MATH:: + + \begin{array}{l} + \max - x_0 \\ + - x_0 + A_i x \leq b_i \text{ for all } i \\ + x \geq 0 + \end{array}\ . + + Such problems are used when the :meth:`initial_dictionary` is + infeasible. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1], [-1, -1]) + sage: b = (1000, 1500, -400) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: AP = P.auxiliary_problem() + """ + X = self.coordinate_ring().gens() + m, n = self.m(), self.n() + if len(X) == m + n: + raise ValueError("auxiliary variable is already among decision " + "ones") + F = self.base_ring() + A = column_matrix(F, [-1] * m).augment(self.A()) + c = vector(F, [-1] + [0] * n) + return LPProblemStandardForm(A, self.b(), c, X[:-m], + slack_variables=X[-m:], + auxiliary_variable=X[0], + objective="w") + + def auxiliary_variable(self): + r""" + Return the auxiliary variable of ``self``. + + Note that the auxiliary variable may or may not be among + :meth:`~LPProblem.decision_variables`. + + OUTPUT: + + - a variable of the :meth:`coordinate_ring` of ``self`` + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1], [-1, -1]) + sage: b = (1000, 1500, -400) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: P.auxiliary_variable() + x0 + sage: P.decision_variables() + (x1, x2) + sage: AP = P.auxiliary_problem() + sage: AP.auxiliary_variable() + x0 + sage: AP.decision_variables() + (x0, x1, x2) + """ + return self._R.gen(0) + + def coordinate_ring(self): + r""" + Return the coordinate ring of ``self``. + + OUTPUT: + + - a polynomial ring over the :meth:`~LPProblem.base_ring` of ``self`` in + the :meth:`auxiliary_variable`, :meth:`~LPProblem.decision_variables`, + and :meth:`slack_variables` with "neglex" order + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1], [-1, -1]) + sage: b = (1000, 1500, -400) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: P.coordinate_ring() + Multivariate Polynomial Ring in x0, x1, x2, x3, x4, x5 + over Rational Field + sage: P.base_ring() + Rational Field + sage: P.auxiliary_variable() + x0 + sage: P.decision_variables() + (x1, x2) + sage: P.slack_variables() + (x3, x4, x5) + """ + return self._R + + def dictionary(self, *x_B): + r""" + Construct a dictionary for ``self`` with given basic variables. + + INPUT: + + - basic variables for the dictionary to be constructed + + OUTPUT: + + - a :class:`dictionary ` + + .. NOTE:: + + This is a synonym for ``self.revised_dictionary(x_B).dictionary()``, + but basic variables are mandatory. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.dictionary("x1", "x2") + sage: D.basic_variables() + (x1, x2) + """ + if not x_B: + raise ValueError("basic variables must be given explicitly") + return self.revised_dictionary(*x_B).dictionary() + + def feasible_dictionary(self, auxiliary_dictionary): + r""" + Construct a feasible dictionary for ``self``. + + INPUT: + + - ``auxiliary_dictionary`` -- an optimal dictionary for the + :meth:`auxiliary_problem` of ``self`` with the optimal value `0` and + a non-basic auxiliary variable + + OUTPUT: + + - a feasible :class:`dictionary ` for ``self`` + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1], [-1, -1]) + sage: b = (1000, 1500, -400) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: AP = P.auxiliary_problem() + sage: D = AP.initial_dictionary() + sage: D.enter(0) + sage: D.leave(5) + sage: D.update() + sage: D.enter(1) + sage: D.leave(0) + sage: D.update() + sage: D.is_optimal() + True + sage: D.objective_value() + 0 + sage: D.basic_solution() + (0, 400, 0) + sage: D = P.feasible_dictionary(D) + sage: D.is_optimal() + False + sage: D.is_feasible() + True + sage: D.objective_value() + 4000 + sage: D.basic_solution() + (400, 0) + """ + # It is good to have sanity checks in this function, but they are a bit + # problematic with numerical dictionaries, so we do only few. + x0 = self.auxiliary_variable() + if x0 not in auxiliary_dictionary.nonbasic_variables(): + raise ValueError("the auxiliary variable must be non-basic") + if not auxiliary_dictionary.is_feasible(): + raise ValueError("the auxiliary dictionary must be feasible") + A, b, c, v, B, N, z = auxiliary_dictionary._AbcvBNz + B = tuple(B) + N = tuple(N) + k = N.index(x0) + N = N[:k] + N[k+1:] + n = len(c) + A = A.matrix_from_columns(range(k) + range(k + 1, n)) + b = copy(b) + c = vector(self.base_ring(), n - 1) + for cj, xj in zip(*self.Abcx()[-2:]): + if xj in N: + c[N.index(xj)] += cj + else: + i = B.index(xj) + c -= cj * A[i] + v += cj * b[i] + B = map(self._R, B) + N = map(self._R, N) + return LPDictionary(A, b, c, v, B, N, self._objective) + + def final_dictionary(self): + r""" + Return the final dictionary of the simplex method applied to ``self``. + + See :meth:`run_simplex_method` for the description of possibilities. + + OUTPUT: + + - a :class:`dictionary ` + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.final_dictionary() + sage: D.is_optimal() + True + + TESTS:: + + sage: P.final_dictionary() is P.final_dictionary() + False + """ + try: + D = self._final_dictionary + # Since dictionaries are "highly mutable", forget the returned one. + del self._final_dictionary + return D + except AttributeError: + self.run_simplex_method() + return self.final_dictionary() + + def final_revised_dictionary(self): + r""" + Return the final dictionary of the revised simplex method applied + to ``self``. + + See :meth:`run_revised_simplex_method` for the description of + possibilities. + + OUTPUT: + + - a :class:`revised dictionary ` + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.final_revised_dictionary() + sage: D.is_optimal() + True + + TESTS:: + + sage: P.final_revised_dictionary() is P.final_revised_dictionary() + False + """ + try: + D = self._final_revised_dictionary + # Since dictionaries are "highly mutable", forget the returned one. + del self._final_revised_dictionary + return D + except AttributeError: + self.run_revised_simplex_method() + return self.final_revised_dictionary() + + def initial_dictionary(self): + r""" + Construct the initial dictionary of ``self``. + + The initial dictionary "defines" :meth:`slack_variables` in terms + of the :meth:`~LPProblem.decision_variables`, i.e. it has slack + variables as basic ones. + + OUTPUT: + + - a :class:`dictionary ` + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + """ + A, b, c, x = self.Abcx() + x = self._R.gens() + m, n = self.m(), self.n() + return LPDictionary(A, b, c, 0, x[-m:], x[-m-n:-m], self._objective) + + def inject_variables(self, scope=None, verbose=True): + r""" + Inject variables of ``self`` into ``scope``. + + INPUT: + + - ``scope`` -- namespace (default: global) + + - ``verbose`` -- if ``True`` (default), names of injected variables + will be printed + + OUTPUT: + + - none + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: P.inject_variables() + Defining x0, x1, x2, x3, x4 + sage: 3*x1 + x2 + x2 + 3*x1 + """ + if scope is None: + scope = get_main_globals() + try: + self._R.inject_variables(scope, verbose) + except AttributeError: + pass + + def revised_dictionary(self, *x_B): + r""" + Construct a revised dictionary for ``self``. + + INPUT: + + - basic variables for the dictionary to be constructed; if not given, + :meth:`slack_variables` will be used, perhaps with the + :meth:`auxiliary_variable` to give a feasible dictionary + + OUTPUT: + + - a :class:`revised dictionary ` + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary("x1", "x2") + sage: D.basic_variables() + (x1, x2) + + If basic variables are not given the initial dictionary is + constructed:: + + sage: P.revised_dictionary().basic_variables() + (x3, x4) + sage: P.initial_dictionary().basic_variables() + (x3, x4) + + Unless it is infeasible, in which case a feasible dictionary for the + auxiliary problem is constructed:: + + sage: A = ([1, 1], [3, 1], [-1,-1]) + sage: b = (1000, 1500, -400) + sage: P = LPProblemStandardForm(A, b, c) + sage: P.initial_dictionary().is_feasible() + False + sage: P.revised_dictionary().basic_variables() + (x3, x4, x0) + """ + if not x_B: + x_B = list(self.slack_variables()) + bm = min(self.b()) + if bm < 0: + x_B[self.b().list().index(bm)] = self.auxiliary_variable() + return LPRevisedDictionary(self, x_B) + + def run_revised_simplex_method(self): + r""" + Apply the revised simplex method to solve ``self`` and show the steps. + + OUTPUT: + + - a string with `\LaTeX` code of intermediate dictionaries + + .. NOTE:: + + You can access the :meth:`final_revised_dictionary`, which can be + one of the following: + + - an optimal dictionary with the :meth:`auxiliary_variable` among + :meth:`~LPRevisedDictionary.basic_variables` and a non-zero + optimal value indicating + that ``self`` is infeasible; + + - a non-optimal dictionary that has marked entering + variable for which there is no choice of the leaving variable, + indicating that ``self`` is unbounded; + + - an optimal dictionary. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1], [-1, -1]) + sage: b = (1000, 1500, -400) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: P.run_revised_simplex_method() + \renewcommand{\arraystretch}{1.500000} + \begin{array}{l} + ... + \text{Entering: $x_{1}$. Leaving: $x_{0}$.}\\ + ... + \text{Entering: $x_{5}$. Leaving: $x_{4}$.}\\ + ... + \text{Entering: $x_{2}$. Leaving: $x_{3}$.}\\ + ... + \text{The optimal value: $6250$. An optimal solution: $\left(250,\,750\right)$.} + \end{array} + """ + result = [] + d = self.revised_dictionary() + while not d.is_optimal(): + entering, leaving = min(d.possible_simplex_method_steps()) + d.enter(entering) + if leaving: + leaving = min(leaving) + d.leave(leaving) + result.append(latex(d)) + result.append(r"\text{{Entering: ${}$. Leaving: ${}$.}}" + .format(latex(entering), latex(leaving))) + result.append("") + result.append( + r"B_\mathrm{new}^{-1} = E^{-1} B_\mathrm{old}^{-1} = " + + latex(d.E_inverse()) + latex(d.B_inverse())) + result.append("") + d.update() + else: + result.append(latex(d)) + result.append(r"\text{{The problem is unbounded in the " + r"${}$ direction.}}".format(latex(entering))) + break + if d.is_optimal(): + result.append(latex(d)) + if self.auxiliary_variable() in d.basic_variables(): + result.append(r"\text{The problem is infeasible.}") + else: + v = d.objective_value() + if self._is_negative: + v = - v + result.append((r"\text{{The optimal value: ${}$. " + "An optimal solution: ${}$.}}").format( + latex(v), latex(d.basic_solution()))) + self._final_revised_dictionary = d + return _assemble_arrayl(result, 1.5) + + def run_simplex_method(self): + r""" + Apply the simplex method to solve ``self`` and show the steps. + + OUTPUT: + + - a string with `\LaTeX` code of intermediate dictionaries + + .. NOTE:: + + You can access the :meth:`final_dictionary`, which can be one + of the following: + + - an optimal dictionary for the :meth:`auxiliary_problem` with a + non-zero optimal value indicating that ``self`` is infeasible; + + - a non-optimal dictionary for ``self`` that has marked entering + variable for which there is no choice of the leaving variable, + indicating that ``self`` is unbounded; + + - an optimal dictionary for ``self``. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1], [-1, -1]) + sage: b = (1000, 1500, -400) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: P.run_simplex_method() # not tested + + You should use the typeset mode as the command above generates long + `\LaTeX` code:: + + sage: print P.run_simplex_method() + \begin{gather*} + ... + \text{The initial dictionary is infeasible, solving auxiliary problem.}\displaybreak[0]\\ + ... + \text{Entering: $x_{0}$. Leaving: $x_{5}$.}\displaybreak[0]\\ + ... + \text{Entering: $x_{1}$. Leaving: $x_{0}$.}\displaybreak[0]\\ + ... + \text{Back to the original problem.}\displaybreak[0]\\ + ... + \text{Entering: $x_{5}$. Leaving: $x_{4}$.}\displaybreak[0]\\ + ... + \text{Entering: $x_{2}$. Leaving: $x_{3}$.}\displaybreak[0]\\ + ... + \text{The optimal value: $6250$. An optimal solution: $\left(250,\,750\right)$.} + \end{gather*} + """ + result = [] + d = self.initial_dictionary() + result.append(latex(d)) + + def step(entering, leaving): + result.append(r"\text{{Entering: ${}$. Leaving: ${}$.}}" + .format(latex(entering), latex(leaving))) + result.append(d.ELLUL(entering, leaving)) + + if d.is_feasible(): + is_feasible = True + else: + result.append(r"\text{The initial dictionary is infeasible, " + "solving auxiliary problem.}") + d = self.auxiliary_problem().initial_dictionary() + result.append(latex(d)) + x0 = self.auxiliary_variable() + _, leaving = min(zip(d.constant_terms(), d.basic_variables())) + step(x0, leaving) + # while not d.is_optimal(): + # either optimality check should handle rounding errors or + while not (d.is_optimal() or x0 in d.nonbasic_variables()): + entering, leaving = min(d.possible_simplex_method_steps()) + step(entering, min(leaving)) + is_feasible = x0 in d.nonbasic_variables() + if is_feasible: + result.append(r"\text{Back to the original problem.}") + d = self.feasible_dictionary(d) + result.append(latex(d)) + else: + result.append(r"\text{The original problem is infeasible.}") + if is_feasible: + while not d.is_optimal(): + entering, leaving = min(d.possible_simplex_method_steps()) + if leaving: + step(entering, min(leaving)) + else: + d.enter(entering) + result.append(r"\text{{The problem is unbounded in the " + r"${}$ direction.}}".format(latex(entering))) + result.append(latex(d)) + break + if d.is_optimal(): + v = d.objective_value() + if self._is_negative: + v = - v + result.append((r"\text{{The optimal value: ${}$. " + "An optimal solution: ${}$.}}").format( + latex(v), latex(d.basic_solution()))) + self._final_dictionary = d + if generate_real_LaTeX: + # This will have to be used via \sagestr, as line&display breaks + # don't get along with reference substitution without wrapping. + return ("\\begin{gather*}\n\\allowdisplaybreaks\n" + + "\\displaybreak[0]\\\\\n".join(result) + + "\n\\end{gather*}") + return _assemble_arrayl(result, 1.5) + + def slack_variables(self): + r""" + Return slack variables of ``self``. + + Slack variables are differences between the constant terms and + left hand sides of the constraints. + + If you want to give custom names to slack variables, you have to do so + during construction of the problem. + + OUTPUT: + + - a tuple + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: P.slack_variables() + (x3, x4) + sage: P = LPProblemStandardForm(A, b, c, ["C", "B"], + ....: slack_variables=["L", "F"]) + sage: P.slack_variables() + (L, F) + """ + return self._R.gens()[-self.m():] + + +class LPAbstractDictionary(SageObject): + r""" + Abstract base class for dictionaries for LP problems. + + Instantiating this class directly is meaningless, see :class:`LPDictionary` + and :class:`LPRevisedDictionary` for useful extensions. + """ + + def __init__(self): + r""" + Initialize internal fields for entering and leaving variables. + + TESTS:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() # indirect doctest + """ + super(LPAbstractDictionary, self).__init__() + self._entering = None + self._leaving = None + + def _repr_(self): + r""" + Return a string representation of ``self``. + + OUTPUT: + + - a string + + TESTS:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: print D._repr_() + LP problem dictionary (use typeset mode to see details) + sage: D = P.revised_dictionary() + sage: print D._repr_() + LP problem dictionary (use typeset mode to see details) + """ + return "LP problem dictionary (use typeset mode to see details)" + + def base_ring(self): + r""" + Return the base ring of ``self``, i.e. the ring of coefficients. + + OUTPUT: + + - a ring + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.base_ring() + Rational Field + sage: D = P.revised_dictionary() + sage: D.base_ring() + Rational Field + """ + return self.coordinate_ring().base_ring() + + def basic_solution(self, include_slack_variables=False): + r""" + Return the basic solution of ``self``. + + The basic solution associated to a dictionary is obtained by setting to + zero all :meth:`~LPDictionary.nonbasic_variables`, in which case + :meth:`~LPDictionary.basic_variables` have to be equal to + :meth:`~LPDictionary.constant_terms` in equations. + It may refer to values of :meth:`~LPProblem.decision_variables` only or + include :meth:`~LPProblemStandardForm.slack_variables` as well. + + INPUT: + + - ``include_slack_variables`` -- (default: ``False``) if ``True``, + values of slack variables will be appended at the end + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.basic_solution() + (0, 0) + sage: D.basic_solution(True) + (0, 0, 1000, 1500) + sage: D = P.revised_dictionary() + sage: D.basic_solution() + (0, 0) + sage: D.basic_solution(True) + (0, 0, 1000, 1500) + """ + vv = zip(self.basic_variables(), self.constant_terms()) + N = self.nonbasic_variables() + vv += [(v, 0) for v in N] + vv.sort() # We use neglex order + v = [value for _, value in vv] + return vector(self.base_ring(), + v if include_slack_variables else v[:len(N)]) + + def coordinate_ring(self): + r""" + Return the coordinate ring of ``self``. + + OUTPUT: + + - a polynomial ring in + :meth:`~LPProblemStandardForm.auxiliary_variable`, + :meth:`~LPProblem.decision_variables`, and + :meth:`~LPProblemStandardForm.slack_variables` of ``self`` over the + :meth:`base_ring` + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.coordinate_ring() + Multivariate Polynomial Ring in x0, x1, x2, x3, x4 + over Rational Field + sage: D = P.revised_dictionary() + sage: D.coordinate_ring() + Multivariate Polynomial Ring in x0, x1, x2, x3, x4 + over Rational Field + """ + return self.basic_variables()[0].parent() + + def dual_ratios(self): + r""" + Return ratios used to determine the entering variable based on leaving. + + OUTPUT: + + - A list of pairs `(r_j, x_j)` where `x_j` is a non-basic variable and + `r_j = c_j / a_{ij}` is the ratio of the objective coefficient `c_j` + to the coefficient `a_{ij}` of `x_j` in the relation for the leaving + variable `x_i`: + + .. MATH:: + + x_i = b_i - \cdots - a_{ij} x_j - \cdots. + + The order of pairs matches the order of + :meth:`~LPDictionary.nonbasic_variables`, + but only `x_j` with negative `a_{ij}` are considered. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1], [-1, -1]) + sage: b = (1000, 1500, -400) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.dictionary(2, 3, 5) + sage: D.leave(3) + sage: D.dual_ratios() + [(5/2, x1), (5, x4)] + sage: D = P.revised_dictionary(2, 3, 5) + sage: D.leave(3) + sage: D.dual_ratios() + [(5/2, x1), (5, x4)] + """ + return [(c / a, x) for c, a, x in zip(self.objective_coefficients(), + self.leaving_coefficients(), + self.nonbasic_variables()) if a < 0] + + def enter(self, v): + r""" + Set ``v`` as the entering variable of ``self``. + + INPUT: + + - ``v`` -- a non-basic variable of ``self``, can be given as a string, + an actual variable, or an integer interpreted as the index of a + variable + + OUTPUT: + + - none, but the selected variable will be used as entering by methods + that require an entering variable and the corresponding column + will be typeset in green + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.enter("x1") + + We can also use indices of variables:: + + sage: D.enter(1) + + Or variable names without quotes after injecting them:: + + sage: P.inject_variables() + Defining x0, x1, x2, x3, x4 + sage: D.enter(x1) + + The same works for revised dictionaries as well:: + + sage: D = P.revised_dictionary() + sage: D.enter(x1) + """ + v = variable(self.coordinate_ring(), v) + if v not in self.nonbasic_variables(): + raise ValueError("entering variable must be non-basic") + self._entering = v + + def is_dual_feasible(self): + r""" + Check if ``self`` is dual feasible. + + OUTPUT: + + - ``True`` if all :meth:`~LPDictionary.objective_coefficients` are + non-positive, ``False`` otherwise + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.is_dual_feasible() + False + sage: D = P.revised_dictionary() + sage: D.is_dual_feasible() + False + """ + return all(ci <= 0 for ci in self.objective_coefficients()) + + def is_feasible(self): + r""" + Check if ``self`` is feasible. + + OUTPUT: + + - ``True`` if all :meth:`~LPDictionary.constant_terms` are + non-negative,``False`` otherwise + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.is_feasible() + True + sage: D = P.revised_dictionary() + sage: D.is_feasible() + True + """ + return all(bi >= 0 for bi in self.constant_terms()) + + def is_optimal(self): + r""" + Check if ``self`` is optimal. + + OUTPUT: + + - ``True`` if ``self`` :meth:`is_feasible` and :meth:`is_dual_feasible` + (i.e. all :meth:`~LPDictionary.constant_terms` are non-negative and + all :meth:`~LPDictionary.objective_coefficients` are non-positive), + ``False`` otherwise. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.is_optimal() + False + sage: D = P.revised_dictionary() + sage: D.is_optimal() + False + sage: D = P.revised_dictionary(1, 2) + sage: D.is_optimal() + True + """ + return self.is_feasible() and self.is_dual_feasible() + + def leave(self, v): + r""" + Set ``v`` as the leaving variable of ``self``. + + INPUT: + + - ``v`` -- a basic variable of ``self``, can be given as a string, an + actual variable, or an integer interpreted as the index of a variable + + OUTPUT: + + - none, but the selected variable will be used as leaving by methods + that require a leaving variable and the corresponding row will be + typeset in red + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.leave("x4") + + We can also use indices of variables:: + + sage: D.leave(4) + + Or variable names without quotes after injecting them:: + + sage: P.inject_variables() + Defining x0, x1, x2, x3, x4 + sage: D.leave(x4) + + The same works for revised dictionaries as well:: + + sage: D = P.revised_dictionary() + sage: D.leave(x4) + """ + v = variable(self.coordinate_ring(), v) + if v not in self.basic_variables(): + raise ValueError("leaving variable must be basic") + self._leaving = v + + def possible_dual_simplex_method_steps(self): + r""" + Return possible dual simplex method steps for ``self``. + + OUTPUT: + + - A list of pairs ``(leaving, entering)``, where ``leaving`` is a + basic variable that may :meth:`leave` and ``entering`` is a list of + non-basic variables that may :meth:`enter` when ``leaving`` leaves. + Note that ``entering`` may be empty, indicating that the problem is + infeasible (since the dual one is unbounded). + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.dictionary(2, 3) + sage: D.possible_dual_simplex_method_steps() + [(x3, [x1])] + sage: D = P.revised_dictionary(2, 3) + sage: D.possible_dual_simplex_method_steps() + [(x3, [x1])] + """ + if not self.is_dual_feasible(): + raise ValueError("dual simplex method steps are applicable to " + "dual feasible dictionaries only") + steps = [] + old_entering = self._entering + self._entering = None + old_leaving = self._leaving + for l in self.possible_leaving(): + self.leave(l) + steps.append((l, self.possible_entering())) + self._entering = old_entering + self._leaving = old_leaving + return steps + + def possible_entering(self): + r""" + Return possible entering variables for ``self``. + + OUTPUT: + + - a list of non-basic variables of ``self`` that can :meth:`enter` on + the next step of the (dual) simplex method + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.possible_entering() + [x1, x2] + sage: D = P.revised_dictionary() + sage: D.possible_entering() + [x1, x2] + """ + if self.is_dual_feasible() and self._leaving is not None: + ratios = self.dual_ratios() + if not ratios: + return [] + min_ratio = min(ratios)[0] + return [v for r, v in ratios if r == min_ratio] + if self.is_feasible(): + return [v for c, v in zip(self.objective_coefficients(), + self.nonbasic_variables()) if c > 0] + raise ValueError("entering variables can be determined for feasible " + "dictionaries or for dual feasible dictionaries " + "with a set leaving variable") + + def possible_leaving(self): + r""" + Return possible leaving variables for ``self``. + + OUTPUT: + + - a list of basic variables of ``self`` that can :meth:`leave` on + the next step of the (dual) simplex method + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.enter(1) + sage: D.possible_leaving() + [x4] + sage: D = P.revised_dictionary() + sage: D.enter(1) + sage: D.possible_leaving() + [x4] + """ + if self.is_feasible() and self._entering is not None: + ratios = self.ratios() + if not ratios: + return [] + min_ratio = min(ratios)[0] + return [v for r, v in ratios if r == min_ratio] + if self.is_dual_feasible(): + return [v for b, v in zip(self.constant_terms(), + self.basic_variables()) if b < 0] + raise ValueError("leaving variables can be determined for feasible " + "dictionaries with a set entering variable " + "or for dual feasible dictionaries") + + def possible_simplex_method_steps(self): + r""" + Return possible simplex method steps for ``self``. + + OUTPUT: + + - A list of pairs ``(entering, leaving)``, where ``entering`` is a + non-basic variable that may :meth:`enter` and ``leaving`` is a list + of basic variables that may :meth:`leave` when ``entering`` enters. + Note that ``leaving`` may be empty, indicating that the problem is + unbounded. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.possible_simplex_method_steps() + [(x1, [x4]), (x2, [x3])] + sage: D = P.revised_dictionary() + sage: D.possible_simplex_method_steps() + [(x1, [x4]), (x2, [x3])] + """ + if not self.is_feasible(): + raise ValueError("simplex method steps are applicable to feasible " + "dictionaries only") + steps = [] + old_entering = self._entering + old_leaving = self._leaving + self._leaving = None + for e in self.possible_entering(): + self.enter(e) + steps.append((e, self.possible_leaving())) + self._entering = old_entering + self._leaving = old_leaving + return steps + + def ratios(self): + r""" + Return ratios used to determine the leaving variable based on entering. + + OUTPUT: + + - A list of pairs `(r_i, x_i)` where `x_i` is a basic variable and + `r_i = b_i / a_{ik}` is the ratio of the constant term `b_i` to the + coefficient `a_{ik}` of the entering variable `x_k` in the relation + for `x_i`: + + .. MATH:: + + x_i = b_i - \cdots - a_{ik} x_k - \cdots. + + The order of pairs matches the order of + :meth:`~LPDictionary.basic_variables`, + but only `x_i` with positive `a_{ik}` are considered. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.enter(1) + sage: D.ratios() + [(1000, x3), (500, x4)] + sage: D = P.revised_dictionary() + sage: D.enter(1) + sage: D.ratios() + [(1000, x3), (500, x4)] + """ + return [(b / a, x) for b, a, x in zip(self.constant_terms(), + self.entering_coefficients(), + self.basic_variables()) if a > 0] + + +class LPDictionary(LPAbstractDictionary): + r""" + Construct a dictionary for an LP problem. + + A dictionary consists of the following data: + + .. MATH:: + + \begin{array}{|l|} + \hline + x_B = b - A x_N\\ + \hline + z = z^* + c x_N\\ + \hline + \end{array} + + INPUT: + + - ``A`` -- a matrix of relation coefficients + + - ``b`` -- a vector of relation constant terms + + - ``c`` -- a vector of objective coefficients + + - ``objective_value`` -- current value of the objective `z^*` + + - ``basic_variables`` -- a list of basic variables `x_B` + + - ``nonbasic_variables`` -- a list of non-basic variables `x_N` + + - ``objective_variable`` -- an objective variable `z` + + OUTPUT: + + - a :class:`dictionary for an LP problem ` + + .. NOTE:: + + This constructor does not check correctness of input, as it is + intended to be used internally by :class:`LPProblemStandardForm`. + + EXAMPLES: + + The intended way to use this class is indirect:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D + LP problem dictionary (use typeset mode to see details) + + But if you want you can create a dictionary without starting with an LP + problem, here is construction of the same dictionary as above:: + + sage: A = matrix(QQ, ([1, 1], [3, 1])) + sage: b = vector(QQ, (1000, 1500)) + sage: c = vector(QQ, (10, 5)) + sage: R = PolynomialRing(QQ, "x1, x2, x3, x4", order="neglex") + sage: from sage.numerical.interactive_simplex_method \ + ....: import LPDictionary + sage: D2 = LPDictionary(A, b, c, 0, R.gens()[2:], R.gens()[:2], "z") + sage: D2 == D + True + """ + + def __init__(self, A, b, c, objective_value, + basic_variables, nonbasic_variables, objective_variable): + r""" + See :class:`LPDictionary` for documentation. + + TESTS::: + + sage: A = matrix(QQ, ([1, 1], [3, 1])) + sage: b = vector(QQ, (1000, 1500)) + sage: c = vector(QQ, (10, 5)) + sage: R = PolynomialRing(QQ, "x1, x2, x3, x4", order="neglex") + sage: from sage.numerical.interactive_simplex_method \ + ....: import LPDictionary + sage: D = LPDictionary(A, b, c, 0, R.gens()[2:], R.gens()[:2], "z") + sage: TestSuite(D).run() + """ + super(LPDictionary, self).__init__() + # We are going to change stuff while LPProblem has immutable data. + A = copy(A) + b = copy(b) + c = copy(c) + B = vector(basic_variables) + N = vector(nonbasic_variables) + self._AbcvBNz = [A, b, c, objective_value, B, N, SR(objective_variable)] + + def __eq__(self, other): + r""" + Check if two LP problem dictionaries are equal. + + INPUT: + + - ``other`` -- anything + + OUTPUT: + + - ``True`` if ``other`` is an :class:`LPDictionary` with all + details the same as ``self``, ``False`` otherwise. + + TESTS:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + + sage: A = matrix(QQ, ([1, 1], [3, 1])) + sage: b = vector(QQ, (1000, 1500)) + sage: c = vector(QQ, (10, 5)) + sage: R = PolynomialRing(QQ, "x1, x2, x3, x4", order="neglex") + sage: from sage.numerical.interactive_simplex_method \ + ....: import LPDictionary + sage: D2 = LPDictionary(A, b, c, 0, R.gens()[2:], R.gens()[:2], "z") + sage: D2 == D + True + + sage: D3 = LPDictionary(A, b, c, 0, R.gens()[2:], R.gens()[:2], "w") + sage: D2 == D3 + False + """ + return (isinstance(other, LPDictionary) and + self._AbcvBNz == other._AbcvBNz) + + def _latex_(self): + r""" + Return a LaTeX representation of ``self``. + + OUTPUT: + + - a string + + TESTS:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: print D._latex_() + \renewcommand{\arraystretch}{1.5} \setlength{\arraycolsep}{0.125em} + \begin{array}{|rcrcrcr|} + \hline + x_{3} & = & 1000 & - & x_{1} & - & x_{2}\\ + x_{4} & = & 1500 & - & 3 x_{1} & - & x_{2}\\ + \hline + z & = & 0 & + & 10 x_{1} & + & 5 x_{2}\\ + \hline + \end{array} + """ + A, b, c, v, B, N, z = self._AbcvBNz + lines = [] + lines.append(r"\renewcommand{\arraystretch}{1.5}") + if generate_real_LaTeX: + lines[-1] += r" \setlength{\arraycolsep}{0.125em}" +# else: +# lines[-1] += r"\require{color}" + lines.append(r"\begin{array}{|rcr%s|}" % ("cr"*len(N))) + lines.append(r"\hline") + for xi, bi, Ai in zip(B, b, A.rows()): + lines.append(_latex_product(-Ai,N, head=[xi, "=", bi], + drop_plus=False, allow_empty=True) + r"\\") + lines.append(r"\hline") + lines.append(_latex_product(c, N, head=[z, "=", v], + drop_plus=False, allow_empty=True) + r"\\") + lines.append(r"\hline") + lines.append(r"\end{array}") + latex.add_package_to_preamble_if_available("color") + if self._entering is not None: + # Highlight the entering variable column + e = 2 * tuple(N).index(self._entering) + 4 + for i, line in enumerate(lines): + line = line.split("&") + if len(line) > 1: + if not generate_real_LaTeX: + line[e] = ("{" + line[e] + "}").replace(r"\\}", r"}\\") + line[e] = r"\color{green}" + line[e] + lines[i] = "&".join(line) + if self._leaving is not None: + # Highlight the leaving variable row + l = tuple(B).index(self._leaving) + 3 + line = lines[l].split("&") + for i, term in enumerate(line): + if not generate_real_LaTeX: + term = ("{" + term + "}").replace(r"\\}", r"}\\") + line[i] = r"\color{red}" + term + line = "&".join(line) + if generate_real_LaTeX: + line = line.replace(r"\color{red}\color{green}", + r"\color{blue}") + else: + line = line.replace(r"\color{red}{\color{green}", + r"\color{blue}{") + lines[l] = line + return "\n".join(lines) + + def ELLUL(self, entering, leaving): + r""" + Perform the Enter-Leave-LaTeX-Update-LaTeX step sequence on ``self``. + + INPUT: + + - ``entering`` -- the entering variable + + - ``leaving`` -- the leaving variable + + OUTPUT: + + - a string with LaTeX code for ``self`` before and after update + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.ELLUL("x1", "x4") + \renewcommand{\arraystretch}{1.5} \setlength{\arraycolsep}{0.125em} + \begin{array}{|rcrcrcr|} + \hline + x_{3} & = & 1000 & - &\color{green} x_{1} & - & x_{2}\\ + \color{red}x_{4} &\color{red} = &\color{red} 1500 &\color{red} - &\color{blue} 3 x_{1} &\color{red} - &\color{red} x_{2}\\ + \hline + z & = & 0 & + &\color{green} 10 x_{1} & + & 5 x_{2}\\ + \hline + \multicolumn{2}{c}{}\\[-3ex] + \hline + x_{3} & = & 500 & + & \frac{1}{3} x_{4} & - & \frac{2}{3} x_{2}\\ + x_{1} & = & 500 & - & \frac{1}{3} x_{4} & - & \frac{1}{3} x_{2}\\ + \hline + z & = & 5000 & - & \frac{10}{3} x_{4} & + & \frac{5}{3} x_{2}\\ + \hline + \end{array} + + This is how the above output looks when rendered: + + .. MATH:: + + \renewcommand{\arraystretch}{1.5} + \begin{array}{|rcrcrcr|} + \hline + x_{3} \!\!\!&\!\!\! = \!\!\!&\!\!\! 1000 \!\!\!&\!\!\! - \!\!\!&\color{green}{\!\!\! x_{1} \!\!\!}&\!\!\! - \!\!\!&\!\!\! x_{2}\\ + \color{red}{x_{4} \!\!\!}&\color{red}{\!\!\! = \!\!\!}&\color{red}{\!\!\! 1500 \!\!\!}&\color{red}{\!\!\! - \!\!\!}&\color{blue}{{\!\!\! 3 x_{1} \!\!\!}}&\color{red}{\!\!\! - \!\!\!}&\color{red}{\!\!\! x_{2}}\\ + \hline + z \!\!\!&\!\!\! = \!\!\!&\!\!\! 0 \!\!\!&\!\!\! + \!\!\!&\color{green}{\!\!\! 10 x_{1} \!\!\!}&\!\!\! + \!\!\!&\!\!\! 5 x_{2}\\ + \hline + \\ + \hline + x_{3} \!\!\!&\!\!\! = \!\!\!&\!\!\! 500 \!\!\!&\!\!\! + \!\!\!&\!\!\! \frac{1}{3} x_{4} \!\!\!&\!\!\! - \!\!\!&\!\!\! \frac{2}{3} x_{2}\\ + x_{1} \!\!\!&\!\!\! = \!\!\!&\!\!\! 500 \!\!\!&\!\!\! - \!\!\!&\!\!\! \frac{1}{3} x_{4} \!\!\!&\!\!\! - \!\!\!&\!\!\! \frac{1}{3} x_{2}\\ + \hline + z \!\!\!&\!\!\! = \!\!\!&\!\!\! 5000 \!\!\!&\!\!\! - \!\!\!&\!\!\! \frac{10}{3} x_{4} \!\!\!&\!\!\! + \!\!\!&\!\!\! \frac{5}{3} x_{2}\\ + \hline + \end{array} + + The column of the entering variable is green, while the row of the + leaving variable is red in the original dictionary state on the top. + The new state after the update step is shown on the bottom. + """ + self.enter(entering) + self.leave(leaving) + result = latex(self).rsplit("\n", 1)[0] # Remove \end{array} + # Make an empty line in the array + if generate_real_LaTeX: + result += "\n" r"\multicolumn{2}{c}{}\\[-3ex]" "\n" + else: + result += "\n\\\\\n" + self.update() + result += latex(self).split("\n", 2)[2] # Remove array header + return LatexExpr(result) + + def basic_variables(self): + r""" + Return the basic variables of ``self``. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.basic_variables() + (x3, x4) + """ + return self._AbcvBNz[4] + + def constant_terms(self): + r""" + Return the constant terms of relations of ``self``. + + OUTPUT: + + - a vector. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.constant_terms() + (1000, 1500) + """ + return self._AbcvBNz[1] + + def entering_coefficients(self): + r""" + Return coefficients of the entering variable. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.enter(1) + sage: D.entering_coefficients() + (1, 3) + """ + if self._entering is None: + raise ValueError("entering variable must be chosen to compute " + "its coefficients") + k = tuple(self.nonbasic_variables()).index(self._entering) + return self._AbcvBNz[0].column(k) + + def leaving_coefficients(self): + r""" + Return coefficients of the leaving variable. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.dictionary(2, 3) + sage: D.leave(3) + sage: D.leaving_coefficients() + (-2, -1) + """ + if self._leaving is None: + raise ValueError("leaving variable must be chosen to compute " + "its coefficients") + i = tuple(self.basic_variables()).index(self._leaving) + return self._AbcvBNz[0][i] + + def nonbasic_variables(self): + r""" + Return non-basic variables of ``self``. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.nonbasic_variables() + (x1, x2) + """ + return self._AbcvBNz[5] + + def objective_coefficients(self): + r""" + Return coefficients of the objective of ``self``. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.objective_coefficients() + (10, 5) + """ + return self._AbcvBNz[2] + + def objective_value(self): + r""" + Return the value of the objective at the + :meth:`~LPAbstractDictionary.basic_solution` of ``self``. + + OUTPUT: + + - a number + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.objective_value() + 0 + """ + return self._AbcvBNz[3] + + def update(self): + r""" + Update ``self`` using previously set entering and leaving variables. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.initial_dictionary() + sage: D.objective_value() + 0 + sage: D.enter("x1") + sage: D.leave("x4") + sage: D.update() + sage: D.objective_value() + 5000 + """ + A, b, c, v, B, N, z = self._AbcvBNz + entering = self._entering + if entering is None: + raise ValueError("entering variable must be set before updating") + leaving = self._leaving + if leaving is None: + raise ValueError("leaving variable must be set before updating") + l = tuple(B).index(leaving) + e = tuple(N).index(entering) + Ale = A[l, e] + if Ale == 0: + raise ValueError("incompatible choice of entering and leaving " + "variables") + # Variables + B[l] = entering + N[e] = leaving + # "The Changing Relation" + b[l] /= Ale + A[l] /= Ale + A[l, e] = 1 / Ale + # Other relations + for i in range(A.nrows()): + if i != l: + Aie = A[i, e] + A[i, e] = 0 + b[i] -= Aie * b[l] + A[i] -= Aie * A[l] + # Objective + ce = c[e] + c[e] = 0 + self._AbcvBNz[2] = c - ce * A[l] + self._AbcvBNz[3] += ce * b[l] + self._entering = None + self._leaving = None + + +def random_dictionary(m, n, bound=5, special_probability=0.2): + r""" + Construct a random dictionary. + + INPUT: + + - ``m`` -- the number of constraints/basic variables + + - ``n`` -- the number of decision/non-basic variables + + - ``bound`` -- (default: 5) a bound on dictionary entries + + - ``special_probability`` -- (default: 0.2) probability of constructing a + potentially infeasible or potentially optimal dictionary + + OUTPUT: + + - an :class:`LP problem dictionary ` + + EXAMPLES:: + + sage: from sage.numerical.interactive_simplex_method \ + ....: import random_dictionary + sage: random_dictionary(3, 4) + LP problem dictionary (use typeset mode to see details) + """ + A = random_matrix(ZZ, m, n, x=-bound, y=bound).change_ring(QQ) + if special_probability < random(): + b = random_vector(ZZ, m, x=0, y=bound).change_ring(QQ) + else: # Allow infeasible dictionary + b = random_vector(ZZ, m, x=-bound, y=bound).change_ring(QQ) + if special_probability < random(): + c = random_vector(ZZ, n, x=-bound, y=bound).change_ring(QQ) + else: # Make dual feasible dictionary + c = random_vector(ZZ, n, x=-bound, y=0).change_ring(QQ) + x_N = list(PolynomialRing(QQ, "x", m + n + 1, order="neglex").gens()) + x_N.pop(0) + x_B = [] + for i in range(m): + x_B.append(x_N.pop(randint(0, n + m - i - 1))) + return LPDictionary(A, b, c, randint(-bound, bound), x_B, x_N, "z") + + +class LPRevisedDictionary(LPAbstractDictionary): + r""" + Construct a revised dictionary for an LP problem. + + INPUT: + + - ``problem`` -- an :class:`LP problem in standard form + ` + + - ``basic_variables`` -- a list of basic variables or their indices + + OUTPUT: + + - a :class:`revised dictionary for an LP problem ` + + A revised dictionary encodes the same relations as a + :class:`regular dictionary `, but stores only what is + "necessary to efficiently compute data for the simplex method". + + Let the original problem be + + .. MATH:: + + \begin{array}{l} + \pm \max cx \\ + Ax \leq b \\ + x \geq 0 + \end{array} + + Let `\bar{x}` be the vector of :meth:`~LPProblem.decision_variables` `x` + followed by the :meth:`~LPProblemStandardForm.slack_variables`. + Let `\bar{c}` be the vector of :meth:`~LPProblem.objective_coefficients` `c` + followed by zeroes for all slack variables. + Let `\bar{A} = (A | I)` be the matrix of + :meth:`~LPProblem.constraint_coefficients` `A` augmented by the identity + matrix as columns corresponding to the slack variables. Then the problem + above can be written as + + .. MATH:: + + \begin{array}{l} + \pm \max \bar{c} \bar{x} \\ + \bar{A} \bar{x} = b \\ + \bar{x} \geq 0 + \end{array} + + and any dictionary is a system of equations equivalent to + `\bar{A} \bar{x} = b`, but resolved for :meth:`basic_variables` `x_B` in + terms of :meth:`nonbasic_variables` `x_N` together with the expression for + the objective in terms of `x_N`. Let :meth:`c_B` and :meth:`c_N` be vectors + "splitting `\bar{c}` into basic and non-basic parts". Let :meth:`B` and + :meth:`A_N` be the splitting of `\bar{A}`. Then the corresponding dictionary + is + + .. MATH:: + + \begin{array}{|l|} + \hline + x_B = B^{-1} b - B^{-1} A_N x_N\\ + \hline + z = y b + \left(c_N - y^T A_N\right) x_N\\ + \hline + \end{array} + + where `y = c_B^T B^{-1}`. To proceed with the simplex method, it is not + necessary to compute all entries of this dictionary. On the other hand, any + entry is easy to compute, if you know `B^{-1}`, so we keep track of it + through the update steps. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: from sage.numerical.interactive_simplex_method \ + ....: import LPRevisedDictionary + sage: D = LPRevisedDictionary(P, [1, 2]) + sage: D.basic_variables() + (x1, x2) + sage: D + LP problem dictionary (use typeset mode to see details) + + The same dictionary can be constructed through the problem:: + + sage: P.revised_dictionary(1, 2) == D + True + + When this dictionary is typeset, you will see two tables like these ones: + + .. MATH:: + + \renewcommand{\arraystretch}{1.500000} + \begin{array}{l} + \begin{array}{l|r|rr||r||r} + x_B & c_B & & \!\!\!\!\!\!\!\! B^{-1} & y & B^{-1} b \\ + \hline + x_{1} & 10 & -\frac{1}{2} & \frac{1}{2} & \frac{5}{2} & 250 \\ + x_{2} & 5 & \frac{3}{2} & -\frac{1}{2} & \frac{5}{2} & 750 \\ + \end{array}\\ + \\ + \begin{array}{r|rr} + x_N & x_{3} & x_{4} \\ + \hline + c_N^T & 0 & 0 \\ + y^T A_N & \frac{5}{2} & \frac{5}{2} \\ + \hline + c_N^T - y^T A_N & -\frac{5}{2} & -\frac{5}{2} \\ + \end{array} + \end{array} + + More details will be shown if entering and leaving variables are set, but in + any case the top table shows `B^{-1}` and a few extra columns, while the + bottom one shows several rows: these are related to columns and rows of + dictionary entries. + """ + + def __init__(self, problem, basic_variables): + r""" + See :class:`LPRevisedDictionary` for documentation. + + TESTS::: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: from sage.numerical.interactive_simplex_method \ + ....: import LPRevisedDictionary + sage: D = LPRevisedDictionary(P, [1, 2]) + sage: TestSuite(D).run() + """ + if problem.auxiliary_variable() == problem.decision_variables()[0]: + raise ValueError("revised dictionaries should not be constructed " + "for auxiliary problems") + super(LPRevisedDictionary, self).__init__() + self._problem = problem + R = problem.coordinate_ring() + self._x_B = vector(R, [variable(R, v) for v in basic_variables]) + + def __eq__(self, other): + r""" + Check if two revised LP problem dictionaries are equal. + + INPUT: + + - ``other`` -- anything + + OUTPUT: + + - ``True`` if ``other`` is an :class:`LPRevisedDictionary` for the same + :class:`LPProblemStandardForm` with the same :meth:`basic_variables`, + ``False`` otherwise + + TESTS:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: from sage.numerical.interactive_simplex_method \ + ....: import LPRevisedDictionary + sage: D1 = LPRevisedDictionary(P, [1, 2]) + sage: D2 = LPRevisedDictionary(P, [1, 2]) + sage: D1 is D2 + False + sage: D1 == D2 + True + sage: D3 = LPRevisedDictionary(P, [2, 0]) + sage: D1 == D3 + False + """ + return (isinstance(other, LPRevisedDictionary) and + self._problem == other._problem and + self._x_B == other._x_B) + + def _latex_(self): + r""" + Return a LaTeX representation of ``self``. + + OUTPUT: + + - a string + + TESTS:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.enter(1) + sage: D.leave(3) + sage: print D._latex_() + \renewcommand{\arraystretch}{1.500000} + \begin{array}{l} + \begin{array}{l|r|rr||r||r|r|r} + x_B & c_B & \multicolumn{2}{c||}{B^{-1}} & y & B^{-1} b & B^{-1} A_{x_{1}} & \hbox{Ratio} \\ + \hline + \color{red} x_{3} & \color{red} 0 & \color{red} 1 & \color{red} 0 & 0 & \color{red} 1000 & \color{red} 1 & \color{red} 1000 \\ + x_{4} & 0 & 0 & 1 & 0 & 1500 & 3 & 500 \\ + \end{array}\\ + \\ + \begin{array}{r|rr} + x_N & \color{green} x_{1} & x_{2} \\ + \hline + c_N^T & \color{green} 10 & 5 \\ + \hline + y^T A_N & \color{green} 0 & 0 \\ + \hline + c_N^T - y^T A_N & \color{green} 10 & 5 \\ + \end{array} + \end{array} + """ + latex.add_package_to_preamble_if_available("color") + x_B = self._x_B + m = len(x_B) + entering = self._entering + leaving = self._leaving + show_ratios = entering is not None and self.is_feasible() + if leaving is not None: + l = x_B.list().index(leaving) + lines = [] + lines.append(r"\begin{array}{l|r|%s||r||r%s%s}" % ("r"*m, + "|r" if entering is not None else "", "|r" if show_ratios else "")) + headers = ["x_B", "c_B"] + if generate_real_LaTeX: + headers.append(r"\multicolumn{%d}{c||}{B^{-1}}" % m) + else: + headers.extend([""] * (m//2)) + headers.append(r"\!\!\!\!\!\!\!\! B^{-1}") + headers.extend([""] * ((m-1)//2)) + headers.extend(["y", "B^{-1} b"]) + if entering is not None: + headers.append("B^{-1} A_{%s}" % latex(entering)) + if show_ratios: + headers.append(r"\hbox{Ratio}") + lines.append(" & ".join(headers) + r" \\") + lines.append(r"\hline") + Bi = self.B_inverse() + c_B = self.c_B() + y = self.y() + Bib = self.constant_terms() + if entering is not None: + Biae = self.entering_coefficients() + if show_ratios: + ratios = self.ratios() + for i in range(m): + entries = [x_B[i], c_B[i]] + entries.extend(Bi.row(i)) + entries.extend([y[i], Bib[i]]) + if entering is not None: + entries.append(Biae[i]) + if show_ratios: + if ratios and ratios[0][1] == x_B[i]: + entries.append(ratios.pop(0)[0]) + terms = map(latex, entries) + if leaving is not None and i == l: + for j, t in enumerate(terms): + if j == m + 2: + continue + if not generate_real_LaTeX: + t = "{" + t + "}" + terms[j] = r"\color{red} " + t + lines.append(" & ".join(terms) + r" \\") + lines.append(r"\end{array}") + top = "\n".join(lines) + + def make_line(header, terms): + terms = map(latex, terms) + if entering is not None: + t = terms[k] + if not generate_real_LaTeX: + t = "{" + t + "}" + terms[k] = r"\color{green} " + t + lines.append(" & ".join([header] + terms) + r" \\") + + lines = [] + x_N = self.x_N() + if entering is not None: + k = x_N.list().index(entering) + lines.append(r"\begin{array}{r|" + "r" * len(x_N) + "}") + make_line("x_N", x_N) + lines.append(r"\hline") + make_line("c_N^T", self.c_N()) + lines.append(r"\hline") + make_line("y^T A_N", y * self.A_N()) + lines.append(r"\hline") + make_line("c_N^T - y^T A_N", self.objective_coefficients()) + if leaving is not None and self.is_dual_feasible(): + lines.append(r"\hline") + make_line("B^{-1}_{%s} A_N" % latex(leaving), + self.leaving_coefficients()) + lines.append(r"\hline") + ratios = self.dual_ratios() + make_line(r"\hbox{Ratio}", [ratios.pop(0)[0] + if ratios and ratios[0][1] == x else "" + for x in x_N]) + lines.append(r"\end{array}") + bottom = "\n".join(lines) + return _assemble_arrayl([top, "", bottom], 1.5) + + def A(self, v): + r""" + Return the column of constraint coefficients corresponding to ``v``. + + INPUT: + + - ``v`` -- a variable, its name, or its index + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.A(1) + (1, 3) + sage: D.A(0) + (-1, -1) + sage: D.A("x3") + (1, 0) + """ + P = self.problem() + R = P.coordinate_ring() + v = variable(R, v) + k = R.gens().index(v) + R = R.base_ring() + m, n = P.m(), P.n() + if k == 0: + return vector(R, [-1] * m) + elif k <= n: + return P.A().column(k - 1) + else: + return identity_matrix(R, m).column(k - n - 1) + + def A_N(self): + r""" + Return the `A_N` matrix, constraint coefficients of + non-basic variables. + + OUTPUT: + + - a matrix + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.A_N() + [1 1] + [3 1] + """ + return column_matrix(self.problem().base_ring(), + [self.A(x) for x in self.x_N()]) + + def B(self): + r""" + Return the `B` matrix, i.e. constraint coefficients of + basic variables. + + OUTPUT: + + - a matrix + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary(1, 2) + sage: D.B() + [1 1] + [3 1] + """ + return column_matrix(self.problem().base_ring(), + [self.A(x) for x in self._x_B]) + + def B_inverse(self): + r""" + Return the inverse of the :meth:`B` matrix. + + This inverse matrix is stored and computed during dictionary update in + a more efficient way than generic inversion. + + OUTPUT: + + - a matrix + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary(1, 2) + sage: D.B_inverse() + [-1/2 1/2] + [ 3/2 -1/2] + """ + try: + return self._B_inverse + except AttributeError: + self._B_inverse = self.B().inverse() + return self._B_inverse + + def E(self): + r""" + Return the eta matrix between ``self`` and the next dictionary. + + OUTPUT: + + - a matrix + + If `B_{\mathrm{old}}` is the current matrix `B` and `B_{\mathrm{new}}` + is the `B` matrix of the next dictionary (after the update step), then + `B_{\mathrm{new}} = B_{\mathrm{old}} E`. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.enter(1) + sage: D.leave(4) + sage: D.E() + [1 1] + [0 3] + """ + if self._entering is None: + raise ValueError("entering variable must be set to compute the " + "eta matrix") + leaving = self._leaving + if leaving is None: + raise ValueError("leaving variable must be set to compute the " + "eta matrix") + l = self._x_B.list().index(leaving) + E = identity_matrix(self.base_ring(), self.problem().m()) + E.set_column(l, self.entering_coefficients()) + return E + + def E_inverse(self): + r""" + Return the inverse of the matrix :meth:`E`. + + This inverse matrix is computed in a more efficient way than generic + inversion. + + OUTPUT: + + - a matrix + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.enter(1) + sage: D.leave(4) + sage: D.E_inverse() + [ 1 -1/3] + [ 0 1/3] + """ + E = self.E() + l = self._x_B.list().index(self._leaving) + d = E[l, l] + if d == 0: + raise ValueError("eta matrix is not invertible due to incompatible " + "choice of entering and leaving variables") + E.set_col_to_multiple_of_col(l, l, -1/d) + E[l, l] = 1 / d + return E + + def basic_indices(self): + r""" + Return the basic indices of ``self``. + + .. NOTE:: + + Basic indices are indices of :meth:`basic_variables` in the list of + generators of the :meth:`~LPProblemStandardForm.coordinate_ring` of + the :meth:`problem` of ``self``, they may not coincide with the + indices of variables which are parts of their names. (They will for + the default indexed names.) + + OUTPUT: + + - a list. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.basic_indices() + [3, 4] + """ + gens = self.coordinate_ring().gens() + return [gens.index(x) for x in self._x_B] + + def basic_variables(self): + r""" + Return the basic variables of ``self``. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.basic_variables() + (x3, x4) + """ + return vector(self._x_B[0].parent(), self._x_B) + + def c_B(self): + r""" + Return the `c_B` vector, objective coefficients of basic variables. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary(1, 2) + sage: D.c_B() + (10, 5) + """ + P = self.problem() + R = self.base_ring() + BB = self.basic_indices() + if 0 in BB: + c_B = vector(R, P.m()) + c_B[BB.index(0)] = -1 + return c_B + else: + c_D = P.c() + n = P.n() + return vector(R, [c_D[k - 1] if k <= n else 0 for k in BB]) + + def c_N(self): + r""" + Return the `c_N` vector, objective coefficients of non-basic variables. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.c_N() + (10, 5) + """ + P = self.problem() + n = P.n() + R = P.base_ring() + if 0 in self.basic_indices(): + return vector(R, n + 1) + else: + c_D = P.c() + return vector(R, (c_D[k - 1] if k <= n else 0 + for k in self.nonbasic_indices())) + + def constant_terms(self): + r""" + Return constant terms in the relations of ``self``. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.constant_terms() + (1000, 1500) + """ + return self.B_inverse() * self.problem().b() + + def dictionary(self): + r""" + Return a regular LP dictionary matching ``self``. + + OUTPUT: + + - an :class:`LP dictionary ` + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1], [-1, -1]) + sage: b = (1000, 1500, -400) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.dictionary() + LP problem dictionary (use typeset mode to see details) + """ + D = LPDictionary(self.B_inverse() * self.A_N(), + self.constant_terms(), + self.objective_coefficients(), + self.objective_value(), + self.basic_variables(), + self.nonbasic_variables(), + "z") + D._entering = self._entering + D._leaving = self._leaving + return D + + def entering_coefficients(self): + r""" + Return coefficients of the entering variable. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.enter(1) + sage: D.entering_coefficients() + (1, 3) + """ + if self._entering is None: + raise ValueError("entering variable must be chosen to compute " + "its coefficients") + return self.B_inverse() * self.A(self._entering) + + def leaving_coefficients(self): + r""" + Return coefficients of the leaving variable. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary(2, 3) + sage: D.leave(3) + sage: D.leaving_coefficients() + (-2, -1) + """ + if self._leaving is None: + raise ValueError("leaving variable must be chosen to compute " + "its coefficients") + i = self.basic_variables().list().index(self._leaving) + return self.B_inverse()[i] * self.A_N() + + def nonbasic_indices(self): + r""" + Return the non-basic indices of ``self``. + + .. NOTE:: + + Non-basic indices are indices of :meth:`nonbasic_variables` in the + list of generators of the + :meth:`~LPProblemStandardForm.coordinate_ring` of the + :meth:`problem` of ``self``, they may not coincide with the indices + of variables which are parts of their names. (They will for the + default indexed names.) + + OUTPUT: + + - a list + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.nonbasic_indices() + [1, 2] + """ + gens = self.coordinate_ring().gens() + return [gens.index(x) for x in self.x_N()] + + def nonbasic_variables(self): + r""" + Return non-basic variables of ``self``. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.nonbasic_variables() + (x1, x2) + """ + R = self.coordinate_ring() + return vector(R, [xi for xi in R.gens()[1:] if xi not in self._x_B]) + + def objective_coefficients(self): + r""" + Return coefficients of the objective of ``self``. + + OUTPUT: + + - a vector + + These are coefficients of non-basic variables when basic variables are + eliminated. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.objective_coefficients() + (10, 5) + """ + return self.c_N() - self.y() * self.A_N() + + def objective_value(self): + r""" + Return the value of the objective at the basic solution of ``self``. + + OUTPUT: + + - a number + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.objective_value() + 0 + """ + return self.y() * self.problem().b() + + def problem(self): + r""" + Return the original problem. + + OUTPUT: + + - an :class:`LP problem in standard form ` + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.problem() is P + True + """ + return self._problem + + def update(self): + r""" + Update ``self`` using previously set entering and leaving variables. + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.objective_value() + 0 + sage: D.enter("x1") + sage: D.leave("x4") + sage: D.update() + sage: D.objective_value() + 5000 + """ + # Update the inverse of B first, in case it is impossible + self._B_inverse = self.E_inverse() * self.B_inverse() + # Now update the rest and clear settings + self._x_B[self._x_B.list().index(self._leaving)] = self._entering + self._entering = None + self._leaving = None + + def y(self): + r""" + Return the `y` vector, the product of :meth:`c_B` and + :meth:`B_inverse`. + + OUTPUT: + + - a vector + + EXAMPLES:: + + sage: A = ([1, 1], [3, 1]) + sage: b = (1000, 1500) + sage: c = (10, 5) + sage: P = LPProblemStandardForm(A, b, c) + sage: D = P.revised_dictionary() + sage: D.y() + (0, 0) + """ + return self.c_B() * self.B_inverse() + + # Aliases for the standard notation + x_B = basic_variables + x_N = nonbasic_variables + diff --git a/src/sage/numerical/linear_functions.pyx b/src/sage/numerical/linear_functions.pyx index 6c531146704..0c691d320b0 100644 --- a/src/sage/numerical/linear_functions.pyx +++ b/src/sage/numerical/linear_functions.pyx @@ -8,7 +8,7 @@ either equalities or less-or-equal. For example:: sage: p = MixedIntegerLinearProgram() sage: x = p.new_variable() - doctest:839: DeprecationWarning: The default behaviour of new_variable() will soon change ! It will return 'real' variables instead of nonnegative ones. Please be explicit and call new_variable(nonnegative=True) instead. + doctest:...: DeprecationWarning: The default value of 'nonnegative' will change, to False instead of True. You should add the explicit 'nonnegative=True'. See http://trac.sagemath.org/15521 for details. sage: f = 1 + x[1] + 2*x[2]; f # a linear function 1 + x_0 + 2*x_1 diff --git a/src/sage/numerical/mip.pxd b/src/sage/numerical/mip.pxd index 1e9b35915f1..6296d4e4624 100644 --- a/src/sage/numerical/mip.pxd +++ b/src/sage/numerical/mip.pxd @@ -32,4 +32,6 @@ cdef class MIPVariable(SageObject): cdef int _vtype cdef char * _name cdef bint _hasname + cdef object _lower_bound + cdef object _upper_bound diff --git a/src/sage/numerical/mip.pyx b/src/sage/numerical/mip.pyx index e2d8150de99..6f20ea39c3c 100644 --- a/src/sage/numerical/mip.pyx +++ b/src/sage/numerical/mip.pyx @@ -53,7 +53,7 @@ A mixed integer linear program can give you an answer: The following example shows all these steps:: sage: p = MixedIntegerLinearProgram(maximization=False, solver = "GLPK") - sage: w = p.new_variable(integer=True) # all variables are non-negative by default + sage: w = p.new_variable(integer=True, nonnegative=True) sage: p.add_constraint(w[0] + w[1] + w[2] - 14*w[3] == 0) sage: p.add_constraint(w[1] + 2*w[2] - 8*w[3] == 0) sage: p.add_constraint(2*w[2] - 3*w[3] == 0) @@ -89,16 +89,14 @@ Different backends compute with different base fields, for example:: sage: p = MixedIntegerLinearProgram(solver = 'GLPK') sage: p.base_ring() Real Double Field - sage: x = p.new_variable() - doctest:839: DeprecationWarning: The default behaviour of new_variable() will soon change ! It will return 'real' variables instead of nonnegative ones. Please be explicit and call new_variable(nonnegative=True) instead. - See http://trac.sagemath.org/15521 for details. + sage: x = p.new_variable(real=True, nonnegative=True) sage: 0.5 + 3/2*x[1] 0.5 + 1.5*x_0 sage: p = MixedIntegerLinearProgram(solver = 'ppl') sage: p.base_ring() Rational Field - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) sage: 0.5 + 3/2*x[1] 1/2 + 3/2*x_0 @@ -172,7 +170,6 @@ from sage.structure.sage_object cimport SageObject from sage.misc.cachefunc import cached_method from sage.numerical.linear_functions import is_LinearFunction, is_LinearConstraint from sage.misc.superseded import deprecated_function_alias, deprecation -from sage.misc.superseded import deprecated_function_alias cdef class MixedIntegerLinearProgram(SageObject): r""" @@ -241,13 +238,34 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: g = graphs.PetersenGraph() sage: p = MixedIntegerLinearProgram(maximization=True) - sage: b = p.new_variable() + sage: b = p.new_variable(binary=True) sage: p.set_objective(sum([b[v] for v in g])) sage: for (u,v) in g.edges(labels=None): - ... p.add_constraint(b[u] + b[v], max=1) - sage: p.set_binary(b) + ....: p.add_constraint(b[u] + b[v], max=1) sage: p.solve(objective_only=True) 4.0 + + TESTS: + + Check that :trac:`16497` is fixed:: + + sage: from sage.numerical.mip import MixedIntegerLinearProgram + sage: for type in ["binary", "integer"]: + ....: k = 3 + ....: items = [1/5, 1/3, 2/3, 3/4, 5/7] + ....: maximum=1 + ....: p=MixedIntegerLinearProgram() + ....: box=p.new_variable(nonnegative=True, **{type:True}) + ....: for b in range(k): + ....: p.add_constraint(p.sum([items[i]*box[i,b] for i in range(len(items))]) <= maximum) + ....: for i in range(len(items)): + ....: p.add_constraint(p.sum([box[i,b] for b in range(k)]) == 1) + ....: p.set_objective(None) + ....: _ = p.solve() + ....: box=p.get_values(box) + ....: print(all(v in ZZ for v in box.values())) + True + True """ def __init__(self, solver=None, maximization=True, @@ -287,7 +305,7 @@ cdef class MixedIntegerLinearProgram(SageObject): - ``constraint_generation`` -- Only used when ``solver=None``. - - When set to ``True``, after solving the + - When set to ``True``, after solving the ``MixedIntegerLinearProgram``, it is possible to add a constraint, and then solve it again. The effect is that solvers that do not support this feature will not be used. @@ -337,6 +355,17 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: sum([1 for x in gc.get_objects() if isinstance(x,C)]) 0 sage: gc.enable() + + Right now the ``nonnegative`` argument is mandatory when a variable is + created, but later the default will change from nonnegative variables to + unbounded variables:: + + sage: p = MixedIntegerLinearProgram() + sage: v = p.new_variable(real=True) + doctest:...: DeprecationWarning: The default value of 'nonnegative' will change, to False instead of True. You should add the explicit 'nonnegative=True'. + See http://trac.sagemath.org/15521 for details. + sage: p.get_min(v[0]) + 0.0 """ self.__BINARY = 0 self.__REAL = -1 @@ -418,7 +447,7 @@ cdef class MixedIntegerLinearProgram(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) sage: p.add_constraint(v[1] + v[2], max=2) sage: print p Mixed Integer Program ( maximization, 2 variables, 1 constraints ) @@ -441,11 +470,12 @@ cdef class MixedIntegerLinearProgram(SageObject): EXAMPLE:: - sage: p = MixedIntegerLinearProgram() - sage: p.add_constraint(p[0] + p[1], max = 10) - sage: q = copy(p) - sage: q.number_of_constraints() - 1 + sage: p = MixedIntegerLinearProgram() + sage: v = p.new_variable(nonnegative=True) + sage: p.add_constraint(v[0] + v[1], max = 10) + sage: q = copy(p) + sage: q.number_of_constraints() + 1 """ cdef MixedIntegerLinearProgram p = \ MixedIntegerLinearProgram(solver="GLPK") @@ -531,7 +561,7 @@ cdef class MixedIntegerLinearProgram(SageObject): """ self._backend.problem_name(name) - def new_variable(self, real=False, nonnegative=False, binary=False, integer=False, dim=1,name=""): + def new_variable(self, real=False, binary=False, integer=False, nonnegative=None, dim=1,name=""): r""" Returns an instance of ``MIPVariable`` associated to the current instance of ``MixedIntegerLinearProgram``. @@ -539,27 +569,24 @@ cdef class MixedIntegerLinearProgram(SageObject): A new variable ``x`` is defined by:: sage: p = MixedIntegerLinearProgram() - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) It behaves exactly as an usual dictionary would. It can use any key argument you may like, as ``x[5]`` or ``x["b"]``, and has methods ``items()`` and ``keys()``. - .. WARNING:: - - By default, all ``x[i]`` are assumed to be non-negative. See - :meth:`set_min` to set a different lower bound. - INPUT: - ``dim`` (integer) -- Defines the dimension of the dictionary. If ``x`` has dimension `2`, its fields will be of the form ``x[key1][key2]``. Deprecated. - - ``binary, integer, nonnegative`` (boolean) -- Set one of these + - ``binary, integer, real`` (boolean) -- Set one of these arguments to ``True`` to ensure that the variable gets the - corresponding type. The default type is ``nonnegative``, which - represents nonnegative real variables. + corresponding type. + + - ``nonnegative`` (boolean) -- whether the variable should be assumed to + be nonnegative. Rather useless for the binary type. - ``name`` (string) -- Associates a name to the variable. This is only useful when exporting the linear program to a file using @@ -580,10 +607,8 @@ cdef class MixedIntegerLinearProgram(SageObject): To define two dictionaries of variables, the first being of real type, and the second of integer type :: - sage: x = p.new_variable(real=True) - doctest:839: DeprecationWarning: The meaning of 'real' will change, to represent real variables instead of nonnegative ones. Please use the new 'nonnegative' variable type. - See http://trac.sagemath.org/15521 for details. - sage: y = p.new_variable(integer=True) + sage: x = p.new_variable(real=True, nonnegative=True) + sage: y = p.new_variable(integer=True, nonnegative=True) sage: p.add_constraint(x[2] + y[3,5], max=2) sage: p.is_integer(x[2]) False @@ -597,37 +622,56 @@ cdef class MixedIntegerLinearProgram(SageObject): ... ValueError: Exactly one of the available types has to be True + Unbounded variables:: + + sage: p = MixedIntegerLinearProgram() + sage: x = p.new_variable(real=True, nonnegative=False) + sage: y = p.new_variable(integer=True, nonnegative=False) + sage: p.add_constraint(x[0]+x[3] <= 8) + sage: p.add_constraint(y[0] >= y[1]) + sage: p.show() + Maximization: + + Constraints: + x_0 + x_1 <= 8.0 + - x_2 + x_3 <= 0.0 + Variables: + x_0 is a continuous variable (min=-oo, max=+oo) + x_1 is a continuous variable (min=-oo, max=+oo) + x_2 is an integer variable (min=-oo, max=+oo) + x_3 is an integer variable (min=-oo, max=+oo) + TESTS: Default behaviour (:trac:`15521`):: - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) sage: p.get_min(x[0]) 0.0 """ - if sum([real, binary, integer]) >= 2: + if sum([real, binary, integer]) > 1: raise ValueError("Exactly one of the available types has to be True") + if nonnegative is None: + if not binary: + deprecation(15521, "The default value of 'nonnegative' will change, to "+ + "False instead of True. You should add the explicit "+ + "'nonnegative=True'.") + nonnegative=True + if binary: vtype = self.__BINARY elif integer: vtype = self.__INTEGER - elif real: - deprecation(15521, "The meaning of 'real' will change, to "+ - "represent real variables instead of nonnegative "+ - "ones. Please use the new 'nonnegative' variable type.") - vtype = self.__REAL - elif nonnegative: - vtype = self.__REAL else: - deprecation(15521, "The default behaviour of new_variable() will "+ - "soon change ! It will return 'real' variables instead "+ - "of nonnegative ones. Please be explicit and call "+ - "new_variable(nonnegative=True) instead.") vtype = self.__REAL - v=MIPVariable(self, vtype, dim=dim,name=name) - return v + return MIPVariable(self, + vtype, + dim=dim, + name=name, + lower_bound=0 if (nonnegative or binary) else None, + upper_bound=1 if binary else None) cpdef int number_of_constraints(self): r""" @@ -942,7 +986,7 @@ cdef class MixedIntegerLinearProgram(SageObject): Without any names :: sage: p = MixedIntegerLinearProgram(solver = "GLPK") - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) sage: p.set_objective(x[1] + x[2]) sage: p.add_constraint(-3*x[1] + 2*x[2], max=2) sage: p.show() @@ -957,7 +1001,7 @@ cdef class MixedIntegerLinearProgram(SageObject): With `\QQ` coefficients:: sage: p = MixedIntegerLinearProgram(solver= 'ppl') - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) sage: p.set_objective(x[1] + 1/2*x[2]) sage: p.add_constraint(-3/5*x[1] + 2/7*x[2], max=2/5) sage: p.show() @@ -1081,7 +1125,7 @@ cdef class MixedIntegerLinearProgram(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram(solver="GLPK") - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) sage: p.set_objective(x[1] + x[2]) sage: p.add_constraint(-3*x[1] + 2*x[2], max=2,name="OneConstraint") sage: p.write_mps(os.path.join(SAGE_TMP, "lp_problem.mps")) @@ -1107,7 +1151,7 @@ cdef class MixedIntegerLinearProgram(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram(solver="GLPK") - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) sage: p.set_objective(x[1] + x[2]) sage: p.add_constraint(-3*x[1] + 2*x[2], max=2) sage: p.write_lp(os.path.join(SAGE_TMP, "lp_problem.lp")) @@ -1145,8 +1189,8 @@ cdef class MixedIntegerLinearProgram(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: x = p.new_variable() - sage: y = p.new_variable() + sage: x = p.new_variable(nonnegative=True) + sage: y = p.new_variable(nonnegative=True) sage: p.set_objective(x[3] + 3*y[2,9] + x[5]) sage: p.add_constraint(x[3] + y[2,9] + 2*x[5], max=2) sage: p.solve() @@ -1183,7 +1227,7 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: p = MixedIntegerLinearProgram() sage: b = p.new_variable(dim=2) - doctest:839: DeprecationWarning: The 'dim' argument will soon disappear. Fortunately variable[1,2] is easier to use than variable[1][2] + doctest:...: DeprecationWarning: The 'dim' argument will soon disappear. Fortunately variable[1,2] is easier to use than variable[1][2] See http://trac.sagemath.org/15489 for details. sage: p.add_constraint(b[1][2] + b[2][3] == 0) sage: _ = p.solve() @@ -1245,7 +1289,7 @@ cdef class MixedIntegerLinearProgram(SageObject): This linear program can be solved as follows:: sage: p = MixedIntegerLinearProgram(maximization=True) - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) sage: p.set_objective(x[1] + 5*x[2]) sage: p.add_constraint(x[1] + 2/10*x[2], max=4) sage: p.add_constraint(1.5*x[1]+3*x[2], max=4) @@ -1312,7 +1356,7 @@ cdef class MixedIntegerLinearProgram(SageObject): It can be solved as follows:: sage: p = MixedIntegerLinearProgram(maximization=True) - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) sage: p.set_objective(x[1] + 5*x[2]) sage: p.add_constraint(x[1] + 0.2*x[2], max=4) sage: p.add_constraint(1.5*x[1] + 3*x[2], max=4) @@ -1342,7 +1386,7 @@ cdef class MixedIntegerLinearProgram(SageObject): The previous program can be rewritten:: sage: p = MixedIntegerLinearProgram(maximization=True) - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) sage: p.set_objective(x[1] + 5*x[2]) sage: p.add_constraint(x[1] + 0.2*x[2] <= 4) sage: p.add_constraint(1.5*x[1] + 3*x[2] <= 4) @@ -1354,7 +1398,7 @@ cdef class MixedIntegerLinearProgram(SageObject): Complex constraints:: sage: p = MixedIntegerLinearProgram(solver = "GLPK") - sage: b = p.new_variable() + sage: b = p.new_variable(nonnegative=True) sage: p.add_constraint( b[8] - b[15] <= 3*b[8] + 9) sage: p.show() Maximization: @@ -1372,7 +1416,7 @@ cdef class MixedIntegerLinearProgram(SageObject): Min/Max are numerical :: - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) sage: p.add_constraint(v[3] + v[5], min = v[6]) Traceback (most recent call last): ... @@ -1425,7 +1469,7 @@ cdef class MixedIntegerLinearProgram(SageObject): Catch ``True`` / ``False`` as INPUT (:trac:`13646`):: sage: p = MixedIntegerLinearProgram() - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) sage: p.add_constraint(True) Traceback (most recent call last): ... @@ -1600,7 +1644,7 @@ cdef class MixedIntegerLinearProgram(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) With the following instruction, all the variables from x will be binary:: @@ -1610,9 +1654,9 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: p.add_constraint(-3*x[0] + 2*x[1], max=2) It is still possible, though, to set one of these - variables as nonnegative while keeping the others as they are:: + variables as integer while keeping the others as they are:: - sage: p.set_nonnegative(x[3]) + sage: p.set_integer(x[3]) TESTS: @@ -1656,7 +1700,7 @@ cdef class MixedIntegerLinearProgram(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) sage: p.set_objective(v[1]) sage: p.is_binary(v[1]) False @@ -1679,7 +1723,7 @@ cdef class MixedIntegerLinearProgram(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) With the following instruction, all the variables from x will be integers:: @@ -1689,9 +1733,9 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: p.add_constraint(-3*x[0] + 2*x[1], max=2) It is still possible, though, to set one of these - variables as nonnegative while keeping the others as they are:: + variables as binary while keeping the others as they are:: - sage: p.set_nonnegative(x[3]) + sage: p.set_binary(x[3]) """ cdef MIPVariable e e = ee @@ -1725,7 +1769,7 @@ cdef class MixedIntegerLinearProgram(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) sage: p.set_objective(v[1]) sage: p.is_integer(v[1]) False @@ -1735,10 +1779,9 @@ cdef class MixedIntegerLinearProgram(SageObject): """ return self._backend.is_variable_integer(self._variables[e]) - set_real = deprecated_function_alias(15521, set_nonnegative) - def set_nonnegative(self,ee): + def set_real(self,ee): r""" - Sets a variable or a ``MIPVariable`` as nonnegative. + Sets a variable or a ``MIPVariable`` as real. INPUT: @@ -1748,12 +1791,12 @@ cdef class MixedIntegerLinearProgram(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) With the following instruction, all the variables - from x will be nonnegative:: + from x will be real:: - sage: p.set_nonnegative(x) + sage: p.set_real(x) sage: p.set_objective(x[0] + x[1]) sage: p.add_constraint(-3*x[0] + 2*x[1], max=2) @@ -1762,15 +1805,7 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: p.set_binary(x[3]) - TESTS: - - :trac:`15521`:: - - sage: p.set_real(x[3]) - doctest:1: DeprecationWarning: set_real is deprecated. Please use set_nonnegative instead. - See http://trac.sagemath.org/15521 for details. """ - cdef MIPVariable e e = ee @@ -1789,10 +1824,9 @@ cdef class MixedIntegerLinearProgram(SageObject): else: raise ValueError("e must be an instance of MIPVariable or one of its elements.") - is_real = deprecated_function_alias(15521,is_nonnegative) - def is_nonnegative(self, e): + def is_real(self, e): r""" - Tests whether the variable is nonnegative. + Tests whether the variable is real. INPUT: @@ -1800,24 +1834,23 @@ cdef class MixedIntegerLinearProgram(SageObject): OUTPUT: - ``True`` if the variable is nonnegative; ``False`` otherwise. + ``True`` if the variable is real; ``False`` otherwise. EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) sage: p.set_objective(v[1]) - sage: p.is_nonnegative(v[1]) + sage: p.is_real(v[1]) True sage: p.set_binary(v[1]) - sage: p.is_nonnegative(v[1]) + sage: p.is_real(v[1]) False - sage: p.set_nonnegative(v[1]) - sage: p.is_nonnegative(v[1]) + sage: p.set_real(v[1]) + sage: p.is_real(v[1]) True """ - return (self._backend.is_variable_continuous(self._variables[e]) and - self._backend.variable_lower_bound(self._variables[e]) == 0) + return self._backend.is_variable_continuous(self._variables[e]) def solve(self, log=None, objective_only=False): r""" @@ -1860,7 +1893,7 @@ cdef class MixedIntegerLinearProgram(SageObject): This linear program can be solved as follows:: sage: p = MixedIntegerLinearProgram(maximization=True) - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) sage: p.set_objective(x[1] + 5*x[2]) sage: p.add_constraint(x[1] + 0.2*x[2], max=4) sage: p.add_constraint(1.5*x[1] + 3*x[2], max=4) @@ -1876,7 +1909,7 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: g = graphs.PetersenGraph() sage: p = MixedIntegerLinearProgram(maximization=True) - sage: b = p.new_variable() + sage: b = p.new_variable(nonnegative=True) sage: p.set_objective(sum([b[v] for v in g])) sage: for (u,v) in g.edges(labels=None): ... p.add_constraint(b[u] + b[v], max=1) @@ -1905,16 +1938,12 @@ cdef class MixedIntegerLinearProgram(SageObject): r""" Sets the minimum value of a variable. - .. WARNING:: - - By default, all variables are defined to be non-negative. - INPUT: - - ``v`` -- a variable (not a ``MIPVariable``, but one of its - elements). - - ``min`` -- the minimum value the variable can take. - When ``min=None``, the variable has no lower bound. + - ``v`` -- a variable. + + - ``min`` -- the minimum value the variable can take. When + ``min=None``, the variable has no lower bound. .. SEEALSO:: @@ -1923,7 +1952,7 @@ cdef class MixedIntegerLinearProgram(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) sage: p.set_objective(v[1]) sage: p.get_min(v[1]) 0.0 @@ -1933,8 +1962,21 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: p.set_min(v[1], None) sage: p.get_min(v[1]) + With a :class:`MIPVariable` as an argument:: + + sage: vv = p.new_variable(real=True, nonnegative=False) + sage: p.get_min(vv) + sage: p.get_min(vv[0]) + sage: p.set_min(vv,5) + sage: p.get_min(vv[0]) + 5.0 + sage: p.get_min(vv[9]) + 5.0 """ - self._backend.variable_lower_bound(self._variables[v], min) + try: + v.set_min(min) + except AttributeError: + self._backend.variable_lower_bound(self._variables[v], min) def set_max(self, v, max): r""" @@ -1942,22 +1984,36 @@ cdef class MixedIntegerLinearProgram(SageObject): INPUT - - ``v`` -- a variable (not a ``MIPVariable``, but one of its - elements). - - ``max`` -- the maximum value the variable can take. - When ``max=None``, the variable has no upper bound. + - ``v`` -- a variable. + + - ``max`` -- the maximum value the variable can take. When + ``max=None``, the variable has no upper bound. EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) sage: p.set_objective(v[1]) sage: p.get_max(v[1]) sage: p.set_max(v[1],6) sage: p.get_max(v[1]) 6.0 + + With a :class:`MIPVariable` as an argument:: + + sage: vv = p.new_variable(real=True, nonnegative=False) + sage: p.get_max(vv) + sage: p.get_max(vv[0]) + sage: p.set_max(vv,5) + sage: p.get_max(vv[0]) + 5.0 + sage: p.get_max(vv[9]) + 5.0 """ - self._backend.variable_upper_bound(self._variables[v], max) + try: + v.set_max(max) + except AttributeError: + self._backend.variable_upper_bound(self._variables[v], max) def get_min(self, v): r""" @@ -1965,17 +2021,17 @@ cdef class MixedIntegerLinearProgram(SageObject): INPUT: - - ``v`` -- a variable (not a ``MIPVariable``, but one of its elements). + - ``v`` -- a variable OUTPUT: - Minimum value of the variable, or ``None`` if - the variable has no lower bound. + Minimum value of the variable, or ``None`` if the variable has no lower + bound. EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) sage: p.set_objective(v[1]) sage: p.get_min(v[1]) 0.0 @@ -1985,7 +2041,10 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: p.set_min(v[1], None) sage: p.get_min(v[1]) """ - return self._backend.variable_lower_bound(self._variables[v]) + try: + return (v)._lower_bound + except TypeError: + return self._backend.variable_lower_bound(self._variables[v]) def get_max(self, v): r""" @@ -1993,24 +2052,27 @@ cdef class MixedIntegerLinearProgram(SageObject): INPUT: - - ``v`` -- a variable (not a ``MIPVariable``, but one of its elements). + - ``v`` -- a variable. OUTPUT: - Maximum value of the variable, or ``None`` if - the variable has no upper bound. + Maximum value of the variable, or ``None`` if the variable has no upper + bound. EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) sage: p.set_objective(v[1]) sage: p.get_max(v[1]) sage: p.set_max(v[1],6) sage: p.get_max(v[1]) 6.0 """ - return self._backend.variable_upper_bound(self._variables[v]) + try: + return (v)._upper_bound + except TypeError: + return self._backend.variable_upper_bound(self._variables[v]) def solver_parameter(self, name, value = None): """ @@ -2098,7 +2160,7 @@ cdef class MixedIntegerLinearProgram(SageObject): EXAMPLES:: sage: p = MixedIntegerLinearProgram() - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) The following command:: @@ -2165,7 +2227,7 @@ class MIPSolverException(RuntimeError): No continuous solution:: sage: p=MixedIntegerLinearProgram(solver="GLPK") - sage: v=p.new_variable() + sage: v=p.new_variable(nonnegative=True) sage: p.add_constraint(v[0],max=5.5) sage: p.add_constraint(v[0],min=7.6) sage: p.set_objective(v[0]) @@ -2180,7 +2242,7 @@ class MIPSolverException(RuntimeError): No integer solution:: sage: p=MixedIntegerLinearProgram(solver="GLPK") - sage: v=p.new_variable() + sage: v=p.new_variable(nonnegative=True) sage: p.add_constraint(v[0],max=5.6) sage: p.add_constraint(v[0],min=5.2) sage: p.set_objective(v[0]) @@ -2214,7 +2276,7 @@ cdef class MIPVariable(SageObject): ``MixedIntegerLinearProgram``. """ - def __cinit__(self, p, vtype, dim=1, name=""): + def __cinit__(self, p, vtype, dim=1, name="", lower_bound=0, upper_bound=None): r""" Constructor for ``MIPVariable``. @@ -2230,18 +2292,24 @@ cdef class MIPVariable(SageObject): - ``name`` -- A name for the ``MIPVariable``. + - ``lower_bound``, ``upper_bound`` -- lower bound and upper + bound on the variable. Set to ``None`` to indicate that the + variable is unbounded. + For more informations, see the method ``MixedIntegerLinearProgram.new_variable``. EXAMPLE:: sage: p=MixedIntegerLinearProgram() - sage: v=p.new_variable() + sage: v=p.new_variable(nonnegative=True) """ self._dim = dim self._dict = {} self._p = p self._vtype = vtype + self._lower_bound = lower_bound + self._upper_bound = upper_bound self._hasname = (len(name) >0) @@ -2271,10 +2339,17 @@ cdef class MIPVariable(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) sage: p.set_objective(v[0] + v[1]) sage: v[0] x_0 + + TESTS: + + This function contains "dim" code that will have to be removed:: + + sage: p = MixedIntegerLinearProgram() + sage: b = p.new_variable(binary=True, dim=2) """ cdef MIPVariable s = self @@ -2284,9 +2359,14 @@ cdef class MIPVariable(SageObject): return self._dict[i] elif self._dim == 1: zero = self._p._backend.zero() - j = self._p._backend.add_variable(zero , None, False, True, False, zero, - (str(self._name) + "[" + str(i) + "]") - if self._hasname else None) + j = self._p._backend.add_variable(lower_bound = self._lower_bound, + upper_bound = self._upper_bound, + binary = False, + continuous = True, + integer = False, + obj = zero, + name = ((str(self._name) + "[" + str(i) + "]") + if self._hasname else None)) v = self._p.linear_function({j : 1}) self._p._variables[v] = j @@ -2299,12 +2379,81 @@ cdef class MIPVariable(SageObject): self._dict[i] = MIPVariable( self._p, self._vtype, - dim=self._dim-1, - name = ("" if not self._hasname - else (str(self._name) + "[" + str(i) + "]"))) + dim = self._dim-1, + name = ("" if not self._hasname + else (str(self._name) + "[" + str(i) + "]")), + lower_bound = self._lower_bound, + upper_bound = self._upper_bound) return self._dict[i] + def set_min(self, min): + r""" + Sets a lower bound on the variable. + + INPUT: + + - ``min`` -- a lower bound, or ``None`` to mean that the variable is + unbounded. + + EXAMPLES:: + + sage: p = MixedIntegerLinearProgram() + sage: v = p.new_variable(real=True, nonnegative=True, dim=2) + sage: p.get_min(v) + 0 + sage: p.get_min(v[0]) + 0 + sage: p.get_min(v[0][0]) + 0.0 + sage: p.set_min(v,4) + sage: p.get_min(v) + 4 + sage: p.get_min(v[0]) + 4 + sage: p.get_min(v[0][0]) + 4.0 + """ + self._lower_bound = min + if self._dim == 1: + for v in self._p._variables: + self._p.set_min(v,min) + else: + for v in self._dict.itervalues(): + v.set_min(min) + + def set_max(self, max): + r""" + Sets an upper bound on the variable. + + INPUT: + + - ``max`` -- an upper bound, or ``None`` to mean that the variable is + unbounded. + + EXAMPLES:: + + sage: p = MixedIntegerLinearProgram() + sage: v = p.new_variable(real=True, nonnegative=True, dim=2) + sage: p.get_max(v) + sage: p.get_max(v[0]) + sage: p.get_max(v[0][0]) + sage: p.set_max(v,4) + sage: p.get_max(v) + 4 + sage: p.get_max(v[0]) + 4 + sage: p.get_max(v[0][0]) + 4.0 + """ + self._upper_bound = max + if self._dim == 1: + for v in self._p._variables: + self._p.set_max(v,max) + else: + for v in self._dict.itervalues(): + v.set_max(max) + def _repr_(self): r""" Returns a representation of self. @@ -2329,7 +2478,7 @@ cdef class MIPVariable(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) sage: p.set_objective(v[0] + v[1]) sage: v.keys() [0, 1] @@ -2343,7 +2492,7 @@ cdef class MIPVariable(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) sage: p.set_objective(v[0] + v[1]) sage: v.items() [(0, x_0), (1, x_1)] @@ -2357,7 +2506,7 @@ cdef class MIPVariable(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) sage: p.set_objective(v[0] + v[1]) sage: v.depth() 1 @@ -2371,7 +2520,7 @@ cdef class MIPVariable(SageObject): EXAMPLE:: sage: p = MixedIntegerLinearProgram() - sage: v = p.new_variable() + sage: v = p.new_variable(nonnegative=True) sage: p.set_objective(v[0] + v[1]) sage: v.values() [x_0, x_1] @@ -2390,7 +2539,7 @@ def Sum(x): See http://trac.sagemath.org/13646 for details. sage: p = MixedIntegerLinearProgram() - sage: x = p.new_variable() + sage: x = p.new_variable(nonnegative=True) sage: Sum([ x[0]+x[1], x[1]+x[2], x[2]+x[3] ]) # deprecation is only shown once x_0 + 2*x_1 + 2*x_2 + x_3 """ diff --git a/src/sage/plot/animate.py b/src/sage/plot/animate.py index 38ba89457a5..0a0eb141766 100644 --- a/src/sage/plot/animate.py +++ b/src/sage/plot/animate.py @@ -29,7 +29,7 @@ Animate using ffmpeg instead of ImageMagick:: - sage: f = sage.misc.temporary_file.tmp_filename(ext='.gif') + sage: f = tmp_filename(ext='.gif') sage: a.save(filename=f,use_ffmpeg=True) # optional -- ffmpeg An animated :class:`sage.plot.graphics.GraphicsArray` of rotating ellipses:: @@ -97,7 +97,7 @@ import os from sage.structure.sage_object import SageObject -from sage.misc.temporary_file import tmp_filename, tmp_dir +from sage.misc.temporary_file import tmp_dir, graphics_filename import plot import sage.misc.misc import sage.misc.viewer @@ -461,7 +461,8 @@ def graphics_array(self, ncols=3): sage: g = a.graphics_array(ncols=2); print g Graphics Array of size 2 x 2 - sage: g.show('sage.png') # optional + sage: f = tmp_filename(ext='.png') + sage: g.show(f) # optional Frames can be specified as a generator too; it is internally converted to a list:: @@ -560,7 +561,7 @@ def gif(self, delay=20, savefile=None, iterations=0, show_path=False, raise OSError(msg) else: if not savefile: - savefile = tmp_filename(ext='.gif') + savefile = graphics_filename(ext='gif') if not savefile.endswith('.gif'): savefile += '.gif' savefile = os.path.abspath(savefile) @@ -631,16 +632,9 @@ def show(self, delay=20, iterations=0): See www.imagemagick.org and www.ffmpeg.org for more information. """ - if sage.doctest.DOCTEST_MODE: - filename = tmp_filename(ext='.gif') - self.gif(savefile=filename, delay=delay, iterations=iterations) - return - - if plot.EMBEDDED_MODE: - self.gif(delay = delay, iterations = iterations) - else: - filename = tmp_filename(ext='.gif') - self.gif(delay=delay, savefile=filename, iterations=iterations) + filename = graphics_filename(ext='gif') + self.gif(savefile=filename, delay=delay, iterations=iterations) + if not (sage.doctest.DOCTEST_MODE or plot.EMBEDDED_MODE): os.system('%s %s 2>/dev/null 1>/dev/null &'%( sage.misc.viewer.browser(), filename)) @@ -749,7 +743,7 @@ def ffmpeg(self, savefile=None, show_path=False, output_format=None, else: if output_format[0] != '.': output_format = '.'+output_format - savefile = tmp_filename(ext=output_format) + savefile = graphics_filename(ext=output_format[1:]) else: if output_format is None: suffix = os.path.splitext(savefile)[1] diff --git a/src/sage/plot/graphics.py b/src/sage/plot/graphics.py index 1e07333537e..d43a8f32ff3 100644 --- a/src/sage/plot/graphics.py +++ b/src/sage/plot/graphics.py @@ -35,7 +35,6 @@ ALLOWED_EXTENSIONS = ['.eps', '.pdf', '.png', '.ps', '.sobj', '.svg'] DEFAULT_DPI = 100 -DOCTEST_MODE_FILE = os.path.join(sage.misc.misc.SAGE_TMP, 'test.png') def show_default(default=None): r""" @@ -52,7 +51,7 @@ def show_default(default=None): ``False`` in doctests:: sage: show_default() # long time - doctest:1: DeprecationWarning: this is done automatically by the doctest framework + doctest:...: DeprecationWarning: this is done automatically by the doctest framework See http://trac.sagemath.org/14469 for details. False """ @@ -1244,7 +1243,7 @@ def _set_scale(self, figure, scale=None, base=None): labelspacing=0.02, loc='best', markerscale=0.6, ncol=1, numpoints=2, shadow=False, title=None) - def show(self, **kwds): + def show(self, filename=None, linkmode=False, **kwds): r""" Show this graphics image with the default image viewer. @@ -1813,24 +1812,20 @@ def show(self, **kwds): ValueError: 'title_pos' must be a list or tuple of two real numbers. """ - # This option should not be passed on to save(). - linkmode = kwds.pop('linkmode', False) + if filename is None: + filename = graphics_filename() - if sage.doctest.DOCTEST_MODE: - kwds.pop('filename', None) - self.save(DOCTEST_MODE_FILE, **kwds) - elif sage.plot.plot.EMBEDDED_MODE: - kwds.setdefault('filename', graphics_filename()) - self.save(**kwds) - if linkmode == True: - return "" % kwds['filename'] + self.save(filename, **kwds) + + if sage.plot.plot.EMBEDDED_MODE: + if linkmode: + return "" % filename else: - html("" % kwds['filename']) - else: - kwds.setdefault('filename', tmp_filename(ext='.png')) - self.save(**kwds) + html("" % filename) + return + if not sage.doctest.DOCTEST_MODE: os.system('%s %s 2>/dev/null 1>/dev/null &' - % (sage.misc.viewer.png_viewer(), kwds['filename'])) + % (sage.misc.viewer.png_viewer(), filename)) def xmin(self, xmin=None): """ @@ -2868,6 +2863,11 @@ def save(self, filename=None, **kwds): sage: a = plot_vector_field((x,-y),(x,-1,1),(y,-1,1)) sage: filename=os.path.join(SAGE_TMP, 'test2.png') sage: a.save(filename) + + The following plot should show the axes; fixes :trac:`14782` :: + + sage: plot(x^2, (x, 1, 2), ticks=[[], []]) + """ options = dict() options.update(self.SHOW_OPTIONS) @@ -2878,9 +2878,12 @@ def save(self, filename=None, **kwds): fig_tight = options.pop('fig_tight') if filename is None: - filename = options.pop('filename') - if filename is None: - filename = graphics_filename() + try: + filename = options.pop('filename') + except KeyError: + # Put this in except (not in pop()) such that the file is + # only created when needed. + filename = graphics_filename() ext = os.path.splitext(filename)[1].lower() if ext not in ALLOWED_EXTENSIONS: @@ -2907,13 +2910,13 @@ def save(self, filename=None, **kwds): # tight_layout adjusts the *subplot* parameters so ticks aren't cut off, etc. figure.tight_layout() + opts = dict(dpi=dpi, transparent=transparent) if fig_tight is True: - figure.savefig(filename, dpi=dpi, bbox_inches='tight', - bbox_extra_artists=self._bbox_extra_artists, - transparent=transparent) - else: - figure.savefig(filename, dpi=dpi, - transparent=transparent) + opts['bbox_inches'] = 'tight' + if self._bbox_extra_artists: + opts['bbox_extra_artists'] = self._bbox_extra_artists + + figure.savefig(filename, **opts) # Restore the rcParams to the original, possibly user-set values (rcParams['ps.useafm'], rcParams['pdf.use14corefonts'], @@ -3222,20 +3225,41 @@ def append(self, g): raise NotImplementedError('Appending to a graphics array is not yet implemented') - def _render(self, filename, dpi=None, figsize=None, axes=None, **args): + def save(self, filename=None, dpi=DEFAULT_DPI, figsize=None, axes=None, + **kwds): r""" - ``_render`` loops over all graphics objects in the array - and adds them to the subplot. This is only used internally - when the plot is actually saved or shown. + Save the ``graphics_array`` to a png called ``filename``. + + We loop over all graphics objects in the array and add them to + a subplot and then render that. + + INPUT: + + - ``filename`` - (default: None) string + + - ``dpi`` - dots per inch + + - ``figsize`` - width or [width, height] + + - ``axes`` - (default: True) EXAMPLES:: - sage: graphics_array([[plot(sin), plot(cos)], [plot(tan), plot(sec)]]) + sage: F = tmp_filename(ext='.png') + sage: L = [plot(sin(k*x),(x,-pi,pi)) for k in [1..3]] + sage: G = graphics_array(L) + sage: G.save(F, dpi=500, axes=False) # long time (6s on sage.math, 2012) TESTS:: - sage: graphics_array([]) + sage: graphics_array([]).save() + sage: graphics_array([[]]).save() """ + if figsize is not None: + self._set_figsize_(figsize) + if filename is None: + filename = graphics_filename() + #glist is a list of Graphics objects: glist = self._glist rows = self._rows @@ -3246,15 +3270,15 @@ def _render(self, filename, dpi=None, figsize=None, axes=None, **args): rows = cols = dims = 1 #make a blank matplotlib Figure: from matplotlib.figure import Figure - figure = Figure(figsize) + figure = Figure(self._figsize) global do_verify do_verify = True for i,g in zip(range(1, dims+1), glist): subplot = figure.add_subplot(rows, cols, i) g.matplotlib(filename, figure=figure, sub=subplot, - verify=do_verify, axes = axes, **args) + verify=do_verify, axes = axes, **kwds) g.save(filename, dpi=dpi, figure=figure, sub=subplot, - verify=do_verify, axes = axes, **args) + verify=do_verify, axes = axes, **kwds) def save_image(self, filename=None, *args, **kwds): r""" @@ -3278,34 +3302,9 @@ def save_image(self, filename=None, *args, **kwds): """ self.save(filename, *args, **kwds) - def save(self, filename=None, dpi=DEFAULT_DPI, figsize=None, - axes = None, **args): - """ - Save the ``graphics_array`` to (for now) a png called - 'filename'. - - OPTIONAL INPUT: - - - ``filename`` - (default: None) string - - - ``dpi`` - dots per inch - - - ``figsize`` - width or [width, height] - - - ``axes`` - (default: True) - - EXAMPLES:: - - sage: F = tmp_filename(ext='.png') - sage: L = [plot(sin(k*x),(x,-pi,pi)) for k in [1..3]] - sage: G = graphics_array(L) - sage: G.save(F,500,axes=False) # long time (6s on sage.math, 2012) - """ - if (figsize is not None): self._set_figsize_(figsize) - self._render(filename, dpi=dpi, figsize=self._figsize, axes = axes, **args) def show(self, filename=None, dpi=DEFAULT_DPI, figsize=None, - axes = None, **args): + axes = None, **kwds): r""" Show this graphics array using the default viewer. @@ -3324,26 +3323,17 @@ def show(self, filename=None, dpi=DEFAULT_DPI, figsize=None, - ``frame`` - (default: False) draw a frame around the image - EXAMPLES: This draws a graphics array with four trig plots and no - axes in any of the plots. + EXAMPLES: - :: + This draws a graphics array with four trig plots and no + axes in any of the plots:: sage: G = graphics_array([[plot(sin), plot(cos)], [plot(tan), plot(sec)]]) sage: G.show(axes=False) """ - if (figsize is not None): self._set_figsize_(figsize) - if sage.doctest.DOCTEST_MODE: - self.save(DOCTEST_MODE_FILE, - dpi=dpi, figsize=self._figsize, axes = axes, **args) - return - if sage.plot.plot.EMBEDDED_MODE: - self.save(filename, dpi=dpi, figsize=self._figsize, axes = axes, **args) - return if filename is None: - filename = tmp_filename(ext='.png') - self._render(filename, dpi=dpi, figsize=self._figsize, axes = axes, **args) - os.system('%s %s 2>/dev/null 1>/dev/null &'%( + filename = graphics_filename() + self.save(filename, dpi=dpi, figsize=figsize, axes = axes, **kwds) + if not sage.doctest.DOCTEST_MODE and not sage.plot.plot.EMBEDDED_MODE: + os.system('%s %s 2>/dev/null 1>/dev/null &'%( sage.misc.viewer.png_viewer(), filename)) - - diff --git a/src/sage/plot/plot.py b/src/sage/plot/plot.py index cdbbc80750a..57f9f1a753d 100644 --- a/src/sage/plot/plot.py +++ b/src/sage/plot/plot.py @@ -753,13 +753,22 @@ def plot(funcs, *args, **kwds): - ``fillalpha`` - (default: 0.5) How transparent the fill is. A number between 0 and 1. - Note that this function does NOT simply sample equally spaced - points between ``xmin`` and ``xmax``. Instead it computes equally spaced - points and add small perturbations to them. This reduces the - possibility of, e.g., sampling sin only at multiples of - `2\pi`, which would yield a very misleading graph. + .. note:: + + - this function does NOT simply sample equally spaced points + between xmin and xmax. Instead it computes equally spaced points + and adds small perturbations to them. This reduces the possibility + of, e.g., sampling `\sin` only at multiples of `2\pi`, which would + yield a very misleading graph. + + - if there is a range of consecutive points where the function has + no value, then those points will be excluded from the plot. See + the example below on automatic exclusion of points. - EXAMPLES: We plot the sin function:: + + EXAMPLES: + + We plot the `\sin` function:: sage: P = plot(sin, (0,10)); print P Graphics object consisting of 1 graphics primitive @@ -1044,6 +1053,23 @@ def plot(funcs, *args, **kwds): sage: f(x) = (floor(x)+0.5) / (1-(x-0.5)^2) sage: plot(f, (x, -3.5, 3.5), detect_poles = 'show', exclude = [-3..3], ymin = -5, ymax = 5) + Regions in which the plot has no values are automatically excluded. The + regions thus excluded are in addition to the exclusion points present + in the ``exclude`` keyword argument.:: + + sage: set_verbose(-1) + sage: plot(arcsec, (x, -2, 2)) # [-1, 1] is excluded automatically + + sage: plot(arcsec, (x, -2, 2), exclude=[1.5]) # x=1.5 is also excluded + + sage: plot(arcsec(x/2), -2, 2) # plot should be empty; no valid points + + sage: plot(sqrt(x^2-1), -2, 2) # [-1, 1] is excluded automatically + + sage: plot(arccsc, -2, 2) # [-1, 1] is excluded automatically + + sage: set_verbose(0) + TESTS: We do not randomize the endpoints:: @@ -1111,6 +1137,17 @@ def plot(funcs, *args, **kwds): Check that :trac:`15030` is fixed:: sage: plot(abs(log(x)), x) + + Check that if excluded points are less than xmin then the exclusion + still works for polar and parametric plots. The following should + show two excluded points:: + + sage: set_verbose(-1) + sage: polar_plot(sin(sqrt(x^2-1)), (x,0,2*pi), exclude=[1/2,2,3]) + + sage: parametric_plot((sqrt(x^2-1),sqrt(x^2-1/2)), (x,0,5), exclude=[1,2,3]) + + sage: set_verbose(0) """ G_kwds = Graphics._extract_kwds_for_show(kwds, ignore=['xmin', 'xmax']) @@ -1233,11 +1270,17 @@ def _plot(funcs, xrange, parametric=False, 1 sage: p1.show(ymin=-10,ymax=10) # should be one legend + Parametric plots that get evaluated at invalid points should still + plot properly (:trac:`13246`):: + + sage: parametric_plot((x, arcsec(x)), (x, -2, 2)) + """ from sage.plot.misc import setup_for_eval_on_grid if funcs == []: return Graphics() + excluded_points = [] funcs, ranges = setup_for_eval_on_grid(funcs, [xrange], options['plot_points']) xmin, xmax, delta = ranges[0] xrange=ranges[0][:2] @@ -1296,32 +1339,54 @@ def _plot(funcs, xrange, parametric=False, v = exclude.variables()[0] points = [e.right() for e in exclude.solve(v) if e.left() == v and (v not in e.right().variables())] # We are only interested in real solutions - exclude = [] for x in points: try: - exclude.append(float(x)) + excluded_points.append(float(x)) except TypeError: pass + excluded_points.sort() - if isinstance(exclude, (list, tuple)): - exclude = sorted(exclude) - # We make sure that points plot points close to the excluded points are computed - epsilon = 0.001*(xmax - xmin) - initial_points = reduce(lambda a,b: a+b, [[x - epsilon, x + epsilon] for x in exclude], []) - data = generate_plot_points(f, xrange, plot_points, adaptive_tolerance, adaptive_recursion, randomize, initial_points) + # We should either have a list in excluded points or exclude + # itself must be a list + elif isinstance(exclude, (list, tuple)): + excluded_points = sorted(exclude) else: raise ValueError('exclude needs to be a list of numbers or an equation') - if exclude == []: - exclude = None + # We make sure that points plot points close to the excluded points are computed + epsilon = 0.001*(xmax - xmin) + initial_points = reduce(lambda a,b: a+b, + [[x - epsilon, x + epsilon] + for x in excluded_points], []) + data = generate_plot_points(f, xrange, plot_points, + adaptive_tolerance, adaptive_recursion, + randomize, initial_points) else: - data = generate_plot_points(f, xrange, plot_points, adaptive_tolerance, adaptive_recursion, randomize) + data = generate_plot_points(f, xrange, plot_points, + adaptive_tolerance, adaptive_recursion, + randomize) + + + for i in range(len(data)-1): + # If the difference between consecutive x-values is more than + # 2 times the difference between two consecutive plot points, then + # add an exclusion point. + if abs(data[i+1][0] - data[i][0]) > 2*abs(xmax - xmin)/plot_points: + excluded_points.append((data[i][0] + data[i+1][0])/2) if parametric: # We need the original x-values to be able to exclude points in parametric plots exclude_data = data - data = [(fdata, g(x)) for x, fdata in data] - + newdata = [] + for x,fdata in data: + try: + newdata.append((fdata, g(x))) + except (ValueError, TypeError): + newdata.append((fdata, 0)) # append a dummy value 0 + excluded_points.append(x) + data = newdata + + excluded_points.sort(reverse=True) G = Graphics() fillcolor = options.pop('fillcolor', 'automatic') @@ -1389,7 +1454,7 @@ def _plot(funcs, xrange, parametric=False, detect_poles = options.pop('detect_poles', False) legend_label = options.pop('legend_label', None) - if exclude is not None or detect_poles != False: + if excluded_points or detect_poles != False: start_index = 0 # setup for pole detection from sage.rings.all import RDF @@ -1401,13 +1466,14 @@ def _plot(funcs, xrange, parametric=False, # setup for exclusion points exclusion_point = 0 - if exclude is not None: - exclude.reverse() - exclusion_point = exclude.pop() + if excluded_points: + exclusion_point = excluded_points.pop() + flag = True for i in range(len(data)-1): x0, y0 = exclude_data[i] x1, y1 = exclude_data[i+1] + # detect poles if (not (polar or parametric)) and detect_poles != False \ and ((y1 > 0 and y0 < 0) or (y1 < 0 and y0 > 0)): @@ -1423,14 +1489,25 @@ def _plot(funcs, xrange, parametric=False, start_index = i+2 # exclude points - if exclude is not None and (x0 <= exclusion_point <= x1): + if x0 > exclusion_point: + while exclusion_point <= x1: + try: + exclusion_point = excluded_points.pop() + except IndexError: + # all excluded points were considered + flag = False + break + + elif flag and (x0 <= exclusion_point <= x1): G += line(data[start_index:i], **options) start_index = i + 2 - try: - exclusion_point = exclude.pop() - except IndexError: - # all excluded points were considered - exclude = None + while exclusion_point <= x1: + try: + exclusion_point = excluded_points.pop() + except IndexError: + # all excluded points were considered + flag = False + break G += line(data[start_index:], legend_label=legend_label, **options) else: diff --git a/src/sage/plot/plot3d/tachyon.py b/src/sage/plot/plot3d/tachyon.py index 82f9fcdb17f..7c47b39a925 100644 --- a/src/sage/plot/plot3d/tachyon.py +++ b/src/sage/plot/plot3d/tachyon.py @@ -344,20 +344,15 @@ def show(self, verbose=0, extra_opts=''): sage: q.light((-1,-1,10), 1,(1,1,1)) sage: q.texture('s') sage: q.sphere((0,0,0),1,'s') - sage: q.show(verbose = False) + sage: q.show(verbose=False) """ - import sage.plot.plot - if sage.doctest.DOCTEST_MODE: - filename = graphics_filename() - self.save(os.path.join(SAGE_TMP, 'test.png'), verbose=verbose, extra_opts=extra_opts) - return - if sage.plot.plot.EMBEDDED_MODE: - filename = graphics_filename() - self.save(filename, verbose=verbose, extra_opts=extra_opts) - return - filename = tmp_filename(ext='.png') + filename = graphics_filename() self.save(filename, verbose=verbose, extra_opts=extra_opts) - os.system('%s %s 2>/dev/null 1>/dev/null &'%(sage.misc.viewer.png_viewer(), filename)) + + from sage.doctest import DOCTEST_MODE + from sage.plot.plot import EMBEDDED_MODE + if not DOCTEST_MODE and not EMBEDDED_MODE: + os.system('%s %s 2>/dev/null 1>/dev/null &'%(sage.misc.viewer.png_viewer(), filename)) def _res(self): r""" diff --git a/src/sage/rings/algebraic_closure_finite_field.py b/src/sage/rings/algebraic_closure_finite_field.py index f2ba912bc92..8b22caf4fe3 100644 --- a/src/sage/rings/algebraic_closure_finite_field.py +++ b/src/sage/rings/algebraic_closure_finite_field.py @@ -425,14 +425,55 @@ def as_finite_field_element(self, minimal=False): sage: s.as_finite_field_element(minimal=True)[0] Finite Field of size 3 + This also works when the element has to be converted between + two non-trivial finite subfields (see :trac:`16509`):: + + sage: K = GF(5).algebraic_closure() + sage: z = K.gen(5) - K.gen(5) + K.gen(2) + sage: z.as_finite_field_element(minimal=True) + (Finite Field in z2 of size 5^2, z2, Ring morphism: + From: Finite Field in z2 of size 5^2 + To: Algebraic closure of Finite Field of size 5 + Defn: z2 |--> z2) + + There is currently no automatic conversion between the various + subfields:: + + sage: a = K.gen(2) + 1 + sage: _,b,_ = a.as_finite_field_element() + sage: K4 = K.subfield(4)[0] + sage: K4(b) + Traceback (most recent call last): + ... + TypeError: unable to coerce from a finite field other than the prime + subfield + + Nevertheless it is possible to use the inclusions that are implemented at + the level of the algebraic closure:: + + sage: f = K.inclusion(2,4); f + Ring morphism: + From: Finite Field in z2 of size 5^2 + To: Finite Field in z4 of size 5^4 + Defn: z2 |--> z4^3 + z4^2 + z4 + 3 + sage: f(b) + z4^3 + z4^2 + z4 + 4 + """ - if not minimal: - l = self._level - else: - l = self._value.minpoly().degree() + Fbar = self.parent() + x = self._value + l = self._level + + if minimal: + m = x.minpoly().degree() + if m == 1: + x = Fbar.base_ring()(x) + else: + x = Fbar.inclusion(m, l).section()(x) + l = m - F, phi = self.parent().subfield(l) - return (F, F(self._value), phi) + F, phi = Fbar.subfield(l) + return (F, x, phi) class AlgebraicClosureFiniteField_generic(Field): @@ -828,6 +869,82 @@ def some_elements(self): """ return (self(1), self.gen(2), 1+self.gen(3)) + def _roots_univariate_polynomial(self, p, ring=None, multiplicities=None, algorithm=None): + r""" + Return a list of pairs ``(root,multiplicity)`` of roots of the polynomial ``p``. + + If the argument ``multiplicities`` is set to ``False`` then return the + list of roots. + + .. SEEALSO:: + + :meth:`_factor_univariate_polynomial` + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(5),'x') + sage: K = GF(5).algebraic_closure('t') + + sage: sorted((x^6 - 1).roots(K,multiplicities=False)) + [1, 4, 2*t2 + 1, 2*t2 + 2, 3*t2 + 3, 3*t2 + 4] + sage: ((K.gen(2)*x - K.gen(3))**2).roots(K) + [(3*t6^5 + 2*t6^4 + 2*t6^2 + 3, 2)] + + sage: for _ in xrange(10): + ....: p = R.random_element(degree=randint(2,8)) + ....: for r in p.roots(K, multiplicities=False): + ....: assert p(r).is_zero() + + """ + from sage.rings.arith import lcm + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + + # first build a polynomial over some finite field + coeffs = [v.as_finite_field_element(minimal=True) for v in p.list()] + l = lcm([c[0].degree() for c in coeffs]) + F, phi = self.subfield(l) + P = p.parent().change_ring(F) + + new_coeffs = [self.inclusion(c[0].degree(), l)(c[1]) for c in coeffs] + + roots = [] # a list of pair (root,multiplicity) + for g, m in P(new_coeffs).factor(): + if g.degree() == 1: + r = phi(-g.constant_coefficient()) + roots.append((r,m)) + else: + ll = l * g.degree() + psi = self.inclusion(l, ll) + FF, pphi = self.subfield(ll) + gg = PolynomialRing(FF, 'x')(map(psi, g)) + for r, _ in gg.roots(): # note: we know that multiplicity is 1 + roots.append((pphi(r), m)) + + if multiplicities: + return roots + else: + return [r[0] for r in roots] + + def _factor_univariate_polynomial(self, p, **kwds): + r""" + Factorization of univariate polynomials. + + EXAMPLES:: + + sage: K = GF(3).algebraic_closure() + sage: R = PolynomialRing(K, 'T') + sage: T = R.gen() + sage: (K.gen(2) * T^2 - 1).factor() + (z2) * (T + z4^3 + z4^2 + z4) * (T + 2*z4^3 + 2*z4^2 + 2*z4) + + sage: for d in xrange(10): + ....: p = R.random_element(degree=randint(2,8)) + ....: assert p.factor().prod() == p + + """ + from sage.structure.factorization import Factorization + R = p.parent() + return Factorization([(R([-root, self.one()]), m) for root, m in p.roots()], unit=p[p.degree()]) class AlgebraicClosureFiniteField_pseudo_conway(AlgebraicClosureFiniteField_generic, WithEqualityById): """ diff --git a/src/sage/rings/all.py b/src/sage/rings/all.py index 7389443dee0..5822f82cc84 100644 --- a/src/sage/rings/all.py +++ b/src/sage/rings/all.py @@ -151,7 +151,8 @@ farey, continued_fraction_list, convergent, convergents, \ continuant, number_of_divisors, hilbert_symbol, hilbert_conductor, \ hilbert_conductor_inverse, falling_factorial, rising_factorial, \ - integer_ceil, integer_floor, two_squares, four_squares, \ + integer_ceil, integer_floor, \ + two_squares, three_squares, four_squares, sum_of_k_squares, \ subfactorial, is_power_of_two, differences, \ sort_complex_numbers_for_display, \ fundamental_discriminant, squarefree_divisors, \ diff --git a/src/sage/rings/arith.py b/src/sage/rings/arith.py index 9c2eb8d9c90..004690b649f 100644 --- a/src/sage/rings/arith.py +++ b/src/sage/rings/arith.py @@ -4906,141 +4906,410 @@ def integer_floor(x): raise NotImplementedError("computation of floor of %s not implemented"%x) - -def two_squares(n, algorithm='gap'): +def two_squares(n): """ - Write the integer n as a sum of two integer squares if possible; - otherwise raise a ValueError. + Write the integer `n` as a sum of two integer squares if possible; + otherwise raise a ``ValueError``. + + INPUT: + + - ``n`` -- an integer + + OUTPUT: a tuple `(a,b)` of non-negative integers such that + `n = a^2 + b^2` with `a <= b`. EXAMPLES:: sage: two_squares(389) (10, 17) - sage: two_squares(7) + sage: two_squares(21) Traceback (most recent call last): ... - ValueError: 7 is not a sum of two squares - sage: a,b = two_squares(2009); a,b - (28, 35) + ValueError: 21 is not a sum of 2 squares + sage: two_squares(21^2) + (0, 21) + sage: a,b = two_squares(100000000000000000129); a,b + (4418521500, 8970878873) sage: a^2 + b^2 - 2009 + 100000000000000000129 + sage: two_squares(2^222+1) + (253801659504708621991421712450521, 2583712713213354898490304645018692) + sage: two_squares(0) + (0, 0) + sage: two_squares(-1) + Traceback (most recent call last): + ... + ValueError: -1 is not a sum of 2 squares + + TESTS:: + + sage: for _ in xrange(100): + ....: a = ZZ.random_element(2**16, 2**20) + ....: b = ZZ.random_element(2**16, 2**20) + ....: n = a**2 + b**2 + ....: aa,bb = two_squares(n) + ....: assert aa**2 + bb**2 == n + + ALGORITHM: - TODO: Create an implementation using PARI's continued fraction - implementation. + See http://www.schorn.ch/howto.html """ - from sage.rings.all import Integer + from sage.rings.all import Integer, Mod n = Integer(n) - if algorithm == 'gap': - import sage.interfaces.gap as gap - a = gap.gap.eval('TwoSquares(%s)'%n) - if a == 'fail': - raise ValueError("%s is not a sum of two squares"%n) - x, y = eval(a) - return Integer(x), Integer(y) + if n <= 0: + if n == 0: + z = ZZ.zero() + return (z, z) + raise ValueError("%s is not a sum of 2 squares"%n) + + if n.nbits() <= 32: + from sage.rings import sum_of_squares + return sum_of_squares.two_squares_pyx(n) + + # Start by factoring n (which seems to be unavoidable) + F = n.factor(proof=False) + + # First check whether it is possible to write n as a sum of two + # squares: all prime powers p^e must have p = 2 or p = 1 mod 4 + # or e even. + for (p,e) in F: + if e % 2 == 1 and p % 4 == 3: + raise ValueError("%s is not a sum of 2 squares"%n) + + # We run over all factors of n, write each factor p^e as + # a sum of 2 squares and accumulate the product + # (using multiplication in Z[I]) in a^2 + b^2. + a = ZZ.one() + b = ZZ.zero() + for (p,e) in F: + if e >= 2: + m = p ** (e//2) + a *= m + b *= m + if e % 2 == 1: + if p == 2: + # (a + bi) *= (1 + I) + a,b = a - b, a + b + else: # p = 1 mod 4 + # Find a square root of -1 mod p. + # If y is a non-square, then y^((p-1)/4) is a square root of -1. + y = Mod(2,p) + while True: + s = y**((p-1)/4) + if not s*s + 1: + s = s.lift() + break + y += 1 + # Apply Cornacchia's algorithm to write p as r^2 + s^2. + r = p + while s*s > p: + r,s = s, r % s + r %= s + + # Multiply (a + bI) by (r + sI) + a,b = a*r - b*s, b*r + a*s + + a = a.abs() + b = b.abs() + assert a*a + b*b == n + if a <= b: + return (a,b) else: - raise RuntimeError("unknown algorithm '%s'"%algorithm) + return (b,a) -def _brute_force_four_squares(n): +def three_squares(n): """ - Brute force search for decomposition into a sum of four squares, - for cases that the main algorithm fails to handle. + Write the integer `n` as a sum of three integer squares if possible; + otherwise raise a ``ValueError``. INPUT: - - ``n`` - a positive integer - - OUTPUT: + - ``n`` -- an integer - - a list of four numbers whose squares sum to n + OUTPUT: a tuple `(a,b,c)` of non-negative integers such that + `n = a^2 + b^2 + c^2` with `a <= b <= c`. EXAMPLES:: - sage: from sage.rings.arith import _brute_force_four_squares - sage: _brute_force_four_squares(567) - [1, 1, 6, 23] + sage: three_squares(389) + (1, 8, 18) + sage: three_squares(946) + (9, 9, 28) + sage: three_squares(2986) + (3, 24, 49) + sage: three_squares(7^100) + (0, 0, 1798465042647412146620280340569649349251249) + sage: three_squares(11^111-1) + (616274160655975340150706442680, 901582938385735143295060746161, 6270382387635744140394001363065311967964099981788593947233) + sage: three_squares(7 * 2^41) + (1048576, 2097152, 3145728) + sage: three_squares(7 * 2^42) + Traceback (most recent call last): + ... + ValueError: 30786325577728 is not a sum of 3 squares + sage: three_squares(0) + (0, 0, 0) + sage: three_squares(-1) + Traceback (most recent call last): + ... + ValueError: -1 is not a sum of 3 squares + + TESTS:: + + sage: for _ in xrange(100): + ....: a = ZZ.random_element(2**16, 2**20) + ....: b = ZZ.random_element(2**16, 2**20) + ....: c = ZZ.random_element(2**16, 2**20) + ....: n = a**2 + b**2 + c**2 + ....: aa,bb,cc = three_squares(n) + ....: assert aa**2 + bb**2 + cc**2 == n + + ALGORITHM: + + See http://www.schorn.ch/howto.html """ - from math import sqrt - for i in range(0,int(sqrt(n))+1): - for j in range(i,int(sqrt(n-i**2))+1): - for k in range(j, int(sqrt(n-i**2-j**2))+1): - rem = n-i**2-j**2-k**2 - if rem >= 0: - l = int(sqrt(rem)) - if rem-l**2==0: - return [i,j,k,l] + from sage.rings.all import Integer + n = Integer(n) + + if n <= 0: + if n == 0: + z = ZZ.zero() + return (z, z, z) + raise ValueError("%s is not a sum of 3 squares"%n) + + if n.nbits() <= 32: + from sage.rings import sum_of_squares + return sum_of_squares.three_squares_pyx(n) + + # First, remove all factors 4 from n + e = n.valuation(2)//2 + m = ZZ.one() << e + N = n >> (2*e) + + # Let x be the largest integer at most sqrt(N) + x, r = N.sqrtrem() + # We need to check for this special case, + # otherwise N - x^2 will always factor. + if not r: + z = ZZ.zero() + return (z, z, x*m) + + # Consider different cases to find an x such that N - x^2 is easily + # written as the sum of 2 squares, because it is either p or 2p, + # with p a prime which is 1 mod 4. + if N % 4 == 1: + # Write N = x^2 + p with x even, p = 1 mod 4 prime + if x % 2 == 1: + x -= 1 + while x >= 0: + p = N - x*x + if p.is_pseudoprime(): + break + x -= 2 + elif N % 4 == 2: + # Write N = x^2 + p with x odd, p = 1 mod 4 prime + if x % 2 == 0: + x -= 1 + while x >= 0: + p = N - x*x + if p.is_pseudoprime(): + break + x -= 2 + elif N % 8 == 3: + # Write N = x^2 + 2p with x odd, p = 1 mod 4 prime + if x % 2 == 0: + x -= 1 + while x >= 0: + p = (N - x*x) >> 1 + if p.is_pseudoprime(): + break + x -= 2 + else: # 7 mod 8 + raise ValueError("%s is not a sum of 3 squares"%n) + + if x < 0: + # We found no good x, brute force instead. + # Normally, this should only happen for small values of N. + if N > 10000: + from warnings import warn + warn("Brute forcing sum of 3 squares for large N = %s"%N, RuntimeWarning) + x = N.isqrt() + + # In the usual case, this loop will only be executed once, since + # we already know the "right" value of x. + # This will only really loop if we hit the "x < 0" case above. + while True: + try: + a,b = two_squares(N - x*x) + break + except ValueError: + x -= 1 + assert x >= 0 + + if x >= b: + return (a*m, b*m, x*m) + elif x >= a: + return (a*m, x*m, b*m) + else: + return (x*m, a*m, b*m) def four_squares(n): """ - Computes the decomposition into the sum of four squares, - using an algorithm described by Peter Schorn at: - http://www.schorn.ch/howto.html. + Write the integer `n` as a sum of four integer squares. INPUT: - - ``n`` - an integer + - ``n`` -- an integer - OUTPUT: - - - a list of four numbers whose squares sum to n + OUTPUT: a tuple `(a,b,c,d)` of non-negative integers such that + `n = a^2 + b^2 + c^2 + d^2` with `a <= b <= c <= d`. EXAMPLES:: sage: four_squares(3) - [0, 1, 1, 1] + (0, 1, 1, 1) + sage: four_squares(13) + (0, 0, 2, 3) sage: four_squares(130) - [0, 0, 3, 11] + (0, 0, 3, 11) sage: four_squares(1101011011004) - [2, 1049178, 2370, 15196] - sage: sum([i-sum([q^2 for q in four_squares(i)]) for i in range(2,10000)]) # long time - 0 + (90, 102, 1220, 1049290) + sage: four_squares(10^100-1) + (155024616290, 2612183768627, 14142135623730950488016887, 99999999999999999999999999999999999999999999999999) + sage: for i in range(2^129, 2^129+10000): # long time + ....: S = four_squares(i) + ....: assert sum(x^2 for x in S) == i + + TESTS:: + + sage: for _ in xrange(100): + ....: n = ZZ.random_element(2**32,2**34) + ....: aa,bb,cc,dd = four_squares(n) + ....: assert aa**2 + bb**2 + cc**2 + dd**2 == n """ - from sage.rings.finite_rings.integer_mod import mod - from math import sqrt - from sage.rings.arith import _brute_force_four_squares - try: - ts = two_squares(n) - return [0,0,ts[0],ts[1]] - except ValueError: - pass - m = n - v = 0 - while mod(m,4) == 0: - v = v +1 - m = m // 4 - if mod(m,8) == 7: - d = 1 - m = m - 1 - else: - d = 0 - if mod(m,8)==3: - x = int(sqrt(m)) - if mod(x,2) == 0: - x = x - 1 - p = (m-x**2) // 2 - while not is_prime(p): - x = x - 2 - p = (m-x**2) // 2 - if x < 0: - # fall back to brute force - m = m + d - return [2**v*q for q in _brute_force_four_squares(m)] - y,z = two_squares(p) - return [2**v*q for q in [d,x,y+z,abs(y-z)]] - x = int(sqrt(m)) - p = m - x**2 - if p == 1: - return[2**v*q for q in [d,0,x,1]] - while not is_prime(p): - x = x - 1 - p = m - x**2 - if x < 0: - # fall back to brute force - m = m + d - return [2**v*q for q in _brute_force_four_squares(m)] - y,z = two_squares(p) - return [2**v*q for q in [d,x,y,z]] + from sage.rings.all import Integer + n = Integer(n) + if n <= 0: + if n == 0: + z = ZZ.zero() + return (z, z, z, z) + raise ValueError("%s is not a sum of 4 squares"%n) + + if n.nbits() <= 32: + from sage.rings import sum_of_squares + return sum_of_squares.four_squares_pyx(n) + + # First, remove all factors 4 from n + e = n.valuation(2) // 2 + m = ZZ.one() << e + N = n >> (2*e) + + # Subtract a suitable x^2 such that N - x^2 is 1,2,3,5,6 mod 8, + # which can then be written as a sum of 3 squares. + x = N.isqrt() + y = N - x*x + if y >= 7 and (y % 4 == 0 or y % 8 == 7): + x -= 1 + y += 2*x + 1 + + a,b,c = three_squares(y) + + # Correct sorting is guaranteed by construction + return (a*m, b*m, c*m, x*m) + +def sum_of_k_squares(k,n): + """ + Write the integer `n` as a sum of `k` integer squares if possible; + otherwise raise a ``ValueError``. + + INPUT: + + - ``k`` -- a non-negative integer + + - ``n`` -- an integer + + OUTPUT: a tuple `(x_1, ..., x_k)` of non-negative integers such that + their squares sum to `n`. + + EXAMPLES:: + + sage: sum_of_k_squares(2, 9634) + (15, 97) + sage: sum_of_k_squares(3, 9634) + (0, 15, 97) + sage: sum_of_k_squares(4, 9634) + (1, 2, 5, 98) + sage: sum_of_k_squares(5, 9634) + (0, 1, 2, 5, 98) + sage: sum_of_k_squares(6, 11^1111-1) + (19215400822645944253860920437586326284, 37204645194585992174252915693267578306, 3473654819477394665857484221256136567800161086815834297092488779216863122, 5860191799617673633547572610351797996721850737768032876360978911074629287841061578270832330322236796556721252602860754789786937515870682024273948, 20457423294558182494001919812379023992538802203730791019728543439765347851316366537094696896669915675685581905102118246887673397020172285247862426612188418787649371716686651256443143210952163970564228423098202682066311189439731080552623884051737264415984619097656479060977602722566383385989, 311628095411678159849237738619458396497534696043580912225334269371611836910345930320700816649653412141574887113710604828156159177769285115652741014638785285820578943010943846225597311231847997461959204894255074229895666356909071243390280307709880906261008237873840245959883405303580405277298513108957483306488193844321589356441983980532251051786704380984788999660195252373574924026139168936921591652831237741973242604363696352878914129671292072201700073286987126265965322808664802662993006926302359371379531571194266134916767573373504566621665949840469229781956838744551367172353) + sage: sum_of_k_squares(7, 0) + (0, 0, 0, 0, 0, 0, 0) + sage: sum_of_k_squares(30,999999) + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7, 44, 999) + sage: sum_of_k_squares(1, 9) + (3,) + sage: sum_of_k_squares(1, 10) + Traceback (most recent call last): + ... + ValueError: 10 is not a sum of 1 square + sage: sum_of_k_squares(1, -10) + Traceback (most recent call last): + ... + ValueError: -10 is not a sum of 1 square + sage: sum_of_k_squares(0, 9) + Traceback (most recent call last): + ... + ValueError: 9 is not a sum of 0 squares + sage: sum_of_k_squares(0, 0) + () + sage: sum_of_k_squares(7, -1) + Traceback (most recent call last): + ... + ValueError: -1 is not a sum of 7 squares + sage: sum_of_k_squares(-1, 0) + Traceback (most recent call last): + ... + ValueError: k = -1 must be non-negative + """ + from sage.rings.all import Integer + n = Integer(n) + k = int(k) + + if k <= 4: + if k == 4: + return four_squares(n) + if k == 3: + return three_squares(n) + if k == 2: + return two_squares(n) + if k == 1: + if n >= 0: + x, r = n.sqrtrem() + if not r: + return (x,) + raise ValueError("%s is not a sum of 1 square"%n) + if k == 0: + if n == 0: + return tuple() + raise ValueError("%s is not a sum of 0 squares"%n) + raise ValueError("k = %s must be non-negative"%k) + + if n < 0: + raise ValueError("%s is not a sum of %s squares"%(n,k)) + + # Recursively subtract the largest square + t = [] + while k > 4: + x = n.isqrt() + t.insert(0, x) + n -= x*x + k -= 1 + + t = list(four_squares(n)) + t + return tuple(t) def subfactorial(n): r""" diff --git a/src/sage/rings/complex_field.py b/src/sage/rings/complex_field.py index 5e549c8e6c8..61b229f5cca 100644 --- a/src/sage/rings/complex_field.py +++ b/src/sage/rings/complex_field.py @@ -677,3 +677,53 @@ def algebraic_closure(self): """ return self + def _factor_univariate_polynomial(self, f): + """ + Factor the univariate polynomial ``f``. + + INPUT: + + - ``f`` -- a univariate polynomial defined over the complex numbers + + OUTPUT: + + - A factorization of ``f`` over the complex numbers into a unit and + monic irreducible factors + + .. NOTE:: + + This is a helper method for + :meth:`sage.rings.polynomial.polynomial_element.Polynomial.factor`. + + This method calls PARI to compute the factorization. + + TESTS:: + + sage: k = ComplexField(100) + sage: R. = k[] + sage: k._factor_univariate_polynomial( x ) + x + sage: k._factor_univariate_polynomial( 2*x ) + (2.0000000000000000000000000000) * x + sage: k._factor_univariate_polynomial( x^2 ) + x^2 + sage: k._factor_univariate_polynomial( x^2 + 3 ) + (x - 1.7320508075688772935274463415*I) * (x + 1.7320508075688772935274463415*I) + sage: k._factor_univariate_polynomial( x^2 + 1 ) + (x - I) * (x + I) + sage: k._factor_univariate_polynomial( k(I) * (x^2 + 1) ) + (1.0000000000000000000000000000*I) * (x - I) * (x + I) + + """ + R = f.parent() + + # if the polynomial does not have complex coefficients, PARI will + # factor it over the reals. To make sure it has complex coefficients we + # multiply with I. + I = R.base_ring().gen() + g = f*I if f.leading_coefficient()!=I else f + + F = list(g._pari_with_name().factor()) + + from sage.structure.factorization import Factorization + return Factorization([(R(g).monic(),e) for g,e in zip(*F)], f.leading_coefficient()) diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index 37d0e5f41d0..10c75eb4490 100644 --- a/src/sage/rings/finite_rings/finite_field_base.pyx +++ b/src/sage/rings/finite_rings/finite_field_base.pyx @@ -7,7 +7,28 @@ TESTS:: sage: F = K.factor(3)[0][0].residue_field() sage: loads(dumps(F)) == F True + +AUTHORS: + +- Adrien Brochard, David Roe, Jeroen Demeyer, Julian Rueth, Niles Johnson, + Peter Bruin, Travis Scrimshaw, Xavier Caruso: initial version + """ +#***************************************************************************** +# Copyright (C) 2009 David Roe +# Copyright (C) 2010 Niles Johnson +# Copyright (C) 2011 Jeroen Demeyer +# Copyright (C) 2012 Adrien Brochard +# Copyright (C) 2012 Travis Scrimshaw +# Copyright (C) 2012 Xavier Caruso +# Copyright (C) 2013 Peter Bruin +# Copyright (C) 2014 Julian Rueth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** include "sage/ext/stdsage.pxi" from sage.categories.finite_fields import FiniteFields @@ -101,6 +122,19 @@ cdef class FiniteField(Field): category = FiniteFields() Field.__init__(self, base, names, normalize, category) + def is_perfect(self): + r""" + Return whether this field is perfect, i.e., every element has a `p`-th + root. Always returns ``True`` since finite fields are perfect. + + EXAMPLES:: + + sage: GF(2).is_perfect() + True + + """ + return True + def __repr__(self): """ String representation of this finite field. @@ -351,6 +385,108 @@ cdef class FiniteField(Field): else: return RingHomset(self, codomain, category) + def _squarefree_decomposition_univariate_polynomial(self, f): + """ + Return the square-free decomposition of this polynomial. This is a + partial factorization into square-free, coprime polynomials. + + This is a helper method for + :meth:`sage.rings.polynomial.squarefree_decomposition`. + + INPUT: + + - ``f`` -- a univariate non-zero polynomial over this field + + ALGORITHM; [Coh]_, algorithm 3.4.2 which is basically the algorithm in + [Yun]_ with special treatment for powers divisible by `p`. + + EXAMPLES:: + + sage: K. = GF(3^2) + sage: R. = K[] + sage: f = x^243+2*x^81+x^9+1 + sage: f.squarefree_decomposition() + (x^27 + 2*x^9 + x + 1)^9 + sage: f = x^243+a*x^27+1 + sage: f.squarefree_decomposition() + (x^9 + (2*a + 1)*x + 1)^27 + + TESTS:: + + sage: for K in [GF(2^18,'a'), GF(3^2,'a'), GF(47^3,'a')]: + ....: R. = K[] + ....: if K.characteristic() < 5: m = 4 + ....: else: m = 1 + ....: for _ in range(m): + ....: f = (R.random_element(4)^3*R.random_element(m)^(m+1))(x^6) + ....: F = f.squarefree_decomposition() + ....: assert F.prod() == f + ....: for i in range(len(F)): + ....: assert gcd(F[i][0], F[i][0].derivative()) == 1 + ....: for j in range(len(F)): + ....: if i == j: continue + ....: assert gcd(F[i][0], F[j][0]) == 1 + ....: + + REFERENCES: + + .. [Coh] H. Cohen, A Course in Computational Algebraic Number + Theory. Springer-Verlag, 1993. + + .. [Yun] Yun, David YY. On square-free decomposition algorithms. + In Proceedings of the third ACM symposium on Symbolic and algebraic + computation, pp. 26-35. ACM, 1976. + + """ + from sage.structure.factorization import Factorization + if f.degree() == 0: + return Factorization([], unit=f[0]) + + factors = [] + p = self.characteristic() + unit = f.leading_coefficient() + T0 = f.monic() + e = 1 + if T0.degree() > 0: + der = T0.derivative() + while der.is_zero(): + T0 = T0.parent()([T0[p*i].pth_root() for i in range(T0.degree()//p + 1)]) + if T0 == 1: + raise RuntimeError + der = T0.derivative() + e = e*p + T = T0.gcd(der) + V = T0 // T + k = 0 + while T0.degree() > 0: + k += 1 + if p.divides(k): + T = T // V + k += 1 + W = V.gcd(T) + if W.degree() < V.degree(): + factors.append((V // W, e*k)) + V = W + T = T // V + if V.degree() == 0: + if T.degree() == 0: + break + # T is of the form sum_{i=0}^n t_i X^{pi} + T0 = T0.parent()([T[p*i].pth_root() for i in range(T.degree()//p + 1)]) + der = T0.derivative() + e = p*e + while der.is_zero(): + T0 = T0.parent()([T0[p*i].pth_root() for i in range(T0.degree()//p + 1)]) + der = T0.derivative() + e = p*e + T = T0.gcd(der) + V = T0 // T + k = 0 + else: + T = T//V + + return Factorization(factors, unit=unit, sort=False) + def gen(self): r""" Return a generator of this field (over its prime field). As this is an diff --git a/src/sage/rings/fraction_field_FpT.pyx b/src/sage/rings/fraction_field_FpT.pyx index ae66b5e69f5..b24f3d00a56 100644 --- a/src/sage/rings/fraction_field_FpT.pyx +++ b/src/sage/rings/fraction_field_FpT.pyx @@ -1117,7 +1117,11 @@ cdef class Polyring_FpT_coerce(RingHomomorphism_coercion): ... ZeroDivisionError: fraction has denominator 0 """ - cdef Polynomial_zmod_flint x = _x + cdef Polynomial_zmod_flint x + try: + x = _x + except TypeError: + raise NotImplementedError('Fraction fields not implemented for this type.') cdef FpTElement ans = PY_NEW(FpTElement) ans._parent = self.codomain() ans.p = self.p diff --git a/src/sage/rings/function_field/constructor.py b/src/sage/rings/function_field/constructor.py index 78aadd710f6..db31b7eda9f 100644 --- a/src/sage/rings/function_field/constructor.py +++ b/src/sage/rings/function_field/constructor.py @@ -137,12 +137,27 @@ def create_key(self,polynomial,names): sage: K. = FunctionField(QQ) sage: R.=K[] sage: L. = K.extension(x-y^2) # indirect doctest + + TESTS: + + Verify that :trac:`16530` has been resolved:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^2-x) + sage: R. = L[] + sage: M. = L.extension(z-1) + sage: R. = K[] + sage: N. = K.extension(z-1) + sage: M is N + False + """ if names is None: names=polynomial.variable_name() if not isinstance(names,tuple): names=(names,) - return (polynomial,names) + return (polynomial,names,polynomial.base_ring()) def create_object(self,version,key,**extra_args): """ diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index 16abb0df13f..55b9ed6debf 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -7,12 +7,11 @@ - Robert Bradshaw (2010-05-30): added is_finite() -- Julian Rueth (2011-06-08): fixed hom(), extension() +- Julian Rueth (2011-06-08, 2011-09-14, 2014-06-23): fixed hom(), extension(); + use @cached_method; added derivation() - Maarten Derickx (2011-09-11): added doctests -- Julian Rueth (2011-09-14): use @cached_method - - Syed Ahmad Lavasani (2011-12-16): added genus(), is_RationalFunctionField() EXAMPLES: @@ -67,7 +66,7 @@ #***************************************************************************** # Copyright (C) 2010 William Stein # Copyright (C) 2010 Robert Bradshaw -# Copyright (C) 2011 Julian Rueth +# Copyright (C) 2011-2014 Julian Rueth # Copyright (C) 2011 Maarten Derickx # # Distributed under the terms of the GNU General Public License (GPL) @@ -113,6 +112,21 @@ class FunctionField(Field): sage: isinstance(K, sage.rings.function_field.function_field.FunctionField) True """ + def is_perfect(self): + r""" + Return whether this field is perfect, i.e., its characteristic is `p=0` + or every element has a `p`-th root. + + EXAMPLES:: + + sage: FunctionField(QQ, 'x').is_perfect() + True + sage: FunctionField(GF(2), 'x').is_perfect() + False + + """ + return self.characteristic() == 0 + def some_elements(self): """ Return a list of elements in the function field. @@ -376,7 +390,7 @@ def __init__(self, polynomial, names, - ``names`` -- variable names (as a tuple of length 1 or string) - ``category`` -- a category (defaults to category of function fields) - EXAMPLES:: + EXAMPLES: We create an extension of a function field:: @@ -463,9 +477,15 @@ def monic_integral_model(self, names): sage: A Function field in z defined by y^5 - x^12 sage: from_A - Morphism of function fields defined by z |--> x^3*y + Function Field morphism: + From: Function field in z defined by y^5 - x^12 + To: Function field in y defined by x^2*y^5 - 1/x + Defn: z |--> x^3*y sage: to_A - Morphism of function fields defined by y |--> 1/x^3*z + Function Field morphism: + From: Function field in y defined by x^2*y^5 - 1/x + To: Function field in z defined by y^5 - x^12 + Defn: y |--> 1/x^3*z sage: to_A(y) 1/x^3*z sage: from_A(to_A(y)) @@ -854,7 +874,8 @@ def hom(self, im_gens, base_morphism=None): We make the field automorphism that sends y to -y:: sage: f = L.hom(-y); f - Morphism of function fields defined by y |--> -y + Function Field endomorphism of Function field in y defined by y^2 - x^3 - 1 + Defn: y |--> -y Evaluation works:: @@ -871,7 +892,8 @@ def hom(self, im_gens, base_morphism=None): We make a morphism of the base rational function field:: sage: phi = K.hom(x+1); phi - Morphism of function fields defined by x |--> x + 1 + Function Field endomorphism of Rational function field in x over Rational Field + Defn: x |--> x + 1 sage: phi(x^3 - 3) x^3 + 3*x^2 + 3*x - 2 sage: (x+1)^3-3 @@ -881,7 +903,9 @@ def hom(self, im_gens, base_morphism=None): base generators go:: sage: L.hom([-y, x]) - Morphism of function fields defined by y |--> -y, x |--> x + Function Field endomorphism of Function field in y defined by y^2 - x^3 - 1 + Defn: y |--> -y + x |--> x The usage of the keyword base_morphism is not implemented yet:: @@ -898,7 +922,11 @@ def hom(self, im_gens, base_morphism=None): We define a morphism, by giving the images of generators:: sage: f = L.hom([4*w, t+1]); f - Morphism of function fields defined by y |--> 4*w, x |--> t + 1 + Function Field morphism: + From: Function field in y defined by y^2 - x^3 - 1 + To: Function field in w defined by 16*w^2 - t^3 - 3*t^2 - 3*t - 2 + Defn: y |--> 4*w + x |--> t + 1 Evaluation works, as expected:: @@ -915,7 +943,12 @@ def hom(self, im_gens, base_morphism=None): This is the function field L with the generators exchanged. We define a morphism to L:: sage: g = L3.hom([x,y]); g - Morphism of function fields defined by xx |--> x, yy |--> y + Function Field morphism: + From: Function field in xx defined by -xx^3 + yy^2 - 1 + To: Function field in y defined by y^2 - x^3 - 1 + Defn: xx |--> x + yy |--> y + """ if base_morphism is not None: raise NotImplementedError("Function field homorphisms with optional argument base_morphism are not implemented yet. Please specify the images of the generators of the base fields manually.") @@ -1018,7 +1051,10 @@ class RationalFunctionField(FunctionField): sage: K. = FunctionField(QQ) sage: L = FunctionField(QQ, 'tbar') # give variable name as second input sage: K.hom(L.gen()) - Morphism of function fields defined by t |--> tbar + Function Field morphism: + From: Rational function field in t over Rational Field + To: Rational function field in tbar over Rational Field + Defn: t |--> tbar """ def __init__(self, constant_field, names, element_class = FunctionFieldElement_rational, @@ -1354,7 +1390,8 @@ def hom(self, im_gens, base_morphism=None): sage: K. = FunctionField(GF(7)) sage: K.hom( (x^4 + 2)/x) - Morphism of function fields defined by x |--> (x^4 + 2)/x + Function Field endomorphism of Rational function field in x over Finite Field of size 7 + Defn: x |--> (x^4 + 2)/x We construct a map from a rational function field into a non-rational extension field:: @@ -1362,7 +1399,10 @@ def hom(self, im_gens, base_morphism=None): sage: K. = FunctionField(GF(7)); R. = K[] sage: L. = K.extension(y^3 + 6*x^3 + x) sage: f = K.hom(y^2 + y + 2); f - Morphism of function fields defined by x |--> y^2 + y + 2 + Function Field morphism: + From: Rational function field in x over Finite Field of size 7 + To: Function field in y defined by y^3 + 6*x^3 + x + Defn: x |--> y^2 + y + 2 sage: f(x) y^2 + y + 2 sage: f(x^2) @@ -1441,3 +1481,49 @@ def genus(self): """ return 0 + @cached_method + def derivation(self): + r""" + Return a generator of the space of derivations over the constant base + field of this function field. + + A derivation on `R` is a map `R \to R` with + `D(\alpha + \beta) = D(\alpha) + D(\beta)` and + `D(\alpha \beta) = \beta D(\alpha)+\alpha D(\beta)` + for all `\alpha, \beta \in R`. For a function + field `K(x)` with `K` perfect, the derivations form a one-dimensional + `K`-vector space generated by the extension of the usual derivation on + `K[x]` (cf. Proposition 10 in [GT1996]_.) + + OUTPUT: + + An endofunction on this function field. + + REFERENCES: + + .. [GT1996] + Gianni, P., & Trager, B. (1996). Square-free algorithms in + positive characteristic. Applicable Algebra in Engineering, + Communication and Computing, 7(1), 1-14. + + EXAMPLES:: + + sage: K. = FunctionField(GF(3)) + sage: K.derivation() + Derivation map: + From: Rational function field in x over Finite Field of size 3 + To: Rational function field in x over Finite Field of size 3 + + TESTS:: + + sage: L. = FunctionField(K) + sage: L.derivation() + Traceback (most recent call last): + ... + NotImplementedError: not implemented for non-perfect base fields + + """ + from maps import FunctionFieldDerivation_rational + if not self.constant_base_field().is_perfect(): + raise NotImplementedError("not implemented for non-perfect base fields") + return FunctionFieldDerivation_rational(self, self.one()) diff --git a/src/sage/rings/function_field/maps.py b/src/sage/rings/function_field/maps.py index 4763d1d3501..01462a9e5fd 100644 --- a/src/sage/rings/function_field/maps.py +++ b/src/sage/rings/function_field/maps.py @@ -5,18 +5,25 @@ - William Stein (2010): initial version -- Julian Rueth (2011-09-14): refactored class hierarchy +- Julian Rueth (2011-09-14, 2014-06-23): refactored class hierarchy; added + derivation classes EXAMPLES:: sage: K. = FunctionField(QQ); R. = K[] sage: K.hom(1/x) - Morphism of function fields defined by x |--> 1/x + Function Field endomorphism of Rational function field in x over Rational Field + Defn: x |--> 1/x sage: L. = K.extension(y^2-x) sage: K.hom(y) - Morphism of function fields defined by x |--> y + Function Field morphism: + From: Rational function field in x over Rational Field + To: Function field in y defined by y^2 - x + Defn: x |--> y sage: L.hom([y,x]) - Morphism of function fields defined by y |--> y, x |--> x + Function Field endomorphism of Function field in y defined by y^2 - x + Defn: y |--> y + x |--> x sage: L.hom([x,y]) Traceback (most recent call last): ... @@ -24,7 +31,7 @@ """ #***************************************************************************** # Copyright (C) 2010 William Stein -# Copyright (C) 2011 Julian Rueth +# Copyright (C) 2011-2014 Julian Rueth # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -33,8 +40,144 @@ #***************************************************************************** from sage.categories.morphism import Morphism +from sage.categories.map import Map from sage.rings.morphism import RingHomomorphism +class FunctionFieldDerivation(Map): + r""" + A base class for derivations on function fields. + + A derivation on `R` is map `R\to R` with + `D(\alpha+\beta)=D(\alpha)+D(\beta)` and `D(\alpha\beta)=\beta + D(\alpha)+\alpha D(\beta)` for all `\alpha,\beta\in R`. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: d = K.derivation() + sage: isinstance(d, sage.rings.function_field.maps.FunctionFieldDerivation) + True + + """ + def __init__(self, K): + r""" + Initialize a derivation from ``K`` to ``K``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: d = K.derivation() # indirect doctest + + """ + from function_field import is_FunctionField + if not is_FunctionField(K): + raise ValueError("K must be a function field") + self.__field = K + from sage.categories.homset import Hom + from sage.categories.sets_cat import Sets + Map.__init__(self, Hom(K,K,Sets())) + + def _repr_type(self): + r""" + Return the type of this map (a derivation), for the purposes of printing out self. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: d = K.derivation() + sage: d._repr_type() + 'Derivation' + + """ + return "Derivation" + + def is_injective(self): + r""" + Return whether this derivation is injective. + + OUTPUT: + + Returns ``False`` since derivations are never injective. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: d = K.derivation() + sage: d.is_injective() + False + + """ + return False + +class FunctionFieldDerivation_rational(FunctionFieldDerivation): + r""" + A derivation on a rational function field. + + INPUT: + + - ``K`` -- a rational function field + + - ``u`` -- an element of ``K``, the image of the generator of ``K`` under + the derivation. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: d = K.derivation() + sage: isinstance(d, sage.rings.function_field.maps.FunctionFieldDerivation_rational) + True + + """ + def __init__(self, K, u): + r""" + Initialize a derivation of ``K`` which sends the generator of ``K`` to + ``u``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: d = K.derivation() # indirect doctest + + """ + from function_field import is_RationalFunctionField + if not is_RationalFunctionField(K): + raise ValueError("K must be a rational function field") + if u.parent() is not K: + raise ValueError("u must be an element in K") + FunctionFieldDerivation.__init__(self, K) + self._u = u + + def _call_(self, x): + r""" + Compute the derivation of ``x``. + + INPUT: + + - ``x`` -- an element of the rational function field + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: d = K.derivation() + sage: d(x) # indirect doctest + 1 + sage: d(x^3) + 3*x^2 + sage: d(1/x) + -1/x^2 + + """ + f,g = x.numerator(),x.denominator() + + if not f.gcd(g).is_one(): + raise NotImplementedError("derivations only implemented for rational functions with coprime numerator and denominator.") + + numerator = f.derivative()*g - f*g.derivative() + if numerator.is_zero(): + return self.codomain().zero() + else: + return self._u * self.codomain()( numerator / g**2 ) + class FunctionFieldIsomorphism(Morphism): r""" A base class for isomorphisms between function fields and @@ -50,7 +193,8 @@ class FunctionFieldIsomorphism(Morphism): """ def _repr_type(self): """ - Return the type of this map (an isomorphism), for the purposes of printing out self. + Return the type of this map (an isomorphism), for the purposes of + printing this map. EXAMPLES:: @@ -218,6 +362,9 @@ def codomain(self): def _repr_type(self): """ + Return the type of this map (an isomorphism), for the purposes of + printing this map. + EXAMPLES:: sage: K. = FunctionField(QQ); R. = K[] @@ -253,7 +400,8 @@ def __init__(self, parent, im_gen, base_morphism): sage: K. = FunctionField(QQ) sage: f = K.hom(1/x); f - Morphism of function fields defined by x |--> 1/x + Function Field endomorphism of Rational function field in x over Rational Field + Defn: x |--> 1/x sage: isinstance(f, sage.rings.function_field.maps.FunctionFieldMorphism) True """ @@ -270,37 +418,42 @@ def is_injective(self): sage: K. = FunctionField(QQ) sage: f = K.hom(1/x); f - Morphism of function fields defined by x |--> 1/x + Function Field endomorphism of Rational function field in x over Rational Field + Defn: x |--> 1/x sage: f.is_injective() True """ return True - def __repr__(self): - """ + def _repr_type(self): + r""" + Return the type of this map (a morphism of function fields), for the + purposes of printing this map. + EXAMPLES:: sage: K. = FunctionField(GF(7)); R. = K[] sage: L. = K.extension(y^3 + 6*x^3 + x) sage: f = L.hom(y*2) - sage: f.__repr__() - 'Morphism of function fields defined by y |--> 2*y' + sage: f._repr_type() + 'Function Field' + """ - return "Morphism of function fields defined by %s"%self._short_repr() + return "Function Field" - def _short_repr(self): + def _repr_defn(self): """ EXAMPLES:: sage: K. = FunctionField(GF(7)); R. = K[] sage: L. = K.extension(y^3 + 6*x^3 + x) sage: f = L.hom(y*2) - sage: f._short_repr() + sage: f._repr_defn() 'y |--> 2*y' """ a = '%s |--> %s'%(self.domain().gen(), self._im_gen) if self._base_morphism is not None: - a += ', ' + self._base_morphism._short_repr() + a += '\n' + self._base_morphism._repr_defn() return a class FunctionFieldMorphism_polymod(FunctionFieldMorphism): @@ -312,7 +465,8 @@ class FunctionFieldMorphism_polymod(FunctionFieldMorphism): sage: K. = FunctionField(QQ); R. = K[] sage: L. = K.extension(y^2 - x) sage: f = L.hom(-y); f - Morphism of function fields defined by y |--> -y + Function Field endomorphism of Function field in y defined by y^2 - x + Defn: y |--> -y """ def __init__(self, parent, im_gen, base_morphism): """ @@ -321,7 +475,8 @@ def __init__(self, parent, im_gen, base_morphism): sage: K. = FunctionField(GF(7)); R. = K[] sage: L. = K.extension(y^3 + 6*x^3 + x) sage: f = L.hom(y*2); f - Morphism of function fields defined by y |--> 2*y + Function Field endomorphism of Function field in y defined by y^3 + 6*x^3 + x + Defn: y |--> 2*y sage: type(f) sage: factor(L.polynomial()) @@ -364,7 +519,8 @@ class FunctionFieldMorphism_rational(FunctionFieldMorphism): sage: K. = FunctionField(QQ) sage: f = K.hom(1/x); f - Morphism of function fields defined by x |--> 1/x + Function Field endomorphism of Rational function field in x over Rational Field + Defn: x |--> 1/x """ def __init__(self, parent, im_gen): """ @@ -372,7 +528,8 @@ def __init__(self, parent, im_gen): sage: K. = FunctionField(GF(7)) sage: f = K.hom(1/x); f - Morphism of function fields defined by x |--> 1/x + Function Field endomorphism of Rational function field in x over Finite Field of size 7 + Defn: x |--> 1/x sage: type(f) """ @@ -384,7 +541,8 @@ def _call_(self, x): sage: K. = FunctionField(GF(7)) sage: f = K.hom(1/x); f - Morphism of function fields defined by x |--> 1/x + Function Field endomorphism of Rational function field in x over Finite Field of size 7 + Defn: x |--> 1/x sage: f(x+1) # indirect doctest (x + 1)/x sage: 1/x + 1 diff --git a/src/sage/rings/integer_ring.pyx b/src/sage/rings/integer_ring.pyx index b2e43e04b5f..6c31232ea9e 100644 --- a/src/sage/rings/integer_ring.pyx +++ b/src/sage/rings/integer_ring.pyx @@ -1238,8 +1238,8 @@ cdef class IntegerRing_class(PrincipalIdealDomain): EXAMPLES:: - sage: magma(ZZ) # optional - magma - Integer Ring # indirect doctest + sage: magma(ZZ) # indirect doctest, optional - magma + Integer Ring """ return 'IntegerRing()' diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx index ab8209e78d5..8548fce3373 100644 --- a/src/sage/rings/number_field/number_field_element.pyx +++ b/src/sage/rings/number_field/number_field_element.pyx @@ -891,12 +891,16 @@ cdef class NumberFieldElement(FieldElement): sage: a.abs(prec=128) 1.2599210498948731647672106072782283506 """ - return self.abs(prec=53, i=0) + return self.abs(prec=53, i=None) - def abs(self, prec=53, i=0): + def abs(self, prec=53, i=None): r""" - Return the absolute value of this element with respect to the - `i`-th complex embedding of parent, to the given precision. + Return the absolute value of this element. + + If ``i`` is provided, then the absolute of the `i`-th embedding is + given. Otherwise, if the number field as a defined embedding into `\CC` + then the corresponding absolute value is returned and if there is none, + it corresponds to the choice ``i=0``. If prec is 53 (the default), then the complex double field is used; otherwise the arbitrary precision (but slow) complex @@ -938,9 +942,24 @@ cdef class NumberFieldElement(FieldElement): 0.414213562373095 sage: a.abs(i=1) 2.41421356237309 + + Check that :trac:`16147` is fixed:: + + sage: x = polygen(ZZ) + sage: f = x^3 - x - 1 + sage: beta = f.complex_roots()[0]; beta + 1.32471795724475 + sage: K. = NumberField(f, embedding=beta) + sage: b.abs() + 1.32471795724475 """ - P = self.number_field().complex_embeddings(prec)[i] - return abs(P(self)) + CCprec = ComplexField(prec) + if i is None and CCprec.has_coerce_map_from(self.parent()): + return CCprec(self).abs() + else: + i = 0 if i is None else i + P = self.number_field().complex_embeddings(prec)[i] + return P(self).abs() def abs_non_arch(self, P, prec=None): r""" diff --git a/src/sage/rings/padics/padic_ZZ_pX_CR_element.pyx b/src/sage/rings/padics/padic_ZZ_pX_CR_element.pyx index 4b115f20a93..db7c42bc08f 100644 --- a/src/sage/rings/padics/padic_ZZ_pX_CR_element.pyx +++ b/src/sage/rings/padics/padic_ZZ_pX_CR_element.pyx @@ -163,15 +163,18 @@ NOTES:: AUTHORS: -- David Roe (2008-01-01) initial version +- David Roe (2008-01-01): initial version -- Robert Harron (2011-09) fixes/enhancements +- Robert Harron (2011-09): fixes/enhancements + +- Julian Rueth (2014-05-09): enable caching through ``_cache_key`` """ #***************************************************************************** # Copyright (C) 2008 David Roe # William Stein +# 2014 Julian Rueth # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -469,6 +472,58 @@ cdef class pAdicZZpXCRElement(pAdicZZpXElement): else: self._set_from_list_both(x, aprec, rprec) + def _cache_key(self): + r""" + Return a hashable key which identifies this element. + + This makes it possible to use this element in caches such as + functions or methods decorated with ``@cached_function`` or + ``@cached_method`` respectively. + + EXAMPLE: + + In the following example, ``a`` and ``b`` compare equal. They cannot + have a meaningful hash value since then their hash value would have to + be the same:: + + sage: K. = Qq(9) + sage: b = a + O(3) + sage: a == b + True + sage: hash(a) + Traceback (most recent call last): + ... + TypeError: unhashable type: 'sage.rings.padics.padic_ZZ_pX_CR_element.pAdicZZpXCRElement' + + However, we want to cache computations which depend on them. Therefore + they define a ``_cache_key`` which is hashable and uniquely identifies + them:: + + sage: a._cache_key() + (..., ((0, 1),), 0, 20) + sage: b._cache_key() + (..., ((0, 1),), 0, 1) + + TESTS: + + Check that zero values are handled correctly:: + + sage: K.zero()._cache_key() + (..., 0) + sage: K(0,1)._cache_key() + (..., 0, 1) + + """ + if self._is_exact_zero(): + return (self.parent(), 0) + elif self._is_inexact_zero(): + return (self.parent(), 0, self.valuation()) + else: + return (self.parent(), + tuple(tuple(c) if isinstance(c, list) else c + for c in self.unit_part().list()), + self.valuation(), self.precision_relative()) + cdef int _set_inexact_zero(self, long absprec) except -1: """ Sets ``self`` to be zero with valuation absprec. diff --git a/src/sage/rings/padics/padic_ZZ_pX_element.pyx b/src/sage/rings/padics/padic_ZZ_pX_element.pyx index 9cfd91538cd..d49f1e518c3 100644 --- a/src/sage/rings/padics/padic_ZZ_pX_element.pyx +++ b/src/sage/rings/padics/padic_ZZ_pX_element.pyx @@ -523,6 +523,30 @@ cdef class pAdicZZpXElement(pAdicExtElement): """ return self.prime_pow + cdef int _pshift_self(self, long shift) except -1: + """ + Multiplies this element by ``p^shift``. + + TESTS: + + Check that :trac:`13647` has been fixed:: + + sage: K = ZpCA(3) + sage: R. = K[] + sage: L. = K.extension(u^2 + 1) + sage: L(R.gen()) + u + O(3^20) + + sage: K = ZpFM(3) + sage: R. = K[] + sage: L. = K.extension(u^2 + 1) + sage: L(R.gen()) + u + O(3^20) + + """ + if shift != 0: + raise NotImplementedError + def _test_preprocess_list(R, L): """ Given a list of elements convertible to ``ntl_ZZ_p``s, finds the diff --git a/src/sage/rings/polynomial/all.py b/src/sage/rings/polynomial/all.py index 194f885601f..3c4f8aae025 100644 --- a/src/sage/rings/polynomial/all.py +++ b/src/sage/rings/polynomial/all.py @@ -17,6 +17,8 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.misc.lazy_import import lazy_import + # Quotient of polynomial ring from sage.rings.polynomial.polynomial_quotient_ring import PolynomialQuotientRing from sage.rings.polynomial.polynomial_quotient_ring_element import PolynomialQuotientRingElement @@ -43,4 +45,4 @@ from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing # Evaluation of cyclotomic polynomials -from sage.rings.polynomial.cyclotomic import cyclotomic_value +from sage.rings.polynomial.cyclotomic import cyclotomic_value \ No newline at end of file diff --git a/src/sage/rings/polynomial/multi_polynomial.pyx b/src/sage/rings/polynomial/multi_polynomial.pyx index 49431a7d780..ffb7697505f 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pyx +++ b/src/sage/rings/polynomial/multi_polynomial.pyx @@ -1212,6 +1212,134 @@ cdef class MPolynomial(CommutativeRingElement): return M + def macaulay_resultant(self, *args): + r""" + This is an implementation of the Macaulay Resultant. It computes + the resultant of universal polynomials as well as polynomials + with constant coefficients. This is a project done in + sage days 55. It's based on the implementation in Maple by + Manfred Minimair, which in turn is based on the references [CLO], [Can], [Mac]. + It calculates the Macaulay resultant for a list of Polynomials, + up to sign! + + AUTHORS: + + - Hao Chen, Solomon Vishkautsan (7-2014) + + INPUT: + + - ``args`` -- a list of `n-1` homogeneous polynomials in `n` variables. + works when ``args[0]`` is the list of polynomials, + or ``args`` is itself the list of polynomials + + OUTPUT: + + - the macaulay resultant + + EXAMPLES: + + The number of polynomials has to match the number of variables:: + + sage: R. = PolynomialRing(QQ,3) + sage: y.macaulay_resultant(x+z) + Traceback (most recent call last): + ... + TypeError: number of polynomials(= 2) must equal number of variables (= 3) + + The polynomials need to be all homogeneous:: + + sage: R. = PolynomialRing(QQ,3) + sage: y.macaulay_resultant([x+z, z+x^3]) + Traceback (most recent call last): + ... + TypeError: resultant for non-homogeneous polynomials is not supported + + All polynomials must be in the same ring:: + + sage: R. = PolynomialRing(QQ,3) + sage: S. = PolynomialRing(QQ, 2) + sage: y.macaulay_resultant(z+x,z) + Traceback (most recent call last): + ... + TypeError: not all inputs are polynomials in the calling ring + + The following example recreates Proposition 2.10 in Ch.3 of Using Algebraic Geometry:: + + sage: K. = PolynomialRing(ZZ, 2) + sage: flist,R = K._macaulay_resultant_universal_polynomials([1,1,2]) + sage: flist[0].macaulay_resultant(flist[1:]) + u2^2*u4^2*u6 - 2*u1*u2*u4*u5*u6 + u1^2*u5^2*u6 - u2^2*u3*u4*u7 + u1*u2*u3*u5*u7 + u0*u2*u4*u5*u7 - u0*u1*u5^2*u7 + u1*u2*u3*u4*u8 - u0*u2*u4^2*u8 - u1^2*u3*u5*u8 + u0*u1*u4*u5*u8 + u2^2*u3^2*u9 - 2*u0*u2*u3*u5*u9 + u0^2*u5^2*u9 - u1*u2*u3^2*u10 + u0*u2*u3*u4*u10 + u0*u1*u3*u5*u10 - u0^2*u4*u5*u10 + u1^2*u3^2*u11 - 2*u0*u1*u3*u4*u11 + u0^2*u4^2*u11 + + The following example degenerates into the determinant of a `3*3` matrix:: + + sage: K. = PolynomialRing(ZZ, 2) + sage: flist,R = K._macaulay_resultant_universal_polynomials([1,1,1]) + sage: flist[0].macaulay_resultant(flist[1:]) + -u2*u4*u6 + u1*u5*u6 + u2*u3*u7 - u0*u5*u7 - u1*u3*u8 + u0*u4*u8 + + The following example is by Patrick Ingram(arxiv:1310.4114):: + + sage: U = PolynomialRing(ZZ,'y',2); y0,y1 = U.gens() + sage: R = PolynomialRing(U,'x',3); x0,x1,x2 = R.gens() + sage: f0 = y0*x2^2 - x0^2 + 2*x1*x2 + sage: f1 = y1*x2^2 - x1^2 + 2*x0*x2 + sage: f2 = x0*x1 - x2^2 + sage: f0.macaulay_resultant(f1,f2) + y0^2*y1^2 - 4*y0^3 - 4*y1^3 + 18*y0*y1 - 27 + + a simple example with constant rational coefficients:: + + sage: R. = PolynomialRing(QQ,4) + sage: w.macaulay_resultant([z,y,x]) + 1 + + an example where the resultant vanishes:: + + sage: R. = PolynomialRing(QQ,3) + sage: (x+y).macaulay_resultant([y^2,x]) + 0 + + an example of bad reduction at a prime ``p = 5``:: + + sage: R. = PolynomialRing(QQ,3) + sage: y.macaulay_resultant([x^3+25*y^2*x,5*z]) + 125 + + The input can given as an unpacked list of polynomials:: + + sage: R. = PolynomialRing(QQ,3) + sage: y.macaulay_resultant(x^3+25*y^2*x,5*z) + 125 + + an example when the coefficients live in a finite field:: + + sage: F = FiniteField(11) + sage: R. = PolynomialRing(F,4) + sage: z.macaulay_resultant([x^3,5*y,w]) + 4 + + example when the denominator in the algorithm vanishes(in this case + the resultant is the constant term of the quotient of + char polynomials of numerator/denominator):: + + sage: R. = PolynomialRing(QQ,3) + sage: y.macaulay_resultant([x+z, z^2]) + -1 + + when there are only 2 polynomials, macaulay resultant degenerates to the traditional resultant:: + + sage: R. = PolynomialRing(QQ,1) + sage: f = x^2+1; g = x^5+1 + sage: fh = f.homogenize() + sage: gh = g.homogenize() + sage: RH = fh.parent() + sage: f.resultant(g) == fh.macaulay_resultant(gh) + True + + """ + if len(args) == 1 and isinstance(args[0],list): + return self.parent().macaulay_resultant(self, *args[0]) + return self.parent().macaulay_resultant(self, *args) def denominator(self): """ diff --git a/src/sage/rings/polynomial/multi_polynomial_element.py b/src/sage/rings/polynomial/multi_polynomial_element.py index cc847adee77..b0aae126113 100644 --- a/src/sage/rings/polynomial/multi_polynomial_element.py +++ b/src/sage/rings/polynomial/multi_polynomial_element.py @@ -1603,6 +1603,13 @@ def factor(self, proof=True): Traceback (most recent call last): ... NotImplementedError: Factorization of multivariate polynomials over prime fields with characteristic > 2^29 is not implemented. + + We check that the original issue in :trac:`7554` is fixed:: + + sage: K. = PolynomialRing(QQ) + sage: R. = PolynomialRing(FractionField(K)) + sage: factor(x) + x """ R = self.parent() diff --git a/src/sage/rings/polynomial/multi_polynomial_ideal.py b/src/sage/rings/polynomial/multi_polynomial_ideal.py index ae57cf7d302..c081c897048 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ideal.py +++ b/src/sage/rings/polynomial/multi_polynomial_ideal.py @@ -1498,6 +1498,15 @@ def vector_space_dimension(self): 0 sage: I.vector_space_dimension() 4 + + When the ideal is not zero-dimensional, we return infinity:: + + sage: R. = PolynomialRing(QQ) + sage: I = R.ideal(x) + sage: I.dimension() + 1 + sage: I.vector_space_dimension() + +Infinity """ R = self.ring() gb = R.ideal(self.groebner_basis()) @@ -1507,7 +1516,8 @@ def vector_space_dimension(self): vd = Integer(vdim(gb, attributes={gb:{'isSB':1}})) if vd == -1: - raise TypeError("ideal is not zero dimensional") + from sage.rings.infinity import Infinity + return Infinity else: return vd @@ -2593,6 +2603,13 @@ def variety(self, ring=None): sage: I.variety() [] + Check that the issue at :trac:`16485` is fixed:: + + sage: R. = PolynomialRing(QQ, order='lex') + sage: I = R.ideal(c^2-2, b-c, a) + sage: I.variety(QQbar) + [...a: 0...] + ALGORITHM: Uses triangular decomposition. @@ -2613,9 +2630,9 @@ def _variety(T, V, v=None): return V variable = f.variable(0) - roots = f.univariate_polynomial().roots(ring=ring) + roots = f.univariate_polynomial().roots(ring=ring, multiplicities=False) - for root,_ in roots: + for root in roots: vbar = v.copy() vbar[variable] = root Tbar = [ f.subs({variable:root}) for f in T ] @@ -2646,7 +2663,7 @@ def _variety(T, V, v=None): V = [] for t in T: - Vbar = _variety(list(t),[]) + Vbar = _variety([P(f) for f in t], []) #Vbar = _variety(list(t.gens()),[]) for v in Vbar: diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index bf10e290cd4..3b6cbdbe84c 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -177,7 +177,7 @@ from sage.libs.singular.decl cimport ( p_NSet, p_GetCoeff, p_Delete, p_GetExp, pNext, rRingVar, omAlloc0, omStrDup, omFree, pDivide, p_SetCoeff0, n_Init, p_DivisibleBy, pLcm, p_LmDivisibleBy, pDivide, p_IsConstant, p_ExpVectorEqual, p_String, p_LmInit, n_Copy, - p_IsUnit, pInvers, p_Head, pSubst, idInit, fast_map, id_Delete, + p_IsUnit, pInvers, p_Head, idInit, fast_map, id_Delete, pIsHomogeneous, pHomogen, p_Totaldegree, singclap_pdivide, singclap_factorize, delete, idLift, IDELEMS, On, Off, SW_USE_CHINREM_GCD, SW_USE_EZGCD, p_LmIsConstant, pTakeOutComp1, singclap_gcd, pp_Mult_qq, p_GetMaxExp, @@ -195,7 +195,7 @@ from sage.libs.singular.polynomial cimport ( singular_polynomial_mul, singular_polynomial_div_coeff, singular_polynomial_pow, singular_polynomial_str, singular_polynomial_latex, singular_polynomial_str_with_changed_varnames, singular_polynomial_deg, - singular_polynomial_length_bounded ) + singular_polynomial_length_bounded, singular_polynomial_subst ) # singular rings from sage.libs.singular.ring cimport singular_ring_new, singular_ring_reference, singular_ring_delete @@ -731,7 +731,9 @@ cdef class MPolynomialRing_libsingular(MPolynomialRing_generic): 2 """ - cdef poly *_p, *mon, *El_poly + cdef poly *_p + cdef poly *mon + cdef poly *El_poly cdef ring *_ring = self._ring cdef number *_n cdef ring *El_ring @@ -1520,7 +1522,8 @@ cdef class MPolynomialRing_libsingular(MPolynomialRing_generic): z^3 + w^3 - z*w """ cdef ring *_ring = self._ring - cdef char **_names, **_orig_names + cdef char **_names + cdef char **_orig_names cdef char *_name cdef int i @@ -1617,7 +1620,8 @@ cdef class MPolynomialRing_libsingular(MPolynomialRing_generic): """ cdef poly *res cdef ring *r = self._ring - cdef number *n, *denom + cdef number *n + cdef number *denom if not self is f._parent: f = self._coerce_c(f) @@ -1832,7 +1836,8 @@ cdef class MPolynomialRing_libsingular(MPolynomialRing_generic): """ cdef int i cdef ring *r - cdef poly *p, *q + cdef poly *p + cdef poly *q if h._parent is not g._parent: g = h._parent._coerce_c(g) @@ -3266,9 +3271,39 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn sage: f = y sage: f.subs({y:x}).subs({x:z}) z + + We test that we change the ring even if there is nothing to do:: + + sage: P = QQ['x,y'] + sage: x = var('x') + sage: parent(P.zero_element() / x) + Symbolic Ring + + We are catching overflows:: + + sage: R. = QQ[] + sage: n=1000; f = x^n + sage: try: + ....: f.subs(x = x^n) + ....: print "no overflow" + ....: except OverflowError: + ....: print "overflow" + overflow # 32-bit + x^1000000 # 64-bit + no overflow # 64-bit + + sage: n=100000; + sage: try: + ....: f = x^n + ....: f.subs(x = x^n) + ....: print "no overflow" + ....: except OverflowError: + ....: print "overflow" + overflow """ cdef int mi, i, need_map, try_symbolic + cdef unsigned long degree = 0 cdef MPolynomialRing_libsingular parent = self._parent cdef ring *_ring = parent._ring @@ -3283,7 +3318,11 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn need_map = 0 try_symbolic = 0 - if fixed is not None: + if self.is_zero(): + # there is nothing to do except to change the ring + try_symbolic = 1 + + if not try_symbolic and fixed is not None: for m,v in fixed.iteritems(): if isinstance(m, (int, Integer)): mi = m+1 @@ -3293,9 +3332,13 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn mi = i break if i > _ring.N: - raise TypeError, "key does not match" + id_Delete(&to_id, _ring) + p_Delete(&_p, _ring) + raise TypeError("key does not match") else: - raise TypeError, "keys do not match self's parent" + id_Delete(&to_id, _ring) + p_Delete(&_p, _ring) + raise TypeError("keys do not match self's parent") try: v = parent._coerce_c(v) except TypeError: @@ -3303,10 +3346,14 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn break _f = (v)._poly if p_IsConstant(_f, _ring): - if(_ring != currRing): rChangeCurrRing(_ring) - _p = pSubst(_p, mi, _f) + singular_polynomial_subst(&_p, mi-1, _f, _ring) else: need_map = 1 + degree = p_GetExp(_p, mi, _ring) * p_GetMaxExp(_f, _ring) + if degree > _ring.bitmask: + id_Delete(&to_id, _ring) + p_Delete(&_p, _ring) + raise OverflowError("Exponent overflow (%d)."%(degree)) to_id.m[mi-1] = p_Copy(_f, _ring) if not try_symbolic: @@ -3318,7 +3365,9 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn mi = i break if i > _ring.N: - raise TypeError, "key does not match" + id_Delete(&to_id, _ring) + p_Delete(&_p, _ring) + raise TypeError("key does not match") try: v = parent._coerce_c(v) except TypeError: @@ -3326,12 +3375,16 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn break _f = (v)._poly if p_IsConstant(_f, _ring): - if(_ring != currRing): rChangeCurrRing(_ring) - _p = pSubst(_p, mi, _f) + singular_polynomial_subst(&_p, mi-1, _f, _ring) else: if to_id.m[mi-1] != NULL: p_Delete(&to_id.m[mi-1],_ring) to_id.m[mi-1] = p_Copy(_f, _ring) + degree = p_GetExp(_p, mi, _ring) * p_GetMaxExp(_f, _ring) + if degree > _ring.bitmask: + id_Delete(&to_id, _ring) + p_Delete(&_p, _ring) + raise OverflowError("Exponent overflow (%d)."%(degree)) need_map = 1 if need_map: @@ -3379,6 +3432,7 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn g[mi-1] = v + gd = parent.gens_dict() for m,v in kw.iteritems(): m = gd[m] for i from 0 < i <= _ring.N: @@ -3610,7 +3664,8 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn sage: f.variables() (x, z) """ - cdef poly *p, *v + cdef poly *p + cdef poly *v cdef ring *r = self._parent_ring if(r != currRing): rChangeCurrRing(r) cdef int i @@ -3844,7 +3899,9 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn cdef ring *r = self._parent_ring if(r != currRing): rChangeCurrRing(r) cdef MPolynomial_libsingular _self, _right - cdef poly *quo, *temp, *p + cdef poly *quo + cdef poly *temp + cdef poly *p _self = self @@ -4476,7 +4533,9 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn (a^2 + a)*x^6*y + (a^3 + a - 1)*x^4*y + (-a)*x^4 """ cdef ring *_ring = self._parent_ring - cdef poly *ret, *prod, *gcd + cdef poly *ret + cdef poly *prod + cdef poly *gcd cdef MPolynomial_libsingular _g if _ring!=currRing: rChangeCurrRing(_ring) @@ -4567,7 +4626,8 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn ZeroDivisionError """ - cdef poly *quo, *rem + cdef poly *quo + cdef poly *rem cdef MPolynomialRing_libsingular parent = self._parent cdef ring *r = self._parent_ring if r!=currRing: rChangeCurrRing(r) @@ -4923,7 +4983,8 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn except ValueError: raise TypeError("not a variable in the same ring as self") - cdef poly *_p, *mon + cdef poly *_p + cdef poly *mon cdef ring *_ring = self._parent_ring if _ring != currRing: rChangeCurrRing(_ring) @@ -5233,7 +5294,8 @@ def unpickle_MPolynomial_libsingular(MPolynomialRing_libsingular R, d): True """ cdef ring *r = R._ring - cdef poly *m, *p + cdef poly *m + cdef poly *p cdef int _i, _e p = p_ISet(0,r) rChangeCurrRing(r) diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx index 2583e3a1c4f..9dd9af35db5 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx @@ -17,6 +17,11 @@ import polynomial_ring from sage.categories.commutative_rings import CommutativeRings _CommutativeRings = CommutativeRings() from sage.rings.polynomial.polynomial_ring_constructor import polynomial_default_category +# added for macaulay_resultant: +from sage.misc.misc_c import prod +from sage.combinat.integer_vector import IntegerVectors +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.arith import binomial def is_MPolynomialRing(x): return bool(PY_TYPE_CHECK(x, MPolynomialRing_generic)) @@ -880,6 +885,309 @@ cdef class MPolynomialRing_generic(sage.rings.ring.CommutativeRing): from polynomial_ring_constructor import PolynomialRing return PolynomialRing(base_ring, self.ngens(), names, order=order) + def _macaulay_resultant_getS(self,mon_deg_tuple,dlist): + r""" + In the Macaulay resultant algorithm the list of all monomials of the total degree is partitioned into sets `S_i`. + This function returns the index `i` for the set `S_i` for the given monomial. + + INPUT: + + - ``mon_deg_tuple`` -- a list representing a monomial of a degree `d` + - ``dlist`` -- a list of degrees `d_i` of the polynomials in question, where + `d = sum(dlist) - len(dlist) + 1` + + OUTPUT: + + - the index `i` such that the input monomial is in `S_i` + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ, 2) + sage: R._macaulay_resultant_getS([1,1,0],[2,1,1]) # the monomial xy where the total degree = 2 + 1 + + sage: R._macaulay_resultant_getS([29,21,8],[10,20,30]) + 0 + + sage: R._macaulay_resultant_getS(range(0,9)+[10],range(1,11)) + 9 + """ + for i in xrange(len(dlist)): + if mon_deg_tuple[i] - dlist[i] >= 0: + return i + + def _macaulay_resultant_is_reduced(self,mon_degs,dlist): + r""" + Helper function for the Macaulay resultant algorithm. + A monomial in the variables `x_0,...,x_n` is called reduced with respect to the list of degrees `d_0,...,d_n` + if the degree of `x_i` in the monomial is `>= d_i` for exactly one `i`. This function checks this property for a monomial. + + INPUT: + + - ``mon_degs`` -- a monomial represented by a vector of degrees + - ``dlist`` -- a list of degrees with respect to which we check reducedness + + OUTPUT: + + - True/False + + EXAMPLES:: + + sage: R. = PolynomialRing(QQ,3) + sage: R._macaulay_resultant_is_reduced([2,3,1],[2,3,3]) # the monomial x^2*y^3*z is not reduced w.r.t. degrees vector [2,3,3] + False + + sage: R. = PolynomialRing(QQ,3) + sage: R._macaulay_resultant_is_reduced([1,3,2],[2,3,3]) # the monomial x*y^3*z^2 is not reduced w.r.t. degrees vector [2,3,3] + True + """ + diff = [mon_degs[i] - dlist[i] for i in xrange(0,len(dlist))] + return len([1 for d in diff if d >= 0]) == 1 + + def _macaulay_resultant_universal_polynomials(self, dlist): + r""" + Given a list of degrees, this function returns a list of ``len(dlist)`` polynomials with ``len(dlist)`` variables, + with generic coefficients. This is useful for generating polynomials for tests, + and for getting a universal macaulay resultant for the given degrees. + + INPUT: + + - ``dlist`` -- a list of degrees. + + OUTPUT: + + - a list of polynomials of the given degrees with general coefficients. + - a polynomial ring over ``self`` generated by the coefficients of the output polynomials. + + EXAMPLES:: + + sage: R. = PolynomialRing(ZZ, 2) + sage: R._macaulay_resultant_universal_polynomials([1,1,2]) + ([u0*x0 + u1*x1 + u2*x2, u3*x0 + u4*x1 + u5*x2, u6*x0^2 + u7*x0*x1 + u9*x1^2 + u8*x0*x2 + u10*x1*x2 + u11*x2^2], Multivariate Polynomial Ring in x0, x1, x2 over Multivariate Polynomial Ring in u0, u1, u2, u3, u4, u5, u6, u7, u8, u9, u10, u11 over Integer Ring) + """ + n = len(dlist) - 1 + number_of_coeffs = sum([binomial(n+di,di) for di in dlist]) + U = PolynomialRing(ZZ,'u',number_of_coeffs) + d = sum(dlist) - len(dlist) + 1 + flist = [] + R = PolynomialRing(U,'x',n+1) + ulist = U.gens() + for d in dlist: + xlist = R.gens() + degs = IntegerVectors(d, n+1) + mon_d = [prod([xlist[i]**(deg[i]) for i in xrange(0,len(deg))]) + for deg in degs] + + f = sum([mon_d[i]*ulist[i] for i in xrange(0,len(mon_d))]) + flist.append (f) + ulist = ulist[len(mon_d):] + return flist, R + + def macaulay_resultant(self, *args, **kwds): + r""" + This is an implementation of the Macaulay Resultant. It computes + the resultant of universal polynomials as well as polynomials + with constant coefficients. This is a project done in + sage days 55. It's based on the implementation in Maple by + Manfred Minimair, which in turn is based on the references listed below: + It calculates the Macaulay resultant for a list of polynomials, + up to sign! + + REFERENCES: + + .. [CLO] D. Cox, J. Little, D. O'Shea. Using Algebraic Geometry. + Springer, 2005. + + .. [Can] J. Canny. Generalised characteristic polynomials. + J. Symbolic Comput. Vol. 9, No. 3, 1990, 241--250. + + .. [Mac] F.S. Macaulay. The algebraic theory of modular systems + Cambridge university press, 1916. + + AUTHORS: + + - Hao Chen, Solomon Vishkautsan (7-2014) + + INPUT: + + - ``args`` -- a list of `n` homogeneous polynomials in `n` variables. + works when ``args[0]`` is the list of polynomials, + or ``args`` is itself the list of polynomials + + kwds: + + - ``sparse`` -- boolean (optional - default: ``False``) + if ``True`` function creates sparse matrices. + + OUTPUT: + + - the macaulay resultant, an element of the base ring of ``self`` + + .. TODO:: + Working with sparse matrices should usually give faster results, + but with the current implementation it actually works slower. + There should be a way to improve performance with regards to this. + + EXAMPLES: + + The number of polynomials has to match the number of variables:: + + sage: R. = PolynomialRing(QQ,3) + sage: R.macaulay_resultant([y,x+z]) + Traceback (most recent call last): + ... + TypeError: number of polynomials(= 2) must equal number of variables (= 3) + + The polynomials need to be all homogeneous:: + + sage: R. = PolynomialRing(QQ,3) + sage: R.macaulay_resultant([y, x+z, z+x^3]) + Traceback (most recent call last): + ... + TypeError: resultant for non-homogeneous polynomials is not supported + + All polynomials must be in the same ring:: + + sage: S. = PolynomialRing(QQ, 2) + sage: R. = PolynomialRing(QQ,3) + sage: S.macaulay_resultant([y, z+x]) + Traceback (most recent call last): + ... + TypeError: not all inputs are polynomials in the calling ring + + The following example recreates Proposition 2.10 in Ch.3 in [CLO]:: + + sage: K. = PolynomialRing(ZZ, 2) + sage: flist,R = K._macaulay_resultant_universal_polynomials([1,1,2]) + sage: R.macaulay_resultant(flist) + u2^2*u4^2*u6 - 2*u1*u2*u4*u5*u6 + u1^2*u5^2*u6 - u2^2*u3*u4*u7 + u1*u2*u3*u5*u7 + u0*u2*u4*u5*u7 - u0*u1*u5^2*u7 + u1*u2*u3*u4*u8 - u0*u2*u4^2*u8 - u1^2*u3*u5*u8 + u0*u1*u4*u5*u8 + u2^2*u3^2*u9 - 2*u0*u2*u3*u5*u9 + u0^2*u5^2*u9 - u1*u2*u3^2*u10 + u0*u2*u3*u4*u10 + u0*u1*u3*u5*u10 - u0^2*u4*u5*u10 + u1^2*u3^2*u11 - 2*u0*u1*u3*u4*u11 + u0^2*u4^2*u11 + + The following example degenerates into the determinant of a `3*3` matrix:: + + sage: K. = PolynomialRing(ZZ, 2) + sage: flist,R = K._macaulay_resultant_universal_polynomials([1,1,1]) + sage: R.macaulay_resultant(flist) + -u2*u4*u6 + u1*u5*u6 + u2*u3*u7 - u0*u5*u7 - u1*u3*u8 + u0*u4*u8 + + The following example is by Patrick Ingram(arxiv:1310.4114):: + + sage: U = PolynomialRing(ZZ,'y',2); y0,y1 = U.gens() + sage: R = PolynomialRing(U,'x',3); x0,x1,x2 = R.gens() + sage: f0 = y0*x2^2 - x0^2 + 2*x1*x2 + sage: f1 = y1*x2^2 - x1^2 + 2*x0*x2 + sage: f2 = x0*x1 - x2^2 + sage: flist = [f0,f1,f2] + sage: R.macaulay_resultant([f0,f1,f2]) + y0^2*y1^2 - 4*y0^3 - 4*y1^3 + 18*y0*y1 - 27 + + a simple example with constant rational coefficients:: + + sage: R. = PolynomialRing(QQ,4) + sage: R.macaulay_resultant([w,z,y,x]) + 1 + + an example where the resultant vanishes:: + + sage: R. = PolynomialRing(QQ,3) + sage: R.macaulay_resultant([x+y,y^2,x]) + 0 + + an example of bad reduction at a prime `p = 5`:: + + sage: R. = PolynomialRing(QQ,3) + sage: R.macaulay_resultant([y,x^3+25*y^2*x,5*z]) + 125 + + The input can given as an unpacked list of polynomials:: + + sage: R. = PolynomialRing(QQ,3) + sage: R.macaulay_resultant(y,x^3+25*y^2*x,5*z) + 125 + + an example when the coefficients live in a finite field:: + + sage: F = FiniteField(11) + sage: R. = PolynomialRing(F,4) + sage: R.macaulay_resultant([z,x^3,5*y,w]) + 4 + + example when the denominator in the algorithm vanishes(in this case + the resultant is the constant term of the quotient of + char polynomials of numerator/denominator):: + + sage: R. = PolynomialRing(QQ,3) + sage: R.macaulay_resultant([y, x+z, z^2]) + -1 + + when there are only 2 polynomials, macaulay resultant degenerates to the traditional resultant:: + + sage: R. = PolynomialRing(QQ,1) + sage: f = x^2+1; g = x^5+1 + sage: fh = f.homogenize() + sage: gh = g.homogenize() + sage: RH = fh.parent() + sage: f.resultant(g) == RH.macaulay_resultant([fh,gh]) + True + + """ + from sage.matrix.constructor import matrix + from sage.matrix.constructor import zero_matrix + + if len(args) == 1 and isinstance(args[0],list): + flist = args[0] + else: + flist = args + + if len(flist) <= 0: + raise TypeError('input list should contain at least 1 polynomial') + if not all([f.is_homogeneous() for f in flist]): + raise TypeError('resultant for non-homogeneous polynomials is not supported') + if not all([self.is_parent_of(f) for f in flist]): + raise TypeError('not all inputs are polynomials in the calling ring') + + sparse = kwds.pop('sparse', False) + + U = self.base_ring() # ring of coefficients of self + dlist = [f.degree() for f in flist] + xlist = self.gens() + if len(xlist) != len(dlist): + raise TypeError('number of polynomials(= %d) must equal number of variables (= %d)'%(len(dlist),len(xlist))) + n = len(dlist) - 1 + d = sum(dlist) - len(dlist) + 1 + mons = IntegerVectors(d, n+1).list() # list of exponent-vectors(/lists) of monomials of degree d + mons_idx = { str(mon) : idx for idx,mon in enumerate(mons)} # a reverse index lookup for monomials + mons_num = len(mons) + mons_to_keep = [] + newflist = [] + flist=[[f.exponents(),f.coefficients()] for f in flist] # strip coefficients of the input polynomials + numer_matrix = zero_matrix(self.base_ring(),mons_num, sparse=sparse) + + for j,mon in enumerate(mons): + # if monomial is not reduced, then we keep it in the denominator matrix: + if not self._macaulay_resultant_is_reduced(mon,dlist): + mons_to_keep.append(j) + si_mon = self._macaulay_resultant_getS(mon, dlist) + # Monomial is in S_i under the partition, now we reduce the i'th degree of the monomial + new_mon = list(mon) + new_mon[si_mon] -= dlist[si_mon] + new_f = [[[g[k] + new_mon[k] for k in range(n+1)] for g in flist[si_mon][0]], flist[si_mon][1]] + + for i,mon in enumerate(new_f[0]): + k = mons_idx[str(mon)] + numer_matrix[j,k]=new_f[1][i] + + denom_matrix = numer_matrix.matrix_from_rows_and_columns(mons_to_keep,mons_to_keep) + if denom_matrix.dimensions()[0] == 0: # here we choose the determinant of an empty matrix to be 1 + return U(numer_matrix.det()) + denom_det = denom_matrix.det() + if denom_det != 0: + return U(numer_matrix.det()/denom_det) + # if we get to this point, the determinant of the denominator was 0, and we get the resultant + # by taking the free coefficient of the quotient of two characteristic polynomials + poly_num = numer_matrix.characteristic_polynomial('T') + poly_denom = denom_matrix.characteristic_polynomial('T') + poly_quo = poly_num.quo_rem(poly_denom)[0] + return U(poly_quo(0)) #################### # Leave *all* old versions! diff --git a/src/sage/rings/polynomial/multi_polynomial_sequence.py b/src/sage/rings/polynomial/multi_polynomial_sequence.py index 57d0a086ffd..20a13e97372 100644 --- a/src/sage/rings/polynomial/multi_polynomial_sequence.py +++ b/src/sage/rings/polynomial/multi_polynomial_sequence.py @@ -757,8 +757,8 @@ def _magma_init_(self, magma): sage: F,s = sr.polynomial_system() sage: F.set_immutable() sage: magma(F) # indirect doctest; optional - magma - Ideal of Polynomial ring of rank 20 over GF(2) - Order: Graded Reverse Lexicographical + Ideal of Boolean polynomial ring of rank 20 over GF(2) + Order: Graded Lexicographical (bit vector word) Variables: k100, k101, k102, k103, x100, x101, x102, x103, w100, w101, w102, w103, s000, s001, s002, s003, k000, k001, k002, k003 Basis: [ @@ -927,7 +927,7 @@ class PolynomialSequence_gf2(PolynomialSequence_generic): """ Polynomial Sequences over `\mathbb{F}_2`. """ - def eliminate_linear_variables(self, maxlength=Infinity, skip=None, return_reductors=False): + def eliminate_linear_variables(self, maxlength=Infinity, skip=None, return_reductors=False, use_polybori=False): """ Return a new system where linear leading variables are eliminated if the tail of the polynomial has length at most @@ -949,6 +949,11 @@ def eliminate_linear_variables(self, maxlength=Infinity, skip=None, return_reduc with linear leading terms which were used for reduction is also returned (default: ``False``). + - ```use_polybori`` - if ``True`` then ``polybori.ll.eliminate`` is + called. While this is typically faster what is implemented here, it + is less flexible (``skip` is not supported) and may increase the + degree (default: ``False``) + OUTPUT: When ``return_reductors==True``, then a pair of sequences of @@ -990,6 +995,21 @@ def eliminate_linear_variables(self, maxlength=Infinity, skip=None, return_reduc sage: R [a + b + d, c + d] + + If the input system is detected to be inconsistent then [1] is returned + and the list of reductors is empty:: + + sage: R. = BooleanPolynomialRing() + sage: S = Sequence([x*y*z+x*y+z*y+x*z, x+y+z+1, x+y+z]) + sage: S.eliminate_linear_variables() + [1] + + sage: R. = BooleanPolynomialRing() + sage: S = Sequence([x*y*z+x*y+z*y+x*z, x+y+z+1, x+y+z]) + sage: S.eliminate_linear_variables(return_reductors=True) + ([1], []) + + TESTS: The function should really dispose of linear equations (:trac:`13968`):: @@ -1008,6 +1028,18 @@ def eliminate_linear_variables(self, maxlength=Infinity, skip=None, return_reduc sage: S.eliminate_linear_variables(return_reductors=True) ([], [x + y, z + 1]) + We test a case which would increase the degree with ``polybori=True``:: + + sage: B. = BooleanPolynomialRing() + sage: f = a*d + a + b*d + c*d + 1 + sage: Sequence([f, a + b*c + c+d + 1]).eliminate_linear_variables() + [a*d + a + b*d + c*d + 1, a + b*c + c + d + 1] + + sage: B. = BooleanPolynomialRing() + sage: f = a*d + a + b*d + c*d + 1 + sage: Sequence([f, a + b*c + c+d + 1]).eliminate_linear_variables(use_polybori=True) + [b*c*d + b*c + b*d + c + d] + .. NOTE:: This is called "massaging" in [CBJ07]_. @@ -1019,6 +1051,7 @@ def eliminate_linear_variables(self, maxlength=Infinity, skip=None, return_reduc Multivariate Polynomials over GF(2) via SAT-Solvers*. Cryptology ePrint Archive: Report 2007/024. available at http://eprint.iacr.org/2007/024 + """ from sage.rings.polynomial.pbori import BooleanPolynomialRing from polybori import gauss_on_polys @@ -1032,7 +1065,7 @@ def eliminate_linear_variables(self, maxlength=Infinity, skip=None, return_reduc F = self reductors = [] - if skip is None and maxlength==Infinity: + if use_polybori and skip is None and maxlength==Infinity: # faster solution based on polybori.ll.eliminate while True: (this_step_reductors, _, higher) = eliminate(F) @@ -1063,6 +1096,11 @@ def eliminate_linear_variables(self, maxlength=Infinity, skip=None, return_reduc break linear = gauss_on_polys(linear) + if 1 in linear: + if return_reductors: + return PolynomialSequence(R, [R(1)]), PolynomialSequence(R, []) + else: + return PolynomialSequence(R, [R(1)]) rb = ll_encode(linear) reductors.extend(linear) @@ -1320,4 +1358,3 @@ def weil_restriction(self): from sage.structure.sage_object import register_unpickle_override register_unpickle_override("sage.crypto.mq.mpolynomialsystem","MPolynomialSystem_generic", PolynomialSequence_generic) register_unpickle_override("sage.crypto.mq.mpolynomialsystem","MPolynomialRoundSystem_generic", PolynomialSequence_generic) - diff --git a/src/sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py b/src/sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py index 179625137c1..4c708131686 100644 --- a/src/sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py +++ b/src/sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py @@ -44,6 +44,13 @@ def __init__(self, parent, x=None, check=True, is_gen=False, construct = False, sage: T. = ZZ[] sage: R(t + 2) (1 + O(13^7))*t + (2 + O(13^7)) + + Check that :trac:`13620` has been fixed:: + + sage: f = R.zero() + sage: R(f.dict()) + 0 + """ Polynomial.__init__(self, parent, is_gen=is_gen) parentbr = parent.base_ring() @@ -98,7 +105,7 @@ def __init__(self, parent, x=None, check=True, is_gen=False, construct = False, check = False elif isinstance(x, dict): zero = parentbr.zero_element() - n = max(x.keys()) + n = max(x.keys()) if x else 0 v = [zero for _ in xrange(n + 1)] for i, z in x.iteritems(): v[i] = z @@ -1050,7 +1057,7 @@ def newton_polygon(self): ... PrecisionError: The coefficient of t^4 has not enough precision - TESTS: + TESTS:: sage: (5*f).newton_polygon() Finite Newton polygon with 4 vertices: (0, 2), (1, 1), (4, 1), (10, 3) diff --git a/src/sage/rings/polynomial/padics/polynomial_padic_flat.py b/src/sage/rings/polynomial/padics/polynomial_padic_flat.py index 5517a2f6016..a016118f06e 100644 --- a/src/sage/rings/polynomial/padics/polynomial_padic_flat.py +++ b/src/sage/rings/polynomial/padics/polynomial_padic_flat.py @@ -18,7 +18,15 @@ class Polynomial_padic_flat(Polynomial_generic_dense, Polynomial_padic): def __init__(self, parent, x=None, check=True, is_gen=False, construct=False, absprec=None): """ - Initialization function for the class Polynomial_padic_flat. + TESTS: + + Check that :trac:`13620` has been fixed:: + + sage: K = ZpFM(3) + sage: R. = K[] + sage: R(R.zero()) + 0 + """ if x is None: Polynomial_generic_dense.__init__(self, parent, x, check, is_gen, construct) @@ -41,7 +49,7 @@ def __init__(self, parent, x=None, check=True, is_gen=False, construct=False, ab if check: m = infinity zero = R(0) - n = max(x.keys()) + n = max(x.keys()) if x else 0 v = [zero for _ in xrange(n+1)] for i, z in x.iteritems(): v[i] = R(z) diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index cda897efafa..e9389e21deb 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -13,8 +13,9 @@ AUTHORS: - Simon King: Use a faster way of conversion from the base ring. -- Julian Rueth (2012-05-25): Fixed is_squarefree() for imperfect fields. - Fixed division without remainder over QQbar. +- Julian Rueth (2012-05-25,2014-05-09): Fixed is_squarefree() for imperfect + fields, fixed division without remainder over QQbar; added ``_cache_key`` + for polynomials with unhashable coefficients - Simon King (2013-10): Implement copying of :class:`PolynomialBaseringInjection`. @@ -829,6 +830,31 @@ cdef class Polynomial(CommutativeAlgebraElement): def __iter__(self): return iter(self.list()) + def _cache_key(self): + """ + Return a hashable key which identifies this element. + + EXAMPLES:: + + sage: K. = Qq(4) + sage: R. = K[] + sage: f = x + sage: hash(f) + Traceback (most recent call last): + ... + TypeError: unhashable type: 'sage.rings.padics.padic_ZZ_pX_CR_element.pAdicZZpXCRElement' + sage: f._cache_key() + (..., (0, 1 + O(2^20))) + + sage: @cached_function + ....: def foo(t): return t + ....: + sage: foo(x) + (1 + O(2^20))*x + + """ + return (self.parent(), tuple(self)) + # you may have to replicate this boilerplate code in derived classes if you override # __richcmp__. The python documentation at http://docs.python.org/api/type-structs.html # explains how __richcmp__, __hash__, and __cmp__ are tied together. @@ -1212,15 +1238,8 @@ cdef class Polynomial(CommutativeAlgebraElement): def squarefree_decomposition(self): """ - Return the square-free decomposition of self. This is a - partial factorization of self into square-free, coprime - polynomials. - - ALGORITHM: In characteristic 0, we use Yun's algorithm, - which works for arbitrary rings of characteristic 0. - If the characteristic is a prime number `p > 0`, we use - [Coh]_, algorithm 3.4.2. This is basically Yun's algorithm - with special treatment for powers divisible by `p`. + Return the square-free decomposition of this polynomial. This is a + partial factorization into square-free, coprime polynomials. EXAMPLES:: @@ -1238,111 +1257,12 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: f.squarefree_decomposition() 1 - TESTS:: - - sage: K. = GF(3^2) - sage: R. = K[] - sage: f = x^243+2*x^81+x^9+1 - sage: f.squarefree_decomposition() - (x^27 + 2*x^9 + x + 1)^9 - sage: f = x^243+a*x^27+1 - sage: f.squarefree_decomposition() - (x^9 + (2*a + 1)*x + 1)^27 - - sage: for K in [GF(2^18,'a'), GF(3^2,'a'), GF(47^3,'a')]: - ....: R. = K[] - ....: if K.characteristic() < 5: m = 4 - ....: else: m = 1 - ....: for _ in range(m): - ....: f = (R.random_element(4)^3*R.random_element(m)^(m+1))(x^6) - ....: F = f.squarefree_decomposition() - ....: assert F.prod() == f - ....: for i in range(len(F)): - ....: assert gcd(F[i][0], F[i][0].derivative()) == 1 - ....: for j in range(len(F)): - ....: if i == j: continue - ....: assert gcd(F[i][0], F[j][0]) == 1 - ....: - - REFERENCES: - - .. [Coh] H. Cohen, A Course in Computational Algebraic Number - Theory. Springer-Verlag, 1993. """ - if not self.base_ring().is_unique_factorization_domain(): - raise NotImplementedError, "Squarefree decomposition not implemented for " + str(self.parent()) - - if self.degree() == 0: - return Factorization([], unit=self[0]) - - p = self.base_ring().characteristic() - factors = [] - if p == 0: - f = [self] - cur = self - while cur.degree() > 0: - cur = cur.gcd(cur.derivative()) - f.append(cur) - - g = [] - for i in range(len(f) - 1): - g.append(f[i] // f[i+1]) - - a = [] - for i in range(len(g) - 1): - a.append(g[i] // g[i+1]) - a.append(g[-1]) - - unit = f[-1] - for i in range(len(a)): - if a[i].degree() > 0: - factors.append((a[i], i+1)) - else: - unit = unit * a[i].constant_coefficient() ** (i + 1) - else: - # Beware that `p`-th roots might not exist. - unit = self.leading_coefficient() - T0 = self.monic() - e = 1 - if T0.degree() > 0: - der = T0.derivative() - while der.is_zero(): - T0 = T0.parent()([T0[p*i].pth_root() for i in range(T0.degree()//p + 1)]) - if T0 == 1: - raise RuntimeError - der = T0.derivative() - e = e*p - T = T0.gcd(der) - V = T0 // T - k = 0 - while T0.degree() > 0: - k += 1 - if p.divides(k): - T = T // V - k += 1 - W = V.gcd(T) - if W.degree() < V.degree(): - factors.append((V // W, e*k)) - V = W - T = T // V - if V.degree() == 0: - if T.degree() == 0: - break - # T is of the form sum_{i=0}^n t_i X^{pi} - T0 = T0.parent()([T[p*i].pth_root() for i in range(T.degree()//p + 1)]) - der = T0.derivative() - e = p*e - while der.is_zero(): - T0 = T0.parent()([T0[p*i].pth_root() for i in range(T0.degree()//p + 1)]) - der = T0.derivative() - e = p*e - T = T0.gcd(der) - V = T0 // T - k = 0 - else: - T = T//V - - return Factorization(factors, unit=unit, sort=False) + if self.degree() < 0: + raise ValueError("square-free decomposition not defined for zero polynomial") + if hasattr(self.base_ring(),'_squarefree_decomposition_univariate_polynomial'): + return self.base_ring()._squarefree_decomposition_univariate_polynomial(self) + raise NotImplementedError("square-free decomposition not implemented for this polynomial") def is_square(self, root=False): """ @@ -3322,6 +3242,23 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: factor(f) (x - a1) * (x^2 + a1*x + a1^2) + We check that :trac:`7554` is fixed:: + + sage: L. = LaurentPolynomialRing(QQ) + sage: F = L.fraction_field() + sage: R. = PolynomialRing(F) + sage: factor(x) + x + sage: factor(x^2 - q^2) + (-1) * (-x + q) * (x + q) + sage: factor(x^2 - q^-2) + (1/q^2) * (q*x - 1) * (q*x + 1) + + sage: P. = PolynomialRing(ZZ) + sage: R. = PolynomialRing(FractionField(P)) + sage: p = (x - a)*(b*x + c)*(a*b*x + a*c) / (a + 2) + sage: factor(p) + (a/(a + 2)) * (x - a) * (b*x + c)^2 """ # PERFORMANCE NOTE: # In many tests with SMALL degree PARI is substantially @@ -3441,21 +3378,33 @@ cdef class Polynomial(CommutativeAlgebraElement): # adds back the unit of the factorization. return self._factor_pari_helper(G) - elif is_RealField(R): - n = pari.set_real_precision(int(3.5*R.prec()) + 1) - G = list(self._pari_with_name().factor()) - - elif sage.rings.complex_field.is_ComplexField(R): - # This is a hack to make the polynomial have complex coefficients, since - # otherwise PARI will factor over RR. - n = pari.set_real_precision(int(3.5*R.prec()) + 1) - if self.leading_coefficient() != R.gen(): - G = list((pari(R.gen())*self._pari_with_name()).factor()) - else: - G = self._pari_with_name().factor() - if G is None: - raise NotImplementedError + # See if we can do this as a singular polynomial as a fallback + # This was copied from the general multivariate implementation + try: + if R.is_finite(): + if R.characteristic() > 1<<29: + raise NotImplementedError("Factorization of multivariate polynomials over prime fields with characteristic > 2^29 is not implemented.") + + P = self.parent() + P._singular_().set_ring() + S = self._singular_().factorize() + factors = S[1] + exponents = S[2] + v = sorted([( P(factors[i+1]), + sage.rings.integer.Integer(exponents[i+1]) ) + for i in range(len(factors))]) + unit = P.one() + for i in range(len(v)): + if v[i][0].is_unit(): + unit = unit * v[i][0] + del v[i] + break + F = Factorization(v, unit=unit) + F.sort() + return F + except (TypeError, AttributeError): + raise NotImplementedError return self._factor_pari_helper(G, n) diff --git a/src/sage/rings/polynomial/polynomial_element_generic.py b/src/sage/rings/polynomial/polynomial_element_generic.py index 39ada32fe02..fb7b7ed1e0a 100644 --- a/src/sage/rings/polynomial/polynomial_element_generic.py +++ b/src/sage/rings/polynomial/polynomial_element_generic.py @@ -555,6 +555,88 @@ def shift(self, n): output[index + n] = coeff return self.parent()(output, check=False) + @coerce_binop + def quo_rem(self, other): + """ + Returns the quotient and remainder of the Euclidean division of + ``self`` and ``other``. + + Raises ZerodivisionError if ``other`` is zero. Raises ArithmeticError + if ``other`` has a nonunit leading coefficient. + + EXAMPLES:: + + sage: P. = PolynomialRing(ZZ,sparse=True) + sage: R. = PolynomialRing(P,sparse=True) + sage: f = R.random_element(10) + sage: g = y^5+R.random_element(4) + sage: q,r = f.quo_rem(g) + sage: f == q*g + r and r.degree() < g.degree() + True + sage: g = x*y^5 + sage: f.quo_rem(g) + Traceback (most recent call last): + ... + ArithmeticError: Nonunit leading coefficient + sage: g = 0 + sage: f.quo_rem(g) + Traceback (most recent call last): + ... + ZeroDivisionError: Division by zero polynomial + + TESTS:: + + sage: P. = PolynomialRing(ZZ,sparse=True) + sage: f = x^10-4*x^6-5 + sage: g = 17*x^22+x^15-3*x^5+1 + sage: q,r = g.quo_rem(f) + sage: g == f*q + r and r.degree() < f.degree() + True + sage: zero = P(0) + sage: zero.quo_rem(f) + (0, 0) + sage: Q. = IntegerModRing(14)[] + sage: f = y^10-4*y^6-5 + sage: g = 17*y^22+y^15-3*y^5+1 + sage: q,r = g.quo_rem(f) + sage: g == f*q + r and r.degree() < f.degree() + True + sage: f += 2*y^10 # 3 is invertible mod 14 + sage: q,r = g.quo_rem(f) + sage: g == f*q + r and r.degree() < f.degree() + True + + AUTHORS: + + - Bruno Grenet (2014-07-09) + """ + if other.is_zero(): + raise ZeroDivisionError("Division by zero polynomial") + if not other.leading_coefficient().is_unit(): + raise ArithmeticError("Nonunit leading coefficient") + if self.is_zero(): + return self, self + + R = self.parent() + + d = other.degree() + if self.degree() < d: + return R.zero_element(), self + + quo = R.zero_element() + rem = self + inv_lc = R.base_ring().one_element()/other.leading_coefficient() + + while rem.degree() >= d: + + c = rem.leading_coefficient()*inv_lc + e = rem.degree() - d + quo += c*R.one_element().shift(e) + # we know that the leading coefficient of rem vanishes + # thus we avoid doing a useless computation + rem = rem[:rem.degree()] - c*other[:d].shift(e) + return (quo,rem) + class Polynomial_generic_domain(Polynomial, IntegralDomainElement): def __init__(self, parent, is_gen=False, construct=False): diff --git a/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx b/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx index 34b13807d9f..36cf77d3870 100644 --- a/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx @@ -330,7 +330,8 @@ cdef class Polynomial_integer_dense_flint(Polynomial): cpdef Integer content(self): r""" Return the greatest common divisor of the coefficients of this - polynomial. + polynomial. The sign is the sign of the leading coefficient. The + content of the zero polynomial is zero. EXAMPLES:: @@ -351,14 +352,27 @@ cdef class Polynomial_integer_dense_flint(Polynomial): 1 sage: (123456789123456789123456789123456789123456789*t).content() 123456789123456789123456789123456789123456789 + + Verify that :trac:`13053` has been resolved:: + + sage: R(-1).content() + -1 + """ + if self.is_zero(): + return ZZ.zero() + cdef fmpz_t c fmpz_init(c) + fmpz_poly_get_coeff_fmpz(c, self.__poly, fmpz_poly_degree(self.__poly)) + cdef int sign = fmpz_sgn(c) + fmpz_poly_content(c, self.__poly) + cdef Integer z = PY_NEW(Integer) fmpz_get_mpz(z.value, c) fmpz_clear(c) - return z + return z if sign == 1 else -z def __reduce__(self): r""" @@ -1091,6 +1105,16 @@ cdef class Polynomial_integer_dense_flint(Polynomial): sage: p = 37 * (x-1)^2 * (x-2)^2 * (x-3)^3 * (x-4) sage: p.squarefree_decomposition() (37) * (x - 4) * (x^2 - 3*x + 2)^2 * (x - 3)^3 + + TESTS: + + Verify that :trac:`13053` has been resolved:: + + sage: R. = PolynomialRing(ZZ, implementation='FLINT') + sage: f=-x^2 + sage: f.squarefree_decomposition() + (-1) * x^2 + """ cdef ZZX_c** v cdef long* e @@ -1100,10 +1124,13 @@ cdef class Polynomial_integer_dense_flint(Polynomial): cdef Integer z cdef Polynomial_integer_dense_flint fac + # the sign of the content is the sign of the leading coefficient z = self.content() if not z.is_one(): fmpz_poly_init(ppart) + # the primitive part returned by FLINT has positive leading + # coefficient fmpz_poly_primitive_part(ppart, self.__poly) fmpz_poly_get_ZZX(ntl_poly, ppart) @@ -1111,6 +1138,7 @@ cdef class Polynomial_integer_dense_flint(Polynomial): else: fmpz_poly_get_ZZX(ntl_poly, self.__poly) + # input is primitive, with positive leading coefficient ZZX_squarefree_decomposition(&v, &e, &n, &ntl_poly) F = [] diff --git a/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx b/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx index 136d4687599..9adf7d91220 100644 --- a/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx +++ b/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx @@ -835,8 +835,17 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): sage: p = 37 * (x-1)^2 * (x-2)^2 * (x-3)^3 * (x-4) sage: p.squarefree_decomposition() (37) * (x - 4) * (x^2 - 3*x + 2)^2 * (x - 3)^3 - """ + TESTS: + + Verify that :trac:`13053` has been resolved:: + + sage: R. = PolynomialRing(ZZ, implementation='NTL') + sage: f=-x^2 + sage: f.squarefree_decomposition() + (-1) * x^2 + + """ cdef Polynomial_integer_dense_ntl p = self c = p.content() if c != 1: diff --git a/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx b/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx index 51da203a3a8..50a93e95172 100644 --- a/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx +++ b/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx @@ -65,7 +65,7 @@ cdef class Polynomial_dense_mod_n(Polynomial): EXAMPLES:: - sage: R. = PolynomialRing(Integers(16)) + sage: R. = PolynomialRing(Integers(16), implementation='NTL') sage: f = x^3 - x + 17 sage: f^2 x^6 + 14*x^4 + 2*x^3 + x^2 + 14*x + 1 @@ -73,14 +73,14 @@ cdef class Polynomial_dense_mod_n(Polynomial): sage: loads(f.dumps()) == f True - sage: R. = Integers(100)[] + sage: R. = PolynomialRing(Integers(100), implementation='NTL') sage: p = 3*x sage: q = 7*x sage: p+q 10*x - sage: R. = Integers(8)[] + sage: R. = PolynomialRing(Integers(8), implementation='NTL') sage: parent(p) - Univariate Polynomial Ring in x over Ring of integers modulo 100 + Univariate Polynomial Ring in x over Ring of integers modulo 100 (using NTL) sage: p + q 10*x sage: R({10:-1}) @@ -156,7 +156,7 @@ cdef class Polynomial_dense_mod_n(Polynomial): """ EXAMPLES:: - sage: t = PolynomialRing(IntegerModRing(17),"t").gen() + sage: t = PolynomialRing(IntegerModRing(17),"t", implementation='NTL').gen() sage: f = t^3 + 3*t - 17 sage: pari(f) Mod(1, 17)*t^3 + Mod(3, 17)*t @@ -185,7 +185,7 @@ cdef class Polynomial_dense_mod_n(Polynomial): EXAMPLES:: - sage: R. = Integers(100)[] + sage: R. = PolynomialRing(Integers(100), implementation='NTL') sage: from sage.rings.polynomial.polynomial_modn_dense_ntl import Polynomial_dense_mod_n sage: f = Polynomial_dense_mod_n(R,[5,10,13,1,4]); f 4*x^4 + x^3 + 13*x^2 + 10*x + 5 @@ -228,7 +228,7 @@ cdef class Polynomial_dense_mod_n(Polynomial): """ EXAMPLES:: - sage: x = PolynomialRing(Integers(100), 'x').0 + sage: x = PolynomialRing(Integers(100), 'x', implementation='NTL').0 sage: (x - 2)*(x^2 - 8*x + 16) x^3 + 90*x^2 + 32*x + 68 """ @@ -263,7 +263,7 @@ cdef class Polynomial_dense_mod_n(Polynomial): EXAMPLES:: - sage: R. = PolynomialRing(Integers(12345678901234567890)) + sage: R. = PolynomialRing(Integers(12345678901234567890), implementation='NTL') sage: p = x^2 + 2*x + 4 sage: p.shift(0) x^2 + 2*x + 4 @@ -313,7 +313,7 @@ cdef class Polynomial_dense_mod_n(Polynomial): EXAMPLES:: - sage: _. = Integers(100)[] + sage: _. = PolynomialRing(Integers(100), implementation='NTL') sage: f = x^3 + 3*x - 17 sage: f.list() [83, 3, 0, 1] @@ -335,7 +335,7 @@ cdef class Polynomial_dense_mod_n(Polynomial): EXAMPLES:: - sage: R. = PolynomialRing(Integers(100)) + sage: R. = PolynomialRing(Integers(100), implementation='NTL') sage: from sage.rings.polynomial.polynomial_modn_dense_ntl import Polynomial_dense_mod_n as poly_modn_dense sage: poly_modn_dense(R, ([1,-2,3])) 3*x^2 + 98*x + 1 @@ -379,7 +379,7 @@ cdef class Polynomial_dense_mod_n(Polynomial): sage: N = 10001 sage: K = Zmod(10001) - sage: P. = PolynomialRing(K) + sage: P. = PolynomialRing(K, implementation='NTL') sage: f = x^3 + 10*x^2 + 5000*x - 222 sage: f.small_roots() [4] @@ -416,7 +416,7 @@ def small_roots(self, X=None, beta=1.0, epsilon=None, **kwds): sage: N = 10001 sage: K = Zmod(10001) - sage: P. = PolynomialRing(K) + sage: P. = PolynomialRing(K, implementation='NTL') sage: f = x^3 + 10*x^2 + 5000*x - 222 This polynomial has no roots without modular reduction (i.e. over `\ZZ`):: @@ -476,7 +476,7 @@ def small_roots(self, X=None, beta=1.0, epsilon=None, **kwds): To recover `K` we consider the following polynomial modulo `N`:: - sage: P. = PolynomialRing(ZmodN) + sage: P. = PolynomialRing(ZmodN, implementation='NTL') sage: f = (2^Nbits - 2^Kbits + x)^e - C and recover its small roots:: @@ -503,7 +503,7 @@ def small_roots(self, X=None, beta=1.0, epsilon=None, **kwds): And try to recover `q` from it:: - sage: F. = PolynomialRing(Zmod(N)) + sage: F. = PolynomialRing(Zmod(N), implementation='NTL') sage: f = x - qbar We know that the error is `\le 2^{\text{hidden}}-1` and that the modulus @@ -589,7 +589,8 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): r""" EXAMPLES:: - sage: R = Integers(5**21) ; S. = R[] + sage: R = Integers(5**21) + sage: S. = PolynomialRing(R, implementation='NTL') sage: S(1/4) 357627868652344 """ @@ -635,7 +636,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(100)[] + sage: R. = PolynomialRing(Integers(100), implementation='NTL') sage: from sage.rings.polynomial.polynomial_modn_dense_ntl import Polynomial_dense_mod_n as poly_modn_dense sage: f = poly_modn_dense(R,[5,0,0,1]) sage: f.int_list() @@ -653,7 +654,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(100)[] + sage: R. = PolynomialRing(Integers(100), implementation='NTL') sage: from sage.rings.polynomial.polynomial_modn_dense_ntl import Polynomial_dense_modn_ntl_zz sage: f = Polynomial_dense_modn_ntl_zz(R,[2, 1])^7 sage: f[3] @@ -689,7 +690,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): """ TESTS:: - sage: R. = Integers(100)[] + sage: R. = PolynomialRing(Integers(100), implementation='NTL') sage: (x+5) + (x^2 - 6) x^2 + x + 99 """ @@ -706,7 +707,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): """ TESTS:: - sage: R. = Integers(100)[] + sage: R. = PolynomialRing(Integers(100), implementation='NTL') sage: (x+5) - (x^2 - 6) 99*x^2 + x + 11 """ @@ -723,7 +724,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): """ TESTS:: - sage: R. = Integers(100)[] + sage: R. = PolynomialRing(Integers(100), implementation='NTL') sage: (x+5) * (x^2 - 1) x^3 + 5*x^2 + 99*x + 95 """ @@ -780,7 +781,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): """ TESTS:: - sage: R. = Integers(100)[] + sage: R. = PolynomialRing(Integers(100), implementation='NTL') sage: (x+5) * 3 3*x + 15 """ @@ -796,7 +797,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): """ TESTS:: - sage: R. = Integers(100)[] + sage: R. = PolynomialRing(Integers(100), implementation='NTL') sage: 3 * (x+5) 3*x + 15 """ @@ -812,14 +813,18 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): """ TESTS:: - sage: R. = Integers(100)[] + sage: R. = PolynomialRing(Integers(100), implementation='NTL') sage: (x-1)^5 x^5 + 95*x^4 + 10*x^3 + 90*x^2 + 5*x + 99 - sage: R. = Integers(101)[] + Negative powers will not work:: + + sage: R. = PolynomialRing(Integers(101), implementation='NTL') sage: (x-1)^(-5) - 1/(x^5 + 96*x^4 + 10*x^3 + 91*x^2 + 5*x + 100) - + Traceback (most recent call last): + ... + NotImplementedError: Fraction fields not implemented for this type. + We define ``0^0`` to be unity, :trac:`13895`:: sage: R. = PolynomialRing(Integers(100), implementation='NTL') @@ -881,7 +886,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(125)[] + sage: R. = PolynomialRing(Integers(125), implementation='NTL') sage: f = x^5+1; g = (x+1)^2 sage: q, r = f.quo_rem(g) sage: q @@ -908,7 +913,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(25)[] + sage: R. = PolynomialRing(Integers(25), implementation='NTL') sage: f = x^7 + 1; g = x^2 - 1 sage: q = f // g; q x^5 + x^3 + x @@ -931,7 +936,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): """ EXAMPLES:: - sage: R. = Integers(81)[] + sage: R. = PolynomialRing(Integers(81), implementation='NTL') sage: f = x^7 + x + 1; g = x^3 sage: r = f % g; r x + 1 @@ -957,7 +962,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(77)[] + sage: R. = PolynomialRing(Integers(77), implementation='NTL') sage: f = x^7 + x + 1 sage: f.shift(1) x^8 + x^2 + x @@ -981,7 +986,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): """ TEST:: - sage: R. = Integers(77)[] + sage: R. = PolynomialRing(Integers(77), implementation='NTL') sage: f = x^5 + 2*x + 1 sage: f << 3 x^8 + 2*x^4 + x^3 @@ -994,7 +999,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): """ TEST:: - sage: R. = Integers(77)[] + sage: R. = PolynomialRing(Integers(77), implementation='NTL') sage: f = x^5 + 2*x + 1 sage: f >> 3 x^2 @@ -1015,7 +1020,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(77)[] + sage: R. = PolynomialRing(Integers(77), implementation='NTL') sage: f = x^4 - x - 1 sage: f._derivative() 4*x^3 + 76 @@ -1048,7 +1053,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(77)[] + sage: R. = PolynomialRing(Integers(77), implementation='NTL') sage: f = x^4 - x - 1 sage: f.reverse() 76*x^4 + 76*x^3 + 1 @@ -1067,7 +1072,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): """ TESTS:: - sage: R. = Integers(77)[] + sage: R. = PolynomialRing(Integers(77), implementation='NTL') sage: f = x^4 - x - 1 sage: not f False @@ -1083,7 +1088,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(10)[] + sage: R. = PolynomialRing(Integers(10), implementation='NTL') sage: x.valuation() 1 sage: f = x-3; f.valuation() @@ -1103,7 +1108,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): """ EXAMPLES:: - sage: R. = Integers(77)[] + sage: R. = PolynomialRing(Integers(77), implementation='NTL') sage: f = x^4 - x - 1 sage: f.degree() 4 @@ -1119,7 +1124,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(77)[] + sage: R. = PolynomialRing(Integers(77), implementation='NTL') sage: f = sum(x^n for n in range(10)); f x^9 + x^8 + x^7 + x^6 + x^5 + x^4 + x^3 + x^2 + x + 1 sage: f.truncate(6) @@ -1137,7 +1142,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(100)[] + sage: R. = PolynomialRing(Integers(100), implementation='NTL') sage: f = x^3+7 sage: f(5) 32 @@ -1147,7 +1152,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): 32 sage: f(x) x^3 + 7 - sage: S. = Integers(5)[] + sage: S. = PolynomialRing(Integers(5), implementation='NTL') sage: f(y) y^3 + 2 """ @@ -1213,7 +1218,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(10^30)[] + sage: R. = PolynomialRing(Integers(10^30), implementation='NTL') sage: from sage.rings.polynomial.polynomial_modn_dense_ntl import Polynomial_dense_modn_ntl_ZZ sage: f = Polynomial_dense_modn_ntl_ZZ(R,[2,1])^7 sage: f[3] @@ -1261,7 +1266,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): """ TESTS:: - sage: R. = Integers(10^30)[] + sage: R. = PolynomialRing(Integers(10^30), implementation='NTL') sage: (x+5) + (x^2 - 6) x^2 + x + 999999999999999999999999999999 """ @@ -1278,7 +1283,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): """ TESTS:: - sage: R. = Integers(10^30)[] + sage: R. = PolynomialRing(Integers(10^30), implementation='NTL') sage: (x+5) - (x^2 - 6) 999999999999999999999999999999*x^2 + x + 11 """ @@ -1295,7 +1300,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): """ TESTS:: - sage: R. = Integers(10^30)[] + sage: R. = PolynomialRing(Integers(10^30), implementation='NTL') sage: (x+5) * (x^2 - 1) x^3 + 5*x^2 + 999999999999999999999999999999*x + 999999999999999999999999999995 """ @@ -1352,7 +1357,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): """ TESTS:: - sage: R. = Integers(10^30)[] + sage: R. = PolynomialRing(Integers(10^30), implementation='NTL') sage: (x+5) * 3 3*x + 15 """ @@ -1369,7 +1374,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): """ TESTS:: - sage: R. = Integers(10^30)[] + sage: R. = PolynomialRing(Integers(10^30), implementation='NTL') sage: 3 * (x+5) 3*x + 15 """ @@ -1379,7 +1384,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): """ TESTS:: - sage: R. = Integers(10^30)[] + sage: R. = PolynomialRing(Integers(10^30), implementation='NTL') sage: (x+1)^5 x^5 + 5*x^4 + 10*x^3 + 10*x^2 + 5*x + 1 @@ -1442,7 +1447,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(10^30)[] + sage: R. = PolynomialRing(Integers(10^30), implementation='NTL') sage: f = x^5+1; g = (x+1)^2 sage: q, r = f.quo_rem(g) sage: q @@ -1469,7 +1474,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(10^30)[] + sage: R. = PolynomialRing(Integers(10^30), implementation='NTL') sage: f = x^7 + 1; g = x^2 - 1 sage: q = f // g; q x^5 + x^3 + x @@ -1492,7 +1497,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): """ EXAMPLES:: - sage: R. = Integers(9^30)[] + sage: R. = PolynomialRing(Integers(9^30), implementation='NTL') sage: f = x^7 + x + 1; g = x^3 - 1 sage: r = f % g; r 2*x + 1 @@ -1518,7 +1523,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(12^30)[] + sage: R. = PolynomialRing(Integers(12^30), implementation='NTL') sage: f = x^7 + x + 1 sage: f.shift(1) x^8 + x^2 + x @@ -1542,7 +1547,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): """ TEST:: - sage: R. = Integers(14^30)[] + sage: R. = PolynomialRing(Integers(14^30), implementation='NTL') sage: f = x^5 + 2*x + 1 sage: f << 3 x^8 + 2*x^4 + x^3 @@ -1555,7 +1560,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): """ TEST:: - sage: R. = Integers(15^30)[] + sage: R. = PolynomialRing(Integers(15^30), implementation='NTL') sage: f = x^5 + 2*x + 1 sage: f >> 3 x^2 @@ -1577,7 +1582,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(12^29)[] + sage: R. = PolynomialRing(Integers(12^29), implementation='NTL') sage: f = x^4 + x + 5 sage: f._derivative() 4*x^3 + 1 @@ -1612,7 +1617,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(12^29)[] + sage: R. = PolynomialRing(Integers(12^29), implementation='NTL') sage: f = x^4 + 2*x + 5 sage: f.reverse() 5*x^4 + 2*x^3 + 1 @@ -1634,7 +1639,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(10^50)[] + sage: R. = PolynomialRing(Integers(10^50), implementation='NTL') sage: x.valuation() 1 sage: f = x-3; f.valuation() @@ -1656,7 +1661,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): """ TESTS:: - sage: R. = Integers(12^29)[] + sage: R. = PolynomialRing(Integers(12^29), implementation='NTL') sage: f = x^4 + 1 sage: not f False @@ -1669,7 +1674,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): """ EXAMPLES:: - sage: R. = Integers(14^34)[] + sage: R. = PolynomialRing(Integers(14^34), implementation='NTL') sage: f = x^4 - x - 1 sage: f.degree() 4 @@ -1685,7 +1690,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(15^30)[] + sage: R. = PolynomialRing(Integers(15^30), implementation='NTL') sage: f = sum(x^n for n in range(10)); f x^9 + x^8 + x^7 + x^6 + x^5 + x^4 + x^3 + x^2 + x + 1 sage: f.truncate(6) @@ -1703,7 +1708,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = Integers(10^30)[] + sage: R. = PolynomialRing(Integers(10^30), implementation='NTL') sage: f = x^3+7 sage: f(5) 132 @@ -1713,7 +1718,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): 132 sage: f(x) x^3 + 7 - sage: S. = Integers(5)[] + sage: S. = PolynomialRing(Integers(5), implementation='NTL') sage: f(y) y^3 + 2 """ @@ -1809,7 +1814,7 @@ cdef class Polynomial_dense_mod_p(Polynomial_dense_mod_n): EXAMPLES:: - sage: R. = GF(19)['x'] + sage: R. = PolynomialRing(GF(19),implementation='NTL') sage: f = x^3 + x + 1; g = x^3 - x - 1 sage: r = f.resultant(g); r 11 @@ -1823,7 +1828,7 @@ cdef class Polynomial_dense_mod_p(Polynomial_dense_mod_n): """ EXAMPLES:: - sage: _. = PolynomialRing(GF(19)) + sage: _. = PolynomialRing(GF(19),implementation='NTL') sage: f = x^3 + 3*x - 17 sage: f.discriminant() 12 diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 0d66856d122..d3e860f2402 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -3134,6 +3134,10 @@ def _repr_(self): 2/7*I + 1/3 sage: QQbar.zeta(4) + 5 I + 5 + sage: QQbar.zeta(4) + 1*I + sage: 3*QQbar.zeta(4) + 3*I sage: QQbar.zeta(17) 0.9324722294043558? + 0.3612416661871530?*I sage: AA(19).sqrt() @@ -3150,6 +3154,36 @@ def _repr_(self): else: return repr(RIF(self._value)) + def _latex_(self): + r""" + Returns the latex representation of this number. + + EXAMPLES:: + + sage: latex(AA(22/7)) + \frac{22}{7} + sage: latex(QQbar(1/3 + 2/7*I)) + \frac{2}{7} \sqrt{-1} + \frac{1}{3} + sage: latex(QQbar.zeta(4) + 5) + \sqrt{-1} + 5 + sage: latex(QQbar.zeta(4)) + 1 \sqrt{-1} + sage: latex(3*QQbar.zeta(4)) + 3 \sqrt{-1} + sage: latex(QQbar.zeta(17)) + 0.9324722294043558? + 0.3612416661871530? \sqrt{-1} + sage: latex(AA(19).sqrt()) + 4.358898943540674? + """ + from sage.misc.latex import latex + if self._descr.is_rational(): + return latex(self._descr._value) + if isinstance(self._descr, ANRootOfUnity) and self._descr._angle == QQ_1_4: + return r'%s \sqrt{-1}'%self._descr._scale + if isinstance(self._descr, ANExtensionElement) and self._descr._generator is QQbar_I_generator: + return latex(self._descr._value) + return repr(self).replace('*I', r' \sqrt{-1}') + def _sage_input_(self, sib, coerce): r""" Produce an expression which will reproduce this value when evaluated. diff --git a/src/sage/rings/quotient_ring_element.py b/src/sage/rings/quotient_ring_element.py index d94df2595bb..4fa21390b1a 100644 --- a/src/sage/rings/quotient_ring_element.py +++ b/src/sage/rings/quotient_ring_element.py @@ -419,6 +419,42 @@ def _div_(self, right): "a multiple of the denominator.") return P(XY[0]) + def _im_gens_(self, codomain, im_gens): + """ + Return the image of ``self`` in ``codomain`` under the map + that sends ``self.parent().gens()`` to ``im_gens``. + + INPUT: + + - ``codomain`` -- a ring + + - ``im_gens`` -- a tuple of elements `f(x)` in ``codomain``, + one for each `x` in ``self.parent().gens()``, that define + a homomorphism `f` from ``self.parent()`` to ``codomain`` + + OUPUT: + + The image of ``self`` in ``codomain`` under the above + homomorphism `f`. + + EXAMPLES: + + Ring homomorphisms whose domain is the fraction field of a + quotient ring work correctly (see :trac:`16135`):: + + sage: R. = QQ[] + sage: K = R.quotient(x^2 - y^3).fraction_field() + sage: L. = FunctionField(QQ) + sage: f = K.hom((t^3, t^2)) + sage: map(f, K.gens()) + [t^3, t^2] + sage: xbar, ybar = K.gens() + sage: f(1/ybar) + 1/t^2 + sage: f(xbar/ybar) + t + """ + return self.lift()._im_gens_(codomain, im_gens) def __int__(self): """ diff --git a/src/sage/rings/real_mpfr.pyx b/src/sage/rings/real_mpfr.pyx index 52386921bbc..92618c0e2ce 100644 --- a/src/sage/rings/real_mpfr.pyx +++ b/src/sage/rings/real_mpfr.pyx @@ -1213,6 +1213,52 @@ cdef class RealField_class(sage.rings.ring.Field): return self(-1) raise ValueError, "No %sth root of unity in self"%n + def _factor_univariate_polynomial(self, f): + """ + Factor the univariate polynomial ``f``. + + INPUT: + + - ``f`` -- a univariate polynomial defined over the real numbers + + OUTPUT: + + - A factorization of ``f`` over the real numbers into a unit and monic + irreducible factors + + .. NOTE:: + + This is a helper method for + :meth:`sage.rings.polynomial.polynomial_element.Polynomial.factor`. + + This method calls PARI to compute the factorization. + + TESTS:: + + sage: k = RealField(100) + sage: R. = k[] + sage: k._factor_univariate_polynomial( x ) + x + sage: k._factor_univariate_polynomial( 2*x ) + (2.0000000000000000000000000000) * x + sage: k._factor_univariate_polynomial( x^2 ) + x^2 + sage: k._factor_univariate_polynomial( x^2 + 1 ) + x^2 + 1.0000000000000000000000000000 + sage: k._factor_univariate_polynomial( x^2 - 1 ) + (x - 1.0000000000000000000000000000) * (x + 1.0000000000000000000000000000) + sage: k._factor_univariate_polynomial( (x - 1)^3 ) + (x - 1.0000000000000000000000000000)^3 + sage: k._factor_univariate_polynomial( x^2 - 3 ) + (x - 1.7320508075688772935274463415) * (x + 1.7320508075688772935274463415) + + """ + R = f.parent() + F = list(f._pari_with_name().factor()) + + from sage.structure.factorization import Factorization + return Factorization([(R(g).monic(),e) for g,e in zip(*F)], f.leading_coefficient()) + #***************************************************************************** # # RealNumber -- element of Real Field diff --git a/src/sage/schemes/elliptic_curves/constructor.py b/src/sage/schemes/elliptic_curves/constructor.py index b0b14738e15..d5319a0144e 100644 --- a/src/sage/schemes/elliptic_curves/constructor.py +++ b/src/sage/schemes/elliptic_curves/constructor.py @@ -40,49 +40,51 @@ from sage.structure.sequence import Sequence from sage.structure.element import parent +from sage.structure.factory import UniqueFactory from sage.symbolic.ring import SR from sage.symbolic.expression import is_SymbolicEquation -def EllipticCurve(x=None, y=None, j=None, minimal_twist=True): +class EllipticCurveFactory(UniqueFactory): r""" Construct an elliptic curve. - In Sage, an elliptic curve is always specified by its a-invariants + In Sage, an elliptic curve is always specified by + (the coefficients of) a long Weierstrass equation .. math:: - y^2 + a_1 xy + a_3 y = x^3 + a_2 x^2 + a_4 x + a_6. + y^2 + a_1 xy + a_3 y = x^3 + a_2 x^2 + a_4 x + a_6. INPUT: There are several ways to construct an elliptic curve: - ``EllipticCurve([a1,a2,a3,a4,a6])``: Elliptic curve with given - a-invariants. The invariants are coerced into the parent of the - first element. If all are integers, they are coerced into the - rational numbers. + `a`-invariants. The invariants are coerced into a common parent. + If all are integers, they are coerced into the rational numbers. - ``EllipticCurve([a4,a6])``: Same as above, but `a_1=a_2=a_3=0`. - - ``EllipticCurve(label)``: Returns the elliptic curve over Q from - the Cremona database with the given label. The label is a + - ``EllipticCurve(label)``: Returns the elliptic curve over `\QQ` + from the Cremona database with the given label. The label is a string, such as ``"11a"`` or ``"37b2"``. The letters in the label *must* be lower case (Cremona's new labeling). - ``EllipticCurve(R, [a1,a2,a3,a4,a6])``: Create the elliptic - curve over ``R`` with given a-invariants. Here ``R`` can be an - arbitrary ring. Note that addition need not be defined. + curve over `R` with given `a`-invariants. Here `R` can be an + arbitrary commutative ring, although most functionality is only + implemented over fields. - ``EllipticCurve(j=j0)`` or ``EllipticCurve_from_j(j0)``: Return - an elliptic curve with j-invariant ``j0``. + an elliptic curve with `j`-invariant ``j0``. - - ``EllipticCurve(polynomial)``: Read off the a-invariants from + - ``EllipticCurve(polynomial)``: Read off the `a`-invariants from the polynomial coefficients, see :func:`EllipticCurve_from_Weierstrass_polynomial`. - In each case above where the input is a list of length 2 or 5, one - can instead give a 2 or 5-tuple instead. + Instead of giving the coefficients as a *list* of length 2 or 5, + one can also give a *tuple*. EXAMPLES: @@ -175,12 +177,18 @@ def EllipticCurve(x=None, y=None, j=None, minimal_twist=True): sage: EllipticCurve(GF(144169),j=1728) Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 144169 + Elliptic curves over the same ring with the same Weierstrass + coefficients are identical, even when they are constructed in + different ways (see :trac:`11474`):: + + sage: EllipticCurve('11a3') is EllipticCurve(QQ, [0, -1, 1, 0, 0]) + True + By default, when a rational value of `j` is given, the constructed curve is a minimal twist (minimal conductor for curves with that `j`-invariant). This can be changed by setting the optional parameter ``minimal_twist``, which is True by default, to False:: - sage: EllipticCurve(j=100) Elliptic Curve defined by y^2 = x^3 + x^2 + 3392*x + 307888 over Rational Field sage: E =EllipticCurve(j=100); E @@ -288,81 +296,175 @@ def EllipticCurve(x=None, y=None, j=None, minimal_twist=True): TypeError: invalid input to EllipticCurve constructor """ - import ell_generic, ell_field, ell_finite_field, ell_number_field, ell_rational_field, ell_padic_field # here to avoid circular includes - - if j is not None: - if not x is None: - if is_Ring(x): + def create_key_and_extra_args(self, x=None, y=None, j=None, minimal_twist=True, **kwds): + """ + Return a ``UniqueFactory`` key and possibly extra parameters. + + INPUT: + + See the documentation for :class:`EllipticCurveFactory`. + + OUTPUT: + + A pair ``(key, extra_args)``: + + - ``key`` has the form `(R, (a_1, a_2, a_3, a_4, a_6))`, + representing a ring and the Weierstrass coefficients of an + elliptic curve over that ring; + + - ``extra_args`` is a dictionary containing additional data to + be inserted into the elliptic curve structure. + + EXAMPLES:: + + sage: EllipticCurve.create_key_and_extra_args(j=8000) + ((Rational Field, (0, -1, 0, -3, -1)), {}) + + When constructing a curve over `\\QQ` from a Cremona or LMFDB + label, the invariants from the database are returned as + ``extra_args``:: + + sage: key, data = EllipticCurve.create_key_and_extra_args('389.a1') + sage: key + (Rational Field, (0, 1, 1, -2, 0)) + sage: data['conductor'] + 389 + sage: data['cremona_label'] + '389a1' + sage: data['lmfdb_label'] + '389.a1' + sage: data['rank'] + 2 + sage: data['torsion_order'] + 1 + + User-specified keywords are also included in ``extra_args``:: + + sage: key, data = EllipticCurve.create_key_and_extra_args((0, 0, 1, -23737, 960366), rank=4) + sage: data['rank'] + 4 + + Furthermore, keywords takes precedence over data from the + database, which can be used to specify an alternative set of + generators for the Mordell-Weil group:: + + sage: key, data = EllipticCurve.create_key_and_extra_args('5077a1', gens=[[1, -1], [-2, 3], [4, -7]]) + sage: data['gens'] + [[1, -1], [-2, 3], [4, -7]] + sage: E = EllipticCurve.create_object(0, key, **data) + sage: E.gens() + [(-2 : 3 : 1), (1 : -1 : 1), (4 : -7 : 1)] + + Note that elliptic curves are equal if and only they have the + same base ring and Weierstrass equation; the data in + ``extra_args`` do not influence comparison of elliptic curves. + A consequence of this is that passing keyword arguments only + works when constructing an elliptic curve the first time: + + sage: E = EllipticCurve('5077a1', gens=[[1, -1], [-2, 3], [4, -7]]) + sage: E.gens() + [(-2 : 3 : 1), (1 : -1 : 1), (4 : -7 : 1)] + sage: E = EllipticCurve('5077a1', gens=[[-2, 3], [-1, 3], [0, 2]]) + sage: E.gens() + [(-2 : 3 : 1), (1 : -1 : 1), (4 : -7 : 1)] + + .. WARNING:: + + Manually specifying extra data is almost never necessary + and is not guaranteed to have any effect, as the above + example shows. Almost no checking is done, so specifying + incorrect data may lead to wrong results of computations + instead of errors or warnings. + + """ + R = None + if is_Ring(x): + (R, x) = (x, y) + + if j is not None: + if R is not None: try: - j = x(j) + j = R(j) except (ZeroDivisionError, ValueError, TypeError): - raise ValueError("First parameter must be a ring containing %s"%j) - else: + raise ValueError("First parameter must be a ring containing %s" % j) + elif x is not None: raise ValueError("First parameter (if present) must be a ring when j is specified") - return EllipticCurve_from_j(j, minimal_twist) + x = coefficients_from_j(j, minimal_twist) - if x is None: - raise TypeError("invalid input to EllipticCurve constructor") + if is_SymbolicEquation(x): + x = x.lhs() - x.rhs() - if is_SymbolicEquation(x): - x = x.lhs() - x.rhs() + if parent(x) is SR: + x = x._polynomial_(rings.QQ['x', 'y']) - if parent(x) is SR: - x = x._polynomial_(rings.QQ['x', 'y']) + if is_MPolynomial(x): + if y is None: + x = coefficients_from_Weierstrass_polynomial(x) + else: + x = coefficients_from_cubic(x, y, morphism=False) - if is_MPolynomial(x): - if y is None: - return EllipticCurve_from_Weierstrass_polynomial(x) - else: - return EllipticCurve_from_cubic(x, y, morphism=False) + if isinstance(x, basestring): + # Interpret x as a Cremona or LMFDB label. + from sage.databases.cremona import CremonaDatabase + x, data = CremonaDatabase().coefficients_and_data(x) + # User-provided keywords may override database entries. + data.update(kwds) + kwds = data - if is_Ring(x): - if is_RationalField(x): - return ell_rational_field.EllipticCurve_rational_field(x, y) - elif is_FiniteField(x) or (is_IntegerModRing(x) and x.characteristic().is_prime()): - return ell_finite_field.EllipticCurve_finite_field(x, y) - elif rings.is_pAdicField(x): - return ell_padic_field.EllipticCurve_padic_field(x, y) - elif is_NumberField(x): - return ell_number_field.EllipticCurve_number_field(x, y) - elif x in _Fields: - return ell_field.EllipticCurve_field(x, y) - return ell_generic.EllipticCurve_generic(x, y) + if not isinstance(x, (list, tuple)): + raise TypeError("invalid input to EllipticCurve constructor") - if isinstance(x, unicode): - x = str(x) + if len(x) == 2: + x = (0, 0, 0, x[0], x[1]) + elif len(x) != 5: + raise ValueError("sequence of coefficients must have length 2 or 5") - if isinstance(x, basestring): - return ell_rational_field.EllipticCurve_rational_field(x) + if R is None: + R = Sequence(x).universe() + if R in (rings.ZZ, int, long): + R = rings.QQ - if is_RingElement(x) and y is None: - raise TypeError("invalid input to EllipticCurve constructor") + return (R, tuple(R(a) for a in x)), kwds - if not isinstance(x, (list, tuple)): - raise TypeError("invalid input to EllipticCurve constructor") + def create_object(self, version, key, **kwds): + """ + Create an object from a ``UniqueFactory`` key. - x = Sequence(x) - if not (len(x) in [2,5]): - raise ValueError("sequence of coefficients must have length 2 or 5") - R = x.universe() + EXAMPLES:: - if isinstance(x[0], (rings.Rational, rings.Integer, int, long)): - return ell_rational_field.EllipticCurve_rational_field(x, y) + sage: E = EllipticCurve.create_object(0, (GF(3), (1, 2, 0, 1, 2))) + sage: type(E) + - elif is_NumberField(R): - return ell_number_field.EllipticCurve_number_field(x, y) + .. NOTE:: - elif rings.is_pAdicField(R): - return ell_padic_field.EllipticCurve_padic_field(x, y) + Keyword arguments are currently only passed to the + constructor for elliptic curves over `\\QQ`; elliptic + curves over other fields do not support them. - elif is_FiniteField(R) or (is_IntegerModRing(R) and R.characteristic().is_prime()): - return ell_finite_field.EllipticCurve_finite_field(x, y) + """ + R, x = key - elif R in _Fields: - return ell_field.EllipticCurve_field(x, y) + if R is rings.QQ: + from ell_rational_field import EllipticCurve_rational_field + return EllipticCurve_rational_field(x, **kwds) + elif is_NumberField(R): + from ell_number_field import EllipticCurve_number_field + return EllipticCurve_number_field(R, x) + elif rings.is_pAdicField(R): + from ell_padic_field import EllipticCurve_padic_field + return EllipticCurve_padic_field(R, x) + elif is_FiniteField(R) or (is_IntegerModRing(R) and R.characteristic().is_prime()): + from ell_finite_field import EllipticCurve_finite_field + return EllipticCurve_finite_field(R, x) + elif R in _Fields: + from ell_field import EllipticCurve_field + return EllipticCurve_field(R, x) + from ell_generic import EllipticCurve_generic + return EllipticCurve_generic(R, x) - return ell_generic.EllipticCurve_generic(x, y) +EllipticCurve = EllipticCurveFactory('sage.schemes.elliptic_curves.constructor.EllipticCurve') def EllipticCurve_from_Weierstrass_polynomial(f): @@ -404,7 +506,21 @@ def EllipticCurve_from_Weierstrass_polynomial(f): sage: from sage.schemes.elliptic_curves.constructor import EllipticCurve_from_Weierstrass_polynomial sage: EllipticCurve_from_Weierstrass_polynomial(-w^2 + z^3 + 1) Elliptic Curve defined by y^2 = x^3 + 1 over Rational Field - """ + """ + return EllipticCurve(coefficients_from_Weierstrass_polynomial(f)) + +def coefficients_from_Weierstrass_polynomial(f): + """ + Return the coefficients `(a_1, a_2, a_3, a_4, a_5)` for a cubic in + Weierstrass form. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.constructor import coefficients_from_Weierstrass_polynomial + sage: R. = QQ[] + sage: coefficients_from_Weierstrass_polynomial(-w^2 + z^3 + 1) + [0, 0, 0, 0, 1] + """ R = f.parent() cubic_variables = [ x for x in R.gens() if f.degree(x) == 3 ] quadratic_variables = [ y for y in R.gens() if f.degree(y) == 2 ] @@ -438,7 +554,7 @@ def EllipticCurve_from_Weierstrass_polynomial(f): raise ValueError('the coefficient of x^3 and -y^2 must be the same') elif x3 != 1: a1, a2, a3, a4, a6 = a1/x3, a2/x3, a3/x3, a4/x3, a6/x3 - return EllipticCurve([a1, a2, a3, a4, a6]) + return [a1, a2, a3, a4, a6] def EllipticCurve_from_c4c6(c4, c6): @@ -516,6 +632,36 @@ def EllipticCurve_from_j(j, minimal_twist=True): True """ + return EllipticCurve(coefficients_from_j(j, minimal_twist)) + +def coefficients_from_j(j, minimal_twist=True): + """ + Return Weierstrass coefficients `(a_1, a_2, a_3, a_4, a_6)` for an + elliptic curve with given `j`-invariant. + + INPUT: + + See :func:`EllipticCurve_from_j`. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.constructor import coefficients_from_j + sage: coefficients_from_j(0) + [0, 0, 1, 0, 0] + sage: coefficients_from_j(1728) + [0, 0, 0, -1, 0] + sage: coefficients_from_j(1) + [1, 0, 0, 36, 3455] + + The ``minimal_twist`` parameter (ignored except over `\\QQ` and + True by default) controls whether or not a minimal twist is + computed:: + + sage: coefficients_from_j(100) + [0, 1, 0, 3392, 307888] + sage: coefficients_from_j(100, minimal_twist=False) + [0, 0, 0, 488400, -530076800] + """ try: K = j.parent() except AttributeError: @@ -526,26 +672,26 @@ def EllipticCurve_from_j(j, minimal_twist=True): char=K.characteristic() if char==2: if j == 0: - return EllipticCurve(K, [ 0, 0, 1, 0, 0 ]) + return Sequence([0, 0, 1, 0, 0], universe=K) else: - return EllipticCurve(K, [ 1, 0, 0, 0, 1/j ]) + return Sequence([1, 0, 0, 0, 1/j], universe=K) if char == 3: if j==0: - return EllipticCurve(K, [ 0, 0, 0, 1, 0 ]) + return Sequence([0, 0, 0, 1, 0], universe=K) else: - return EllipticCurve(K, [ 0, j, 0, 0, -j**2 ]) + return Sequence([0, j, 0, 0, -j**2], universe=K) if K is rings.RationalField(): # we construct the minimal twist, i.e. the curve with minimal # conductor with this j_invariant: if j == 0: - return EllipticCurve(K, [ 0, 0, 1, 0, 0 ]) # 27a3 + return Sequence([0, 0, 1, 0, 0], universe=K) # 27a3 if j == 1728: - return EllipticCurve(K, [ 0, 0, 0, -1, 0 ]) # 32a2 + return Sequence([0, 0, 0, -1, 0], universe=K) # 32a2 if not minimal_twist: k=j-1728 - return EllipticCurve(K, [0,0,0,-3*j*k, -2*j*k**2]) + return Sequence([0, 0, 0, -3*j*k, -2*j*k**2], universe=K) n = j.numerator() m = n-1728*j.denominator() @@ -567,15 +713,15 @@ def EllipticCurve_from_j(j, minimal_twist=True): Elist = [E1] + [E1.quadratic_twist(t) for t in tw] crv_cmp = lambda E,F: cmp(E.conductor(),F.conductor()) Elist.sort(cmp=crv_cmp) - return Elist[0] + return Sequence(Elist[0].ainvs()) # defaults for all other fields: if j == 0: - return EllipticCurve(K, [ 0, 0, 0, 0, 1 ]) + return Sequence([0, 0, 0, 0, 1], universe=K) if j == 1728: - return EllipticCurve(K, [ 0, 0, 0, 1, 0 ]) + return Sequence([0, 0, 0, 1, 0], universe=K) k=j-1728 - return EllipticCurve(K, [0,0,0,-3*j*k, -2*j*k**2]) + return Sequence([0, 0, 0, -3*j*k, -2*j*k**2], universe=K) def EllipticCurve_from_cubic(F, P, morphism=True): diff --git a/src/sage/schemes/elliptic_curves/ec_database.py b/src/sage/schemes/elliptic_curves/ec_database.py index f4d0970b408..f812b3ddce5 100644 --- a/src/sage/schemes/elliptic_curves/ec_database.py +++ b/src/sage/schemes/elliptic_curves/ec_database.py @@ -35,7 +35,7 @@ import os -from ell_rational_field import (EllipticCurve_rational_field) +from constructor import EllipticCurve class EllipticCurves: def rank(self, rank, tors=0, n=10, labels=False): @@ -98,7 +98,7 @@ def rank(self, rank, tors=0, n=10, labels=False): if labels: v.append(label) else: - E = EllipticCurve_rational_field(eval(ainvs)) + E = EllipticCurve(eval(ainvs)) E._set_rank(r) E._set_torsion_order(t) E._set_conductor(N) diff --git a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py index 5ef333d27bc..8de8b635cd8 100644 --- a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py +++ b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py @@ -59,7 +59,7 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from copy import deepcopy, copy +from copy import copy from sage.categories import homset @@ -736,6 +736,22 @@ class EllipticCurveIsogeny(Morphism): sage: phi.rational_maps() (((4/25*i + 3/25)*x^5 + (4/5*i - 2/5)*x^3 - x)/(x^4 + (-4/5*i + 2/5)*x^2 + (-4/25*i - 3/25)), ((11/125*i + 2/125)*x^6*y + (-23/125*i + 64/125)*x^4*y + (141/125*i + 162/125)*x^2*y + (3/25*i - 4/25)*y)/(x^6 + (-6/5*i + 3/5)*x^4 + (-12/25*i - 9/25)*x^2 + (2/125*i - 11/125))) + + Domain and codomain tests (see :trac:`12880`):: + + sage: E = EllipticCurve(QQ, [0,0,0,1,0]) + sage: phi = EllipticCurveIsogeny(E, E(0,0)) + sage: phi.domain() == E + True + sage: phi.codomain() + Elliptic Curve defined by y^2 = x^3 - 4*x over Rational Field + + sage: E = EllipticCurve(GF(31), [1,0,0,1,2]) + sage: phi = EllipticCurveIsogeny(E, [17, 1]) + sage: phi.domain() + Elliptic Curve defined by y^2 + x*y = x^3 + x + 2 over Finite Field of size 31 + sage: phi.codomain() + Elliptic Curve defined by y^2 + x*y = x^3 + 24*x + 6 over Finite Field of size 31 """ #################### @@ -1174,7 +1190,7 @@ def __neg__(self): kernel_list = self.__kernel_list self.__kernel_list = None - output = deepcopy(self) + output = copy(self) # reset the kernel lists output.__kernel_list = copy(kernel_list) @@ -1322,7 +1338,7 @@ def __perform_inheritance_housekeeping(self): self._codomain = self.__E2 # sets up the parent - parent = homset.Hom(self.__E1(0).parent(), self.__E2(0).parent()) + parent = homset.Hom(self.__E1, self.__E2) Morphism.__init__(self, parent) return @@ -2574,46 +2590,6 @@ def __compute_E2_via_kohel(self): # public isogeny methods # - def domain(self): - r""" - Returns the domain curve of this isogeny. - - EXAMPLES:: - - sage: E = EllipticCurve(QQ, [0,0,0,1,0]) - sage: phi = EllipticCurveIsogeny(E, E(0,0)) - sage: phi.domain() == E - True - - sage: E = EllipticCurve(GF(31), [1,0,0,1,2]) - sage: phi = EllipticCurveIsogeny(E, [17, 1]) - sage: phi.domain() - Elliptic Curve defined by y^2 + x*y = x^3 + x + 2 over Finite Field of size 31 - - """ - return self.__E1 - - - def codomain(self): - r""" - Returns the codomain (range) curve of this isogeny. - - EXAMPLES:: - - sage: E = EllipticCurve(QQ, [0,0,0,1,0]) - sage: phi = EllipticCurveIsogeny(E, E((0,0))) - sage: phi.codomain() - Elliptic Curve defined by y^2 = x^3 - 4*x over Rational Field - - sage: E = EllipticCurve(GF(31), [1,0,0,1,2]) - sage: phi = EllipticCurveIsogeny(E, [17, 1]) - sage: phi.codomain() - Elliptic Curve defined by y^2 + x*y = x^3 + 24*x + 6 over Finite Field of size 31 - - """ - return self.__E2 - - def degree(self): r""" Returns the degree of this isogeny. @@ -3343,25 +3319,16 @@ def _composition_(self, right, homset): NotImplementedError The following should test that :meth:`_composition_` is called - upon a product. However phi is currently improperly - constructed (see :trac:`12880`), which triggers an assertion - failure before the actual call :: + upon a product (modified for :trac:`12880` ; see :trac:`16245` where we + fix the _composition_ issue). sage: phi*phi Traceback (most recent call last): ... - ValueError: Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7 is not in Category of hom sets in Category of schemes - - Here would be the desired output:: - - sage: phi*phi # not tested - Traceback (most recent call last): - ... NotImplementedError """ raise NotImplementedError - def is_injective(self): r""" Method inherited from the morphism class. Returns ``True`` if @@ -3771,13 +3738,9 @@ def compute_intermediate_curves(E1, E2): sage: compute_intermediate_curves(E, E2) (Elliptic Curve defined by y^2 = x^3 + x over Number Field in i with defining polynomial x^2 + 1, Elliptic Curve defined by y^2 = x^3 + 16*x over Number Field in i with defining polynomial x^2 + 1, - Generic morphism: - From: Abelian group of points on Elliptic Curve defined by y^2 = x^3 + x over Number Field in i with defining polynomial x^2 + 1 - To: Abelian group of points on Elliptic Curve defined by y^2 = x^3 + x over Number Field in i with defining polynomial x^2 + 1 + Generic endomorphism of Abelian group of points on Elliptic Curve defined by y^2 = x^3 + x over Number Field in i with defining polynomial x^2 + 1 Via: (u,r,s,t) = (1, 0, 0, 0), - Generic morphism: - From: Abelian group of points on Elliptic Curve defined by y^2 = x^3 + 16*x over Number Field in i with defining polynomial x^2 + 1 - To: Abelian group of points on Elliptic Curve defined by y^2 = x^3 + 16*x over Number Field in i with defining polynomial x^2 + 1 + Generic endomorphism of Abelian group of points on Elliptic Curve defined by y^2 = x^3 + 16*x over Number Field in i with defining polynomial x^2 + 1 Via: (u,r,s,t) = (1, 0, 0, 0)) """ @@ -3847,13 +3810,9 @@ def compute_sequence_of_maps(E1, E2, ell): sage: E = EllipticCurve(K, [0,0,0,1,0]) sage: E2 = EllipticCurve(K, [0,0,0,16,0]) sage: compute_sequence_of_maps(E, E2, 4) - (Generic morphism: - From: Abelian group of points on Elliptic Curve defined by y^2 = x^3 + x over Number Field in i with defining polynomial x^2 + 1 - To: Abelian group of points on Elliptic Curve defined by y^2 = x^3 + x over Number Field in i with defining polynomial x^2 + 1 + (Generic endomorphism of Abelian group of points on Elliptic Curve defined by y^2 = x^3 + x over Number Field in i with defining polynomial x^2 + 1 Via: (u,r,s,t) = (1, 0, 0, 0), - Generic morphism: - From: Abelian group of points on Elliptic Curve defined by y^2 = x^3 + 16*x over Number Field in i with defining polynomial x^2 + 1 - To: Abelian group of points on Elliptic Curve defined by y^2 = x^3 + 16*x over Number Field in i with defining polynomial x^2 + 1 + Generic endomorphism of Abelian group of points on Elliptic Curve defined by y^2 = x^3 + 16*x over Number Field in i with defining polynomial x^2 + 1 Via: (u,r,s,t) = (1, 0, 0, 0), Elliptic Curve defined by y^2 = x^3 + x over Number Field in i with defining polynomial x^2 + 1, Elliptic Curve defined by y^2 = x^3 + 16*x over Number Field in i with defining polynomial x^2 + 1, diff --git a/src/sage/schemes/elliptic_curves/ell_field.py b/src/sage/schemes/elliptic_curves/ell_field.py index 47cb4472e25..4c53147ba71 100644 --- a/src/sage/schemes/elliptic_curves/ell_field.py +++ b/src/sage/schemes/elliptic_curves/ell_field.py @@ -19,6 +19,7 @@ from sage.rings.complex_field import is_ComplexField from sage.rings.real_mpfr import is_RealField from constructor import EllipticCurve +from sage.schemes.elliptic_curves.ell_point import EllipticCurvePoint_field from ell_curve_isogeny import EllipticCurveIsogeny, isogeny_codomain_from_kernel @@ -26,6 +27,8 @@ class EllipticCurve_field(ell_generic.EllipticCurve_generic): base_field = ell_generic.EllipticCurve_generic.base_ring + _point = EllipticCurvePoint_field + # Twists: rewritten by John Cremona as follows: # # Quadratic twist allowed except when char=2, j=0 diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py index 98a2840e887..f671a3c4bb5 100644 --- a/src/sage/schemes/elliptic_curves/ell_finite_field.py +++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py @@ -50,59 +50,43 @@ class EllipticCurve_finite_field(EllipticCurve_field, HyperellipticCurve_finite_field): """ Elliptic curve over a finite field. - """ - def __init__(self, x, y=None): - """ - Special constructor for elliptic curves over a finite field - - EXAMPLES:: - - sage: EllipticCurve(GF(101),[2,3]) - Elliptic Curve defined by y^2 = x^3 + 2*x + 3 over Finite Field of size 101 - :: - - sage: F=GF(101^2, 'a') - sage: EllipticCurve([F(2),F(3)]) - Elliptic Curve defined by y^2 = x^3 + 2*x + 3 over Finite Field in a of size 101^2 - - Elliptic curves over `\ZZ/N\ZZ` with `N` prime are of type - "elliptic curve over a finite field":: - - sage: F = Zmod(101) - sage: EllipticCurve(F, [2, 3]) - Elliptic Curve defined by y^2 = x^3 + 2*x + 3 over Ring of integers modulo 101 - sage: E = EllipticCurve([F(2), F(3)]) - sage: type(E) - - sage: E.category() - Category of schemes over Ring of integers modulo 101 - - Elliptic curves over `\ZZ/N\ZZ` with `N` composite are of type - "generic elliptic curve":: - - sage: F = Zmod(95) - sage: EllipticCurve(F, [2, 3]) - Elliptic Curve defined by y^2 = x^3 + 2*x + 3 over Ring of integers modulo 95 - sage: E = EllipticCurve([F(2), F(3)]) - sage: type(E) - - sage: E.category() - Category of schemes over Ring of integers modulo 95 - sage: TestSuite(E).run(skip=["_test_elements"]) - """ - if isinstance(x, list): - seq = Sequence(x) - else: - seq = Sequence(y, universe=x) - ainvs = list(seq) - field = seq.universe() - if not isinstance(field, ring.Ring): - raise TypeError + EXAMPLES:: - EllipticCurve_field.__init__(self, ainvs) + sage: EllipticCurve(GF(101),[2,3]) + Elliptic Curve defined by y^2 = x^3 + 2*x + 3 over Finite Field of size 101 + + sage: F=GF(101^2, 'a') + sage: EllipticCurve([F(2),F(3)]) + Elliptic Curve defined by y^2 = x^3 + 2*x + 3 over Finite Field in a of size 101^2 + + Elliptic curves over `\ZZ/N\ZZ` with `N` prime are of type + "elliptic curve over a finite field":: + + sage: F = Zmod(101) + sage: EllipticCurve(F, [2, 3]) + Elliptic Curve defined by y^2 = x^3 + 2*x + 3 over Ring of integers modulo 101 + sage: E = EllipticCurve([F(2), F(3)]) + sage: type(E) + + sage: E.category() + Category of schemes over Ring of integers modulo 101 + + Elliptic curves over `\ZZ/N\ZZ` with `N` composite are of type + "generic elliptic curve":: + + sage: F = Zmod(95) + sage: EllipticCurve(F, [2, 3]) + Elliptic Curve defined by y^2 = x^3 + 2*x + 3 over Ring of integers modulo 95 + sage: E = EllipticCurve([F(2), F(3)]) + sage: type(E) + + sage: E.category() + Category of schemes over Ring of integers modulo 95 + sage: TestSuite(E).run(skip=["_test_elements"]) + """ - self._point = ell_point.EllipticCurvePoint_finite_field + _point = ell_point.EllipticCurvePoint_finite_field def plot(self, *args, **kwds): """ diff --git a/src/sage/schemes/elliptic_curves/ell_generic.py b/src/sage/schemes/elliptic_curves/ell_generic.py index 3604674a3bb..53e27de3228 100644 --- a/src/sage/schemes/elliptic_curves/ell_generic.py +++ b/src/sage/schemes/elliptic_curves/ell_generic.py @@ -63,6 +63,7 @@ from sage.rings.number_field.number_field_base import is_NumberField import sage.misc.misc as misc from sage.misc.cachefunc import cached_method, cached_function +from sage.misc.fast_methods import WithEqualityById # Schemes import sage.schemes.projective.projective_space as projective_space @@ -101,7 +102,7 @@ def is_EllipticCurve(x): """ return isinstance(x, EllipticCurve_generic) -class EllipticCurve_generic(plane_curve.ProjectiveCurve_generic): +class EllipticCurve_generic(WithEqualityById, plane_curve.ProjectiveCurve_generic): r""" Elliptic curve over a generic base ring. @@ -116,18 +117,22 @@ class EllipticCurve_generic(plane_curve.ProjectiveCurve_generic): sage: -5*P (179051/80089 : -91814227/22665187 : 1) """ - def __init__(self, ainvs, extra=None): + def __init__(self, K, ainvs): r""" - Constructor from `a`-invariants (long or short Weierstrass coefficients). + Construct an elliptic curve from Weierstrass `a`-coefficients. INPUT: - - ``ainvs`` (list) -- either `[a_1,a_2,a_3,a_4,a_6]` or - `[a_4,a_6]` (with `a_1=a_2=a_3=0` in the second case). + - ``K`` -- a ring - .. note:: + - ``ainvs`` -- a list or tuple `[a_1, a_2, a_3, a_4, a_6]` of + Weierstrass coefficients. + + .. NOTE:: - See constructor.py for more variants. + This class should not be called directly; use + :class:`sage.constructor.EllipticCurve` to construct + elliptic curves. EXAMPLES:: @@ -146,41 +151,26 @@ def __init__(self, ainvs, extra=None): sage: EllipticCurve(IntegerModRing(91),[1,2,3,4,5]) Elliptic Curve defined by y^2 + x*y + 3*y = x^3 + 2*x^2 + 4*x + 5 over Ring of integers modulo 91 """ - if extra is not None: # possibility of two arguments - K, ainvs = ainvs, extra - else: - K = ainvs[0].parent() - assert len(ainvs) == 2 or len(ainvs) == 5 self.__base_ring = K - ainvs = [K(x) for x in ainvs] - if len(ainvs) == 2: - ainvs = [K(0),K(0),K(0)] + ainvs - self.__ainvs = tuple(ainvs) + self.__ainvs = tuple(K(a) for a in ainvs) if self.discriminant() == 0: - raise ArithmeticError("Invariants %s define a singular curve."%ainvs) + raise ArithmeticError("invariants " + str(ainvs) + " define a singular curve") PP = projective_space.ProjectiveSpace(2, K, names='xyz'); x, y, z = PP.coordinate_ring().gens() a1, a2, a3, a4, a6 = ainvs f = y**2*z + (a1*x + a3*z)*y*z \ - (x**3 + a2*x**2*z + a4*x*z**2 + a6*z**3) plane_curve.ProjectiveCurve_generic.__init__(self, PP, f) - # TODO: cleanup, are these two point classes redundant? # See #1975: we deliberately set the class to # EllipticCurvePoint_finite_field for finite rings, so that we # can do some arithmetic on points over Z/NZ, for teaching # purposes. - from sage.rings.finite_rings.constructor import is_FiniteField from sage.rings.finite_rings.integer_mod_ring import is_IntegerModRing - if is_FiniteField(K) or is_IntegerModRing(K): - self._morphism = self._point = ell_point.EllipticCurvePoint_finite_field - elif K.is_field(): - if is_NumberField(K): - self._morphism = self._point = ell_point.EllipticCurvePoint_number_field - else: - self._morphism = self._point = ell_point.EllipticCurvePoint_field - else: - self._morphism = self._point = ell_point.EllipticCurvePoint + if is_IntegerModRing(K): + self._point = ell_point.EllipticCurvePoint_finite_field + + _point = ell_point.EllipticCurvePoint def _defining_params_(self): r""" @@ -198,19 +188,6 @@ def _defining_params_(self): """ return (self.__base_ring, list(self.__ainvs)) - def __hash__(self): - """ - TESTS:: - - sage: E = EllipticCurve('37a') - sage: hash(E) - -1437250549 # 32-bit - -2189969105152029685 # 64-bit - sage: hash(E) != hash(E.change_ring(GF(7))) - True - """ - return hash((self.__base_ring, self.__ainvs)) - def _repr_(self): """ String representation of elliptic curve. @@ -427,25 +404,6 @@ def _symbolic_(self, SR): x, y = SR.var('x, y') return y**2 + a[0]*x*y + a[2]*y == x**3 + a[1]*x**2 + a[3]*x + a[4] - def __cmp__(self, other): - """ - Standard comparison function for elliptic curves, to allow sorting - and equality testing. - - EXAMPLES:: - - sage: E=EllipticCurve(QQ,[1,1]) - sage: F=EllipticCurve(QQ,[0,0,0,1,1]) - sage: E==F - True - """ - if not isinstance(other, EllipticCurve_generic): - return -1 - t = cmp(self.base_ring(), other.base_ring()) - if t: - return t - return cmp(self.ainvs(), other.ainvs()) - def __contains__(self, P): """ Returns True if and only if P is a point on the elliptic curve. P @@ -2301,19 +2259,11 @@ def isomorphisms(self, other, field=None): sage: E = EllipticCurve_from_j(QQ(0)) # a curve with j=0 over QQ sage: F = EllipticCurve('27a3') # should be the same one sage: E.isomorphisms(F); - [Generic morphism: - From: Abelian group of points on Elliptic Curve defined - by y^2 + y = x^3 over Rational Field - To: Abelian group of points on Elliptic Curve defined - by y^2 + y = x^3 over Rational Field - Via: (u,r,s,t) = (-1, 0, 0, -1), Generic morphism: - From: Abelian group of points on Elliptic Curve defined - by y^2 + y = x^3 over Rational Field - To: Abelian group of points on Elliptic Curve defined - by y^2 + y = x^3 over Rational Field + [Generic endomorphism of Abelian group of points on Elliptic Curve defined by y^2 + y = x^3 over Rational Field + Via: (u,r,s,t) = (-1, 0, 0, -1), + Generic endomorphism of Abelian group of points on Elliptic Curve defined by y^2 + y = x^3 over Rational Field Via: (u,r,s,t) = (1, 0, 0, 0)] - We can also find isomorphisms defined over extension fields:: sage: E=EllipticCurve(GF(7),[0,0,0,1,1]) diff --git a/src/sage/schemes/elliptic_curves/ell_number_field.py b/src/sage/schemes/elliptic_curves/ell_number_field.py index 6ed45de5107..2425f34cc57 100644 --- a/src/sage/schemes/elliptic_curves/ell_number_field.py +++ b/src/sage/schemes/elliptic_curves/ell_number_field.py @@ -119,15 +119,8 @@ class EllipticCurve_number_field(EllipticCurve_field): sage: EllipticCurve([i, i - 1, i + 1, 24*i + 15, 14*i + 35]) Elliptic Curve defined by y^2 + i*x*y + (i+1)*y = x^3 + (i-1)*x^2 + (24*i+15)*x + (14*i+35) over Number Field in i with defining polynomial x^2 + 1 """ - def __init__(self, x, y=None): + def __init__(self, K, ainvs): r""" - Allow some ways to create an elliptic curve over a number - field in addition to the generic ones. - - INPUT: - - - ``x``, ``y`` -- see examples. - EXAMPLES: A curve from the database of curves over `\QQ`, but over a larger field: @@ -142,25 +135,10 @@ def __init__(self, x, y=None): Elliptic Curve defined by y^2 + y = x^3 + (-1)*x^2 over Number Field in i with defining polynomial x^2 + 1 """ - if y is None: - if isinstance(x, list): - ainvs = x - field = ainvs[0].parent() - else: - if isinstance(y, str): - from sage.databases.cremona import CremonaDatabase - field = x - X = CremonaDatabase()[y] - ainvs = list(X.a_invariants()) - else: - field = x - ainvs = y - if not (isinstance(field, Ring) and isinstance(ainvs,list)): - raise TypeError - - EllipticCurve_field.__init__(self, [field(x) for x in ainvs]) - self._point = ell_point.EllipticCurvePoint_number_field self._known_points = [] + EllipticCurve_field.__init__(self, K, ainvs) + + _point = ell_point.EllipticCurvePoint_number_field def base_extend(self, R): """ diff --git a/src/sage/schemes/elliptic_curves/ell_padic_field.py b/src/sage/schemes/elliptic_curves/ell_padic_field.py index 9ca4d8d94d5..e7b226283d1 100644 --- a/src/sage/schemes/elliptic_curves/ell_padic_field.py +++ b/src/sage/schemes/elliptic_curves/ell_padic_field.py @@ -35,38 +35,19 @@ class EllipticCurve_padic_field(EllipticCurve_field, HyperellipticCurve_padic_field): """ Elliptic curve over a padic field. + + EXAMPLES:: + + sage: Qp=pAdicField(17) + sage: E=EllipticCurve(Qp,[2,3]); E + Elliptic Curve defined by y^2 = x^3 + (2+O(17^20))*x + (3+O(17^20)) over 17-adic Field with capped relative precision 20 + sage: E == loads(dumps(E)) + True """ - def __init__(self, x, y=None): - """ - Constructor from [a1,a2,a3,a4,a6] or [a4,a6]. - EXAMPLES:: + _point = ell_point.EllipticCurvePoint_field - sage: Qp=pAdicField(17) - sage: E=EllipticCurve(Qp,[2,3]); E - Elliptic Curve defined by y^2 = x^3 + (2+O(17^20))*x + (3+O(17^20)) over 17-adic Field with capped relative precision 20 - sage: E == loads(dumps(E)) - True - """ - if y is None: - if isinstance(x, list): - ainvs = x - field = ainvs[0].parent() - else: - if isinstance(y, str): - field = x - X = sage.databases.cremona.CremonaDatabase()[y] - ainvs = [field(a) for a in X.a_invariants()] - else: - field = x - ainvs = y - if not (isinstance(field, ring.Ring) and isinstance(ainvs,list)): - raise TypeError - - EllipticCurve_field.__init__(self, [field(x) for x in ainvs]) - - self._point = ell_point.EllipticCurvePoint_field - self._genus = 1 + _genus = 1 def frobenius(self, P=None): """ diff --git a/src/sage/schemes/elliptic_curves/ell_rational_field.py b/src/sage/schemes/elliptic_curves/ell_rational_field.py index cc2226e88e1..9e3be1be89e 100644 --- a/src/sage/schemes/elliptic_curves/ell_rational_field.py +++ b/src/sage/schemes/elliptic_curves/ell_rational_field.py @@ -117,13 +117,14 @@ class EllipticCurve_rational_field(EllipticCurve_number_field): INPUT: - - ``ainvs`` (list or string) -- either `[a_1,a_2,a_3,a_4,a_6]` or - `[a_4,a_6]` (with `a_1=a_2=a_3=0`) or a valid label from the - database. + - ``ainvs`` -- a list or tuple `[a_1, a_2, a_3, a_4, a_6]` of + Weierstrass coefficients. .. note:: - See constructor.py for more variants. + This class should not be called directly; use + :class:`sage.constructor.EllipticCurve` to construct + elliptic curves. EXAMPLES: @@ -133,51 +134,26 @@ class EllipticCurve_rational_field(EllipticCurve_number_field): Elliptic Curve defined by y^2 + x*y + 3*y = x^3 + 2*x^2 + 4*x + 5 over Rational Field Construction from Weierstrass coefficients (`a`-invariants), - short form (sets `a_1=a_2=a_3=0`):: + short form (sets `a_1 = a_2 = a_3 = 0`):: sage: EllipticCurve([4,5]).ainvs() (0, 0, 0, 4, 5) - Construction from a database label:: + Constructor from a Cremona label:: sage: EllipticCurve('389a1') Elliptic Curve defined by y^2 + y = x^3 + x^2 - 2*x over Rational Field + Constructor from an LMFDB label:: + + sage: EllipticCurve('462.f3') + Elliptic Curve defined by y^2 + x*y = x^3 - 363*x + 1305 over Rational Field + """ - def __init__(self, ainvs, extra=None): + def __init__(self, ainvs, **kwds): r""" Constructor for the EllipticCurve_rational_field class. - INPUT: - - - ``ainvs`` (list or string) -- either `[a_1,a_2,a_3,a_4,a_6]` - or `[a_4,a_6]` (with `a_1=a_2=a_3=0`) or a valid label from - the database. - - .. note:: - - See constructor.py for more variants. - - EXAMPLES:: - - sage: E = EllipticCurve([1,2,3,4,5]); E - Elliptic Curve defined by y^2 + x*y + 3*y = x^3 + 2*x^2 + 4*x + 5 over Rational Field - - Constructor from `[a_4,a_6]` sets `a_1=a_2=a_3=0`:: - - sage: EllipticCurve([4,5]).ainvs() - (0, 0, 0, 4, 5) - - Constructor from a Cremona label:: - - sage: EllipticCurve('389a1') - Elliptic Curve defined by y^2 + y = x^3 + x^2 - 2*x over Rational Field - - Constructor from an LMFDB label:: - - sage: EllipticCurve('462.f3') - Elliptic Curve defined by y^2 + x*y = x^3 - 363*x + 1305 over Rational Field - TESTS: When constructing a curve from the large database using a @@ -190,8 +166,6 @@ def __init__(self, ainvs, extra=None): [True, True] """ - if extra is not None: # possibility of two arguments (the first would be the field) - ainvs = extra self.__np = {} self.__gens = {} self.__rank = {} @@ -199,27 +173,24 @@ def __init__(self, ainvs, extra=None): self.__generalized_modular_degree = {} self.__generalized_congruence_number = {} self._isoclass = {} - if isinstance(ainvs, str): - label = ainvs - X = sage.databases.cremona.CremonaDatabase()[label] - EllipticCurve_number_field.__init__(self, Q, list(X.a_invariants())) - for attr in ['rank', 'torsion_order', 'cremona_label', 'conductor', - 'modular_degree', 'gens', 'regulator']: - s = "_EllipticCurve_rational_field__"+attr - if hasattr(X,s): - if attr == 'gens': # see #10999 - gens_dict = getattr(X, s) - for boo in gens_dict.keys(): - gens_dict[boo] = [self(P) for P in gens_dict[boo]] - setattr(self, s, gens_dict) - else: - setattr(self, s, getattr(X, s)) - if hasattr(X,'_lmfdb_label'): - self._lmfdb_label = X._lmfdb_label - return EllipticCurve_number_field.__init__(self, Q, ainvs) - if self.base_ring() != Q: - raise TypeError("Base field (=%s) must be the Rational Field."%self.base_ring()) + + if 'conductor' in kwds: + self._set_conductor(kwds['conductor']) + if 'cremona_label' in kwds: + self._set_cremona_label(kwds['cremona_label']) + if 'gens' in kwds: + self._set_gens(kwds['gens']) + if 'lmfdb_label' in kwds: + self._lmfdb_label = kwds['lmfdb_label'] + if 'modular_degree' in kwds: + self._set_modular_degree(kwds['modular_degree']) + if 'rank' in kwds: + self._set_rank(kwds['rank']) + if 'regulator' in kwds: + self.__regulator[True] = kwds['regulator'] + if 'torsion_order' in kwds: + self._set_torsion_order(kwds['torsion_order']) def _set_rank(self, r): """ @@ -279,11 +250,11 @@ def _set_cremona_label(self, L): sage: E._set_cremona_label('bogus') sage: E.label() 'bogus' - sage: E.database_curve().label() + sage: label = E.database_attributes()['cremona_label']; label '37a1' sage: E.label() # no change 'bogus' - sage: E._set_cremona_label(E.database_curve().label()) + sage: E._set_cremona_label(label) sage: E.label() # now it is correct '37a1' """ @@ -306,6 +277,7 @@ def _set_conductor(self, N): sage: E._set_conductor(99) # bogus value -- not checked sage: E.conductor() # returns bogus cached value 99 + sage: E._set_conductor(37) """ self.__conductor_pari = Integer(N) @@ -326,9 +298,7 @@ def _set_modular_degree(self, deg): sage: E._set_modular_degree(123456789) sage: E.modular_degree() 123456789 - sage: E._set_modular_degree(E.database_curve().modular_degree()) - sage: E.modular_degree() - 1984 + sage: E._set_modular_degree(1984) """ self.__modular_degree = Integer(deg) @@ -634,6 +604,7 @@ def pari_curve(self, prec=None, factor=1): :: sage: E = EllipticCurve('37a1') + sage: _ = E.__dict__.pop('_pari_curve') # clear cached data sage: Epari = E.pari_curve() sage: Epari[14].python().prec() 64 @@ -748,11 +719,51 @@ def pari_mincurve(self, prec=None, factor=1): # self.__min_transform = change return mc + @cached_method + def database_attributes(self): + """ + Return a dictionary containing information about ``self`` in + the elliptic curve database. + + If there is no elliptic curve isomorphic to ``self`` in the + database, a ``RuntimeError`` is raised. + + EXAMPLES:: + + sage: E = EllipticCurve((0, 0, 1, -1, 0)) + sage: data = E.database_attributes() + sage: data['conductor'] + 37 + sage: data['cremona_label'] + '37a1' + sage: data['rank'] + 1 + sage: data['torsion_order'] + 1 + + sage: E = EllipticCurve((8, 13, 21, 34, 55)) + sage: E.database_attributes() + Traceback (most recent call last): + ... + RuntimeError: no database entry for Elliptic Curve defined by y^2 + 8*x*y + 21*y = x^3 + 13*x^2 + 34*x + 55 over Rational Field + + """ + from sage.databases.cremona import CremonaDatabase + ainvs = self.minimal_model().ainvs() + try: + return CremonaDatabase().data_from_coefficients(ainvs) + except RuntimeError: + raise RuntimeError("no database entry for %s" % self) + def database_curve(self): """ Return the curve in the elliptic curve database isomorphic to this curve, if possible. Otherwise raise a RuntimeError exception. + Since :trac:`11474`, this returns exactly the same curve as + :meth:`minimal_model`; the only difference is the additional + work of checking whether the curve is in the database. + EXAMPLES:: sage: E = EllipticCurve([0,1,2,3,4]) @@ -777,7 +788,6 @@ def database_curve(self): raise RuntimeError("Elliptic curve %s not in the database."%self) return self.__database_curve - def Np(self, p): r""" The number of points on `E` modulo `p`. @@ -1459,6 +1469,7 @@ def simon_two_descent(self, verbose=0, lim1=5, lim3=50, limtriv=3, sage: E.simon_two_descent() (1, 1, [(0 : 0 : 1)]) sage: E = EllipticCurve('389a1') + sage: E._known_points = [] # clear cached points sage: E.simon_two_descent() (2, 2, [(5/4 : 5/8 : 1), (-3/4 : 7/8 : 1)]) sage: E = EllipticCurve('5077a1') @@ -1691,9 +1702,10 @@ def rank(self, use_database=False, verbose=False, return self.__rank[True] if use_database: try: - self.__rank[True] = self.database_curve().rank() + self.__rank[True] = self.database_attributes()['rank'] return self.__rank[True] - except (AttributeError, RuntimeError): + except (KeyError, RuntimeError): + # curve not in database, or rank not known pass if not only_use_mwrank: N = self.conductor() @@ -1894,13 +1906,12 @@ def _compute_gens(self, proof, if use_database: try: - E = self.database_curve() + E = self.minimal_model() + data = self.database_attributes() iso = E.isomorphism_to(self) - try: - return [iso(P) for P in E.__gens[True]], True - except (KeyError,AttributeError): # database curve does not have the gens - pass - except (RuntimeError, KeyError): # curve or gens not in database + return [iso(E(P)) for P in data['gens']], True + except (KeyError, RuntimeError): + # curve not in database, or generators not known pass if self.conductor() > 10**7: @@ -2609,7 +2620,7 @@ def minimal_model(self): return self.__minimal_model except AttributeError: F = self.pari_mincurve() - self.__minimal_model = EllipticCurve_rational_field([Q(F[i]) for i in range(5)]) + self.__minimal_model = constructor.EllipticCurve([Q(F[i]) for i in range(5)]) return self.__minimal_model def is_minimal(self): @@ -3156,7 +3167,7 @@ def integral_weierstrass_model(self): sage: E = EllipticCurve('17a1') sage: E.integral_weierstrass_model() #random - doctest:1: DeprecationWarning: integral_weierstrass_model is deprecated, use integral_short_weierstrass_model instead! + doctest:...: DeprecationWarning: integral_weierstrass_model is deprecated, use integral_short_weierstrass_model instead! Elliptic Curve defined by y^2 = x^3 - 11*x - 890 over Rational Field """ from sage.misc.superseded import deprecation @@ -3543,16 +3554,16 @@ def cremona_label(self, space=False): RuntimeError: Cremona label not known for Elliptic Curve defined by y^2 + y = x^3 - 79*x + 342 over Rational Field. """ try: - if not space: - return self.__cremona_label.replace(' ','') - return self.__cremona_label + label = self.__cremona_label except AttributeError: try: - X = self.database_curve() + label = self.database_attributes()['cremona_label'] except RuntimeError: raise RuntimeError("Cremona label not known for %s."%self) - self.__cremona_label = X.__cremona_label - return self.cremona_label(space) + self.__cremona_label = label + if not space: + return label.replace(' ', '') + return label label = cremona_label @@ -4746,7 +4757,7 @@ def is_reducible(self, p): EXAMPLES:: sage: EllipticCurve('20a1').is_reducible(3) #random - doctest:1: DeprecationWarning: is_reducible is deprecated, use galois_representation().is_reducible(p) instead! + doctest:...: DeprecationWarning: is_reducible is deprecated, use galois_representation().is_reducible(p) instead! True """ @@ -4766,7 +4777,7 @@ def is_irreducible(self, p): EXAMPLES:: sage: EllipticCurve('20a1').is_irreducible(7) #random - doctest:1: DeprecationWarning: is_irreducible is deprecated, use galois_representation().is_irreducible(p) instead! + doctest:...: DeprecationWarning: is_irreducible is deprecated, use galois_representation().is_irreducible(p) instead! True """ @@ -4786,7 +4797,7 @@ def is_surjective(self, p, A=1000): EXAMPLES:: sage: EllipticCurve('20a1').is_surjective(7) #random - doctest:1: DeprecationWarning: is_surjective is deprecated, use galois_representation().is_surjective(p) instead! + doctest:...: DeprecationWarning: is_surjective is deprecated, use galois_representation().is_surjective(p) instead! True """ @@ -4806,7 +4817,7 @@ def reducible_primes(self): EXAMPLES:: sage: EllipticCurve('20a1').reducible_primes() #random - doctest:1: DeprecationWarning: reducible_primes is deprecated, use galois_representation().reducible_primes() instead! + doctest:...: DeprecationWarning: reducible_primes is deprecated, use galois_representation().reducible_primes() instead! [2,3] """ @@ -4826,7 +4837,7 @@ def non_surjective(self, A=1000): EXAMPLES:: sage: EllipticCurve('20a1').non_surjective() #random - doctest:1: DeprecationWarning: non_surjective is deprecated, use galois_representation().non_surjective() instead! + doctest:...: DeprecationWarning: non_surjective is deprecated, use galois_representation().non_surjective() instead! [2,3] """ diff --git a/src/sage/schemes/elliptic_curves/heegner.py b/src/sage/schemes/elliptic_curves/heegner.py index ad96a2e836a..214d9822e41 100644 --- a/src/sage/schemes/elliptic_curves/heegner.py +++ b/src/sage/schemes/elliptic_curves/heegner.py @@ -2624,7 +2624,7 @@ def __hash__(self): EXAMPLES:: sage: y = EllipticCurve('389a').heegner_point(-7,5) - sage: hash(y) + sage: hash(y) # random output -756867903203770682 # 64-bit -274399546 # 32-bit """ @@ -2929,7 +2929,7 @@ def __hash__(self): """ EXAMPLES:: - sage: hash(EllipticCurve('389a').heegner_point(-7,5)) + sage: hash(EllipticCurve('389a').heegner_point(-7,5)) # random output -756867903203770682 # 64-bit -274399546 # 32-bit """ @@ -3488,7 +3488,7 @@ def _numerical_approx_conjugates_over_QQ(self, prec=53): [(-1.89564392373896 - 0.444771808762067*I : -1.50000000000000 + 2.13102976222246*I : 1.00000000000000), ...] sage: y._numerical_approx_conjugates_over_QQ(prec=10) [(-1.9 - 0.44*I : -1.5 + 2.1*I : 1.0), ... - (-1.9 - 0.44*I : -1.5 + 2.1*I : 1.0)] + (1.4 + 0.0024*I : -1.7 - 0.0046*I : 1.0)] """ v = [] for z in self.conjugates_over_K(): @@ -3518,8 +3518,8 @@ def _numerical_approx_xy_poly(self, prec=53): sage: E = EllipticCurve('37a') sage: y = E.heegner_point(-7,3); y Heegner point of discriminant -7 and conductor 3 on elliptic curve of conductor 37 - sage: y._numerical_approx_xy_poly() - (X^8 + 6.00000000000000*X^7 + 8.99999999999998*X^6 - 12.0000000000000*X^5 - 42.0000000000000*X^4 - 17.999999999999...*X^3 + 36.0000000000001*X^2 + 35.9999999999999*X + 8.999999999999..., X^8 + 12.0000000000000*X^7 + 72.0000000000000*X^6 + 270.000000000000*X^5 + 678.000000000001*X^4 + 1152.00000000000*X^3 + 1269.00000000000*X^2 + 810.00000000000...*X + 225.000000000001) + sage: y._numerical_approx_xy_poly() # rel tol 1e-14 + (X^8 + 6.00000000000000*X^7 + 8.99999999999998*X^6 - 12.0000000000000*X^5 - 42.0000000000000*X^4 - 17.9999999999999*X^3 + 36.0000000000001*X^2 + 35.9999999999999*X + 8.99999999999995, X^8 + 12.0000000000000*X^7 + 72.0000000000000*X^6 + 270.000000000000*X^5 + 678.000000000001*X^4 + 1152.00000000000*X^3 + 1269.00000000000*X^2 + 810.000000000002*X + 225.000000000001) """ v = self._numerical_approx_conjugates_over_QQ(prec) R = ComplexField(prec)['X'] diff --git a/src/sage/schemes/elliptic_curves/padic_lseries.py b/src/sage/schemes/elliptic_curves/padic_lseries.py index 1e147b254aa..4011736c51f 100644 --- a/src/sage/schemes/elliptic_curves/padic_lseries.py +++ b/src/sage/schemes/elliptic_curves/padic_lseries.py @@ -663,6 +663,7 @@ def _get_series_from_cache(self, n, prec, D, eta): sage: E = EllipticCurve('11a1') sage: Lp = E.padic_lseries(5) + sage: Lp._pAdicLseries__series = {} # clear cached series sage: Lp._get_series_from_cache(3,5,1,0) sage: Lp.series(3,prec=5) 5 + 4*5^2 + 4*5^3 + O(5^4) + O(5)*T + O(5)*T^2 + O(5)*T^3 + O(5)*T^4 + O(T^5) diff --git a/src/sage/schemes/elliptic_curves/period_lattice.py b/src/sage/schemes/elliptic_curves/period_lattice.py index 7025cc01b22..a62cc9259fc 100644 --- a/src/sage/schemes/elliptic_curves/period_lattice.py +++ b/src/sage/schemes/elliptic_curves/period_lattice.py @@ -1044,7 +1044,7 @@ def ei(self): sage: E = EllipticCurve([0,1,0,a,a]) sage: L = E.period_lattice(K.embeddings(RealField())[0]) sage: L.ei() - [0.?e-19 - 1.122462048309373?*I, 0.?e-19 + 1.122462048309373?*I, -1] + [0.?e-17 - 1.122462048309373?*I, 0.?e-17 + 1.122462048309373?*I, -1] sage: L = E.period_lattice(K.embeddings(ComplexField())[0]) sage: L.ei() diff --git a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py index 6e46bdc9c29..98a295d1797 100644 --- a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py +++ b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py @@ -9,6 +9,8 @@ - Alyson Deines, Marina Gresham, Gagan Sekhon, (2010) +- Daniel Krenn (2011) + - Jean-Pierre Flori, Jan Tuitman (2013) EXAMPLES:: @@ -25,6 +27,7 @@ # Copyright (C) 2007 Robert Bradshaw # Copyright (C) 2010 Alyson Deines , Marina Gresham # , Gagan Sekhon +# Copyright (C) 2011 Daniel Krenn # Copyright (C) 2013 Jean-Pierre Flori , # Jan Tuitman # @@ -282,15 +285,13 @@ def frobenius_polynomial_cardinalities(self, a=None): sage: H.frobenius_polynomial_cardinalities() x^4 - x^3 - 52*x^2 - 37*x + 1369 - Over a non-prime field, this currently does not work for ``g \geq 2``:: + Curve over a non-prime field:: - sage: K. = GF(17**3) + sage: K. = GF(7**2) sage: R. = PolynomialRing(K) sage: H = HyperellipticCurve(t^5 + z*t + z^2) sage: H.frobenius_polynomial_cardinalities() - Traceback (most recent call last): - ... - NotImplementedError: Naive method only implemented over base field or extensions of prime fields in odd characteristic. + x^4 + 8*x^3 + 70*x^2 + 392*x + 2401 This method may actually be useful when `hypellfrob` does not work:: @@ -355,7 +356,7 @@ def frobenius_polynomial_matrix(self, M=None, algorithm='hypellfrob'): sage: H.frobenius_polynomial_matrix() x^8 + 281*x^7 + 55939*x^6 + 14144175*x^5 + 3156455369*x^4 + 707194605825*x^3 + 139841906155939*x^2 + 35122892542149719*x + 6249500014999800001 sage: H = HyperellipticCurve(t^15 + t^5 + 1) - sage: H.frobenius_polynomial_matrix() # long time + sage: H.frobenius_polynomial_matrix() # long time, 8s on a Corei7 x^14 - 76*x^13 + 220846*x^12 - 12984372*x^11 + 24374326657*x^10 - 1203243210304*x^9 + 1770558798515792*x^8 - 74401511415210496*x^7 + 88526169366991084208*x^6 - 3007987702642212810304*x^5 + 3046608028331197124223343*x^4 - 81145833008762983138584372*x^3 + 69007473838551978905211279154*x^2 - 1187357507124810002849977200076*x + 781140631562281254374947500349999 This `hypellfrob` program doesn't support non-prime fields:: @@ -414,27 +415,21 @@ def frobenius_polynomial(self): sage: H.frobenius_polynomial() x^6 - 14*x^5 + 1512*x^4 - 66290*x^3 + 3028536*x^2 - 56168126*x + 8036054027 - Curves defined over a non-prime field are only supported for ``g < 2`` - in odd characteristic and a naive algorithm is used - (and we should actually use fast point counting on elliptic curves):: + Curves defined over a non-prime field are supported as well, + but a naive algorithm is used; especially when ``g = 1``, + fast point counting on elliptic curves should be used:: sage: K. = GF(23**3) sage: R. = PolynomialRing(K) sage: H = HyperellipticCurve(t^3 + z*t + 4) - sage: H.frobenius_polynomial() # long time + sage: H.frobenius_polynomial() # long time, 4s on a Corei7 x^2 - 15*x + 12167 - To support higher genera, we need a way of easily chacking for - squareness in quotient rings of polynomial rings over finite fields - of odd characteristic:: - - sage: K. = GF(23**3) + sage: K. = GF(3**3) sage: R. = PolynomialRing(K) sage: H = HyperellipticCurve(t^5 + z*t + z**3) sage: H.frobenius_polynomial() - Traceback (most recent call last): - ... - NotImplementedError: Naive method only implemented over base field or extensions of prime fields in odd characteristic. + x^4 - 3*x^3 + 10*x^2 - 81*x + 729 Over prime fields of odd characteristic, when ``h`` is non-zero, this naive algorithm is currently used as well, whereas we should @@ -443,7 +438,7 @@ def frobenius_polynomial(self): sage: K = GF(101) sage: R. = PolynomialRing(K) sage: H = HyperellipticCurve(t^5 + 27*t + 3, t) - sage: H.frobenius_polynomial() + sage: H.frobenius_polynomial() # long time, 3s on a Corei7 x^4 + 2*x^3 - 58*x^2 + 202*x + 10201 In even characteristic, the naive algorithm could cover all cases @@ -455,9 +450,7 @@ def frobenius_polynomial(self): sage: R. = PolynomialRing(K) sage: H = HyperellipticCurve(t^5 + z*t + z**3, t) sage: H.frobenius_polynomial() - Traceback (most recent call last): - ... - NotImplementedError: object does not support iteration + x^4 - x^3 + 16*x^2 - 32*x + 1024 """ K = self.base_ring() e = K.degree() @@ -549,7 +542,6 @@ def _points_fast_sqrt(self): for x in K: points.append(self.point([x, f(x).sqrt(), one], check=True)) else: - R = f.base_ring() a_sqrts = { } # Artin-Schreier 2-roots for x in K: a_sqrts[x**2 + x] = x # char 2 => x^2 - x == x^2 + x @@ -793,13 +785,13 @@ def count_points_frobenius_polynomial(self, n=1, f=None): The following computation takes a long time as the complete characteristic polynomial of the frobenius is computed:: - sage: H.count_points_frobenius_polynomial(3) # long time, 20s on a Corei7 + sage: H.count_points_frobenius_polynomial(3) # long time, 20s on a Corei7 (when computed before the following test of course) [49491, 2500024375, 124992509154249] As the polynomial is cached, further computations of number of points are really fast:: - sage: H.count_points_frobenius_polynomial(19) + sage: H.count_points_frobenius_polynomial(19) # long time, because of the previous test [49491, 2500024375, 124992509154249, @@ -823,13 +815,12 @@ def count_points_frobenius_polynomial(self, n=1, f=None): if f is None: f = self.frobenius_polynomial() - g = self.genus() q = self.base_ring().cardinality() S = PowerSeriesRing(QQ, default_prec=n+1, names='t') frev = f.reverse() - # the coefficients() method of power series only returns non-zero coefficients - # so let's use the list() method - # but this does not work for zero which gives the empty list + # the coefficients() method of power series only returns + # non-zero coefficients so let us use the list() method but + # this does not work for zero which gives the empty list flog = S(frev).log() return [q**(i+1) + 1 + ZZ((i+1)*flog[i+1]) for i in xrange(n)] @@ -873,7 +864,7 @@ def count_points_exhaustive(self, n=1, naive=False): This behavior can be disabled by passing `naive=True`:: - sage: H.count_points_exhaustive(n=6, naive=True) + sage: H.count_points_exhaustive(n=6, naive=True) # long time, 7s on a Corei7 [9, 27, 108, 675, 3069, 16302] """ g = self.genus() @@ -1019,6 +1010,25 @@ def count_points(self, n=1): sage: H = HyperellipticCurve(t^13 + 3*t^5 + 5) sage: H.count_points(n=6) [112, 16360, 2045356, 260199160, 33038302802, 4195868633548] + + sage: P. = PolynomialRing(GF(3)) + sage: H = HyperellipticCurve(x^3+x^2+1) + sage: C1 = H.count_points(4); C1 + [6, 12, 18, 96] + sage: C2 = sage.schemes.generic.scheme.Scheme.count_points(H,4); C2 # long time, 2s on a Corei7 + [6, 12, 18, 96] + sage: C1 == C2 # long time, because we need C2 to be defined + True + + sage: P. = PolynomialRing(GF(9,'a')) + sage: H = HyperellipticCurve(x^5+x^2+1) + sage: H.count_points(5) + [18, 78, 738, 6366, 60018] + + sage: F. = GF(4); P. = F[] + sage: H = HyperellipticCurve(x^5+a*x^2+1, x+a+1) + sage: H.count_points(6) + [2, 24, 74, 256, 1082, 4272] """ K = self.base_ring() q = K.cardinality() @@ -1056,6 +1066,16 @@ def cardinality_exhaustive(self, extension_degree=1, algorithm=None): sage: H = HyperellipticCurve(t^7 + 3*t^5 + 5) sage: H.cardinality_exhaustive() 1025 + + sage: P. = PolynomialRing(GF(9,'a')) + sage: H = HyperellipticCurve(x^5+x^2+1) + sage: H.count_points(5) + [18, 78, 738, 6366, 60018] + + sage: F. = GF(4); P. = F[] + sage: H = HyperellipticCurve(x^5+a*x^2+1, x+a+1) + sage: H.count_points(6) + [2, 24, 74, 256, 1082, 4272] """ K = self.base_ring() g = self.genus() @@ -1094,68 +1114,39 @@ def cardinality_exhaustive(self, extension_degree=1, algorithm=None): a += 2 # affine points + if n == 1: + # the base field + L = K + fext = f + hext = h + else: + # extension of the prime field + from sage.categories.homset import Hom + L = GF(K.cardinality()**n, names='z') + P = L['t'] + emb = Hom(K, L)[0] + fext = P([emb(c) for c in f]) + + hext = P([emb(c) for c in h]) + if K.characteristic() == 2: - if n == 1: - # the base field - L = K - elif K.degree() == 1: - # extension of the prime field - L = GF(2**n, name='t') - else: - # general extension - # this might be costly, but everything here is anyway - # WARNING: - # 1. beware that if K is actually defined as a quotient of - # a polynomial ring, then polynomial_ring() will return - # will return the polynomial ring it is a quotient of, - # and not a polynomial ring on top of the quotient ring... - # IMHO this is a bug - # 2. if we rather used the PolynomialRing constructor, we would - # get the correct construction, but we then need to make sure - # we call trace enough below, e.g. with a while loop where we - # repeatedly apply trace() till the result lives in the prime - # field - # 3. for our application we don't really care as hyperelliptic - # curves defined over finite field represented as quotient rings - # won't end up in the HyperellipticCurve_finite_field class - # 4. and this does not work as a quotient ring of a polynomial - # ring over a finite field is not iterable, so the following - # for loop will fail at the moment - L = K.extension(K.polynomial_ring().irreducible_element(n)) for x in L: - s = f(x) - r = h(x) + s = fext(x) + r = hext(x) if r == 0: a += 1 - elif (s/r**2).trace().trace() == 0: - # currently extension of finite fields may be represented - # as quotient rings of polynomial rings, - # so for these we need: - # - one trace to go to the base field, - # - another trace to go to F_2. - # if L is actually represented directly as a finite field, - # the second trace does not hurt as tr(F_p) = id + elif (s/r**2).trace() == 0: a += 2 else: - if n == 1: - # L is the base field - L = K - elif K.degree() == 1: - # L is an extension of the prime field - L = GF(K.cardinality()**n, name='t') - else: - # there is no easy way to construct extensions of finite fields - # of odd characteristic in such a way that we can check for - # squares easily - raise NotImplementedError("Naive method only implemented over base field or extensions of prime fields in odd characteristic.") for x in L: - s = f(x) - r = h(x) + s = fext(x) + r = hext(x) d = r**2 + 4*s if d.is_zero(): a += 1 elif d.is_square(): a += 2 + return a def cardinality_hypellfrob(self, extension_degree=1, algorithm=None): @@ -1230,6 +1221,44 @@ def cardinality(self, extension_degree=1): # No smart method available return self.cardinality_exhaustive(n) + def zeta_function(self): + r""" + Gives the zeta function of the hyperelliptic curve. + + EXAMPLES:: + + sage: F = GF(2); R. = F[] + sage: H = HyperellipticCurve(t^9 + t, t^4) + sage: H.zeta_function() + (16*x^8 + 8*x^7 + 8*x^6 + 4*x^5 + 6*x^4 + 2*x^3 + 2*x^2 + x + 1)/(2*x^2 - 3*x + 1) + + sage: F. = GF(4); R. = F[] + sage: H = HyperellipticCurve(t^5 + t^3 + t^2 + t + 1, t^2 + t + 1) + sage: H.zeta_function() + (16*x^4 + 8*x^3 + x^2 + 2*x + 1)/(4*x^2 - 5*x + 1) + + sage: F. = GF(9); R. = F[] + sage: H = HyperellipticCurve(t^5 + a*t) + sage: H.zeta_function() + (81*x^4 + 72*x^3 + 32*x^2 + 8*x + 1)/(9*x^2 - 10*x + 1) + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurve(t^5 + t + 2) + sage: H.zeta_function() + (1369*x^4 + 37*x^3 - 52*x^2 + x + 1)/(37*x^2 - 38*x + 1) + + A quadratic twist:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurve(2*t^5 + 2*t + 4) + sage: H.zeta_function() + (1369*x^4 - 37*x^3 - 52*x^2 - x + 1)/(37*x^2 - 38*x + 1) + """ + q = self.base_ring().cardinality() + P = self.frobenius_polynomial() + x = P.parent().gen(0) + return P.reverse() / ((1-x)*(1-q*x)) + #This where Cartier Matrix is actually computed. This is either called by E.Cartier_matrix, E.a_number, or E.Hasse_Witt @cached_method def _Cartier_matrix_cached(self): @@ -1522,29 +1551,29 @@ def _Hasse_Witt_cached(self): #Since Trac Ticket #11115, there is a different cache for methods #that don't accept arguments. Anyway, the easiest is to call #the cached method and simply see whether the data belong to self. - M, Coeffs,g, Fq, p, E= self._Cartier_matrix_cached() - if E!=self: + M, Coeffs, g, Fq, p, E = self._Cartier_matrix_cached() + if E != self: self._Cartier_matrix_cached.clear_cache() - M, Coeffs,g, Fq, p, E= self._Cartier_matrix_cached() + M, Coeffs, g, Fq, p, E = self._Cartier_matrix_cached() #This compute the action of p^kth Frobenius on list of coefficients def frob_mat(Coeffs, k): - a = p**k + a = p ** k mat = [] - Coeffs_pow = [c**a for c in Coeffs] - for i in range(1,g+1): - H=[(Coeffs[j]) for j in range((p*i-1), (p*i - g-1), -1)] - mat.append(H); - return matrix(Fq,mat) + Coeffs_pow = [c ** a for c in Coeffs] # not used ?? + for i in range(1, g + 1): + H = [(Coeffs[j]) for j in range((p*i-1), (p*i - g-1), -1)] + mat.append(H) + return matrix(Fq, mat) #Computes all the different possible action of frobenius on matrix M and stores in list Mall - Mall = [M] + [frob_mat(Coeffs,k) for k in range(1,g)] + Mall = [M] + [frob_mat(Coeffs, k) for k in range(1, g)] #initial N=I, so we can go through Mall and multiply all matrices with I and #get the Hasse-Witt matrix. - N = identity_matrix(Fq,g) + N = identity_matrix(Fq, g) for l in Mall: - N = N*l; + N = N * l return N, E #This is the function which is actually called by command line diff --git a/src/sage/schemes/hyperelliptic_curves/monsky_washnitzer.py b/src/sage/schemes/hyperelliptic_curves/monsky_washnitzer.py index e2d6b961b93..4c815289d95 100644 --- a/src/sage/schemes/hyperelliptic_curves/monsky_washnitzer.py +++ b/src/sage/schemes/hyperelliptic_curves/monsky_washnitzer.py @@ -34,12 +34,16 @@ algorithms - Robert Bradshaw (2007-04): generalization to hyperelliptic curves + +- Julian Rueth (2014-05-09): improved caching + """ #***************************************************************************** # Copyright (C) 2006 William Stein # 2006 Robert Bradshaw # 2006 David Harvey +# 2014 Julian Rueth # # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ @@ -56,6 +60,8 @@ from sage.modules.all import vector from sage.rings.ring import CommutativeAlgebra from sage.structure.element import CommutativeAlgebraElement +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.cachefunc import cached_method from sage.rings.infinity import Infinity from sage.rings.arith import binomial, integer_ceil as ceil @@ -1829,44 +1835,30 @@ def matrix_of_frobenius_hyperelliptic(Q, p=None, prec=None, M=None): return M.transpose(), [f for f, a in reduced] -# For uniqueness (as many of the non-trivial calculations are cached along the way). - -_special_ring_cache = {} -_mw_cache = {} - - -def SpecialHyperellipticQuotientRing(*args): - if args in _special_ring_cache: - R = _special_ring_cache[args]() - if R is not None: - return R - R = SpecialHyperellipticQuotientRing_class(*args) - _special_ring_cache[args] = weakref.ref(R) - return R - - -def MonskyWashnitzerDifferentialRing(base_ring): - if base_ring in _mw_cache: - R = _mw_cache[base_ring]() - if R is not None: - return R +class SpecialHyperellipticQuotientRing(UniqueRepresentation, CommutativeAlgebra): + _p = None - R = MonskyWashnitzerDifferentialRing_class(base_ring) - _mw_cache[base_ring] = weakref.ref(R) - return R + def __init__(self, Q, R=None, invert_y=True): + r""" + Initialization. + TESTS: -class SpecialHyperellipticQuotientRing_class(CommutativeAlgebra): + Check that caching works:: - _p = None + sage: R. = QQ['x'] + sage: E = HyperellipticCurve(x^5-3*x+1) + sage: from sage.schemes.hyperelliptic_curves.monsky_washnitzer import SpecialHyperellipticQuotientRing + sage: SpecialHyperellipticQuotientRing(E) is SpecialHyperellipticQuotientRing(E) + True - def __init__(self, Q, R=None, invert_y=True): + """ if R is None: R = Q.base_ring() # Trac ticket #9138: CommutativeAlgebra.__init__ must not be # done so early. It tries to register a coercion, but that - # requires the hash bein available. But the hash, in its + # requires the hash being available. But the hash, in its # default implementation, relies on the string representation, # which is not available at this point. #CommutativeAlgebra.__init__(self, R) # moved to below. @@ -2170,7 +2162,7 @@ def is_field(self, proof=True): False """ return False - +SpecialHyperellipticQuotientRing_class = SpecialHyperellipticQuotientRing class SpecialHyperellipticQuotientElement(CommutativeAlgebraElement): @@ -2600,16 +2592,27 @@ def coeffs(self, R=None): coeffs = transpose_list(coeffs) return [V(a) for a in coeffs], y_offset - -class MonskyWashnitzerDifferentialRing_class(Module): - +class MonskyWashnitzerDifferentialRing(UniqueRepresentation, Module): + r""" + A ring of Monsky--Washnitzer differentials over ``base_ring``. + """ def __init__(self, base_ring): r""" - Class for the ring of Monsky--Washnitzer differentials over a given - base ring. + Initialization. + + TESTS: + + Check that caching works:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurve(x^5-3*x+1) + sage: from sage.schemes.hyperelliptic_curves.monsky_washnitzer import SpecialHyperellipticQuotientRing, MonskyWashnitzerDifferentialRing + sage: S = SpecialHyperellipticQuotientRing(E) + sage: MonskyWashnitzerDifferentialRing(S) is MonskyWashnitzerDifferentialRing(S) + True + """ Module.__init__(self, base_ring) - self._cache = {} def invariant_differential(self): """ @@ -2725,6 +2728,7 @@ def Q(self): """ return self.base_ring().Q() + @cached_method def x_to_p(self, p): """ Returns and caches `x^p`, reduced via the relations coming from the @@ -2742,13 +2746,9 @@ def x_to_p(self, p): sage: MW.x_to_p(101) is MW.x_to_p(101) True """ - try: - return self._cache["x_to_p", p] - except KeyError: - x_to_p = self.base_ring().x() ** p - self._cache["x_to_p", p] = x_to_p - return x_to_p + return self.base_ring().x() ** p + @cached_method def frob_Q(self, p): """ Returns and caches `Q(x^p)`, which is used in computing the image of @@ -2766,13 +2766,7 @@ def frob_Q(self, p): sage: MW.frob_Q(11) is MW.frob_Q(11) True """ - try: - return self._cache["frobQ", p] - except KeyError: - x_to_p = self.x_to_p(p) - frobQ = self.base_ring()._Q.change_ring(self.base_ring())(x_to_p) - self._cache["frobQ", p] = frobQ - return frobQ + return self.base_ring()._Q.change_ring(self.base_ring())(self.x_to_p(p)) def frob_invariant_differential(self, prec, p): r""" @@ -2814,7 +2808,7 @@ def frob_invariant_differential(self, prec, p): x_to_p = x*x_to_p_less_1 # cache for future use - self._cache["x_to_p", p] = x_to_p + self.x_to_p.set_cache(p, x_to_p) prof("frob_Q") a = self.frob_Q(p) >> 2*p # frobQ * y^{-2p} @@ -2922,7 +2916,7 @@ def helper_matrix(self): else: self._helper_matrix = ~A return self._helper_matrix - +MonskyWashnitzerDifferentialRing_class = MonskyWashnitzerDifferentialRing class MonskyWashnitzerDifferential(ModuleElement): @@ -2934,7 +2928,7 @@ def __init__(self, parent, val=0, offset=0): INPUT: - ``parent`` -- Monsky-Washnitzer differential ring (instance of class - :class:`~MonskyWashnitzerDifferentialRing_class` + :class:`~MonskyWashnitzerDifferentialRing` - ``val`` -- element of the base ring, or list of coefficients diff --git a/src/sage/sets/family.py b/src/sage/sets/family.py index 296d07faf35..d289cc69e86 100644 --- a/src/sage/sets/family.py +++ b/src/sage/sets/family.py @@ -547,6 +547,34 @@ def __init__(self, dictionary, keys = None): self.keys = dictionary.keys self.values = dictionary.values + @cached_method + def __hash__(self): + """ + Return a hash value for ``self``. + + EXAMPLES:: + + sage: f = Family(["c", "a", "b"], lambda x: x+x) + sage: hash(f) == hash(f) + True + sage: f2 = Family(["a", "c", "b"], lambda x: x+x) + sage: hash(f) == hash(f2) + True + sage: g = Family(["b", "c", "a"], lambda x: x+x+x) + sage: hash(f) == hash(g) + False + + :: + + sage: f = Family({1:[1,2]}) + sage: hash(f) == hash(f) + True + """ + try: + return hash(frozenset(self._dictionary.items())) + except (TypeError, ValueError): + return hash(frozenset(list(self.keys()) + map(repr, self.values()))) + def keys(self): """ Returns the index set of this family @@ -857,6 +885,39 @@ def __init__(self, set, function, name=None): self.function = function self.function_name = name + @cached_method + def __hash__(self): + """ + Return a hash value for ``self``. + + EXAMPLES:: + + sage: from sage.sets.family import LazyFamily + sage: f = LazyFamily([3,4,7], lambda i: 2*i) + sage: hash(f) == hash(f) + True + sage: g = LazyFamily(ZZ, lambda i: 2*i) + sage: hash(g) == hash(g) + True + sage: h = LazyFamily(ZZ, lambda i: 2*i, name='foo') + sage: hash(h) == hash(h) + True + + :: + + sage: class X(object): + ....: def __call__(self, x): + ....: return x + ....: __hash__ = None + sage: f = Family([1,2,3], X()) + sage: hash(f) == hash(f) + True + """ + try: + return hash(self.keys()) + hash(self.function) + except (TypeError, ValueError): + return super(LazyFamily, self).__hash__() + def __eq__(self, other): """ WARNING: Since there is no way to compare function, we only compare @@ -1073,6 +1134,19 @@ def __eq__(self, other): return (isinstance(other, self.__class__) and self._enumeration == other._enumeration) + def __hash__(self): + """ + Return a hash value for ``self``. + + EXAMPLES:: + + sage: from sage.sets.family import TrivialFamily + sage: f = TrivialFamily((3,4,7)) + sage: hash(f) == hash(f) + True + """ + return hash(self._enumeration) + def _repr_(self): """ EXAMPLES:: diff --git a/src/sage/sets/integer_range.py b/src/sage/sets/integer_range.py index f3288f4a4ac..86270bcfa34 100644 --- a/src/sage/sets/integer_range.py +++ b/src/sage/sets/integer_range.py @@ -64,7 +64,7 @@ class IntegerRange(UniqueRepresentation, Parent): sage: list(IntegerRange(2,5)) [2, 3, 4] sage: I = IntegerRange(2,100,5); I - {2, 7, .., 97} + {2, 7, ..., 97} sage: list(I) [2, 7, 12, 17, 22, 27, 32, 37, 42, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97] sage: I.category() @@ -90,7 +90,7 @@ class IntegerRange(UniqueRepresentation, Parent): arithmetic progression starting from the ``begin`` by step ``step``:: sage: I = IntegerRange(54,Infinity,3); I - {54, 57, ..} + {54, 57, ...} sage: I.category() Category of facade infinite enumerated sets sage: p = iter(I) @@ -98,7 +98,7 @@ class IntegerRange(UniqueRepresentation, Parent): (54, 57, 60, 63, 66, 69) sage: I = IntegerRange(54,-Infinity,-3); I - {54, 51, ..} + {54, 51, ...} sage: I.category() Category of facade infinite enumerated sets sage: p = iter(I) @@ -128,7 +128,7 @@ class IntegerRange(UniqueRepresentation, Parent): but the enumeration will begin with this ``middle_point``:: sage: I = IntegerRange(123,-12,-14); I - {123, 109, .., -3} + {123, 109, ..., -3} sage: list(I) [123, 109, 95, 81, 67, 53, 39, 25, 11, -3] sage: J = IntegerRange(123,-12,-14,25); J @@ -200,7 +200,7 @@ class IntegerRange(UniqueRepresentation, Parent): ... L2.sort() ... assert L1 == L2 - Thanks to #8543 empty integer range are allowed:: + Thanks to :trac:`8543` empty integer range are allowed:: sage: TestSuite(IntegerRange(0, 5, -1)).run() """ @@ -393,9 +393,9 @@ def _repr_(self): if self.cardinality() < 6: return "{" + ", ".join(str(x) for x in self) + "}" elif self._step == 1: - return "{%s, .., %s}"%(self._begin, self._end-self._step) + return "{%s, ..., %s}"%(self._begin, self._end-self._step) else: - return "{%s, %s, .., %s}"%(self._begin, self._begin+self._step, + return "{%s, %s, ..., %s}"%(self._begin, self._begin+self._step, self._end-self._step) def rank(self,x): @@ -537,16 +537,16 @@ def _repr_(self): TESTS:: sage: IntegerRange(123,12,-4) #indirect doctest - {123, 119, .., 15} + {123, 119, ..., 15} sage: IntegerRange(-57,1,3) #indirect doctest - {-57, -54, .., 0} + {-57, -54, ..., 0} sage: IntegerRange(-57,Infinity,8) #indirect doctest - {-57, -49, ..} + {-57, -49, ...} sage: IntegerRange(-112,-Infinity,-13) #indirect doctest - {-112, -125, ..} + {-112, -125, ...} """ - return "{%s, %s, ..}"%(self._begin, self._begin+self._step) + return "{%s, %s, ...}"%(self._begin, self._begin+self._step) def __contains__(self, elt): r""" diff --git a/src/sage/structure/element.pxd b/src/sage/structure/element.pxd index 9cac7a7bc8c..72804c08cb3 100644 --- a/src/sage/structure/element.pxd +++ b/src/sage/structure/element.pxd @@ -113,7 +113,7 @@ cdef class Vector(ModuleElement): cdef bint is_dense_c(self) -cdef class Matrix(AlgebraElement): +cdef class Matrix(ModuleElement): # All matrix classes must be written in Cython cdef Py_ssize_t _nrows cdef Py_ssize_t _ncols diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 79702444fef..a0f662be39f 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -2524,7 +2524,7 @@ cdef class Vector(ModuleElement): def is_Vector(x): return IS_INSTANCE(x, Vector) -cdef class Matrix(AlgebraElement): +cdef class Matrix(ModuleElement): cdef bint is_sparse_c(self): raise NotImplementedError @@ -2539,36 +2539,6 @@ cdef class Matrix(AlgebraElement): global coercion_model return coercion_model.bin_op(left, right, imul) - cpdef RingElement _mul_(left, RingElement right): - """ - TESTS:: - - sage: m = matrix - sage: a = m([[m([[1,2],[3,4]]),m([[5,6],[7,8]])],[m([[9,10],[11,12]]),m([[13,14],[15,16]])]]) - sage: 3*a - [[ 3 6] - [ 9 12] [15 18] - [21 24]] - [[27 30] - [33 36] [39 42] - [45 48]] - - sage: m = matrix - sage: a = m([[m([[1,2],[3,4]]),m([[5,6],[7,8]])],[m([[9,10],[11,12]]),m([[13,14],[15,16]])]]) - sage: a*3 - [[ 3 6] - [ 9 12] [15 18] - [21 24]] - [[27 30] - [33 36] [39 42] - [45 48]] - """ - if have_same_parent(left, right): - return (left)._matrix_times_matrix_(right) - else: - global coercion_model - return coercion_model.bin_op(left, right, mul) - def __mul__(left, right): """ Multiplication of matrix by matrix, vector, or scalar @@ -2730,6 +2700,27 @@ cdef class Matrix(AlgebraElement): ... TypeError: unsupported operand parent(s) for '*': 'Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field' and 'Univariate Polynomial Ring in y over Rational Field' + Examples with matrices having matrix coefficients:: + + sage: m = matrix + sage: a = m([[m([[1,2],[3,4]]),m([[5,6],[7,8]])],[m([[9,10],[11,12]]),m([[13,14],[15,16]])]]) + sage: 3*a + [[ 3 6] + [ 9 12] [15 18] + [21 24]] + [[27 30] + [33 36] [39 42] + [45 48]] + + sage: m = matrix + sage: a = m([[m([[1,2],[3,4]]),m([[5,6],[7,8]])],[m([[9,10],[11,12]]),m([[13,14],[15,16]])]]) + sage: a*3 + [[ 3 6] + [ 9 12] [15 18] + [21 24]] + [[27 30] + [33 36] [39 42] + [45 48]] """ if have_same_parent(left, right): return (left)._matrix_times_matrix_(right) @@ -2737,6 +2728,23 @@ cdef class Matrix(AlgebraElement): global coercion_model return coercion_model.bin_op(left, right, mul) + def __div__(left, right): + """ + Division of the matrix ``left`` by the matrix or scalar ``right``. + + EXAMPLES:: + + sage: a = matrix(ZZ, 2, range(4)) + sage: a / 5 + [ 0 1/5] + [2/5 3/5] + """ + if have_same_parent(left, right): + return (left)._matrix_times_matrix_(~right) + else: + global coercion_model + return coercion_model.bin_op(left, right, div) + cdef Vector _vector_times_matrix_(matrix_right, Vector vector_left): raise TypeError diff --git a/src/sage/structure/factory.pyx b/src/sage/structure/factory.pyx index 69c90cca88e..12c2e04243e 100644 --- a/src/sage/structure/factory.pyx +++ b/src/sage/structure/factory.pyx @@ -37,13 +37,15 @@ easier to use than a factory. AUTHORS: -- Robert Bradshaw (2008), initial version. -- Simon King (2013), extended documentation. +- Robert Bradshaw (2008): initial version. +- Simon King (2013): extended documentation. +- Julian Rueth (2014-05-09): use ``_cache_key`` if parameters are unhashable """ #***************************************************************************** # Copyright (C) 2008 Robert Bradshaw +# 2014 Julian Rueth # # Distributed under the terms of the GNU General Public License (GPL) # @@ -383,23 +385,37 @@ cdef class UniqueFactory(SageObject): sage: test_factory.get_object(3.0, 'a', {}) is test_factory.get_object(3.0, 'b', {}) Making object b False + + TESTS: + + Check that :trac:`16317` has been fixed, i.e., caching works for + unhashable objects:: + + sage: K. = Qq(4) + sage: test_factory.get_object(3.0, (K(1), 'c'), {}) is test_factory.get_object(3.0, (K(1), 'c'), {}) + Making object (1 + O(2^20), 'c') + True + """ + cache_key = key try: - return self._cache[version, key] + try: + return self._cache[version, cache_key] + except TypeError: # key is unhashable + from sage.misc.cachefunc import _cache_key + cache_key = _cache_key(cache_key) + return self._cache[version, cache_key] except KeyError: pass obj = self.create_object(version, key, **extra_args) - self._cache[version, key] = obj + self._cache[version, cache_key] = obj try: - other_keys = self.other_keys(key, obj) - for key in other_keys: + for key in self.other_keys(key, obj): try: - obj = self._cache[version, key] - break - except KeyError: - pass - for key in other_keys: - self._cache[version, key] = obj + self._cache[version, key] = obj + except TypeError: # key is unhashable + from sage.misc.cachefunc import _cache_key + self._cache[version, _cache_key(key)] = obj obj._factory_data = self, version, key, extra_args if obj.__class__.__reduce__.__objclass__ is object: # replace the generic object __reduce__ to use this one diff --git a/src/sage/structure/indexed_generators.py b/src/sage/structure/indexed_generators.py new file mode 100644 index 00000000000..ff5465e0d4d --- /dev/null +++ b/src/sage/structure/indexed_generators.py @@ -0,0 +1,436 @@ +""" +Indexed Generators +""" +#***************************************************************************** +# Copyright (C) 2013 Travis Scrimshaw , +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.rings.all import Integer + +class IndexedGenerators(object): + r""" + Abstract base class for parents whose elements consist of generators + indexed by an arbitrary set. + + Options controlling the printing of elements: + + - ``prefix`` -- string, prefix used for printing elements of this + module (optional, default 'x'). With the default, a monomial + indexed by 'a' would be printed as ``x['a']``. + + - ``latex_prefix`` -- string or ``None``, prefix used in the `\LaTeX` + representation of elements (optional, default ``None``). If this is + anything except the empty string, it prints the index as a + subscript. If this is None, it uses the setting for ``prefix``, + so if ``prefix`` is set to "B", then a monomial indexed by 'a' + would be printed as ``B_{a}``. If this is the empty string, then + don't print monomials as subscripts: the monomial indexed by 'a' + would be printed as ``a``, or as ``[a]`` if ``latex_bracket`` is + True. + + - ``bracket`` -- ``None``, bool, string, or list or tuple of + strings (optional, default ``None``): if ``None``, use the value of the + attribute ``self._repr_option_bracket``, which has default value + ``True``. (``self._repr_option_bracket`` is available for backwards + compatibility. Users should set ``bracket`` instead. If + ``bracket`` is set to anything except ``None``, it overrides + the value of ``self._repr_option_bracket``.) If ``False``, do not + include brackets when printing elements: a monomial indexed by + 'a' would be printed as ``B'a'``, and a monomial indexed by + (1,2,3) would be printed as ``B(1,2,3)``. If True, use "[" and + "]" as brackets. If it is one of "[", "(", or "{", use it and + its partner as brackets. If it is any other string, use it as + both brackets. If it is a list or tuple of strings, use the + first entry as the left bracket and the second entry as the + right bracket. + + - ``latex_bracket`` -- bool, string, or list or tuple of strings + (optional, default False): if ``False``, do not include brackets in + the LaTeX representation of elements. This option is only + relevant if ``latex_prefix`` is the empty string; otherwise, + brackets are not used regardless. If ``True``, use "\left[" and + "\right]" as brackets. If this is one of "[", "(", "\\{", "|", + or "||", use it and its partner, prepended with "\left" and + "\right", as brackets. If this is any other string, use it as + both brackets. If this is a list or tuple of strings, use the + first entry as the left bracket and the second entry as the + right bracket. + + - ``scalar_mult`` -- string to use for scalar multiplication in + the print representation (optional, default "*") + + - ``latex_scalar_mult`` -- string or ``None`` (default: ``None``), + string to use for scalar multiplication in the latex + representation. If None, use the empty string if ``scalar_mult`` + is set to "*", otherwise use the value of ``scalar_mult``. + + - ``tensor_symbol`` -- string or ``None`` (default: ``None``), + string to use for tensor product in the print representation. If + ``None``, use ``sage.categories.tensor.symbol``. + + - ``generator_cmp`` -- a comparison function (default: ``cmp``), + to use for sorting elements in the output of elements + + .. NOTE:: + + These print options may also be accessed and modified using the + :meth:`print_options` method, after the parent has been defined. + + EXAMPLES: + + We demonstrate a variety of the input options:: + + sage: from sage.structure.indexed_generators import IndexedGenerators + sage: I = IndexedGenerators(ZZ, prefix='A') + sage: I._repr_generator(2) + 'A[2]' + sage: I._latex_generator(2) + 'A_{2}' + + sage: I = IndexedGenerators(ZZ, bracket='(') + sage: I._repr_generator(2) + 'x(2)' + sage: I._latex_generator(2) + 'x_{2}' + + sage: I = IndexedGenerators(ZZ, prefix="", latex_bracket='(') + sage: I._repr_generator(2) + '[2]' + sage: I._latex_generator(2) + \left( 2 \right) + + sage: I = IndexedGenerators(ZZ, bracket=['|', '>']) + sage: I._repr_generator(2) + 'x|2>' + """ + def __init__(self, indices, prefix="x", **kwds): + """ + Initialize ``self``. + + EXAMPLES: + + This is a mixin class, so don't need pickling equality:: + + sage: I = sage.structure.indexed_generators.IndexedGenerators(ZZ) + sage: TestSuite(I).run(skip='_test_pickling') + """ + self._indices = indices + + # printing options for elements (set when initializing self). + # This includes self._repr_option_bracket (kept for backwards + # compatibility, declared to be True by default, needs to be + # overridden explicitly). + self._print_options = {'prefix': prefix, + 'bracket': None, + 'latex_bracket': False, + 'latex_prefix': None, + 'scalar_mult': "*", + 'latex_scalar_mult': None, + 'tensor_symbol': None, + 'generator_cmp': cmp} + # 'bracket': its default value here is None, meaning that + # the value of self._repr_option_bracket is used; the default + # value of that attribute is True -- see immediately before + # the method _repr_generator. If 'bracket' is any value + # except None, then it overrides the value of + # self._repr_option_bracket. Future users might consider + # using 'bracket' instead of _repr_option_bracket. + self.print_options(**kwds) + + def indices(self): + """ + Return the indices of ``self``. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: F.indices() + {'a', 'b', 'c'} + """ + return self._indices + + def prefix(self): + """ + Return the prefix used when displaying elements of self. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: F.prefix() + 'B' + + :: + + sage: X = SchubertPolynomialRing(QQ) + sage: X.prefix() + 'X' + """ + return self._print_options['prefix'] + + def print_options(self, **kwds): + """ + Return the current print options, or set an option. + + INPUT: all of the input is optional; if present, it should be + in the form of keyword pairs, such as + ``latex_bracket='('``. The allowable keywords are: + + - ``prefix`` + - ``latex_prefix`` + - ``bracket`` + - ``latex_bracket`` + - ``scalar_mult`` + - ``latex_scalar_mult`` + - ``tensor_symbol`` + - ``generator_cmp`` + + See the documentation for :class:`CombinatorialFreeModule` for + descriptions of the effects of setting each of these options. + + OUTPUT: if the user provides any input, set the appropriate + option(s) and return nothing. Otherwise, return the + dictionary of settings for print and LaTeX representations. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2,3], prefix='x') + sage: F.print_options() + {...'prefix': 'x'...} + sage: F.print_options(bracket='(') + sage: F.print_options() + {...'bracket': '('...} + + TESTS:: + + sage: sorted(F.print_options().items()) + [('bracket', '('), ('generator_cmp', ), + ('latex_bracket', False), ('latex_prefix', None), + ('latex_scalar_mult', None), ('prefix', 'x'), + ('scalar_mult', '*'), ('tensor_symbol', None)] + sage: F.print_options(bracket='[') # reset + """ + # don't just use kwds.get(...) because I want to distinguish + # between an argument like "option=None" and the option not + # being there altogether. + if kwds: + for option in kwds: + # TODO: make this into a set and put it in a global variable? + if option in ['prefix', 'latex_prefix', 'bracket', 'latex_bracket', + 'scalar_mult', 'latex_scalar_mult', 'tensor_symbol', + 'generator_cmp' + ]: + self._print_options[option] = kwds[option] + else: + raise ValueError('{} is not a valid print option.'.format(option)) + return + return self._print_options + + _repr_option_bracket = True + + def _repr_generator(self, m): + """ + Return a string representing the generator indexed by ``m``. + + The output can be customized by setting any of the following + options when initializing the parent: + + - ``prefix`` + - ``bracket`` + - ``scalar_mult`` + + Alternatively, one can use the :meth:`print_options` method + to achieve the same effect. To modify the bracket setting, + one can also set ``self._repr_option_bracket`` as long as one + has *not* set the ``bracket`` option: if the + ``bracket`` option is anything but ``None``, it overrides + the value of ``self._repr_option_bracket``. + + See the documentation for :class:`CombinatorialFreeModule` for + details on the initialization options. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: e = F.basis() + sage: e['a'] + 2*e['b'] # indirect doctest + B['a'] + 2*B['b'] + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c'], prefix="F") + sage: e = F.basis() + sage: e['a'] + 2*e['b'] # indirect doctest + F['a'] + 2*F['b'] + + sage: QS3 = CombinatorialFreeModule(QQ, Permutations(3), prefix="") + sage: original_print_options = QS3.print_options() + sage: a = 2*QS3([1,2,3])+4*QS3([3,2,1]) + sage: a # indirect doctest + 2*[[1, 2, 3]] + 4*[[3, 2, 1]] + + sage: QS3.print_options(bracket = False) + sage: a # indirect doctest + 2*[1, 2, 3] + 4*[3, 2, 1] + + sage: QS3.print_options(prefix='') + sage: a # indirect doctest + 2*[1, 2, 3] + 4*[3, 2, 1] + + sage: QS3.print_options(bracket="|", scalar_mult=" *@* ") + sage: a # indirect doctest + 2 *@* |[1, 2, 3]| + 4 *@* |[3, 2, 1]| + + sage: QS3.print_options(**original_print_options) # reset + + TESTS:: + + sage: F = CombinatorialFreeModule(QQ, [('a', 'b'), ('c','d')]) + sage: e = F.basis() + sage: e[('a','b')] + 2*e[('c','d')] # indirect doctest + B[('a', 'b')] + 2*B[('c', 'd')] + """ + bracket = self._print_options.get('bracket', None) + bracket_d = {"{": "}", "[": "]", "(": ")"} + if bracket is None: + bracket = self._repr_option_bracket + if bracket is True: + left = "[" + right = "]" + elif bracket is False: + left = "" + right = "" + elif isinstance(bracket, (tuple, list)): + left = bracket[0] + right = bracket[1] + elif bracket in bracket_d: + left = bracket + right = bracket_d[bracket] + else: + left = bracket + right = bracket + return self.prefix() + left + repr(m) + right # mind the (m), to accept a tuple for m + + def _ascii_art_generator(self, m): + r""" + Return an ascii art representing the generator indexed by ``m``. + + TESTS:: + + sage: R = NonCommutativeSymmetricFunctions(QQ).R() + sage: ascii_art(R[1,2,2,4]) + R + **** + ** + ** + * + sage: Partitions.global_options(diagram_str="#", convention="french") + sage: ascii_art(R[1,2,2,4]) + R + # + ## + ## + #### + """ + from sage.misc.ascii_art import AsciiArt, ascii_art + pref = AsciiArt([self.prefix()]) + r = pref * (AsciiArt([" "**Integer(len(pref))]) + ascii_art(m)) + r._baseline = r._h - 1 + return r + + def _latex_generator(self, m): + r""" + Return a `\LaTeX` for the generator indexed by ``m``. + + The output can be customized by setting any of the following + options when initializing the parent: + + - ``prefix`` + - ``latex_prefix`` + - ``latex_bracket`` + + (Alternatively, one can use the :meth:`print_options` method + to achieve the same effect.) + + See the documentation for :class:`CombinatorialFreeModule` for + details on the initialization options. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: e = F.basis() + sage: latex(e['a'] + 2*e['b']) # indirect doctest + B_{a} + 2B_{b} + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c'], prefix="C") + sage: e = F.basis() + sage: latex(e['a'] + 2*e['b']) # indirect doctest + C_{a} + 2C_{b} + + sage: QS3 = CombinatorialFreeModule(QQ, Permutations(3), prefix="", scalar_mult="*") + sage: original_print_options = QS3.print_options() + sage: a = 2*QS3([1,2,3])+4*QS3([3,2,1]) + sage: latex(a) # indirect doctest + 2[1, 2, 3] + 4[3, 2, 1] + sage: QS3.print_options(latex_bracket=True) + sage: latex(a) # indirect doctest + 2\left[ [1, 2, 3] \right] + 4\left[ [3, 2, 1] \right] + sage: QS3.print_options(latex_bracket="(") + sage: latex(a) # indirect doctest + 2\left( [1, 2, 3] \right) + 4\left( [3, 2, 1] \right) + sage: QS3.print_options(latex_bracket=('\\myleftbracket', '\\myrightbracket')) + sage: latex(a) # indirect doctest + 2\myleftbracket [1, 2, 3] \myrightbracket + 4\myleftbracket [3, 2, 1] \myrightbracket + sage: QS3.print_options(**original_print_options) # reset + + TESTS:: + + sage: F = CombinatorialFreeModule(QQ, [('a', 'b'), (0,1,2)]) + sage: e = F.basis() + sage: latex(e[('a','b')]) # indirect doctest + B_{('a', 'b')} + sage: latex(2*e[(0,1,2)]) # indirect doctest + 2B_{\left(0, 1, 2\right)} + sage: F = CombinatorialFreeModule(QQ, [('a', 'b'), (0,1,2)], prefix="") + sage: e = F.basis() + sage: latex(2*e[(0,1,2)]) # indirect doctest + 2\left(0, 1, 2\right) + """ + from sage.misc.latex import latex + + s = latex(m) + if s.find('\\text{\\textt') != -1: + # m contains "non-LaTeXed" strings, use string representation + s = str(m) + + # dictionary with left-right pairs of "brackets". put pairs + # in here accept \\left and \\right as prefixes. + bracket_d = {"{": "\\}", "[": "]", "(": ")", "\\{": "\\}", + "|": "|", "||": "||"} + bracket = self._print_options.get('latex_bracket', False) + if bracket is True: + left = "\\left[" + right = "\\right]" + elif bracket is False: + left = "" + right = "" + elif isinstance(bracket, (tuple, list)): + left = bracket[0] + right = bracket[1] + elif bracket in bracket_d: + left = bracket + right = bracket_d[bracket] + if left == "{": + left = "\\{" + left = "\\left" + left + right = "\\right" + right + else: + left = bracket + right = bracket + prefix = self._print_options.get('latex_prefix') + if prefix is None: + prefix = self._print_options.get('prefix') + if prefix == "": + return left + s + right + return "%s_{%s}" % (prefix, s) + diff --git a/src/sage/structure/sage_object.pyx b/src/sage/structure/sage_object.pyx index 928978246a2..0b9d80e1ed0 100644 --- a/src/sage/structure/sage_object.pyx +++ b/src/sage/structure/sage_object.pyx @@ -221,6 +221,71 @@ cdef class SageObject: def __hash__(self): return hash(self.__repr__()) + def _cache_key(self): + r""" + Return a hashable key which identifies this objects for caching. The + output must be hashable itself, or a tuple of objects which are + hashable or define a ``_cache_key``. + + This method will only be called if the object itself is not hashable. + + Some immutable objects (such as `p`-adic numbers) cannot implement a + reasonable hash function because their ``==`` operator has been + modified to return ``True`` for objects which might behave differently + in some computations:: + + sage: K. = Qq(9) + sage: b = a + O(3) + sage: c = a + 3 + sage: b + a + O(3) + sage: c + a + 3 + O(3^20) + sage: b == c + True + sage: b == a + True + sage: c == a + False + + If such objects defined a non-trivial hash function, this would break + caching in many places. However, such objects should still be usable in + caches. This can be achieved by defining an appropriate + ``_cache_key``:: + + sage: hash(b) + Traceback (most recent call last): + ... + TypeError: unhashable type: 'sage.rings.padics.padic_ZZ_pX_CR_element.pAdicZZpXCRElement' + sage: @cached_method + ....: def f(x): return x==a + sage: f(b) + True + sage: f(c) # if b and c were hashable, this would return True + False + + sage: b._cache_key() + (..., ((0, 1),), 0, 1) + sage: c._cache_key() + (..., ((0, 1), (1,)), 0, 20) + + An implementation must make sure that for elements ``a`` and ``b``, + if ``a != b``, then also ``a._cache_key() != b._cache_key()``. + In practice this means that the ``_cache_key`` should always include + the parent as its first argument:: + + sage: S. = Qq(4) + sage: d = a + O(2) + sage: b._cache_key() == d._cache_key() # this would be True if the parents were not included + False + + """ + try: + hash(self) + except TypeError: + raise TypeError("{} is not hashable and does not implement _cache_key()".format(type(self))) + else: + assert False, "_cache_key() must not be called for hashable elements" ############################################################################# # DATABASE Related code diff --git a/src/sage/symbolic/assumptions.py b/src/sage/symbolic/assumptions.py index bd28dc27415..b5a498ec6e9 100644 --- a/src/sage/symbolic/assumptions.py +++ b/src/sage/symbolic/assumptions.py @@ -118,7 +118,7 @@ def assume(self): cur = maxima.get("context") self._context = maxima.newcontext('context' + maxima._next_var_name()) try: - maxima.eval("declare(%s, %s)" % (repr(self._var), self._assumption)) + maxima.eval("declare(%s, %s)" % (self._var._maxima_init_(), self._assumption)) # except TypeError, mess: # if 'inconsistent' in str(mess): # note Maxima doesn't tell you if declarations are redundant # raise ValueError, "Assumption is inconsistent" diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 8c96bdf0ec2..2cd0bcce5f8 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -657,7 +657,7 @@ cdef class Expression(CommutativeRingElement): sage: f._magma_init_(magma) '"sin(cos(x^2) + log(x))"' sage: magma(f) # optional - magma - sin(log(x) + cos(x^2)) + sin(cos(x^2) + log(x)) sage: magma(f).Type() # optional - magma MonStgElt """ @@ -1565,7 +1565,7 @@ cdef class Expression(CommutativeRingElement): sage: f = x+2 > sqrt(3) sage: f._maxima_init_assume_() - '((x)+(2))>((3/1)^(1/2))' + '((_SAGE_VAR_x)+(2))>((3/1)^(1/2))' """ from sage.calculus.calculus import maxima @@ -1586,12 +1586,12 @@ cdef class Expression(CommutativeRingElement): sage: x = var('x') sage: x._assume_str() - 'x' + '_SAGE_VAR_x' sage: y = function('y', x) sage: y._assume_str() 'y' sage: abs(x)._assume_str() - 'abs(x)' + 'abs(_SAGE_VAR_x)' """ # if this is a function with a single argument which is a symbol, i.e. # this is of the form f(x), we pass the string 'f > 0' @@ -3905,7 +3905,7 @@ cdef class Expression(CommutativeRingElement): if var is None: cmd = 'trigreduce(%s)'%(M.name()) else: - cmd = 'trigreduce(%s,%s)'%(M.name(),str(var)) + cmd = 'trigreduce(%s,%s)'%(M.name(),'_SAGE_VAR_'+str(var)) ans = P(cmd) return self.parent()(ans) @@ -8057,6 +8057,69 @@ cdef class Expression(CommutativeRingElement): full_simplify = simplify_full + def simplify_hypergeometric(self, algorithm='maxima'): + """ + Simplify an expression containing hypergeometric functions + + INPUT: + + - ``self`` -- symbolic expression + + - ``algorithm`` -- (default: ``'maxima'``) the algorithm to use for + for simplification. Implemented are ``'maxima'``, which uses Maxima's + ``hgfred`` function, and ``'sage'``, which uses an algorithm + implemented in the hypergeometric module + + ALIAS: :meth:`hypergeometric_simplify` and + :meth:`simplify_hypergeometric` are the same + + EXAMPLES:: + + sage: hypergeometric((5, 4), (4, 1, 2, 3), + ....: x).simplify_hypergeometric() + 1/144*x^2*hypergeometric((), (3, 4), x) +... + 1/3*x*hypergeometric((), (2, 3), x) + hypergeometric((), (1, 2), x) + sage: (2*hypergeometric((), (), x)).simplify_hypergeometric() + 2*e^x + sage: (nest(lambda y: hypergeometric([y], [1], x), 3, 1) + ....: .simplify_hypergeometric()) + laguerre(-laguerre(-e^x, x), x) + sage: (nest(lambda y: hypergeometric([y], [1], x), 3, 1) + ....: .simplify_hypergeometric(algorithm='sage')) + hypergeometric((hypergeometric((e^x,), (1,), x),), (1,), x) + + """ + from sage.functions.hypergeometric import hypergeometric, closed_form + from sage.calculus.calculus import maxima + try: + op = self.operator() + except RuntimeError: + return self + ops = self.operands() + if op == hypergeometric: + if algorithm == 'maxima': + return (self.parent() + (maxima.hgfred(map(lambda o: o.simplify_hypergeometric(algorithm), + ops[0].operands()), + map(lambda o: o.simplify_hypergeometric(algorithm), + ops[1].operands()), + ops[2].simplify_hypergeometric(algorithm)))) + elif algorithm == 'sage': + return (closed_form + (hypergeometric(map(lambda o: o.simplify_hypergeometric(algorithm), + ops[0].operands()), + map(lambda o: o.simplify_hypergeometric(algorithm), + ops[1].operands()), + ops[2].simplify_hypergeometric(algorithm)))) + else: + return NotImplementedError('unknown algorithm') + if not op: + return self + return op(*map(lambda o: o.simplify_hypergeometric(algorithm), ops)) + + hypergeometric_simplify = simplify_hypergeometric + + def simplify_rectform(self, complexity_measure = string_length): r""" Attempt to simplify this expression by expressing it in the @@ -8129,7 +8192,7 @@ cdef class Expression(CommutativeRingElement): if complexity_measure is None: return simplified_expr - + if complexity_measure(simplified_expr) < complexity_measure(self): return simplified_expr else: @@ -8771,7 +8834,8 @@ cdef class Expression(CommutativeRingElement): if len(dontfactor) > 0: m = self._maxima_() name = m.name() - cmd = 'block([dontfactor:%s],factor(%s))'%(dontfactor, name) + varstr = ','.join(['_SAGE_VAR_'+str(v) for v in dontfactor]) + cmd = 'block([dontfactor:[%s]],factor(%s))'%(varstr, name) return symbolic_expression_from_maxima_string(cmd) else: try: @@ -9136,8 +9200,7 @@ cdef class Expression(CommutativeRingElement): INPUT: - - - ``x`` - variable to solve for + - ``x`` - variable(s) to solve for - ``multiplicities`` - bool (default: False); if True, return corresponding multiplicities. This keyword is @@ -9263,6 +9326,13 @@ cdef class Expression(CommutativeRingElement): sage: sol.sage() [[x == 1/4*pi + pi*z...]] + We can also solve for several variables:: + + sage: var('b, c') + (b, c) + sage: solve((b-1)*(c-1), [b,c]) + [[b == 1, c == r3], [b == r4, c == 1]] + Some basic inequalities can be also solved:: sage: x,y=var('x,y'); (ln(x)-ln(y)>0).solve(x) @@ -9336,7 +9406,7 @@ cdef class Expression(CommutativeRingElement): sage: x.solve((1,2)) Traceback (most recent call last): ... - TypeError: 1 is not a valid variable. + TypeError: (1, 2) are not valid variables. """ import operator cdef Expression ex @@ -9358,25 +9428,22 @@ cdef class Expression(CommutativeRingElement): if multiplicities and to_poly_solve: raise NotImplementedError("to_poly_solve does not return multiplicities") - # Take care of cases like solve([x^2-1], [x]) for consistency with - # multiple variable input in sage.symbolic.relation.solve(). - # There *should* be only one variable in the list, since it is - # passed from sage.symbolic.relation.solve() and multiple variables - # there don't call this function. - if isinstance(x, (list, tuple)): - x = x[0] - if x is None: - v = ex.variables() - if len(v) == 0: - if multiplicities: - return [], [] - else: - return [] - x = v[0] + if isinstance(x, (list, tuple)): + if not all([isinstance(i, Expression) for i in x]): + raise TypeError("%s are not valid variables." % repr(x)) + else: + if x is None: + v = ex.variables() + if len(v) == 0: + if multiplicities: + return [], [] + else: + return [] + x = v[0] - if not isinstance(x, Expression): - raise TypeError("%s is not a valid variable." % repr(x)) + if not isinstance(x, Expression): + raise TypeError("%s is not a valid variable." % repr(x)) m = ex._maxima_() P = m.parent() @@ -9872,7 +9939,6 @@ cdef class Expression(CommutativeRingElement): with respect to the variable `v` with endpoints `a` and `b`. - INPUT: - ``v`` - a variable or variable name @@ -9908,6 +9974,41 @@ cdef class Expression(CommutativeRingElement): sage: (1/k^5).sum(k, 1, oo) zeta(5) + .. WARNING:: + + This function only works with symbolic expressions. To sum any + other objects like list elements or function return values, + please use python summation, see + http://docs.python.org/library/functions.html#sum + + In particular, this does not work:: + + sage: n = var('n') + sage: list=[1,2,3,4,5] + sage: sum(list[n],n,0,3) + Traceback (most recent call last): + ... + TypeError: unable to convert x (=n) to an integer + + Use python ``sum()`` instead:: + + sage: sum(list[n] for n in range(4)) + 10 + + Also, only a limited number of functions are recognized in symbolic sums:: + + sage: sum(valuation(n,2),n,1,5) + Traceback (most recent call last): + ... + AttributeError: 'sage.symbolic.expression.Expression' object has no attribute 'valuation' + + Again, use python ``sum()``:: + + sage: sum(valuation(n+1,2) for n in range(5)) + 3 + + (now back to the Sage ``sum`` examples) + A well known binomial identity:: sage: assume(n>=0) diff --git a/src/sage/symbolic/expression_conversions.py b/src/sage/symbolic/expression_conversions.py index db429c9ba48..5e04ab1c7a1 100644 --- a/src/sage/symbolic/expression_conversions.py +++ b/src/sage/symbolic/expression_conversions.py @@ -217,6 +217,8 @@ def __call__(self, ex=None): return self.relation(ex, operator) elif isinstance(operator, FDerivativeOperator): return self.derivative(ex, operator) + elif operator == tuple: + return self.tuple(ex) else: return self.composition(ex, operator) @@ -399,7 +401,7 @@ def __init__(self, interface): sage: m(sin(a)) 'sin((%pi)+(2))' sage: m(exp(x^2) + pi + 2) - '(%pi)+(exp((x)^(2)))+(2)' + '(%pi)+(exp((_SAGE_VAR_x)^(2)))+(2)' """ self.name_init = "_%s_init_"%interface.name() @@ -413,12 +415,18 @@ def symbol(self, ex): sage: from sage.symbolic.expression_conversions import InterfaceInit sage: m = InterfaceInit(maxima) sage: m.symbol(x) - 'x' + '_SAGE_VAR_x' sage: f(x) = x sage: m.symbol(f) + '_SAGE_VAR_x' + sage: ii = InterfaceInit(gp) + sage: ii.symbol(x) 'x' """ - return repr(SR(ex)) + if self.interface.name()=='maxima': + return '_SAGE_VAR_'+repr(SR(ex)) + else: + return repr(SR(ex)) def pyobject(self, ex, obj): """ @@ -453,13 +461,27 @@ def relation(self, ex, operator): sage: from sage.symbolic.expression_conversions import InterfaceInit sage: m = InterfaceInit(maxima) sage: m.relation(x==3, operator.eq) - 'x = 3' + '_SAGE_VAR_x = 3' sage: m.relation(x==3, operator.lt) - 'x < 3' + '_SAGE_VAR_x < 3' """ return "%s %s %s"%(self(ex.lhs()), self.relation_symbols[operator], self(ex.rhs())) + def tuple(self, ex): + """ + EXAMPLES:: + + sage: from sage.symbolic.expression_conversions import InterfaceInit + sage: m = InterfaceInit(maxima) + sage: t = SR._force_pyobject((3, 4, e^x)) + sage: m.tuple(t) + '[3,4,exp(_SAGE_VAR_x)]' + """ + x = map(self, ex.operands()) + X = ','.join(x) + return '%s%s%s'%(self.interface._left_list_delim(), X, self.interface._right_list_delim()) + def derivative(self, ex, operator): """ EXAMPLES:: @@ -470,10 +492,10 @@ def derivative(self, ex, operator): sage: a = f(x).diff(x); a D[0](f)(x) sage: print m.derivative(a, a.operator()) - diff('f(x), x, 1) + diff('f(_SAGE_VAR_x), _SAGE_VAR_x, 1) sage: b = f(x).diff(x, x) sage: print m.derivative(b, b.operator()) - diff('f(x), x, 2) + diff('f(_SAGE_VAR_x), _SAGE_VAR_x, 2) We can also convert expressions where the argument is not just a variable, but the result is an "at" expression using temporary @@ -504,19 +526,21 @@ def derivative(self, ex, operator): params = operator.parameter_set() params = ["%s, %s"%(temp_args[i], params.count(i)) for i in set(params)] subs = ["%s = %s"%(t,a) for t,a in zip(temp_args,args)] - return "at(diff('%s(%s), %s), [%s])"%(f.name(), + outstr = "at(diff('%s(%s), %s), [%s])"%(f.name(), ", ".join(map(repr,temp_args)), ", ".join(params), - ", ".join(subs)) - - f = operator.function() - params = operator.parameter_set() - params = ["%s, %s"%(args[i], params.count(i)) for i in set(params)] - - return "diff('%s(%s), %s)"%(f.name(), - ", ".join(map(repr, args)), - ", ".join(params)) - + ", ".join(subs)) + else: + f = operator.function() + params = operator.parameter_set() + def prep_sage(arg): + return '_SAGE_VAR_' + repr(arg) + params = ["%s, %s"%(prep_sage(args[i]), params.count(i)) for i in set(params)] + outstr = "diff('%s(%s), %s)"%(f.name(), + ", ".join(map(prep_sage, args)), + ", ".join(params)) + return outstr + def arithmetic(self, ex, operator): """ EXAMPLES:: @@ -525,7 +549,7 @@ def arithmetic(self, ex, operator): sage: from sage.symbolic.expression_conversions import InterfaceInit sage: m = InterfaceInit(maxima) sage: m.arithmetic(x+2, operator.add) - '(x)+(2)' + '(_SAGE_VAR_x)+(2)' """ args = ["(%s)"%self(op) for op in ex.operands()] return arithmetic_operators[operator].join(args) @@ -537,9 +561,9 @@ def composition(self, ex, operator): sage: from sage.symbolic.expression_conversions import InterfaceInit sage: m = InterfaceInit(maxima) sage: m.composition(sin(x), sin) - 'sin(x)' + 'sin(_SAGE_VAR_x)' sage: m.composition(ceil(x), ceil) - 'ceiling(x)' + 'ceiling(_SAGE_VAR_x)' sage: m = InterfaceInit(mathematica) sage: m.composition(sin(x), sin) @@ -1419,6 +1443,18 @@ def composition(self, ex, function): """ return self.etb.call(function, *ex.operands()) + def tuple(self, ex): + r""" + Given a symbolic tuple, return its elements as a Python list + + EXAMPLES:: + + sage: from sage.ext.fast_callable import ExpressionTreeBuilder + sage: etb = ExpressionTreeBuilder(vars=['x']) + sage: SR._force_pyobject((2, 3, x^2))._fast_callable_(etb) + [2, 3, x^2] + """ + return ex.operands() def fast_callable(ex, etb): """ diff --git a/src/sage/symbolic/function.pyx b/src/sage/symbolic/function.pyx index f774812bf62..408b6da9cf0 100644 --- a/src/sage/symbolic/function.pyx +++ b/src/sage/symbolic/function.pyx @@ -697,7 +697,7 @@ cdef class Function(SageObject): sage: import numpy sage: a = numpy.arange(5) sage: csc(a) - doctest:270: RuntimeWarning: divide by zero encountered in divide + doctest:...: RuntimeWarning: divide by zero encountered in divide array([ inf, 1.18839511, 1.09975017, 7.0861674 , -1.32134871]) sage: factorial(a) @@ -934,9 +934,10 @@ cdef class BuiltinFunction(Function): # conversion to the original parent failed # we try if it works with the corresponding complex domain - if org_parent is float: + if org_parent is float or org_parent is complex: try: - return complex(res) + from sage.rings.complex_double import CDF + return complex(CDF(res)) except (TypeError, ValueError): pass elif hasattr(org_parent, 'complex_field'): diff --git a/src/sage/symbolic/integration/integral.py b/src/sage/symbolic/integration/integral.py index de384ee3587..bcfb90e7a25 100644 --- a/src/sage/symbolic/integration/integral.py +++ b/src/sage/symbolic/integration/integral.py @@ -393,7 +393,7 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None): Traceback (most recent call last): ... ValueError: Computation failed since Maxima requested additional - constraints; using the 'assume' command before integral evaluation + constraints; using the 'assume' command before evaluation *may* help (example of legal syntax is 'assume(n>0)', see `assume?` for more details) Is n equal to -1? @@ -515,7 +515,7 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None): Traceback (most recent call last): ... ValueError: Computation failed since Maxima requested additional - constraints; using the 'assume' command before integral evaluation + constraints; using the 'assume' command before evaluation *may* help (example of legal syntax is 'assume(a>0)', see `assume?` for more details) Is a positive or negative? @@ -555,7 +555,7 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None): sage: res = integral(f,x,0.0001414, 1.); res Traceback (most recent call last): ... - ValueError: Computation failed since Maxima requested additional constraints; using the 'assume' command before integral evaluation *may* help (example of legal syntax is 'assume(50015104*y^2-50015103>0)', see `assume?` for more details) + ValueError: Computation failed since Maxima requested additional constraints; using the 'assume' command before evaluation *may* help (example of legal syntax is 'assume(50015104*y^2-50015103>0)', see `assume?` for more details) Is 50015104*y^2-50015103 positive, negative or zero? sage: assume(y>1) sage: res = integral(f,x,0.0001414, 1.); res diff --git a/src/sage/symbolic/random_tests.py b/src/sage/symbolic/random_tests.py index a9a57bcc472..ff04312d25c 100644 --- a/src/sage/symbolic/random_tests.py +++ b/src/sage/symbolic/random_tests.py @@ -18,6 +18,7 @@ import sage.calculus.calculus import sage.symbolic.pynac from sage.symbolic.constants import * +from sage.functions.hypergeometric import hypergeometric ################################################################### @@ -51,7 +52,8 @@ def _mk_full_functions(): return [(1.0, f, f.number_of_arguments()) for (name, f) in items if hasattr(f, 'number_of_arguments') and - f.number_of_arguments() > 0] + f.number_of_arguments() > 0 and + f <> hypergeometric] # For creating simple expressions diff --git a/src/sage/symbolic/relation.py b/src/sage/symbolic/relation.py index 3c9e907844a..6550a622822 100644 --- a/src/sage/symbolic/relation.py +++ b/src/sage/symbolic/relation.py @@ -204,7 +204,7 @@ sage: x = var('x') sage: eq = (x^(3/5) >= pi^2 + e^i) sage: eq._maxima_init_() - '(x)^(3/5) >= ((%pi)^(2))+(exp(0+%i*1))' + '(_SAGE_VAR_x)^(3/5) >= ((%pi)^(2))+(exp(0+%i*1))' sage: e1 = x^3 + x == sin(2*x) sage: z = e1._maxima_() sage: z.parent() is sage.calculus.calculus.maxima @@ -719,7 +719,7 @@ def solve(f, *args, **kwds): sage: solve([x == 1], (1, a)) Traceback (most recent call last): ... - TypeError: 1 is not a valid variable. + TypeError: (1, a) are not valid variables. Test that the original version of a system in the French Sage book now works (:trac:`14306`):: diff --git a/src/sage/symbolic/ring.pyx b/src/sage/symbolic/ring.pyx index 51db377601c..348f50b1e8f 100644 --- a/src/sage/symbolic/ring.pyx +++ b/src/sage/symbolic/ring.pyx @@ -22,13 +22,13 @@ include "sage/ext/cdefs.pxi" from ginac cimport * from sage.rings.integer cimport Integer -from sage.rings.real_mpfr import RealNumber +from sage.rings.real_mpfr cimport RealNumber from sage.symbolic.expression cimport Expression, new_Expression_from_GEx, new_Expression_from_pyobject, is_Expression from sage.libs.pari.pari_instance cimport PariInstance from sage.misc.latex import latex_variable_name -from sage.structure.element cimport RingElement, Element +from sage.structure.element cimport RingElement, Element, Matrix from sage.structure.parent_base import ParentWithBase from sage.rings.ring cimport CommutativeRing from sage.categories.morphism cimport Morphism @@ -283,7 +283,7 @@ cdef class SymbolicRing(CommutativeRing): return new_Expression_from_GEx(self, g_mInfinity) elif x is unsigned_infinity: return new_Expression_from_GEx(self, g_UnsignedInfinity) - elif isinstance(x, RingElement): + elif isinstance(x, (RingElement, Matrix)): GEx_construct_pyobject(exp, x) else: raise TypeError @@ -745,9 +745,11 @@ cdef class SymbolicRing(CommutativeRing): elif len(args) == 1 and isinstance(args[0], dict): d = args[0] else: - from sage.misc.superseded import deprecation - vars = _the_element.operands() - deprecation(5930, "Substitution using function-call syntax and unnamed arguments is deprecated and will be removed from a future release of Sage; you can use named arguments instead, like EXPR(x=..., y=...)") + import inspect + if not hasattr(_the_element,'_fast_callable_') or not inspect.ismethod(_the_element._fast_callable_): + # only warn if _the_element is not dynamic + from sage.misc.superseded import deprecation + deprecation(5930, "Substitution using function-call syntax and unnamed arguments is deprecated and will be removed from a future release of Sage; you can use named arguments instead, like EXPR(x=..., y=...)") d = {} vars = _the_element.variables() diff --git a/src/sage/tests/french_book/recequadiff.py b/src/sage/tests/french_book/recequadiff.py index 76fd47fbb89..c78d266cc03 100755 --- a/src/sage/tests/french_book/recequadiff.py +++ b/src/sage/tests/french_book/recequadiff.py @@ -20,33 +20,33 @@ Sage example in ./recequadiff.tex, line 182:: sage: desolve(diff(y,x) + 3*y == exp(x), y, show_method=True) - [1/4*(4*c + e^(4*x))*e^(-3*x), 'linear'] + [1/4*(4*_C + e^(4*x))*e^(-3*x), 'linear'] Sage example in ./recequadiff.tex, line 194:: sage: desolve(y*diff(y,x) == x, y, show_method=True) - [1/2*y(x)^2 == 1/2*x^2 + c, 'separable'] + [1/2*y(x)^2 == 1/2*x^2 + _C, 'separable'] Sage example in ./recequadiff.tex, line 204:: sage: desolve(diff(y,x) == exp(x+y), y, show_method=True) - [-(e^(x + y(x)) + 1)*e^(-y(x)) == c, 'exact'] + [-(e^(x + y(x)) + 1)*e^(-y(x)) == _C, 'exact'] Sage example in ./recequadiff.tex, line 215:: sage: desolve(diff(y,x)-y == x*y^4, y, show_method=True) - [e^x/(-1/3*(3*x - 1)*e^(3*x) + c)^(1/3), 'bernoulli'] + [e^x/(-1/3*(3*x - 1)*e^(3*x) + _C)^(1/3), 'bernoulli'] Sage example in ./recequadiff.tex, line 227:: sage: desolve(x^2*diff(y,x) == y^2+x*y+x^2, y, show_method=True) - [c*x == e^(arctan(y(x)/x)), 'homogeneous'] + [_C*x == e^(arctan(y(x)/x)), 'homogeneous'] Sage example in ./recequadiff.tex, line 244:: sage: desolve(diff(y,x) == (cos(y)-2*x)/(y+x*sin(y)), y, ... show_method=True) - [x^2 - x*cos(y(x)) + 1/2*y(x)^2 == c, 'exact'] + [x^2 - x*cos(y(x)) + 1/2*y(x)^2 == _C, 'exact'] Sage example in ./recequadiff.tex, line 263:: @@ -58,7 +58,7 @@ sage: desolve(y == x*diff(y,x)-diff(y,x)^2, y, ... contrib_ode=True, show_method=True) - [[y(x) == -c^2 + c*x, y(x) == 1/4*x^2], 'clairault'] + [[y(x) == -_C^2 + _C*x, y(x) == 1/4*x^2], 'clairault'] Sage example in ./recequadiff.tex, line 293:: @@ -68,13 +68,13 @@ sage: DE = diff(y,x)+2*y == x**2-2*x+3 sage: desolve(DE, y) - 1/4*((2*x^2 - 2*x + 1)*e^(2*x) - 2*(2*x - 1)*e^(2*x) + 4*c + 1/4*((2*x^2 - 2*x + 1)*e^(2*x) - 2*(2*x - 1)*e^(2*x) + 4*_C + 6*e^(2*x))*e^(-2*x) Sage example in ./recequadiff.tex, line 305:: sage: desolve(DE, y).expand() - 1/2*x^2 + c*e^(-2*x) - 3/2*x + 9/4 + 1/2*x^2 + _C*e^(-2*x) - 3/2*x + 9/4 Sage example in ./recequadiff.tex, line 321:: @@ -90,29 +90,29 @@ sage: x = var('x'); y = function('y', x) sage: desolve(diff(y,x)*log(y) == y*sin(x), y, show_method=True) - [1/2*log(y(x))^2 == c - cos(x), 'separable'] + [1/2*log(y(x))^2 == _C - cos(x), 'separable'] Sage example in ./recequadiff.tex, line 348:: sage: ed(x) = desolve(diff(y,x)*log(y) == y*sin(x), y); ed(x) - 1/2*log(y(x))^2 == c - cos(x) + 1/2*log(y(x))^2 == _C - cos(x) Sage example in ./recequadiff.tex, line 356:: sage: solve(ed, y) - [y(x) == e^(-sqrt(2*c - 2*cos(x))), y(x) == e^(sqrt(2*c - 2*cos(x)))] + [y(x) == e^(-sqrt(2*_C - 2*cos(x))), y(x) == e^(sqrt(2*_C - 2*cos(x)))] Sage example in ./recequadiff.tex, line 367:: - sage: solve(ed, y)[0].subs_expr(c==5).rhs() + sage: solve(ed, y)[0].subs_expr(_C==5).rhs() Traceback (most recent call last): ... - NameError: name 'c' is not defined + NameError: name '_C' is not defined Sage example in ./recequadiff.tex, line 377:: sage: ed.variables() - (c, x) + (_C, x) Sage example in ./recequadiff.tex, line 384:: @@ -154,24 +154,24 @@ sage: assume(x>0) sage: desolve(DE, u) - x == c*e^(arcsinh(x^2*u(x)/sqrt(x^4))) + x == _C*e^(arcsinh(x^2*u(x)/sqrt(x^4))) Sage example in ./recequadiff.tex, line 505:: sage: S = desolve(DE,u)._maxima_().ev(logarc=True).sage().solve(u); S - [u(x) == -(sqrt(u(x)^2 + 1)*c - x)/c] + [u(x) == -(sqrt(u(x)^2 + 1)*_C - x)/_C] Sage example in ./recequadiff.tex, line 519:: sage: solu = (x-S[0]*c)^2; solu - (c*u(x) - x)^2 == (u(x)^2 + 1)*c^2 + (_C*u(x) - x)^2 == (u(x)^2 + 1)*_C^2 sage: sol = solu.solve(u); sol - [u(x) == -1/2*(c^2 - x^2)/(c*x)] + [u(x) == -1/2*(_C^2 - x^2)/(_C*x)] Sage example in ./recequadiff.tex, line 526:: sage: y(x) = x*sol[0].rhs(); y(x) - -1/2*(c^2 - x^2)/c + -1/2*(_C^2 - x^2)/_C Sage example in ./recequadiff.tex, line 535:: @@ -185,28 +185,28 @@ sage: x = var('x'); y = function('y', x); a, b = var('a, b') sage: DE = diff(y,x) - a*y == -b*y**2 sage: sol(x) = desolve(DE,[y,x]); sol(x) - -(log(b*y(x) - a) - log(y(x)))/a == c + x + -(log(b*y(x) - a) - log(y(x)))/a == _C + x Sage example in ./recequadiff.tex, line 575:: sage: Sol(x) = solve(sol, y)[0]; Sol(x) - log(y(x)) == a*(c + x) + log(b*y(x) - a) + log(y(x)) == (_C + x)*a + log(b*y(x) - a) Sage example in ./recequadiff.tex, line 582:: sage: Sol(x) = Sol(x).lhs()-Sol(x).rhs(); Sol(x) - -a*(c + x) - log(b*y(x) - a) + log(y(x)) + -(_C + x)*a - log(b*y(x) - a) + log(y(x)) sage: Sol = Sol.simplify_log(); Sol(x) - -a*(c + x) + log(y(x)/(b*y(x) - a)) + -(_C + x)*a + log(y(x)/(b*y(x) - a)) sage: solve(Sol, y)[0].simplify() - y(x) == a*e^(a*c + a*x)/(b*e^(a*c + a*x) - 1) + y(x) == a*e^(_C*a + a*x)/(b*e^(_C*a + a*x) - 1) Sage example in ./recequadiff.tex, line 602:: sage: x = var('x'); y = function('y', x) sage: DE = diff(y,x,2)+3*y == x^2-7*x+31 sage: desolve(DE, y).expand() - 1/3*x^2 + k2*cos(sqrt(3)*x) + k1*sin(sqrt(3)*x) - 7/3*x + 91/9 + 1/3*x^2 + _K2*cos(sqrt(3)*x) + _K1*sin(sqrt(3)*x) - 7/3*x + 91/9 Sage example in ./recequadiff.tex, line 611:: @@ -240,20 +240,20 @@ Sage example in ./recequadiff.tex, line 709:: sage: g(t) = desolve(eq2(x,t),[g,t]); g(t) - c*e^(k*t) + _C*e^(k*t) Sage example in ./recequadiff.tex, line 717:: sage: desolve(eq1,[f,x]) Traceback (most recent call last): ... - TypeError: ECL says: Maxima asks: + TypeError: Computation failed ... Is k positive, negative or zero? Sage example in ./recequadiff.tex, line 728:: sage: assume(k>0); desolve(eq1,[f,x]) - k1*e^(sqrt(k)*x) + k2*e^(-sqrt(k)*x) + _K1*e^(sqrt(k)*x) + _K2*e^(-sqrt(k)*x) Sage example in ./recequadiff.tex, line 782:: diff --git a/src/sage/version.py b/src/sage/version.py index 771523e0f6f..2e7f4e794ce 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.3.beta4' -date = '2014-06-19' +version = '6.3.beta6' +date = '2014-07-19'