diff --git a/VERSION.txt b/VERSION.txt index c152a49d9d0..fdb8415d095 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -Sage version 7.0, released 2016-01-19 +Sage version 7.1.beta1, released 2016-01-28 diff --git a/build/pkgs/cliquer/SPKG.txt b/build/pkgs/cliquer/SPKG.txt index 853c784786e..1dfd7d48e6d 100644 --- a/build/pkgs/cliquer/SPKG.txt +++ b/build/pkgs/cliquer/SPKG.txt @@ -16,7 +16,4 @@ http://users.tkk.fi/pat/cliquer.html * None == Patches == - * Makefile.patch: Patch the Makefile for Sage. Remove hardcoded - compiler and linker flags, allow flags to be set from spkg-install. - More importantly, we're building cliquer as a dynamic shared library, - instead of a stand-alone program. + * autotoolized - see https://github.com/dimpase/autocliquer diff --git a/build/pkgs/cliquer/checksums.ini b/build/pkgs/cliquer/checksums.ini index 7e28001b68f..cec6f64dcc2 100644 --- a/build/pkgs/cliquer/checksums.ini +++ b/build/pkgs/cliquer/checksums.ini @@ -1,4 +1,4 @@ -tarball=cliquer-VERSION.tar.bz2 -sha1=8239530eb14c1273c32ffcf3b671dd3766084374 -md5=32b97b6689318b58d9c099e2c7769521 -cksum=3641271864 +tarball=cliquer-VERSION.tar.gz +sha1=930ec79f64facb8ac4035a3e3702ed2b1dabd092 +md5=1d985e0bed876cc69aed43953a814a6a +cksum=766312376 diff --git a/build/pkgs/cliquer/package-version.txt b/build/pkgs/cliquer/package-version.txt index 52e40c7ff6e..8a68fb3ec24 100644 --- a/build/pkgs/cliquer/package-version.txt +++ b/build/pkgs/cliquer/package-version.txt @@ -1 +1 @@ -1.21.p2 +1.21.p3 diff --git a/build/pkgs/cliquer/patches/Makefile.patch b/build/pkgs/cliquer/patches/Makefile.patch deleted file mode 100644 index f773ecce3eb..00000000000 --- a/build/pkgs/cliquer/patches/Makefile.patch +++ /dev/null @@ -1,46 +0,0 @@ -diff -ru src/Makefile b/Makefile ---- src/Makefile 2010-01-22 08:53:21.000000000 +0100 -+++ b/Makefile 2014-01-16 14:55:51.977047191 +0100 -@@ -1,24 +1,3 @@ -- --##### Configurable options: -- --## Compiler: --CC=gcc --#CC=cc -- --## Compiler flags: -- --# GCC: (also -march=pentium etc, for machine-dependent optimizing) --CFLAGS=-Wall -O3 -fomit-frame-pointer -funroll-loops -- --# GCC w/ debugging: --#CFLAGS=-Wall -g -DINLINE= -- --# Compaq C / Digital C: --#CFLAGS=-arch=host -fast -- --# SunOS: --#CFLAGS=-fast -- - ## Program options: - - # Enable long options for cl (eg. "cl --help"), comment out to disable. -@@ -29,14 +8,14 @@ - ##### End of configurable options - - --all: cl -+all: libcliquer.so - - - testcases: testcases.o cliquer.o graph.o reorder.o - $(CC) $(LDFLAGS) -o $@ testcases.o cliquer.o graph.o reorder.o - --cl: cl.o cliquer.o graph.o reorder.o -- $(CC) $(LDFLAGS) -o $@ cl.o cliquer.o graph.o reorder.o -+libcliquer.so: cl.o cliquer.o graph.o reorder.o -+ $(CC) $(LDFLAGS) $(SAGESOFLAGS) -o $@ cl.o cliquer.o graph.o reorder.o - - - cl.o testcases.o cliquer.o graph.o reorder.o: cliquer.h set.h graph.h misc.h reorder.h Makefile cliquerconf.h diff --git a/build/pkgs/cliquer/spkg-check b/build/pkgs/cliquer/spkg-check index 3c6fd5964f0..cec33f60e3b 100755 --- a/build/pkgs/cliquer/spkg-check +++ b/build/pkgs/cliquer/spkg-check @@ -1,35 +1,18 @@ #!/usr/bin/env bash -if [ "$SAGE_LOCAL" = "" ]; then - echo "SAGE_LOCAL undefined ... exiting"; - echo "Maybe run 'sage -sh'?" +if [ -z "$SAGE_LOCAL" ]; then + echo >&2 "Error: SAGE_LOCAL undefined - exiting..." + echo >&2 "Maybe run 'sage -sh'?" exit 1 fi -OPTIMIZATION_FLAGS="-O3 -funroll-loops -fomit-frame-pointer" -# Work around a bug in gcc 4.6.0: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48774 -if [ "`testcc.sh $CC`" = GCC ] ; then - if $CC -dumpversion 2>/dev/null |grep >/dev/null '^4\.6\.[01]' ; then - echo "Warning: Working around bug in gcc 4.6.0" - OPTIMIZATION_FLAGS="$OPTIMIZATION_FLAGS -fno-ivopts" - fi -fi - -CFLAGS="$CFLAGS `testcflags.sh -g $OPTIMIZATION_FLAGS -fPIC -KPIC -Wall`" -CPPFLAGS="$CPPFLAGS -I$SAGE_LOCAL/include" -LDFLAGS="$LDFLAGS -L$SAGE_LOCAL/lib" +cd src -# Compile for 64-bit if SAGE64 is set to 'yes'. -# On 64-bit hardware, we don't need to set this variable to true. A -# 64-bit cliquer library would be built on such platform. -if [ "$SAGE64" = yes ]; then - CFLAGS="$CFLAGS -m64 " - LDFLAGS="$LDFLAGS -m64 " +echo "Now building and running cliquer's test suite..." +$MAKE check +if [ $? -ne 0 ]; then + echo >&2 "Error: The cliquer's test suite failed." + exit 1 fi -# Export everything -export CFLAGS -export CPPFLAGS -export LDFLAGS - -cd src && $MAKE test +echo "The cliquer's test suite passed successfully." diff --git a/build/pkgs/cliquer/spkg-install b/build/pkgs/cliquer/spkg-install index efee877c98d..10b9cabca11 100755 --- a/build/pkgs/cliquer/spkg-install +++ b/build/pkgs/cliquer/spkg-install @@ -1,43 +1,14 @@ #!/usr/bin/env bash -OPTIMIZATION_FLAGS="-O3 -funroll-loops -fomit-frame-pointer" -# Work around a bug in gcc 4.6.0: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48774 -if [ "`testcc.sh $CC`" = GCC ] ; then - if $CC -dumpversion 2>/dev/null |grep >/dev/null '^4\.6\.[01]' ; then - echo "Warning: Working around bug in gcc 4.6.0" - OPTIMIZATION_FLAGS="$OPTIMIZATION_FLAGS -fno-ivopts" - fi -fi - -CFLAGS="$CFLAGS `testcflags.sh -g $OPTIMIZATION_FLAGS -fPIC -KPIC -Wall`" -CPPFLAGS="$CPPFLAGS -I$SAGE_LOCAL/include" - -# Compile for 64-bit if SAGE64 is set to 'yes'. -# On 64-bit hardware, we don't need to set this variable to true. A -# 64-bit cliquer library would be built on such platform. -if [ "$SAGE64" = yes ]; then - echo "Building a 64-bit version of cliquer" - CFLAGS="$CFLAGS -m64 " - LDFLAGS="$LDFLAGS -m64 " -fi - -# Flags for building a dynamically linked shared object. -if [ "$UNAME" = "Darwin" ]; then - SAGESOFLAGS="-dynamiclib -single_module -flat_namespace -undefined dynamic_lookup" -elif [ "$UNAME" = "SunOS" ]; then - SAGESOFLAGS="-shared -Wl,-h,libcliquer.so -Wl,-ztext" -else - SAGESOFLAGS="-shared -Wl,-soname,libcliquer.so" +if [ -z "$SAGE_LOCAL" ]; then + echo >&2 "SAGE_LOCAL undefined - exiting..." + echo >&2 "Maybe run 'sage -sh'?" + exit 1 fi -# Export everything -export CFLAGS -export CPPFLAGS -export LDFLAGS -export SAGESOFLAGS - cd src +echo "Applying patches..." # Apply all patches for patch in ../patches/*.patch; do [ -r "$patch" ] || continue # Skip non-existing or non-readable patches @@ -49,22 +20,16 @@ for patch in ../patches/*.patch; do fi done -$MAKE +echo "Configuring..." +./configure --prefix="$SAGE_LOCAL" --disable-static --libdir="$SAGE_LOCAL/lib" if [ $? -ne 0 ]; then - echo >&2 "Failed to compile cliquer" - exit 1 + echo >&2 "Error configuring cliquer" + exit 1 fi -rm -rf "$SAGE_LOCAL/include/cliquer/" -mkdir -p "$SAGE_LOCAL/include/cliquer/" -cp *.h "$SAGE_LOCAL/include/cliquer/" - -if [ "$UNAME" = "Darwin" ]; then - cp -f libcliquer.so "$SAGE_LOCAL/lib/libcliquer.dylib" - install_name_tool -id "${SAGE_LOCAL}"/lib/libcliquer.dylib "${SAGE_LOCAL}"/lib/libcliquer.dylib -elif [ "$UNAME" = "CYGWIN" ]; then - cp -f libcliquer.so "$SAGE_LOCAL/lib/libcliquer.dll" +echo "Building and installing ..." +$MAKE install +if [ $? -ne 0 ]; then + echo >&2 "Error installing cliquer" + exit 1 fi - -# Copy this in all cases, in any case it doesn't hurt. -cp -f libcliquer.so "$SAGE_LOCAL/lib/libcliquer.so" diff --git a/build/pkgs/cliquer/spkg-src b/build/pkgs/cliquer/spkg-src new file mode 100755 index 00000000000..bcbdabecbe1 --- /dev/null +++ b/build/pkgs/cliquer/spkg-src @@ -0,0 +1,23 @@ +#!/bin/sh +# +# creates the tarball in the current dir, to be moved to ../../../upstream + +#PATCHLEVEL=".p0" +PATCHLEVEL= + +rm -rf autocliquer/ +git clone https://github.com/dimpase/autocliquer.git +cd autocliquer/ + +VERSION=`autoconf --trace='AC_INIT:$2'` +autoreconf -fi +automake --add-missing --copy +./configure + +rm -f cliquer-$VERSION.tar.gz +make dist +mv cliquer-$VERSION.tar.gz ../ +cd .. +rm -rf autocliquer/ + + diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 0482c018bb4..51b29042b05 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=631a348f0ac864b3ec37dfcb1d7fab79e213a075 -md5=5759d84c96102aee89c7eaea0aadf842 -cksum=2510923339 +sha1=667c4449d5d193a6520ad38cf9c0f49e209cbc21 +md5=de3e80d1d278a05e430669fd8beefa19 +cksum=3190707453 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index dee261df401..aaacbe66291 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -140 +142 diff --git a/build/pkgs/gp2c/checksums.ini b/build/pkgs/gp2c/checksums.ini index 0f114ad0f51..b057d08ce7b 100644 --- a/build/pkgs/gp2c/checksums.ini +++ b/build/pkgs/gp2c/checksums.ini @@ -1,4 +1,4 @@ tarball=gp2c-VERSION.tar.gz -sha1=5acb1a13e1ed8ee877ffb34baa3b817e720f3e50 -md5=cb263990e399153aca6a2540930b4600 -cksum=1931194041 +sha1=e07cebffcd09c0d644d52335130984f33042b46f +md5=3f6bb47d41ddca7b6a4938d16abbe4e8 +cksum=3338275717 diff --git a/build/pkgs/gp2c/package-version.txt b/build/pkgs/gp2c/package-version.txt index 6eb15535643..e353504d336 100644 --- a/build/pkgs/gp2c/package-version.txt +++ b/build/pkgs/gp2c/package-version.txt @@ -1 +1 @@ -0.0.9pl3 +0.0.9pl5 diff --git a/build/pkgs/gp2c/patches/20150326.patch b/build/pkgs/gp2c/patches/20150326.patch deleted file mode 100644 index 3f7651f8162..00000000000 --- a/build/pkgs/gp2c/patches/20150326.patch +++ /dev/null @@ -1,218 +0,0 @@ -Various fixes between released version 0.0.9pl3 and gp2c git repo - -diff -ru gp2c-0.0.9pl3/gp2c gp2c/gp2c ---- gp2c-0.0.9pl3/gp2c 2011-10-02 22:28:23.000000000 +0200 -+++ gp2c/gp2c 2015-07-09 14:50:05.600098572 +0200 -@@ -14,4 +14,4 @@ - EOF - exit 1; - fi --GP2C_FUNC_DSC=desc/func.dsc src/gp2c $* -+GP2C_FUNC_DSC=desc/func.dsc exec src/gp2c "$@" -diff -ru gp2c-0.0.9pl3/gp2c-dbg gp2c/gp2c-dbg ---- gp2c-0.0.9pl3/gp2c-dbg 2011-10-02 22:28:23.000000000 +0200 -+++ gp2c/gp2c-dbg 2015-07-09 14:50:05.601098584 +0200 -@@ -13,4 +13,4 @@ - EOF - exit 1; - fi --GP2C=./gp2c scripts/gp2c-dbg $* -+GP2C=./gp2c exec scripts/gp2c-dbg "$@" -diff -ru gp2c-0.0.9pl3/gp2c-run gp2c/gp2c-run ---- gp2c-0.0.9pl3/gp2c-run 2011-10-02 22:28:23.000000000 +0200 -+++ gp2c/gp2c-run 2015-07-09 14:50:05.601098584 +0200 -@@ -13,4 +13,4 @@ - EOF - exit 1; - fi --GP2C=./gp2c scripts/gp2c-run $* -+GP2C=./gp2c exec scripts/gp2c-run "$@" -diff -ru gp2c-0.0.9pl3/src/funcdesc.c gp2c/src/funcdesc.c ---- gp2c-0.0.9pl3/src/funcdesc.c 2014-11-17 14:30:55.000000000 +0100 -+++ gp2c/src/funcdesc.c 2015-07-09 14:50:05.603098609 +0200 -@@ -575,7 +575,8 @@ - gpfunc *gp = lfunc + findfunction(entryname(arg[0])); - if (gp->spec==GPuser && gp->user->wrapper>=0) - { -- if (funcmode(*gp)&(1<user->wrapper; -+ if ((funcmode(*gp)&(1<fout, nb-1,arg+1,FC_tovecprec,d->nerr); - else - { -diff -ru gp2c-0.0.9pl3/src/funcspec.c gp2c/src/funcspec.c ---- gp2c-0.0.9pl3/src/funcspec.c 2014-11-22 22:28:59.000000000 +0100 -+++ gp2c/src/funcspec.c 2015-07-09 14:50:05.604098621 +0200 -@@ -432,8 +432,11 @@ - arg[0] = newleaf(pred); - } - genblock(arg[0],n); -- arg[1]=geninsertvar(arg[1],ret); -- makesubblock(arg[1]); -+ if (arg[1]!=GNOARG) -+ { -+ arg[1]=geninsertvar(arg[1],ret); -+ makesubblock(arg[1]); -+ } - if(nb==3) - { - arg[2]=geninsertvar(arg[2],ret==-1?-1:newleaf(ret)); -diff -ru gp2c-0.0.9pl3/src/genfunc.c gp2c/src/genfunc.c ---- gp2c-0.0.9pl3/src/genfunc.c 2014-12-16 10:56:21.000000000 +0100 -+++ gp2c/src/genfunc.c 2015-07-09 14:50:05.605098633 +0200 -@@ -524,13 +524,15 @@ - } - if (name) - { -- if (i>=nb) -- die(err_func,"too few arguments in lambda"); - fputs(" ",fout); - if (c=='p') - fprintf(fout,"prec"); - else -+ { -+ if (i>=nb) -+ die(err_func,"too few arguments in lambda"); - gencode(fout, name[i++]); -+ } - } - } - if (name && i=nb) -- die(err_func,"too few arguments in lambda"); - if (c=='p') -- fprintf(fout,"prec"); -+ { -+ if (mode&(1<=nb) -+ die(err_func,"too few arguments in lambda"); -+ if (!firstarg) fprintf(fout,", "); -+ firstarg=0; -+ ot = tree[name[i]].t; -+ tree[name[i]].t = t; -+ gencast(fout, name[i], ot); -+ tree[name[i]].t = ot; -+ i++; -+ } - } - if (name && iuser->defnode; - int savc=s_ctx.n; -@@ -650,7 +667,6 @@ - int res; - ctxvar *vres; - context *fc=block+gp->user->bl; -- gpfunc *wr=lfunc+wrap; - gpdescarg *rule; - if (!wr->proto.code) - die(wr->node,"Wrapper not defined"); -@@ -688,7 +704,7 @@ - fprintf(fout," = "); - } - fprintf(fout,"%s(",gp->proto.cname); -- firstarg=genwrapargs(fout, wrap, nb, stack); -+ firstarg=genwrapargs(fout, wrap, nb, stack, m); - for (i=0,d=1;is.n;i++) - { - ctxvar *v=fc->c+i; -diff -ru gp2c-0.0.9pl3/src/printnode.c gp2c/src/printnode.c ---- gp2c-0.0.9pl3/src/printnode.c 2013-10-12 20:32:23.000000000 +0200 -+++ gp2c/src/printnode.c 2015-07-09 14:50:05.606098645 +0200 -@@ -231,6 +231,10 @@ - fprintf(fout,"&"); - printnode(fout,x); - break; -+ case Fvararg: -+ printnode(fout,x); -+ fprintf(fout,"[..]"); -+ break; - case Fcall: - printnode(fout,x); - fprintf(fout,"("); -diff -ru gp2c-0.0.9pl3/src/topfunc.c gp2c/src/topfunc.c ---- gp2c-0.0.9pl3/src/topfunc.c 2014-12-16 10:53:09.000000000 +0100 -+++ gp2c/src/topfunc.c 2015-07-09 14:50:05.606098645 +0200 -@@ -109,7 +109,7 @@ - int nn = newanon(); - int nf = newnode(Fdeffunc,newnode(Ffunction,nn,x),y); - int seq = newnode(Fentry,nn,-1); -- topfunc(n,p,fun,pfun,nf,wr); -+ topfunc(nf,p,fun,pfun,nf,wr); - if (fun>=0) tree[n] = tree[seq]; - } - -diff -ru gp2c-0.0.9pl3/test2/gp/apply.gp gp2c/test2/gp/apply.gp ---- gp2c-0.0.9pl3/test2/gp/apply.gp 2013-06-11 12:02:48.000000000 +0200 -+++ gp2c/test2/gp/apply.gp 2015-07-09 14:50:05.609098682 +0200 -@@ -3,6 +3,7 @@ - f3(f,v)=apply(f,v) - g(x)=x^2+1 - f4(v)=apply(g,v) -+f5(z:small)=apply(n:small->(n+z)!,[1,2,3,4,5]) - - g1(v,z)=select(x->x>z,v) - g2(v)=select(isprime,v) -diff -ru gp2c-0.0.9pl3/test2/input/apply gp2c/test2/input/apply ---- gp2c-0.0.9pl3/test2/input/apply 2013-06-11 12:02:48.000000000 +0200 -+++ gp2c/test2/input/apply 2015-07-09 14:50:05.609098682 +0200 -@@ -2,6 +2,7 @@ - f2([1,2,3,4]) - f3(sqr,[1,2,3,4]) - f4([1,2,3,4]) -+f5(-1) - - g1([1,2,3,4],2) - g2([1,2,3,4]) -diff -ru gp2c-0.0.9pl3/test2/res/apply.res gp2c/test2/res/apply.res ---- gp2c-0.0.9pl3/test2/res/apply.res 2013-06-11 12:02:48.000000000 +0200 -+++ gp2c/test2/res/apply.res 2015-07-09 14:50:05.610098694 +0200 -@@ -2,6 +2,7 @@ - [1, 4, 9, 16] - [1, 4, 9, 16] - [2, 5, 10, 17] -+[1, 1, 2, 6, 24] - [3, 4] - [2, 3] - [2, 3] diff --git a/build/pkgs/gp2c/patches/global.patch b/build/pkgs/gp2c/patches/global.patch deleted file mode 100644 index dd0c24d0472..00000000000 --- a/build/pkgs/gp2c/patches/global.patch +++ /dev/null @@ -1,126 +0,0 @@ -Patch taken from upstream to fix global() - -diff --git a/src/genblock.c b/src/genblock.c -index a3582d2..e626e0f 100644 ---- a/src/genblock.c -+++ b/src/genblock.c -@@ -251,7 +251,11 @@ int genblockdeclaration(int args, int n, int flag, int type, int *seq) - /*Make sure GEN objects are gerepilable.*/ - val = newnode(Ftag, newnode(Ftag, newsmall(0), Ggen), tv); - if (decl==global) -- fillvar(var,flag,tv,val); -+ { -+ int f=fillvar(var,flag,tv,-1); -+ if (autogc) -+ affectval(f,val,seq); -+ } - else - pushvar(var,flag,tv,val); - break; -@@ -270,10 +274,12 @@ int genblockdeclaration(int args, int n, int flag, int type, int *seq) - val=newcall("_const_quote",newstringnode(entryname(stack[i]),-1)); - affectval(fillvar(stack[i],flag,mint,-1),val,seq); - } -- else if (autogc) -+ else - { -+ int f = fillvar(stack[i],flag,mint,-1); - /*Make sure (implicitly GEN) objects are gerepilable.*/ -- fillvar(stack[i],flag,mint, newsmall(0)); -+ if (autogc) -+ affectval(f, newsmall(0), seq); - } - break; - case function: -diff --git a/src/topfunc.c b/src/topfunc.c -index 872830e..bff787f 100644 ---- a/src/topfunc.c -+++ b/src/topfunc.c -@@ -154,13 +154,12 @@ static int topfuncproto(int n, int fun, int pfun, int nf) - break; - case 'I': - case 'E': -- if (wr>=-1) -- { -- case 'J': -- seq = newnode(Flambda,var,newleaf(a)); -- tree[a] = tree[seq]; -- topfunclambda(a, n, fun, pfun, wr); -- } -+ if (wr<-1) -+ break; -+ case 'J': /* Fall through */ -+ seq = newnode(Flambda,var,newleaf(a)); -+ tree[a] = tree[seq]; -+ topfunclambda(a, n, fun, pfun, wr); - break; - } - break; -diff --git a/src/toplevel.c b/src/toplevel.c -index f5999c4..23c92a8 100644 ---- a/src/toplevel.c -+++ b/src/toplevel.c -@@ -120,7 +120,7 @@ void gentoplevel(int n) - } - else - gentoplevel(x); -- if (y>=0 && tree[y].f!=Fdeffunc) -+ if (y>=0 && tree[y].f!=Fdeffunc && tree[y].f!=Fseq) - { - int dy = detag(y); - if (isfunc(dy,"global")) -diff --git a/test/gp/initfunc.gp b/test/gp/initfunc.gp -index 03c1dd3..ed0b87d 100644 ---- a/test/gp/initfunc.gp -+++ b/test/gp/initfunc.gp -@@ -1,4 +1,4 @@ --global(y:var,n:small=0,m:small,k=2,L:vec,T2) -+global(y:var='y,n:small=0,m:small,k=2,L:vec,T2) - T=clone([4,3,2,2,1,1,1,1,0,0,0,0,0,0,0,0]) - T2=clone([4,3,2,2,1,1,1,1,0,0,0,0,0,0,0,0]) - L=[]~; -@@ -15,3 +15,10 @@ aff()=print(n," ",m); - M=[1,2;3,4] - print(x^4+k); - pow_M(k)=M^k -+global(x1:int,x2:int); -+global(y1,y2):int; -+global(z1,z2); -+f1(x)=my(z=x^2+1);if(z==1,x1=x,x2=x);z -+f2(x)=my(z=x^2+1);if(z==1,y1=x,y2=x);z -+f3(x)=my(z=x^2+1);if(z==1,z1=x,z2=x);z -+global(t1);t1=0 -diff --git a/test/input/initfunc b/test/input/initfunc -index 8506d08..46a126d 100644 ---- a/test/input/initfunc -+++ b/test/input/initfunc -@@ -2,3 +2,9 @@ init_initfunc(); print(pow_M(2)) - vector(100,i,mybfffo(i)) - vector(31,i,mybfffo(1<<(i-1))) - for(i=1,5,count();aff()) -+init_initfunc();f1(0) -+init_initfunc();f1(1) -+init_initfunc();f2(0) -+init_initfunc();f2(1) -+init_initfunc();f3(0) -+init_initfunc();f3(1) -diff --git a/test/res/initfunc.res b/test/res/initfunc.res -index 391d09d..a1e538c 100644 ---- a/test/res/initfunc.res -+++ b/test/res/initfunc.res -@@ -13,3 +13,15 @@ x^4 + 2 - 3 30 - 4 30 - 5 29 -+x^4 + 2 -+1 -+x^4 + 2 -+2 -+x^4 + 2 -+1 -+x^4 + 2 -+2 -+x^4 + 2 -+1 -+x^4 + 2 -+2 diff --git a/build/pkgs/meataxe/SPKG.txt b/build/pkgs/meataxe/SPKG.txt new file mode 100644 index 00000000000..9892003f3e2 --- /dev/null +++ b/build/pkgs/meataxe/SPKG.txt @@ -0,0 +1,27 @@ += MeatAxe = + +== Description == + +The MeatAxe is a set of programs for working with matrix representations +over finite fields. Permutation representations are supported to some +extent, too. + +The MeatAxe is developed for the UNIX operating system. Supported platforms +include Linux (x86), SunOS/Solaris (Sparc), HP/UX, DEC OSF/1 (Alpha), and +Windows NT 4.0 (x86, Alpha, PPC). + +== License == + +The C Meat-Axe is free software: you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation, either version 2 of the License, or (at your option) any later +version. + +== Upstream Contact == + +Michael Ringe (mringe@math.rwth-aachen.de) + +== Special Update/Build Instructions == + +The original upstream tarball was re-packaged, so that it unpacks into a single +folder called meataxe-2.4.24 diff --git a/build/pkgs/meataxe/checksums.ini b/build/pkgs/meataxe/checksums.ini new file mode 100644 index 00000000000..b4d4356ad4e --- /dev/null +++ b/build/pkgs/meataxe/checksums.ini @@ -0,0 +1,4 @@ +tarball=meataxe-VERSION.tar.gz +sha1=0aa4313cc430c78e058068feba805428ef2324aa +md5=e0f384e37a69671c73c2904e4e69dc01 +cksum=3083268116 diff --git a/build/pkgs/meataxe/dependencies b/build/pkgs/meataxe/dependencies new file mode 100644 index 00000000000..2f9f3849682 --- /dev/null +++ b/build/pkgs/meataxe/dependencies @@ -0,0 +1 @@ +# no dependencies diff --git a/build/pkgs/meataxe/package-version.txt b/build/pkgs/meataxe/package-version.txt new file mode 100644 index 00000000000..208b2a0070d --- /dev/null +++ b/build/pkgs/meataxe/package-version.txt @@ -0,0 +1 @@ +2.4.24.p1 diff --git a/build/pkgs/meataxe/patches/IO_fixes.patch b/build/pkgs/meataxe/patches/IO_fixes.patch new file mode 100644 index 00000000000..933908be6be --- /dev/null +++ b/build/pkgs/meataxe/patches/IO_fixes.patch @@ -0,0 +1,78 @@ +Read and create library files in the directory given by MtxLibDir. + +The patch keeps a promise given by upstream. + +AUTHOR: Simon King 2015-09-18, simon.king@uni-jena.de + +diff --git a/src/maketabF.c b/src/maketabF.c +index fa03eda..d7af83e 100644 +--- a/src/maketabF.c ++++ b/src/maketabF.c +@@ -319,7 +319,7 @@ static void writeheader() + int i, j; + + sprintf(filename,"p%3.3ld.zzz",Q); +- fd = SysFopen(filename,FM_CREATE); ++ fd = SysFopen(filename,FM_CREATE|FM_LIB); + if (fd == NULL) + { + perror(filename); +diff --git a/src/os.c b/src/os.c +index a7f4271..b07b971 100644 +--- a/src/os.c ++++ b/src/os.c +@@ -227,25 +227,31 @@ FILE *SysFopen(const char *name, int mode) + MTX_ERROR1("Invalid file mode %d",mode); + return NULL; + } +- f = fopen(name,fmodes[m]); +- if (f != NULL) +- return f; + + /* Search library directory + ------------------------ */ + if ((mode & FM_LIB) != 0) + { +- strcpy(buf,MtxLibDir); +- strcat(buf,"/"); +- strcat(buf,name); +- f = fopen(buf,fmodes[m]); ++ if (*MtxLibDir != 0) ++ { ++ strcpy(buf,MtxLibDir); ++ strcat(buf,"/"); ++ strcat(buf,name); ++ f = fopen(buf,fmodes[m]); ++ } ++ else ++ f = fopen(name,fmodes[m]); + } +- ++ else ++ { ++ f = fopen(name,fmodes[m]); ++ } ++ if (f != NULL) ++ return f; + /* Error handling + -------------- */ + if (f == NULL && (mode & FM_NOERROR) == 0) +- MTX_ERROR1("%s: %S",name); +- ++ MTX_ERROR1("%s: %S",name); + return f; + } + +diff --git a/src/zcv.c b/src/zcv.c +index a9ad7a3..763c9fb 100644 +--- a/src/zcv.c ++++ b/src/zcv.c +@@ -584,7 +584,7 @@ static int Init(int argc, const char **argv) + inpname = App->ArgV[0]; + if (strcmp(inpname,"-")) + { +- src = SysFopen(inpname,FM_READ|FM_TEXT|FM_LIB); ++ src = SysFopen(inpname,FM_READ|FM_TEXT); + if (src == NULL) + { + MTX_ERROR1("Cannot open %s",inpname); diff --git a/build/pkgs/meataxe/patches/StrassenWinogradImplementation.patch b/build/pkgs/meataxe/patches/StrassenWinogradImplementation.patch new file mode 100644 index 00000000000..43664cf6560 --- /dev/null +++ b/build/pkgs/meataxe/patches/StrassenWinogradImplementation.patch @@ -0,0 +1,1246 @@ +Implement Strassen-Winograd multiplication in MeatAxe. + +We use the schedule from Douglas-Heroux-Slishman-Smith; +see also Boyer-Pernet-Zhou, "Memory efficient scheduling of +Strassen-Winograd's matrix multiplication algorithm", +Table 1 (ISSAC 2009). + +AUTHOR: Simon King 2015-09-19, simon.king@uni-jena.de + +diff --git a/Makefile b/Makefile +index b78e244..2ada31e 100644 +--- a/Makefile ++++ b/Makefile +@@ -88,6 +88,7 @@ LIB_OBJS=\ + temap \ + tkinfo vec2mat \ + wgen \ ++ window \ + zcleanrow zcmprow zgap zpermrow \ + zzz2 \ + version +diff --git a/src/kernel-0.c b/src/kernel-0.c +index 4f0a973..178b6cb 100644 +--- a/src/kernel-0.c ++++ b/src/kernel-0.c +@@ -24,8 +24,8 @@ + MTX_DEFINE_FILE_INFO + + typedef unsigned char BYTE; +-static int MPB = 0; /* No. of marks per byte */ +-static int LPR = 0; /* Long ints per row */ ++int MPB = 0; /* No. of marks per byte */ ++int LPR = 0; /* Long ints per row */ + + + +@@ -646,7 +646,7 @@ PTR FfAddRow(PTR dest, PTR src) + + + /** +- ** Add a part two rows. ++ ** Add a part of two rows. + ** This works like FfAddRow(), but the operation is performed only on a given range of + ** columns. Note that the working range is not specified as column indexes but in units of + ** long integers! +@@ -707,7 +707,217 @@ PTR FfAddRowPartial(PTR dest, PTR src, int first, int len) + return dest; + } + ++/** ++ ** Subtract two rows. ++ ** This function subtracts src from dest. Field order and row size must have been set before. ++ ** @param dest The row to subtract from. ++ ** @param src The row to subtract. ++ ** @return Always returns dest. ++ **/ ++ ++PTR FfSubRow(PTR dest, PTR src) ++{ ++ register int i; ++ ++ if (FfChar == 2) /* characteristic 2 is simple... */ ++ { ++#ifdef ASM_MMX ++ /* This assumes Intel with 4 bytes per long, but MMX implies Intel anyway.*/ ++ __asm__( ++ " pushl %ebx\n" ++ " pushl %ecx\n" ++ " pushl %edx\n" + ++ " movl 8(%ebp),%ecx\n" ++ " movl 12(%ebp),%ebx\n" ++ " movl LPR,%edx\n" ++ " sarl $1,%edx\n" ++ " je .SUBROW2\n" ++ " .align 16\n" ++ ".SUBROW1:\n" ++ " movq (%ebx),%mm0\n" ++ " addl $8,%ebx\n" ++ " pxor (%ecx),%mm0\n" ++ " movq %mm0,(%ecx)\n" ++ " addl $8,%ecx\n" ++ " decl %edx\n" ++ " jne .SUBROW1\n" ++ ".SUBROW2:\n" ++ " popl %edx\n" ++ " popl %ecx\n" ++ " popl %ebx\n" ++ ); ++#else ++ register long *l1 = (long *) dest; ++ register long *l2 = (long *) src; ++ for (i = LPR; i != 0; --i) ++ { ++ register long x = *l2++; ++ if (x != 0) *l1 ^= x; ++ l1++; ++ } ++#endif ++ } ++ else /* any other characteristic */ ++ { ++ FEL *table_inv = mtx_tmult[mtx_taddinv[FF_ONE]]; ++#ifdef ASM_MMX ++ register BYTE *p1 = dest; ++ register unsigned long *p2 = (unsigned long *) src; ++ for (i = LPR; i != 0; --i) ++ { ++ register unsigned long a; ++ if ((a = *p2++) != 0) { ++ *p1++ = mtx_tadd[*p1][table_inv[a & 0xffL]]; ++ a >>= 8; ++ *p1++ = mtx_tadd[*p1][table_inv[a & 0xffL]]; ++ a >>= 8; ++ *p1++ = mtx_tadd[*p1][table_inv[a & 0xffL]]; ++ a >>= 8; ++ *p1++ = mtx_tadd[*p1][table_inv[a & 0xffL]]; ++ } else ++ p1 += 4; ++ } ++#else ++ register FEL *p1 = dest; ++ register FEL *p2 = src; ++ for (i = FfTrueRowSize(FfNoc); i != 0; --i) ++ { ++ register int x = *p2++; ++ if (x != 0) *p1 = mtx_tadd[*p1][table_inv[x]]; ++ p1++; ++ } ++#endif ++ } ++ return dest; ++} ++ ++ ++/** ++ ** Subtract a part of two rows. ++ ** This works like FfSubRow(), but the operation is performed only on a given range of ++ ** columns. Note that the working range is not specified as column indexes but in units of ++ ** long integers! ++ ** @param dest The row to subtract from. ++ ** @param src The row to subtract. ++ ** @param first Number of long integers to skip. ++ ** @param len Number of long integers to add. ++ ** @return Always returns dest. ++ **/ ++ ++PTR FfSubRowPartial(PTR dest, PTR src, int first, int len) ++{ ++ register long i; ++ ++ if (FfChar == 2) /* characteristic 2 is simple... */ ++#ifdef ASM_MMX ++ __asm__("\n movl 8(%ebp),%ecx\n" ++ " movl 12(%ebp),%ebx\n" ++ " movl 16(%ebp),%edx\n" ++ " sall $2,%edx\n" ++ " addl %edx,%ecx\n" ++ " addl %edx,%ebx\n" ++ " movl 20(%ebp),%edx\n" ++ " sarl $1,%edx\n" ++ " je .SUBROWPART_1\n" ++ " .align 16\n" ++ ".SUBROWPART_2:\n" ++ " movq (%ebx),%mm0\n" ++ " addl $8,%ebx\n" ++ " pxor (%ecx),%mm0\n" ++ " movq %mm0,(%ecx)\n" ++ " addl $8,%ecx\n" ++ " decl %edx\n" ++ " jne .SUBROWPART_2\n" ++ ".SUBROWPART_1:\n" ++ ); ++#else ++ { register long *l1 = (long *) dest + first; ++ register long *l2 = (long *) src + first; ++ for (i = len; i != 0; --i) ++ { ++ register long x = *l2++; ++ *l1 ^= x; ++ l1++; ++ } ++ } ++#endif ++ else /* any other characteristic */ ++ { FEL *table_inv = mtx_tmult[mtx_taddinv[FF_ONE]]; ++ register BYTE *p1 = dest + first * sizeof(long); ++ register BYTE *p2 = src + first * sizeof(long); ++ for (i = len*sizeof(long); i != 0; --i) ++ { ++ register int x = *p2++; ++ *p1 = mtx_tadd[*p1][table_inv[x]]; ++ p1++; ++ } ++ } ++ return dest; ++} ++ ++ ++/** ++ ** Subtract a part of two rows. ++ ** The difference to FfSubRowPartial is that dest is replaced ++ ** by src-dest, not by dest-src. ++ ** @param dest The row to subtract. ++ ** @param src The row to subtract from. ++ ** @param first Number of long integers to skip. ++ ** @param len Number of long integers to add. ++ ** @return Always returns dest. ++ **/ ++ ++PTR FfSubRowPartialReverse(PTR dest, PTR src, int first, int len) ++{ ++ register long i; ++ ++ if (FfChar == 2) /* characteristic 2 is simple... */ ++#ifdef ASM_MMX ++ __asm__("\n movl 8(%ebp),%ecx\n" ++ " movl 12(%ebp),%ebx\n" ++ " movl 16(%ebp),%edx\n" ++ " sall $2,%edx\n" ++ " addl %edx,%ecx\n" ++ " addl %edx,%ebx\n" ++ " movl 20(%ebp),%edx\n" ++ " sarl $1,%edx\n" ++ " je .SUBROWPART_1\n" ++ " .align 16\n" ++ ".SUBROWPART_2:\n" ++ " movq (%ebx),%mm0\n" ++ " addl $8,%ebx\n" ++ " pxor (%ecx),%mm0\n" ++ " movq %mm0,(%ecx)\n" ++ " addl $8,%ecx\n" ++ " decl %edx\n" ++ " jne .SUBROWPART_2\n" ++ ".SUBROWPART_1:\n" ++ ); ++#else ++ { register long *l1 = (long *) dest + first; ++ register long *l2 = (long *) src + first; ++ for (i = len; i != 0; --i) ++ { ++ register long x = *l2++; ++ *l1 ^= x; ++ l1++; ++ } ++ } ++#endif ++ else /* any other characteristic */ ++ { FEL *table_inv = mtx_tmult[mtx_taddinv[FF_ONE]]; ++ register BYTE *p1 = dest + first * sizeof(long); ++ register BYTE *p2 = src + first * sizeof(long); ++ for (i = len*sizeof(long); i != 0; --i) ++ { ++ register int x = *p2++; ++ *p1 = mtx_tadd[table_inv[*p1]][x]; ++ p1++; ++ } ++ } ++ return dest; ++} + + + /** +diff --git a/src/meataxe.h b/src/meataxe.h +index 819e88e..e2f5a84 100644 +--- a/src/meataxe.h ++++ b/src/meataxe.h +@@ -107,6 +107,9 @@ extern int FfChar; /**< Current characteristic */ + extern FEL FfGen; /**< Generator */ + extern int FfNoc; /**< Number of columns for row ops */ + extern size_t FfCurrentRowSize; ++extern int FfCurrentRowSizeIo; ++extern int MPB; /** No. of marks per byte */ ++extern int LPR; /** Long ints per row */ + + + /* Arithmetic */ +@@ -125,6 +128,9 @@ int FfSetNoc(int noc); + void FfAddMulRow(PTR dest, PTR src, FEL f); + PTR FfAddRow(PTR dest, PTR src); + PTR FfAddRowPartial(PTR dest, PTR src, int first, int len); ++PTR FfSubRow(PTR dest, PTR src); ++PTR FfSubRowPartial(PTR dest, PTR src, int first, int len); ++PTR FfSubRowPartialReverse(PTR dest, PTR src, int first, int len); + PTR FfAlloc(int nor); + int FfCmpRows(PTR p1, PTR p2); + void FfCleanRow(PTR row, PTR matrix, int nor, const int *piv); +@@ -519,6 +525,8 @@ int MatIsValid(const Matrix_t *m); + Matrix_t *MatLoad(const char *fn); + Matrix_t *MatMul(Matrix_t *dest, const Matrix_t *src); + Matrix_t *MatMulScalar(Matrix_t *dest, FEL coeff); ++Matrix_t *MatMulStrassen(Matrix_t *dest, const Matrix_t *A, const Matrix_t *B); ++void StrassenSetCutoff(size_t size); + long MatNullity(const Matrix_t *mat); + long MatNullity__(Matrix_t *mat); + Matrix_t *MatNullSpace(const Matrix_t *mat); +diff --git a/src/window.c b/src/window.c +new file mode 100644 +index 0000000..f374028 +--- /dev/null ++++ b/src/window.c +@@ -0,0 +1,944 @@ ++/* ========================== C MeatAxe ============================= ++ window.c - Matrix window operations and Strassen-Winograd multiplication ++ ++ (C) Copyright 2015 Simon King, Institut fuer Mathematik, ++ FSU Jena, Germany ++ This program is free software; see the file COPYING for details. ++ ================================================================== */ ++ ++#include ++#include ++#include ++#include "meataxe.h" ++ ++/* -------------------------------------------------------------------------- ++ Local data ++ -------------------------------------------------------------------------- */ ++ ++MTX_DEFINE_FILE_INFO ++ ++typedef unsigned char BYTE; ++ ++typedef struct ++{ ++ int Nor; /* #rows of the window */ ++ size_t RowSize; /* size of window rows in long integers */ ++ Matrix_t *Matrix; /* ambient matrix containing the window */ ++ PTR ULCorner; /* Pointer to the upper left window corner */ ++} ++ MatrixWindow_t; ++ ++size_t cutoff = sizeof(long)/2; ++ ++/** The divide-and-conquer approach is only done for ++ * matrices with at least "cutoff*MPB*sizeof(long)" rows which ++ * are formed by at least "cutoff" longs. ++ * ++ * The above rule means that the "critical matrices" are square. ++ **/ ++void StrassenSetCutoff(size_t size) ++{ if (size) ++ cutoff = size; ++ else ++ cutoff = sizeof(long)/2; ++} ++ ++/* ------------------------------------------------------------------ ++ ++ Allocation and deallocation of a matrix window ++ ++ ------------------------------------------------------------------ */ ++/** ++ * Note that the rowsize is given in long, not in byte. The reason is ++ * functions such as FfAddRowPartial or FfAddMapRowWindow internally ++ * operating on longs. By consequence, in the Strassen-Winograd ++ * multiplication algorithm, we have to divide our matrix rows ++ * into longs, not into bytes. ++ **/ ++ ++/* Allocation with initialisation */ ++/* Create an empty matrix that is identical with the window. */ ++/* fl is the field size, nor is the number of rows. rowsize is */ ++/* the size of a row in longs. */ ++MatrixWindow_t *WindowAlloc(int fl, int nor, size_t rowsize) ++{ ++ MatrixWindow_t *out; ++ out = ALLOC(MatrixWindow_t); ++ if (out == NULL) ++ { ++ MTX_ERROR1("%E",MTX_ERR_NOMEM); ++ return NULL; ++ } ++ FfSetField(fl); ++ out->Matrix = MatAlloc(fl, nor, rowsize*sizeof(long)*MPB); ++ if (out->Matrix == NULL) ++ { ++ free(out); ++ MTX_ERROR1("%E",MTX_ERR_NOMEM); ++ return NULL; ++ } ++ out->ULCorner = out->Matrix->Data; ++ out->Nor = nor; ++ out->RowSize = rowsize; ++ return out; ++} ++ ++/** WARNING: Only to be used if the surrounding matrix can be destroyed ++ Otherwise, just do free(m)! **/ ++void WindowFree(MatrixWindow_t *m) ++{ ++ if (m->Matrix != NULL) ++ { ++ MatFree(m->Matrix); ++ } ++ free(m); ++} ++ ++/* ------------------------------------------------------------------ ++ * Auxiliary / Debugging ++ ----------------------------------------------------------------- */ ++ ++void WindowShow(MatrixWindow_t *A) ++{ ++long i,j; ++PTR p = A->ULCorner; ++FfSetNoc(A->Matrix->Noc); ++for (i=A->Nor; i>0; i--, FfStepPtr(&p)) ++ { ++ for (j=0; j< (A->RowSize)*sizeof(long); j++) ++ printf("%3.3d ", (unsigned char)p[j]); ++ printf("\n"); ++ } ++} ++ ++/** ++ ** Overwrite the window by zeroes, but let the ++ ** rest of the ambient matrix untouched ++ **/ ++ ++void WindowClear(MatrixWindow_t *A) ++{ ++register long i; ++register size_t rowsize = A->RowSize*sizeof(long); ++PTR p = A->ULCorner; ++FfSetNoc(A->Matrix->Noc); ++for (i=A->Nor; i>0; i--, FfStepPtr(&p)) ++{ memset(p, FF_ZERO, rowsize); } ++} ++ ++/** ++ ** Multiply a vector by a matrix window. ++ ** This function multiplies the vector @em row from the right by the matrix window ++ ** @em mat and adds the result into @em result. ++ ** The number of columns in both @em mat and @em result is determined by @em rowsize. ++ ** @attention @em result and @em row must not overlap. Otherwise the result is ++ ** undefined. ++ ** @param row The source vector (nor columns). ++ ** @param matrix A matrix window (nor by (rowsize*sizeof(long)*MPB)) of a matrix whose rowsize is FfCurrRowSize. ++ ** @param nor number of rows in the matrix window. ++ ** @param[out] result The resulting vector ((rowsize*sizeof(long)*MPB) columns). ++ ** @param rowsize number of longs forming a row of @em mat. ++ **/ ++ ++void FfAddMapRowWindow(PTR row, PTR matrix, int nor, PTR result, size_t rowsize) ++ ++{ ++ register int i; ++ register FEL f; ++ BYTE *m = (BYTE *) matrix; ++ ++#ifdef DEBUG ++ if (result >= row && result < row + FfRowSize(nor)) ++ MTX_ERROR("row and result overlap: undefined result!"); ++ if (row >= result && row < result + (rowsize*sizeof(long))) ++ MTX_ERROR("row and result overlap: undefined result!"); ++#endif ++ ++ if (FfOrder == 2) /* GF(2) is a special case */ ++ { ++ register long *x1 = (long *) matrix; ++ register BYTE *r = (BYTE *) row; ++ ++ for (i = nor; i > 0; ++r) ++ { ++ register BYTE mask; ++ if (*r == 0) ++ { ++ i -= 8; ++ x1 += 8 * LPR; /* Skip 8 rows of the matrix window in the ambient matrix*/ ++ continue; ++ } ++ for (mask = 0x80; mask != 0 && i > 0; mask >>= 1, --i) ++ { ++ if ((mask & *r) == 0) ++ { ++ x1 += LPR; /* Skip a single row */ ++ continue; ++ } ++ ++#ifdef ASM_MMX ++__asm__(" pushl %ebx\n"); ++__asm__(" movl %0,%%ebx" : : "g" (x1) ); ++__asm__(" pushl %ecx\n" ++ " pushl %edx\n" ++ " movl 20(%ebp),%ecx\n" /* result */ ++ ); ++__asm__ ( ++ " movl 24(%ebp),%edx\n" /* this time, it is rowsize, not LPR */ ++ " sarl $1,%edx\n" ++ " je .FASTXOR_1\n" ++ " .align 16\n" ++ ".FASTXOR_2:\n" ++ " movq (%ebx),%mm0\n" ++ " addl $8,%ebx\n" ++ " pxor (%ecx),%mm0\n" ++ " movq %mm0,(%ecx)\n" ++ " addl $8,%ecx\n" ++ " decl %edx\n" ++ " jne .FASTXOR_2\n" ++ ".FASTXOR_1:\n" ++ " popl %edx\n" ++ " popl %ecx\n"); ++__asm__(" movl %%ebx,%0" : : "g" (x1) ); ++__asm__(" popl %ebx\n" ++ ); ++#else ++ { ++ register long *x2 = (long *)result; ++ register int k; ++ for (k = rowsize; k; --k) ++ *x2++ ^= *x1++; ++ /* Now, x1 points to the first item ++ * after the current line of the window. ++ * We need to move it to the first position ++ * of the next line of the window. ++ */ ++ x1 += (LPR-rowsize); ++ } ++#endif ++ } ++ } ++ } ++ else /* Any other field */ ++ { ++ register BYTE *brow = (BYTE *) row; ++ register int pos = 0; ++ size_t l_rowsize = rowsize*sizeof(long); ++ for (i = nor; i > 0; --i) ++ { ++ f = mtx_textract[pos][*brow]; ++ if (++pos == (int) MPB) ++ { ++ pos = 0; ++ ++brow; ++ } ++ if (f != FF_ZERO) ++ { ++ register BYTE *v = m; ++ register BYTE *r = result; ++ if (f == FF_ONE) ++ { ++ register size_t k = l_rowsize; ++ for (; k != 0; --k) ++ { ++ *r = mtx_tadd[*r][*v++]; ++ ++r; ++ } ++ } ++ else ++ { ++ register BYTE *multab = mtx_tmult[f]; ++ register size_t k = l_rowsize; ++ for (; k != 0; --k) ++ { ++ if (*v != 0) ++ *r = mtx_tadd[multab[*v]][*r]; ++ ++v; ++ ++r; ++ } ++ } ++ } ++ m += FfCurrentRowSize; /* next row of window in the ambient matrix */ ++ } ++ } ++} ++ ++/** dest := left+right ++ left and right must be distinct, but one of them may coincide with dest -- under the assumption ++ that, in that case, the ambient matrices coincide as well. **/ ++MatrixWindow_t *WindowSum(MatrixWindow_t *dest, MatrixWindow_t *left, MatrixWindow_t *right) ++{ ++ PTR x, result, tmp; ++ int i; ++ ++ int lnoc, rnoc, dnoc; ++ ++ FfSetField(left->Matrix->Field); ++ if (left->Matrix->Field != right->Matrix->Field || (left->Nor != right->Nor) || (left->RowSize != right->RowSize)) ++ { ++ MTX_ERROR1("Windows cannot be added: %E", MTX_ERR_INCOMPAT); ++ return NULL; ++ } ++ size_t rowsize = left->RowSize; ++ ++ lnoc = left->Matrix->Noc; ++ rnoc = right->Matrix->Noc; ++ dnoc = dest->Matrix->Noc; ++ /* We have to distinguish cases as to whether dest ++ is equal to either left or right */ ++ result = dest->ULCorner; ++ if (left->ULCorner == dest->ULCorner) ++ { /* we write into left */ ++ x = right->ULCorner; ++ for (i = left->Nor; i != 0; --i) ++ { ++ FfAddRowPartial(result, x, 0, rowsize); ++ FfSetNoc(dnoc); ++ FfStepPtr(&result); ++ FfSetNoc(rnoc); ++ FfStepPtr(&x); ++ } ++ } ++ else if (right->ULCorner == dest->ULCorner) ++ { /* we write into right */ ++ x = left->ULCorner; ++ for (i = left->Nor; i != 0; --i) ++ { ++ FfAddRowPartial(result, x, 0, rowsize); ++ FfSetNoc(dnoc); ++ FfStepPtr(&result); ++ FfSetNoc(lnoc); ++ FfStepPtr(&x); ++ } ++ } ++ else ++ { /* we need to copy left into dest first */ ++ x = right->ULCorner; ++ tmp = left->ULCorner; ++ size_t l_rowsize = rowsize * sizeof(long); ++ for (i = left->Nor; i != 0; --i) ++ { ++ memcpy(result, tmp, l_rowsize); ++ FfSetNoc(lnoc); ++ FfStepPtr(&tmp); ++ FfAddRowPartial(result, x, 0, rowsize); ++ FfSetNoc(dnoc); ++ FfStepPtr(&result); ++ FfSetNoc(rnoc); ++ FfStepPtr(&x); ++ } ++ } ++ return dest; ++} ++ ++/** dest := left-right ++ left and right must be distinct, but one of them may coincide with dest -- under the assumption ++ that, in that case, the ambient matrices coincide as well. ++**/ ++MatrixWindow_t *WindowDif(MatrixWindow_t *dest, MatrixWindow_t *left, MatrixWindow_t *right) ++{ ++ PTR x, result, tmp; ++ int i; ++ int lnoc, rnoc, dnoc; ++ ++ FfSetField(left->Matrix->Field); ++ if (left->Matrix->Field != right->Matrix->Field || (left->Nor != right->Nor) || (left->RowSize != right->RowSize)) ++ { ++ MTX_ERROR1("Windows cannot be subtracted: %E", MTX_ERR_INCOMPAT); ++ return NULL; ++ } ++ size_t rowsize = left->RowSize; ++ ++ lnoc = left->Matrix->Noc; ++ rnoc = right->Matrix->Noc; ++ dnoc = dest->Matrix->Noc; ++ /* We have to distinguish cases as to whether dest ++ is equal to either left or right */ ++ result = dest->ULCorner; ++ if (left->ULCorner == dest->ULCorner) ++ { /* we write into left */ ++ x = right->ULCorner; ++ for (i = left->Nor; i != 0; --i) ++ { ++ FfSubRowPartial(result, x, 0, rowsize); ++ FfSetNoc(dnoc); ++ FfStepPtr(&result); ++ FfSetNoc(rnoc); ++ FfStepPtr(&x); ++ } ++ } ++ else if (right->ULCorner == dest->ULCorner) ++ { /* we write into right */ ++ x = left->ULCorner; ++ for (i = left->Nor; i != 0; --i) ++ { ++ FfSubRowPartialReverse(result, x, 0, rowsize); ++ FfSetNoc(dnoc); ++ FfStepPtr(&result); ++ FfSetNoc(lnoc); ++ FfStepPtr(&x); ++ } ++ } ++ else ++ { /* we need to copy left into dest first */ ++ x = right->ULCorner; ++ tmp = left->ULCorner; ++ size_t l_rowsize = rowsize * sizeof(long); ++ for (i = left->Nor; i != 0; --i) ++ { ++ memcpy(result, tmp, l_rowsize); ++ FfSetNoc(lnoc); ++ FfStepPtr(&tmp); ++ FfSubRowPartial(result, x, 0, rowsize); ++ FfSetNoc(dnoc); ++ FfStepPtr(&result); ++ FfSetNoc(rnoc); ++ FfStepPtr(&x); ++ } ++ } ++ return dest; ++} ++ ++/** ++ Add left*right to dest. ++ ++ It is assumed that "dest->Matrix" is allocated (with the correct field and dimensions as well), so that we ++ can write the result into it. Moreover, the chunk of memory pointed at by dest MUST be disjoint ++ from the chunks for left and right! ++ ++ Dimensions are not tested! ++**/ ++MatrixWindow_t *WindowAddMul(MatrixWindow_t *dest, MatrixWindow_t *left, MatrixWindow_t *right) ++{ ++ PTR x, y, result; ++ long i; ++ ++ FfSetField(left->Matrix->Field); ++ x = left->ULCorner; ++ y = right->ULCorner; ++ result = dest->ULCorner; ++ ++ for (i = dest->Nor; i != 0; --i) ++ { ++ /* Set the noc of the surrounding matrix of the right factor, ++ which is assumed by zmaprow_window */ ++ FfSetNoc(right->Matrix->Noc); ++ FfAddMapRowWindow(x, y, right->Nor, result, right->RowSize); ++ /* We want to step to the next line of the left factor */ ++ FfSetNoc(left->Matrix->Noc); ++ FfStepPtr(&x); ++ /* We want to step to the next line of the result */ ++ FfSetNoc(dest->Matrix->Noc); ++ FfStepPtr(&result); ++ } ++ /* ++ dest->RowSize = right->RowSize; ++ dest->Nor = left->Nor; ++ */ ++ return dest; ++} ++ ++inline void MatrixToWindow (MatrixWindow_t *out, const Matrix_t *M, long nor, long rowsize, PTR p) ++/* presumably M will be freed separately. Hence, use free(...) to free ++ the result of this function ++*/ ++{ ++ out->Matrix = M; ++ out->Nor = nor; ++ out->RowSize = rowsize; ++ out->ULCorner = p; ++} ++ ++/** ++ ** Multiply matrix windows ++ ** This function multiplies @em A_win from the right by @em B_win and writes ++ ** the result into @em dest_win. ++ ** The matrix windows must be compatible for multiplication, i.e. they must be over ++ ** the same field, and the number of columns of @em A_win must be equal to the ++ ** number of rows of @em B_win. ++ ** Moreover, it is assumed that @em dest_win is allocated in the right dimensions. ++ ** Since parts of @em dest_win are used to store temporary results, it is essential ++ ** that @em dest_win initially is zero! ++ ** @param[out] dest_win Result. ++ ** @param A_win Left factor. ++ ** @param B_win Right factor ++ ** @return The function returns 0 on success and a nonzero value on error. ++ **/ ++ ++int StrassenStep(MatrixWindow_t *dest_win, MatrixWindow_t *A_win, MatrixWindow_t *B_win) ++{ ++ FfSetField(A_win->Matrix->Field); ++ int MPL = MPB*sizeof(long); ++ int full_nrow_cutoff = cutoff*MPL; ++ /* Determine the size of submatrices in divide-and-conquer */ ++ /** ++ * Note that the rowsize is given in the unit "long". ++ * Generally we have trailing padding empty bytes. We have to cut ++ * so that two full blocks fit into the non-padded area. This is what we do: ++ * - We halve the number of rows of A (rounded down). ++ * - We halve the rowsize of B (rounded down) , since padding doesn't matter here. ++ * - We determine how many FULL longs fit into a *row* (of A) of B->Nor items. ++ * Half of it (rounded down) gives the rowsize of A's submatrices. ++ * - From that rowsize, we obtain the corresponding number of rows of ++ * B's submatrices. ++ **/ ++ /* ++ printf("we start with A_win\n"); ++ WindowShow(A_win); ++ */ ++ int A_sub_nrows = A_win->Nor/2; ++ size_t B_sub_rowsize = B_win->RowSize/2; ++ size_t A_sub_rowsize = (B_win->Nor/MPL)/2; ++ int B_sub_nrows = A_sub_rowsize*MPL; ++ /*printf("A_sub_nrows %d\nA_subrowsize %d\nB_sub_nrows %d\nB_sub_rowsize %d\n", A_sub_nrows,A_sub_rowsize,B_sub_nrows,B_sub_rowsize);*/ ++ ++ /* If the submatrices were too small, we use school book multiplication */ ++ if ((A_sub_nrows < full_nrow_cutoff) || ++ (B_sub_nrows < full_nrow_cutoff) || ++ (A_sub_rowsize < cutoff) || ++ (B_sub_rowsize < cutoff)) ++ { ++ /* The ambient matrix of dest_win is supposed to be empty. Thus, we add rather than overwrite */ ++ /* printf("Classical for %d x %d and %d x %d\n", A_win->Nor, A_win->RowSize*MPB*sizeof(long), B_win->Nor, B_win->RowSize*MPB*sizeof(long));*/ ++ WindowAddMul(dest_win, A_win, B_win); ++ return 0; ++ } ++ /* printf("Strassen step for %d x %d and %d x %d\n", A_win->Nor, A_win->RowSize*MPB*sizeof(long), B_win->Nor, B_win->RowSize*MPB*sizeof(long));*/ ++ size_t B_sub_rowsize2 = B_sub_rowsize + B_sub_rowsize; ++ size_t A_sub_rowsize2 = A_sub_rowsize + A_sub_rowsize; ++ size_t B_sub_rowsize2b = B_sub_rowsize2*sizeof(long); /* size in byte */ ++ size_t A_sub_rowsize2b = A_sub_rowsize2*sizeof(long); ++ int B_sub_nrows2 = B_sub_nrows + B_sub_nrows; ++ int A_sub_nrows2 = A_sub_nrows + A_sub_nrows; ++ ++ Matrix_t *A, *B, *dest; ++ A = A_win->Matrix; ++ B = B_win->Matrix; ++ dest = dest_win->Matrix; ++ ++ /* Because of rounding, there are stripes on the right ++ * and the lower boundary that are not part of the ++ * clean divide-and-conquer algorithm. ++ * */ ++ int A_nrows_rem = A_win->Nor - A_sub_nrows2; ++ size_t A_rowsize_rem = A_win->RowSize - A_sub_rowsize2; ++ ++ int B_nrows_rem = B_win->Nor - B_sub_nrows2; ++ size_t B_rowsize_rem = B_win->RowSize - B_sub_rowsize2; ++ ++ /* ---------------------------------------------------- ++ * Allocate temporary space. ++ * We use a schedule introduced by Douglas-Heroux-Slishman-Smith ++ * (see also Boyer-Pernet-Zhou, "Memory efficient scheduling of ++ * Strassen-Winograd's matrix multiplication algorithm", Table 1). ++ ---------------------------------------------------- */ ++ ++ MatrixWindow_t *X, *Y; ++ if (A_sub_rowsize>B_sub_rowsize) ++ { ++ X = WindowAlloc(A->Field, A_sub_nrows, A_sub_rowsize); } ++ else ++ { ++ X = WindowAlloc(A->Field, A_sub_nrows, B_sub_rowsize); } ++ if (X == NULL) ++ { MTX_ERROR1("Error allocating a temporary window: %E",MTX_ERR_NOMEM); ++ return 1; ++ } ++ Y = WindowAlloc(A->Field, B_sub_nrows, B_sub_rowsize); ++ if (Y == NULL) ++ { ++ WindowFree(X); ++ MTX_ERROR1("Error allocating a temporary window: %E",MTX_ERR_NOMEM); ++ return 1; ++ } ++ ++ /* Define the sub-windows of A, B and dest */ ++ /* ++ printf("original windows\n"); ++ printf("A\n"); ++ WindowShow(A_win); ++ printf("B\n"); ++ WindowShow(B_win); ++ printf("dest\n"); ++ WindowShow(dest_win); ++ printf("scratch X\n"); ++ WindowShow(X); ++ printf("scratch Y\n"); ++ WindowShow(Y); ++ */ ++ FfSetNoc(A->Noc); ++ MatrixWindow_t A00[1], A01[1], A10[1], A11[1], B00[1], B01[1], B10[1], B11[1]; ++ MatrixWindow_t A_last_col[1], A_last_row[1]; ++ MatrixWindow_t B_last_col[1], B_last_row[1], B_bulk[1]; ++ MatrixWindow_t dest_last_col[1], dest_last_row[1], dest_bulk[1]; ++ MatrixToWindow(A00, A, A_sub_nrows, A_sub_rowsize, A_win->ULCorner); ++ MatrixToWindow(A01, A, A_sub_nrows, A_sub_rowsize, (PTR)((char*)(A_win->ULCorner)+A_sub_rowsize*sizeof(long))); ++ MatrixToWindow(A10, A, A_sub_nrows, A_sub_rowsize, FfGetPtr(A_win->ULCorner, A_sub_nrows)); ++ MatrixToWindow(A11, A, A_sub_nrows, A_sub_rowsize, ++ (PTR)((char*)(A_win->ULCorner)+(A_sub_nrows*FfCurrentRowSize+A_sub_rowsize*sizeof(long)))); ++ /* ++ printf("A00\n"); ++ WindowShow(A00); ++ printf("A01\n"); ++ WindowShow(A01); ++ printf("A10\n"); ++ WindowShow(A10); ++ printf("A11\n"); ++ WindowShow(A11); ++ */ ++ FfSetNoc(B->Noc); ++ MatrixToWindow(B00, B, B_sub_nrows, B_sub_rowsize, B_win->ULCorner); ++ MatrixToWindow(B01, B, B_sub_nrows, B_sub_rowsize, (PTR)((char*)(B_win->ULCorner)+B_sub_rowsize*sizeof(long))); ++ MatrixToWindow(B10, B, B_sub_nrows, B_sub_rowsize, FfGetPtr(B_win->ULCorner, B_sub_nrows)); ++ MatrixToWindow(B11, B, B_sub_nrows, B_sub_rowsize, ++ (PTR)((char*)(B_win->ULCorner)+(B_sub_nrows*FfCurrentRowSize+B_sub_rowsize*sizeof(long)))); ++ /* ++ printf("B00\n"); ++ WindowShow(B00); ++ printf("B01\n"); ++ WindowShow(B01); ++ printf("B10\n"); ++ WindowShow(B10); ++ printf("B11\n"); ++ WindowShow(B11); ++ */ ++ FfSetNoc(dest->Noc); // since we may multiply into X, the size is not necessarily the same as for B. ++ PTR dest00 = dest_win->ULCorner; ++ PTR dest01 = (PTR)((char*)(dest_win->ULCorner)+B_sub_rowsize*sizeof(long)); ++ PTR dest10 = FfGetPtr(dest_win->ULCorner,A_sub_nrows); ++ PTR dest11 = (PTR)((char*)(dest_win->ULCorner)+(A_sub_nrows*FfCurrentRowSize)+B_sub_rowsize*sizeof(long)); ++ ++ /* Matrix windows containing temporary results */ ++ MatrixWindow_t S0[1], S1[1], S2[1], S3[1], T0[1], T1[1], T2[1], T3[1], P0[1], P1[1], P2[1], P3[1], P4[1], P5[1], P6[1], U0[1], U1[1], U2[1], U3[1], U4[1], U5[1], U6[1]; ++ ++ /* 1. S2 = A00-A10 in X */ ++ S2->Nor = A_sub_nrows; ++ S2->RowSize = A_sub_rowsize; ++ S2->Matrix = X->Matrix; ++ S2->ULCorner = X->ULCorner; ++ WindowDif(S2, A00, A10); ++ /* ++ printf("1. S2 = A00-A10 in X\n"); ++ WindowShow(X); ++ printf("resp.\n"); ++ WindowShow(S2); ++ */ ++ ++ /* 2. T2 = B11-B01 in Y */ ++ T2->Nor = B_sub_nrows; ++ T2->RowSize = B_sub_rowsize; ++ T2->Matrix = Y->Matrix; ++ T2->ULCorner = Y->ULCorner; ++ WindowDif(T2, B11, B01); ++ /* ++ printf("2. T2 = B11-B01 in Y\n"); ++ WindowShow(Y); ++ */ ++ ++ /* 3. P6 = S2*T2 in dest10 */ ++ P6->Nor = A_sub_nrows; ++ P6->RowSize = B_sub_rowsize; ++ P6->Matrix = dest; ++ P6->ULCorner = dest10; ++ /* dest is supposed to be empty */ ++ if (StrassenStep(P6, S2, T2)) return 1; ++ /* ++ printf("3. P6 = S2*T2 in dest10\n"); ++ WindowShow(dest_win); ++ */ ++ ++ /* 4. S0 = A10+A11 in X */ ++ S0->Nor = A_sub_nrows; ++ S0->RowSize = A_sub_rowsize; ++ S0->Matrix = X->Matrix; ++ S0->ULCorner = X->ULCorner; ++ WindowSum(S0, A10, A11); ++ /* ++ printf("4. S0 = A10+A11 in X\n"); ++ WindowShow(X); ++ */ ++ ++ /* 5. T0 = B01-B00 in Y */ ++ T0->Nor = B_sub_nrows; ++ T0->RowSize = B_sub_rowsize; ++ T0->Matrix = Y->Matrix; ++ T0->ULCorner = Y->ULCorner; ++ WindowDif(T0, B01, B00); ++ /* ++ printf("5. T0 = B01-B00 in Y\n"); ++ WindowShow(Y); ++ */ ++ ++ /* 6. P4 = S0*T0 in dest11 */ ++ P4->Nor = A_sub_nrows; ++ P4->RowSize = B_sub_rowsize; ++ P4->Matrix = dest; ++ P4->ULCorner = dest11; ++ /* dest is supposed to be empty */ ++ if (StrassenStep(P4, S0, T0)) return 1; ++ /* ++ printf("6. P4 = S0*T0 in dest11\n"); ++ WindowShow(dest_win); ++ */ ++ ++ /* 7. S1 = S0-A00 in X */ ++ S1->Nor = A_sub_nrows; ++ S1->RowSize = A_sub_rowsize; ++ S1->Matrix = X->Matrix; ++ S1->ULCorner = X->ULCorner; ++ WindowDif(S1, S0, A00); ++ /* ++ printf("7. S1 = S0-A00 in X\n"); ++ WindowShow(X); ++ */ ++ ++ /* 8. T1 = B11-T0 in Y */ ++ T1->Nor = B_sub_nrows; ++ T1->RowSize = B_sub_rowsize; ++ T1->Matrix = Y->Matrix; ++ T1->ULCorner = Y->ULCorner; ++ WindowDif(T1, B11, T0); ++ /* ++ printf("8. T1 = B11-T0 in Y\n"); ++ WindowShow(Y); ++ */ ++ ++ /* 9. P5 = S1*T1 in dest01 */ ++ P5->Nor = A_sub_nrows; ++ P5->RowSize = B_sub_rowsize; ++ P5->Matrix = dest; ++ P5->ULCorner = dest01; ++ /* dest is supposed to be empty */ ++ if (StrassenStep(P5, S1, T1)) return 1; ++ /* ++ printf("9. P5 = S1*T1 in dest01\n"); ++ WindowShow(dest_win); ++ */ ++ ++ /*10. S3 = A01-S1 in X */ ++ S3->Nor = A_sub_nrows; ++ S3->RowSize = A_sub_rowsize; ++ S3->Matrix = X->Matrix; ++ S3->ULCorner = X->ULCorner; ++ WindowDif(S3, A01, S1); ++ /* ++ printf("10. S3 = A01-S1 in X\n"); ++ WindowShow(X); ++ */ ++ ++ /*11. P2 = S3*B11 in dest00 */ ++ P2->Nor = A_sub_nrows; ++ P2->RowSize = B_sub_rowsize; ++ P2->Matrix = dest; ++ P2->ULCorner = dest00; ++ /* That part of dest is still supposed to be empty */ ++ if (StrassenStep(P2, S3, B11)) return 1; ++ /* ++ printf("11. P2 = S3*B11 in dest00\n"); ++ WindowShow(dest_win); ++ */ ++ ++ /*12. P0 = A00*B00 in X */ ++ P0->Nor = A_sub_nrows; ++ P0->RowSize = B_sub_rowsize; ++ P0->Matrix = X->Matrix; ++ P0->ULCorner = X->ULCorner; ++ /* ++ This time, the matrix we write our product to may be non-empty. ++ Hence, we clear the destination first. ++ */ ++ WindowClear(P0); ++ if (StrassenStep(P0, A00, B00)) return 1; ++ /* ++ printf("12. P0 = A00*B00 in X\n"); ++ WindowShow(X); ++ */ ++ ++ /*13. U1 = P0+P5 in dest01 */ ++ U1->Nor = A_sub_nrows; ++ U1->RowSize = B_sub_rowsize; ++ U1->Matrix = dest; ++ U1->ULCorner = dest01; ++ WindowSum(U1, P0, P5); ++ /* ++ printf("13. U1 = P0+P5 in dest01\n"); ++ WindowShow(dest_win); ++ */ ++ ++ /*14. U2 = U1+P6 in dest10 */ ++ U2->Nor = A_sub_nrows; ++ U2->RowSize = B_sub_rowsize; ++ U2->Matrix = dest; ++ U2->ULCorner = dest10; ++ WindowSum(U2, U1, P6); ++ /* ++ printf("14. U2 = U1+P6 in dest10\n"); ++ WindowShow(dest_win); ++ */ ++ ++ /*15. U3 = U1+P4 in dest01 */ ++ U3->Nor = A_sub_nrows; ++ U3->RowSize = B_sub_rowsize; ++ U3->Matrix = dest; ++ U3->ULCorner = dest01; ++ WindowSum(U3, U1, P4); ++ /* ++ printf("15. U3 = U1+P4 in dest01\n"); ++ WindowShow(dest_win); ++ */ ++ ++ /*16. U6 = U2+P4 in dest11 (final) */ ++ U6->Nor = A_sub_nrows; ++ U6->RowSize = B_sub_rowsize; ++ U6->Matrix = dest; ++ U6->ULCorner = dest11; ++ WindowSum(U6, U2, P4); ++ /* ++ printf("16. U6 = U2+P4 in dest11 (final)\n"); ++ WindowShow(dest_win); ++ */ ++ ++ /*17. U4 = U3+P2 in dest01 (final) */ ++ U4->Nor = A_sub_nrows; ++ U4->RowSize = B_sub_rowsize; ++ U4->Matrix = dest; ++ U4->ULCorner = dest01; ++ WindowSum(U4, U3, P2); ++ /* ++ printf("17. U4 = U3+P2 in dest01 (final)\n"); ++ WindowShow(dest_win); ++ */ ++ ++ /*18. T3 = T1-B10 in Y */ ++ T3->Nor = B_sub_nrows; ++ T3->RowSize = B_sub_rowsize; ++ T3->Matrix = Y->Matrix; ++ T3->ULCorner = Y->ULCorner; ++ WindowDif(T3, T1, B10); ++ /* ++ printf("18. T3 = T1-B10 in Y\n"); ++ WindowShow(Y); ++ */ ++ ++ /*19. P3 = A11*T3 in dest00 */ ++ P3->Nor = A_sub_nrows; ++ P3->RowSize = B_sub_rowsize; ++ P3->Matrix = dest; ++ P3->ULCorner = dest00; ++ /* Meanwhile dest00 is non-empty. Hence, overwrite */ ++ WindowClear(P3); ++ if (StrassenStep(P3, A11, T3)) return 1; ++ /* ++ printf("19. P3 = A11*T3 in dest00\n"); ++ WindowShow(dest_win); ++ */ ++ ++ /*20. U5 = U2-P3 in dest10 (final) */ ++ U5->Nor = A_sub_nrows; ++ U5->RowSize = B_sub_rowsize; ++ U5->Matrix = dest; ++ U5->ULCorner = dest10; ++ WindowDif(U5, U2, P3); ++ /* ++ printf("20. U5 = U2-P3 in dest10 (final)\n"); ++ WindowShow(dest_win); ++ */ ++ ++ /*21. P1 = A01*B10 in dest00 */ ++ P1->Nor = A_sub_nrows; ++ P1->RowSize = B_sub_rowsize; ++ P1->Matrix = dest; ++ P1->ULCorner = dest00; ++ /* Again, we need to overwrite */ ++ WindowClear(P1); ++ if (StrassenStep(P1, A01, B10)) return 1; ++ /* ++ printf("21. P1 = A01*B10 in dest00\n"); ++ WindowShow(dest_win); ++ */ ++ ++ /*22. U0 = P0+P1 in dest00 (final) */ ++ U0->Nor = A_sub_nrows; ++ U0->RowSize = B_sub_rowsize; ++ U0->Matrix = dest; ++ U0->ULCorner = dest00; ++ WindowSum(U0, P0, P1); ++ /* ++ printf("22. U0 = P0+P1 in dest00 (final)\n"); ++ WindowShow(dest_win); ++ */ ++ WindowFree(X); ++ WindowFree(Y); ++ ++ /* --------------------------------------------------------- ++ Deal with the leftovers on the bottom and the right wing ++ --------------------------------------------------------- */ ++ ++ if (B_rowsize_rem) ++ { ++ MatrixToWindow(B_last_col, B, B_win->Nor, B_rowsize_rem, (PTR)((char*)(B_win->ULCorner) + B_sub_rowsize2b)); ++ MatrixToWindow(dest_last_col, dest, A_win->Nor, B_rowsize_rem, (PTR)((char*)(dest_win->ULCorner) + B_sub_rowsize2b)); ++ /* that part of dest is still supposed to be empty, so we can add the product */ ++ WindowAddMul(dest_last_col, A_win, B_last_col); ++ } ++ if (A_nrows_rem) ++ { ++ FfSetNoc(A->Noc); ++ MatrixToWindow(A_last_row, A, A_nrows_rem, A_win->RowSize, (PTR)((char*)(A_win->ULCorner) + (A_sub_nrows2*FfCurrentRowSize))); ++ if (B_rowsize_rem) /* We have already considered the lower right corner in the previous if-clause */ ++ { ++ MatrixToWindow(B_bulk, B, B_win->Nor, B_sub_rowsize2, B_win->ULCorner); ++ FfSetNoc(dest->Noc); ++ MatrixToWindow(dest_last_row, dest, A_nrows_rem, B_sub_rowsize2, (PTR)((char*)(dest_win->ULCorner) + (A_sub_nrows2*FfCurrentRowSize))); ++ /* that part of dest is still supposed to be empty, so we can add the product */ ++ WindowAddMul(dest_last_row, A_last_row, B_bulk); ++ } ++ else ++ { ++ FfSetNoc(dest->Noc); ++ MatrixToWindow(dest_last_row, dest, A_nrows_rem, B_win->RowSize, (PTR)((char*)(dest_win->ULCorner) + (A_sub_nrows2*FfCurrentRowSize))); ++ /* that part of dest is still supposed to be empty, so we can add the product */ ++ WindowAddMul(dest_last_row, A_last_row, B_win); ++ } ++ } ++ if (A_rowsize_rem) ++ { /* By the above operations, we don't need to consider the lower right corner of either A or B. */ ++ MatrixToWindow(A_last_col, A, A_sub_nrows2, A_rowsize_rem, (PTR)((char*)(A_win->ULCorner) + A_sub_rowsize2b)); ++ FfSetNoc(B->Noc); ++ MatrixToWindow(B_last_row, B, B_nrows_rem, B_sub_rowsize2, (PTR)((char*)(B_win->ULCorner) + (B_sub_nrows2*FfCurrentRowSize))); ++ FfSetNoc(dest->Noc); ++ MatrixToWindow(dest_bulk, dest, A_sub_nrows2, B_sub_rowsize2, dest_win->ULCorner); ++ /* now we are supposed to add the product to the result obtained so far */ ++ WindowAddMul(dest_bulk, A_last_col, B_last_row); ++ } ++ return 0; ++} ++ ++/** ++ ** Multiply matrices ++ ** This function multiplies @em A from the right by @em B and writes ++ ** the result into @em dest. ++ ** The matrices must be compatible for multiplication, i.e. they must be over ++ ** the same field, and the number of columns of @em A must be equal to the ++ ** number of rows of @em B. ++ ** Moreover, it is assumed that @em dest is allocated in the right dimensions. ++ ** Since parts of @em dest are used to store temporary results, it is essential ++ ** that @em dest initially is zero! ++ ** @param[out] dest Result. ++ ** @param A Left factor. ++ ** @param B Right factor ++ ** @return The function returns @em dest, or NULL on error. ++ **/ ++Matrix_t *MatMulStrassen(Matrix_t *dest, const Matrix_t *A, const Matrix_t *B) ++{ ++ FfSetField(A->Field); ++ MatrixWindow_t A_win[1], B_win[1], dest_win[1]; ++ FfSetNoc(A->Noc); ++ MatrixToWindow(A_win, A, A->Nor, LPR, A->Data); ++ FfSetNoc(B->Noc); ++ MatrixToWindow(B_win, B, B->Nor, LPR, B->Data); ++ FfSetNoc(dest->Noc); ++ MatrixToWindow(dest_win, dest, A->Nor, LPR, dest->Data); ++ if (StrassenStep(dest_win, A_win, B_win)) return NULL; ++ return dest; ++} diff --git a/build/pkgs/meataxe/patches/StrassenWinogradUsage.patch b/build/pkgs/meataxe/patches/StrassenWinogradUsage.patch new file mode 100644 index 00000000000..1959336c3fe --- /dev/null +++ b/build/pkgs/meataxe/patches/StrassenWinogradUsage.patch @@ -0,0 +1,359 @@ +Use Strassen-Winograd multiplication in some MeatAxe functions. + +AUTHOR: Simon King 2015-09-18, simon.king@uni-jena.de + +diff --git a/src/chbasis.c b/src/chbasis.c +index 1ea9c9f..34cf886 100644 +--- a/src/chbasis.c ++++ b/src/chbasis.c +@@ -9,6 +9,8 @@ + + + #include "meataxe.h" ++#include ++#include + + MTX_DEFINE_FILE_INFO + +@@ -36,9 +38,6 @@ MTX_DEFINE_FILE_INFO + int MrChangeBasis(MatRep_t *rep, const Matrix_t *trans) + + { +- Matrix_t *bi; +- int i; +- + /* Check arguments + --------------- */ + if (!MrIsValid(rep)) +@@ -46,11 +45,6 @@ int MrChangeBasis(MatRep_t *rep, const Matrix_t *trans) + MTX_ERROR1("rep: %E",MTX_ERR_BADARG); + return -1; + } +- if (!MatIsValid(trans)) +- { +- MTX_ERROR1("trans: %E",MTX_ERR_BADARG); +- return -1; +- } + if (rep->NGen <= 0) + return 0; + if (trans->Field != rep->Gen[0]->Field || +@@ -60,54 +54,50 @@ int MrChangeBasis(MatRep_t *rep, const Matrix_t *trans) + MTX_ERROR1("%E",MTX_ERR_INCOMPAT); + return -1; + } +- +- +- /* Basis transformation +- -------------------- */ +- if ((bi = MatInverse(trans)) == NULL) +- { +- MTX_ERROR("Basis transformation is singular"); +- return -1; +- } +- for (i = 0; i < rep->NGen; ++i) +- { +- Matrix_t *tmp = MatDup(trans); +- MatMul(tmp,rep->Gen[i]); +- MatMul(tmp,bi); +- MatFree(rep->Gen[i]); +- rep->Gen[i] = tmp; +- } +- MatFree(bi); +- return 0; ++ return ChangeBasis(trans, rep->NGen, (const Matrix_t **)(rep->Gen), rep->Gen); + } + + +- +-int ChangeBasisOLD(const Matrix_t *M, int ngen, const Matrix_t *gen[], ++/** Conjugate a list @em gen of @em ngen square matrices over the same ++ * field and of the same dimensions by a mattrix @em trans ++ * and write the result into @em newgen. If @em gen == @em newgen, then ++ * the previous content of @em newgen will be overridden. **/ ++int ChangeBasis(const Matrix_t *trans, int ngen, const Matrix_t *gen[], + Matrix_t *newgen[]) + + { +- Matrix_t *bi, *tmp; ++ Matrix_t *bi; + int i; + + MTX_VERIFY(ngen >= 0); +- if (!MatIsValid(M)) ++ if (!MatIsValid(trans)) ++ { ++ MTX_ERROR1("trans: %E",MTX_ERR_BADARG); + return -1; +- if ((bi = MatInverse(M)) == NULL) ++ } ++ ++ if ((bi = MatInverse(trans)) == NULL) + { +- MTX_ERROR("Matrix is singular"); ++ MTX_ERROR("Basis transformation is singular"); + return -1; + } ++ ++ Matrix_t *tmp = MatAlloc(trans->Field, trans->Nor, trans->Noc); ++ size_t tmpsize = FfCurrentRowSize*trans->Nor; + for (i = 0; i < ngen; ++i) + { +- tmp = MatDup(M); +- MatMul(tmp,gen[i]); +- MatMul(tmp,bi); +- if ((const Matrix_t **)newgen == gen) +- MatFree(newgen[i]); +- newgen[i] = tmp; ++ MTX_VERIFY(gen[i]->Nor==trans->Nor); ++ MTX_VERIFY(gen[i]->Noc==trans->Noc); ++ memset(tmp->Data, FF_ZERO, tmpsize); ++ MatMulStrassen(tmp, trans, gen[i]); ++ if ((const Matrix_t **)newgen == gen) ++ memset(newgen[i]->Data, FF_ZERO, tmpsize); ++ else ++ newgen[i] = MatAlloc(trans->Field, trans->Nor, trans->Noc); ++ MatMulStrassen(newgen[i], tmp, bi); + } + MatFree(bi); ++ MatFree(tmp); + return 0; + } + +diff --git a/src/chop.c b/src/chop.c +index 65a2a98..0f3f38f 100644 +--- a/src/chop.c ++++ b/src/chop.c +@@ -538,7 +538,7 @@ static int checkspl(const MatRep_t *rep, Matrix_t *nsp) + ------------------------------------------------------------ */ + sb1 = SpinUp(nsp,rep,SF_FIRST|SF_CYCLIC|SF_STD,NULL,NULL); + MTX_VERIFY(sb1 != NULL && sb1->Nor == sb1->Noc); +- ChangeBasisOLD(sb1,LI.NGen,(const Matrix_t **)rep->Gen,g1); ++ ChangeBasis(sb1,LI.NGen,(const Matrix_t **)rep->Gen,g1); + endo = MrAlloc(0,NULL,0); + + sb2 = NULL; /* Mark as unused */ +@@ -576,7 +576,7 @@ static int checkspl(const MatRep_t *rep, Matrix_t *nsp) + sb2 = SpinUp(v2,rep,SF_FIRST|SF_CYCLIC|SF_STD,NULL,NULL); + MTX_VERIFY(sb2 != NULL && sb2->Nor == sb2->Noc); + MatFree(v2); +- ChangeBasisOLD(sb2,rep->NGen,(const Matrix_t **)rep->Gen,g2); ++ ChangeBasis(sb2,rep->NGen,(const Matrix_t **)rep->Gen,g2); + + /* Compare the two representations. If they are different, + we know that the splitting field degree must be smaller +@@ -762,7 +762,7 @@ static void newirred(node_t *n) + LI.Cf[i].spl = n->spl = n->nsp->Nor; + b = SpinUp(n->nsp,n->Rep,SF_FIRST|SF_CYCLIC|SF_STD,NULL,NULL); + MTX_VERIFY(b != NULL && b->Nor == b->Noc); +- ChangeBasisOLD(b,LI.NGen,(const Matrix_t **)n->Rep->Gen,n->Rep->Gen); ++ ChangeBasis(b,LI.NGen,(const Matrix_t **)n->Rep->Gen,n->Rep->Gen); + MatFree(b); + + /* Write out the generators +diff --git a/src/homcomp.c b/src/homcomp.c +index a808089..351af2b 100644 +--- a/src/homcomp.c ++++ b/src/homcomp.c +@@ -112,10 +112,10 @@ Matrix_t *HomogeneousPart(MatRep_t *m, MatRep_t *s, Matrix_t *npw, + { + PTR matptr = MatGetPtr(A,j); + int u; +- a = MatDup(V[j]); +- b = MatDup(s->Gen[i]); +- MatMul(a,m->Gen[i]); /* the equations that describe */ +- MatMul(b,V[j]); /* that a vector in the null- */ ++ a = MatAlloc(V[j]->Field, V[j]->Nor, m->Gen[i]->Noc); ++ b = MatAlloc(s->Gen[i]->Field, s->Gen[i]->Nor, V[j]->Noc); ++ MatMulStrassen(a, V[j], m->Gen[i]); /* the equations that describe */ ++ MatMulStrassen(b,s->Gen[i], V[j]); /* that a vector in the null- */ + MatMulScalar(b,FfNeg(FF_ONE)); /* space is the first element */ + MatAdd(a, b); /* of a standard basis of a */ + /* module isomorphic to S */ +diff --git a/src/isisom.c b/src/isisom.c +index 790d2b0..e2b7f07 100644 +--- a/src/isisom.c ++++ b/src/isisom.c +@@ -9,7 +9,7 @@ + + + #include "meataxe.h" +- ++#include + + MTX_DEFINE_FILE_INFO + +@@ -114,7 +114,7 @@ int IsIsomorphic(const MatRep_t *rep1, const CfInfo *info1, + { + int j; + WgData_t *wg; +- Matrix_t *word, *m, *seed, *b, *bi; ++ Matrix_t *word, *m, *seed, *b, *g1, *g2; + int result; + + if (CheckArgs(rep1->NGen,rep1->Gen,info1,rep2->Gen,use_pw) != 0) +@@ -148,27 +148,35 @@ int IsIsomorphic(const MatRep_t *rep1, const CfInfo *info1, + MatFree(b); + return 0; + } +- bi = MatInverse(b); + + /* Compare generators + ------------------ */ ++ /** ++ * We test whether b*rep2_j*b^-1 == rep1_j ++ * by testing whether b*rep2_j == rep1_j*b ++ * */ ++ g1 = MatAlloc(b->Field, b->Nor, b->Noc); ++ g2 = MatAlloc(b->Field, b->Nor, b->Noc); ++ size_t memsize = FfCurrentRowSize*b->Nor; + for (j = 0, result = 0; result == 0 && j < rep2->NGen; ++j) + { +- Matrix_t *g = MatDup(b); +- MatMul(g,rep2->Gen[j]); +- MatMul(g,bi); +- if (MatCompare(g,rep1->Gen[j]) != 0) +- result = 1; +- MatFree(g); ++ MatMulStrassen(g2, b, rep2->Gen[j]); ++ MatMulStrassen(g1, rep1->Gen[j], b); ++ if (MatCompare(g1, g2) != 0) ++ { result = 1; ++ break; ++ } ++ memset(g1->Data, FF_ZERO, memsize); ++ memset(g2->Data, FF_ZERO, memsize); + } + + /* Clean up + -------- */ + if (trans != NULL && result == 0) +- *trans = b; ++ *trans = b; + else +- MatFree(b); +- MatFree(bi); +- ++ MatFree(b); ++ MatFree(g1); ++ MatFree(g2); + return (result == 0); + } +diff --git a/src/meataxe.h b/src/meataxe.h +index e2f5a84..5123f1c 100644 +--- a/src/meataxe.h ++++ b/src/meataxe.h +@@ -1096,11 +1096,7 @@ int LdFree(LdLattice_t *l); + int LdAddIncidence(LdLattice_t *lat, int sub, int sup); + int LdSetPositions(LdLattice_t *l); + +- +- +- +-/* OLD STUFF */ +-int ChangeBasisOLD(const Matrix_t *M, int ngen, const Matrix_t *gen[], ++int ChangeBasis(const Matrix_t *M, int ngen, const Matrix_t *gen[], + Matrix_t *newgen[]); + + +diff --git a/src/mktree.c b/src/mktree.c +index ede7881..3e99489 100644 +--- a/src/mktree.c ++++ b/src/mktree.c +@@ -213,8 +213,8 @@ static int MakeTree() + { + /* Calculate next element + ---------------------- */ +- Matrix_t *newelem = MatDup(Elms[src].Matrix); +- MatMul(newelem,Rep->Gen[g]); ++ Matrix_t *newelem = MatAlloc(Elms[src].Matrix->Field, Elms[src].Matrix->Nor, Rep->Gen[g]->Noc); ++ MatMulStrassen(newelem, Elms[src].Matrix, Rep->Gen[g]); + + /* If it is new, add to tree, else discard + --------------------------------------- */ +diff --git a/src/precond.c b/src/precond.c +index f144716..efc2f3d 100644 +--- a/src/precond.c ++++ b/src/precond.c +@@ -391,8 +391,8 @@ static void MakePQ(int n, int mj, int nj) + for (k = 0; k < spl; ++k) + { + FEL f; +- Matrix_t *x = MatDup(endo[i]); +- MatMul(x,endo[k]); ++ Matrix_t *x = MatAlloc(endo[i]->Field, endo[i]->Nor, endo[k]->Noc); ++ MatMulStrassen(x,endo[i],endo[k]); + f = MatTrace(x); + FfInsert(pptr,k,f); + MatFree(x); +diff --git a/src/pseudochop.c b/src/pseudochop.c +index 68cadae..3f1fa97 100644 +--- a/src/pseudochop.c ++++ b/src/pseudochop.c +@@ -105,8 +105,8 @@ int main(int argc, const char *argv[]) + { + Matrix_t *newmat; + oldnul = newnul; +- newmat = MatDup(old); +- MatMul(newmat,old); ++ newmat = MatAlloc(old->Field, old->Nor, old->Noc); ++ MatMulStrassen(newmat, old, old); + MatFree(old); + MatFree(nulsp); + old = MatDup(newmat); +diff --git a/src/pwkond.c b/src/pwkond.c +index c14c20e..5eaa5de 100644 +--- a/src/pwkond.c ++++ b/src/pwkond.c +@@ -309,8 +309,8 @@ static void gkond(const Lat_Info *li, int i, Matrix_t *b, Matrix_t *k, + char fn[LAT_MAXBASENAME+10]; + Matrix_t *x1, *x2; + +- x1 = MatDup(k); +- MatMul(x1,w); ++ x1 = MatAlloc(k->Field, k->Nor, w->Noc); ++ MatMulStrassen(x1, k, w); + x2 = QProjection(b,x1); + sprintf(fn,"%s%s.%s",li->BaseName,Lat_CfName(li,i),name); + MatSave(x2,fn); +@@ -340,7 +340,7 @@ static void Standardize(int cf) + MESSAGE(0,(" Transforming to standard basis\n")); + sb = SpinUp(CfList[cf].PWNullSpace,CfList[cf].Gen, + SF_FIRST|SF_CYCLIC|SF_STD,&script,NULL); +- ChangeBasisOLD(sb,CfList[cf].Gen->NGen, ++ ChangeBasis(sb,CfList[cf].Gen->NGen, + (const Matrix_t **)CfList[cf].Gen->Gen,std); + MatFree(sb); + +@@ -782,7 +782,7 @@ static int try2(long w, FEL f) + MESSAGE(3,("failed\n")); + return -1; /* Nullity should be 0 */ + } +- nul = MatNullity__(MatMul(MatDup(word),word)); ++ nul = MatNullity__(MatMulStrassen(MatAlloc(word->Field, word->Nor, word->Noc), word, word)); + if (nul != CfList[i].Info->spl) + { + MatFree(word); +@@ -915,7 +915,7 @@ static int try_p(long w) + /* Check if the nullity is stable + ------------------------------ */ + wp = MatInsert(word,mp->Factor[k]); +- wp2 = MatMul(MatDup(wp),wp); ++ wp2 = MatMulStrassen(MatAlloc(wp->Field, wp->Nor, wp->Noc), wp, wp); + MatFree(wp); + nul = MatNullity__(wp2); + if (nul != CfList[i].Info->spl) +diff --git a/src/soc.c b/src/soc.c +index 789a02b..199a2e0 100644 +--- a/src/soc.c ++++ b/src/soc.c +@@ -294,8 +294,8 @@ static int NextLayer() + Matrix_t *mat, *stgen; + + mat = MatCutRows(basis,basis->Nor - Dimension,Dimension); +- stgen = MatDup(bas); +- MatMul(stgen, mat); ++ stgen = MatAlloc(bas->Field, bas->Nor, mat->Noc); ++ MatMulStrassen(stgen, bas, mat); + MatCopyRegion(basis,basis->Nor - Dimension,0,stgen,0,0,Dimension,-1); + MatFree(mat); + MatFree(stgen); diff --git a/build/pkgs/meataxe/patches/TweakEchelon.patch b/build/pkgs/meataxe/patches/TweakEchelon.patch new file mode 100644 index 00000000000..eeee5e4d8f6 --- /dev/null +++ b/build/pkgs/meataxe/patches/TweakEchelon.patch @@ -0,0 +1,229 @@ +Improve echelon computation by restricting FfAddMulRow to the +nonzero part of the to-be-added row. + +Also remove some compiler warnings. + +AUTHOR: + +- Simon King, 2015-09-22 +diff --git a/src/c-kernel.c b/src/c-kernel.c +index f74e97e..d4355bc 100644 +--- a/src/c-kernel.c ++++ b/src/c-kernel.c +@@ -311,13 +311,10 @@ void TestFelToInt(unsigned flags) + static void TestSubfield1(int fld, int sub) + + { +- FEL tabfld[256], tabsub[256]; ++ FEL tabsub[256]; + FEL tabemb[256]; + int i; + +- FfSetField(fld); +- for (i = 0; i < fld; ++i) +- tabfld[i] = FfFromInt(i); + FfSetField(sub); + for (i = 0; i < sub; ++i) + tabsub[i] = FfFromInt(i); +diff --git a/src/cfcomp.c b/src/cfcomp.c +index 7434549..fa739d6 100644 +--- a/src/cfcomp.c ++++ b/src/cfcomp.c +@@ -131,7 +131,7 @@ static void Compare(const char *name) + { + ReadGens(name); + FindEquiv(name); +- FreeGens(name); ++ FreeGens(); + } + + +diff --git a/src/kernel-0.c b/src/kernel-0.c +index 178b6cb..6ef2f72 100644 +--- a/src/kernel-0.c ++++ b/src/kernel-0.c +@@ -919,6 +919,54 @@ PTR FfSubRowPartialReverse(PTR dest, PTR src, int first, int len) + return dest; + } + ++/** ++ ** Add a multiple of a part of a row. ++ ** This function adds a multiple of @em src to @em dest. ++ ** This works like FfAddRow(), but the operation is performed only on a given range of ++ ** columns. ++ ** @param dest The row to add to. ++ ** @param src The row to add. ++ ** @param first Number of bytes to skip. ++ ** @param len Number of bytes to add. ++**/ ++/* Warning!! Let L be the long integer to which the first byte of the a row ++ * belongs. It is assumed that all previous bytes in L are zero! ++ * Moreover, it is assumed that either the part of the rows ends at the ++ * end of the row, or that it ends with a full long. ++ */ ++void FfAddMulRowPartial(PTR dest, PTR src, FEL f, int first, int len) ++{ ++ register int i; ++ register BYTE *p1, *p2, *multab; ++ ++ CHECKFEL(f); ++ if (f == FF_ZERO) ++ return; ++ int lfirst; ++ if (f == FF_ONE) ++ { ++ lfirst = first/sizeof(long); ++ if (first+len>=FfCurrentRowSizeIo) ++ { ++ FfAddRowPartial(dest,src,lfirst,FfCurrentRowSize/sizeof(long)-lfirst); ++ return; ++ } ++ FfAddRowPartial(dest,src,lfirst,(first+len)/sizeof(long)-lfirst); ++ return; ++ } ++ multab = mtx_tmult[f]; ++ p1 = dest + first; ++ p2 = src + first; ++ int rem = FfCurrentRowSizeIo - first; ++ if (rem > len) rem = len; ++ for (i = rem; i != 0; --i) ++ { ++ register BYTE x = *p2++; ++ if (x!=0) ++ *p1 = mtx_tadd[*p1][multab[x]]; ++ ++p1; ++ } ++} + + /** + ** Multiply a row by a coefficient. +@@ -977,10 +1025,12 @@ void FfAddMulRow(PTR dest, PTR src, FEL f) + multab = mtx_tmult[f]; + p1 = dest; + p2 = src; +- for (i = FfTrueRowSize(FfNoc); i != 0; --i) ++ for (i = FfCurrentRowSizeIo; i != 0; --i) + { +- *p1 = mtx_tadd[*p1][multab[*p2++]]; +- ++p1; ++ register BYTE x = *p2++; ++ if (x!=0) ++ *p1 = mtx_tadd[*p1][multab[x]]; ++ ++p1; + } + } + +@@ -1131,7 +1181,9 @@ __asm__(" popl %ebx\n" + { + for (; k != 0; --k) + { +- *r = mtx_tadd[*r][*v++]; ++ register BYTE x = *v++; ++ if (x!=0) ++ *r = mtx_tadd[*r][x]; + ++r; + } + } +@@ -1140,9 +1192,9 @@ __asm__(" popl %ebx\n" + register BYTE *multab = mtx_tmult[f]; + for (; k != 0; --k) + { +- if (*v != 0) +- *r = mtx_tadd[multab[*v]][*r]; +- ++v; ++ if (*v != 0) ++ *r = mtx_tadd[multab[*v]][*r]; ++ ++v; + ++r; + } + } +diff --git a/src/matcopy.c b/src/matcopy.c +index 75b29c0..457dfeb 100644 +--- a/src/matcopy.c ++++ b/src/matcopy.c +@@ -57,7 +57,10 @@ int MatCopyRegion(Matrix_t *dest, int destrow, int destcol, + if (!MatIsValid(src) || !MatIsValid(dest)) + return -1; + if (src->Field != dest->Field) +- return MTX_ERROR1("%E",MTX_ERR_INCOMPAT), -1; ++ { ++ MTX_ERROR1("%E",MTX_ERR_INCOMPAT); ++ return -1; ++ } + if (nrows == -1) + nrows = src->Nor - row1; + if (ncols == -1) +diff --git a/src/meataxe.h b/src/meataxe.h +index 5123f1c..368b37b 100644 +--- a/src/meataxe.h ++++ b/src/meataxe.h +@@ -126,6 +126,7 @@ int FfSetNoc(int noc); + + + void FfAddMulRow(PTR dest, PTR src, FEL f); ++void FfAddMulRowPartial(PTR dest, PTR src, FEL f, int first, int len); + PTR FfAddRow(PTR dest, PTR src); + PTR FfAddRowPartial(PTR dest, PTR src, int first, int len); + PTR FfSubRow(PTR dest, PTR src); +diff --git a/src/window.c b/src/window.c +index f374028..9c87694 100644 +--- a/src/window.c ++++ b/src/window.c +@@ -236,12 +236,14 @@ __asm__(" popl %ebx\n" + { + register BYTE *v = m; + register BYTE *r = result; ++ register BYTE x; + if (f == FF_ONE) + { + register size_t k = l_rowsize; + for (; k != 0; --k) + { +- *r = mtx_tadd[*r][*v++]; ++ x=*v++; ++ if (x) *r = mtx_tadd[*r][x]; + ++r; + } + } +@@ -251,9 +253,8 @@ __asm__(" popl %ebx\n" + register size_t k = l_rowsize; + for (; k != 0; --k) + { +- if (*v != 0) +- *r = mtx_tadd[multab[*v]][*r]; +- ++v; ++ x=*v++; ++ if (x) *r = mtx_tadd[multab[x]][*r]; + ++r; + } + } +diff --git a/src/zcleanrow.c b/src/zcleanrow.c +index 649e551..b4dcb30 100644 +--- a/src/zcleanrow.c ++++ b/src/zcleanrow.c +@@ -35,18 +35,21 @@ MTX_DEFINE_FILE_INFO + + void FfCleanRow(PTR row, PTR matrix, int nor, const int *piv) + { +- int i; ++ register int i, pivi, first; + PTR x; + + for (i=0, x=matrix; i < nor; ++i, FfStepPtr(&x)) + { +- FEL f = FfExtract(row,piv[i]); ++ pivi = piv[i]; ++ FEL f = FfExtract(row,pivi); + if (f != FF_ZERO) +- FfAddMulRow(row,x,FfNeg(FfDiv(f,FfExtract(x,piv[i])))); ++ { ++ first = pivi/MPB; ++ FfAddMulRowPartial(row,x,FfNeg(FfDiv(f,FfExtract(x,pivi))),first,FfCurrentRowSizeIo-first); ++ } + } + } + +- + /** + ** Clean Row and Record Operations. + ** This function works like FfCleanRow(), but it stores a record of the operations performed diff --git a/build/pkgs/meataxe/patches/UseErrorPropagation.patch b/build/pkgs/meataxe/patches/UseErrorPropagation.patch new file mode 100644 index 00000000000..00745e8b967 --- /dev/null +++ b/build/pkgs/meataxe/patches/UseErrorPropagation.patch @@ -0,0 +1,1147 @@ +In functions that appear in matrix arithmetic, use specific return values +on error, and propagate errors. This is *not* done in other parts of +MeatAxe (e.g., not for greased matrices or polynomials) and not for +standalone programs. + +AUTHOR: + +- Simon King, 2015-09-26 + +diff --git a/src/cfinfo.c b/src/cfinfo.c +index 293526b..9c1a004 100644 +--- a/src/cfinfo.c ++++ b/src/cfinfo.c +@@ -215,7 +215,7 @@ int Lat_ReadInfo(Lat_Info *li, const char *basename) + } + for (i = 0; i < li->NCf; ++i) + { +- ReadWord(f,&(li->Cf[i].idword),&(li->Cf[i].idpol),fn); ++ if (!ReadWord(f,&(li->Cf[i].idword),&(li->Cf[i].idpol),fn)) return -1; + if (StfMatch(f,i < li->NCf - 1 ? "," : "];") != 0) + { + MTX_ERROR2("%s: %E",fn,MTX_ERR_FILEFMT); +@@ -232,7 +232,7 @@ int Lat_ReadInfo(Lat_Info *li, const char *basename) + } + for (i = 0; i < li->NCf; ++i) + { +- ReadWord(f,&(li->Cf[i].peakword),&(li->Cf[i].peakpol),fn); ++ if (!ReadWord(f,&(li->Cf[i].peakword),&(li->Cf[i].peakpol),fn)) return -1; + if (StfMatch(f,i < li->NCf - 1 ? "," : "];") != 0) + { + MTX_ERROR2("%s: %E",fn,MTX_ERR_FILEFMT); +diff --git a/src/chbasis.c b/src/chbasis.c +index 34cf886..f1ee2e8 100644 +--- a/src/chbasis.c ++++ b/src/chbasis.c +@@ -61,7 +61,8 @@ int MrChangeBasis(MatRep_t *rep, const Matrix_t *trans) + /** Conjugate a list @em gen of @em ngen square matrices over the same + * field and of the same dimensions by a mattrix @em trans + * and write the result into @em newgen. If @em gen == @em newgen, then +- * the previous content of @em newgen will be overridden. **/ ++ * the previous content of @em newgen will be overridden. ++ * Return -1 on error and 0 on success. **/ + int ChangeBasis(const Matrix_t *trans, int ngen, const Matrix_t *gen[], + Matrix_t *newgen[]) + +@@ -83,18 +84,36 @@ int ChangeBasis(const Matrix_t *trans, int ngen, const Matrix_t *gen[], + } + + Matrix_t *tmp = MatAlloc(trans->Field, trans->Nor, trans->Noc); ++ if (!tmp) return -1; + size_t tmpsize = FfCurrentRowSize*trans->Nor; + for (i = 0; i < ngen; ++i) + { + MTX_VERIFY(gen[i]->Nor==trans->Nor); + MTX_VERIFY(gen[i]->Noc==trans->Noc); + memset(tmp->Data, FF_ZERO, tmpsize); +- MatMulStrassen(tmp, trans, gen[i]); ++ if (!MatMulStrassen(tmp, trans, gen[i])) ++ { ++ MatFree(tmp); ++ return -1; ++ } + if ((const Matrix_t **)newgen == gen) + memset(newgen[i]->Data, FF_ZERO, tmpsize); + else ++ { + newgen[i] = MatAlloc(trans->Field, trans->Nor, trans->Noc); +- MatMulStrassen(newgen[i], tmp, bi); ++ if (!newgen[i]) ++ { ++ MatFree(tmp); ++ MatFree(bi); ++ return -1; ++ } ++ } ++ if (!MatMulStrassen(newgen[i], tmp, bi)) ++ { ++ MatFree(tmp); ++ MatFree(bi); ++ return -1; ++ } + } + MatFree(bi); + MatFree(tmp); +diff --git a/src/ffio.c b/src/ffio.c +index 92f9360..d2e3f1c 100644 +--- a/src/ffio.c ++++ b/src/ffio.c +@@ -71,8 +71,11 @@ int FfReadRows(FILE *f, PTR buf, int n) + if (fread(b,FfTrueRowSize(FfNoc),1,f) != 1) break; + b += FfCurrentRowSize; + } +- if (ferror(f)) +- MTX_ERROR("Read failed: %S"); ++ if (ferror(f)) ++ { ++ MTX_ERROR("Read failed: %S"); ++ return -1; ++ } + return i; + } + +@@ -106,8 +109,11 @@ int FfWriteRows(FILE *f, PTR buf, int n) + if (fwrite(b,FfTrueRowSize(FfNoc),1,f) != 1) break; + b += FfCurrentRowSize; + } +- if (ferror(f)) +- MTX_ERROR("Write failed: %S"); ++ if (ferror(f)) ++ { ++ MTX_ERROR("Write failed: %S"); ++ return -1; ++ } + return i; + } + +diff --git a/src/kernel-0.c b/src/kernel-0.c +index 6ef2f72..431f01a 100644 +--- a/src/kernel-0.c ++++ b/src/kernel-0.c +@@ -304,7 +304,10 @@ static FILE *OpenTableFile(int fl) + /* Create the table file. + ---------------------- */ + if (FfMakeTables(fl) != 0) +- MTX_ERROR("Unable to build arithmetic tables"); ++ { ++ MTX_ERROR("Unable to build arithmetic tables"); ++ return NULL; ++ } + fd = SysFopen(fn,FM_READ|FM_LIB); + return fd; + } +@@ -363,8 +366,7 @@ static int ReadTableFile(FILE *fd, int field) + return -1; + } + FfOrder = field; +- FfSetNoc(FfOrder); +- return 0; ++ return FfSetNoc(FfOrder); + } + + +@@ -471,7 +473,7 @@ size_t FfTrueRowSize(int noc) + ** Embed a subfield. + ** @param a Element of the subfield field. + ** @param subfield Subfield order. Must be a divisor of the current field order. +- ** @return @em a, embedded into the current field. ++ ** @return @em a, embedded into the current field, or 255 on error. + **/ + + FEL FfEmbed(FEL a, int subfield) +@@ -482,7 +484,9 @@ FEL FfEmbed(FEL a, int subfield) + return a; + for (i = 0; mtx_embedord[i] != subfield && i < 4; ++i); + if (i >= 4) +- MTX_ERROR2("Cannot embed GF(%d) into GF(%d)",(int)subfield,(int)FfOrder); ++ { MTX_ERROR2("Cannot embed GF(%d) into GF(%d)",(int)subfield,(int)FfOrder); ++ return (FEL)255; ++ } + return mtx_embed[i][a]; + } + +@@ -498,6 +502,7 @@ FEL FfEmbed(FEL a, int subfield) + ** FfSetField(subfield). + ** @param a Element of the current field. + ** @param subfield Subfield order. Must be a divisor of the current field order. ++ ** Return 255 on error. + **/ + + FEL FfRestrict(FEL a, int subfield) +@@ -511,6 +516,7 @@ FEL FfRestrict(FEL a, int subfield) + { + MTX_ERROR2("Cannot restrict GF(%d) to GF(%d)",(int)FfOrder, + (int)subfield); ++ return (FEL)255; + } + return mtx_restrict[i][a]; + } +diff --git a/src/maddmul.c b/src/maddmul.c +index f5c171d..24ad3a5 100644 +--- a/src/maddmul.c ++++ b/src/maddmul.c +@@ -59,7 +59,7 @@ Matrix_t *MatAddMul(Matrix_t *dest, const Matrix_t *src, FEL coeff) + ------------ */ + PTR dp = dest->Data, sp = src->Data; + int n; +- FfSetField(src->Field); ++ FfSetField(src->Field); /* No error checking */ + FfSetNoc(src->Noc); + for (n = src->Nor; n > 0; --n) + { +diff --git a/src/maketabF.c b/src/maketabF.c +index d7af83e..0fa26fb 100644 +--- a/src/maketabF.c ++++ b/src/maketabF.c +@@ -175,7 +175,7 @@ static void polymod(POLY a, POLY b) + testprim() - Test for primitivity. + ----------------------------------------------------------------- */ + +-static void testprim() ++static int testprim() + { + int i, a[256]; + +@@ -187,7 +187,9 @@ static void testprim() + { + fprintf(stderr,"*** a[%d]=%d.",i,a[i]); + MTX_ERROR("Polynome is not primitive."); ++ return 1; + } ++ return 0; + } + + +@@ -195,7 +197,7 @@ static void testprim() + initarith() - Initialize index and zech logarithm tables. + ----------------------------------------------------------------- */ + +-static void initarith() ++static int initarith() + { int i,elem; + POLY a; + +@@ -214,7 +216,7 @@ static void initarith() + polmultx(a); + polymod(a,irred); + } +- testprim(); ++ if (testprim()) return 1; + + /* Calculate zech logarithms + ------------------------- */ +@@ -222,6 +224,7 @@ static void initarith() + { elem = (int)((i%P)==P-1 ? i+1-P : i+1); /* add 1 */ + zech[indx[i]]=indx[elem]; /* Zech-table=result */ + } ++ return 0; + } + + +@@ -314,7 +317,7 @@ static BYTE pack(BYTE a[8]) + and initialize tables. + ----------------------------------------------------------------- */ + +-static void writeheader() ++static int writeheader() + { + int i, j; + +@@ -324,6 +327,7 @@ static void writeheader() + { + perror(filename); + MTX_ERROR("Cannot open table file"); ++ return 1; + } + for (CPM=1,maxmem=Q; (long)maxmem * Q <= 256L; ++CPM, maxmem *= Q); + for (i = 0; irrednrs[i] != (int) Q && irrednrs[i] != 0; ++i); +@@ -333,7 +337,7 @@ static void writeheader() + for (j = 0; j <= MAXGRAD; j++) + irred[j] = irreducibles[i][MAXGRAD-j]; + G = P; /* Generator is X */ +- initarith(); /* Init index- and Zech-tables */ ++ if (initarith()) return 1; /* Init index- and Zech-tables */ + } + else + { +@@ -357,6 +361,7 @@ static void writeheader() + } + MESSAGE(1,("Generator : %ld\n",info[1])); + MESSAGE(1,("Packing : %ld/byte\n",info[3])); ++ return 0; + } + + +@@ -364,14 +369,14 @@ static void writeheader() + checkq() - Set Q and N. Verify that Q is a prime power. + ----------------------------------------------------------------- */ + +-static void checkq(long l) ++static int checkq(long l) + { + long q, d; + + if (l < 2 || l > 256) + { +- fprintf(stderr,"Field order out of range (2-256)\n"); +- exit(EXIT_ERR); ++ MTX_ERROR1("Field order out of range (2-256): %E", MTX_ERR_RANGE); ++ return 1; + } + + Q = l; +@@ -381,9 +386,10 @@ static void checkq(long l) + q /= d; + if (q != 1) + { +- fprintf(stderr,"Illegal Field order\n"); +- exit(EXIT_ERR); ++ MTX_ERROR("Illegal Field order\n"); ++ return 1; + } ++ return 0; + } + + +@@ -407,7 +413,7 @@ static void inittables() + mkembed() - Calculate embeddings of all subfields. + ----------------------------------------------------------------- */ + +-static void mkembed() ++static int mkembed() + { + int n; /* Degree of subfield over Z_p */ + long q; /* subfield order */ +@@ -456,6 +462,7 @@ static void mkembed() + { + fprintf(stderr,"*** q=%ld, Q=%ld.",q,Q); + MTX_ERROR("Internal error."); ++ return 1; + } + + /* Calculate a generator for the subfield +@@ -502,13 +509,13 @@ static void mkembed() + fflush(stdout); + } + } ++ return 0; + } + + + static int Init(int field) + { +- checkq(field); +- return 0; ++ return checkq(field); + } + + /* ----------------------------------------------------------------- +@@ -526,7 +533,7 @@ int FfMakeTables(int field) + ---------- */ + if (Init(field) != 0) + return 1; +- writeheader(); /* Open file and write header */ ++ if (writeheader()) return 1; /* Open file and write header */ + inittables(); + + /* Make insert table +@@ -618,7 +625,7 @@ int FfMakeTables(int field) + } + } + +- mkembed(); ++ if (mkembed()) return 1; + + MESSAGE(1,("Writing tables to %s\n",filename)); + if ( +@@ -639,6 +646,7 @@ int FfMakeTables(int field) + { + perror(filename); + MTX_ERROR("Error writing table file"); ++ return 1; + } + fclose(fd); + return(0); +diff --git a/src/matadd.c b/src/matadd.c +index 54dbcb1..2d86d86 100644 +--- a/src/matadd.c ++++ b/src/matadd.c +@@ -48,7 +48,7 @@ Matrix_t *MatAdd(Matrix_t *dest, const Matrix_t *src) + ------------------- */ + dp = dest->Data; + sp = src->Data; +- FfSetField(src->Field); ++ FfSetField(src->Field); /* No error checking */ + FfSetNoc(src->Noc); + for (n = src->Nor; n > 0; --n) + { +diff --git a/src/matclean.c b/src/matclean.c +index e7307bf..16f02d6 100644 +--- a/src/matclean.c ++++ b/src/matclean.c +@@ -53,7 +53,7 @@ int MatClean(Matrix_t *mat, const Matrix_t *sub) + + /* Clean + ----- */ +- FfSetNoc(mat->Noc); ++ FfSetNoc(mat->Noc); /* No error checking */ + for (i = 0; i < mat->Nor; ++i) + { + PTR m = MatGetPtr(mat,i); +diff --git a/src/matcmp.c b/src/matcmp.c +index b778ec4..d503285 100644 +--- a/src/matcmp.c ++++ b/src/matcmp.c +@@ -38,7 +38,7 @@ MTX_DEFINE_FILE_INFO + ** not necessarily mean that an error has occured. + ** @param a First matrix. + ** @param b Second matrix. +- ** @return 0 if the matrices are equal, nonzero otherwise (see description). ++ ** @return 0 if the matrices are equal, nonzero otherwise (see description), -2 on error. + **/ + + int MatCompare(const Matrix_t *a, const Matrix_t *b) +@@ -50,7 +50,7 @@ int MatCompare(const Matrix_t *a, const Matrix_t *b) + if (!MatIsValid(a) || !MatIsValid(b)) + { + MTX_ERROR1("%E",MTX_ERR_BADARG); +- return -1; ++ return -2; + } + + /* Compare fields and dimensions +@@ -65,7 +65,7 @@ int MatCompare(const Matrix_t *a, const Matrix_t *b) + /* Compare the entries row by row. We do not use memcmp on the + whole matrix because we must ignore padding bytes. + ----------------------------------------------------------- */ +- FfSetField(a->Field); ++ FfSetField(a->Field); /* No error checking */ + FfSetNoc(a->Noc); + for (i = 0; i < a->Nor; ++i) + { +diff --git a/src/matcopy.c b/src/matcopy.c +index 75852dd..c3e7850 100644 +--- a/src/matcopy.c ++++ b/src/matcopy.c +@@ -105,7 +105,7 @@ int MatCopyRegion(Matrix_t *dest, int destrow, int destcol, + { + #ifdef PARANOID + FEL f; +- FfSetNoc(src->Noc); ++ FfSetNoc(src->Noc); /* No error checking */ + f = FfExtract(s,k); + FfSetNoc(dest->Noc); + FfInsert(d,destcol+k-col1,f); +diff --git a/src/matcore.c b/src/matcore.c +index 1f27dfd..0dc9d92 100644 +--- a/src/matcore.c ++++ b/src/matcore.c +@@ -131,7 +131,7 @@ Matrix_t *MatAlloc(int field, int nor, int noc) + SysFree(m); + return NULL; + } +- FfSetNoc(noc); ++ if (FfSetNoc(noc)) return NULL; + m->Magic = MAT_MAGIC; + m->Field = field; + m->Nor = nor; +diff --git a/src/matcut.c b/src/matcut.c +index fde0662..f274311 100644 +--- a/src/matcut.c ++++ b/src/matcut.c +@@ -79,11 +79,12 @@ Matrix_t *MatCut(const Matrix_t *src, int row1, int col1, int nrows, int ncols) + /* Initialize pointers to the source and destination matrix + -------------------------------------------------------- */ + s = MatGetPtr(src,row1); ++ if (!s) return NULL; + d = result->Data; + + /* Copy the requested data + ----------------------- */ +- FfSetNoc(ncols); ++ if (FfSetNoc(ncols)) return NULL; + for (n = nrows; n > 0; --n) + { + if (col1 == 0) +@@ -95,9 +96,9 @@ Matrix_t *MatCut(const Matrix_t *src, int row1, int col1, int nrows, int ncols) + { + #ifdef PARANOID + FEL f; +- FfSetNoc(src->Noc); ++ FfSetNoc(src->Noc); /* No error checking */ + f = FfExtract(s,col1+k); +- FfSetNoc(ncols); ++ FfSetNoc(ncols); /* error was checked above */ + FfInsert(d,k,f); + #else + FfInsert(d,k,FfExtract(s,col1+k)); +diff --git a/src/matech.c b/src/matech.c +index ed31cf4..ee52ebe 100644 +--- a/src/matech.c ++++ b/src/matech.c +@@ -124,7 +124,7 @@ int MatEchelonize(Matrix_t *mat) + + /* Build the pivot table + --------------------- */ +- FfSetField(mat->Field); ++ FfSetField(mat->Field); /* No error checking */ + FfSetNoc(mat->Noc); + rank = zmkechelon(mat->Data,mat->Nor,mat->Noc,mat->PivotTable,is_pivot); + +@@ -163,13 +163,14 @@ long MatNullity(const Matrix_t *mat) + ** This function calculates the dimension of the null-space of a matrix + ** and deletes the matrix. + ** @param mat Pointer to the matrix. +- ** @return Nullity of @em mat, or -$ on error. ++ ** @return Nullity of @em mat, or $-1$ on error. + **/ + + long MatNullity__(Matrix_t *mat) + { + long nul; +- MatEchelonize(mat); ++ if (!mat) return -1; ++ if (MatEchelonize(mat)==-1) return -1; + nul = mat->Noc - mat->Nor; + MatFree(mat); + return nul; +diff --git a/src/matins.c b/src/matins.c +index 45c31e4..50fe9c1 100644 +--- a/src/matins.c ++++ b/src/matins.c +@@ -54,7 +54,7 @@ Matrix_t *MatInsert_(Matrix_t *mat, const Poly_t *pol) + return NULL; + } + +- FfSetField(mat->Field); ++ FfSetField(mat->Field); /* No error checking */ + FfSetNoc(nor); + + /* Special case: p(x) = 0 +@@ -81,7 +81,10 @@ Matrix_t *MatInsert_(Matrix_t *mat, const Poly_t *pol) + /* Evaluate p(A) + ------------- */ + if (pol->Degree > 1) +- x = MatDup(mat); ++ { ++ x = MatDup(mat); ++ if (!x) return NULL; ++ } + if ((f = pol->Data[pol->Degree]) != FF_ONE) + { + for (l = nor, v = mat->Data; l > 0; --l, FfStepPtr(&v)) +@@ -147,6 +150,7 @@ Matrix_t *MatInsert(const Matrix_t *mat, const Poly_t *pol) + if (pol->Degree == 0) + { + x = MatAlloc(mat->Field,nor,nor); ++ if (!x) return NULL; + for (l = 0, v = x->Data; l < nor; ++l, FfStepPtr(&v)) + FfInsert(v,l,pol->Data[0]); + return x; +@@ -155,6 +159,7 @@ Matrix_t *MatInsert(const Matrix_t *mat, const Poly_t *pol) + /* Evaluate p(A) + ------------- */ + x = MatDup(mat); ++ if (!x) return NULL; + if ((f = pol->Data[pol->Degree]) != FF_ONE) + { + for (l = nor, v = x->Data; l > 0; --l, FfStepPtr(&v)) +diff --git a/src/matinv.c b/src/matinv.c +index 217eb0e..990cbe7 100644 +--- a/src/matinv.c ++++ b/src/matinv.c +@@ -114,6 +114,7 @@ Matrix_t *MatInverse(const Matrix_t *mat) + /* Copy matrix into workspace + -------------------------- */ + tmp = FfAlloc(mat->Nor); ++ if (!tmp) return NULL; + memcpy(tmp,mat->Data,FfCurrentRowSize * mat->Nor); + + /* Inversion +diff --git a/src/matmul.c b/src/matmul.c +index 20f5e88..bed30fc 100644 +--- a/src/matmul.c ++++ b/src/matmul.c +@@ -63,7 +63,7 @@ Matrix_t *MatMul(Matrix_t *dest, const Matrix_t *src) + + /* Matrix multiplication + --------------------- */ +- FfSetField(src->Field); ++ FfSetField(src->Field); /* no error checking, since the matrix *exists* */ + FfSetNoc(src->Noc); + result = tmp = FfAlloc(dest->Nor); + if (result == NULL) +diff --git a/src/matnull.c b/src/matnull.c +index 4f28566..2550b96 100644 +--- a/src/matnull.c ++++ b/src/matnull.c +@@ -27,6 +27,8 @@ MTX_DEFINE_FILE_INFO + - |piv| contains a pivot table for the null space. + If |flags| is nonzero, the null-space is not reduced to echelon form, + and the contents of |piv| are undefined. ++ ++ Return -1 on error, the dimension of the null-space on success. + ** @see + **/ + +@@ -40,7 +42,7 @@ static long znullsp(PTR matrix, long nor, int *piv, PTR nsp, int flags) + + /* Make the identity matrix in . + ---------------------------------- */ +- FfSetNoc(nor); ++ if (FfSetNoc(nor)) return -1; + x = nsp; + for (i = 0; i < nor; ++i) + { +@@ -61,13 +63,12 @@ static long znullsp(PTR matrix, long nor, int *piv, PTR nsp, int flags) + + for (k = 0; k < i; ++k) + { +- FfSetNoc(noc); ++ FfSetNoc(noc); /* No error checking, since noc used to be the previously assigned number of columns */ + if ((p = piv[k]) >= 0 && (f = FfExtract(x,p)) != FF_ZERO) + { + f = FfNeg(FfDiv(f,FfExtract(xx,p))); +- FfSetNoc(noc); + FfAddMulRow(x,xx,f); +- FfSetNoc(nor); ++ FfSetNoc(nor); /* we have asserted above that it doesn't fail */ + FfAddMulRow(y,yy,f); + } + FfSetNoc(noc); +@@ -151,11 +152,21 @@ Matrix_t *MatNullSpace_(Matrix_t *mat, int flags) + if (nsp == NULL) + return NULL; + nsp->PivotTable = NREALLOC(nsp->PivotTable,int,mat->Nor); ++ if (!nsp->PivotTable) ++ { ++ MatFree(nsp); ++ return NULL; ++ } + + /* Calculate the null-space + ------------------------ */ +- FfSetNoc(mat->Noc); ++ FfSetNoc(mat->Noc); /* No error checking */ + dim = znullsp(mat->Data,mat->Nor,nsp->PivotTable,nsp->Data,flags); ++ if (dim==-1) ++ { ++ MatFree(nsp); ++ return NULL; ++ } + if (flags) + { + SysFree(nsp->PivotTable); +diff --git a/src/matorder.c b/src/matorder.c +index 16aec74..24b31a3 100644 +--- a/src/matorder.c ++++ b/src/matorder.c +@@ -32,7 +32,7 @@ MTX_DEFINE_FILE_INFO + ** the order is greater than 1000000, or if the order on any cyclic + ** subspace is greater than 1000. + ** @param mat Pointer to the matrix. +- ** @return The order of @em mat, or 1 on error. ++ ** @return The order of @em mat, or -1 on error. + **/ + + int MatOrder(const Matrix_t *mat) +@@ -59,15 +59,29 @@ int MatOrder(const Matrix_t *mat) + FfSetNoc(mat->Noc); + nor = mat->Nor; + m1 = FfAlloc(nor); ++ if (!m1) return -1; + memcpy(m1,mat->Data,FfCurrentRowSize * nor); + bend = basis = FfAlloc(nor+1); ++ if (!bend) ++ { ++ SysFree(m1); ++ return -1; ++ } + + piv = NALLOC(int,nor+1); + done = NALLOC(char,nor); ++ if (!piv || !done) ++ { SysFree(m1); ++ return -1; ++ } + memset(done,0,(size_t)nor); + v1 = FfAlloc(1); + v2 = FfAlloc(1); + v3 = FfAlloc(1); ++ if (!v1 || !v2 || !v3) ++ { SysFree(m1); ++ return -1; ++ } + tord = ord = 1; + dim = 0; + j1 = 1; +diff --git a/src/matpivot.c b/src/matpivot.c +index abe342a..c843282 100644 +--- a/src/matpivot.c ++++ b/src/matpivot.c +@@ -71,7 +71,6 @@ static int zmkpivot(PTR matrix, int nor, int noc, int *piv, int *ispiv) + + int MatPivotize(Matrix_t *mat) + { +- int rc; + int *newtab; + static int *is_pivot = NULL; + static int maxnoc = -1; +@@ -106,9 +105,7 @@ int MatPivotize(Matrix_t *mat) + --------------------- */ + FfSetField(mat->Field); + FfSetNoc(mat->Noc); +- rc = zmkpivot(mat->Data,mat->Nor,mat->Noc,mat->PivotTable,is_pivot); +- +- return rc; ++ return zmkpivot(mat->Data,mat->Nor,mat->Noc,mat->PivotTable,is_pivot); + } + + /** +diff --git a/src/matpwr.c b/src/matpwr.c +index cd66b5e..b06e5b2 100644 +--- a/src/matpwr.c ++++ b/src/matpwr.c +@@ -119,8 +119,14 @@ Matrix_t *MatPower(const Matrix_t *mat, long n) + FfSetField(mat->Field); + FfSetNoc(mat->Noc); + tmp = FfAlloc(FfNoc); ++ if (!tmp) return NULL; + memcpy(tmp,mat->Data,FfCurrentRowSize * FfNoc); + tmp2 = FfAlloc(FfNoc); ++ if (!tmp2) ++ { ++ SysFree(tmp); ++ return NULL; ++ } + result = MatAlloc(mat->Field,mat->Nor,mat->Noc); + if (result != NULL) + matpwr_(n,tmp,result->Data,tmp2); +diff --git a/src/matread.c b/src/matread.c +index 031d100..06e6e6b 100644 +--- a/src/matread.c ++++ b/src/matread.c +@@ -46,8 +46,9 @@ Matrix_t *MatRead(FILE *f) + return NULL; + if (FfReadRows(f,m->Data,m->Nor) != m->Nor) + { +- MatFree(m); +- return NULL; ++ MTX_ERROR("Number of given rows does not coincide with given row number"); ++ MatFree(m); ++ return NULL; + } + return m; + } +diff --git a/src/mattrace.c b/src/mattrace.c +index f500248..772a6e4 100644 +--- a/src/mattrace.c ++++ b/src/mattrace.c +@@ -21,7 +21,7 @@ + ** This function calculates the sum of all diagonal elements of a matrix. + ** Note that the matrix need not be square. + ** @param mat Pointer to the matrix. +- ** @return Trace of @a mat, @c FF_ZERO on error. ++ ** @return Trace of @a mat, @c 255 on error. + **/ + + FEL MatTrace(const Matrix_t *mat) +@@ -35,7 +35,7 @@ FEL MatTrace(const Matrix_t *mat) + ------------------ */ + #ifdef DEBUG + if (!MatIsValid(mat)) +- return FF_ZERO; ++ return (FEL)255; + #endif + + maxi = mat->Nor > mat->Noc ? mat->Noc : mat->Nor; +diff --git a/src/matwrite.c b/src/matwrite.c +index 1fb6af3..b364e80 100644 +--- a/src/matwrite.c ++++ b/src/matwrite.c +@@ -44,7 +44,10 @@ int MatWrite(const Matrix_t *mat, FILE *f) + FfSetField(mat->Field); + FfSetNoc(mat->Noc); + if (FfWriteRows(f,mat->Data,mat->Nor) != mat->Nor) +- return -1; ++ { ++ MTX_ERROR("Cannot write rows"); ++ return -1; ++ } + return 0; + } + +@@ -75,7 +78,10 @@ int MatSave(const Matrix_t *mat, const char *fn) + i = MatWrite(mat,f); + fclose(f); + if (i != 0) +- MTX_ERROR1("Cannot write matrix to %s",fn); ++ { ++ MTX_ERROR1("Cannot write matrix to %s",fn); ++ return -1; ++ } + return i; + } + +diff --git a/src/meataxe.h b/src/meataxe.h +index 368b37b..0efa7dd 100644 +--- a/src/meataxe.h ++++ b/src/meataxe.h +@@ -135,8 +135,8 @@ PTR FfSubRowPartialReverse(PTR dest, PTR src, int first, int len); + PTR FfAlloc(int nor); + int FfCmpRows(PTR p1, PTR p2); + void FfCleanRow(PTR row, PTR matrix, int nor, const int *piv); +-void FfCleanRow2(PTR row, PTR matrix, int nor, const int *piv, PTR row2); +-void FfCleanRowAndRepeat(PTR row, PTR mat, int nor, const int *piv, ++int FfCleanRow2(PTR row, PTR matrix, int nor, const int *piv, PTR row2); ++int FfCleanRowAndRepeat(PTR row, PTR mat, int nor, const int *piv, + PTR row2, PTR mat2); + void FfCopyRow(PTR dest, PTR src); + FEL FfEmbed(FEL a, int subfield); +diff --git a/src/mmulscal.c b/src/mmulscal.c +index 281be16..9bff3ff 100644 +--- a/src/mmulscal.c ++++ b/src/mmulscal.c +@@ -21,7 +21,7 @@ + ** Multiply a Matrix by a Constant. + ** @param dest Pointer to the matrix. + ** @param coeff Value to multiply with. +- ** @return The function returns @a dest. ++ ** @return The function returns @a dest, or NULL on error in debug mode only. + **/ + + Matrix_t *MatMulScalar(Matrix_t *dest, FEL coeff) +diff --git a/src/mtensor.c b/src/mtensor.c +index b2b9ac7..2fb90ca 100644 +--- a/src/mtensor.c ++++ b/src/mtensor.c +@@ -85,6 +85,11 @@ Matrix_t *MatTensor(const Matrix_t *m1, const Matrix_t *m2) + ---------------------------------------- */ + x1 = m1->Data; + x3 = MatGetPtr(temat,i2); ++ if (!x3) ++ { ++ MatFree(temat); ++ return NULL; ++ } + FfSetNoc(temat->Noc); + + /* Loop through all rows of +diff --git a/src/quotient.c b/src/quotient.c +index f44b556..eea0754 100644 +--- a/src/quotient.c ++++ b/src/quotient.c +@@ -82,16 +82,23 @@ Matrix_t *QProjection(const Matrix_t *subspace, const Matrix_t *vectors) + sdim = subspace->Nor; + qdim = subspace->Noc - sdim; + result = MatAlloc(subspace->Field,vectors->Nor,qdim); ++ if (!result) return NULL; + + /* Calculate the projection + ------------------------ */ + FfSetNoc(subspace->Noc); + tmp = FfAlloc(1); ++ if (!tmp) return NULL; + non_piv = subspace->PivotTable + subspace->Nor; + for (i = 0; i < vectors->Nor; ++i) + { + int k; + PTR q = MatGetPtr(result,i); ++ if (!q) ++ { ++ SysFree(tmp); ++ return NULL; ++ } + FfCopyRow(tmp,MatGetPtr(vectors,i)); + FfCleanRow(tmp,subspace->Data,sdim,subspace->PivotTable); + for (k = 0; k < qdim; ++k) +@@ -158,14 +165,20 @@ Matrix_t *QAction(const Matrix_t *subspace, const Matrix_t *gen) + + /* Calculate the action on the quotient + ------------------------------------ */ +- FfSetNoc(dim); ++ FfSetNoc(dim); /* No error checking, since dim is the ->Noc of an existing matrix */ + tmp = FfAlloc(1); ++ if (!tmp) return NULL; + piv = subspace->PivotTable; + non_piv = piv + subspace->Nor; + for (k = 0; k < qdim; ++k) + { + int l; + PTR qx = MatGetPtr(action,k); ++ if (!qx) ++ { ++ SysFree(tmp); ++ return NULL; ++ } + FfCopyRow(tmp,MatGetPtr(gen,non_piv[k])); + FfCleanRow(tmp,subspace->Data,sdim,piv); + for (l = 0; l < qdim; ++l) +diff --git a/src/saction.c b/src/saction.c +index adae3cf..0aba44d 100644 +--- a/src/saction.c ++++ b/src/saction.c +@@ -68,8 +68,14 @@ Matrix_t *SAction(const Matrix_t *subspace, const Matrix_t *gen) + sdim = subspace->Nor; + FfSetField(subspace->Field); + action = MatAlloc(FfOrder,sdim,sdim); +- FfSetNoc(dim); ++ if (!action) return NULL; ++ FfSetNoc(dim); /* No error checking, since dim is the ->Noc of an existing matrix */ + tmp = FfAlloc(1); ++ if (!tmp) ++ { ++ MatFree(action); ++ return NULL; ++ } + + /* Calaculate the action. + ---------------------- */ +@@ -77,6 +83,12 @@ Matrix_t *SAction(const Matrix_t *subspace, const Matrix_t *gen) + { + PTR xi = MatGetPtr(subspace,i); + PTR yi = MatGetPtr(action,i); ++ if (!xi || !yi) ++ { ++ MatFree(action); ++ SysFree(tmp); ++ return NULL; ++ } + FEL f; + + /* Calculate the image of the -th row of . +@@ -85,10 +97,20 @@ Matrix_t *SAction(const Matrix_t *subspace, const Matrix_t *gen) + + /* Clean the image with the subspace and store coefficients. + --------------------------------------------------------- */ +- FfCleanRow2(tmp,subspace->Data,sdim,subspace->PivotTable,yi); ++ if (FfCleanRow2(tmp,subspace->Data,sdim,subspace->PivotTable,yi)) ++ { ++ MatFree(action); ++ SysFree(tmp); ++ return NULL; ++ } + if (FfFindPivot(tmp,&f) >= 0) +- MTX_ERROR("Split(): Subspace not invariant"); ++ { ++ MatFree(action); ++ SysFree(tmp); ++ MTX_ERROR("Split(): Subspace not invariant"); ++ return NULL; + } ++ } + + /* Clean up and return the result. + ------------------------------- */ +diff --git a/src/stabpwr.c b/src/stabpwr.c +index ff33bc6..01282ab 100644 +--- a/src/stabpwr.c ++++ b/src/stabpwr.c +@@ -68,15 +68,18 @@ int StablePower_(Matrix_t *mat, int *pwr, Matrix_t **ker) + --------------------------- */ + p = 1; + k1 = MatNullSpace(mat); +- MatMul(mat,mat); ++ if (!k1) return -1; ++ if (!MatMul(mat,mat)) return -1; + k2 = MatNullSpace(mat); ++ if (!k2) return -1; + while (k2->Nor > k1->Nor) + { + p *= 2; + MatFree(k1); + k1 = k2; +- MatMul(mat,mat); ++ if (!MatMul(mat,mat)) return -1; + k2 = MatNullSpace(mat); ++ if (!k2) return -1; + } + MatFree(k2); + +diff --git a/src/sumint.c b/src/sumint.c +index 278acd8..905fa79 100644 +--- a/src/sumint.c ++++ b/src/sumint.c +@@ -77,7 +77,7 @@ int FfSumAndIntersection(PTR wrk1, int *nor1, int *nor2, PTR wrk2, int *piv) + { + FEL f; + int p; +- FfCleanRowAndRepeat(x1,wrk1,k,piv,x2,wrk2); ++ if (FfCleanRowAndRepeat(x1,wrk1,k,piv,x2,wrk2)) return -1; + if ((p = FfFindPivot(x1,&f)) < 0) + continue; /* Null row - ignore */ + if (k < i) +diff --git a/src/temap.c b/src/temap.c +index 7ba445a..4c2d493 100644 +--- a/src/temap.c ++++ b/src/temap.c +@@ -74,17 +74,21 @@ Matrix_t *TensorMap(Matrix_t *vec, const Matrix_t *a, const Matrix_t *b) + for (i = 0; i < vec->Nor; ++i) + { + Matrix_t *tmp = MatTransposed(a); ++ if (!tmp) return NULL; + Matrix_t *v = VectorToMatrix(vec,i,b->Nor); + if (v == NULL) + { + MTX_ERROR("Conversion failed"); +- break; ++ return NULL; + } +- MatMul(tmp,v); ++ if (!MatMul(tmp,v)) return NULL; + MatFree(v); +- MatMul(tmp,b); ++ if (!MatMul(tmp,b)) return NULL; + if (MatrixToVector(tmp,result,i)) +- MTX_ERROR("Conversion failed"); ++ { ++ MTX_ERROR("Conversion failed"); ++ return NULL; ++ } + MatFree(tmp); + } + return result; +diff --git a/src/vec2mat.c b/src/vec2mat.c +index 1047805..e76ad88 100644 +--- a/src/vec2mat.c ++++ b/src/vec2mat.c +@@ -63,8 +63,11 @@ Matrix_t *VectorToMatrix(Matrix_t *vecs, int n, int noc) + return NULL; + for (i = 0; i < result->Nor; ++i) + { +- if (MatCopyRegion(result,i,0, vecs,n,i*noc,1,noc) != 0) +- MTX_ERROR("Copy failed"); ++ if (MatCopyRegion(result,i,0, vecs,n,i*noc,1,noc) != 0) ++ { ++ MTX_ERROR("Copy failed"); ++ return NULL; ++ } + } + return result; + } +diff --git a/src/window.c b/src/window.c +index 9c87694..fbeb943 100644 +--- a/src/window.c ++++ b/src/window.c +@@ -69,7 +69,11 @@ MatrixWindow_t *WindowAlloc(int fl, int nor, size_t rowsize) + MTX_ERROR1("%E",MTX_ERR_NOMEM); + return NULL; + } +- FfSetField(fl); ++ if (FfSetField(fl)) ++ { ++ free(out); ++ return NULL; ++ } + out->Matrix = MatAlloc(fl, nor, rowsize*sizeof(long)*MPB); + if (out->Matrix == NULL) + { +@@ -266,7 +270,8 @@ __asm__(" popl %ebx\n" + + /** dest := left+right + left and right must be distinct, but one of them may coincide with dest -- under the assumption +- that, in that case, the ambient matrices coincide as well. **/ ++ that, in that case, the ambient matrices coincide as well. ++ Return dest, or NULL on error (the only error may occur in a compatibility check). **/ + MatrixWindow_t *WindowSum(MatrixWindow_t *dest, MatrixWindow_t *left, MatrixWindow_t *right) + { + PTR x, result, tmp; +@@ -335,6 +340,7 @@ MatrixWindow_t *WindowSum(MatrixWindow_t *dest, MatrixWindow_t *left, MatrixWind + /** dest := left-right + left and right must be distinct, but one of them may coincide with dest -- under the assumption + that, in that case, the ambient matrices coincide as well. ++ Return dest, or NULL on error (the only error may occur in a compatibility check). + **/ + MatrixWindow_t *WindowDif(MatrixWindow_t *dest, MatrixWindow_t *left, MatrixWindow_t *right) + { +@@ -407,7 +413,7 @@ MatrixWindow_t *WindowDif(MatrixWindow_t *dest, MatrixWindow_t *left, MatrixWind + can write the result into it. Moreover, the chunk of memory pointed at by dest MUST be disjoint + from the chunks for left and right! + +- Dimensions are not tested! ++ Dimensions are not tested, always dest will be returned (no error value). + **/ + MatrixWindow_t *WindowAddMul(MatrixWindow_t *dest, MatrixWindow_t *left, MatrixWindow_t *right) + { +@@ -617,7 +623,7 @@ int StrassenStep(MatrixWindow_t *dest_win, MatrixWindow_t *A_win, MatrixWindow_t + S2->RowSize = A_sub_rowsize; + S2->Matrix = X->Matrix; + S2->ULCorner = X->ULCorner; +- WindowDif(S2, A00, A10); ++ WindowDif(S2, A00, A10); /* No error checking, as we know that the windows are compatible */ + /* + printf("1. S2 = A00-A10 in X\n"); + WindowShow(X); +@@ -653,7 +659,7 @@ int StrassenStep(MatrixWindow_t *dest_win, MatrixWindow_t *A_win, MatrixWindow_t + S0->RowSize = A_sub_rowsize; + S0->Matrix = X->Matrix; + S0->ULCorner = X->ULCorner; +- WindowSum(S0, A10, A11); ++ WindowSum(S0, A10, A11); /* no error checking here and below, as we know the dimensions of the windows */ + /* + printf("4. S0 = A10+A11 in X\n"); + WindowShow(X); +diff --git a/src/zcleanrow.c b/src/zcleanrow.c +index b4dcb30..d36a165 100644 +--- a/src/zcleanrow.c ++++ b/src/zcleanrow.c +@@ -63,10 +63,10 @@ void FfCleanRow(PTR row, PTR matrix, int nor, const int *piv) + ** @param nor Number of rows. + ** @param piv Pivot table for @em matrix. + ** @param row2 Pointer to row where the operations are recorded. +- ** @return Always 0. ++ ** @return 0, or 1 on error. + **/ + +-void FfCleanRow2(PTR row, PTR mat, int nor, const int *piv, PTR row2) ++int FfCleanRow2(PTR row, PTR mat, int nor, const int *piv, PTR row2) + { + int i; + PTR x; +@@ -74,7 +74,7 @@ void FfCleanRow2(PTR row, PTR mat, int nor, const int *piv, PTR row2) + if (row2 == NULL || piv == NULL) + { + MTX_ERROR1("%E",MTX_ERR_BADARG); +- return; ++ return 1; + } + for (i = 0, x = mat; i < nor; ++i, FfStepPtr(&x)) + { +@@ -86,6 +86,7 @@ void FfCleanRow2(PTR row, PTR mat, int nor, const int *piv, PTR row2) + FfInsert(row2,i,f); + } + } ++ return 0; + } + + +@@ -100,10 +101,10 @@ void FfCleanRow2(PTR row, PTR mat, int nor, const int *piv, PTR row2) + ** @param piv Pivot table for @em mat. + ** @param row2 Pointer to the second row to be cleaned. + ** @param mat2 Matrix to the second matrix. +- ** @return Always 0. ++ ** @return 0, or 1 on error. + **/ + +-void FfCleanRowAndRepeat(PTR row, PTR mat, int nor, const int *piv, PTR row2, PTR mat2) ++int FfCleanRowAndRepeat(PTR row, PTR mat, int nor, const int *piv, PTR row2, PTR mat2) + { + int i; + PTR x, x2; +@@ -112,7 +113,7 @@ void FfCleanRowAndRepeat(PTR row, PTR mat, int nor, const int *piv, PTR row2, PT + if (row2 == NULL || piv == NULL || row2 == NULL || mat2 == NULL) + { + MTX_ERROR1("%E",MTX_ERR_BADARG); +- return; ++ return 1; + } + #endif + for (i = 0, x = mat, x2 = mat2; i < nor; ++i, FfStepPtr(&x), FfStepPtr(&x2)) +@@ -125,6 +126,7 @@ void FfCleanRowAndRepeat(PTR row, PTR mat, int nor, const int *piv, PTR row2, PT + FfAddMulRow(row2,x2,f); + } + } ++ return 0; + } + + diff --git a/build/pkgs/meataxe/spkg-install b/build/pkgs/meataxe/spkg-install new file mode 100755 index 00000000000..7733e9e44cb --- /dev/null +++ b/build/pkgs/meataxe/spkg-install @@ -0,0 +1,95 @@ +#!/usr/bin/env bash + +if [ -z "$SAGE_LOCAL" ]; then + echo >&2 "SAGE_LOCAL undefined ... exiting" + echo >&2 "Maybe run 'sage --sh'?" + exit 1 +fi + +cd src + +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'" + return 1 + fi +done + +## The following *could* be put into Makefile.conf + +# This is the place where arithmetic tables and some other input files are +# searched by default. +export MTXLIB="$DOT_SAGE/meataxe" +# Directory where executables are installed. +export MTXBIN="$SAGE_LOCAL/bin" +# Default compiler flags +export CFLAGS1="-std=gnu99 -O -Wall -fPIC" +# Field size up to GF(256) +export ZZZ=0 + +# In principle, one should uncomment for field sizes up to GF(2^16). +# But upstream doesn't provide the required sources. +#export ZZZ=1 + +# The following is just to make MeatAxe's Makefile happy +touch Makefile.conf + +# We create a directory for the multiplication tables +mkdir -p $MTXLIB + +if [ $? -ne 0 ]; then + echo >&2 "Error creating directory for multiplication tables." + exit 1 +fi + +## Install! Aparently MeatAxe would rebuild everything when +## testing, and "make check" also installs. So, if a test +## is requested then we do it in one go. + +if [ "x$SAGE_CHECK" = xyes ]; then + $MAKE check +else + $MAKE +fi + +if [ $? -ne 0 ]; then + echo >&2 "Error installing MeatAxe." + exit 1 +fi + +## Surprisingly, MeatAxe's Makefile does NOT install the binaries +## in MTXBIN. Hence, we do it manually. + +mv bin/* "$MTXBIN" +if [ $? -ne 0 ]; then + echo >&2 "Error copying MeatAxe executables." + exit 1 +fi + +# We move the meataxe library to a permanent location +mv tmp/libmtx.a "$SAGE_LOCAL/lib" +if [ $? -ne 0 ]; then + echo >&2 "Error copying MeatAxe library." + exit 1 +fi + +cp src/meataxe.h "$SAGE_LOCAL/include/" +if [ $? -ne 0 ]; then + echo >&2 "Error copying MeatAxe header." + exit 1 +fi + +# Are we supposed to install the documentation? +if [ "x$SAGE_SPKG_INSTALL_DOCS" = xyes ] ; then + mkdir -p $SAGE_ROOT/local/share/doc/meataxe/ + cp -r doc/* $SAGE_ROOT/local/share/doc/meataxe/ + if [ $? -ne 0 ]; then + echo "Error copying documentation." + exit 1 + else + echo "The documentation can be found in $SAGE_ROOT/local/share/doc/meataxe/" + fi +fi diff --git a/build/pkgs/meataxe/type b/build/pkgs/meataxe/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/meataxe/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/notebook/package-version.txt b/build/pkgs/notebook/package-version.txt index f0cd2d909bb..427ea92c3db 100644 --- a/build/pkgs/notebook/package-version.txt +++ b/build/pkgs/notebook/package-version.txt @@ -1 +1 @@ -4.0.4.p2 +4.0.4.p3 diff --git a/build/pkgs/notebook/patches/help_link_url_fix.patch b/build/pkgs/notebook/patches/help_link_url_fix.patch new file mode 100644 index 00000000000..cfa07dee3d3 --- /dev/null +++ b/build/pkgs/notebook/patches/help_link_url_fix.patch @@ -0,0 +1,24 @@ +Use require.toUrl for help_links + +Dirty patch for the minified js, real PR is at +https://github.com/jupyter/notebook/pull/958 + + +--- a/notebook/static/notebook/js/main.min.js 2016-01-15 10:20:30.769442884 +0100 ++++ b/notebook/static/notebook/js/main.min.js 2016-01-15 10:20:53.073164049 +0100 +@@ -28358,7 +28358,7 @@ + .append($("") + .attr('target', '_blank') + .attr('title', 'Opens in a new window') +- .attr('href', link.url) ++ .attr('href', require.toUrl(link.url)) + .append($("") + .addClass("fa fa-external-link menu-icon pull-right") + ) +@@ -30547,4 +30547,4 @@ + define("notebook/js/main", function(){}); + + +-//# sourceMappingURL=main.min.js.map +\ No newline at end of file ++//# sourceMappingURL=main.min.js.map diff --git a/build/pkgs/ntl/package-version.txt b/build/pkgs/ntl/package-version.txt index 1d9dfd00d48..9641c01e7a1 100644 --- a/build/pkgs/ntl/package-version.txt +++ b/build/pkgs/ntl/package-version.txt @@ -1 +1 @@ -9.6.2.p0 +9.6.2.p1 diff --git a/build/pkgs/ntl/spkg-install b/build/pkgs/ntl/spkg-install index a9dee4679a8..cbb6ba126a4 100755 --- a/build/pkgs/ntl/spkg-install +++ b/build/pkgs/ntl/spkg-install @@ -86,8 +86,7 @@ ntl_configure() CXX="$CXX" CXXFLAGS="$CXXFLAGS $SHAREDFLAGS" \ LDFLAGS="$LDFLAGS" LIBTOOL_LINK_FLAGS="$LIBTOOL_LINK_FLAGS" \ NTL_GMP_LIP=on NTL_GF2X_LIB=on \ - LIBTOOL="$CUR/src/libtool/libtool"\ - NTL_LEGACY_SP_MULMOD=on + LIBTOOL="$CUR/src/libtool/libtool" if [ $? -ne 0 ]; then echo >&2 "Error configuring NTL." diff --git a/build/pkgs/pari/package-version.txt b/build/pkgs/pari/package-version.txt index 04d5fd34a94..b8abed0bd0f 100644 --- a/build/pkgs/pari/package-version.txt +++ b/build/pkgs/pari/package-version.txt @@ -1 +1 @@ -2.8-2044-g89b0f1e.p0 +2.8-2044-g89b0f1e.p1 diff --git a/build/pkgs/pari/patches/README.txt b/build/pkgs/pari/patches/README.txt index a7444b27d1b..01f52e99355 100644 --- a/build/pkgs/pari/patches/README.txt +++ b/build/pkgs/pari/patches/README.txt @@ -12,5 +12,5 @@ Patches to configuration files: the flag unconditionally. C files: -* public_memory_functions.patch (Jeroen Demeyer, #16997): Make some of - PARI's private memory functions public to improve interface with Sage. +* stackwarn.patch (Jeroen Demeyer, #19883): do not display warnings + regarding the stack size (unless DEBUGMEM is set). diff --git a/build/pkgs/pari/patches/public_memory_functions.patch b/build/pkgs/pari/patches/public_memory_functions.patch deleted file mode 100644 index b3726d7dfdf..00000000000 --- a/build/pkgs/pari/patches/public_memory_functions.patch +++ /dev/null @@ -1,61 +0,0 @@ -diff --git a/src/headers/paridecl.h b/src/headers/paridecl.h -index 7067183..4ede6ed 100644 ---- a/src/headers/paridecl.h -+++ b/src/headers/paridecl.h -@@ -2819,6 +2819,9 @@ GEN pari_thread_start(struct pari_thread *t); - void pari_thread_valloc(struct pari_thread *t, size_t s, size_t v, GEN arg); - GEN pari_version(void); - void pari_warn(int numerr, ...); -+void * pari_mainstack_malloc(size_t size); -+void pari_mainstack_mfree(void *s, size_t size); -+void pari_mainstack_free(struct pari_mainstack *st); - void paristack_alloc(size_t rsize, size_t vsize); - void paristack_newrsize(ulong newsize); - void paristack_resize(ulong newsize); -diff --git a/src/language/init.c b/src/language/init.c -index 7b5922d..2a578d7 100644 ---- a/src/language/init.c -+++ b/src/language/init.c -@@ -612,7 +612,7 @@ pari_add_oldmodule(entree *ep) - #ifndef MAP_NORESERVE - #define MAP_NORESERVE 0 - #endif --static void * -+void * - pari_mainstack_malloc(size_t size) - { - void *b = mmap(NULL, size, PROT_READ|PROT_WRITE, -@@ -620,7 +620,7 @@ pari_mainstack_malloc(size_t size) - return (b == MAP_FAILED) ? NULL: b; - } - --static void -+void - pari_mainstack_mfree(void *s, size_t size) - { - munmap(s, size); -@@ -634,13 +634,13 @@ pari_mainstack_mreset(void *s, size_t size) - - #else - #define PARI_STACK_ALIGN (0x40UL) --static void * -+void * - pari_mainstack_malloc(size_t s) - { - return malloc(s); /* NOT pari_malloc, e_MEM would be deadly */ - } - --static void -+void - pari_mainstack_mfree(void *s, size_t size) { (void) size; free(s); } - - static void -@@ -681,7 +681,7 @@ pari_mainstack_alloc(struct pari_mainstack *st, size_t rsize, size_t vsize) - st->memused = 0; - } - --static void -+void - pari_mainstack_free(struct pari_mainstack *st) - { - pari_mainstack_mfree((void*)st->vbot, st->vsize ? st->vsize : fix_size(st->rsize)); diff --git a/build/pkgs/pari/patches/stackwarn.patch b/build/pkgs/pari/patches/stackwarn.patch new file mode 100644 index 00000000000..00121150ab5 --- /dev/null +++ b/build/pkgs/pari/patches/stackwarn.patch @@ -0,0 +1,53 @@ +commit 7cf2260b69d3711f9292a3abab4d3a35d0c74059 +Author: Jeroen Demeyer +Date: Thu Jan 14 10:13:39 2016 +0100 + + Use DEBUGMEM for stack size warnings + +diff --git a/src/language/init.c b/src/language/init.c +index fa5c167..5cd1a13 100644 +--- a/src/language/init.c ++++ b/src/language/init.c +@@ -722,7 +722,8 @@ parivstack_resize(ulong newsize) + evalstate_reset(); + paristack_setsize(pari_mainstack->rsize, newsize); + s = pari_mainstack->vsize ? pari_mainstack->vsize : pari_mainstack->rsize; +- pari_warn(warner,"new maximum stack size = %lu (%.3f Mbytes)", s, s/1048576.); ++ if (DEBUGMEM > 0) ++ pari_warn(warner,"new maximum stack size = %lu (%.3f Mbytes)", s, s/1048576.); + pari_init_errcatch(); + cb_pari_err_recover(-1); + } +@@ -736,7 +737,8 @@ paristack_newrsize(ulong newsize) + pari_mainstack_resize(pari_mainstack, newsize, vsize); + evalstate_reset(); + s = pari_mainstack->rsize; +- pari_warn(warner,"new stack size = %lu (%.3f Mbytes)", s, s/1048576.); ++ if (DEBUGMEM > 0) ++ pari_warn(warner,"new stack size = %lu (%.3f Mbytes)", s, s/1048576.); + pari_init_errcatch(); + cb_pari_err_recover(-1); + } +@@ -750,7 +752,8 @@ paristack_resize(ulong newsize) + newsize = maxuu(minuu(newsize, vsize), pari_mainstack->size); + pari_mainstack->size = newsize; + pari_mainstack->bot = pari_mainstack->top - pari_mainstack->size; +- pari_warn(warner,"increasing stack size to %lu",newsize); ++ if (DEBUGMEM > 0) ++ pari_warn(warner,"increasing stack size to %lu",newsize); + } + + void +diff --git a/src/test/dotest b/src/test/dotest +index 89c54c4..a101cfc 100755 +--- a/src/test/dotest ++++ b/src/test/dotest +@@ -80,7 +80,7 @@ for testdata in $testlist; do + for suf in $SUF; do + file_diff=$testname-$suf.dif + gp=$execdir/gp-$suf +- (echo 'gettime();0;'; cat $file_in; echo 'print("Total time spent: ",gettime);') | $RUNTEST $gp -q --test > $file_test 2>&1 ++ (echo 'gettime();default(debugmem,1);'; cat $file_in; echo 'print("Total time spent: ",gettime);') | $RUNTEST $gp -q --test > $file_test 2>&1 + diff -c $file_out $file_test > $file_diff + pat=`grep "^[-+!] " $file_diff | grep -v "Total time"` + time=`${tail}1 $file_test | sed -n 's,.*Total time spent: \(.*\),\1,p'` diff --git a/build/pkgs/pari/patches/warnmem.patch b/build/pkgs/pari/patches/warnmem.patch new file mode 100644 index 00000000000..2451917206b --- /dev/null +++ b/build/pkgs/pari/patches/warnmem.patch @@ -0,0 +1,37 @@ +commit e061b74744de9a8d4ab349ab81d8fa9afcdbccb5 +Author: Jeroen Demeyer +Date: Wed Jan 20 09:58:19 2016 +0100 + + Use DEBUGMEM>1 for all garbage collection warnings + +diff --git a/src/basemath/hnf_snf.c b/src/basemath/hnf_snf.c +index 8f0e346..4f2e3cb 100644 +--- a/src/basemath/hnf_snf.c ++++ b/src/basemath/hnf_snf.c +@@ -1352,7 +1352,7 @@ ZM_hnfcenter(GEN M) + for (i = 1; i <= j; i++) gel(Mk,i) = subii(gel(Mk,i), mulii(q,gel(Mj,i))); + if (gc_needed(av,1)) + { +- if (DEBUGMEM) pari_warn(warnmem,"ZM_hnfcenter, j = %ld",j); ++ if (DEBUGMEM>1) pari_warn(warnmem,"ZM_hnfcenter, j = %ld",j); + M = gerepilecopy(av, M); + } + } +@@ -1533,7 +1533,7 @@ ZM_hnflll(GEN A, GEN *ptB, int remove) + if (gc_needed(av,3)) + { + GEN b = D-1; +- if (DEBUGMEM) pari_warn(warnmem,"hnflll (reducing), kmax = %ld",kmax); ++ if (DEBUGMEM>1) pari_warn(warnmem,"hnflll (reducing), kmax = %ld",kmax); + gerepileall(av, B? 4: 3, &A, &lambda, &b, &B); + D = b+1; + } +@@ -1543,7 +1543,7 @@ ZM_hnflll(GEN A, GEN *ptB, int remove) + if (gc_needed(av,3)) + { + GEN b = D-1; +- if (DEBUGMEM) pari_warn(warnmem,"hnflll, kmax = %ld / %ld",kmax,n-1); ++ if (DEBUGMEM>1) pari_warn(warnmem,"hnflll, kmax = %ld / %ld",kmax,n-1); + gerepileall(av, B? 4: 3, &A, &lambda, &b, &B); + D = b+1; + } diff --git a/build/pkgs/pynac/checksums.ini b/build/pkgs/pynac/checksums.ini index 274a2a0a0a0..084dc92442b 100644 --- a/build/pkgs/pynac/checksums.ini +++ b/build/pkgs/pynac/checksums.ini @@ -1,4 +1,4 @@ tarball=pynac-VERSION.tar.bz2 -sha1=1c955b11dd52b6eefb43d5ebf00c3a735140faec -md5=4cdac770128d41e230b06e3c82d928e0 -cksum=2890824965 +sha1=8de4355307261c4012922560b4257e07606aabea +md5=551c7a8b07d8b5dbc7bf9adb8f710362 +cksum=226559555 diff --git a/build/pkgs/pynac/package-version.txt b/build/pkgs/pynac/package-version.txt index a918a2aa18d..ee6cdce3c29 100644 --- a/build/pkgs/pynac/package-version.txt +++ b/build/pkgs/pynac/package-version.txt @@ -1 +1 @@ -0.6.0 +0.6.1 diff --git a/build/pkgs/python2/spkg-install b/build/pkgs/python2/spkg-install index 875764dfb01..8cda95c9295 100755 --- a/build/pkgs/python2/spkg-install +++ b/build/pkgs/python2/spkg-install @@ -42,13 +42,26 @@ if [ "$SAGE_VALGRIND" = "yes" ]; then PYTHON_CONFIGURE="$PYTHON_CONFIGURE --without-pymalloc" fi -if [ `uname` = "Darwin" ]; then +# Use EXTRA_CFLAGS for user-defined CFLAGS since Python puts its own +# default flags like -O3 after CFLAGS but before EXTRA_CFLAGS. +# We also disable warnings about unused variables/functions which are +# common in Cython-generated code. +export EXTRA_CFLAGS="`testcflags.sh -Wno-unused` $CFLAGS" +unset CFLAGS + +if [ "$UNAME" = Darwin ]; then PYTHON_CONFIGURE="--disable-toolbox-glue $PYTHON_CONFIGURE" # Workaround for El Capitan, Trac #19626 xcode=$(xcode-select --print-path) mkdir "$CUR"/include cp -rp "${xcode}"/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-migrator/sdk/MacOSX.sdk/usr/include/openssl "$CUR"/include - export CFLAGS="$CFLAGS -I$CUR/include" + export CFLAGS="-I$CUR/include" +elif [ "$UNAME" = SunOS ]; then + # Enable some C99 features on Solaris. This in particular enables + # the isinf() and isfinite() functions. It works both for C and + # C++ code (which is not true for -std=c99). + # See http://trac.sagemath.org/sage_trac/ticket/14265 + export CFLAGS="-D__C99FEATURES__" fi if [ "$SAGE64" = yes ]; then @@ -56,14 +69,6 @@ if [ "$SAGE64" = yes ]; then export CC="$CC -m64" fi -# Enable some C99 features on Solaris. This in particular enables the -# isinf() and isfinite() functions. It works both for C and C++ code -# (which is not true for -std=c99). -# See http://trac.sagemath.org/sage_trac/ticket/14265 -if [ "$UNAME" = SunOS ]; then - export CFLAGS="-D__C99FEATURES__ $CFLAGS" -fi - build() { diff --git a/build/pkgs/rpy2/patches/cygwin.patch b/build/pkgs/rpy2/patches/cygwin.patch index 90641d43455..d2088e419e1 100644 --- a/build/pkgs/rpy2/patches/cygwin.patch +++ b/build/pkgs/rpy2/patches/cygwin.patch @@ -58,15 +58,6 @@ diff -ru rpy2-2.7.4.orig/rpy/rinterface/_rinterface.c rpy2-2.7.4/rpy/rinterface/ void win32CallBack() { /* called during i/o, eval, graphics in ProcessEvents */ -@@ -1489,7 +1489,7 @@ - } else { - rtruefalse = FALSE; - } --#if defined(Win32) || defined(Win64) -+#if defined(Win32) || defined(Win64) || defined(__CYGWIN__) - Rp->R_Interactive = rtruefalse; - #else - R_Interactive = rtruefalse; @@ -3749,7 +3749,7 @@ } @@ -103,3 +94,15 @@ diff -ru rpy2-2.7.4.orig/rpy/rinterface/_rinterface.c rpy2-2.7.4/rpy/rinterface/ NAComplex_Type.tp_base=&PyComplex_Type; #endif if (PyType_Ready(&NAComplex_Type) < 0) { +diff -ru rpy2-2.7.4.orig/setup rpy2-2.7.4/setup.py +--- rpy2-2.7.4.orig/setup.py 2016-01-15 01:45:48.447894400 -0800 ++++ rpy2-2.7.4/setup.py 2016-01-15 01:48:55.222874000 -0800 +@@ -160,7 +160,7 @@ + extra_link_args = [] + extra_compile_args = [] + include_dirs = [] +- libraries = [] ++ libraries = ['readline'] + library_dirs = [] + + #FIXME: crude way (will break in many cases) diff --git a/src/bin/sage-banner b/src/bin/sage-banner index 1d71aa46db5..ff246a65d98 100644 --- a/src/bin/sage-banner +++ b/src/bin/sage-banner @@ -1,5 +1,8 @@ ┌────────────────────────────────────────────────────────────────────┐ -│ SageMath Version 7.0, Release Date: 2016-01-19 │ +│ SageMath Version 7.1.beta1, Release Date: 2016-01-28 │ │ Type "notebook()" for the browser-based notebook interface. │ │ Type "help()" for help. │ └────────────────────────────────────────────────────────────────────┘ +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Warning: this is a prerelease version, and it may be unstable. ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 0853b4fc51d..72c99a71edc 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='7.0' -SAGE_RELEASE_DATE='2016-01-19' +SAGE_VERSION='7.1.beta1' +SAGE_RELEASE_DATE='2016-01-28' diff --git a/src/doc/common/conf.py b/src/doc/common/conf.py index 1b32a9a603c..e10433f7059 100644 --- a/src/doc/common/conf.py +++ b/src/doc/common/conf.py @@ -745,6 +745,9 @@ def skip_TESTS_block(app, what, name, obj, options, docstringlines): See sage.misc.sagedoc.skip_TESTS_block for more information. """ from sage.misc.sagedoc import skip_TESTS_block as sagedoc_skip_TESTS + if len(docstringlines) == 0: + # No docstring, so don't do anything. See :trac:`19932`. + return s = sagedoc_skip_TESTS("\n".join(docstringlines)) lines = s.split("\n") for i in range(len(lines)): diff --git a/src/doc/common/sage_autodoc.py b/src/doc/common/sage_autodoc.py index 67e05f109d8..54da7dcd4bc 100644 --- a/src/doc/common/sage_autodoc.py +++ b/src/doc/common/sage_autodoc.py @@ -42,7 +42,8 @@ from sphinx.util.pycompat import base_exception, class_types from sphinx.util.docstrings import prepare_docstring -from sage.misc.sageinspect import sage_getdoc_original, sage_getargspec, isclassinstance +from sage.misc.sageinspect import (sage_getdoc_original, + sage_getargspec, sage_formatargspec, isclassinstance) from sage.misc.lazy_import import LazyImport #: extended signature RE: with explicit module name separated by :: @@ -866,7 +867,7 @@ def format_args(self): argspec = self.args_on_obj(self.object) if argspec is None: return None - args = inspect.formatargspec(*argspec) + args = sage_formatargspec(*argspec) # escape backslashes for reST args = args.replace('\\', '\\\\') return args @@ -957,7 +958,7 @@ def format_args(self): argspec = sage_getargspec(initmeth) if argspec[0] and argspec[0][0] in ('cls', 'self'): del argspec[0][0] - return inspect.formatargspec(*argspec) + return sage_formatargspec(*argspec) def format_signature(self): if self.doc_as_attr: @@ -1114,7 +1115,9 @@ def args_on_obj(self, obj): def format_args(self): argspec = self.args_on_obj(self.object) - return inspect.formatargspec(*argspec) if argspec is not None else None + if argspec is None: + return None + return sage_formatargspec(*argspec) def document_members(self, all_members=False): pass diff --git a/src/doc/en/reference/algebras/index.rst b/src/doc/en/reference/algebras/index.rst index f07d5a3f78e..f9b04d812db 100644 --- a/src/doc/en/reference/algebras/index.rst +++ b/src/doc/en/reference/algebras/index.rst @@ -38,6 +38,8 @@ Algebras sage/algebras/hall_algebra sage/algebras/jordan_algebra + sage/algebras/orlik_solomon + sage/algebras/quatalg/quaternion_algebra sage/algebras/schur_algebra diff --git a/src/doc/en/reference/asymptotic/index.rst b/src/doc/en/reference/asymptotic/index.rst index 3a15f21a3d1..86ef7bb03ba 100644 --- a/src/doc/en/reference/asymptotic/index.rst +++ b/src/doc/en/reference/asymptotic/index.rst @@ -11,6 +11,14 @@ the module - :doc:`sage/rings/asymptotic/asymptotic_ring`. +Asymptotic Expansion Generators +------------------------------- + +Some common asymptotic expansions can be generated in + +- :doc:`sage/rings/asymptotic/asymptotic_expansion_generators`. + + Supplements ----------- @@ -51,6 +59,7 @@ Asymptotic Expansions --- Table of Contents .. toctree:: sage/rings/asymptotic/asymptotic_ring + sage/rings/asymptotic/asymptotic_expansion_generators sage/rings/asymptotic/growth_group sage/rings/asymptotic/growth_group_cartesian sage/rings/asymptotic/term_monoid diff --git a/src/doc/en/reference/calculus/index.rst b/src/doc/en/reference/calculus/index.rst index 981462f2206..08b764e1770 100644 --- a/src/doc/en/reference/calculus/index.rst +++ b/src/doc/en/reference/calculus/index.rst @@ -11,6 +11,7 @@ Symbolic Calculus sage/calculus/calculus sage/symbolic/units sage/symbolic/ring + sage/symbolic/subring sage/symbolic/function sage/symbolic/function_factory sage/calculus/functional diff --git a/src/doc/en/reference/coding/index.rst b/src/doc/en/reference/coding/index.rst index dc56b715243..94155007dbe 100644 --- a/src/doc/en/reference/coding/index.rst +++ b/src/doc/en/reference/coding/index.rst @@ -3,29 +3,66 @@ Coding Theory ============= +Abstract classes, catalogs and databases +---------------------------------------- + .. toctree:: - :maxdepth: 1 + :maxdepth: 2 sage/coding/decoder - sage/coding/decoders_catalog sage/coding/encoder - sage/coding/encoders_catalog - sage/coding/channel_constructions + sage/coding/bounds_catalog sage/coding/channels_catalog sage/coding/codes_catalog + sage/coding/decoders_catalog + sage/coding/encoders_catalog + sage/coding/two_weight_db + +Linear codes and related constructions +--------------------------------------- + +.. toctree:: + :maxdepth: 1 + + sage/coding/binary_code sage/coding/grs sage/coding/linear_code sage/coding/code_constructions - sage/coding/guava sage/coding/sd_codes - sage/coding/bounds_catalog + sage/coding/guava + +Bounds on codes +--------------- + +.. toctree:: + :maxdepth: 1 + sage/coding/code_bounds - sage/coding/codecan/codecan - sage/coding/codecan/autgroup_can_label sage/coding/delsarte_bounds + +Channels and related constructions +---------------------------------- + +.. toctree:: + :maxdepth: 2 + + sage/coding/channel_constructions + +Source coding +------------- + +.. toctree:: + :maxdepth: 1 + sage/coding/source_coding/huffman - sage/coding/binary_code - sage/coding/decoder - sage/coding/two_weight_db + +Canonical forms +--------------- + +.. toctree:: + :maxdepth: 1 + + sage/coding/codecan/codecan + sage/coding/codecan/autgroup_can_label .. include:: ../footer.txt diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index 88f0d74a3c0..11602a135e3 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -34,7 +34,7 @@ Comprehensive Module list sage/combinat/cluster_algebra_quiver/mutation_type sage/combinat/cluster_algebra_quiver/quiver sage/combinat/cluster_algebra_quiver/quiver_mutation_type - sage/combinat/colored_permutations + sage/combinat/colored_permutations sage/combinat/combinat sage/combinat/combinat_cython sage/combinat/combination @@ -70,6 +70,7 @@ Comprehensive Module list sage/combinat/crystals/littelmann_path sage/combinat/crystals/monomial_crystals sage/combinat/crystals/spins + sage/combinat/crystals/star_crystal sage/combinat/crystals/tensor_product sage/combinat/cyclic_sieving_phenomenon sage/combinat/debruijn_sequence diff --git a/src/doc/en/reference/finite_rings/index.rst b/src/doc/en/reference/finite_rings/index.rst index 15a1066f957..a3965ae540a 100644 --- a/src/doc/en/reference/finite_rings/index.rst +++ b/src/doc/en/reference/finite_rings/index.rst @@ -16,7 +16,7 @@ Finite Fields .. toctree:: :maxdepth: 2 - sage/rings/finite_rings/constructor + sage/rings/finite_rings/finite_field_constructor sage/rings/finite_rings/finite_field_base sage/rings/finite_rings/element_base sage/rings/finite_rings/homset diff --git a/src/doc/en/reference/index.rst b/src/doc/en/reference/index.rst index ed28a7ac84a..104cf4e2684 100644 --- a/src/doc/en/reference/index.rst +++ b/src/doc/en/reference/index.rst @@ -86,6 +86,7 @@ Geometry and Topology * :doc:`Combinatorial Geometry ` * :doc:`Cell Complexes and their Homology ` * :doc:`Differential Forms ` +* :doc:`Manifolds ` * :doc:`Parametrized Surfaces ` Number Theory, Algebraic Geometry diff --git a/src/doc/en/reference/libs/index.rst b/src/doc/en/reference/libs/index.rst index 7f60aac9e62..d701e91c76b 100644 --- a/src/doc/en/reference/libs/index.rst +++ b/src/doc/en/reference/libs/index.rst @@ -50,7 +50,6 @@ to be aware of the modules described in this chapter. sage/libs/libecm sage/libs/lrcalc/lrcalc sage/libs/pari/handle_error - sage/libs/pari/gen_py sage/libs/pari/gen sage/libs/pari/pari_instance sage/libs/pari/closure diff --git a/src/doc/en/reference/manifolds/conf.py b/src/doc/en/reference/manifolds/conf.py new file mode 120000 index 00000000000..2bdf7e68470 --- /dev/null +++ b/src/doc/en/reference/manifolds/conf.py @@ -0,0 +1 @@ +../conf_sub.py \ No newline at end of file diff --git a/src/doc/en/reference/manifolds/index.rst b/src/doc/en/reference/manifolds/index.rst new file mode 100644 index 00000000000..f0d5932b1f3 --- /dev/null +++ b/src/doc/en/reference/manifolds/index.rst @@ -0,0 +1,18 @@ +Manifolds +========= + +This is the Sage implementation of manifolds resulting from the +`SageManifolds project `_. +This section describes only the "manifold" part of SageManifolds; +the pure algebraic part is described in the section +:ref:`tensors-on-free-modules`. + +More documentation (in particular example worksheets) can be found +`here `_. + +.. toctree:: + :maxdepth: 2 + + manifold + +.. include:: ../footer.txt diff --git a/src/doc/en/reference/manifolds/manifold.rst b/src/doc/en/reference/manifolds/manifold.rst new file mode 100644 index 00000000000..d9364a345c3 --- /dev/null +++ b/src/doc/en/reference/manifolds/manifold.rst @@ -0,0 +1,15 @@ +Topological Manifolds +===================== + +.. toctree:: + :maxdepth: 2 + + sage/manifolds/manifold + + sage/manifolds/subset + + sage/manifolds/structure + + sage/manifolds/point + + sage/manifolds/chart diff --git a/src/doc/en/reference/modules/index.rst b/src/doc/en/reference/modules/index.rst index 94226e21741..c2d2a85ad95 100644 --- a/src/doc/en/reference/modules/index.rst +++ b/src/doc/en/reference/modules/index.rst @@ -46,4 +46,8 @@ Modules sage/modules/vector_real_double_dense sage/modules/vector_symbolic_dense + sage/modules/filtered_vector_space + sage/modules/multi_filtered_vector_space + sage/modules/tensor_operations + .. include:: ../footer.txt diff --git a/src/doc/en/reference/repl/index.rst b/src/doc/en/reference/repl/index.rst index 0b7b2d11074..1ddae8bf0e3 100644 --- a/src/doc/en/reference/repl/index.rst +++ b/src/doc/en/reference/repl/index.rst @@ -93,6 +93,7 @@ Miscellaneous sage/repl/interpreter sage/repl/ipython_extension + sage/repl/interface_magic sage/repl/ipython_kernel/install sage/repl/ipython_kernel/kernel sage/repl/ipython_tests diff --git a/src/doc/en/reference/schemes/index.rst b/src/doc/en/reference/schemes/index.rst index 7124500cb7b..47ff17ff5ba 100644 --- a/src/doc/en/reference/schemes/index.rst +++ b/src/doc/en/reference/schemes/index.rst @@ -79,5 +79,7 @@ Toric Varieties sage/schemes/toric/homset sage/schemes/toric/points + sage/schemes/toric/sheaf/constructor + sage/schemes/toric/sheaf/klyachko .. include:: ../footer.txt diff --git a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/modular_forms_and_hecke_operators.rst b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/modular_forms_and_hecke_operators.rst index f908b03c883..681f38cb3d9 100644 --- a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/modular_forms_and_hecke_operators.rst +++ b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/modular_forms_and_hecke_operators.rst @@ -167,7 +167,7 @@ dimension formulas. sage: a = [dimension_cusp_forms(Gamma0(N),2) for N in [1..25]]; a [0, 0, ..., 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 2, 2, 1, 0] sage: oeis(a) # optional - internet - 0: A001617: Genus of modular group GAMMA_0 (n). Or, genus of modular curve X_0(n). + 0: A001617: Genus of modular group Gamma_0(n). Or, genus of modular curve X_0(n). Sage doesn't have simple formulas for dimensions of spaces of modular forms of weight :math:`1`, since such formulas perhaps do diff --git a/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst b/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst index af5ed9821dd..847d838240e 100644 --- a/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst +++ b/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst @@ -366,7 +366,7 @@ SL versus GL ------------ Sage takes the weight space for type ``['A',r]`` to be `r+1` -dimensional. As a biproduct, if you create the Weyl character ring +dimensional. As a by-product, if you create the Weyl character ring with the command:: sage: A2 = WeylCharacterRing("A2") diff --git a/src/module_list.py b/src/module_list.py index 66e432267ee..49b681da530 100644 --- a/src/module_list.py +++ b/src/module_list.py @@ -656,6 +656,14 @@ def uname_specific(name, value, alternative): Extension('sage.libs.mpmath.ext_libmp', sources = ["sage/libs/mpmath/ext_libmp.pyx"]), + ################################### + ## + ## sage.libs.arb + ## + ################################### + + Extension('*', ["sage/libs/arb/*.pyx"]), + ################################### ## ## sage.libs.eclib @@ -903,6 +911,11 @@ def uname_specific(name, value, alternative): Extension('sage.matrix.matrix_window', sources = ['sage/matrix/matrix_window.pyx']), + OptionalExtension("sage.matrix.matrix_gfpn_dense", + sources = ['sage/matrix/matrix_gfpn_dense.pyx'], + libraries = ['mtx'], + package = 'meataxe'), + Extension('sage.matrix.misc', sources = ['sage/matrix/misc.pyx'], libraries=['mpfr']), @@ -1429,6 +1442,11 @@ def uname_specific(name, value, alternative): Extension('sage.rings.polynomial.cyclotomic', sources = ['sage/rings/polynomial/cyclotomic.pyx']), + Extension('sage.rings.polynomial.evaluation', + libraries = ["flint", "gmp", "ntl", "mpfr", "mpfi"], + sources = ['sage/rings/polynomial/evaluation.pyx'], + language = 'c++'), + Extension('sage.rings.polynomial.laurent_polynomial', sources = ['sage/rings/polynomial/laurent_polynomial.pyx']), @@ -1531,6 +1549,7 @@ def uname_specific(name, value, alternative): Extension('sage.rings.polynomial.symmetric_reduction', sources = ['sage/rings/polynomial/symmetric_reduction.pyx']), + ################################ ## ## sage.rings.semirings diff --git a/src/sage/algebras/affine_nil_temperley_lieb.py b/src/sage/algebras/affine_nil_temperley_lieb.py index 52e43ec58b8..4bc0cde250b 100644 --- a/src/sage/algebras/affine_nil_temperley_lieb.py +++ b/src/sage/algebras/affine_nil_temperley_lieb.py @@ -41,7 +41,7 @@ class AffineNilTemperleyLiebTypeA(CombinatorialFreeModule): sage: a[0]*a[3]*a[0] 0 sage: A.an_element() - 2*a0 + 3*a0*a1 + 1 + a0*a1*a2*a3 + 2*a0 + 1 + 3*a1 + a0*a1*a2*a3 """ def __init__(self, n, R = ZZ, prefix = 'a'): diff --git a/src/sage/algebras/catalog.py b/src/sage/algebras/catalog.py index 06910534633..7352c77cd83 100644 --- a/src/sage/algebras/catalog.py +++ b/src/sage/algebras/catalog.py @@ -30,6 +30,8 @@ ` - :class:`algebras.NilCoxeter ` +- :class:`algebras.OrlikSolomon + ` - :func:`algebras.Quaternion ` - :class:`algebras.Schur ` @@ -53,6 +55,7 @@ lazy_import('sage.algebras.free_zinbiel_algebra', 'FreeZinbielAlgebra', 'FreeZinbiel') lazy_import('sage.algebras.hall_algebra', 'HallAlgebra', 'Hall') lazy_import('sage.algebras.jordan_algebra', 'JordanAlgebra', 'Jordan') +lazy_import('sage.algebras.orlik_solomon', 'OrlikSolomonAlgebra', 'OrlikSolomon') lazy_import('sage.algebras.shuffle_algebra', 'ShuffleAlgebra', 'Shuffle') lazy_import('sage.algebras.schur_algebra', 'SchurAlgebra', 'Schur') lazy_import('sage.algebras.commutative_dga', 'GradedCommutativeAlgebra', 'GradedCommutative') diff --git a/src/sage/algebras/free_algebra_quotient.py b/src/sage/algebras/free_algebra_quotient.py index e4edc04a738..f95d76779a0 100644 --- a/src/sage/algebras/free_algebra_quotient.py +++ b/src/sage/algebras/free_algebra_quotient.py @@ -49,7 +49,6 @@ from sage.algebras.algebra import Algebra from sage.algebras.free_algebra import is_FreeAlgebra from sage.algebras.free_algebra_quotient_element import FreeAlgebraQuotientElement -from sage.structure.parent_gens import ParentWithGens from sage.structure.unique_representation import UniqueRepresentation class FreeAlgebraQuotient(UniqueRepresentation, Algebra, object): diff --git a/src/sage/algebras/nil_coxeter_algebra.py b/src/sage/algebras/nil_coxeter_algebra.py index 2411c5a48e3..b84bae29dc1 100644 --- a/src/sage/algebras/nil_coxeter_algebra.py +++ b/src/sage/algebras/nil_coxeter_algebra.py @@ -39,7 +39,7 @@ class NilCoxeterAlgebra(IwahoriHeckeAlgebra.T): sage: u2*u1*u2 == u1*u2*u1 True sage: U.an_element() - u[0,1,2,3] + 3*u[0,1] + 2*u[0] + 1 + u[0,1,2,3] + 2*u[0] + 3*u[1] + 1 """ def __init__(self, W, base_ring = QQ, prefix='u'): diff --git a/src/sage/algebras/orlik_solomon.py b/src/sage/algebras/orlik_solomon.py new file mode 100644 index 00000000000..e3d720a9b9d --- /dev/null +++ b/src/sage/algebras/orlik_solomon.py @@ -0,0 +1,452 @@ +r""" +Orlik-Solomon Algebras +""" + +#***************************************************************************** +# Copyright (C) 2015 William Slofstra +# Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.combinat.free_module import CombinatorialFreeModule +from sage.categories.algebras import Algebras +from sage.sets.family import Family + +class OrlikSolomonAlgebra(CombinatorialFreeModule): + r""" + An Orlik-Solomon algebra. + + Let `R` be a commutative ring. Let `M` be a matroid with ground set + `X`. Let `C(M)` denote the set of circuits of `M`. Let `E` denote + the exterior algera over `R` generated by `\{ e_x \mid x \in X \}`. + The *Orlik-Solomon ideal* `J(M)` is the ideal of `E` generated by + + .. MATH:: + + \partial e_S := \sum_{i=1}^t (-1)^{i-1} e_{j_1} \wedge e_{j_2} + \wedge \cdots \wedge \widehat{e}_{j_i} \wedge \cdots \wedge e_{j_t} + + for all `S = \left\{ j_1 < j_2 < \cdots < j_t \right\} \in C(M)`, + where `\widehat{e}_{j_i}` means that the term `e_{j_i}` is being + omitted. The notation `\partial e_S` is not a coincidence, as + `\partial e_S` is actually the image of + `e_S := e_{j_1} \wedge e_{j_2} \wedge \cdots \wedge e_{j_t}` under the + unique derivation `\partial` of `E` which sends all `e_x` to `1`. + + It is easy to see that `\partial e_S \in J(M)` not only for circuits + `S`, but also for any dependent set `S` of `M`. Moreover, every + dependent set `S` of `M` satisfies `e_S \in J(M)`. + + The *Orlik-Solomon algebra* `A(M)` is the quotient `E / J(M)`. This is + a graded finite-dimensional skew-commutative `R`-algebra. Fix + some ordering on `X`; then, the NBC sets of `M` (that is, the subsets + of `X` containing no broken circuit of `M`) form a basis of `A(M)`. + (Here, a *broken circuit* of `M` is defined to be the result of + removing the smallest element from a circuit of `M`.) + + In the current implementation, the basis of `A(M)` is indexed by the + NBC sets, which are implemented as frozensets. + + INPUT: + + - ``R`` -- the base ring + - ``M`` -- the defining matroid + - ``ordering`` -- (optional) an ordering of the ground set + + EXAMPLES: + + We create the Orlik-Solomon algebra of the uniform matroid `U(3, 4)` + and do some basic computations:: + + sage: M = matroids.Uniform(3, 4) + sage: OS = M.orlik_solomon_algebra(QQ) + sage: OS.dimension() + 14 + sage: G = OS.algebra_generators() + sage: M.broken_circuits() + frozenset({frozenset({1, 2, 3})}) + sage: G[1] * G[2] * G[3] + OS{0, 1, 2} - OS{0, 1, 3} + OS{0, 2, 3} + + REFERENCES: + + .. [CE01] Raul Cordovil and Gwihen Etienne. + *A note on the Orlik-Solomon algebra*. + Europ. J. Combinatorics. **22** (2001). pp. 165-170. + http://www.math.ist.utl.pt/~rcordov/Ce.pdf + + - :wikipedia:`Arrangement_of_hyperplanes#The_Orlik-Solomon_algebra` + """ + @staticmethod + def __classcall_private__(cls, R, M, ordering=None): + """ + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: M = matroids.Wheel(3) + sage: from sage.algebras.orlik_solomon import OrlikSolomonAlgebra + sage: OS1 = OrlikSolomonAlgebra(QQ, M) + sage: OS2 = OrlikSolomonAlgebra(QQ, M, ordering=(0,1,2,3,4,5)) + sage: OS3 = OrlikSolomonAlgebra(QQ, M, ordering=[0,1,2,3,4,5]) + sage: OS1 is OS2 and OS2 is OS3 + True + """ + if ordering is None: + ordering = sorted(M.groundset()) + return super(OrlikSolomonAlgebra, cls).__classcall__(cls, R, M, tuple(ordering)) + + def __init__(self, R, M, ordering=None): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: M = matroids.Wheel(3) + sage: OS = M.orlik_solomon_algebra(QQ) + sage: TestSuite(OS).run() + + We check on the matroid associated to the graph with 3 vertices and + 2 edges between each vertex:: + + sage: G = Graph([[1,2],[1,2],[2,3],[2,3],[1,3],[1,3]], multiedges=True) + sage: M = Matroid(G) + sage: OS = M.orlik_solomon_algebra(QQ) + sage: elts = OS.some_elements() + list(OS.algebra_generators()) + sage: TestSuite(OS).run(elements=elts) + """ + self._M = M + self._sorting = {x:i for i,x in enumerate(ordering)} + + # set up the dictionary of broken circuits + self._broken_circuits = dict() + for c in self._M.circuits(): + L = sorted(c, key=lambda x: self._sorting[x]) + self._broken_circuits[frozenset(L[1:])] = L[0] + + cat = Algebras(R).FiniteDimensional().WithBasis().Graded() + CombinatorialFreeModule.__init__(self, R, M.no_broken_circuits_sets(ordering), + prefix='OS', bracket='{', + generator_cmp=self._cmp_term, + category=cat) + + def _cmp_term(self, x, y): + """ + Compare the terms indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: M = matroids.Wheel(3) + sage: OS = M.orlik_solomon_algebra(QQ) + sage: OS._cmp_term(frozenset({1, 2}), frozenset({1, 4})) + -1 + sage: OS._cmp_term(frozenset({0, 1, 2}), frozenset({1, 4})) + -1 + sage: OS._cmp_term(frozenset({}), frozenset({1, 4})) + 1 + """ + c = cmp(len(x), len(y)) + if c != 0: + return -c + return cmp(sorted(x), sorted(y)) + + def _repr_term(self, m): + """ + Return a string representation of the basis element indexed by `m`. + + EXAMPLES:: + + sage: M = matroids.Uniform(3, 4) + sage: OS = M.orlik_solomon_algebra(QQ) + sage: OS._repr_term(frozenset([0])) + 'OS{0}' + """ + return "OS{{{}}}".format(str(list(m))[1:-1]) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: M = matroids.Wheel(3) + sage: M.orlik_solomon_algebra(QQ) + Orlik-Solomon algebra of Wheel(3): Regular matroid of rank 3 + on 6 elements with 16 bases + """ + return "Orlik-Solomon algebra of {}".format(self._M) + + @cached_method + def one_basis(self): + """ + Return the index of the basis element corresponding to `1` + in ``self``. + + EXAMPLES:: + + sage: M = matroids.Wheel(3) + sage: OS = M.orlik_solomon_algebra(QQ) + sage: OS.one_basis() == frozenset([]) + True + """ + return frozenset({}) + + @cached_method + def algebra_generators(self): + """ + Return the algebra generators of ``self``. + + These form a family indexed by the ground set `X` of `M`. For + each `x \in X`, the `x`-th element is `e_x`. + + EXAMPLES:: + + sage: M = matroids.Uniform(2, 2) + sage: OS = M.orlik_solomon_algebra(QQ) + sage: OS.algebra_generators() + Finite family {0: OS{0}, 1: OS{1}} + + sage: M = matroids.Uniform(1, 2) + sage: OS = M.orlik_solomon_algebra(QQ) + sage: OS.algebra_generators() + Finite family {0: OS{0}, 1: OS{0}} + + sage: M = matroids.Uniform(1, 3) + sage: OS = M.orlik_solomon_algebra(QQ) + sage: OS.algebra_generators() + Finite family {0: OS{0}, 1: OS{0}, 2: OS{0}} + """ + return Family(sorted(self._M.groundset()), + lambda i: self.subset_image(frozenset([i]))) + + @cached_method + def product_on_basis(self, a, b): + """ + Return the product in ``self`` of the basis elements + indexed by ``a`` and ``b``. + + EXAMPLES:: + + sage: M = matroids.Wheel(3) + sage: OS = M.orlik_solomon_algebra(QQ) + sage: OS.product_on_basis(frozenset([2]), frozenset([3,4])) + OS{0, 1, 2} - OS{0, 1, 4} + OS{0, 2, 3} + OS{0, 3, 4} + + :: + + sage: G = OS.algebra_generators() + sage: prod(G) + 0 + sage: G[2] * G[4] + -OS{1, 2} + OS{1, 4} + sage: G[3] * G[4] * G[2] + OS{0, 1, 2} - OS{0, 1, 4} + OS{0, 2, 3} + OS{0, 3, 4} + sage: G[2] * G[3] * G[4] + OS{0, 1, 2} - OS{0, 1, 4} + OS{0, 2, 3} + OS{0, 3, 4} + sage: G[3] * G[2] * G[4] + -OS{0, 1, 2} + OS{0, 1, 4} - OS{0, 2, 3} - OS{0, 3, 4} + + TESTS: + + Let us check that `e_{s_1} e_{s_2} \cdots e_{s_k} = e_S` for any + subset `S = \{ s_1 < s_2 < \cdots < s_k \}` of the ground set:: + + sage: G = Graph([[1,2],[1,2],[2,3],[3,4],[4,2]], multiedges=True) + sage: M = Matroid(G) + sage: E = M.groundset_list() + sage: OS = M.orlik_solomon_algebra(ZZ) + sage: G = OS.algebra_generators() + sage: import itertools + sage: def test_prod(F): + ....: LHS = OS.subset_image(frozenset(F)) + ....: RHS = OS.prod([G[i] for i in sorted(F)]) + ....: return LHS == RHS + sage: all( test_prod(F) for k in range(len(E)+1) + ....: for F in itertools.combinations(E, k) ) + True + """ + if not a: + return self.basis()[b] + if not b: + return self.basis()[a] + + if not a.isdisjoint(b): + return self.zero() + + R = self.base_ring() + # since a is disjoint from b, we can just multiply the generator + if len(a) == 1: + i = list(a)[0] + # insert i into nbc, keeping track of sign in coeff + ns = b.union({i}) + ns_sorted = sorted(ns, key=lambda x: self._sorting[x]) + coeff = (-1)**ns_sorted.index(i) + + return R(coeff) * self.subset_image(ns) + + # r is the accumalator + # we reverse a in the product, so add a sign + # note that l>=2 here + if len(a) % 4 < 2: + sign = R.one() + else: + sign = - R.one() + r = self._from_dict({b: sign}, remove_zeros=False) + + # now do the multiplication generator by generator + G = self.algebra_generators() + for i in sorted(a, key=lambda x: self._sorting[x]): + r = G[i] * r + + return r + + @cached_method + def subset_image(self, S): + """ + Return the element `e_S` of `A(M)` (``== self``) corresponding to + a subset `S` of the ground set of `M`. + + INPUT: + + - ``S`` -- a frozenset which is a subset of the ground set of `M` + + EXAMPLES:: + + sage: M = matroids.Wheel(3) + sage: OS = M.orlik_solomon_algebra(QQ) + sage: BC = sorted(M.broken_circuits(), key=sorted) + sage: for bc in BC: (sorted(bc), OS.subset_image(bc)) + ([1, 3], -OS{0, 1} + OS{0, 3}) + ([1, 4, 5], OS{0, 1, 4} - OS{0, 1, 5} - OS{0, 3, 4} + OS{0, 3, 5}) + ([2, 3, 4], OS{0, 1, 2} - OS{0, 1, 4} + OS{0, 2, 3} + OS{0, 3, 4}) + ([2, 3, 5], OS{0, 2, 3} + OS{0, 3, 5}) + ([2, 4], -OS{1, 2} + OS{1, 4}) + ([2, 5], -OS{0, 2} + OS{0, 5}) + ([4, 5], -OS{3, 4} + OS{3, 5}) + + sage: M4 = matroids.CompleteGraphic(4) + sage: OS = M4.orlik_solomon_algebra(QQ) + sage: OS.subset_image(frozenset({2,3,4})) + OS{0, 2, 3} + OS{0, 3, 4} + + An example of a custom ordering:: + + sage: G = Graph([[3, 4], [4, 1], [1, 2], [2, 3], [3, 5], [5, 6], [6, 3]]) + sage: M = Matroid(G) + sage: s = [(5, 6), (1, 2), (3, 5), (2, 3), (1, 4), (3, 6), (3, 4)] + sage: sorted([sorted(c) for c in M.circuits()]) + [[(1, 2), (1, 4), (2, 3), (3, 4)], + [(3, 5), (3, 6), (5, 6)]] + sage: OS = M.orlik_solomon_algebra(QQ, ordering=s) + sage: OS.subset_image(frozenset([])) + OS{} + sage: OS.subset_image(frozenset([(1,2),(3,4),(1,4),(2,3)])) + 0 + sage: OS.subset_image(frozenset([(2,3),(1,2),(3,4)])) + OS{(1, 2), (3, 4), (2, 3)} + sage: OS.subset_image(frozenset([(1,4),(3,4),(2,3),(3,6),(5,6)])) + -OS{(1, 2), (5, 6), (2, 3), (1, 4), (3, 6)} + + OS{(1, 2), (5, 6), (3, 4), (1, 4), (3, 6)} + - OS{(1, 2), (5, 6), (3, 4), (2, 3), (3, 6)} + sage: OS.subset_image(frozenset([(1,4),(3,4),(2,3),(3,6),(3,5)])) + OS{(1, 2), (5, 6), (2, 3), (1, 4), (3, 5)} + - OS{(1, 2), (5, 6), (2, 3), (1, 4), (3, 6)} + + OS{(1, 2), (5, 6), (3, 4), (1, 4), (3, 5)} + + OS{(1, 2), (5, 6), (3, 4), (1, 4), (3, 6)} + - OS{(1, 2), (5, 6), (3, 4), (2, 3), (3, 5)} + - OS{(1, 2), (5, 6), (3, 4), (2, 3), (3, 6)} + + TESTS:: + + sage: G = Graph([[1,2],[1,2],[2,3],[2,3],[1,3],[1,3]], multiedges=True) + sage: M = Matroid(G) + sage: sorted([sorted(c) for c in M.circuits()]) + [[0, 1], [0, 2, 4], [0, 2, 5], [0, 3, 4], + [0, 3, 5], [1, 2, 4], [1, 2, 5], [1, 3, 4], + [1, 3, 5], [2, 3], [4, 5]] + sage: OS = M.orlik_solomon_algebra(QQ) + sage: OS.subset_image(frozenset([])) + OS{} + sage: OS.subset_image(frozenset([1, 2, 3])) + 0 + sage: OS.subset_image(frozenset([1, 3, 5])) + 0 + sage: OS.subset_image(frozenset([1, 2])) + OS{0, 2} + sage: OS.subset_image(frozenset([3, 4])) + -OS{0, 2} + OS{0, 4} + sage: OS.subset_image(frozenset([1, 5])) + OS{0, 4} + + sage: G = Graph([[1,2],[1,2],[2,3],[3,4],[4,2]], multiedges=True) + sage: M = Matroid(G) + sage: sorted([sorted(c) for c in M.circuits()]) + [[0, 1], [2, 3, 4]] + sage: OS = M.orlik_solomon_algebra(QQ) + sage: OS.subset_image(frozenset([])) + OS{} + sage: OS.subset_image(frozenset([1, 3, 4])) + -OS{0, 2, 3} + OS{0, 2, 4} + + We check on a non-standard ordering:: + + sage: M = matroids.Wheel(3) + sage: o = [5,4,3,2,1,0] + sage: OS = M.orlik_solomon_algebra(QQ, ordering=o) + sage: BC = sorted(M.broken_circuits(ordering=o), key=sorted) + sage: for bc in BC: (sorted(bc), OS.subset_image(bc)) + ([0, 1], OS{0, 3} - OS{1, 3}) + ([0, 1, 4], OS{0, 3, 5} - OS{0, 4, 5} - OS{1, 3, 5} + OS{1, 4, 5}) + ([0, 2], OS{0, 5} - OS{2, 5}) + ([0, 2, 3], -OS{0, 3, 5} + OS{2, 3, 5}) + ([1, 2], OS{1, 4} - OS{2, 4}) + ([1, 2, 3], -OS{1, 3, 5} + OS{1, 4, 5} + OS{2, 3, 5} - OS{2, 4, 5}) + ([3, 4], OS{3, 5} - OS{4, 5}) + """ + if not isinstance(S, frozenset): + raise ValueError("S needs to be a frozenset") + for bc in self._broken_circuits: + if bc.issubset(S): + i = self._broken_circuits[bc] + if i in S: + # ``S`` contains not just a broken circuit, but an + # actual circuit; then `e_S = 0`. + return self.zero() + coeff = self.base_ring().one() + # Now, reduce ``S``, and build the result ``r``: + r = self.zero() + switch = False + Si = S.union({i}) + Ss = sorted(Si, key=lambda x: self._sorting[x]) + for j in Ss: + if j in bc: + r += coeff * self.subset_image(Si.difference({j})) + if switch: + coeff *= -1 + if j == i: + switch = True + return r + else: # So ``S`` is an NBC set. + return self.monomial(S) + + def degree_on_basis(self, m): + """ + Return the degree of the basis element indexed by ``m``. + + EXAMPLES:: + + sage: M = matroids.Wheel(3) + sage: OS = M.orlik_solomon_algebra(QQ) + sage: OS.degree_on_basis(frozenset([1])) + 1 + sage: OS.degree_on_basis(frozenset([0, 2, 3])) + 3 + """ + return len(m) + diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 331b02ab6b8..4729da631eb 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -38,7 +38,7 @@ from sage.rings.all import RR, Integer from sage.rings.integer_ring import ZZ from sage.rings.rational import Rational -from sage.rings.finite_rings.constructor import GF +from sage.rings.finite_rings.finite_field_constructor import GF from sage.rings.ring import Algebra from sage.rings.ideal import Ideal_fractional diff --git a/src/sage/all.py b/src/sage/all.py index 658a3451a36..297d7d03131 100644 --- a/src/sage/all.py +++ b/src/sage/all.py @@ -174,6 +174,8 @@ from sage.game_theory.all import * +from sage.manifolds.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/arith/misc.py b/src/sage/arith/misc.py index 6690669d8ac..4fc5df2f790 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -259,6 +259,7 @@ def bernoulli(n, algorithm='default', num_threads=1): - ``'default'`` -- use 'flint' for n <= 300000, and 'bernmm' otherwise (this is just a heuristic, and not guaranteed to be optimal on all hardware) + - ``'arb'`` -- use the arb library - ``'flint'`` -- use the FLINT library - ``'pari'`` -- use the PARI C library - ``'gap'`` -- use GAP @@ -278,6 +279,8 @@ def bernoulli(n, algorithm='default', num_threads=1): We demonstrate each of the alternative algorithms:: + sage: bernoulli(12, algorithm='arb') + -691/2730 sage: bernoulli(12, algorithm='flint') -691/2730 sage: bernoulli(12, algorithm='gap') @@ -295,7 +298,7 @@ def bernoulli(n, algorithm='default', num_threads=1): TESTS:: - sage: algs = ['gap','gp','pari','bernmm','flint'] + sage: algs = ['arb','gap','gp','pari','bernmm','flint'] sage: test_list = [ZZ.random_element(2, 2255) for _ in range(500)] sage: vals = [[bernoulli(i,algorithm = j) for j in algs] for i in test_list] # long time (up to 21s on sage.math, 2011) sage: union([len(union(x))==1 for x in vals]) # long time (depends on previous line) @@ -315,7 +318,10 @@ def bernoulli(n, algorithm='default', num_threads=1): if algorithm == 'default': algorithm = 'flint' if n <= 300000 else 'bernmm' - if algorithm == 'flint': + if algorithm == 'arb': + import sage.libs.arb.arith as arb_arith + return arb_arith.bernoulli(n) + elif algorithm == 'flint': return flint_arith.bernoulli_number(n) elif algorithm == 'pari': x = pari(n).bernfrac() # Use the PARI C library diff --git a/src/sage/categories/coxeter_groups.py b/src/sage/categories/coxeter_groups.py index f98faf1f33e..80e15a2d3e7 100644 --- a/src/sage/categories/coxeter_groups.py +++ b/src/sage/categories/coxeter_groups.py @@ -210,7 +210,7 @@ def __iter__(self): [ 0 1 0] [ 0 0 1] sage: next(g) - [ 0 -1 2] + [ 1 0 0] [ 1 -1 1] [ 0 0 1] """ @@ -235,7 +235,7 @@ def weak_order_ideal(self, predicate, side ="right", category = None): sage: I.cardinality() 7 sage: list(I) - [(), (1,), (1, 2), (1, 2, 1), (2,), (2, 1), (2, 1, 2)] + [(), (1,), (2,), (1, 2), (2, 1), (1, 2, 1), (2, 1, 2)] We now consider an infinite Coxeter group:: @@ -243,7 +243,7 @@ def weak_order_ideal(self, predicate, side ="right", category = None): sage: I = W.weak_order_ideal(predicate = lambda w: w.length() <= 2) sage: list(iter(I)) [ - [1 0] [-1 2] [ 3 -2] [ 1 0] [-1 2] + [1 0] [-1 2] [ 1 0] [ 3 -2] [-1 2] [0 1], [ 0 1], [ 2 -1], [ 2 -1], [-2 3] ] @@ -262,7 +262,7 @@ def weak_order_ideal(self, predicate, side ="right", category = None): 5 sage: list(I) [ - [1 0] [-1 2] [ 3 -2] [ 1 0] [-1 2] + [1 0] [-1 2] [ 1 0] [ 3 -2] [-1 2] [0 1], [ 0 1], [ 2 -1], [ 2 -1], [-2 3] ] @@ -277,6 +277,15 @@ def weak_order_ideal(self, predicate, side ="right", category = None): roughly Constant Amortized Time and constant memory (taking the operations and size of the generated objects as constants). + + TESTS: + + We iterate over each level (i.e., breadth-first-search in the + search forest), see :trac:`19926`:: + + sage: W = CoxeterGroup(['A',2]) + sage: [x.length() for x in W] + [0, 1, 1, 2, 2, 3] """ from sage.combinat.backtrack import SearchForest def succ(u): @@ -287,15 +296,17 @@ def succ(u): return from sage.categories.finite_coxeter_groups import FiniteCoxeterGroups default_category = FiniteEnumeratedSets() if self in FiniteCoxeterGroups() else EnumeratedSets() - return SearchForest((self.one(),), succ, category = default_category.or_subcategory(category)) + return SearchForest((self.one(),), succ, algorithm='breadth', + category = default_category.or_subcategory(category)) - def grassmannian_elements(self, side = "right"): + def grassmannian_elements(self, side="right"): """ - INPUT: + Return the left or right grassmanian elements of ``self`` + as an enumerated set. - - ``side``: "left" or "right" (default: "right") + INPUT: - Returns the left or right grassmanian elements of self, as an enumerated set + - ``side`` -- (default: ``"right"``) ``"left"`` or ``"right"`` EXAMPLES:: @@ -304,7 +315,9 @@ def grassmannian_elements(self, side = "right"): sage: G.cardinality() 12 sage: G.list() - [(0, 1, 2, 3), (1, 0, 2, 3), (2, 0, 1, 3), (3, 0, 1, 2), (0, 2, 1, 3), (1, 2, 0, 3), (0, 3, 1, 2), (1, 3, 0, 2), (2, 3, 0, 1), (0, 1, 3, 2), (0, 2, 3, 1), (1, 2, 3, 0)] + [(0, 1, 2, 3), (1, 0, 2, 3), (0, 2, 1, 3), (0, 1, 3, 2), + (2, 0, 1, 3), (1, 2, 0, 3), (0, 3, 1, 2), (0, 2, 3, 1), + (3, 0, 1, 2), (1, 3, 0, 2), (1, 2, 3, 0), (2, 3, 0, 1)] sage: sorted(tuple(w.descents()) for w in G) [(), (0,), (0,), (0,), (1,), (1,), (1,), (1,), (1,), (2,), (2,), (2,)] sage: G = S.grassmannian_elements(side = "left") @@ -314,7 +327,8 @@ def grassmannian_elements(self, side = "right"): [(), (0,), (0,), (0,), (1,), (1,), (1,), (1,), (1,), (2,), (2,), (2,)] """ order_side = "left" if side == "right" else "right" - return self.weak_order_ideal(attrcall("is_grassmannian", side = side), side = order_side) + return self.weak_order_ideal(attrcall("is_grassmannian", side=side), + side=order_side) def from_reduced_word(self, word): r""" diff --git a/src/sage/categories/examples/finite_coxeter_groups.py b/src/sage/categories/examples/finite_coxeter_groups.py index 5b011e8444e..2242c5f487d 100644 --- a/src/sage/categories/examples/finite_coxeter_groups.py +++ b/src/sage/categories/examples/finite_coxeter_groups.py @@ -48,15 +48,15 @@ class DihedralGroup(UniqueRepresentation, Parent): sage: list(G) [(), - (1,), - (1, 2), - (1, 2, 1), - (1, 2, 1, 2), - (1, 2, 1, 2, 1), - (2,), - (2, 1), - (2, 1, 2), - (2, 1, 2, 1)] + (1,), + (2,), + (1, 2), + (2, 1), + (1, 2, 1), + (2, 1, 2), + (1, 2, 1, 2), + (2, 1, 2, 1), + (1, 2, 1, 2, 1)] This reduced word is unique, except for the longest element where the choosen reduced word is `(1,2,1,2\dots)`:: @@ -229,10 +229,10 @@ def apply_simple_reflection_right(self, i): EXAMPLES:: sage: D5 = FiniteCoxeterGroups().example(5) - sage: [i^2 for i in D5] - [(), (), (1, 2, 1, 2), (), (2, 1), (), (), (2, 1, 2, 1), (), (1, 2)] - sage: [i^5 for i in D5] - [(), (1,), (), (1, 2, 1), (), (1, 2, 1, 2, 1), (2,), (), (2, 1, 2), ()] + sage: [i^2 for i in D5] # indirect doctest + [(), (), (), (1, 2, 1, 2), (2, 1, 2, 1), (), (), (2, 1), (1, 2), ()] + sage: [i^5 for i in D5] # indirect doctest + [(), (1,), (2,), (), (), (1, 2, 1), (2, 1, 2), (), (), (1, 2, 1, 2, 1)] """ from copy import copy reduced_word = copy(self.value) diff --git a/src/sage/categories/finite_coxeter_groups.py b/src/sage/categories/finite_coxeter_groups.py index 8c332ea803b..30f0d33ed8e 100644 --- a/src/sage/categories/finite_coxeter_groups.py +++ b/src/sage/categories/finite_coxeter_groups.py @@ -68,7 +68,7 @@ class ParentMethods: sage: W.some_elements() [(1,), (2,), (), (1, 2)] sage: list(W) - [(), (1,), (1, 2), (1, 2, 1), (2,), (2, 1)] + [(), (1,), (2,), (1, 2), (2, 1), (1, 2, 1)] """ some_elements = CoxeterGroups.ParentMethods.__dict__["some_elements"] __iter__ = CoxeterGroups.ParentMethods.__dict__["__iter__"] diff --git a/src/sage/categories/magmas.py b/src/sage/categories/magmas.py index 24e868de6c8..c71f8a0470a 100644 --- a/src/sage/categories/magmas.py +++ b/src/sage/categories/magmas.py @@ -500,6 +500,107 @@ def is_empty(self): """ return False + class ElementMethods: + + def __truediv__(left, right): + """ + Return the result of the division of ``left`` by ``right``, if possible. + + This top-level implementation delegates the work to + the ``_div_`` method if ``left`` and ``right`` have + the same parent and to coercion otherwise. See the + extensive documentation at the top of + :ref:`sage.structure.element`. + + .. SEEALSO:: :meth:`_div_` + + EXAMPLES:: + + sage: G = FreeGroup(2) + sage: x0, x1 = G.group_generators() + sage: c1 = cartesian_product([x0, x1]) + sage: c2 = cartesian_product([x1, x0]) + sage: c1.__div__(c2) + (x0*x1^-1, x1*x0^-1) + sage: c1 / c2 + (x0*x1^-1, x1*x0^-1) + + Division supports coercion:: + + sage: C = cartesian_product([G, G]) + sage: H = Hom(G, C) + sage: phi = H(lambda g: cartesian_product([g, g])) + sage: phi.register_as_coercion() + sage: x1 / c1 + (x1*x0^-1, 1) + sage: c1 / x1 + (x0*x1^-1, 1) + + Depending on how the division itself is implemented in + :meth:`_div_`, division may fail even when ``right`` + actually divides ``left``:: + + sage: x = cartesian_product([2, 1]) + sage: y = cartesian_product([1, 1]) + sage: x / y + (2, 1) + sage: x / x + Traceback (most recent call last): + ... + TypeError: no conversion of this rational to integer + + TESTS:: + + sage: c1.__div__.__module__ + 'sage.categories.magmas' + """ + from sage.structure.element import have_same_parent + if have_same_parent(left, right): + return left._div_(right) + from sage.structure.element import get_coercion_model + import operator + return get_coercion_model().bin_op(left, right, operator.div) + __div__ = __truediv__ # For Python2/3 compatibility; see e.g. #18578 + + def _div_(left, right): + r""" + Default implementation of division, multiplying (on the right) by the inverse. + + INPUT: + + - ``left``, ``right`` -- two elements of the same unital magma + + .. SEEALSO:: :meth:`__div__` + + EXAMPLES:: + + sage: G = FreeGroup(2) + sage: x0, x1 = G.group_generators() + sage: c1 = cartesian_product([x0, x1]) + sage: c2 = cartesian_product([x1, x0]) + sage: c1._div_(c2) + (x0*x1^-1, x1*x0^-1) + + With this implementation, division will fail as soon + as ``right`` is not invertible, even if ``right`` + actually divides ``left``:: + + sage: x = cartesian_product([2, 1]) + sage: y = cartesian_product([1, 1]) + sage: x / y + (2, 1) + sage: x / x + Traceback (most recent call last): + ... + TypeError: no conversion of this rational to integer + + TESTS:: + + sage: c1._div_.__module__ + 'sage.categories.magmas' + """ + return left._mul_(~right) + class SubcategoryMethods: @cached_method diff --git a/src/sage/categories/posets.py b/src/sage/categories/posets.py index 52baeec2e41..4226e3569ea 100644 --- a/src/sage/categories/posets.py +++ b/src/sage/categories/posets.py @@ -285,8 +285,6 @@ def order_ideal(self, elements): [0, 1, 2, 3, 4, 5, 6, 7, 8, 10] """ - lower_set = order_ideal - @abstract_method(optional = True) def order_filter(self, elements): r""" @@ -304,8 +302,6 @@ def order_filter(self, elements): [3, 7, 8, 9, 10, 11, 12, 13, 14, 15] """ - upper_set = order_filter - def directed_subset(self, elements, direction): r""" Return the order filter or the order ideal generated by a diff --git a/src/sage/coding/binary_code.pyx b/src/sage/coding/binary_code.pyx index 7cb979bd6b3..9673810afed 100644 --- a/src/sage/coding/binary_code.pyx +++ b/src/sage/coding/binary_code.pyx @@ -1,5 +1,5 @@ r""" -Fast binary code routines. +Fast binary code routines Some computations with linear binary codes. Fix a basis for $GF(2)^n$. A linear binary code is a linear subspace of $GF(2)^n$, together with diff --git a/src/sage/coding/channel_constructions.py b/src/sage/coding/channel_constructions.py index 44c3cdab68e..05f07447f67 100644 --- a/src/sage/coding/channel_constructions.py +++ b/src/sage/coding/channel_constructions.py @@ -5,6 +5,29 @@ the input space (the message) and transforms it into an element of the output space (the transmitted message). +In Sage, Channels simulate error-prone transmission over communication +channels, and we borrow the nomenclature from communication theory, such as +"transmission" and "positions" as the elements of transmitted vectors. +Transmission can be achieved with two methods: + +- :meth:`Channel.transmit`. Considering a channel ``Chan`` and a message + ``msg``, transmitting ``msg`` with ``Chan`` can be done this way:: + + Chan.transmit(msg) + + It can also be written in a more convenient way:: + + Chan(msg) + +- :meth:`transmit_unsafe`. This does the exact same thing as + :meth:`transmit` except that it does not check if ``msg`` belongs to the + input space of ``Chan``:: + + Chan.transmit_unsafe(msg) + +This is useful in e.g. an inner-loop of a long simulation as a +lighter-weight alternative to :meth:`Channel.transmit`. + This file contains the following elements: - :class:`Channel`, the abstract class for Channels @@ -26,12 +49,13 @@ from sage.structure.sage_object import SageObject from sage.rings.integer import Integer -from sage.rings.finite_rings.constructor import GF +from sage.rings.finite_rings.finite_field_constructor import GF from sage.misc.prandom import randint, random, sample from sage.modules.free_module_element import vector from sage.misc.abstract_method import abstract_method from sage.categories.cartesian_product import cartesian_product from sage.modules.free_module import VectorSpace +from sage.functions.other import binomial from copy import copy def random_error_vector(n, F, error_positions): @@ -272,24 +296,6 @@ class StaticErrorRateChannel(Channel): The input space and the output space of this channel are the same. - The main purpose of communication channels is to transmit messages, which can be achieved with - two methods: - - - with the method :meth:`Channel.transmit`. Considering a channel ``Chan`` - and a message ``msg``, transmitting - ``msg`` with ``Chan`` can be done this way:: - - Chan.transmit(msg) - - It can also be written in a more convenient way:: - - Chan(msg) - - - with the method :meth:`transmit_unsafe`. It does the exact same thing as :meth:`transmit` except - that it does not check if ``msg`` belongs to the input space of ``Chan``:: - - Chan.transmit_unsafe(msg) - INPUT: - ``space`` -- the space of both input and output @@ -448,24 +454,6 @@ class ErrorErasureChannel(Channel): The output space of this channel is a Cartesian product between its input space and a VectorSpace of the same dimension over GF(2) - The main purpose of communication channels is to transmit messages, which can be achieved with - two methods: - - - with the method :meth:`Channel.transmit`. Considering a channel ``Chan`` - and a message ``msg``, transmitting - ``msg`` with ``Chan`` can be done this way:: - - Chan.transmit(msg) - - It can also be written in a more convenient way:: - - Chan(msg) - - - with the method :meth:`transmit_unsafe`. It does the exact same thing as :meth:`transmit` except - that it does not check if ``msg`` belongs to the input space of ``Chan``:: - - Chan.transmit_unsafe(msg) - INPUT: - ``space`` -- the input and output space @@ -658,3 +646,197 @@ def number_erasures(self): (3, 3) """ return self._number_erasures + + + + + + + + + + +class QarySymmetricChannel(Channel): + r""" + The q-ary symmetric, memoryless communication channel. + + Given an alphabet `\Sigma` with `|\Sigma| = q` and an error probability + `\epsilon`, a q-ary symmetric channel sends an element of `\Sigma` into the + same element with probability `1 - \epsilon`, and any one of the other `q - + 1` elements with probability `\frac{\epsilon}{q - 1}`. This implementation + operates over vectors in `\Sigma^n`, and "transmits" each element of the + vector independently in the above manner. + + Though `\Sigma` is usually taken to be a finite field, this implementation + allows any structure for which Sage can represent `\Sigma^n` and for which + `\Sigma` has a `random_element()` method. However, beware that if `\Sigma` + is infinite, errors will not be uniformly distributed (since + `random_element()` does not draw uniformly at random). + + The input space and the output space of this channel are the same: + `\Sigma^n`. + + INPUT: + + - ``space`` -- the input and output space of the channel. It has to be + `GF(q)^n` for some finite field `GF(q)`. + + - ``epsilon`` -- the transmission error probability of the individual elements. + + EXAMPLES: + + We construct a QarySymmetricChannel which corrupts 30% of all transmitted + symbols:: + + sage: epsilon = 0.3 + sage: Chan = channels.QarySymmetricChannel(GF(59)^50, epsilon) + sage: Chan + q-ary symmetric channel with error probability 0.300000000000000, + of input and output space Vector space of dimension 50 over Finite Field of size 59 + """ + + def __init__(self, space, epsilon): + r""" + TESTS: + + If ``space`` is not a vector space, an error is raised:: + + sage: epsilon = 0.42 + sage: Chan = channels.QarySymmetricChannel(GF(59), epsilon) + Traceback (most recent call last): + ... + ValueError: space has to be of the form Sigma^n, where Sigma has a random_element() method + + If ``epsilon`` is not between 0 and 1, an error is raised:: + + sage: epsilon = 42 + sage: Chan = channels.QarySymmetricChannel(GF(59)^50, epsilon) + Traceback (most recent call last): + ... + ValueError: Error probability must be between 0 and 1 + """ + if epsilon >= 1 or epsilon <= 0: + raise ValueError("Error probability must be between 0 and 1") + + super(QarySymmetricChannel, self).__init__(space, space) + self._epsilon = epsilon + try: + self.transmit_unsafe(space.random_element()) + except: + raise ValueError("space has to be of the form Sigma^n, where Sigma has a random_element() method") + + def __repr__(self): + r""" + Returns a string representation of ``self``. + + EXAMPLES:: + + sage: epsilon = 0.3 + sage: Chan = channels.QarySymmetricChannel(GF(59)^50, epsilon) + sage: Chan + q-ary symmetric channel with error probability 0.300000000000000, + of input and output space Vector space of dimension 50 over Finite Field of size 59 + """ + return "q-ary symmetric channel with error probability %s, of input and output space %s"\ + % (self.error_probability(), self.input_space()) + + def _latex_(self): + r""" + Returns a latex representation of ``self``. + + EXAMPLES:: + + sage: epsilon = 0.3 + sage: Chan = channels.QarySymmetricChannel(GF(59)^50, epsilon) + sage: latex(Chan) + \textnormal{q-ary symmetric channel with error probability 0.300000000000000, + of input and output space Vector space of dimension 50 over Finite Field of size 59} + """ + return "\\textnormal{q-ary symmetric channel with error probability %s, of input and output space %s}"\ + % (self.error_probability(), self.input_space()) + + def transmit_unsafe(self, message): + r""" + Returns ``message`` where each of the symbols has been changed to another from the alphabet with + probability :meth:`error_probability`. + + This method does not check if ``message`` belongs to the input space of``self``. + + INPUT: + + - ``message`` -- a vector + + EXAMPLES:: + + sage: F = GF(59)^11 + sage: epsilon = 0.3 + sage: Chan = channels.QarySymmetricChannel(F, epsilon) + sage: msg = F((3, 14, 15, 9, 26, 53, 58, 9, 7, 9, 3)) + sage: set_random_seed(10) + sage: Chan.transmit_unsafe(msg) + (3, 14, 15, 53, 12, 53, 58, 9, 55, 9, 3) + """ + epsilon = self.error_probability() + V = self.input_space() + F = V.base_ring() + msg = copy(message.list()) + for i in range(len(msg)): + if random() <= epsilon: + a = F.random_element() + while a == msg[i]: + a = F.random_element() + msg[i] = a + return V(msg) + + def error_probability(self): + r""" + Returns the error probability of a single symbol transmission of + ``self``. + + EXAMPLES:: + + sage: epsilon = 0.3 + sage: Chan = channels.QarySymmetricChannel(GF(59)^50, epsilon) + sage: Chan.error_probability() + 0.300000000000000 + """ + return self._epsilon + + def probability_of_exactly_t_errors(self, t): + r""" + Returns the probability ``self`` has to return + exactly ``t`` errors. + + INPUT: + + - ``t`` -- an integer + + EXAMPLES:: + + sage: epsilon = 0.3 + sage: Chan = channels.QarySymmetricChannel(GF(59)^50, epsilon) + sage: Chan.probability_of_exactly_t_errors(15) + 0.122346861835401 + """ + n = self.input_space().dimension() + epsilon = self.error_probability() + return binomial(n, t) * epsilon**t * (1-epsilon)**(n-t) + + def probability_of_at_most_t_errors(self, t): + r""" + Returns the probability ``self`` has to return + at most ``t`` errors. + + INPUT: + + - ``t`` -- an integer + + EXAMPLES:: + + sage: epsilon = 0.3 + sage: Chan = channels.QarySymmetricChannel(GF(59)^50, epsilon) + sage: Chan.probability_of_at_most_t_errors(20) + 0.952236164579467 + """ + return sum(self.probability_of_exactly_t_errors(i) + for i in range(t+1)) diff --git a/src/sage/coding/channels_catalog.py b/src/sage/coding/channels_catalog.py index 625826d4bd7..be93f670cb6 100644 --- a/src/sage/coding/channels_catalog.py +++ b/src/sage/coding/channels_catalog.py @@ -1,11 +1,13 @@ r""" -Index of Channels +Index of channels The ``channels`` object may be used to access the codes that Sage can build. - :func:`channel_constructions.ErrorErasureChannel ` - :func:`channel_constructions.StaticErrorRateChannel ` +- :func:`channel_constructions.QarySymmetricChannel ` + .. NOTE:: To import these names into the global namespace, use: @@ -14,4 +16,4 @@ """ -from channel_constructions import (ErrorErasureChannel, StaticErrorRateChannel) +from channel_constructions import (ErrorErasureChannel, StaticErrorRateChannel, QarySymmetricChannel) diff --git a/src/sage/coding/code_constructions.py b/src/sage/coding/code_constructions.py index 7d44067ee47..1dfe0a579ff 100644 --- a/src/sage/coding/code_constructions.py +++ b/src/sage/coding/code_constructions.py @@ -152,7 +152,7 @@ from sage.matrix.matrix_space import MatrixSpace from sage.matrix.constructor import matrix -from sage.rings.finite_rings.constructor import FiniteField as GF +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.groups.perm_gps.permgroup_named import SymmetricGroup from sage.misc.all import prod from linear_code import LinearCodeFromVectorSpace, LinearCode diff --git a/src/sage/coding/codecan/autgroup_can_label.pyx b/src/sage/coding/codecan/autgroup_can_label.pyx index 6f171adf165..8ba1397c562 100644 --- a/src/sage/coding/codecan/autgroup_can_label.pyx +++ b/src/sage/coding/codecan/autgroup_can_label.pyx @@ -1,5 +1,5 @@ r""" -Canonical forms and automorphisms for linear codes over finite fields. +Canonical forms and automorphisms for linear codes over finite fields We implemented the algorithm described in [Feu2009]_ which computes, a unique code (canonical form) in the equivalence class of a given diff --git a/src/sage/coding/codecan/codecan.pyx b/src/sage/coding/codecan/codecan.pyx index 7b0bec30e4a..b8ca45c6cde 100644 --- a/src/sage/coding/codecan/codecan.pyx +++ b/src/sage/coding/codecan/codecan.pyx @@ -1,5 +1,5 @@ r""" -Canonical forms and automorphism group computation for linear codes over finite fields. +Canonical forms and automorphism group computation for linear codes over finite fields We implemented the algorithm described in [Feu2009]_ which computes the unique semilinearly isometric code (canonical form) in the equivalence class of a given diff --git a/src/sage/coding/codes_catalog.py b/src/sage/coding/codes_catalog.py index dea34888d76..dae184932d7 100644 --- a/src/sage/coding/codes_catalog.py +++ b/src/sage/coding/codes_catalog.py @@ -1,5 +1,5 @@ r""" -Index of Codes +Index of codes The ``codes`` object may be used to access the codes that Sage can build. diff --git a/src/sage/coding/delsarte_bounds.py b/src/sage/coding/delsarte_bounds.py index bfc864a6ab1..08014e2ebb6 100644 --- a/src/sage/coding/delsarte_bounds.py +++ b/src/sage/coding/delsarte_bounds.py @@ -1,7 +1,7 @@ r""" -Delsarte, a.k.a. Linear Programming (LP), upper bounds. +Delsarte, a.k.a. Linear Programming (LP), upper bounds -This module provides LP upper bounds for the parameters of codes. +This module provides LP upper bounds for the parameters of codes. Exact LP solver, PPL, is used by defaut, ensuring that no rounding/overflow problems occur. diff --git a/src/sage/coding/grs.py b/src/sage/coding/grs.py index d72514bddce..8d7434e29a9 100644 --- a/src/sage/coding/grs.py +++ b/src/sage/coding/grs.py @@ -1,5 +1,5 @@ r""" -Generalized Reed-Solomon Code +Generalized Reed-Solomon code Given `n` different evaluation points `\alpha_1, \dots, \alpha_n` from some finite field `F`, and `n` column multipliers `\beta_1, \dots, \beta_n`, the diff --git a/src/sage/coding/guava.py b/src/sage/coding/guava.py index 43842884356..29fcf0cac79 100644 --- a/src/sage/coding/guava.py +++ b/src/sage/coding/guava.py @@ -1,5 +1,5 @@ r""" -Guava error-correcting code constructions. +Guava error-correcting code constructions This module only contains Guava wrappers (Guava is an optional GAP package). @@ -34,7 +34,7 @@ from sage.interfaces.all import gap from sage.misc.randstate import current_randstate from sage.matrix.matrix_space import MatrixSpace -from sage.rings.finite_rings.constructor import FiniteField as GF +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.interfaces.gap import gfq_gap_to_sage from sage.groups.perm_gps.permgroup import * from linear_code import * diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index d3dcc3e7033..732e40f9aa7 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Linear Codes +Linear code VERSION: 1.2 @@ -217,7 +217,7 @@ from sage.categories.fields import Fields from copy import copy from sage.interfaces.all import gap -from sage.rings.finite_rings.constructor import FiniteField as GF +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.groups.perm_gps.permgroup import PermutationGroup from sage.matrix.matrix_space import MatrixSpace from sage.matrix.constructor import Matrix diff --git a/src/sage/coding/sd_codes.py b/src/sage/coding/sd_codes.py index 1c36ed125ee..d04a9b7140b 100644 --- a/src/sage/coding/sd_codes.py +++ b/src/sage/coding/sd_codes.py @@ -88,7 +88,7 @@ Math 3 (1972) 209-246. """ from sage.misc.lazy_import import lazy_import -from sage.rings.finite_rings.constructor import FiniteField as GF +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.matrix.matrix_space import MatrixSpace lazy_import("sage.coding.linear_code", "LinearCode") #from linear_code import LinearCode diff --git a/src/sage/coding/two_weight_db.py b/src/sage/coding/two_weight_db.py index 6752eb498e1..07f3f5516f5 100644 --- a/src/sage/coding/two_weight_db.py +++ b/src/sage/coding/two_weight_db.py @@ -39,7 +39,7 @@ ....: assert (code['w1'], code['w2']) == (w1, w2) """ -from sage.rings.finite_rings.constructor import FiniteField as GF +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.matrix.constructor import Matrix from sage.coding.linear_code import LinearCode diff --git a/src/sage/combinat/binary_recurrence_sequences.py b/src/sage/combinat/binary_recurrence_sequences.py index b82c52c9e8d..2aac724cc84 100644 --- a/src/sage/combinat/binary_recurrence_sequences.py +++ b/src/sage/combinat/binary_recurrence_sequences.py @@ -67,7 +67,7 @@ from sage.matrix.constructor import matrix, vector from sage.rings.number_field.number_field import QuadraticField from sage.rings.finite_rings.integer_mod_ring import Integers -from sage.rings.finite_rings.constructor import GF +from sage.rings.finite_rings.finite_field_constructor import GF from sage.rings.integer import Integer from sage.arith.all import gcd, lcm, next_prime, is_prime, next_prime_power, legendre_symbol from sage.functions.log import log diff --git a/src/sage/combinat/crystals/catalog_infinity_crystals.py b/src/sage/combinat/crystals/catalog_infinity_crystals.py index 36e785f78e6..680921a4677 100644 --- a/src/sage/combinat/crystals/catalog_infinity_crystals.py +++ b/src/sage/combinat/crystals/catalog_infinity_crystals.py @@ -5,10 +5,12 @@ * :class:`GeneralizedYoungWalls ` +* :class:`LSPaths ` * :class:`NakajimaMonomials ` * :class:`PolyhedralRealization ` * :class:`RiggedConfigurations ` +* :class:`Star ` * :class:`Tableaux ` """ from generalized_young_walls import InfinityCrystalOfGeneralizedYoungWalls as GeneralizedYoungWalls @@ -16,4 +18,6 @@ from sage.combinat.rigged_configurations.rc_infinity import InfinityCrystalOfRiggedConfigurations as RiggedConfigurations from infinity_crystals import InfinityCrystalOfTableaux as Tableaux from sage.combinat.crystals.polyhedral_realization import InfinityCrystalAsPolyhedralRealization as PolyhedralRealization +from sage.combinat.crystals.star_crystal import StarCrystal as Star +from sage.combinat.crystals.littelmann_path import InfinityCrystalOfLSPaths as LSPaths diff --git a/src/sage/combinat/crystals/littelmann_path.py b/src/sage/combinat/crystals/littelmann_path.py index a2f68709eb2..0797003bb5f 100644 --- a/src/sage/combinat/crystals/littelmann_path.py +++ b/src/sage/combinat/crystals/littelmann_path.py @@ -5,7 +5,9 @@ - Mark Shimozono, Anne Schilling (2012): Initial version - Anne Schilling (2013): Implemented - :class:`~sage.combinat.crystals.littlemann_path.CrystalOfProjectedLevelZeroLSPaths` + :class:`~sage.combinat.crystals.littelmann_path.CrystalOfProjectedLevelZeroLSPaths` +- Travis Scrimshaw (2016): Implemented + :class:`~sage.combinat.crystals.littelmann_path.InfinityCrystalOfLSPaths` """ #**************************************************************************** # Copyright (C) 2012 Mark Shimozono @@ -23,7 +25,7 @@ # http://www.gnu.org/licenses/ #**************************************************************************** -from sage.misc.cachefunc import cached_in_parent_method +from sage.misc.cachefunc import cached_in_parent_method, cached_method from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element_wrapper import ElementWrapper from sage.structure.parent import Parent @@ -124,7 +126,7 @@ def __classcall_private__(cls, starting_weight, cartan_type = None, starting_wei Classcall to mend the input. Internally, the - :class:`~sage.combinat.crystals.littlemann_path.CrystalOfLSPaths` code + :class:`~sage.combinat.crystals.littelmann_path.CrystalOfLSPaths` code works with a ``starting_weight`` that is in the weight space associated to the crystal. The user can, however, also input a ``cartan_type`` and the coefficients of the fundamental weights as @@ -178,7 +180,7 @@ def __init__(self, starting_weight, starting_weight_parent): sage: C.weight.parent() Extended weight space over the Rational Field of the Root system of type ['A', 2, 1] sage: C.module_generators - [(-Lambda[0] + Lambda[2],)] + ((-Lambda[0] + Lambda[2],),) TESTS:: @@ -219,10 +221,10 @@ def __init__(self, starting_weight, starting_weight_parent): Parent.__init__(self, category = ClassicalCrystals()) if starting_weight == starting_weight.parent().zero(): - initial_element = self(tuple([])) + initial_element = self(()) else: - initial_element = self(tuple([starting_weight])) - self.module_generators = [initial_element] + initial_element = self((starting_weight,)) + self.module_generators = (initial_element,) def _repr_(self): """ @@ -297,18 +299,7 @@ def compress(self): sage: c.compress() (Lambda[1] + Lambda[2],) """ - def positively_parallel_weights(v, w): - """ - Checks whether the vectors ``v`` and ``w`` are positive scalar multiples of each other. - """ - supp = v.support() - if len(supp) > 0: - i = supp[0] - if v[i]*w[i] > 0 and v[i]*w == w[i]*v: - return True - return False - - if len(self.value) == 0: + if not self.value: return self q = [] curr = self.value[0] @@ -626,6 +617,10 @@ def _latex_(self): return [latex(p) for p in self.value] +##################################################################### +## Projected level-zero + + class CrystalOfProjectedLevelZeroLSPaths(CrystalOfLSPaths): r""" Crystal of projected level zero LS paths. @@ -636,7 +631,7 @@ class CrystalOfProjectedLevelZeroLSPaths(CrystalOfLSPaths): When ``weight`` is just a single fundamental weight `\Lambda_r`, this crystal is isomorphic to a Kirillov-Reshetikhin (KR) crystal, see also - :meth:`sage.combinat.crystals.kirillov_reshetikhin.crystals.KirillovReshetikhinFromLSPaths`. + :meth:`sage.combinat.crystals.kirillov_reshetikhin.KirillovReshetikhinFromLSPaths`. For general weights, it is isomorphic to a tensor product of single-column KR crystals. EXAMPLES:: @@ -677,9 +672,9 @@ def __classcall_private__(cls, weight): Classcall to mend the input. Internally, the - :class:`~sage.combinat.crystals.littlemann_path.CrystalOfProjectedLevelZeroLSPaths` + :class:`~sage.combinat.crystals.littelmann_path.CrystalOfProjectedLevelZeroLSPaths` uses a level zero weight, which is passed on to - :class:`~sage.combinat.crystals.littlemann_path.CrystalOfLSPaths`. + :class:`~sage.combinat.crystals.littelmann_path.CrystalOfLSPaths`. ``weight`` is first coerced to a level zero weight. TESTS:: @@ -1116,3 +1111,320 @@ def stretch_short_root(a): return s/2 else: return s + + +##################################################################### +## B(\infty) + + +class InfinityCrystalOfLSPaths(UniqueRepresentation, Parent): + r""" + LS path model for `\mathcal{B}(\infty)`. + + Elements of `\mathcal{B}(\infty)` are equivalence classes of paths `[\pi]` + in `\mathcal{B}(k\rho)` for `k\gg 0`, where `\rho` is the Weyl vector. A + canonical representative for an element of `\mathcal{B}(\infty)` is chosen + by taking `k` to be minimal such that the endpoint of `\pi` is strictly + dominant but its representative in `\mathcal{B}((k-1)\rho)` is on the wall + of the dominant chamber. + + REFERENCES: + + .. [LZ11] Bin Li and Hechun Zhang. + *Path realization of crystal* `B(\infty)`. + Front. Math. China, **6** (4), (2011) pp. 689--706. + :doi:`10.1007/s11464-010-0073-x` + """ + @staticmethod + def __classcall_private__(cls, cartan_type): + """ + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: B1 = crystals.infinity.LSPaths(['A',4]) + sage: B2 = crystals.infinity.LSPaths('A4') + sage: B3 = crystals.infinity.LSPaths(CartanType(['A',4])) + sage: B1 is B2 and B2 is B3 + True + """ + cartan_type = CartanType(cartan_type) + return super(InfinityCrystalOfLSPaths, cls).__classcall__(cls, cartan_type) + + def __init__(self, cartan_type): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: B = crystals.infinity.LSPaths(['D',4,3]) + sage: TestSuite(B).run(max_runs=500) + sage: B = crystals.infinity.LSPaths(['B',3]) + sage: TestSuite(B).run() # long time + """ + Parent.__init__(self, category=(HighestWeightCrystals(), + InfiniteEnumeratedSets())) + self._cartan_type = cartan_type + self.module_generators = (self.module_generator(),) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: crystals.infinity.LSPaths(['A',4]) + The infinity crystal of LS paths of type ['A', 4] + """ + return "The infinity crystal of LS paths of type %s" % self._cartan_type + + @cached_method + def module_generator(self): + r""" + Return the module generator (or highest weight element) of ``self``. + + The module generator is the unique path + `\pi_\infty\colon t \mapsto t\rho`, for `t \in [0,\infty)`. + + EXAMPLES:: + + sage: B = crystals.infinity.LSPaths(['A',6,2]) + sage: mg = B.module_generator(); mg + (Lambda[0] + Lambda[1] + Lambda[2] + Lambda[3],) + sage: mg.weight() + 0 + """ + rho = self.weight_lattice_realization().rho() + return self((rho,)) + + def weight_lattice_realization(self): + """ + Return the weight lattice realization of ``self``. + + EXAMPLES:: + + sage: B = crystals.infinity.LSPaths(['C',4]) + sage: B.weight_lattice_realization() + Weight space over the Rational Field of the Root system of type ['C', 4] + """ + if self._cartan_type.is_affine(): + return self._cartan_type.root_system().weight_space(extended=True) + return self._cartan_type.root_system().weight_space() + + class Element(CrystalOfLSPaths.Element): + + def e(self, i, power=1, length_only=False): + r""" + Return the `i`-th crystal raising operator on ``self``. + + INPUT: + + - ``i`` -- element of the index set + - ``power`` -- (default: 1) positive integer; specifies the + power of the lowering operator to be applied + - ``length_only`` -- (default: ``False``) boolean; if ``True``, + then return the distance to the anti-dominant end of the + `i`-string of ``self`` + + EXAMPLES:: + + sage: B = crystals.infinity.LSPaths(['B',3,1]) + sage: mg = B.module_generator() + sage: mg.e(0) + sage: mg.e(1) + sage: mg.e(2) + sage: x = mg.f_string([1,0,2,1,0,2,1,1,0]) + sage: all(x.f(i).e(i) == x for i in B.index_set()) + True + sage: all(x.e(i).f(i) == x for i in B.index_set() if x.epsilon(i) > 0) + True + + TESTS: + + Check that this works in affine types:: + + sage: B = crystals.infinity.LSPaths(['A',3,1]) + sage: mg = B.highest_weight_vector() + sage: x = mg.f_string([0,1,2,3]) + sage: x.e_string([3,2,1,0]) == mg + True + + We check that :meth:`epsilon` works:: + + sage: B = crystals.infinity.LSPaths(['D',4]) + sage: mg = B.highest_weight_vector() + sage: x = mg.f_string([1,3,4,2,4,3,2,1,4]) + sage: [x.epsilon(i) for i in B.index_set()] + [1, 1, 0, 1] + """ + ret = super(InfinityCrystalOfLSPaths.Element, self).e(i, power=power, + length_only=length_only) + if ret is None: + return None + if length_only: + return ret + WLR = self.parent().weight_lattice_realization() + value = list(ret.value) + endpoint = sum(p for p in value) + rho = WLR.rho() + h = WLR.simple_coroots() + I = self.parent().index_set() + + if not positively_parallel_weights(value[-1], rho): + value.append(rho) + endpoint += rho + + while any(endpoint.scalar(alc) < 1 for alc in h): + value[-1] += rho + endpoint += rho + while all(endpoint.scalar(alc) > 1 for alc in h): + value[-1] -= rho + endpoint -= rho + while value[-1] == WLR.zero(): + value.pop() + ret.value = tuple(value) + return ret + + def f(self, i, power=1, length_only=False): + r""" + Return the `i`-th crystal lowering operator on ``self``. + + INPUT: + + - ``i`` -- element of the index set + - ``power`` -- (default: 1) positive integer; specifies the + power of the lowering operator to be applied + - ``length_only`` -- (default: ``False``) boolean; if ``True``, + then return the distance to the anti-dominant end of the + `i`-string of ``self`` + + EXAMPLES:: + + sage: B = crystals.infinity.LSPaths(['D',3,2]) + sage: mg = B.highest_weight_vector() + sage: mg.f(1) + (3*Lambda[0] - Lambda[1] + 3*Lambda[2], + 2*Lambda[0] + 2*Lambda[1] + 2*Lambda[2]) + sage: mg.f(2) + (Lambda[0] + 2*Lambda[1] - Lambda[2], + 2*Lambda[0] + 2*Lambda[1] + 2*Lambda[2]) + sage: mg.f(0) + (-Lambda[0] + 2*Lambda[1] + Lambda[2] - delta, + 2*Lambda[0] + 2*Lambda[1] + 2*Lambda[2]) + """ + dual_path = self.dualize() + dual_path = super(InfinityCrystalOfLSPaths.Element, dual_path).e(i, power, length_only=length_only) + if length_only: + return dual_path + if dual_path is None: + return None + ret = dual_path.dualize() + WLR = self.parent().weight_lattice_realization() + value = list(ret.value) + endpoint = sum(p for p in value) + rho = WLR.rho() + h = WLR.simple_coroots() + + if not positively_parallel_weights(value[-1], rho): + value.append(rho) + endpoint += rho + + while any(endpoint.scalar(alc) < 1 for alc in h): + value[-1] += rho + endpoint += rho + while all(endpoint.scalar(alc) > 1 for alc in h): + value[-1] -= rho + endpoint -= rho + while value[-1] == WLR.zero(): + value.pop() + ret.value = tuple(value) + return ret + + @cached_method + def weight(self): + """ + Return the weight of ``self``. + + .. TODO:: + + This is a generic algorithm. We should find a better + description and implement it. + + EXAMPLES:: + + sage: B = crystals.infinity.LSPaths(['E',6]) + sage: mg = B.highest_weight_vector() + sage: f_seq = [1,4,2,6,4,2,3,1,5,5] + sage: x = mg.f_string(f_seq) + sage: x.weight() + -3*Lambda[1] - 2*Lambda[2] + 2*Lambda[3] + Lambda[4] - Lambda[5] + + sage: al = B.cartan_type().root_system().weight_space().simple_roots() + sage: x.weight() == -sum(al[i] for i in f_seq) + True + """ + WLR = self.parent().weight_lattice_realization() + alpha = WLR.simple_roots() + return -WLR.sum(alpha[i] for i in self.to_highest_weight()[1]) + + def phi(self,i): + r""" + Return `\varphi_i` of ``self``. + + Let `\pi \in \mathcal{B}(\infty)`. Define + + .. MATH:: + + \varphi_i(\pi) := \varepsilon_i(\pi) + \langle h_i, + \mathrm{wt}(\pi) \rangle, + + where `h_i` is the `i`-th simple coroot and `\mathrm{wt}(\pi)` + is the :meth:`weight` of `\pi`. + + INPUT: + + - ``i`` -- element of the index set + + EXAMPLES:: + + sage: B = crystals.infinity.LSPaths(['D',4]) + sage: mg = B.highest_weight_vector() + sage: x = mg.f_string([1,3,4,2,4,3,2,1,4]) + sage: [x.phi(i) for i in B.index_set()] + [-1, 4, -2, -3] + """ + WLR = self.parent().weight_lattice_realization() + h = WLR.simple_coroots() + return self.epsilon(i) + WLR(self.weight()).scalar(h[i]) + + +##################################################################### +## Helper functions + + +def positively_parallel_weights(v, w): + """ + Check whether the vectors ``v`` and ``w`` are positive scalar + multiples of each other. + + EXAMPLES:: + + sage: from sage.combinat.crystals.littelmann_path import positively_parallel_weights + sage: La = RootSystem(['A',5,2]).weight_space(extended=True).fundamental_weights() + sage: rho = sum(La) + sage: positively_parallel_weights(rho, 4*rho) + True + sage: positively_parallel_weights(4*rho, rho) + True + sage: positively_parallel_weights(rho, -rho) + False + sage: positively_parallel_weights(rho, La[1] + La[2]) + False + """ + supp = v.support() + if len(supp) > 0: + i = supp[0] + if v[i]*w[i] > 0 and v[i]*w == w[i]*v: + return True + return False + diff --git a/src/sage/combinat/crystals/star_crystal.py b/src/sage/combinat/crystals/star_crystal.py new file mode 100644 index 00000000000..b29bd3531f4 --- /dev/null +++ b/src/sage/combinat/crystals/star_crystal.py @@ -0,0 +1,297 @@ +r""" +Star-Crystal Structure On `B(\infty)` + +AUTHORS: + +- Ben Salisbury: Initial version + +- Travis Scrimshaw: Initial version +""" + +#***************************************************************************** +# Copyright (C) 2016 Ben Salisbury +# Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.element_wrapper import ElementWrapper +from sage.categories.highest_weight_crystals import HighestWeightCrystals +from sage.combinat.root_system.cartan_type import CartanType +from sage.combinat.crystals.elementary_crystals import ElementaryCrystal +from sage.combinat.crystals.tensor_product import TensorProductOfCrystals + + +class StarCrystal(UniqueRepresentation, Parent): + r""" + The star-crystal or `*`-crystal version of a highest weight crystal. + + The `*`-crystal structure on `B(\infty)` is the structure induced by + the algebra antiautomorphism `* \colon U_q(\mathfrak{g}) \longrightarrow + U_q(\mathfrak{g})` that stabilizes the negative half `U_q^-(\mathfrak{g})`. + It is defined by + + .. MATH:: + + E_i^* = E_i , \ \ \ + F_i^* = F_i , \ \ \ + q^* = q, \ \ \ + (q^h)^* = q^{-h}, + + where `E_i` and `F_i` are the Chevalley generators of `U_q(\mathfrak{g})` + and `h` is an element of the Cartan subalgebra. + + The induced operation on the crystal `B(\infty)` is called the + *Kashiwara involution*. Its implementation here is based on the + recursive algorithm from Theorem 2.2.1 of [Kash95]_, which states + that for any `i \in I` there is a unique strict crystal embedding + + .. MATH:: + + \Psi_i\colon B(\infty) \longrightarrow B_i \otimes B(\infty) + + such that + + - `u_{\infty} \mapsto b_i(0) \otimes u_{\infty}`, where `u_{\infty}` + is the highest weight vector in `B(\infty)`; + + - if `\Psi_i(b) = f_i^mb_i(0) \otimes b_0`, then + `\Psi_i(f_i^*b) =f_i^{m+1}b_i(0) \otimes b_0` + and `\varepsilon_i(b^*) = m`; + + - the image of `\Psi_i` is `\{f_i^mb_i(0)\otimes b : + \varepsilon_i(b^*) = 0, \ m\ge 0\}`. + + Here, `B_i` is the `i`-th elementary crystal. See + :class:`~sage.combinat.crystals.elementary_crystals.ElementaryCrystal` + for more information. + + INPUT: + + - ``Binf`` -- a crystal from + :class:`~sage.combinat.crystals.catalog_infinity_crystals` + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(['A',2]) + sage: Bstar = crystals.infinity.Star(B) + sage: mg = Bstar.highest_weight_vector() + sage: mg + [[1, 1], [2]] + sage: mg.f_string([1,2,1,2,2]) + [[1, 1, 1, 1, 1, 2, 2], [2, 3, 3, 3]] + + REFERENCES: + + .. [Kash95] M. Kashiwara. + The crystal base and Littelmann's refined Demazure character formula. + Duke Math. J. **71** (1993), no. 3, 839-858. + """ + def __init__(self, Binf): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(['A',2]) + sage: Bstar = crystals.infinity.Star(B) + sage: TestSuite(Bstar).run(max_runs=40) + sage: TestSuite(Bstar).run(max_runs=1000) # long time + """ + self._Binf = Binf + self._cartan_type = Binf.cartan_type() + Parent.__init__(self, category=HighestWeightCrystals().Infinite()) + self.module_generators = (self(self._Binf.module_generators[0]),) + t0 = Binf.highest_weight_vector() + B = {i: ElementaryCrystal(Binf.cartan_type(),i) for i in self.index_set()} + self._tens = {i: B[i].tensor(Binf) for i in self.index_set()} + gens = {i: self._tens[i](B[i](0), t0) for i in self.index_set()} + self._embedding = {i: Binf.crystal_morphism({t0: gens[i]}) for i in self.index_set()} + self._pullback = {i: self._tens[i].crystal_morphism({gens[i]: t0}) for i in self.index_set()} + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: Y = crystals.infinity.GeneralizedYoungWalls(3) + sage: Ystar = crystals.infinity.Star(Y) + sage: Ystar + Star-crystal version of Crystal of generalized Young walls of type ['A', 3, 1] + """ + return "Star-crystal version of %s" % self._Binf + + class Element(ElementWrapper): + + def e(self,i): + r""" + Return the action of `e_i^*` on ``self``. + + INPUT: + + - ``i`` -- an element of the index set + + EXAMPLES:: + + sage: RC = crystals.infinity.RiggedConfigurations(['E',6,1]) + sage: RCstar = crystals.infinity.Star(RC) + sage: nuJ = RCstar.module_generators[0].f_string([0,4,6,1,2]) + sage: ascii_art(nuJ.e(1)) + -1[ ]-1 (/) 0[ ]1 (/) -1[ ]-1 (/) -2[ ]-1 + + sage: M = crystals.infinity.NakajimaMonomials(['B',2,1]) + sage: Mstar = crystals.infinity.Star(M) + sage: m = Mstar.module_generators[0].f_string([0,1,2,2,1,0]) + sage: m.e(1) + Y(0,0)^-1 Y(0,2)^-1 Y(1,1) Y(1,2)^-1 Y(2,1)^2 + """ + P = self.parent() + image = P._embedding[i](self.value) + if image[0].e(i)._m > 0: + return None + return P(P._pullback[i]( P._tens[i](image[0].e(i),image[1]) )) + + def f(self,i): + r""" + Return the action of `f_i^*` on ``self``. + + INPUT: + + - ``i`` -- an element of the index set + + EXAMPLES:: + + sage: T = crystals.infinity.Tableaux("G2") + sage: Tstar = crystals.infinity.Star(T) + sage: t = Tstar.module_generators[0].f_string([1,2,1,1,2]) + sage: t + [[1, 1, 1, 2, 0], [2, 3]] + + sage: M = crystals.infinity.NakajimaMonomials(['B',2,1]) + sage: Mstar = crystals.infinity.Star(M) + sage: m = Mstar.module_generators[0].f_string([0,1,2,2,1,0]) + sage: m + Y(0,0)^-1 Y(0,2)^-1 Y(1,0)^-1 Y(1,2)^-1 Y(2,0)^2 Y(2,1)^2 + """ + P = self.parent() + image = P._embedding[i](self.value) + return P(P._pullback[i]( P._tens[i](image[0].f(i),image[1]) )) + + def weight(self): + r""" + Return the weight of ``self``. + + EXAMPLES:: + + sage: RC = crystals.infinity.RiggedConfigurations(['E',6,1]) + sage: RCstar = crystals.infinity.Star(RC) + sage: nuJ = RCstar.module_generators[0].f_string([0,4,6,1,2]) + sage: nuJ.weight() + -Lambda[0] - 2*Lambda[1] + 2*Lambda[3] - Lambda[4] + + 2*Lambda[5] - 2*Lambda[6] - delta + """ + return self.value.weight() + + def epsilon(self, i): + r""" + Return `\varepsilon_i^*` of ``self``. + + INPUT: + + - ``i`` -- an element of the index set + + EXAMPLES:: + + sage: Y = crystals.infinity.GeneralizedYoungWalls(3) + sage: Ystar = crystals.infinity.Star(Y) + sage: y = Ystar.module_generators[0].f_string([0,1,3,2,1,0]) + sage: [y.epsilon(i) for i in y.index_set()] + [1, 0, 1, 0] + + sage: RC = crystals.infinity.RiggedConfigurations(['E',6,1]) + sage: RCstar = crystals.infinity.Star(RC) + sage: nuJ = RCstar.module_generators[0].f_string([0,4,6,1,2]) + sage: [nuJ.epsilon(i) for i in nuJ.index_set()] + [0, 1, 1, 0, 0, 0, 1] + """ + ep = -1 + while self is not None: + ep += 1 + self = self.e(i) + return ep + + def phi(self, i): + r""" + Return `\varphi_i^*` of ``self``. + + For `b \in B(\infty)`, + + .. MATH:: + + \varphi_i^*(b) = \varepsilon_i^*(b) + \langle h_i, + \mathrm{wt}(b) \rangle, + + where `h_i` is a simple coroot. + + INPUT: + + - ``i`` -- an element of the index set + + EXAMPLES:: + + sage: T = crystals.infinity.Tableaux("A2") + sage: Tstar = crystals.infinity.Star(T) + sage: t = Tstar.module_generators[0].f_string([1,2,1,1,2]) + sage: [t.phi(i) for i in t.index_set()] + [-3, 1] + + sage: M = crystals.infinity.NakajimaMonomials(['B',2,1]) + sage: Mstar = crystals.infinity.Star(M) + sage: m = Mstar.module_generators[0].f_string([0,1,2,2,1,0]) + sage: [m.phi(i) for i in m.index_set()] + [-1, -1, 4] + """ + P = self.parent().weight_lattice_realization() + ac = P.simple_coroot(i) + return P(self.weight()).scalar(ac) + self.epsilon(i) + + def jump(self, i): + r""" + Return the `i`-jump of ``self``. + + For `b \in B(\infty)`, + + .. MATH:: + + \operatorname{jump}_i(b) = \varepsilon_i(b) + \varepsilon_i^*(b) + + \langle h_i, \mathrm{wt}(b) \rangle, + + where `h_i` is a simple coroot. + + INPUT: + + - ``i`` -- an element of the index set + + EXAMPLES:: + + sage: RC = crystals.infinity.RiggedConfigurations("D4") + sage: RCstar = crystals.infinity.Star(RC) + sage: nu0star = RCstar.module_generators[0] + sage: nustar = nu0star.f_string([2,1,3,4,2]) + sage: [nustar.jump(i) for i in RC.index_set()] + [0, 1, 0, 0] + sage: nustar = nu0star.f_string([2,1,3,4,2,2,1,3,2]) # long time + sage: [nustar.jump(i) for i in RC.index_set()] # long time + [1, 0, 1, 2] + """ + P = self.parent().weight_lattice_realization() + ac = P.simple_coroot(i) + return P(self.value.weight()).scalar(ac) + self.epsilon(i) + self.value.epsilon(i) + diff --git a/src/sage/combinat/designs/bibd.py b/src/sage/combinat/designs/bibd.py index 99e14ad8651..8e20cf2837a 100644 --- a/src/sage/combinat/designs/bibd.py +++ b/src/sage/combinat/designs/bibd.py @@ -635,7 +635,7 @@ def v_4_1_BIBD(v, check=True): return projective_plane(3)._blocks if v == 16: from block_design import AffineGeometryDesign - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField return AffineGeometryDesign(2,1,FiniteField(4,'x'))._blocks if v == 25 or v == 37: from difference_family import difference_family @@ -1084,7 +1084,7 @@ def BIBD_5q_5_for_q_prime_power(q): sage: for q in [25, 45, 65, 85, 125, 145, 185, 205, 305, 405, 605]: # long time ....: _ = BIBD_5q_5_for_q_prime_power(q/5) # long time """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField if q%4 != 1 or not is_prime_power(q): raise ValueError("q is not a prime power or q%4!=1.") @@ -1187,7 +1187,7 @@ def BIBD_from_arc_in_desarguesian_projective_plane(n,k,existence=False): # From now on, the code assumes the notations of [Denniston69] for n,q, so # that the BIBD returned by the method will have the requested parameters. - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.libs.gap.libgap import libgap from sage.matrix.constructor import Matrix diff --git a/src/sage/combinat/designs/block_design.py b/src/sage/combinat/designs/block_design.py index 51d0dc3171d..7d14d629678 100644 --- a/src/sage/combinat/designs/block_design.py +++ b/src/sage/combinat/designs/block_design.py @@ -60,7 +60,7 @@ from sage.arith.all import binomial, integer_floor, is_prime_power from incidence_structures import IncidenceStructure from sage.misc.decorators import rename_keyword -from sage.rings.finite_rings.constructor import FiniteField +from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.categories.sets_cat import EmptySetError from sage.misc.unknown import Unknown from sage.matrix.matrix_space import MatrixSpace diff --git a/src/sage/combinat/designs/database.py b/src/sage/combinat/designs/database.py index 32f0c7d8b8b..89f8192a745 100644 --- a/src/sage/combinat/designs/database.py +++ b/src/sage/combinat/designs/database.py @@ -378,7 +378,7 @@ def OA_9_40(): sage: designs.orthogonal_arrays.is_available(9,40) True """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField A = [ [(0,None),(0,None),(0,None),(0,None),(0,None),(0,None),(0,None),(0,None),(0,None),(0,None)], @@ -678,7 +678,7 @@ def OA_11_80(): sage: designs.orthogonal_arrays.is_available(11,80) True """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField A = [ [(0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (0,None)], @@ -720,7 +720,7 @@ def OA_15_112(): sage: designs.orthogonal_arrays.is_available(15,112) True """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField A = [ [(0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (1,None), (4,None), (2,None), (2,None), (4,None), (1,None)], @@ -899,7 +899,7 @@ def OA_11_160(): sage: designs.orthogonal_arrays.is_available(11,160) True """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField A = [ [(0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (1,None), (4,None), (4,None), (1,None)], @@ -942,7 +942,7 @@ def OA_16_176(): sage: designs.orthogonal_arrays.is_available(16,176) True """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField A = [ [(0 ,None),(0 ,None),(0 ,None),(0 ,None),(0 ,None),(0 ,None),(0 ,None),(0 ,None),(0 ,None),(0 ,None),(0 ,None),(0 ,None),(1 ,None),(4 ,None),(9 ,None)], @@ -1141,7 +1141,7 @@ def OA_16_208(): sage: designs.orthogonal_arrays.is_available(16,208) True """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField A = [ [(0 ,None), (0 ,None), (0 ,None), (0 ,None), (0 ,None), (0 ,None), (0 ,None), (0 ,None), (0 ,None), (0 ,None), (0 ,None), (0 ,None), (0 ,None), (0 ,None), (1 ,None)], @@ -1200,7 +1200,7 @@ def OA_15_224(): sage: designs.orthogonal_arrays.is_available(15,224) True """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField A = [ [(0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (1,None), (4,None), (2,None), (2,None), (4,None), (1,None)], @@ -1286,7 +1286,7 @@ def OA_20_352(): sage: designs.orthogonal_arrays.is_available(20,352) True """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField # Column 8, line 6 : 4,25 became 4,27 # line 17: 3,0 became 3,None @@ -1345,7 +1345,7 @@ def OA_20_416(): sage: designs.orthogonal_arrays.is_available(20,416) True """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField Z = None A=[ @@ -1405,7 +1405,7 @@ def OA_20_544(): sage: designs.orthogonal_arrays.is_available(20,544) True """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField Z = None @@ -1469,7 +1469,7 @@ def OA_17_560(): sage: designs.orthogonal_arrays.is_available(17,560) True """ - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF alpha = 5 beta = 4 p = 2 @@ -1533,7 +1533,7 @@ def OA_11_640(): sage: designs.orthogonal_arrays.is_available(11,640) True """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField A = [ [(0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (1,None), (4,None), (4,None), (1,None)], @@ -1860,7 +1860,7 @@ def OA_15_896(): sage: designs.orthogonal_arrays.is_available(15,896) True """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField A = [ [(0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (0,None), (1,None), (4,None), (2,None), (2,None), (4,None), (1,None)], @@ -3539,7 +3539,7 @@ def DM_45_7_1(): sage: _ = designs.difference_matrix(45,7) """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.categories.cartesian_product import cartesian_product G533 = cartesian_product((FiniteField(5),FiniteField(3),FiniteField(3))) @@ -3592,7 +3592,7 @@ def DM_48_9_1(): sage: _ = designs.difference_matrix(48,9) """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField F16 = FiniteField(16,'x') F3 = FiniteField(3) F3F16 = F3.cartesian_product(F16) @@ -3684,7 +3684,7 @@ def DM_52_6_1(): sage: _ = designs.difference_matrix(52,6) """ from sage.rings.finite_rings.integer_mod_ring import IntegerModRing as AdditiveCyclic - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField F4 = FiniteField(4,'z') G13 = FiniteField(13) G = F4.cartesian_product(G13) @@ -3796,7 +3796,7 @@ def DM_56_8_1(): sage: _ = designs.difference_matrix(56,8) """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField F8 = FiniteField(8,'z') F7 = FiniteField(7) G = F8.cartesian_product(F7) @@ -3923,7 +3923,7 @@ def DM_75_8_1(): sage: _ = designs.difference_matrix(75,8) """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.categories.cartesian_product import cartesian_product F3 = FiniteField(3) @@ -4160,7 +4160,7 @@ def BIBD_45_9_8(from_code=False): """ if from_code: from sage.coding.code_constructions import ExtendedQuadraticResidueCode - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField C = ExtendedQuadraticResidueCode(47,FiniteField(2)) min_weight = [map(int,x)[3:] for x in C if x.hamming_weight() == 12 and diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index 52fd4ffdd64..23cabe7d4da 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -46,6 +46,7 @@ import sage.arith.all as arith from sage.misc.unknown import Unknown from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ def group_law(G): r""" @@ -368,7 +369,7 @@ def singer_difference_set(q,d): assert q.is_prime_power() assert d >= 2 - from sage.rings.finite_rings.constructor import GF + from sage.rings.finite_rings.finite_field_constructor import GF from sage.rings.finite_rings.conway_polynomials import conway_polynomial from sage.rings.finite_rings.integer_mod_ring import Zmod @@ -947,7 +948,7 @@ def twin_prime_powers_difference_set(p, check=True): sage: D [[(1, 1), (1, 4), (2, 2), (2, 3), (0, 0), (1, 0), (2, 0)]] """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.categories.cartesian_product import cartesian_product from itertools import product Fp = FiniteField(p,'x') @@ -974,6 +975,142 @@ def twin_prime_powers_difference_set(p, check=True): return G, [d] +def are_mcfarland_1973_parameters(v, k, lmbda, return_parameters=False): + r""" + Test whether ``(v,k,lmbda)`` is a triple that can be obtained from the + construction from [McF1973]_. + + See :func:`mcfarland_1973_construction`. + + INPUT: + + - ``v``, ``k``, ``lmbda`` - (integers) parameters of the difference family + + - ``return_parameters`` -- (boolean, default ``False``) if ``True`` return a + pair ``(True, (q, s))`` so that ``(q,s)`` can be used in the function + :func:`mcfarland_1973_construction` to actually build a + ``(v,k,lmbda)``-difference family. Or ``(False, None)`` if the + construction is not possible. + + EXAMPLES:: + + sage: from sage.combinat.designs.difference_family import are_mcfarland_1973_parameters + sage: are_mcfarland_1973_parameters(64, 28, 12) + True + sage: are_mcfarland_1973_parameters(64, 28, 12, return_parameters=True) + (True, (2, 2)) + sage: are_mcfarland_1973_parameters(60, 13, 5) + False + sage: are_mcfarland_1973_parameters(98125, 19500, 3875) + True + sage: are_mcfarland_1973_parameters(98125, 19500, 3875, True) + (True, (5, 3)) + + sage: from sage.combinat.designs.difference_family import are_mcfarland_1973_parameters + sage: for v in range(1, 100): + ....: for k in range(1,30): + ....: for l in range(1,15): + ....: if are_mcfarland_1973_parameters(v,k,l): + ....: answer, (q,s) = are_mcfarland_1973_parameters(v,k,l,return_parameters=True) + ....: print v,k,l,q,s + ....: assert answer is True + ....: assert designs.difference_family(v,k,l,existence=True) is True + ....: G,D = designs.difference_family(v,k,l) + 16 6 2 2 1 + 45 12 3 3 1 + 64 28 12 2 2 + 96 20 4 4 1 + """ + if v <= k or k <= lmbda: + return (False,None) if return_parameters else False + k = ZZ(k) + lmbda = ZZ(lmbda) + qs,r = (k - lmbda).sqrtrem() # sqrt(k-l) should be q^s + if r or (qs*(qs-1))%lmbda: + return (False,None) if return_parameters else False + + q = qs*(qs-1) // lmbda + 1 + if (q <= 1 or + v * (q-1) != qs*q * (qs*q+q-2) or + k * (q-1)!= qs * (qs*q-1)): + return (False,None) if return_parameters else False + + # NOTE: below we compute the value of s so that qs = q^s. If the method + # is_power_of of integers would be able to return the exponent, we could use + # that... but currently this is not the case + # see trac ticket #19792 + p1,a1 = qs.is_prime_power(get_data=True) + p2,a2 = q.is_prime_power(get_data=True) + + if a1 == 0 or a2 == 0 or p1 != p2 or a1%a2: + return (False,None) if return_parameters else False + + return (True, (q, a1//a2)) if return_parameters else True + +def mcfarland_1973_construction(q, s): + r""" + Return a difference set. + + The difference set returned has the following parameters + + .. MATH:: + + v = \frac{q^{s+1}(q^{s+1}+q-2)}{q-1}, + k = \frac{q^s (q^{s+1}-1)}{q-1}, + \lambda = \frac{q^s(q^s-1)}{q-1} + + This construction is due to [McF1973]_. + + INPUT: + + - ``q``, ``s`` - (integers) parameters for the difference set (see the above + formulas for the expression of ``v``, ``k``, ``l`` in terms of ``q`` and + ``s``) + + .. SEEALSO:: + + The function :func:`are_mcfarland_1973_parameters` makes the translation + between the parameters `(q,s)` corresponding to a given triple + `(v,k,\lambda)`. + + REFERENCES: + + .. [McF1973] Robert L. McFarland + "A family of difference sets in non-cyclic groups" + Journal of Combinatorial Theory (A) vol 15 (1973). + http://dx.doi.org/10.1016/0097-3165(73)90031-9 + + EXAMPLES:: + + sage: from sage.combinat.designs.difference_family import ( + ....: mcfarland_1973_construction, is_difference_family) + + sage: G,D = mcfarland_1973_construction(3, 1) + sage: assert is_difference_family(G, D, 45, 12, 3) + + sage: G,D = mcfarland_1973_construction(2, 2) + sage: assert is_difference_family(G, D, 64, 28, 12) + """ + from sage.rings.finite_rings.finite_field_constructor import GF + from sage.modules.free_module import VectorSpace + from sage.rings.finite_rings.integer_mod_ring import Zmod + from sage.categories.cartesian_product import cartesian_product + from itertools import izip + + r = (q**(s+1)-1) // (q-1) + F = GF(q,'a') + V = VectorSpace(F, s+1) + K = Zmod(r+1) + + G = cartesian_product([F]*(s+1) + [K]) + + D = [] + for k,H in izip(K, V.subspaces(s)): + for v in H: + D.append(G((tuple(v) + (k,)))) + + return G,[D] + def difference_family(v, k, l=1, existence=False, explain_construction=False, check=True): r""" Return a (``k``, ``l``)-difference family on an Abelian group of cardinality ``v``. @@ -1033,14 +1170,16 @@ def difference_family(v, k, l=1, existence=False, explain_construction=False, ch sage: G,D = designs.difference_family(15,7,3) sage: G - The Cartesian product of (Finite Field of size 3, Finite Field of size 5) + Ring of integers modulo 15 sage: D - [[(1, 1), (1, 4), (2, 2), (2, 3), (0, 0), (1, 0), (2, 0)]] + [[0, 1, 2, 4, 5, 8, 10]] sage: print designs.difference_family(15,7,3,explain_construction=True) - Twin prime powers difference family + Singer difference set sage: print designs.difference_family(91,10,1,explain_construction=True) Singer difference set + sage: print designs.difference_family(64,28,12, explain_construction=True) + McFarland 1973 construction For `k=6,7` we look at the set of small prime powers for which a construction is available:: @@ -1233,7 +1372,7 @@ def difference_family(v, k, l=1, existence=False, explain_construction=False, ch elif explain_construction: return "The database contains a ({},{})-evenly distributed set".format(v,k) - from sage.rings.finite_rings.constructor import GF + from sage.rings.finite_rings.finite_field_constructor import GF poly,B = EDS[k][v] if poly is None: # q is prime K = G = GF(v) @@ -1264,36 +1403,54 @@ def difference_family(v, k, l=1, existence=False, explain_construction=False, ch return G, [range(1,v)] factorization = arith.factor(v) - D = None + if len(factorization) == 1: + from sage.rings.finite_rings.finite_field_constructor import GF + K = GF(v,'z') - if len(factorization) == 1: # i.e. is v a prime power - from sage.rings.finite_rings.constructor import GF - G = K = GF(v,'z') + if are_mcfarland_1973_parameters(v,k,l): + if existence: + return True + elif explain_construction: + return "McFarland 1973 construction" + else: + _, (q,s) = are_mcfarland_1973_parameters(v,k,l,True) + G,D = mcfarland_1973_construction(q,s) - if radical_difference_family(K, k, l, existence=True): - if existence: - return True - elif explain_construction: - return "Radical difference family on a finite field" - else: - D = radical_difference_family(K,k,l) + elif are_hyperplanes_in_projective_geometry_parameters(v,k,l): + if existence: + return True + elif explain_construction: + return "Singer difference set" + else: + _, (q,d) = are_hyperplanes_in_projective_geometry_parameters(v,k,l,True) + G,D = singer_difference_set(q,d) - elif l == 1 and k == 6 and df_q_6_1(K,existence=True): - if existence: - return True - elif explain_construction: - return "Wilson 1972 difference family made from the union of two cyclotomic cosets" - else: - D = df_q_6_1(K) + elif len(factorization) == 1 and radical_difference_family(K, k, l, existence=True): + if existence: + return True + elif explain_construction: + return "Radical difference family on a finite field" + else: + D = radical_difference_family(K,k,l) + G = K + + elif len(factorization) == 1 and l == 1 and k == 6 and df_q_6_1(K, existence=True): + if existence: + return True + elif explain_construction: + return "Wilson 1972 difference family made from the union of two cyclotomic cosets" + else: + D = df_q_6_1(K) + G = K - # Twin prime powers construction - # i.e. v = p(p+2) where p and p+2 are prime powers - # k = (v-1)/2 - # lambda = (k-1)/2 (ie 2l+1 = k) elif (k == (v-1)//2 and l == (k-1)//2 and len(factorization) == 2 and abs(pow(*factorization[0]) - pow(*factorization[1])) == 2): + # Twin prime powers construction + # i.e. v = p(p+2) where p and p+2 are prime powers + # k = (v-1)/2 + # lambda = (k-1)/2 (ie 2l+1 = k) if existence: return True elif explain_construction: @@ -1305,16 +1462,7 @@ def difference_family(v, k, l=1, existence=False, explain_construction=False, ch p,q = q,p G,D = twin_prime_powers_difference_set(p,check=False) - if D is None and are_hyperplanes_in_projective_geometry_parameters(v,k,l): - _, (q,d) = are_hyperplanes_in_projective_geometry_parameters(v,k,l,True) - if existence: - return True - elif explain_construction: - return "Singer difference set" - else: - G,D = singer_difference_set(q,d) - - if D is None: + else: if existence: return Unknown raise NotImplementedError("No constructions for these parameters") diff --git a/src/sage/combinat/designs/difference_matrices.py b/src/sage/combinat/designs/difference_matrices.py index 6a25fb5f11c..5c53532284d 100644 --- a/src/sage/combinat/designs/difference_matrices.py +++ b/src/sage/combinat/designs/difference_matrices.py @@ -12,7 +12,7 @@ from sage.misc.unknown import Unknown from sage.misc.cachefunc import cached_function from sage.categories.sets_cat import EmptySetError -from sage.rings.finite_rings.constructor import FiniteField +from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.arith.all import is_prime_power, divisors from designs_pyx import is_difference_matrix from database import DM as DM_constructions diff --git a/src/sage/combinat/designs/group_divisible_designs.py b/src/sage/combinat/designs/group_divisible_designs.py index 1a9859adf71..b576a2fa01e 100644 --- a/src/sage/combinat/designs/group_divisible_designs.py +++ b/src/sage/combinat/designs/group_divisible_designs.py @@ -178,7 +178,7 @@ def GDD_4_2(q,existence=False,check=True): if existence: return True - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF G = GF(q,'x') w = G.primitive_element() e = w**((q-1)/3) diff --git a/src/sage/combinat/designs/orthogonal_arrays.py b/src/sage/combinat/designs/orthogonal_arrays.py index 58721e4d90f..ee7d9c36ba2 100644 --- a/src/sage/combinat/designs/orthogonal_arrays.py +++ b/src/sage/combinat/designs/orthogonal_arrays.py @@ -1655,7 +1655,7 @@ def OA_n_times_2_pow_c_from_matrix(k,c,G,A,Y,check=True): Some new MOLS of order 2np for p a prime power, The Australasian Journal of Combinatorics, vol 10 (1994) """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.rings.integer import Integer from itertools import izip,combinations from designs_pyx import is_difference_matrix @@ -1855,7 +1855,7 @@ def OA_from_Vmt(m,t,V): sage: _ = designs.orthogonal_arrays.build(6,46) # indirect doctest """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField q = m*t+1 Fq, M = QDM_from_Vmt(m,t,V) return OA_from_quasi_difference_matrix(M,Fq,add_col = False) @@ -1905,7 +1905,7 @@ def QDM_from_Vmt(m,t,V): sage: _ = designs.orthogonal_arrays.build(6,46) # indirect doctest """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField q = m*t+1 Fq = FiniteField(q, 'x') w = Fq.multiplicative_generator() diff --git a/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py b/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py index f95bb553b62..75fec6e9526 100644 --- a/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py +++ b/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py @@ -671,7 +671,7 @@ def thwart_lemma_3_5(k,n,m,a,b,c,d=0,complement=False,explain_construction=False Designs, Codes and Cryptography 5, no. 3 (1995): 189-197. """ from sage.arith.all import is_prime_power - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF if complement: a,b,c = n-a,n-b,n-c @@ -784,7 +784,7 @@ def thwart_lemma_4_1(k,n,m,explain_construction=False): Canad. Math. Bull vol7 num.4 (1964) """ from sage.combinat.designs.designs_pyx import is_orthogonal_array - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.arith.all import is_prime_power from block_design import DesarguesianProjectivePlaneDesign from itertools import chain diff --git a/src/sage/combinat/designs/resolvable_bibd.py b/src/sage/combinat/designs/resolvable_bibd.py index 0ab02ce8cd6..e378052e865 100644 --- a/src/sage/combinat/designs/resolvable_bibd.py +++ b/src/sage/combinat/designs/resolvable_bibd.py @@ -202,7 +202,7 @@ def kirkman_triple_system(v,existence=False): # # For all prime powers q=1 mod 6, there exists a KTS(2q+1) elif ((v-1)//2)%6 == 1 and is_prime_power((v-1)//2): - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF q = (v-1)//2 K = GF(q,'x') a = K.primitive_element() @@ -243,7 +243,7 @@ def kirkman_triple_system(v,existence=False): # # For all prime powers q=1 mod 6, there exists a KTS(3q) elif (v//3)%6 == 1 and is_prime_power(v//3): - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF q = v//3 K = GF(q,'x') a = K.primitive_element() @@ -391,7 +391,7 @@ def v_4_1_rbibd(v,existence=False): if existence: return Unknown raise NotImplementedError("I don't know how to build a ({},{},1)-RBIBD!".format(v,4)) - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF q = (v-1)//3 nn = (q-1)//4 G = GF(q,'x') diff --git a/src/sage/combinat/interval_posets.py b/src/sage/combinat/interval_posets.py index d67af0aca55..7d6cb643f91 100644 --- a/src/sage/combinat/interval_posets.py +++ b/src/sage/combinat/interval_posets.py @@ -416,10 +416,99 @@ def latex_options(self): d["vspace"] = self.parent().global_options["latex_vspace"] return d + def _find_node_positions(self, hspace=1, vspace=1): + """ + Compute a nice embedding. + + If `x` precedes `y`, then `y` will always be placed on top of `x` + and/or to the right of `x`. + Decreasing relations tend to be drawn vertically and increasing + relations horizontally. + The algorithm tries to avoid superposition but on big + interval-posets, it might happen. + + OUTPUT: + + a dictionary {vertex: (x,y)} + + EXAMPLES:: + + sage: ti = TamariIntervalPosets(4)[2] + sage: ti._find_node_positions().values() + [[0, 0], [0, -1], [0, -2], [1, -2]] + """ + node_positions = {} + + to_draw = [(1, 0)] + current_parent = [self.increasing_parent(1)] + parenty = [0] + x = 0 + y = 0 + for i in xrange(2, self.size() + 1): + decreasing_parent = self.decreasing_parent(i) + increasing_parent = self.increasing_parent(i) + while to_draw and (decreasing_parent is None or + decreasing_parent < to_draw[-1][0]): + n = to_draw.pop() + node_positions[n[0]] = [x, n[1]] + if i != current_parent[-1]: + if (not self.le(i, i - 1) and decreasing_parent is not None): + x += hspace + if current_parent[-1] is not None: + y -= vspace + else: + y -= vspace + if increasing_parent != current_parent[-1]: + current_parent.append(increasing_parent) + parenty.append(y) + nodey = y + else: + current_parent.pop() + x += hspace + nodey = parenty.pop() + if not current_parent or increasing_parent != current_parent[-1]: + current_parent.append(increasing_parent) + parenty.append(nodey) + to_draw.append((i, nodey)) + + for n in to_draw: + node_positions[n[0]] = [x, n[1]] + return node_positions + + def plot(self, **kwds): + """ + Return a picture. + + The picture represents the Hasse diagram, where the covers are + colored in blue if they are increasing and in red if they are + decreasing. + + This uses the same coordinates as the latex view. + + EXAMPLES:: + + sage: ti = TamariIntervalPosets(4)[2] + sage: ti.plot() + Graphics object consisting of 6 graphics primitives + """ + c0 = 'blue' # self.latex_options()["color_increasing"] + c1 = 'red' # self.latex_options()["color_decreasing"] + G = self.poset().hasse_diagram() + G.set_pos(self._find_node_positions()) + for a, b, c in G.edges(): + if a < b: + G.set_edge_label(a, b, 0) + else: + G.set_edge_label(a, b, 1) + return G.plot(color_by_label={0: c0, 1: c1}, **kwds) + def _latex_(self): r""" A latex representation of ``self`` using the tikzpicture package. + This picture shows the union of the Hasse diagrams of the + initial and final forests. + If `x` precedes `y`, then `y` will always be placed on top of `x` and/or to the right of `x`. Decreasing relations tend to be drawn vertically and increasing @@ -435,20 +524,22 @@ def _latex_(self): sage: ip = TamariIntervalPoset(4,[(2,4),(3,4),(2,1),(3,1)]) sage: print ip._latex_() \begin{tikzpicture}[scale=1] + \node(T1) at (1,0) {1}; \node(T2) at (0,-1) {2}; \node(T3) at (1,-2) {3}; - \node(T1) at (1,0) {1}; \node(T4) at (2,-1) {4}; - \draw[line width = 0.5, color=blue] (T2) -- (T4); + \draw[line width = 0.5, color=red] (T3) -- (T1); \draw[line width = 0.5, color=red] (T2) -- (T1); + \draw[line width = 0.5, color=blue] (T2) -- (T4); \draw[line width = 0.5, color=blue] (T3) -- (T4); - \draw[line width = 0.5, color=red] (T3) -- (T1); \end{tikzpicture} """ latex.add_package_to_preamble_if_available("tikz") latex_options = self.latex_options() start = "\\begin{tikzpicture}[scale=" + str(latex_options['tikz_scale']) + "]\n" end = "\\end{tikzpicture}" + vspace = latex_options["vspace"] + hspace = latex_options["hspace"] def draw_node(j, x, y): r""" @@ -467,65 +558,21 @@ def draw_decreasing(i, j): Internal method to draw decreasing relations """ return "\\draw[line width = " + str(latex_options["line_width"]) + ", color=" + latex_options["color_decreasing"] + "] (T" + str(i) + ") -- (T" + str(j) + ");\n" + if self.size() == 0: nodes = "\\node(T0) at (0,0){$\emptyset$};" relations = "" else: - nodes = "" # latex for node decraltions + positions = self._find_node_positions(hspace, vspace) + nodes = "" # latex for node declarations relations = "" # latex for drawing relations - to_draw = [] - to_draw.append((1, 0)) # a pilo of nodes to be drawn with their y position - - current_parent = [self.increasing_parent(1)] # a pilo for the current increasing parents - parenty = [0] # a pilo for the current parent y positions - if current_parent[-1] is not None: - relations += draw_increasing(1, current_parent[-1]) - vspace = latex_options["vspace"] - hspace = latex_options["hspace"] - x = 0 - y = 0 - - # the idea is that we draw the nodes from left to right and save their y position - for i in xrange(2, self.size() + 1): - # at each, we draw all possible nodes and add the current node to the to_draw pilo - decreasing_parent = self.decreasing_parent(i) - increasing_parent = self.increasing_parent(i) - while len(to_draw) > 0 and (decreasing_parent is None or decreasing_parent < to_draw[-1][0]): - # we draw all the nodes which can be placed at x - # we know these nodes won't have any more decreasing children (so their horizontal position is fixed) - n = to_draw.pop() - nodes += draw_node(n[0], x, n[1]) - if i != current_parent[-1]: - #i is not the current increasing parent - if (not self.le(i, i - 1) and decreasing_parent is not None): - # there is no decreasing relation between i and i-1 - #they share a decreasing parent and are placed alongside horizontally - x += hspace - if current_parent[-1] is not None: - y -= vspace - else: - #otherwise, they are placed alongside vertically - y -= vspace - if increasing_parent != current_parent[-1]: - current_parent.append(increasing_parent) - parenty.append(y) - nodey = y - else: - # i is the current increasing parent so it takes the current vertical position - current_parent.pop() - x += hspace - nodey = parenty.pop() - if len(current_parent) == 0 or increasing_parent != current_parent[-1]: - current_parent.append(increasing_parent) - parenty.append(nodey) - to_draw.append((i, nodey)) - if increasing_parent is not None: - relations += draw_increasing(i, increasing_parent) - if decreasing_parent is not None: - relations += draw_decreasing(i, decreasing_parent) - for n in to_draw: - # we draw all remaining nodes - nodes += draw_node(n[0], x, n[1]) + for i in range(1, self.size() + 1): + nodes += draw_node(i, *positions[i]) + for i, j in self.decreasing_cover_relations(): + relations += draw_decreasing(i, j) + for i, j in self.increasing_cover_relations(): + relations += draw_increasing(i, j) + return start + nodes + relations + end def poset(self): @@ -556,10 +603,11 @@ def __hash__(self): EXAMPLES:: - sage: hash(TamariIntervalPosets(4)[0]) - 3527539 + sage: len(set([hash(u) for u in TamariIntervalPosets(4)])) + 68 """ - return hash(self._cover_relations) + pair = (self.size(), tuple(tuple(e) for e in self._cover_relations)) + return hash(pair) @cached_method def increasing_cover_relations(self): diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index f966e4e166a..65c4ad5f74c 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -135,7 +135,7 @@ def hadamard_matrix_paleyI(n, normalize=True): if not(is_prime_power(p) and (p % 4 == 3)): raise ValueError("The order %s is not covered by the Paley type I construction." % n) - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField K = FiniteField(p,'x') K_list = list(K) K_list.insert(0,K.zero()) @@ -198,7 +198,7 @@ def hadamard_matrix_paleyII(n): if not(n%2==0 and is_prime_power(q) and (q % 4 == 1)): raise ValueError("The order %s is not covered by the Paley type II construction." % n) - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField K = FiniteField(q,'x') K_list = list(K) K_list.insert(0,K.zero()) @@ -514,11 +514,11 @@ def regular_symmetric_hadamard_matrix_with_constant_diagonal(n,e,existence=False A Hadamard matrix is said to be *regular* if its rows all sum to the same value. - When `\epsilon\in\{-1,+1\}`, we say that `M` is a `(n,\epsilon)-RSHCD` if + For `\epsilon\in\{-1,+1\}`, we say that `M` is a `(n,\epsilon)-RSHCD` if `M` is a regular symmetric Hadamard matrix with constant diagonal - `\delta\in\{-1,+1\}` and row values all equal to `\delta \epsilon + `\delta\in\{-1,+1\}` and row sums all equal to `\delta \epsilon \sqrt(n)`. For more information, see [HX10]_ or 10.5.1 in - [BH12]_. + [BH12]_. For the case `n=324`, see :func:`RSHCD_324` and [CP16]_. INPUT: @@ -550,6 +550,11 @@ def regular_symmetric_hadamard_matrix_with_constant_diagonal(n,e,existence=False 100 x 100 dense matrix over Integer Ring 196 x 196 dense matrix over Integer Ring + sage: for n,e in [(324,1),(324,-1)]: # not tested - long time, tested in RSHCD_324 + ....: print regular_symmetric_hadamard_matrix_with_constant_diagonal(n,e) # not tested - long time + 324 x 324 dense matrix over Integer Ring + 324 x 324 dense matrix over Integer Ring + From two close prime powers:: sage: print regular_symmetric_hadamard_matrix_with_constant_diagonal(64,-1) @@ -619,6 +624,10 @@ def true(): return true() M = strongly_regular_graph(196,91,42,42).adjacency_matrix() M = J(196) - 2*M + elif n == 324: + if existence: + return true() + M = RSHCD_324(e) elif ( e == 1 and n%16 == 0 and is_square(n) and @@ -655,6 +664,63 @@ def true(): return M +def RSHCD_324(e): + r""" + Return a size 324x324 Regular Symmetric Hadamard Matrix with Constant Diagonal. + + We build the matrix `M` for the case `n=324`, `\epsilon=1` directly from + :meth:`JankoKharaghaniTonchevGraph + ` + and for the case `\epsilon=-1` from the "twist" `M'` of `M`, using Lemma 11 + in [HX10]_. Namely, it turns out that the matrix + + .. math:: + + M'=\begin{pmatrix} M_{12} & M_{11}\\ M_{11}^\top & M_{21} \end{pmatrix}, + \quad\text{where}\quad + M=\begin{pmatrix} M_{11} & M_{12}\\ M_{21} & M_{22} \end{pmatrix}, + + and the `M_{ij}` are 162x162-blocks, also RSHCD, its diagonal blocks having zero row + sums, as needed by [loc.cit.]. Interestingly, the corresponding + `(324,152,70,72)`-strongly regular graph + has a vertex-transitive automorphism group of order 2592, twice the order of the + (intransitive) automorphism group of the graph corresponding to `M`. Cf. [CP16]_. + + INPUT: + + - ``e`` -- one of `-1` or `+1`, equal to the value of `\epsilon` + + TESTS:: + + sage: from sage.combinat.matrices.hadamard_matrix import RSHCD_324, is_hadamard_matrix + sage: for e in [1,-1]: # long time + ....: M = RSHCD_324(e) # long time + ....: print M==M.T,is_hadamard_matrix(M),all([M[i,i]==1 for i in xrange(324)]) # long time + ....: print set(map(sum,M)) # long time + True True True + set([18]) + True True True + set([-18]) + + REFERENCE: + + .. [CP16] N. Cohen, D. Pasechnik, + Implementing Brouwer's database of strongly regular graphs, + http://arxiv.org/abs/1601.00181 + """ + + from sage.graphs.generators.smallgraphs import JankoKharaghaniTonchevGraph as JKTG + M = JKTG().adjacency_matrix() + M = J(324) - 2*M + if e==-1: + M1=M[:162].T + M2=M[162:].T + M11=M1[:162] + M12=M1[162:].T + M21=M2[:162].T + M=block_matrix([[M12,-M11],[-M11.T,M21]]) + return M + def _helper_payley_matrix(n, zero_position=True): r""" Return the marix constructed in Lemma 1.19 page 291 of [SWW72]_. @@ -716,7 +782,7 @@ def _helper_payley_matrix(n, zero_position=True): [ 1 1 -1 -1 1 -1 -1 1 1 0 -1] [ 1 -1 1 -1 -1 -1 1 1 -1 1 0] """ - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF K = GF(n,conway=True,prefix='x') # Order the elements of K in K_list @@ -839,10 +905,10 @@ def williamson_goethals_seidel_skew_hadamard_matrix(a, b, c, d, check=True): .. [GS70s] J.M. Goethals and J. J. Seidel, A skew Hadamard matrix of order 36, - J. Aust. Math. Soc. 11(1970), 343-344 + J. Aust. Math. Soc. 11(1970), 343-344 .. [Wall71] J. Wallis, A skew-Hadamard matrix of order 92, - Bull. Aust. Math. Soc. 5(1971), 203-204 + Bull. Aust. Math. Soc. 5(1971), 203-204 .. [KoSt08] C. Koukouvinos, S. Stylianou On skew-Hadamard matrices, Discrete Math. 308(2008) 2723-2731 @@ -857,7 +923,7 @@ def williamson_goethals_seidel_skew_hadamard_matrix(a, b, c, d, check=True): assert A+A.T==2*I(n) M = block_matrix([[ A, B*R, C*R, D*R], - [-B*R, A, -D.T*R, C.T*R], + [-B*R, A, -D.T*R, C.T*R], [-C*R, D.T*R, A, -B.T*R], [-D*R, -C.T*R, B.T*R, A]]) if check: @@ -900,7 +966,7 @@ def pmtoZ(s): if existence: return n in [36, 52, 92] - + if n==36: a=[ 1, 1, 1, -1, 1, -1, 1, -1, -1] b=[ 1, -1, 1, 1, -1, -1, 1, 1, -1] @@ -918,8 +984,8 @@ def pmtoZ(s): c = [1, 1,-1,-1,-1, 1,-1, 1,-1, 1,-1, 1, 1,-1, 1,-1, 1,-1, 1,-1,-1,-1, 1] d = [1,-1,-1,-1,-1, 1,-1,-1, 1,-1,-1, 1, 1,-1,-1, 1,-1,-1, 1,-1,-1,-1,-1] return WGS(a,b,c,d, check=check) - return None - + return None + _skew_had_cache={} def skew_hadamard_matrix(n,existence=False, skew_normalize=True, check=True): diff --git a/src/sage/combinat/matrices/latin.py b/src/sage/combinat/matrices/latin.py index 8bcab6bf107..6bf0fc14675 100644 --- a/src/sage/combinat/matrices/latin.py +++ b/src/sage/combinat/matrices/latin.py @@ -140,7 +140,7 @@ from sage.interfaces.gap import gap from sage.groups.perm_gps.permgroup import PermutationGroup from sage.arith.all import is_prime -from sage.rings.finite_rings.constructor import FiniteField +from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.misc.misc import uniq from sage.misc.flatten import flatten diff --git a/src/sage/combinat/ordered_tree.py b/src/sage/combinat/ordered_tree.py index 5935bb2af45..cefb02569e0 100644 --- a/src/sage/combinat/ordered_tree.py +++ b/src/sage/combinat/ordered_tree.py @@ -1136,7 +1136,7 @@ class LabelledOrderedTree(AbstractLabelledClonableTree, OrderedTree): INPUT: - ``children`` -- a list or tuple or more generally any iterable - of trees or object convertible to trees + of trees or object convertible to trees - ``label`` -- any Sage object (default: ``None``) EXAMPLES:: diff --git a/src/sage/combinat/posets/lattices.py b/src/sage/combinat/posets/lattices.py index af47772aa01..43109049ef3 100644 --- a/src/sage/combinat/posets/lattices.py +++ b/src/sage/combinat/posets/lattices.py @@ -734,7 +734,7 @@ def complements(self, element=None): INPUT: - - ``element`` - an element of the poset whose complement is + - ``element`` - an element of the lattice whose complement is returned. If ``None`` (default) then dictionary of complements for all elements having at least one complement is returned. @@ -1084,7 +1084,7 @@ def is_upper_semimodular(self): Return ``True`` if the lattice is upper semimodular and ``False`` otherwise. - A lattice is upper semimodular if for any `x` in the poset that is + A lattice is upper semimodular if for any `x` in the lattice that is covered by `y` and `z`, both `y` and `z` are covered by their join. See also :meth:`is_modular` and :meth:`is_lower_semimodular`. @@ -1126,7 +1126,7 @@ def is_lower_semimodular(self): Return ``True`` if the lattice is lower semimodular and ``False`` otherwise. - A lattice is lower semimodular if for any `x` in the poset that covers + A lattice is lower semimodular if for any `x` in the lattice that covers `y` and `z`, both `y` and `z` cover their meet. See also :meth:`is_modular` and :meth:`is_upper_semimodular`. diff --git a/src/sage/combinat/posets/poset_examples.py b/src/sage/combinat/posets/poset_examples.py index 98cdbd6b2e9..0e16a16e90c 100644 --- a/src/sage/combinat/posets/poset_examples.py +++ b/src/sage/combinat/posets/poset_examples.py @@ -29,6 +29,7 @@ :meth:`~Posets.RestrictedIntegerPartitions` | Return the poset of integer partitions of `n`, ordered by restricted refinement. :meth:`~Posets.ShardPoset` | Return the shard intersection order. :meth:`~Posets.SSTPoset` | Return the poset on semistandard tableaux of shape `s` and largest entry `f` that is ordered by componentwise comparison. + :meth:`~Posets.StandardExample` | Return the standard example of a poset with dimension `n`. :meth:`~Posets.SymmetricGroupBruhatIntervalPoset` | The poset of permutations with respect to Bruhat order. :meth:`~Posets.SymmetricGroupBruhatOrderPoset` | The poset of permutations with respect to Bruhat order. :meth:`~Posets.SymmetricGroupWeakOrderPoset` | The poset of permutations of `\{ 1, 2, \ldots, n \}` with respect to the weak order. @@ -513,6 +514,65 @@ def tableaux_is_less_than(a,b): E = SemistandardTableaux(s, max_entry=f) return Poset((E, tableaux_is_less_than)) + @staticmethod + def StandardExample(n, facade=None): + r""" + Return the partially ordered set on ``2n`` elements with + dimension ``n``. + + Let `P` be the poset on `\{0, 1, 2, \ldots, 2n-1\}` whose defining + relations are that `i < j` for every `0 \leq i < n \leq j < 2n` + except when `i + n = j`. The poset `P` is the so-called + *standard example* of a poset with dimension `n`. + + INPUT: + + - ``n`` -- an integer `\ge 2`, dimension of the constructed poset + - ``facade`` (boolean) -- whether to make the returned poset a + facade poset (see :mod:`sage.categories.facade_sets`); the + default behaviour is the same as the default behaviour of + the :func:`~sage.combinat.posets.posets.Poset` constructor + + OUTPUT: + + The standard example of a poset of dimension `n`. + + EXAMPLES:: + + sage: A = Posets.StandardExample(3); A + Finite poset containing 6 elements + sage: A.dimension() + 3 + + REFERENCES: + + .. [Rosen] K. Rosen *Handbook of Discrete and Combinatorial + Mathematics* (1999), Chapman and Hall. + + .. [Garg] V. Garg *Introduction to Lattice Theory with Computer + Science Applications* (2015), Wiley. + + TESTS:: + + sage: A = Posets.StandardExample(10); A + Finite poset containing 20 elements + sage: len(A.cover_relations()) + 90 + + sage: P = Posets.StandardExample(5, facade=False) + sage: P(4) < P(3), P(4) > P(3) + (False, False) + """ + try: + n = Integer(n) + except TypeError: + raise TypeError("dimension must be an integer, not {0}".format(n)) + if n < 2: + raise ValueError("dimension must be at least 2, not {0}".format(n)) + return Poset((range(2*n), [[i, j+n] for i in range(n) + for j in range(n) if i != j]), + facade=facade) + @staticmethod def SymmetricGroupBruhatOrderPoset(n): """ diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index ef34f1983e7..c055b24768f 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -171,7 +171,7 @@ :meth:`~FinitePoset.chain_polytope` | Return the chain polytope of the poset. :meth:`~FinitePoset.order_polytope` | Return the order polytope of the poset. -**Other & not yet classified** +**Graphs** .. csv-table:: :class: contentstable @@ -182,6 +182,15 @@ :meth:`~FinitePoset.cover_relations_graph` | Return the (undirected) graph of cover relations. :meth:`~FinitePoset.comparability_graph` | Return the comparability graph of the poset. :meth:`~FinitePoset.incomparability_graph` | Return the incomparability graph of the poset. + :meth:`~FinitePoset.linear_extensions_graph` | Return the linear extensions graph of the poset. + +**Other & not yet classified** + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + :meth:`~FinitePoset.isomorphic_subposets` | Return all subposets isomorphic to another poset. :meth:`~FinitePoset.isomorphic_subposets_iterator` | Return an iterator over the subposets isomorphic to another poset. :meth:`~FinitePoset.has_isomorphic_subposet` | Return ``True`` if the poset contains a subposet isomorphic to another poset. diff --git a/src/sage/combinat/q_analogues.py b/src/sage/combinat/q_analogues.py index e4c21a06386..d717c3a2a32 100644 --- a/src/sage/combinat/q_analogues.py +++ b/src/sage/combinat/q_analogues.py @@ -313,13 +313,13 @@ def q_binomial(n, k, q=None, algorithm='auto'): else: num = prod(one - q**i for i in range(n-k+1, n+1)) try: - return num//denom - except TypeError: try: - return num/denom - except (TypeError,ZeroDivisionError): - #try a substitution - return q_binomial(n,k)(q) + return num // denom + except TypeError: + return num / denom + except (TypeError, ZeroDivisionError): + # use substitution instead + return q_binomial(n,k)(q) elif algorithm == 'cyclo_generic': from sage.rings.polynomial.cyclotomic import cyclotomic_value return prod(cyclotomic_value(d,q) diff --git a/src/sage/combinat/quickref.py b/src/sage/combinat/quickref.py index 64d9cd70fac..efaeb99224c 100644 --- a/src/sage/combinat/quickref.py +++ b/src/sage/combinat/quickref.py @@ -7,7 +7,7 @@ sage: s = oeis([1,3,19,211]); s # optional - internet 0: A000275: Coefficients of a Bessel function (reciprocal of J_0(z)); also pairs of permutations with rise/rise forbidden. sage: s[0].programs() # optional - internet - 0: (PARI) a(n)=if(n<0,0,n!^2*4^n*polcoeff(1/besselj(0,x+x*O(x^(2*n))),2*n)) /* _Michael Somos_, May 17 2004 */ + 0: (PARI) {a(n) = if( n<0, 0, n!^2 * 4^n * polcoeff( 1 / besselj(0, x + x * O(x^(2*n))), 2*n))}; /* _Michael Somos_, May 17 2004 */ Combinatorial objects:: diff --git a/src/sage/combinat/species/composition_species.py b/src/sage/combinat/species/composition_species.py index 88e5544305f..0d6dbb1b67e 100644 --- a/src/sage/combinat/species/composition_species.py +++ b/src/sage/combinat/species/composition_species.py @@ -63,11 +63,22 @@ def transport(self, perm): f, gs = self._list pi = self._partition.transport(perm) f = f.change_labels(pi._list) - g = [g.change_labels(part) for g,part in zip(gs, pi.labels())] + g = [g.change_labels(part) for g,part in zip(gs, pi)] return self.__class__(self, self._labels, pi, f, gs) def change_labels(self, labels): """ + Return a relabelled structure. + + INPUT: + + - ``labels``, a list of labels. + + OUTPUT: + + A structure with the i-th label of self replaced with the i-th + label of the list. + EXAMPLES:: sage: p = PermutationGroupElement((2,3)) @@ -81,8 +92,8 @@ def change_labels(self, labels): """ f, gs = self._list pi = self._partition.change_labels(labels) - f = f.change_labels(pi._list) - g = [g.change_labels(part) for g,part in zip(gs, pi.labels())] + f = f.change_labels(list(pi)) + g = [g.change_labels(part) for g,part in zip(gs, pi)] return self.__class__(self, labels, pi, f, g) @@ -155,11 +166,11 @@ def _structures(self, structure_class, labels): from itertools import product P = PartitionSpecies() for pi in P.structures(labels): - #The labels of the G-structures will be just be the things - #in labels - gs = product(*[self._G.structures(part.labels()) for part in pi]) + # The labels of the G-structures will be just be the things + # in labels + gs = product(*[self._G.structures(part.label_subset()) for part in pi]) - #The labels of the F-structure will be set objects + # The labels of the F-structure will be set objects fs = self._F.structures(list(pi)) for f, gg in product(fs, gs): yield structure_class(self, labels, pi, f, gg) diff --git a/src/sage/combinat/species/library.py b/src/sage/combinat/species/library.py index 1aac7491563..ae6adf3eb31 100644 --- a/src/sage/combinat/species/library.py +++ b/src/sage/combinat/species/library.py @@ -53,14 +53,14 @@ def SimpleGraphSpecies(): TESTS:: sage: seq = S.isotype_generating_series().counts(6)[1:] - sage: oeis(seq)[0] # optional - internet + sage: oeis(seq)[0] # optional -- internet A000088: Number of graphs on n unlabeled nodes. :: sage: seq = S.generating_series().counts(10)[1:] - sage: oeis(seq)[0] # optional - internet - A006125: 2^(n(n-1)/2). + sage: oeis(seq)[0] # optional -- internet + A006125: a(n) = 2^(n(n-1)/2). """ E = SetSpecies() E2 = SetSpecies(size=2) @@ -97,7 +97,7 @@ def BinaryTreeSpecies(): TESTS:: sage: seq = B.isotype_generating_series().counts(10)[1:] - sage: oeis(seq)[0] # optional - internet + sage: oeis(seq)[0] # optional -- internet A000108: Catalan numbers: C(n) = binomial(2n,n)/(n+1) = (2n)!/(n!(n+1)!). Also called Segner numbers. """ B = CombinatorialSpecies() @@ -130,7 +130,7 @@ def BinaryForestSpecies(): TESTS:: sage: seq = F.isotype_generating_series().counts(10)[1:] - sage: oeis(seq)[0] # optional - internet + sage: oeis(seq)[0] # optional -- internet A052854: Number of forests of ordered trees on n total nodes. """ B = BinaryTreeSpecies() diff --git a/src/sage/combinat/species/partition_species.py b/src/sage/combinat/species/partition_species.py index c9f9db9bbac..6781fd00310 100644 --- a/src/sage/combinat/species/partition_species.py +++ b/src/sage/combinat/species/partition_species.py @@ -111,6 +111,17 @@ def automorphism_group(self): def change_labels(self, labels): """ + Return a relabelled structure. + + INPUT: + + - ``labels``, a list of labels. + + OUTPUT: + + A structure with the i-th label of self replaced with the i-th + label of the list. + EXAMPLES:: sage: p = PermutationGroupElement((2,3)) diff --git a/src/sage/combinat/species/product_species.py b/src/sage/combinat/species/product_species.py index 9e1f98c9a52..2f3e7ff01d4 100644 --- a/src/sage/combinat/species/product_species.py +++ b/src/sage/combinat/species/product_species.py @@ -21,7 +21,20 @@ from sage.misc.cachefunc import cached_function from sage.structure.unique_representation import UniqueRepresentation -class ProductSpeciesStructure(GenericSpeciesStructure): +class ProductSpeciesStructure(GenericSpeciesStructure): + def __init__(self, parent, labels, subset, left, right): + """ + TESTS:: + + sage: S = species.SetSpecies() + sage: F = S * S + sage: a = F.structures(['a','b','c']).random_element() + sage: a == loads(dumps(a)) + True + """ + self._subset = subset + GenericSpeciesStructure.__init__(self, parent, labels, [left, right]) + def __repr__(self): """ Returns the string representation of this object. @@ -41,19 +54,6 @@ def __repr__(self): right = "(%s)"%right return "%s*%s"%(left, right) - def __init__(self, parent, labels, subset, left, right): - """ - TESTS:: - - sage: S = species.SetSpecies() - sage: F = S * S - sage: a = F.structures(['a','b','c']).random_element() - sage: a == loads(dumps(a)) - True - """ - self._subset = subset - GenericSpeciesStructure.__init__(self, parent, labels, [left, right]) - def transport(self, perm): """ EXAMPLES:: @@ -68,8 +68,8 @@ def transport(self, perm): """ left, right = self._list new_subset = self._subset.transport(perm) - left_labels = new_subset.labels() - right_labels = new_subset.complement().labels() + left_labels = new_subset.label_subset() + right_labels = new_subset.complement().label_subset() return self.__class__(self.parent(), self._labels, new_subset, @@ -108,8 +108,8 @@ def canonical_label(self): """ left, right = self._list new_subset = self._subset.canonical_label() - left_labels = new_subset.labels() - right_labels = new_subset.complement().labels() + left_labels = new_subset.label_subset() + right_labels = new_subset.complement().label_subset() return self.__class__(self.parent(), self._labels, new_subset, @@ -118,6 +118,17 @@ def canonical_label(self): def change_labels(self, labels): """ + Return a relabelled structure. + + INPUT: + + - ``labels``, a list of labels. + + OUTPUT: + + A structure with the i-th label of self replaced with the i-th + label of the list. + EXAMPLES:: sage: S = species.SetSpecies() @@ -129,8 +140,8 @@ def change_labels(self, labels): """ left, right = self._list new_subset = self._subset.change_labels(labels) - left_labels = new_subset.labels() - right_labels = new_subset.complement().labels() + left_labels = new_subset.label_subset() + right_labels = new_subset.complement().label_subset() return self.__class__(self.parent(), labels, new_subset, left.change_labels(left_labels), @@ -221,11 +232,40 @@ def __init__(self, F, G, min=None, max=None, weight=None): _default_structure_class = ProductSpeciesStructure + def left_factor(self): + """ + Returns the left factor of this product. + + EXAMPLES:: + + sage: P = species.PermutationSpecies() + sage: X = species.SingletonSpecies() + sage: F = P*X + sage: F.left_factor() + Permutation species + """ + return self._F + + def right_factor(self): + """ + Returns the right factor of this product. + + EXAMPLES:: + + sage: P = species.PermutationSpecies() + sage: X = species.SingletonSpecies() + sage: F = P*X + sage: F.right_factor() + Singleton species + """ + return self._G + def _name(self): """ Note that we use a function to return the name of this species because we can't do it in the __init__ method due to it - requiring that self._F and self._G already be unpickled. + requiring that self.left_factor() and self.right_factor() + already be unpickled. EXAMPLES:: @@ -234,7 +274,7 @@ def _name(self): sage: F._name() 'Product of (Permutation species) and (Permutation species)' """ - return "Product of (%s) and (%s)"%(self._F, self._G) + return "Product of (%s) and (%s)"%(self.left_factor(), self.right_factor()) def _structures(self, structure_class, labels): """ @@ -271,12 +311,12 @@ def _times_gen(self, structure_class, attr, labels): S = SubsetSpecies() for u in getattr(S, attr)(labels): - vl = u.complement().labels() - ul = u.labels() - if c(self._F, len(ul)) == 0 or c(self._G, len(vl)) == 0: + vl = u.complement().label_subset() + ul = u.label_subset() + if c(self.left_factor(), len(ul)) == 0 or c(self.right_factor(), len(vl)) == 0: continue - for x in getattr(self._F, attr)(ul): - for y in getattr(self._G, attr)(vl): + for x in getattr(self.left_factor(), attr)(ul): + for y in getattr(self.right_factor(), attr)(vl): yield structure_class(self, labels, u, x, y) def _gs(self, series_ring, base_ring): @@ -288,7 +328,8 @@ def _gs(self, series_ring, base_ring): sage: F.generating_series().coefficients(5) [1, 2, 3, 4, 5] """ - res = self._F.generating_series(base_ring) * self._G.generating_series(base_ring) + res = (self.left_factor().generating_series(base_ring) * + self.right_factor().generating_series(base_ring)) if self.is_weighted(): res = self._weight * res return res @@ -303,8 +344,8 @@ def _itgs(self, series_ring, base_ring): sage: F.isotype_generating_series().coefficients(5) [1, 2, 5, 10, 20] """ - res = (self._F.isotype_generating_series(base_ring) * - self._G.isotype_generating_series(base_ring)) + res = (self.left_factor().isotype_generating_series(base_ring) * + self.right_factor().isotype_generating_series(base_ring)) if self.is_weighted(): res = self._weight * res return res @@ -322,8 +363,8 @@ def _cis(self, series_ring, base_ring): 4*p[1, 1, 1] + 4*p[2, 1] + 2*p[3], 5*p[1, 1, 1, 1] + 6*p[2, 1, 1] + 3*p[2, 2] + 4*p[3, 1] + 2*p[4]] """ - res = (self._F.cycle_index_series(base_ring) * - self._G.cycle_index_series(base_ring)) + res = (self.left_factor().cycle_index_series(base_ring) * + self.right_factor().cycle_index_series(base_ring)) if self.is_weighted(): res = self._weight * res return res @@ -355,7 +396,9 @@ def weight_ring(self): sage: C.weight_ring() Univariate Polynomial Ring in t over Rational Field """ - return self._common_parent([self._F.weight_ring(), self._G.weight_ring(), self._weight.parent()]) + return self._common_parent([self.left_factor().weight_ring(), + self.right_factor().weight_ring(), + self._weight.parent()]) def _equation(self, var_mapping): """ diff --git a/src/sage/combinat/species/structure.py b/src/sage/combinat/species/structure.py index 748de2f2de1..9be327ddc3d 100644 --- a/src/sage/combinat/species/structure.py +++ b/src/sage/combinat/species/structure.py @@ -37,32 +37,17 @@ # http://www.gnu.org/licenses/ #***************************************************************************** from sage.combinat.combinat import CombinatorialClass, CombinatorialObject -from sage.structure.sage_object import SageObject from sage.rings.integer import Integer from copy import copy -class SpeciesStructure(SageObject): - def parent(self): - """ - Returns the species that this structure is associated with. - - EXAMPLES:: - sage: L = species.LinearOrderSpecies() - sage: a,b = L.structures([1,2]) - sage: a.parent() - Linear order species - """ - try: - return self._parent - except AttributeError: - raise NotImplementedError - -class GenericSpeciesStructure(CombinatorialObject, SpeciesStructure): +class GenericSpeciesStructure(CombinatorialObject): def __init__(self, parent, labels, list): """ - EXAMPLES:: + This is a base class from which the classes for the structures inherit. + EXAMPLES:: + sage: from sage.combinat.species.structure import GenericSpeciesStructure sage: a = GenericSpeciesStructure(None, [2,3,4], [1,2,3]) sage: a @@ -76,6 +61,22 @@ def __init__(self, parent, labels, list): self._labels = labels CombinatorialObject.__init__(self, list) + def parent(self): + """ + Returns the species that this structure is associated with. + + EXAMPLES:: + + sage: L = species.LinearOrderSpecies() + sage: a,b = L.structures([1,2]) + sage: a.parent() + Linear order species + """ + try: + return self._parent + except AttributeError: + raise NotImplementedError + def __repr__(self): """ EXAMPLES:: @@ -85,7 +86,7 @@ def __repr__(self): sage: a [2, 3, 4] """ - return repr(self.labels()) + return repr([self._relabel(i) for i in self._list]) def __eq__(self, other): """ @@ -105,17 +106,35 @@ def __eq__(self, other): def labels(self): """ + Returns the labels used for this structure. + + .. note:: + + This includes labels which may not "appear" in this + particular structure. + EXAMPLES:: sage: P = species.SubsetSpecies() - sage: S = P.structures(["a", "b", "c"]) - sage: [s.labels() for s in S] - [[], ['a'], ['b'], ['c'], ['a', 'b'], ['a', 'c'], ['b', 'c'], ['a', 'b', 'c']] + sage: s = P.structures(["a", "b", "c"]).random_element() + sage: s.labels() + ['a', 'b', 'c'] """ - return [self._relabel(i) for i in self._list] + return copy(self._labels) def change_labels(self, labels): """ + Return a relabelled structure. + + INPUT: + + - ``labels``, a list of labels. + + OUTPUT: + + A structure with the i-th label of self replaced with the i-th + label of the list. + EXAMPLES:: sage: P = species.SubsetSpecies() @@ -166,9 +185,32 @@ def is_isomorphic(self, x): else: return False +#For backward compatibility. This should be removed in the near +#future since I doubt that there is any code that depends directly on +#SpeciesStructure. +SpeciesStructure = GenericSpeciesStructure + class SpeciesStructureWrapper(GenericSpeciesStructure): def __init__(self, parent, s, **options): """ + This is a class for the structures of species such as the sum + species that do not provide "additional" structure. For example, + if you have the sum `C` of species `A` and `B`, + then a structure of `C` will either be either something from `A` or `B`. + Instead of just returning one of these directly, a "wrapper" is + put around them so that they have their parent is `C` rather than `A` or + `B`:: + + sage: X = species.SingletonSpecies() + sage: X2 = X+X + sage: s = X2.structures([1]).random_element(); s + 1 + sage: s.parent() + Sum of (Singleton species) and (Singleton species) + sage: from sage.combinat.species.structure import SpeciesStructureWrapper + sage: issubclass(type(s), SpeciesStructureWrapper) + True + EXAMPLES:: sage: E = species.SetSpecies(); B = E+E @@ -233,19 +275,31 @@ def canonical_label(self): """ return self.__class__(self._parent, self._s.canonical_label(), **self._options) - def labels(self): + def change_labels(self, labels): """ + Return a relabelled structure. + + INPUT: + + - ``labels``, a list of labels. + + OUTPUT: + + A structure with the i-th label of self replaced with the i-th + label of the list. + EXAMPLES:: - sage: P = species.PartitionSpecies() - sage: s = (P+P).structures([1,2,3]).random_element(); s - {{1, 3}, {2}} - sage: s.labels() - [{1, 3}, {2}] - sage: type(_) - + sage: X = species.SingletonSpecies() + sage: X2 = X+X + sage: s = X2.structures([1]).random_element(); s + 1 + sage: s.change_labels(['a']) + 'a' """ - return self._s.labels() + c = GenericSpeciesStructure.change_labels(self, labels) + c._s = c._s.change_labels(labels) + return c ############################################################## @@ -254,6 +308,15 @@ def labels(self): class SpeciesWrapper(CombinatorialClass): def __init__(self, species, labels, iterator, generating_series, name, structure_class): """ + This is a abstract base class for the set of structures of a + species as well as the set of isotypes of the species. + + .. note:: + + One typically does not use :class:`SpeciesWrapper` + directly, but instead instantiates one of its subclasses: + :class:`StructuresWrapper` or :class:`IsotypesWrapper`. + EXAMPLES:: sage: from sage.combinat.species.structure import SpeciesWrapper @@ -273,6 +336,20 @@ def __init__(self, species, labels, iterator, generating_series, name, structure self._name = "%s for %s with labels %s"%(name, species, labels) self._structure_class = structure_class if structure_class is not None else species._default_structure_class + def labels(self): + """ + Returns the labels used on these structures. If `X` is the + species, then :meth:`labels` returns the preimage of these + structures under the functor `X`. + + EXAMPLES:: + + sage: F = species.SetSpecies() + sage: F.structures([1,2,3]).labels() + [1, 2, 3] + """ + return copy(self._labels) + def __iter__(self): """ EXAMPLES:: @@ -302,6 +379,8 @@ def __iter__(self): def cardinality(self): """ + Returns the number of structures in this set. + EXAMPLES:: sage: F = species.SetSpecies() @@ -313,6 +392,10 @@ def cardinality(self): class StructuresWrapper(SpeciesWrapper): def __init__(self, species, labels, structure_class): """ + A base class for the set of structures of a species with given + set of labels. An object of this type is returned when you + call the :meth:`structures` method of a species. + EXAMPLES:: sage: F = species.SetSpecies() @@ -329,6 +412,10 @@ def __init__(self, species, labels, structure_class): class IsotypesWrapper(SpeciesWrapper): def __init__(self, species, labels, structure_class): """ + A base class for the set of isotypes of a species with given + set of labels. An object of this type is returned when you + call the :meth:`isotypes` method of a species. + EXAMPLES:: sage: F = species.SetSpecies() @@ -346,6 +433,10 @@ def __init__(self, species, labels, structure_class): class SimpleStructuresWrapper(SpeciesWrapper): def __init__(self, species, labels, structure_class): """ + .. warning:: + + This is deprecated and currently not used for anything. + EXAMPLES:: sage: F = species.SetSpecies() @@ -359,9 +450,14 @@ def __init__(self, species, labels, structure_class): "Simple structures", structure_class) + class SimpleIsotypesWrapper(SpeciesWrapper): def __init__(self, species, labels, structure_class): """ + .. warning:: + + This is deprecated and currently not used for anything. + EXAMPLES:: sage: F = species.SetSpecies() diff --git a/src/sage/combinat/species/subset_species.py b/src/sage/combinat/species/subset_species.py index 2e6fbfbdfdb..3064a7509d1 100644 --- a/src/sage/combinat/species/subset_species.py +++ b/src/sage/combinat/species/subset_species.py @@ -49,13 +49,17 @@ def canonical_label(self): rng = range(1, len(self._list)+1) return self.__class__(self.parent(), self._labels, rng) - def labels(self): + + def label_subset(self): """ + Returns a subset of the labels that "appear" in this + structure. + EXAMPLES:: sage: P = species.SubsetSpecies() sage: S = P.structures(["a", "b", "c"]) - sage: [s.labels() for s in S] + sage: [s.label_subset() for s in S] [[], ['a'], ['b'], ['c'], ['a', 'b'], ['a', 'c'], ['b', 'c'], ['a', 'b', 'c']] """ return [self._relabel(i) for i in self._list] diff --git a/src/sage/combinat/species/sum_species.py b/src/sage/combinat/species/sum_species.py index 62db8ff11a5..62f54960900 100644 --- a/src/sage/combinat/species/sum_species.py +++ b/src/sage/combinat/species/sum_species.py @@ -61,11 +61,38 @@ def __init__(self, F, G, min=None, max=None, weight=None): _default_structure_class = SumSpeciesStructure + def left_summand(self): + """ + Returns the left summand of this species. + + EXAMPLES:: + + sage: P = species.PermutationSpecies() + sage: F = P + P*P + sage: F.left_summand() + Permutation species + """ + return self._F + + def right_summand(self): + """ + Returns the right summand of this species. + + EXAMPLES:: + + sage: P = species.PermutationSpecies() + sage: F = P + P*P + sage: F.right_summand() + Product of (Permutation species) and (Permutation species) + """ + return self._G + def _name(self): """ Note that we use a function to return the name of this species because we can't do it in the __init__ method due to it - requiring that self._F and self._G already be unpickled. + requiring that self.left_summand() and self.right_summand() + already be unpickled. EXAMPLES:: @@ -74,7 +101,7 @@ def _name(self): sage: F._name() 'Sum of (Permutation species) and (Permutation species)' """ - return "Sum of (%s) and (%s)"%(self._F, self._G) + return "Sum of (%s) and (%s)"%(self.left_summand(), self.right_summand()) def _structures(self, structure_class, labels): """ @@ -85,10 +112,10 @@ def _structures(self, structure_class, labels): sage: F.structures([1,2]).list() [[1, 2], [2, 1], [1, 2], [2, 1]] """ - for res in self._F.structures(labels): + for res in self.left_summand().structures(labels): yield structure_class(self, res, tag="left") - for res in self._G.structures(labels): + for res in self.right_summand().structures(labels): yield structure_class(self, res, tag="right") def _isotypes(self, structure_class, labels): @@ -117,7 +144,8 @@ def _gs(self, series_ring, base_ring): sage: F.generating_series().coefficients(5) [2, 2, 2, 2, 2] """ - return self._F.generating_series(base_ring) + self._G.generating_series(base_ring) + return (self.left_summand().generating_series(base_ring) + + self.right_summand().generating_series(base_ring)) def _itgs(self, series_ring, base_ring): @@ -131,8 +159,8 @@ def _itgs(self, series_ring, base_ring): sage: F.isotype_generating_series().coefficients(5) [2, 2, 4, 6, 10] """ - return (self._F.isotype_generating_series(base_ring) + - self._G.isotype_generating_series(base_ring)) + return (self.left_summand().isotype_generating_series(base_ring) + + self.right_summand().isotype_generating_series(base_ring)) def _cis(self, series_ring, base_ring): """ @@ -149,7 +177,8 @@ def _cis(self, series_ring, base_ring): 2*p[1, 1, 1] + 2*p[2, 1] + 2*p[3], 2*p[1, 1, 1, 1] + 2*p[2, 1, 1] + 2*p[2, 2] + 2*p[3, 1] + 2*p[4]] """ - return self._F.cycle_index_series(base_ring) + self._G.cycle_index_series(base_ring) + return (self.left_summand().cycle_index_series(base_ring) + + self.right_summand().cycle_index_series(base_ring)) def weight_ring(self): """ @@ -171,7 +200,8 @@ def weight_ring(self): sage: C.weight_ring() Univariate Polynomial Ring in t over Rational Field """ - return self._common_parent([self._F.weight_ring(), self._G.weight_ring()]) + return self._common_parent([self.left_summand().weight_ring(), + self.right_summand().weight_ring()]) def _equation(self, var_mapping): """ diff --git a/src/sage/combinat/subword_complex.py b/src/sage/combinat/subword_complex.py index 9989765ece4..5c8b2021de0 100644 --- a/src/sage/combinat/subword_complex.py +++ b/src/sage/combinat/subword_complex.py @@ -496,7 +496,7 @@ def kappa_preimages(self): sage: kappa = SC.kappa_preimages() sage: for F in SC: print F, [w.reduced_word() for w in kappa[F]] (0, 1) [[]] - (0, 4) [[2, 1], [2]] + (0, 4) [[2], [2, 1]] (1, 2) [[1]] (2, 3) [[1, 2]] (3, 4) [[1, 2, 1]] @@ -948,8 +948,8 @@ def kappa_preimage(self): (0, 4) sage: F.kappa_preimage() [ - [-1 1] [ 1 0] - [-1 0], [ 1 -1] + [ 1 0] [-1 1] + [ 1 -1], [-1 0] ] """ W = self.parent().group() diff --git a/src/sage/combinat/tutorial.py b/src/sage/combinat/tutorial.py index 36f06856d7a..cd79c7622ba 100644 --- a/src/sage/combinat/tutorial.py +++ b/src/sage/combinat/tutorial.py @@ -223,7 +223,7 @@ sage: oeis([1,1,2,5,14]) # optional -- internet 0: A000108: Catalan numbers: C(n) = binomial(2n,n)/(n+1) = (2n)!/(n!(n+1)!). Also called Segner numbers. - 1: A120588: G.f. satisfies: 3*A(x) = 2 + x + A(x)^2, starting with [1,1,1]. + 1: A120588: G.f. satisfies: 3*A(x) = 2 + x + A(x)^2, with a(0) = 1. 2: A080937: Number of Catalan paths (nonnegative, starting and ending at 0, step +/-1) of 2*n steps with all values <= 5. The result suggests that the trees are counted by one of the most famous @@ -1730,10 +1730,9 @@ sage: oeis(L) # optional -- internet 0: A000045: Fibonacci numbers: F(n) = F(n-1) + F(n-2) with F(0) = 0 and F(1) = 1. - 1: A185357: Expansion of 1/(1 - x - x^2 + x^18 - x^20). + 1: A212804: Expansion of (1-x)/(1-x-x^2). 2: A132636: Fib(n) mod n^3. - This is an immediate consequence of the recurrence relation. One can also generate immediately all the Fibonacci words of a given length, with the same limitations resulting from the generic display. diff --git a/src/sage/crypto/block_cipher/miniaes.py b/src/sage/crypto/block_cipher/miniaes.py index ebb682cdfe4..a40545d2e6e 100644 --- a/src/sage/crypto/block_cipher/miniaes.py +++ b/src/sage/crypto/block_cipher/miniaes.py @@ -30,7 +30,7 @@ from sage.matrix.matrix_space import MatrixSpace from sage.monoids.string_monoid import BinaryStrings from sage.monoids.string_monoid_element import StringMonoidElement -from sage.rings.finite_rings.constructor import FiniteField +from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.rings.integer import Integer from sage.structure.sage_object import SageObject diff --git a/src/sage/crypto/block_cipher/sdes.py b/src/sage/crypto/block_cipher/sdes.py index 904728c6cd3..c45a21784ea 100644 --- a/src/sage/crypto/block_cipher/sdes.py +++ b/src/sage/crypto/block_cipher/sdes.py @@ -1255,7 +1255,7 @@ def permute_substitute(self, B, key): if len(key) != 8: raise ValueError("input key must be an 8-bit subkey") - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField GF = FiniteField(2, "x") bin = BinaryStrings() bin_to_GF2 = {bin("0"): GF(0), bin("1"): GF(1)} diff --git a/src/sage/crypto/boolean_function.pyx b/src/sage/crypto/boolean_function.pyx index 4ecc77ccac9..e4096667ea6 100644 --- a/src/sage/crypto/boolean_function.pyx +++ b/src/sage/crypto/boolean_function.pyx @@ -31,9 +31,9 @@ from libc.string cimport memcpy from sage.structure.sage_object cimport SageObject from sage.rings.integer_ring import ZZ from sage.rings.integer cimport Integer -from sage.rings.finite_rings.constructor import GF +from sage.rings.finite_rings.finite_field_constructor import GF from sage.rings.polynomial.pbori import BooleanPolynomial -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.finite_rings.finite_field_givaro import FiniteField_givaro from sage.rings.polynomial.polynomial_element import is_Polynomial diff --git a/src/sage/crypto/lfsr.py b/src/sage/crypto/lfsr.py index aa154d500d5..20285e0904c 100644 --- a/src/sage/crypto/lfsr.py +++ b/src/sage/crypto/lfsr.py @@ -148,7 +148,7 @@ from sage.structure.all import Sequence from sage.rings.all import Integer, PolynomialRing -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField def lfsr_sequence(key, fill, n): diff --git a/src/sage/crypto/mq/rijndael_gf.py b/src/sage/crypto/mq/rijndael_gf.py index 3af4eb37aa3..a9c0e0f0fe8 100644 --- a/src/sage/crypto/mq/rijndael_gf.py +++ b/src/sage/crypto/mq/rijndael_gf.py @@ -431,7 +431,7 @@ from sage.matrix.constructor import matrix from sage.matrix.constructor import column_matrix from sage.structure.element import Matrix -from sage.rings.finite_rings.constructor import FiniteField +from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.rings.integer import Integer from sage.structure.sage_object import SageObject from sage.matrix.matrix_space import MatrixSpace diff --git a/src/sage/crypto/mq/sbox.py b/src/sage/crypto/mq/sbox.py index 97b30adbefd..d95d0bcd2be 100644 --- a/src/sage/crypto/mq/sbox.py +++ b/src/sage/crypto/mq/sbox.py @@ -7,7 +7,7 @@ from sage.misc.misc_c import prod as mul from sage.modules.free_module_element import vector from sage.rings.finite_rings.element_base import is_FiniteFieldElement -from sage.rings.finite_rings.constructor import FiniteField as GF +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.rings.ideal import FieldIdeal, Ideal from sage.rings.integer_ring import ZZ from sage.rings.integer import Integer diff --git a/src/sage/crypto/mq/sr.py b/src/sage/crypto/mq/sr.py index f37d3d3ef27..ccea00666fb 100644 --- a/src/sage/crypto/mq/sr.py +++ b/src/sage/crypto/mq/sr.py @@ -310,7 +310,7 @@ Within the AES*\; in Advances in Cryptology \- CRYPTO 2002\; LNCS 2442\; Springer Verlag 2002 """ -from sage.rings.finite_rings.constructor import FiniteField as GF +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.rings.integer_ring import ZZ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing, BooleanPolynomialRing_constructor as BooleanPolynomialRing diff --git a/src/sage/crypto/stream.py b/src/sage/crypto/stream.py index 2964eb578d3..42be38832f2 100644 --- a/src/sage/crypto/stream.py +++ b/src/sage/crypto/stream.py @@ -18,7 +18,7 @@ from sage.crypto.util import random_blum_prime from sage.monoids.string_monoid import BinaryStrings from sage.arith.all import gcd, power_mod -from sage.rings.finite_rings.constructor import FiniteField +from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.rings.finite_rings.integer_mod_ring import IntegerModFactory from sage.rings.polynomial.polynomial_element import is_Polynomial from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing diff --git a/src/sage/data_structures/mutable_poset.py b/src/sage/data_structures/mutable_poset.py index 2f30ccb4535..f774cf33085 100644 --- a/src/sage/data_structures/mutable_poset.py +++ b/src/sage/data_structures/mutable_poset.py @@ -3451,6 +3451,11 @@ def map(self, function, topological=False, reverse=False): Since this method works inplace, it is not allowed that ``function`` alters the key of an element. + .. NOTE:: + + If ``function`` returns ``None``, then the element is + removed. + EXAMPLES:: sage: from sage.data_structures.mutable_poset import MutablePoset as MP @@ -3464,6 +3469,11 @@ def map(self, function, topological=False, reverse=False): sage: P poset((1, 2, 3), (1, 3, 4), (2, 1, 3), (2, 2, 4), (4, 4, 8)) + TESTS:: + + sage: P.map(lambda e: e if e[2] != 4 else None); P + poset((1, 2, 3), (2, 1, 3), (4, 4, 8)) + .. SEEALSO:: :meth:`copy`, @@ -3471,8 +3481,13 @@ def map(self, function, topological=False, reverse=False): """ shells = self.shells_topological(reverse=reverse) \ if topological else self.shells() + remove = [] for shell in shells: shell._element_ = function(shell._element_) + if shell._element_ is None: + remove.append(shell.key) + for key in remove: + self.remove(key) def mapped(self, function): diff --git a/src/sage/databases/oeis.py b/src/sage/databases/oeis.py index c625d52f9f7..ff882867bc8 100644 --- a/src/sage/databases/oeis.py +++ b/src/sage/databases/oeis.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" The On-Line Encyclopedia of Integer Sequences (OEIS) @@ -25,10 +26,12 @@ sage: search = oeis([3, 7, 15, 1], max_results=4) ; search # optional -- internet 0: A001203: Continued fraction expansion of Pi. - 1: A165416: Irregular array read by rows: The n-th row contains those distinct positive integers that each, when written in binary, occurs as a substring in binary n. - 2: A193583: Number of fixed points under iteration of sum of squares of digits in base b. - 3: A082495: (2^n-1) mod n. + 1: A082495: a(n) = (2^n - 1) mod n. + 2: A165416: Irregular array read by rows: The n-th row contains those distinct positive integers that each, when written in binary, occurs as a substring in binary n. + 3: A246674: Run Length Transform of A000225. + sage: [u.id() for u in search] # optional -- internet + ['A001203', 'A082495', 'A165416', 'A246674'] sage: c = search[0] ; c # optional -- internet A001203: Continued fraction expansion of Pi. @@ -40,7 +43,7 @@ sage: c.examples() # optional -- internet 0: Pi = 3.1415926535897932384... 1: = 3 + 1/(7 + 1/(15 + 1/(1 + 1/(292 + ...)))) - 2: = [a_0; a_1, a_2, a_3, ...] = [3; 7, 15, 292, ...] + 2: = [a_0; a_1, a_2, a_3, ...] = [3; 7, 15, 1, 292, ...] sage: c.comments() # optional -- internet 0: The first 5,821,569,425 terms were computed by _Eric W. Weisstein_ on Sep 18 2011. @@ -49,8 +52,8 @@ :: - sage: x = c.natural_object() ; x.parent() # optional -- internet - Field of all continued fractions + sage: x = c.natural_object() ; type(x) # optional -- internet + sage: x.convergents()[:7] # optional -- internet [3, 22/7, 333/106, 355/113, 103993/33102, 104348/33215, 208341/66317] @@ -86,7 +89,7 @@ 0: A000798: Number of different quasi-orders (or topologies, or transitive digraphs) with n labeled elements. 1: A001035: Number of partially ordered sets ("posets") with n labeled elements (or labeled acyclic transitive digraphs). 2: A001930: Number of topologies, or transitive digraphs with n unlabeled nodes. - 3: A006057: Number of labeled topologies with n points. + 3: A006057: Number of topologies on n labeled points satisfying axioms T_0-T_4. 4: A079263: Number of constrained mixed models with n factors. 5: A079265: Number of antisymmetric transitive binary relations on n unlabeled points. @@ -103,15 +106,15 @@ 5832742205057, 51724158235372] sage: oeis(L) # optional -- internet - 0: A000110: Bell or exponential numbers: ways of placing n labeled balls into n indistinguishable boxes. + 0: A000110: Bell or exponential numbers: number of ways to partition a set of n labeled elements. sage: b = _[0] # optional -- internet sage: b.formulas()[0] # optional -- internet - 'E.g.f.: exp( exp(x) - 1).' + 'E.g.f.: exp(exp(x) - 1).' - sage: b.comments()[89] # optional -- internet - 'Number n is prime if mod(a(n)-2,n) = 0. [From _Dmitry Kruchinin_, Feb 14 2012]' + sage: [i for i in b.comments() if 'prime' in i][-1] # optional -- internet + 'Number n is prime if mod(a(n)-2,n) = 0. -_Dmitry Kruchinin_, Feb 14 2012' sage: [n for n in range(2, 20) if (b(n)-2) % n == 0] # optional -- internet [2, 3, 5, 7, 11, 13, 17, 19] @@ -144,13 +147,7 @@ #***************************************************************************** from sage.structure.sage_object import SageObject -from sage.structure.sequence import Sequence -from sage.misc.lazy_import import lazy_import -lazy_import('sage.rings.semirings.non_negative_integer_semiring', 'NN') -from sage.rings.integer_ring import IntegerRing from sage.rings.integer import Integer -from sage.rings.contfrac import ContinuedFractionField -from sage.rings.real_lazy import RealLazyField from sage.misc.misc import verbose from sage.misc.cachefunc import cached_method from sage.misc.flatten import flatten @@ -163,6 +160,7 @@ oeis_url = 'http://oeis.org/' + def _fetch(url): r""" Fetch the given ``url``. @@ -182,7 +180,7 @@ def _fetch(url): '' """ try: - _ = verbose("Fetching URL %s ..." %url, caller_name='OEIS') + verbose("Fetching URL %s ..." % url, caller_name='OEIS') f = urlopen(url) result = f.read() f.close() @@ -190,6 +188,7 @@ def _fetch(url): except IOError as msg: raise IOError("%s\nError fetching %s." % (msg, url)) + def _urls(html_string): r""" Return the list of URLs contained in ``html_string``. @@ -215,6 +214,7 @@ def _urls(html_string): """ urls = [] from HTMLParser import HTMLParser + class MyHTMLParser(HTMLParser): def handle_starttag(self, tag, attrs): if tag == 'a': @@ -224,8 +224,10 @@ def handle_starttag(self, tag, attrs): MyHTMLParser().feed(html_string) return urls + to_tuple = lambda string: tuple(Integer(x) for x in string.split(",") if x) + class OEIS: r""" The On-Line Encyclopedia of Integer Sequences. @@ -280,7 +282,7 @@ class OEIS: sage: search = oeis([1,2,3,5,8,13]) ; search # optional -- internet 0: A000045: Fibonacci numbers: F(n) = F(n-1) + F(n-2) with F(0) = 0 and F(1) = 1. - 1: A027926: Triangular array T read by rows: T(n,0)=T(n,2n)=1 for n >= 0; ... + 1: A027926: Triangular array T read by rows: T(n,0) = T(n,2n) = 1 for n >= 0; T(n,1) = 1 for n >= 1; T(n,k) = T(n-1,k-2) + T(n-1,k-1) for k = 2..2n-1, n >= 2. 2: A001129: Iccanobif numbers: reverse digits of two previous terms and add. sage: fibo = search[0] # optional -- internet @@ -310,7 +312,7 @@ class OEIS: sage: sfibo.first_terms(absolute_value=True)[2:20] == fibo.first_terms()[:18] # optional -- internet True - sage: fibo.formulas()[3] # optional -- internet + sage: fibo.formulas()[4] # optional -- internet 'F(n) = F(n-1) + F(n-2) = -(-1)^n F(-n).' sage: fibo.comments()[1] # optional -- internet @@ -335,7 +337,7 @@ class OEIS: sage: oeis([1,2,3,5,8,13]) # optional -- internet 0: A000045: Fibonacci numbers: F(n) = F(n-1) + F(n-2) with F(0) = 0 and F(1) = 1. - 1: A027926: Triangular array T read by rows: T(n,0)=T(n,2n)=1 for n >= 0; ... + 1: A027926: Triangular array T read by rows: T(n,0) = T(n,2n) = 1 for n >= 0; T(n,1) = 1 for n >= 1; T(n,k) = T(n-1,k-2) + T(n-1,k-1) for k = 2..2n-1, n >= 2. 2: A001129: Iccanobif numbers: reverse digits of two previous terms and add. sage: fibo = oeis('A000045') # optional -- internet @@ -346,7 +348,7 @@ class OEIS: sage: oeis([1,2,3,5,8,13]) # optional -- internet 0: A000045: Fibonacci numbers: F(n) = F(n-1) + F(n-2) with F(0) = 0 and F(1) = 1. - 1: A027926: Triangular array T read by rows: T(n,0)=T(n,2n)=1 for n >= 0; ... + 1: A027926: Triangular array T read by rows: T(n,0) = T(n,2n) = 1 for n >= 0; T(n,1) = 1 for n >= 1; T(n,k) = T(n-1,k-2) + T(n-1,k-1) for k = 2..2n-1, n >= 2. 2: A001129: Iccanobif numbers: reverse digits of two previous terms and add. sage: fibo = _[0] # optional -- internet @@ -364,7 +366,7 @@ def __call__(self, query, max_results=3, first_result=0): TypeError: __call__() takes at least 2 arguments (1 given) """ if isinstance(query, str): - if re.match('^A[0-9]{6}$',query): + if re.match('^A[0-9]{6}$', query): return self.find_by_id(query) else: return self.find_by_description(query, max_results, first_result) @@ -408,7 +410,7 @@ def find_by_id(self, ident): if not isinstance(ident, str): ident = str(ident) ident = 'A000000'[:-len(ident)] + ident - options = {'q':ident, 'n':'1', 'fmt':'text'} + options = {'q': ident, 'n': '1', 'fmt': 'text'} url = oeis_url + "search?" + urlencode(options) sequence = _fetch(url).split('\n\n')[2] return OEISSequence(sequence) @@ -459,10 +461,10 @@ def find_by_description(self, description, max_results=3, first_result=0): 2: A131957: Busy Beaver sigma variation: maximum number of 1's ... 3: A052200: Number of n-state, 2-symbol, d+ in {LEFT, RIGHT}, ... """ - options = {'q':description, - 'n':str(max_results), - 'fmt':'text', - 'start':str(first_result)} + options = {'q': description, + 'n': str(max_results), + 'fmt': 'text', + 'start': str(first_result)} url = oeis_url + "search?" + urlencode(options) sequence_list = _fetch(url).split('\n\n')[2:-1] return FancyTuple([OEISSequence(_) for _ in sequence_list]) @@ -493,7 +495,7 @@ def find_by_subsequence(self, subsequence, max_results=3, first_result=0): sage: oeis.find_by_subsequence([2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]) # optional -- internet 0: A000045: Fibonacci numbers: F(n) = F(n-1) + F(n-2) with F(0) = 0 and F(1) = 1. 1: A177194: Fibonacci numbers whose decimal expression does not contain any digit 0. - 2: A020695: Pisot sequence E(2,3). + 2: A212804: Expansion of (1-x)/(1-x-x^2). sage: fibo = _[0] ; fibo # optional -- internet A000045: Fibonacci numbers: F(n) = F(n-1) + F(n-2) with F(0) = 0 and F(1) = 1. @@ -599,6 +601,7 @@ def _imaginary_sequence(self, keywords='sign,easy'): """ return OEISSequence(self._imaginary_entry(keywords)) + class OEISSequence(SageObject): r""" The class of OEIS sequences. @@ -814,7 +817,7 @@ def author(self): A000045: Fibonacci numbers: F(n) = F(n-1) + F(n-2) with F(0) = 0 and F(1) = 1. sage: f.author() # optional -- internet - '_N. J. A. Sloane_.' + '_N. J. A. Sloane_, Apr 30 1991' TESTS:: @@ -838,7 +841,7 @@ def keywords(self): A000045: Fibonacci numbers: F(n) = F(n-1) + F(n-2) with F(0) = 0 and F(1) = 1. sage: f.keywords() # optional -- internet - ('core', 'nonn', 'easy', 'nice', 'changed') + ('core', 'nonn', 'nice', 'easy', 'hear') TESTS:: @@ -863,8 +866,7 @@ def natural_object(self): RealLazyField()). - If the sequence ``self`` corresponds to the convergents of a - continued fraction, returns the associated continued - fraction (as an element of ContinuedFractionField()). + continued fraction, returns the associated continued fraction. .. WARNING:: @@ -882,10 +884,14 @@ def natural_object(self): sage: g = oeis("A002852") ; g # optional -- internet A002852: Continued fraction for Euler's constant (or Euler-Mascheroni constant) gamma. - sage: x = g.natural_object() ; x.parent() # optional -- internet - Field of all continued fractions + sage: x = g.natural_object() ; type(x) # optional -- internet + - sage: x[:20] == continued_fraction(euler_gamma, nterms=20) # optional -- internet + sage: RDF(x) == RDF(euler_gamma) # optional -- internet + True + + sage: cfg = continued_fraction(euler_gamma) + sage: x[:90] == cfg[:90] # optional -- internet True :: @@ -921,7 +927,7 @@ def natural_object(self): :: sage: sfib = oeis('A039834') ; sfib # optional -- internet - A039834: a(n+2)=-a(n+1)+a(n) (signed Fibonacci numbers); or Fibonacci numbers (A000045) extended to negative indices. + A039834: a(n+2) = -a(n+1)+a(n) (signed Fibonacci numbers); or Fibonacci numbers (A000045) extended to negative indices. sage: x = sfib.natural_object() ; x.universe() # optional -- internet Integer Ring @@ -929,8 +935,8 @@ def natural_object(self): TESTS:: sage: s = oeis._imaginary_sequence('nonn,cofr') - sage: s.natural_object().parent() - QQ as continued fractions + sage: type(s.natural_object()) + sage: s = oeis._imaginary_sequence('nonn') sage: s.natural_object().universe() @@ -941,15 +947,21 @@ def natural_object(self): Integer Ring """ if 'cofr' in self.keywords() and not 'frac' in self.keywords(): - return ContinuedFractionField()(self.first_terms()) - if 'cons' in self.keywords(): + from sage.rings.continued_fraction import continued_fraction + return continued_fraction(self.first_terms()) + elif 'cons' in self.keywords(): offset = self.offsets()[0] terms = self.first_terms() + tuple([0] * abs(offset)) + from sage.rings.real_lazy import RealLazyField return RealLazyField()('0' + ''.join(map(str, terms[:offset])) + '.' + ''.join(map(str, terms[offset:]))) elif 'nonn' in self.keywords(): + from sage.structure.sequence import Sequence + from sage.rings.semirings.non_negative_integer_semiring import NN return Sequence(self.first_terms(), NN) else: - return Sequence(self.first_terms(),IntegerRing()) + from sage.structure.sequence import Sequence + from sage.rings.integer_ring import ZZ + return Sequence(self.first_terms(), ZZ) def is_finite(self): r""" @@ -971,7 +983,7 @@ def is_finite(self): EXAMPLES:: sage: s = oeis('A114288') ; s # optional -- internet - A114288: Lexicographically minimal solution of any 9 X 9 sudoku, read by rows. + A114288: Lexicographically earliest solution of any 9 X 9 sudoku, read by rows. sage: s.is_finite() # optional -- internet True @@ -1017,7 +1029,7 @@ def is_full(self): EXAMPLES:: sage: s = oeis('A114288') ; s # optional -- internet - A114288: Lexicographically minimal solution of any 9 X 9 sudoku, read by rows. + A114288: Lexicographically earliest solution of any 9 X 9 sudoku, read by rows. sage: s.is_full() # optional -- internet True @@ -1096,9 +1108,9 @@ def first_terms(self, number=None, absolute_value=False): 1 """ if absolute_value or ('nonn' in self.keywords()): - fields = ['S','T','U'] + fields = ['S', 'T', 'U'] elif ('sign' in self.keywords()): - fields = ['V','W','X'] + fields = ['V', 'W', 'X'] else: raise TypeError("You found a sign inconsistency, please contact OEIS") return to_tuple(" ".join(flatten([self._fields[a] for a in fields])))[:number] @@ -1301,9 +1313,8 @@ def __iter__(self): if not self.is_full(): raise LookupError("Future values not provided by OEIS.") - def __eq__(self,other): + def __eq__(self, other): r""" - Returns ``True`` if ``self`` is equal to ``other`` and ``False`` otherwise. Two integer sequences are considered equal if they have the same OEIS ID. @@ -1332,7 +1343,6 @@ def __eq__(self,other): def __ne__(self, other): r""" - Returns ``True`` if ``self`` has a different OEIS ID than ``other`` and ``False`` otherwise. @@ -1373,7 +1383,8 @@ def references(self): sage: w.references() # optional -- internet 0: A. H. Beiler, Recreations in the Theory of Numbers, Dover, NY, 1964, p. 52. 1: C. Clawson, Mathematical Mysteries, Plenum Press, 1996, p. 180. - 2: Edgar Costa, Robert Gerbicz, and David Harvey, A search for Wilson primes, 2012 + 2: R. Crandall and C. Pomerance, Prime Numbers: A Computational Perspective, Springer, NY, 2001; see p. 29. + 3: G. H. Hardy and E. M. Wright, An Introduction to the Theory of Numbers, 5th ed., Oxford Univ. Press, 1979, th. 80. ... sage: _[0] # optional -- internet @@ -1461,7 +1472,7 @@ def links(self, browse=None, format='guess'): webbrowser.open(url_list[url_number]) elif browse == 'all': for url in url_list: - webbrowser.open(url) + webbrowser.open(url) def formulas(self): r""" @@ -1476,7 +1487,7 @@ def formulas(self): sage: f = oeis(45) ; f # optional -- internet A000045: Fibonacci numbers: F(n) = F(n-1) + F(n-2) with F(0) = 0 and F(1) = 1. - sage: f.formulas()[1] # optional -- internet + sage: f.formulas()[2] # optional -- internet 'F(n) = ((1+sqrt(5))^n-(1-sqrt(5))^n)/(2^n*sqrt(5)).' TESTS:: @@ -1542,10 +1553,10 @@ def extensions_or_errors(self): EXAMPLES:: sage: sfibo = oeis('A039834') ; sfibo # optional -- internet - A039834: a(n+2)=-a(n+1)+a(n) (signed Fibonacci numbers); or Fibonacci numbers (A000045) extended to negative indices. + A039834: a(n+2) = -a(n+1)+a(n) (signed Fibonacci numbers); or Fibonacci numbers (A000045) extended to negative indices. sage: sfibo.extensions_or_errors()[0] # optional -- internet - 'Signs corrected by Len Smiley (smiley(AT)math.uaa.alaska.edu) and _N. J. A. Sloane_.' + 'Signs corrected by _Len Smiley_ and _N. J. A. Sloane_.' TESTS:: @@ -1572,7 +1583,7 @@ def examples(self): sage: c.examples() # optional -- internet 0: Pi = 3.1415926535897932384... 1: = 3 + 1/(7 + 1/(15 + 1/(1 + 1/(292 + ...)))) - 2: = [a_0; a_1, a_2, a_3, ...] = [3; 7, 15, 292, ...] + 2: = [a_0; a_1, a_2, a_3, ...] = [3; 7, 15, 1, 292, ...] TESTS:: @@ -1596,9 +1607,9 @@ def comments(self): A000045: Fibonacci numbers: F(n) = F(n-1) + F(n-2) with F(0) = 0 and F(1) = 1. sage: f.comments()[:3] # optional -- internet - ("Also called Lam{\\'e}'s sequence.", - "F(n+2) = number of binary sequences of length n that have no consecutive 0's.", - 'F(n+2) = number of subsets of {1,2,...,n} that contain no consecutive integers.') + 0: Also sometimes called Lamé's sequence. + 1: F(n+2) = number of binary sequences of length n that have no consecutive 0's. + 2: F(n+2) = number of subsets of {1,2,...,n} that contain no consecutive integers. TESTS:: @@ -1639,8 +1650,9 @@ def browse(self): EXAMPLES:: - sage: f = oeis(45) ; f # optional -- internet + sage: f = oeis(45) ; f # optional -- internet webbrowser A000045: Fibonacci numbers: F(n) = F(n-1) + F(n-2) with F(0) = 0 and F(1) = 1. + sage: f.browse() # optional -- internet webbrowser TESTS:: @@ -1663,16 +1675,16 @@ def show(self): A012345 NAME - sinh(arcsin(x)*arcsin(x))=2/2!*x^2+8/4!*x^4+248/6!*x^6+11328/8!*x^8... + Coefficients in the expansion sinh(arcsin(x)*arcsin(x)) = 2*x^2/2!+8*x^4/4!+248*x^6/6!+11328*x^8/8!+... FIRST TERMS - (2, 8, 248, 11328, 849312, 94857600, 14819214720, 3091936512000, ... - - KEYWORDS - ('nonn',) + (2, 8, 248, 11328, 849312, 94857600, 14819214720, 3091936512000, 831657655349760, 280473756197529600, 115967597965430077440, 57712257892456911912960, 34039765801079493369569280) + FORMULAS + ... OFFSETS (0, 1) + URL http://oeis.org/A012345 @@ -1702,14 +1714,14 @@ def show(self): 'programs', 'keywords', 'offsets', 'url', 'old_IDs', 'author', 'extensions_or_errors']: if embedded() and s == 'links': - print re.sub('_',' ',s).upper() + print re.sub('_', ' ', s).upper() getattr(self, s)() print '\n' else: result = getattr(self, s)() if result != '' and result != ('',) and result != (): - print re.sub('_',' ',s).upper() - print str(result) + '\n' + print re.sub('_', ' ', s).upper() + print str(result) + '\n' def programs(self, language='other'): r""" @@ -1732,7 +1744,7 @@ def programs(self, language='other'): A001113: Decimal expansion of e. sage: ee.programs()[0] # optional -- internet - '(PARI) { default(realprecision, 50080); x=exp(1); for (n=1, 50000, d=floor(x); x=(x-d)*10; write("b001113.txt", n, " ", d)); } [From Harry J. Smith, Apr 15 2009]' + '(PARI) { default(realprecision, 50080); x=exp(1); for (n=1, 50000, d=floor(x); x=(x-d)*10; write("b001113.txt", n, " ", d)); } \\\\ _Harry J. Smith_, Apr 15 2009' TESTS:: @@ -1761,6 +1773,7 @@ def programs(self, language='other'): else: return FancyTuple(self._fields['o']) + class FancyTuple(tuple): r""" This class inherits from ``tuple``, it allows to nicely print tuples whose @@ -1794,8 +1807,30 @@ def __repr__(self): 3: three 4: 4 """ - length = len(str(len(self)-1)) + length = len(str(len(self) - 1)) return '\n'.join((('{0:>%d}' % length).format(str(i)) + ': ' + str(self[i]) for i in range(len(self)))) -oeis = OEIS() + def __getslice__(self, i, j): + r""" + The slice of a FancyTuple remains a FancyTuple. + + EXAMPLES:: + sage: from sage.databases.oeis import FancyTuple + sage: t = FancyTuple(['zero', 'one', 'two', 'three', 4]) + sage: t[-2:] + 0: three + 1: 4 + + TESTS:: + + sage: t = ('é', 'è', 'à', 'ç') + sage: t + ('\xc3\xa9', '\xc3\xa8', '\xc3\xa0', '\xc3\xa7') + sage: FancyTuple(t)[2:4] + 0: à + 1: ç + """ + return FancyTuple(tuple(self).__getslice__(i, j)) + +oeis = OEIS() diff --git a/src/sage/functions/log.py b/src/sage/functions/log.py index a25b85f5976..1b7dfd0b00f 100644 --- a/src/sage/functions/log.py +++ b/src/sage/functions/log.py @@ -39,6 +39,8 @@ def __init__(self): 12.182493960703473 sage: exp(RDF('2.5')) 12.182493960703473 + sage: exp(I*pi/12) + 1/12*sqrt(6)*(sqrt(3) + 3) - 1/12*I*sqrt(6)*(sqrt(3) - 3) To prevent automatic evaluation, use the ``hold`` parameter:: diff --git a/src/sage/functions/transcendental.py b/src/sage/functions/transcendental.py index 6f1327c6e1f..1a2681967b9 100644 --- a/src/sage/functions/transcendental.py +++ b/src/sage/functions/transcendental.py @@ -63,6 +63,10 @@ def __init__(self): zeta(I) sage: zeta(I).n() 0.00330022368532410 - 0.418155449141322*I + sage: zeta(sqrt(2)) + zeta(sqrt(2)) + sage: zeta(sqrt(2)).n() # rel tol 1e-10 + 3.02073767948603 It is possible to use the ``hold`` argument to prevent automatic evaluation:: @@ -79,7 +83,7 @@ def __init__(self): Check that :trac:`15846` is resolved:: sage: zeta(x).series(x==1, 1) - 1*(x - 1)^(-1) + (euler_gamma + log(2) + log(pi) + 2*zetaderiv(1, 0)) + Order(x - 1) + 1*(x - 1)^(-1) + (euler_gamma) + Order(x - 1) sage: zeta(x).residue(x==1) 1 @@ -95,6 +99,13 @@ def __init__(self): Infinity sage: zeta(x).subs(x=1) Infinity + + Check that :trac:`19799` is resolved:: + + sage: zeta(pi) + zeta(pi) + sage: zeta(pi).n() # rel tol 1e-10 + 1.17624173838258 """ GinacFunction.__init__(self, "zeta") diff --git a/src/sage/functions/trig.py b/src/sage/functions/trig.py index b362662a84f..b6c397a5036 100644 --- a/src/sage/functions/trig.py +++ b/src/sage/functions/trig.py @@ -160,6 +160,10 @@ def __init__(self): sage: tan(complex(1,1)) # rel tol 1e-15 (0.2717525853195118+1.0839233273386946j) + Check that :trac:`19791` is fixed:: + + sage: tan(2+I).imag().n() + 1.16673625724092 """ GinacFunction.__init__(self, "tan", latex_name=r"\tan") diff --git a/src/sage/games/hexad.py b/src/sage/games/hexad.py index 30911094693..4abe37b662c 100644 --- a/src/sage/games/hexad.py +++ b/src/sage/games/hexad.py @@ -79,7 +79,7 @@ from sage.rings.rational_field import RationalField QQ = RationalField() infinity = Infinity -from sage.rings.finite_rings.constructor import FiniteField +from sage.rings.finite_rings.finite_field_constructor import FiniteField GF = FiniteField from sage.calculus.calculus import SR #SR = SymbolicRing() diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index 44527666dde..6122592820c 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -3326,7 +3326,7 @@ def complex(self, base_ring=ZZ, extended=False): 0 where the leftmost non-zero entry is in degree `0` and the - rightmost entry in degree `d`. See [Klyachko], eq. (3.2). This + rightmost entry in degree `d`. See [Klyachko]_, eq. (3.2). This complex computes the homology of `|\Sigma|\subset N_\RR` with arbitrary support, @@ -3429,13 +3429,6 @@ def complex(self, base_ring=ZZ, extended=False): sage: fan = Fan([Cone([(-1,0,0),(0,-1,0),(0,0,-1)])]) sage: fan.complex().homology() {0: 0, 1: 0, 2: 0, 3: 0} - - REFERENCES: - - .. [Klyachko] - A. A. Klyachko, - Equivariant Bundles on Toral Varieties. - Mathematics of the USSR - Izvestiya 35 (1990), 337-375. """ dim = self.dim() delta = dict() diff --git a/src/sage/geometry/hyperplane_arrangement/arrangement.py b/src/sage/geometry/hyperplane_arrangement/arrangement.py index bb58ffd52a0..ba7880cf079 100644 --- a/src/sage/geometry/hyperplane_arrangement/arrangement.py +++ b/src/sage/geometry/hyperplane_arrangement/arrangement.py @@ -331,7 +331,6 @@ # Possible extensions for hyperplane_arrangement.py: # - the big face lattice -# - Orlik-Solomon algebras # - create ties with the Sage matroid methods # - hyperplane arrangements over other fields @@ -1966,6 +1965,31 @@ def matroid(self): from sage.matroids.constructor import Matroid return Matroid(matrix=matrix(norms).transpose()) + def orlik_solomon_algebra(self, base_ring=None, ordering=None): + """ + Return the Orlik-Solomon algebra of ``self``. + + INPUT: + + - ``base_ring`` -- (default: the base field of ``self``) the ring + over which the Orlik-Solomon algebra will be defined + - ``ordering`` -- (optional) an ordering of the ground set + + EXAMPLES:: + + sage: P. = HyperplaneArrangements(QQ) + sage: A = P(x, y, z, x+y+z, 2*x+y+z, 2*x+3*y+z, 2*x+3*y+4*z) + sage: A.orlik_solomon_algebra() + Orlik-Solomon algebra of Linear matroid of rank 3 on 7 elements + represented over the Rational Field + sage: A.orlik_solomon_algebra(base_ring=ZZ) + Orlik-Solomon algebra of Linear matroid of rank 3 on 7 elements + represented over the Rational Field + """ + if base_ring is None: + base_ring = self.base_ring() + return self.matroid().orlik_solomon_algebra(base_ring, ordering) + @cached_method def minimal_generated_number(self): r""" diff --git a/src/sage/geometry/polyhedron/constructor.py b/src/sage/geometry/polyhedron/constructor.py index 192f3042560..e1a34b7b5ef 100644 --- a/src/sage/geometry/polyhedron/constructor.py +++ b/src/sage/geometry/polyhedron/constructor.py @@ -406,7 +406,7 @@ def Polyhedron(vertices=None, rays=None, lines=None, if all(is_Integer(x) for x in values): if got_Vrep: base_ring = ZZ - else: # integral inequalities usually do not determine a latice polytope! + else: # integral inequalities usually do not determine a lattice polytope! base_ring = QQ convert = False elif all(is_Rational(x) for x in values): diff --git a/src/sage/graphs/digraph_generators.py b/src/sage/graphs/digraph_generators.py index f9966376f99..9da5939de33 100644 --- a/src/sage/graphs/digraph_generators.py +++ b/src/sage/graphs/digraph_generators.py @@ -293,7 +293,7 @@ def ButterflyGraph(self, n, vertices='strings'): butterfly[(padded_bv,i)]=[(padded_bv,i+1), (padded_bw,i+1)] elif vertices=='vectors': from sage.modules.free_module import VectorSpace - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField from copy import copy butterfly = {} for v in VectorSpace(FiniteField(2),n): diff --git a/src/sage/graphs/generators/classical_geometries.py b/src/sage/graphs/generators/classical_geometries.py index d67dd828664..53c0b81ea3c 100644 --- a/src/sage/graphs/generators/classical_geometries.py +++ b/src/sage/graphs/generators/classical_geometries.py @@ -22,7 +22,7 @@ from sage.graphs.graph import Graph from sage.graphs import graph from sage.arith.all import is_prime_power -from sage.rings.finite_rings.constructor import FiniteField +from sage.rings.finite_rings.finite_field_constructor import FiniteField def SymplecticPolarGraph(d, q, algorithm=None): r""" @@ -1181,7 +1181,7 @@ def HaemersGraph(q, hyperoval=None, hyperoval_matching=None, field=None, check_h """ from sage.modules.free_module_element import free_module_element as vector - from sage.rings.finite_rings.constructor import GF + from sage.rings.finite_rings.finite_field_constructor import GF from itertools import combinations p, k = is_prime_power(q,get_data=True) diff --git a/src/sage/graphs/generators/families.py b/src/sage/graphs/generators/families.py index 74810ca98fa..13554456110 100644 --- a/src/sage/graphs/generators/families.py +++ b/src/sage/graphs/generators/families.py @@ -47,7 +47,7 @@ def JohnsonGraph(n, k): True The complement of the Johnson graph `J(n,2)` is isomorphic to the Kneser - Graph `K(n,2)`. In paritcular the complement of `J(5,2)` is isomorphic to + Graph `K(n,2)`. In particular the complement of `J(5,2)` is isomorphic to the Petersen graph. :: sage: g = graphs.JohnsonGraph(5,2) @@ -1587,7 +1587,7 @@ def PaleyGraph(q): True """ from sage.rings.finite_rings.integer_mod import mod - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.arith.all import is_prime_power assert is_prime_power(q), "Parameter q must be a prime power" assert mod(q,4)==1, "Parameter q must be congruent to 1 mod 4" @@ -2558,7 +2558,7 @@ def MathonPseudocyclicStronglyRegularGraph(t, G=None, L=None): Colloq. Math. Soc. János Bolyai, 25, North-Holland, Amsterdam-New York, 1981. """ - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.rings.integer_ring import ZZ from sage.matrix.constructor import matrix, block_matrix, \ ones_matrix, identity_matrix diff --git a/src/sage/graphs/generators/smallgraphs.py b/src/sage/graphs/generators/smallgraphs.py index 99f8039a9ea..0d453759b7a 100644 --- a/src/sage/graphs/generators/smallgraphs.py +++ b/src/sage/graphs/generators/smallgraphs.py @@ -1413,7 +1413,7 @@ def BrouwerHaemersGraph(): sage: set(g.spectrum()) == {20,2,-7} True """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.modules.free_module import VectorSpace from sage.matrix.constructor import Matrix from sage.matrix.constructor import identity_matrix @@ -1949,7 +1949,7 @@ def DejterGraph(): """ from sage.graphs.generators.families import CubeGraph from sage.coding.code_constructions import HammingCode - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField from string import join g = CubeGraph(7) @@ -4859,7 +4859,7 @@ def JankoKharaghaniGraph(v): http://journals.cambridge.org/article_S1446788700033929 """ - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.matrix.constructor import matrix # The notations of [JK02] are rather tricky, and so this code attempts to @@ -4930,3 +4930,72 @@ def JankoKharaghaniGraph(v): return Graph([e for e,v in D.dict().iteritems() if v == 1], multiedges=False, name="Janko-Kharaghani") + +def JankoKharaghaniTonchevGraph(): + r""" + Returns a (324,153,72,72)-strongly regular graph from [JKT01]_ + + Build the graph using the description given in [JKT01]_, taking + sets B1 and B163 in the text as adjacencies of vertices 1 and 163, + respectively, and taking the edge orbits of the group `G` provided. + + EXAMPLES:: + + sage: Gamma=graphs.JankoKharaghaniTonchevGraph() # long time + sage: Gamma.is_strongly_regular(parameters=True) # long time + (324, 153, 72, 72) + + REFERENCES: + + .. [JKT01] Z.Janko, H.Kharaghani, V.D.Tonchev + The existence of a Bush-type Hadamard matrix of order 324 + and two new infinite classes of symmetric designs. + Des. Codes Cryptogr. 24(2001), 225-232 + + """ + from itertools import product + from sage.misc.misc_c import prod + from sage.combinat.permutation import Permutation as P + from sage.libs.gap.libgap import libgap + + m1=prod([P((9*x+k,9*x+k+3,9*x+k+6)) for k,x in product(xrange(1,4),xrange(36))]) + m2=prod([P((3*x+1,3*x+2,3*x+3)) for x in xrange(108)]) + t=prod(prod(map(P,[(9*x+2,9*x+3),(9*x+4,9*x+7),(9*x+5,9*x+9),(9*x+6,9*x+8)])) for + x in xrange(36)) + n1=prod(prod(map(P,[(1+x,19+x,37+x),(55+x,73+x,91+x),(109+x,127+x,145+x), + (163+x,181+x,199+x),(217+x,235+x,253+x),(271+x,289+x,307+x)])) + for x in xrange(18)) + n2=prod(prod(map(P,[(1+x,55+x,109+x),(19+x,73+x,127+x),(37+x,91+x,145+x), + (163+x,217+x,271+x),(181+x,235+x,289+x),(199+x,253+x,307+x)])) + for x in xrange(18)) + s=prod(prod(map(P,[(19+x,37+x),(55+x,109+x),(73+x,145+x),(91+x,127+x), + (181+x,199+x),(217+x,271+x),(235+x,307+x),(253+x,289+x)])) + for x in xrange(18)) + k=prod(prod(map(P,[(18*x+1,18*x+10),(18*x+2,18*x+11),(18*x+3,18*x+12), + (18*x+4,18*x+13),(18*x+5,18*x+14),(18*x+6,18*x+15),(18*x+7,18*x+16), + (18*x+8,18*x+17),(18*x+9,18*x+18)])) + for x in xrange(18)) + G=libgap.Group(map(lambda p: libgap.PermList(p), [m1,m2,t,n1,n2,s,k])) + st=libgap.Group(map(lambda p: libgap.PermList(p), [t,s])) + B1=(19,22,25,29,30,31,33,34,35,37,40,43,47,48,49,51,52,53,55,56,57,65, + 66,67,68,70,72,76,77,78,79,80,81,82,86,90,92,93,95,96,98,99,100,105,107, + 109,110,111,119,120,121,122,124,126,128,129,131,132,134,135,136,141,143, + 148,149,150,151,152,153,154,158,162,167,168,170,171,172,176,177,179,180, + 184,186,187,188,190,191,192,193,196,202,204,205,206,208,209,210,211,214, + 218,219,221,225,226,227,228,229,232,236,237,238,241,244,245,246,249,251, + 254,255,256,259,262,265,266,268,270,272,273,275,279,280,281,282,283,286, + 290,291,292,295,298,301,302,304,306,308,309,310,313,316,317,318,321,323) + B163=(5,6,8,9,10,14,15,17,18,22,24,25,26,28,29,30,31,34,40,42,43,44,46, + 47,48,49,52,56,57,59,63,64,65,66,67,70,74,75,76,79,82,83,84,87,89,92,93, + 94,97,100,103,104,106,108,110,111,113,117,118,119,120,121,124,128,129, + 130,133,136,139,140,142,144,146,147,148,151,154,155,156,159,161,181,185, + 189,191,192,194,195,197,198,199,203,207,209,210,212,213,215,216,217,222, + 224,229,230,231,232,233,234,236,237,238,240,241,242,244,245,246,254,255, + 256,257,259,261,262,265,268,271,276,278,283,284,285,286,287,288,290,291, + 292,293,295,297,298,301,304,308,309,310,312,313,314,316,317,318) + Gamma=Graph(multiedges=False,name='Janko-Kharaghani-Tonchev') + for i,b in ((1,B1),(163,B163)): + for j in map(lambda x: x[0], st.OrbitsDomain(b)): + Gamma.add_edges(map(tuple,G.Orbit(libgap.Set([i,j]), libgap.OnSets))) + Gamma.relabel() + return Gamma diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 718e20f28fd..c9a8e0231e0 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -155,6 +155,7 @@ :meth:`~GenericGraph.is_vertex_transitive` | Return whether the automorphism group of self is transitive within the partition provided :meth:`~GenericGraph.is_isomorphic` | Test for isomorphism between self and other. :meth:`~GenericGraph.canonical_label` | Return the unique graph on `\{0,1,...,n-1\}` ( ``n = self.order()`` ) which 1) is isomorphic to self 2) is invariant in the isomorphism class. + :meth:`~GenericGraph.is_cayley` | Check whether the graph is a Cayley graph. **Graph properties:** @@ -7026,17 +7027,21 @@ def traveling_salesman_problem(self, use_edge_labels = False, solver = None, con else: edges = [(u,v,self.edge_label(u,v)), (v,u,self.edge_label(v,u))] - answer = self.subgraph(edges = edges) + answer = self.subgraph(edges = edges, immutable = False) answer.set_pos(self.get_pos()) answer.name("TSP from "+self.name()) + if self.is_immutable(): + answer = answer.copy(immutable = True) return answer else: if self.has_multiple_edges() and len(self.edge_label(u,v)) > 1: edges = self.edges() edges.sort(key=weight) - answer = self.subgraph(edges = edges[:2]) + answer = self.subgraph(edges = edges[:2], immutable = False) answer.set_pos(self.get_pos()) answer.name("TSP from "+self.name()) + if self.is_immutable(): + answer = answer.copy(immutable = True) return answer raise EmptySetError("The given graph is not Hamiltonian") @@ -7215,9 +7220,11 @@ def traveling_salesman_problem(self, use_edge_labels = False, solver = None, con raise EmptySetError("The given graph is not Hamiltonian") # We can now return the TSP ! - answer = self.subgraph(edges = h.edges()) + answer = self.subgraph(edges = h.edges(), immutable = False) answer.set_pos(self.get_pos()) answer.name("TSP from "+g.name()) + if self.is_immutable(): + answer = answer.copy(immutable = True) return answer ################################################# @@ -12548,7 +12555,7 @@ def is_clique(self, vertices=None, directed_clique=False): False """ if directed_clique and self._directed: - subgraph=self.subgraph(vertices) + subgraph=self.subgraph(vertices, immutable = False) subgraph.allow_loops(False) subgraph.allow_multiple_edges(False) n=subgraph.order() @@ -20789,6 +20796,158 @@ def canonical_label(self, partition=None, certify=False, verbosity=0, else: return H + def is_cayley(self, return_group = False, mapping = False, + generators = False, allow_disconnected = False): + r""" + Check whether the graph is a Cayley graph. + + If none of the parameters are ``True``, return a boolean indicating + whether the graph is a Cayley graph. Otherwise, return a tuple + containing said boolean and the requested data. If the graph is not + a Cayley graph, each of the data will be ``None``. + + .. NOTE:: + + For this routine to work on all graphs, the optional packages + ``gap_packages`` and ``database_gap`` need to be installed: to do + so, it is enough to run ``sage -i gap_packages database_gap``. + + INPUT: + + - ``return_group`` (boolean; ``False``) -- If True, return a group for + which the graph is a Cayley graph. + + - ``mapping`` (boolean; ``False``) -- If True, return a mapping from + vertices to group elements. + + - ``generators`` (boolean; ``False``) -- If True, return the generating + set of the Cayley graph. + + - ``allow_disconnected`` (boolean; ``False``) -- If True, disconnected + graphs are considered Cayley if they can be obtained from the Cayley + construction with a generating set that does not generate the group. + + ALGORITHM: + + For connected graphs, find a regular subgroup of the automorphism + group. For disconnected graphs, check that the graph is + vertex-transitive and perform the check on one of its connected + components. If a simple graph has density over 1/2, perform the check + on its complement as its disconnectedness may increase performance. + + EXAMPLES: + + A Petersen Graph is not a Cayley graph:: + + sage: g = graphs.PetersenGraph() + sage: g.is_cayley() + False + + A Cayley digraph is a Cayley graph:: + + sage: C7 = groups.permutation.Cyclic(7) + sage: S = [(1,2,3,4,5,6,7), (1,3,5,7,2,4,6), (1,5,2,6,3,7,4)] + sage: d = C7.cayley_graph(generators=S) + sage: d.is_cayley() + True + + Graphs with loops and multiedges will have identity and repeated + elements, respectively, among the generators:: + + sage: g = Graph(graphs.PaleyGraph(9), loops=True, multiedges=True) + sage: g.add_edges([(u, u) for u in g]) + sage: g.add_edges([(u, u+1) for u in g]) + sage: _, S = g.is_cayley(generators=True) + sage: S # random + [(), + (0,2,1)(a,a + 2,a + 1)(2*a,2*a + 2,2*a + 1), + (0,2,1)(a,a + 2,a + 1)(2*a,2*a + 2,2*a + 1), + (0,1,2)(a,a + 1,a + 2)(2*a,2*a + 1,2*a + 2), + (0,1,2)(a,a + 1,a + 2)(2*a,2*a + 1,2*a + 2), + (0,2*a + 2,a + 1)(1,2*a,a + 2)(2,2*a + 1,a), + (0,a + 1,2*a + 2)(1,a + 2,2*a)(2,a,2*a + 1)] + + TESTS: + + Cayley graphs can be reconstructed from the group and generating set:: + + sage: g = graphs.PaleyGraph(9) + sage: _, G, S = g.is_cayley(return_group=True, generators=True) + sage: Graph(G.cayley_graph(generators=S)).is_isomorphic(g) + True + + A disconnected graphs may also be a Cayley graph:: + + sage: g = graphs.PaleyGraph(9) + sage: h = g.disjoint_union(g) + sage: h = h.disjoint_union(h) + sage: h = h.disjoint_union(g) + sage: _, G, d, S = h.is_cayley(return_group=True, mapping=True, generators=True, allow_disconnected=True) + sage: all(set(d[u] for u in h.neighbors(v)) == set(d[v]*x for x in S) for v in h) + True + + The method also works efficiently with dense simple graphs:: + + sage: graphs.CompleteBipartiteGraph(50, 50).is_cayley() + True + + """ + compute_map = mapping or generators + certificate = return_group or compute_map + c, G, map, genset = False, None, None, None + if not self.is_connected(): + if allow_disconnected and self.is_vertex_transitive(): + C = self.connected_components_subgraphs() + if certificate: + c, CG = C[0].is_cayley(return_group = True) + if c: + from sage.groups.perm_gps.permgroup import PermutationGroup + I = [C[0].is_isomorphic(g, certify=True)[1] for g in C] + # gens generate the direct product of CG and a cyclic group + gens = [sum([[tuple([M[x] for x in p]) + for p in h.cycle_tuples()] for M in I], []) + for h in CG.gens()] + \ + [[tuple([M[v] for M in I]) + for v in C[0].vertices()]] + G = PermutationGroup(gens, domain = self.vertices()) + else: + c = C[0].is_cayley(return_group = False) + elif not self.allows_loops() and not self.allows_multiple_edges() and \ + self.density() > Rational(1)/Rational(2): + if certificate: + c, G = self.complement().is_cayley(return_group = True, + allow_disconnected = True) + else: + c = self.complement().is_cayley(return_group = False, + allow_disconnected = True) + else: + A = self.automorphism_group() + if certificate: + G = A.has_regular_subgroup(return_group = True) + c = G is not None + else: + c = A.has_regular_subgroup(return_group = False) + if c and compute_map: + v = next(self.vertex_iterator()) + map = {(f**-1)(v): f for f in G} + if generators: + # self.(out_)neighbors ignores multiedges, + # so we use edge_iterator instead + adj = [y if v == x else x + for x, y, z in self.edge_iterator(v)] + genset = [map[u] for u in adj] + if certificate: + out = [c] + if return_group: + out.append(G) + if mapping: + out.append(map) + if generators: + out.append(genset) + return tuple(out) + else: + return c + import types import sage.graphs.distances_all_pairs diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index bbe712c65c0..cc842af9096 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -3648,7 +3648,7 @@ def chromatic_symmetric_function(self, R=None): sage: s(XG) 30*s[1, 1, 1, 1, 1] + 10*s[2, 1, 1, 1] + 10*s[2, 2, 1] - Not all graphs have a postive Schur expansion:: + Not all graphs have a positive Schur expansion:: sage: G = graphs.ClawGraph() sage: XG = G.chromatic_symmetric_function(); XG diff --git a/src/sage/graphs/graph_generators.py b/src/sage/graphs/graph_generators.py index 3319716c742..63672b7b81f 100644 --- a/src/sage/graphs/graph_generators.py +++ b/src/sage/graphs/graph_generators.py @@ -128,6 +128,7 @@ def __append_to_doc(methods): "HoltGraph", "HortonGraph", "JankoKharaghaniGraph", + "JankoKharaghaniTonchevGraph", "KittellGraph", "KrackhardtKiteGraph", "Klein3RegularGraph", @@ -1924,6 +1925,7 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None HoltGraph = staticmethod(sage.graphs.generators.smallgraphs.HoltGraph) HortonGraph = staticmethod(sage.graphs.generators.smallgraphs.HortonGraph) JankoKharaghaniGraph = staticmethod(sage.graphs.generators.smallgraphs.JankoKharaghaniGraph) + JankoKharaghaniTonchevGraph = staticmethod(sage.graphs.generators.smallgraphs.JankoKharaghaniTonchevGraph) KittellGraph = staticmethod(sage.graphs.generators.smallgraphs.KittellGraph) KrackhardtKiteGraph = staticmethod(sage.graphs.generators.smallgraphs.KrackhardtKiteGraph) Klein3RegularGraph = staticmethod(sage.graphs.generators.smallgraphs.Klein3RegularGraph) diff --git a/src/sage/graphs/strongly_regular_db.pyx b/src/sage/graphs/strongly_regular_db.pyx index c7c9dc9c616..2c1a24436cd 100644 --- a/src/sage/graphs/strongly_regular_db.pyx +++ b/src/sage/graphs/strongly_regular_db.pyx @@ -38,7 +38,7 @@ from sage.combinat.designs.bibd import balanced_incomplete_block_design from sage.graphs.graph import Graph from libc.math cimport sqrt, floor from sage.matrix.constructor import Matrix -from sage.rings.finite_rings.constructor import FiniteField as GF +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.coding.linear_code import LinearCode from sage.rings.sum_of_squares cimport two_squares_c from libc.stdint cimport uint_fast32_t @@ -1135,7 +1135,9 @@ def SRG_from_RSHCD(v,k,l,mu, existence=False,check=True): guys), you may want to disable it whenever you want speed. Set to ``True`` by default. - EXAMPLES:: + EXAMPLES: + + some graphs :: sage: from sage.graphs.strongly_regular_db import SRG_from_RSHCD sage: SRG_from_RSHCD(784, 0, 14, 38, existence=True) @@ -1145,6 +1147,16 @@ def SRG_from_RSHCD(v,k,l,mu, existence=False,check=True): sage: SRG_from_RSHCD(144, 65, 28, 30) Graph on 144 vertices + an example with vertex-transitive automorphism group, found during the + implementation of the case `v=324` :: + + sage: G=SRG_from_RSHCD(324,152,70,72) # long time + sage: a=G.automorphism_group() # long time + sage: a.order() # long time + 2592 + sage: len(a.orbits()) # long time + 1 + TESTS:: sage: SRG_from_RSHCD(784, 0, 14, 38) diff --git a/src/sage/groups/affine_gps/affine_group.py b/src/sage/groups/affine_gps/affine_group.py index 61bf372f931..419c73fbf8e 100644 --- a/src/sage/groups/affine_gps/affine_group.py +++ b/src/sage/groups/affine_gps/affine_group.py @@ -173,7 +173,7 @@ def __classcall__(cls, *args, **kwds): degree, ring = args from sage.rings.integer import is_Integer if is_Integer(ring): - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField var = kwds.get('var', 'a') ring = FiniteField(ring, var) return super(AffineGroup, cls).__classcall__(cls, degree, ring) diff --git a/src/sage/groups/matrix_gps/finitely_generated.py b/src/sage/groups/matrix_gps/finitely_generated.py index 36fb41a18a0..305b57052bc 100644 --- a/src/sage/groups/matrix_gps/finitely_generated.py +++ b/src/sage/groups/matrix_gps/finitely_generated.py @@ -63,7 +63,7 @@ from sage.rings.all import ZZ from sage.rings.integer import is_Integer from sage.rings.ring import is_Ring -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.interfaces.gap import gap from sage.matrix.matrix import is_Matrix from sage.matrix.matrix_space import MatrixSpace, is_MatrixSpace @@ -194,7 +194,7 @@ def QuaternionMatrixGroupGF3(): sage: QP.is_isomorphic(H) False """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.matrix.matrix_space import MatrixSpace MS = MatrixSpace(FiniteField(3), 2) aye = MS([1,1,1,2]) diff --git a/src/sage/groups/matrix_gps/matrix_group.py b/src/sage/groups/matrix_gps/matrix_group.py index 4195fa41e8a..698a58790d6 100644 --- a/src/sage/groups/matrix_gps/matrix_group.py +++ b/src/sage/groups/matrix_gps/matrix_group.py @@ -51,7 +51,7 @@ from sage.rings.all import ZZ from sage.rings.integer import is_Integer from sage.rings.ring import is_Ring -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.interfaces.gap import gap from sage.matrix.matrix import is_Matrix from sage.matrix.matrix_space import MatrixSpace, is_MatrixSpace diff --git a/src/sage/groups/matrix_gps/morphism.py b/src/sage/groups/matrix_gps/morphism.py index 2d4a0e67d47..2e2c47a805e 100644 --- a/src/sage/groups/matrix_gps/morphism.py +++ b/src/sage/groups/matrix_gps/morphism.py @@ -224,17 +224,17 @@ def pushforward(self, J, *args,**kwds): INPUT: - ``J`` -- a subgroup or an element of the domain of ``self``. + ``J`` -- a subgroup or an element of the domain of ``self`` OUTPUT: - The image of ``J`` under ``self`` + The image of ``J`` under ``self``. - NOTE: + .. NOTE:: - ``pushforward`` is the method that is used when a map is called on - anything that is not an element of its domain. For historical reasons, - we keep the alias ``image()`` for this method. + ``pushforward`` is the method that is used when a map is called + on anything that is not an element of its domain. For historical + reasons, we keep the alias ``image()`` for this method. EXAMPLES:: @@ -254,9 +254,9 @@ def pushforward(self, J, *args,**kwds): [0 1] ) - The following tests against trac ticket #10659:: + The following tests against :trac:`10659`:: - sage: phi(H) # indirect doctestest + sage: phi(H) # indirect doctest Matrix group over Finite Field of size 7 with 1 generators ( [4 0] [0 1] @@ -269,16 +269,17 @@ def pushforward(self, J, *args,**kwds): from sage.groups.matrix_gps.all import MatrixGroup img_gens = [x.matrix(F) for x in phi.Image(gapJ).GeneratorsOfGroup()] return MatrixGroup(img_gens) - return phi.Image(gapJ).matrix(F) + C = self.codomain() + return C(phi.Image(gapJ).matrix(F)) image = pushforward - def _call_( self, g ): + def _call_(self, g): """ Call syntax for morphisms. - Some python code for wrapping GAP's Images function for a - matrix group G. Returns an error if g is not in G. + Some python code for wrapping GAP's ``Images`` function for a + matrix group ``G``. Returns an error if ``g`` is not in ``G``. EXAMPLES:: @@ -304,10 +305,10 @@ def _call_( self, g ): [1 1] [0 1] - TEST: + TESTS: The following tests that the call method was successfully - improved in trac ticket #10659:: + improved in :trac:`10659`:: sage: O = WeylGroup(['D',6]) sage: r = prod(O.gens()) @@ -347,9 +348,19 @@ def _call_( self, g ): [0 0 0 0 1 0] [ 0 0 0 0 1 0] [1 0 0 0 0 0], [-1 0 0 0 0 0] ) + + We check that :trac:`19780` is fixed:: + + sage: G = groups.matrix.SO(3, 3) + sage: H = groups.matrix.GL(3, 3) + sage: phi = G.hom([H(x) for x in G.gens()]) + sage: phi(G.one()).parent() + General Linear Group of degree 3 over Finite Field of size 3 """ phi = self.gap() G = self.domain() - F = G.base_ring() + C = self.codomain() + F = C.base_ring() h = g.gap() - return phi.Image(h).matrix(F) + return C(phi.Image(h).matrix(F)) + diff --git a/src/sage/groups/matrix_gps/named_group.py b/src/sage/groups/matrix_gps/named_group.py index c2b1b6189b4..d27d97636ed 100644 --- a/src/sage/groups/matrix_gps/named_group.py +++ b/src/sage/groups/matrix_gps/named_group.py @@ -110,7 +110,7 @@ def normalize_args_vectorspace(*args, **kwds): from sage.rings.integer import is_Integer try: ring = ZZ(ring) - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField var = kwds.get('var', 'a') ring = FiniteField(ring, var) except (ValueError, TypeError): diff --git a/src/sage/groups/perm_gps/partn_ref/refinement_binary.pyx b/src/sage/groups/perm_gps/partn_ref/refinement_binary.pyx index 726c7909c14..cd2a4724dd0 100644 --- a/src/sage/groups/perm_gps/partn_ref/refinement_binary.pyx +++ b/src/sage/groups/perm_gps/partn_ref/refinement_binary.pyx @@ -1052,7 +1052,7 @@ def random_tests(num=50, n_max=50, k_max=6, nwords_max=200, perms_per_code=10, d from sage.misc.prandom import random, randint from sage.combinat.permutation import Permutations from sage.matrix.constructor import random_matrix, matrix - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF cdef int h, i, j, n, k, num_tests = 0, num_codes = 0 cdef LinearBinaryCodeStruct B, C cdef NonlinearBinaryCodeStruct B_n, C_n diff --git a/src/sage/groups/perm_gps/partn_ref/refinement_matrices.pyx b/src/sage/groups/perm_gps/partn_ref/refinement_matrices.pyx index 1776a179b0d..296150b403e 100644 --- a/src/sage/groups/perm_gps/partn_ref/refinement_matrices.pyx +++ b/src/sage/groups/perm_gps/partn_ref/refinement_matrices.pyx @@ -317,7 +317,7 @@ def random_tests(n=10, nrows_max=50, ncols_max=50, nsymbols_max=10, perms_per_ma from sage.misc.prandom import random, randint from sage.combinat.permutation import Permutations from sage.matrix.constructor import random_matrix, matrix - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.arith.all import next_prime cdef int h, i, j, nrows, k, num_tests = 0, num_matrices = 0 cdef MatrixStruct M, N diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index 336f0d400a0..bd860c917de 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -27,6 +27,13 @@ Mathematical Folk Humor." Notices Amer. Math. Soc. 52, 24-34, 2005. +Index of methods +---------------- + +Here are the method of a :func:`PermutationGroup` + +{METHODS_OF_PermutationGroup_generic} + AUTHORS: - David Joyner (2005-10-14): first version @@ -930,17 +937,19 @@ def identity(self): return self._element_class()([], self, check=True) def exponent(self): - """ - Computes the exponent of the group. The exponent `e` of a - group `G` is the LCM of the orders of its elements, that - is, `e` is the smallest integer such that `g^e=1` - for all `g \in G`. + r""" + Computes the exponent of the group. + + The exponent `e` of a group `G` is the LCM of the orders of its + elements, that is, `e` is the smallest integer such that `g^e=1` for all + `g \in G`. EXAMPLES:: sage: G = AlternatingGroup(4) sage: G.exponent() 6 + """ return Integer(self._gap_().Exponent()) @@ -1744,9 +1753,10 @@ def socle(self): def frattini_subgroup(self): r""" - Returns the Frattini subgroup of ``self``. The Frattini - subgroup of a group $G$ is the intersection of all - maximal subgroups of $G$. + Returns the Frattini subgroup of ``self``. + + The Frattini subgroup of a group $G$ is the intersection of all maximal + subgroups of `G`. EXAMPLES:: @@ -1762,9 +1772,10 @@ def frattini_subgroup(self): def fitting_subgroup(self): r""" - Returns the Fitting subgroup of ``self``. The Fitting - subgroup of a group $G$ is the largest nilpotent normal - subgroup of $G$. + Returns the Fitting subgroup of ``self``. + + The Fitting subgroup of a group $G$ is the largest nilpotent normal + subgroup of `G`. EXAMPLES:: @@ -2554,6 +2565,7 @@ def cohomology(self, n, p = 0): r""" Computes the group cohomology `H^n(G, F)`, where `F = \ZZ` if `p=0` and `F = \ZZ / p \ZZ` if `p > 0` is a prime. + Wraps HAP's ``GroupHomology`` function, written by Graham Ellis. REQUIRES: GAP package HAP (in gap_packages-\*.spkg). @@ -2602,7 +2614,9 @@ def cohomology_part(self, n, p = 0): """ Computes the p-part of the group cohomology `H^n(G, F)`, where `F = \ZZ` if `p=0` and `F = \ZZ / p \ZZ` if - `p > 0` is a prime. Wraps HAP's Homology function, written + `p > 0` is a prime. + + Wraps HAP's Homology function, written by Graham Ellis, applied to the `p`-Sylow subgroup of `G`. @@ -2713,7 +2727,9 @@ def character_table(self): r""" Returns the matrix of values of the irreducible characters of a permutation group `G` at the conjugacy classes of - `G`. The columns represent the conjugacy classes of + `G`. + + The columns represent the conjugacy classes of `G` and the rows represent the different irreducible characters in the ordering given by GAP. @@ -2846,7 +2862,9 @@ def character(self, values): def conjugacy_classes_representatives(self): """ Returns a complete list of representatives of conjugacy classes in - a permutation group `G`. The ordering is that given by GAP. + a permutation group `G`. + + The ordering is that given by GAP. EXAMPLES:: @@ -2878,8 +2896,9 @@ def conjugacy_classes_representatives(self): def conjugacy_classes_subgroups(self): """ Returns a complete list of representatives of conjugacy classes of - subgroups in a permutation group `G`. The ordering is that given by - GAP. + subgroups in a permutation group `G`. + + The ordering is that given by GAP. EXAMPLES:: @@ -2959,6 +2978,82 @@ def subgroups(self): all_sg.append(self.subgroup(gap_group=h)) return all_sg + @cached_method + def _regular_subgroup_gap(self): + r""" + Return a conjugacy class of regular subgroups, if there is one, as a + GAP element. + + This allows finding such a group without constructing it in Sage. + The result is cached, so constructing the obtained subgroup later is + possible without recomputing it. + + EXAMPLES: + + The symmetric group on 4 elements has a regular subgroup:: + + sage: S4 = groups.permutation.Symmetric(4) + sage: S4._regular_subgroup_gap() # random + ConjugacyClassSubgroups(SymmetricGroup( [ 1 .. 4 ] ),Group( + [ (1,4)(2,3), (1,3)(2,4) ] )) + + """ + gap = self._gap_().parent() + C = gap.new(""" + First(ConjugacyClassesSubgroups(%s), + x -> IsRegular(Representative(x), [1..%d])) + """ % (self._gap_().name(), self.degree())) + # prevent caching GAP fails + if gap.eval('%s = fail' % C.name()) == 'true': + return None + return C + + @cached_method + def has_regular_subgroup(self, return_group = False): + r""" + Return whether the group contains a regular subgroup. + + INPUT: + + - ``return_group`` (boolean) -- If ``return_group = True``, a regular + subgroup is returned if there is one, and ``None`` if there isn't. + When ``return_group = False`` (default), only a boolean indicating + whether such a group exists is returned instead. + + EXAMPLES: + + The symmetric group on 4 elements has a regular subgroup:: + + sage: S4 = groups.permutation.Symmetric(4) + sage: S4.has_regular_subgroup() + True + sage: S4.has_regular_subgroup(return_group = True) # random + Subgroup of (Symmetric group of order 4! as a permutation group) generated by [(1,3)(2,4), (1,4)(2,3)] + + But the automorphism group of Petersen's graph does not:: + + sage: G = graphs.PetersenGraph().automorphism_group() + sage: G.has_regular_subgroup() + False + + """ + b = False + G = None + if self.order() % self.degree() == 0: + if self.order() == len(self.domain()): + b = self.is_transitive() + if b: + G = self + else: + C = self._regular_subgroup_gap() + b = (C is not None) + if b and return_group: + G = self.subgroup(gap_group=C.Representative()) + if return_group: + return G + else: + return b + def blocks_all(self, representatives = True): r""" Returns the list of block systems of imprimitivity. @@ -3632,10 +3727,10 @@ def fixed_points(self): return [i for i in self.domain() if i not in non_fixed_points] def is_transitive(self, domain=None): - """ + r""" Returns ``True`` if ``self`` acts transitively on ``domain``. - A group $G$ acts transitively on set $S$ if for all $x,y\in S$ - there is some $g\in G$ such that $x^g=y$. + A group $G$ acts transitively on set $S$ if for all `x,y\in S` + there is some `g\in G` such that `x^g=y`. EXAMPLES:: @@ -3939,7 +4034,7 @@ def molien_series(self): pi = self._gap_().NaturalCharacter() # because NaturalCharacter forgets about fixed points : pi += self._gap_().TrivialCharacter() * len(self.fixed_points()) - + M = pi.MolienSeries() R = QQ['x'] @@ -3974,14 +4069,14 @@ def normal_subgroups(self): return [self.subgroup(gap_group=group) for group in NS] def poincare_series(self, p=2, n=10): - """ - Returns the Poincare series of `G \mod p` (`p \geq 2` must be a - prime), for `n` large. In other words, if you input a finite - group `G`, a prime `p`, and a positive integer `n`, it returns a - quotient of polynomials `f(x) = P(x) / Q(x)` whose coefficient of - `x^k` equals the rank of the vector space - `H_k(G, \ZZ / p \ZZ)`, for all `k` in the - range `1 \leq k \leq n`. + r""" + Returns the Poincare series of `G \mod p` (`p \geq 2` must be a prime), + for `n` large. + + In other words, if you input a finite group `G`, a prime `p`, and a + positive integer `n`, it returns a quotient of polynomials `f(x) = P(x) + / Q(x)` whose coefficient of `x^k` equals the rank of the vector space + `H_k(G, \ZZ / p \ZZ)`, for all `k` in the range `1 \leq k \leq n`. REQUIRES: GAP package HAP (in gap_packages-\*.spkg). @@ -3997,6 +4092,7 @@ def poincare_series(self, p=2, n=10): AUTHORS: - David Joyner and Graham Ellis + """ if not is_package_installed('gap_packages'): raise RuntimeError("You must install the optional gap_packages package.") @@ -4017,7 +4113,9 @@ def sylow_subgroup(self, p): """ Returns a Sylow `p`-subgroup of the finite group `G`, where `p` is a prime. This is a `p`-subgroup of `G` whose index in `G` is coprime to - `p`. Wraps the GAP function ``SylowSubgroup``. + `p`. + + Wraps the GAP function ``SylowSubgroup``. EXAMPLES:: @@ -4300,3 +4398,5 @@ def is_normal(self, other=None): other = self.ambient_group() return PermutationGroup_generic.is_normal(self, other) +from sage.misc.rest_index_of_methods import gen_rest_table_index +__doc__ = __doc__.format(METHODS_OF_PermutationGroup_generic=gen_rest_table_index(PermutationGroup_generic)) diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index 5b5eb7f93d0..dc06826b270 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -85,7 +85,7 @@ from sage.rings.all import Integer from sage.interfaces.all import gap -from sage.rings.finite_rings.constructor import FiniteField as GF +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.arith.all import factor, valuation from sage.groups.abelian_gps.abelian_group import AbelianGroup from sage.misc.functional import is_even diff --git a/src/sage/interfaces/all.py b/src/sage/interfaces/all.py index 90edec342c9..e751feff750 100644 --- a/src/sage/interfaces/all.py +++ b/src/sage/interfaces/all.py @@ -3,45 +3,71 @@ from frobby import frobby from four_ti_2 import four_ti_2 -from axiom import Axiom, axiom, axiom_console -from fricas import FriCAS, fricas, fricas_console +from axiom import Axiom, axiom +from fricas import FriCAS, fricas -from gap import gap, gap_reset_workspace, gap_console, set_gap_memory_pool_size, Gap -from gap3 import gap3, gap3_console, gap3_version, Gap3 +from gap import gap, gap_reset_workspace, set_gap_memory_pool_size, Gap +from gap3 import gap3, gap3_version, Gap3 lazy_import('sage.interfaces.genus2reduction', ['genus2reduction', 'Genus2reduction']) from gfan import gfan, Gfan -from giac import giac, giac_console, Giac -from gp import gp, gp_console, gp_version, Gp -from gnuplot import gnuplot, gnuplot_console -from kash import kash, kash_console, kash_version, Kash -from lisp import lisp, lisp_console, Lisp -from magma import magma, magma_console, magma_version, Magma +from giac import giac, Giac +from gp import gp, gp_version, Gp +from gnuplot import gnuplot +from kash import kash, kash_version, Kash +from lisp import lisp, Lisp +from magma import magma, magma_version, Magma from magma_free import magma_free -from macaulay2 import macaulay2, macaulay2_console, Macaulay2 -from maple import maple, maple_console, Maple -from maxima_abstract import maxima_console +from macaulay2 import macaulay2, Macaulay2 +from maple import maple, Maple from maxima import maxima, Maxima # import problems #from maxima_lib import maxima_lib -from mathematica import mathematica, mathematica_console, Mathematica -from matlab import matlab, matlab_console, matlab_version, Matlab -from mupad import mupad, mupad_console, Mupad # NOT functional yet -from mwrank import mwrank, Mwrank, mwrank_console -from octave import octave, octave_console, octave_version, Octave -from qepcad import qepcad, qepcad_console, qepcad_version, qepcad_formula +from mathematica import mathematica, Mathematica +from matlab import matlab, matlab_version, Matlab +from mupad import mupad, Mupad # NOT functional yet +from mwrank import mwrank, Mwrank +from octave import octave, octave_version, Octave +from qepcad import qepcad, qepcad_version, qepcad_formula from qsieve import qsieve -from singular import singular, singular_console, singular_version, Singular -from sage0 import sage0 as sage0, sage0_console, sage0_version, Sage +from singular import singular, singular_version, Singular +from sage0 import sage0 as sage0, sage0_version, Sage from scilab import scilab from tachyon import tachyon_rt from psage import PSage from ecm import ECM, ecm from povray import povray -from lie import lie, lie_console, LiE -from r import r, r_console, R, r_version +from lie import lie, LiE +from r import r, R, r_version from read_data import read_data interfaces = ['gap', 'gap3', 'giac', 'gp', 'mathematica', 'gnuplot', \ 'kash', 'magma', 'macaulay2', 'maple', 'maxima', \ 'mathematica', 'mwrank', 'octave', 'r', \ 'singular', 'sage0', 'sage'] + + +from sage.repl.rich_output.display_manager import get_display_manager +if get_display_manager().is_in_terminal(): + from axiom import axiom_console + from fricas import fricas_console + from gap import gap_console + from gap3 import gap3_console + from giac import giac_console + from gp import gp_console + from gnuplot import gnuplot_console + from kash import kash_console + from lisp import lisp_console + from magma import magma_console + from macaulay2 import macaulay2_console + from maple import maple_console + from maxima_abstract import maxima_console + from mathematica import mathematica_console + from matlab import matlab_console + from mupad import mupad_console + from mwrank import mwrank_console + from octave import octave_console + from qepcad import qepcad_console + from singular import singular_console + from sage0 import sage0_console + from lie import lie_console + from r import r_console diff --git a/src/sage/interfaces/axiom.py b/src/sage/interfaces/axiom.py index 1ac2013d081..f7efc22bc02 100644 --- a/src/sage/interfaces/axiom.py +++ b/src/sage/interfaces/axiom.py @@ -1020,5 +1020,8 @@ def axiom_console(): ----------------------------------------------------------------------------- """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%axiom magics instead.') os.system('axiom -nox') diff --git a/src/sage/interfaces/fricas.py b/src/sage/interfaces/fricas.py index 5b0520874ae..56e4fc4a427 100644 --- a/src/sage/interfaces/fricas.py +++ b/src/sage/interfaces/fricas.py @@ -336,6 +336,9 @@ def fricas_console(): Issue )quit to leave AXIOM and return to shell. ----------------------------------------------------------------------------- """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%fricas magics instead.') os.system('fricas -nox') def __doctest_cleanup(): diff --git a/src/sage/interfaces/gap.py b/src/sage/interfaces/gap.py index 0f1f942c3fe..e21931fea79 100644 --- a/src/sage/interfaces/gap.py +++ b/src/sage/interfaces/gap.py @@ -1884,7 +1884,10 @@ def gap_console(): True sage: 'sorry' not in gap_startup True - """ + """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%gap magics instead.') cmd, _ = gap_command(use_workspace_cache=False) cmd += ' ' + os.path.join(SAGE_EXTCODE,'gap','console.g') os.system(cmd) diff --git a/src/sage/interfaces/gap3.py b/src/sage/interfaces/gap3.py index 309b01fd44a..092420a6add 100644 --- a/src/sage/interfaces/gap3.py +++ b/src/sage/interfaces/gap3.py @@ -879,6 +879,9 @@ def gap3_console(): For help enter: ? gap> """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%gap3 magics instead.') os.system(gap3_cmd) def gap3_version(): diff --git a/src/sage/interfaces/giac.py b/src/sage/interfaces/giac.py index e403c1c622e..e1a46115865 100644 --- a/src/sage/interfaces/giac.py +++ b/src/sage/interfaces/giac.py @@ -1134,7 +1134,10 @@ def giac_console(): ------------------------------------------------- Press CTRL and D simultaneously to finish session Type ?commandname for help - """ + """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%giac magics instead.') os.system('giac') diff --git a/src/sage/interfaces/gnuplot.py b/src/sage/interfaces/gnuplot.py index 981b7f98571..bf669fd380d 100644 --- a/src/sage/interfaces/gnuplot.py +++ b/src/sage/interfaces/gnuplot.py @@ -189,6 +189,9 @@ def console(self): def gnuplot_console(): + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%gnuplot magics instead.') os.system('sage-native-execute gnuplot') diff --git a/src/sage/interfaces/gp.py b/src/sage/interfaces/gp.py index b7bec6615ed..dc89173ab35 100644 --- a/src/sage/interfaces/gp.py +++ b/src/sage/interfaces/gp.py @@ -890,7 +890,7 @@ def _sage_(self): sage: gp(I).sage() i sage: gp(I).sage().parent() - Maximal Order in Number Field in i with defining polynomial x^2 + 1 + Number Field in i with defining polynomial x^2 + 1 :: @@ -1096,6 +1096,9 @@ def gp_console(): compiled: Jul 21 2010, gcc-4.6.0 20100705 (experimental) (GCC) (readline v6.0 enabled, extended help enabled) """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%gp magics instead.') os.system('gp') diff --git a/src/sage/interfaces/kash.py b/src/sage/interfaces/kash.py index 14e64ceb321..cb4335a835b 100644 --- a/src/sage/interfaces/kash.py +++ b/src/sage/interfaces/kash.py @@ -697,6 +697,9 @@ def reduce_load_Kash(): def kash_console(): + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%kash magics instead.') os.system("kash3 ") diff --git a/src/sage/interfaces/lie.py b/src/sage/interfaces/lie.py index e9834c86733..314d9070aef 100644 --- a/src/sage/interfaces/lie.py +++ b/src/sage/interfaces/lie.py @@ -931,6 +931,9 @@ def lie_console(): ... """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%lie magics instead.') os.system('bash `which lie`') diff --git a/src/sage/interfaces/lisp.py b/src/sage/interfaces/lisp.py index 5c0a6f70bbc..102b768db03 100644 --- a/src/sage/interfaces/lisp.py +++ b/src/sage/interfaces/lisp.py @@ -561,4 +561,7 @@ def lisp_console(): Type :h for Help. Top level. ... """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%lisp magics instead.') os.system('ecl') diff --git a/src/sage/interfaces/macaulay2.py b/src/sage/interfaces/macaulay2.py index 4844e6243ae..8d086852087 100644 --- a/src/sage/interfaces/macaulay2.py +++ b/src/sage/interfaces/macaulay2.py @@ -1220,6 +1220,9 @@ def macaulay2_console(): ... """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%macaulay2 magics instead.') os.system('M2') diff --git a/src/sage/interfaces/magma.py b/src/sage/interfaces/magma.py index 0832e97d14d..7b3e6a6caaf 100644 --- a/src/sage/interfaces/magma.py +++ b/src/sage/interfaces/magma.py @@ -2733,6 +2733,9 @@ def magma_console(): > Total time: 2.820 seconds, Total memory usage: 3.95MB """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%magma magics instead.') console('sage-native-execute magma') def magma_version(): diff --git a/src/sage/interfaces/maple.py b/src/sage/interfaces/maple.py index c1b22dbffb8..43aea2118d9 100644 --- a/src/sage/interfaces/maple.py +++ b/src/sage/interfaces/maple.py @@ -1169,6 +1169,9 @@ def maple_console(): | Type ? for help. > """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%maple magics instead.') os.system('maple') diff --git a/src/sage/interfaces/mathematica.py b/src/sage/interfaces/mathematica.py index b8d2a236801..ddb49cf5432 100644 --- a/src/sage/interfaces/mathematica.py +++ b/src/sage/interfaces/mathematica.py @@ -973,6 +973,9 @@ def reduce_load(X): def mathematica_console(readline=True): + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%mathematica magics instead.') if not readline: os.system('math') return diff --git a/src/sage/interfaces/matlab.py b/src/sage/interfaces/matlab.py index 45db1d78e60..5a97d3e62c0 100644 --- a/src/sage/interfaces/matlab.py +++ b/src/sage/interfaces/matlab.py @@ -387,6 +387,9 @@ def matlab_console(): matlab, like Sage, remembers its history from one session to another. """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%matlab magics instead.') os.system('matlab -nodisplay') diff --git a/src/sage/interfaces/maxima_abstract.py b/src/sage/interfaces/maxima_abstract.py index 14ac6b522bd..f1bf3b9602b 100644 --- a/src/sage/interfaces/maxima_abstract.py +++ b/src/sage/interfaces/maxima_abstract.py @@ -2366,4 +2366,7 @@ def maxima_console(): Maxima 5.34.1 http://maxima.sourceforge.net ... """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%maxima magics instead.') os.system('maxima') diff --git a/src/sage/interfaces/maxima_lib.py b/src/sage/interfaces/maxima_lib.py index 2793e21ee43..57ba0bfd34f 100644 --- a/src/sage/interfaces/maxima_lib.py +++ b/src/sage/interfaces/maxima_lib.py @@ -1565,7 +1565,7 @@ def sr_to_max(expr): max_op_dict[op_max]=op return EclObject(([sage_op_dict[op]], [sr_to_max(o) for o in expr.operands()])) - elif expr.is_symbol() or expr.is_constant(): + elif expr.is_symbol() or expr._is_registered_constant_(): if not expr in sage_sym_dict: sym_max=maxima(expr).ecl() sage_sym_dict[expr]=sym_max diff --git a/src/sage/interfaces/mupad.py b/src/sage/interfaces/mupad.py index 6ec1cb065c6..4f4cfcc8fad 100644 --- a/src/sage/interfaces/mupad.py +++ b/src/sage/interfaces/mupad.py @@ -680,6 +680,9 @@ def mupad_console(): *----* Licensed to: ... """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%mupad magics instead.') os.system('mupkern') diff --git a/src/sage/interfaces/mwrank.py b/src/sage/interfaces/mwrank.py index 2585e02a8cc..2bb30463164 100644 --- a/src/sage/interfaces/mwrank.py +++ b/src/sage/interfaces/mwrank.py @@ -358,5 +358,8 @@ def mwrank_console(): sage: mwrank_console() # not tested: expects console input Program mwrank: ... """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%mwrank magics instead.') os.system('mwrank') diff --git a/src/sage/interfaces/octave.py b/src/sage/interfaces/octave.py index 862b3fc786f..176cb7e1eac 100644 --- a/src/sage/interfaces/octave.py +++ b/src/sage/interfaces/octave.py @@ -773,6 +773,9 @@ def octave_console(): octave, like Sage, remembers its history from one session to another. """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%octave magics instead.') os.system('octave') diff --git a/src/sage/interfaces/qepcad.py b/src/sage/interfaces/qepcad.py index bde7c745139..183626bf483 100644 --- a/src/sage/interfaces/qepcad.py +++ b/src/sage/interfaces/qepcad.py @@ -1667,6 +1667,9 @@ def qepcad_console(memcells=None): ... Enter an informal description between '[' and ']': """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%qepcat magics instead.') # This will only spawn local processes os.system(_qepcad_cmd(memcells)) diff --git a/src/sage/interfaces/r.py b/src/sage/interfaces/r.py index 38d006b033f..01c1f48354d 100644 --- a/src/sage/interfaces/r.py +++ b/src/sage/interfaces/r.py @@ -2097,6 +2097,9 @@ def r_console(): ISBN 3-900051-07-0 ... """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%r magics instead.') # This will only spawn local processes os.system('R --vanilla') diff --git a/src/sage/interfaces/sage0.py b/src/sage/interfaces/sage0.py index 1462030975e..166d58be19a 100644 --- a/src/sage/interfaces/sage0.py +++ b/src/sage/interfaces/sage0.py @@ -529,6 +529,9 @@ def sage0_console(): ---------------------------------------------------------------------- ... """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%sage0 magics instead.') os.system('sage') def sage0_version(): diff --git a/src/sage/interfaces/singular.py b/src/sage/interfaces/singular.py index 44a231ea2b9..e1893da0ef2 100644 --- a/src/sage/interfaces/singular.py +++ b/src/sage/interfaces/singular.py @@ -2326,6 +2326,9 @@ def singular_console(): by: G.-M. Greuel, G. Pfister, H. Schoenemann \ Nov 2007 FB Mathematik der Universitaet, D-67653 Kaiserslautern \ """ + from sage.repl.rich_output.display_manager import get_display_manager + if not get_display_manager().is_in_terminal(): + raise RuntimeError('Can use the console only in the terminal. Try %%singular magics instead.') os.system('Singular') diff --git a/src/sage/libs/arb/acb.pxd b/src/sage/libs/arb/acb.pxd index c04b39d6d4d..180ed97f2c8 100644 --- a/src/sage/libs/arb/acb.pxd +++ b/src/sage/libs/arb/acb.pxd @@ -158,3 +158,6 @@ cdef extern from "acb.h": void acb_agm1(acb_t m, const acb_t z, long prec) void acb_agm1_cpx(acb_ptr m, const acb_t z, long len, long prec) + + acb_ptr _acb_vec_init(long n) + void _acb_vec_clear(acb_ptr v, long n) diff --git a/src/sage/libs/arb/arith.pyx b/src/sage/libs/arb/arith.pyx new file mode 100644 index 00000000000..8cd25979899 --- /dev/null +++ b/src/sage/libs/arb/arith.pyx @@ -0,0 +1,54 @@ +""" +Arithmetic functions using the arb library +""" + +#***************************************************************************** +# Copyright (C) 2016 Jeroen Demeyer +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from ..flint.types cimport ulong +from ..flint.fmpq cimport fmpq_t, fmpq_init, fmpq_clear, fmpq_get_mpq +from .bernoulli cimport bernoulli_fmpq_ui +from sage.rings.rational cimport Rational + +def bernoulli(n): + """ + Return the ``n``-th Bernoulli number using ``arb``. + + INPUT: + + - ``n`` -- unsigned integer + + OUTPUT: a rational number + + EXAMPLES:: + + sage: from sage.libs.arb.arith import bernoulli + sage: bernoulli(24) + -236364091/2730 + sage: bernoulli(0) + 1 + sage: bernoulli(1) + -1/2 + + TESTS:: + + sage: bernoulli(-1) + Traceback (most recent call last): + ... + OverflowError: can't convert negative value to mp_limb_t + """ + cdef ulong i = n + cdef Rational q = Rational.__new__(Rational) + cdef fmpq_t x + fmpq_init(x) + bernoulli_fmpq_ui(x, i) + fmpq_get_mpq(q.value, x) + fmpq_clear(x) + return q diff --git a/src/sage/libs/arb/bernoulli.pxd b/src/sage/libs/arb/bernoulli.pxd new file mode 100644 index 00000000000..f1d1974a57d --- /dev/null +++ b/src/sage/libs/arb/bernoulli.pxd @@ -0,0 +1,6 @@ +# distutils: libraries = arb + +from ..flint.types cimport fmpq_t, ulong + +cdef extern from "bernoulli.h": + void bernoulli_fmpq_ui(fmpq_t b, ulong n) diff --git a/src/sage/libs/fes.pyx b/src/sage/libs/fes.pyx index 842ae99d680..4dc0e1231c5 100644 --- a/src/sage/libs/fes.pyx +++ b/src/sage/libs/fes.pyx @@ -79,7 +79,7 @@ include 'sage/ext/stdsage.pxi' #sage_calloc(), sage_free() from sage.rings.integer import Integer from sage.rings.infinity import Infinity -from sage.rings.finite_rings.constructor import FiniteField as GF +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.structure.parent cimport Parent from sage.structure.sequence import Sequence diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index a9a39ad224e..2415513a270 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1434,7 +1434,7 @@ cdef class GapElement_FiniteField(GapElement): deg = self.DegreeFFE().sage() char = self.Characteristic().sage() if ring is None: - from sage.rings.finite_rings.constructor import GF + from sage.rings.finite_rings.finite_field_constructor import GF ring = GF(char**deg, name=var) if self.IsOne(): @@ -1678,7 +1678,7 @@ cdef class GapElement_Ring(GapElement): Finite Field in A of size 3^2 """ size = self.Size().sage() - from sage.rings.finite_rings.constructor import GF + from sage.rings.finite_rings.finite_field_constructor import GF return GF(size, name=var) @@ -2602,7 +2602,7 @@ cdef class GapElement_RecordIterator(object): def __next__(self): r""" - The next elemnt in the record. + Return the next element in the record. OUTPUT: diff --git a/src/sage/libs/meataxe.pxd b/src/sage/libs/meataxe.pxd new file mode 100644 index 00000000000..68878f3fa19 --- /dev/null +++ b/src/sage/libs/meataxe.pxd @@ -0,0 +1,148 @@ +#***************************************************************************** +# Copyright (C) 2015 Simon King +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +# +# Import SOME features from meataxe.h +# (most types are not needed, but listed here +# in the comments, for completeness) +# +cdef extern from "meataxe.h": + # general ctype emulations + # ctypedef int size_t # size_t should be a standard type! + # ctypedef unsigned long Ulong + # ctypedef unsigned short Ushort + # ctypedef unsigned char Uchar + ctypedef unsigned char FEL + ctypedef FEL *PTR + + # global constants + cdef extern int FfOrder # Current field order + cdef extern int FfChar # Current characteristic + cdef extern FEL FfGen # Generator + cdef extern int FfNoc # Number of columns for row ops + cdef extern size_t FfCurrentRowSize # The byte size of a single row in memory, + # always a multiple of sizeof(long) + cdef extern size_t FfCurrentRowSizeIo # The number of bytes actually used in a row. + cdef extern char MtxLibDir[250] # Where to search/create multiplication tables + + # we only wrap MeatAxe for small fields (size < 255) + cdef extern FEL mtx_tmult[256][256] + cdef extern FEL mtx_tadd[256][256] + cdef extern FEL mtx_taddinv[256] + cdef extern FEL mtx_tmultinv[256] + cdef extern FEL mtx_tinsert[8][256] + cdef extern FEL mtx_textract[8][256] + cdef extern FEL FF_ONE, FF_ZERO + +######################################### +# function prototypes + ## global parameters + size_t FfRowSize(int noc) + size_t FfTrueRowSize(int noc) # Difference to FfRowSize: Doesn't count padding bytes + int FfSetField(int field) except -1 + int FfSetNoc(int ncols) except -1 + + ## Finite Fields + # FEL FfAdd(FEL a,FEL b) + # FEL FfSub(FEL a, FEL b) + # FEL FfNeg(FEL a) + # FEL FfMul(FEL a, FEL b) + # FEL FfDiv(FEL a, FEL b) + # FEL FfInv(FEL a) + # FEL FfEmbed(FEL a, int subfield) except 255 + # FEL FfRestrict(FEL a, int subfield) except 255 + FEL FfFromInt(int l) + int FfToInt(FEL f) + + ## Rows + void FfMulRow(PTR row, FEL mark) + void FfAddMulRow(PTR dest, PTR src, FEL f) + PTR FfAddRow(PTR dest, PTR src) + PTR FfSubRow(PTR dest, PTR src) + FEL FfExtract(PTR row, int col) + void FfInsert(PTR row, int col, FEL mark) + int FfFindPivot(PTR row, FEL *mark) + FEL FfScalarProduct(PTR a, PTR b) + void FfSwapRows(PTR dest, PTR src) + void FfPermRow(PTR row, long *perm, PTR result) + int FfCmpRows(PTR p1, PTR p2) + + ## multiple rows + PTR FfAlloc(int nor) except NULL + void FfExtractColumn(PTR mat,int nor,int col,PTR result) + int FfStepPtr(PTR *x) # Advance to next row + PTR FfGetPtr(PTR base, int row) # Advance to "row" rows after base + void FfInsert(PTR row, int col, FEL mark) + void FfMapRow(PTR row, PTR matrix, int nor, PTR result) + + ############ + ## Skip: Application, error handling, i/o + + ############ + ## Matrices + ############ + ctypedef struct Matrix_t: + unsigned long Magic #/* Used internally */ + int Field, Nor, Noc #/* Field, #rows, #columns */ + PTR Data #/* Pointer to data area */ + int RowSize # Size (in bytes) of one row + int *PivotTable # Pivot table (if matrix is in echelon form + ## Basic memory operations + Matrix_t *MatAlloc(int field, int nor, int noc) except NULL + int MatFree(Matrix_t *mat) + PTR MatGetPtr(Matrix_t *mat, int row) except NULL + int MatCompare(Matrix_t *a, Matrix_t *b) except -2 + int MatCopyRegion(Matrix_t *dest, int destrow, int destcol, Matrix_t *src, int row1, int col1, int nrows, int ncols) except -1 + Matrix_t *MatCut(Matrix_t *src, int row1, int col1, int nrows, int ncols) except NULL + Matrix_t *MatCutRows(Matrix_t *src, int row1, int nrows) except NULL + Matrix_t *MatDup(Matrix_t *src) except NULL + Matrix_t *MatId(int fl, int nor) except NULL + Matrix_t *MatLoad(char *fn) except NULL + int MatSave(Matrix_t *mat, char *fn) except -1 + + + ## Basic Arithmetic ## general rule: dest is changed, src/mat are unchanged! + Matrix_t *MatTransposed(Matrix_t *src) except NULL + Matrix_t *MatAdd(Matrix_t *dest, Matrix_t *src) except NULL + Matrix_t *MatAddMul(Matrix_t *dest, Matrix_t *src, FEL coeff) except NULL + Matrix_t *MatMul(Matrix_t *dest, Matrix_t *src) except NULL + Matrix_t *MatMulScalar(Matrix_t *dest, FEL coeff) except NULL + Matrix_t *MatPower(Matrix_t *mat, long n) except NULL + int StablePower(Matrix_t *mat, int *pwr, Matrix_t **ker) except -1 + FEL MatTrace(Matrix_t *mat) except 255 + Matrix_t *MatMulStrassen(Matrix_t *dest, Matrix_t *A, Matrix_t *B) except NULL + void StrassenSetCutoff(size_t size) + + ## "Higher" Arithmetic + Matrix_t *MatTensor(Matrix_t *m1, Matrix_t *m2) except NULL + Matrix_t *TensorMap(Matrix_t *vec, Matrix_t *a, Matrix_t *b) except NULL + + int MatClean(Matrix_t *mat, Matrix_t *sub) except -1 + int MatEchelonize(Matrix_t *mat) except -1 + int MatOrder(Matrix_t *mat) except? -1 + long MatNullity(Matrix_t *mat) + Matrix_t *MatInverse(Matrix_t *src) except NULL + Matrix_t *MatNullSpace(Matrix_t *mat) except NULL + + ## Error handling + cdef extern int MTX_ERR_NOMEM, MTX_ERR_GAME_OVER, MTX_ERR_DIV0, MTX_ERR_FILEFMT, MTX_ERR_BADARG + cdef extern int MTX_ERR_RANGE, MTX_ERR_NOTECH, MTX_ERR_NOTSQUARE, MTX_ERR_INCOMPAT + cdef extern int MTX_ERR_BADUSAGE, MTX_ERR_OPTION, MTX_ERR_NARGS, MTX_ERR_NOTMATRIX, MTX_ERR_NOTPERM + ctypedef struct MtxFileInfo_t: + char *Name + char *BaseName + + ctypedef struct MtxErrorRecord_t: + MtxFileInfo_t *FileInfo + int LineNo + char *Text + + ctypedef void MtxErrorHandler_t(MtxErrorRecord_t*) + MtxErrorHandler_t *MtxSetErrorHandler(MtxErrorHandler_t *h) diff --git a/src/sage/libs/ntl/ntl_GF2E.pyx b/src/sage/libs/ntl/ntl_GF2E.pyx index 06cec5eeba3..13877a7e57b 100644 --- a/src/sage/libs/ntl/ntl_GF2E.pyx +++ b/src/sage/libs/ntl/ntl_GF2E.pyx @@ -446,7 +446,7 @@ cdef class ntl_GF2E(object): e = GF2E_degree() if k is None: - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField f = self.c.m._sage_() k = FiniteField(2**e, name='a', modulus=f) diff --git a/src/sage/libs/ntl/ntl_GF2X.pyx b/src/sage/libs/ntl/ntl_GF2X.pyx index 5d8251fb974..419004239a6 100644 --- a/src/sage/libs/ntl/ntl_GF2X.pyx +++ b/src/sage/libs/ntl/ntl_GF2X.pyx @@ -529,7 +529,7 @@ cdef class ntl_GF2X(object): """ if R is None: from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField R = PolynomialRing(FiniteField(2), 'x') return R(map(int,self.list())) diff --git a/src/sage/libs/ntl/ntl_mat_GF2.pyx b/src/sage/libs/ntl/ntl_mat_GF2.pyx index 0ef43e339b9..a97505e7243 100644 --- a/src/sage/libs/ntl/ntl_mat_GF2.pyx +++ b/src/sage/libs/ntl/ntl_mat_GF2.pyx @@ -538,7 +538,7 @@ cdef class ntl_mat_GF2(object): [0 0 0 1 1 1] [0 0 1 1 1 1] """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.matrix.constructor import Matrix m = Matrix(FiniteField(2),self.x.NumRows(),self.x.NumCols()) diff --git a/src/sage/libs/ntl/ntl_mat_GF2E.pyx b/src/sage/libs/ntl/ntl_mat_GF2E.pyx index f165dd18f1b..96cc56d6532 100644 --- a/src/sage/libs/ntl/ntl_mat_GF2E.pyx +++ b/src/sage/libs/ntl/ntl_mat_GF2E.pyx @@ -521,7 +521,7 @@ cdef class ntl_mat_GF2E(object): [a^2 + 1 a^2 + a] """ if k is None: - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField f = self.c.m._sage_() e = GF2E_degree() k = FiniteField(2**e, name='a', modulus=f) diff --git a/src/sage/libs/pari/gen.pyx b/src/sage/libs/pari/gen.pyx index 75a3286802a..7a528d621c5 100644 --- a/src/sage/libs/pari/gen.pyx +++ b/src/sage/libs/pari/gen.pyx @@ -73,10 +73,12 @@ from sage.libs.gmp.mpz cimport * from sage.libs.gmp.pylong cimport mpz_set_pylong from sage.libs.pari.closure cimport objtoclosure -from pari_instance cimport PariInstance, prec_bits_to_words, pari_instance +from pari_instance cimport (PariInstance, pari_instance, + prec_bits_to_words, prec_words_to_bits) cdef PariInstance P = pari_instance from sage.rings.integer cimport Integer +from sage.rings.rational cimport Rational include 'auto_gen.pxi' @@ -1408,12 +1410,22 @@ cdef class gen(gen_auto): def python(self, locals=None): """ - Return Python eval of self. + Return the closest Python/Sage equivalent of the given PARI object. - Note: is self is a real (type t_REAL) the result will be a - RealField element of the equivalent precision; if self is a complex - (type t_COMPLEX) the result will be a ComplexField element of - precision the minimum precision of the real and imaginary parts. + INPUT: + + - `z` -- PARI ``gen`` + + - `locals` -- optional dictionary used in fallback cases that + involve :func:`sage_eval` + + .. NOTE:: + + If ``self`` is a real (type ``t_REAL``), then the result + will be a RealField element of the equivalent precision; + if ``self`` is a complex (type ``t_COMPLEX``), then the + result will be a ComplexField element of precision the + maximal precision of the real and imaginary parts. EXAMPLES:: @@ -1426,17 +1438,115 @@ cdef class gen(gen_auto): sage: f.python({'x':x, 'y':y}) 2/3*x^3 + x + y - 5/7 - You can also use .sage, which is a psynonym:: + You can also use :meth:`.sage`, which is an alias:: sage: f.sage({'x':x, 'y':y}) 2/3*x^3 + x + y - 5/7 - """ - import sage.libs.pari.gen_py - return sage.libs.pari.gen_py.python(self, locals=locals) - sage = python + Converting a real number:: + + sage: pari.set_real_precision(70) + 15 + sage: a = pari('1.234').python(); a + 1.234000000000000000000000000000000000000000000000000000000000000000000000000 + sage: a.parent() + Real Field with 256 bits of precision + sage: pari.set_real_precision(15) + 70 + sage: a = pari('1.234').python(); a + 1.23400000000000000 + sage: a.parent() + Real Field with 64 bits of precision + + For complex numbers, the parent depends on the PARI type:: + + sage: a = pari('(3+I)').python(); a + i + 3 + sage: a.parent() + Number Field in i with defining polynomial x^2 + 1 + + sage: a = pari('2^31-1').python(); a + 2147483647 + sage: a.parent() + Integer Ring + + sage: a = pari('12/34').python(); a + 6/17 + sage: a.parent() + Rational Field + + sage: a = pari('(3+I)/2').python(); a + 1/2*i + 3/2 + sage: a.parent() + Number Field in i with defining polynomial x^2 + 1 + + sage: z = pari(CC(1.0+2.0*I)); z + 1.00000000000000 + 2.00000000000000*I + sage: a = z.python(); a + 1.00000000000000000 + 2.00000000000000000*I + sage: a.parent() + Complex Field with 64 bits of precision + + sage: I = sqrt(-1) + sage: a = pari(1.0 + 2.0*I).python(); a + 1.00000000000000000 + 2.00000000000000000*I + sage: a.parent() + Complex Field with 64 bits of precision + + Vectors and matrices:: + + sage: a = pari('[1,2,3,4]') + sage: a + [1, 2, 3, 4] + sage: a.type() + 't_VEC' + sage: b = a.python(); b + [1, 2, 3, 4] + sage: type(b) + + + sage: a = pari('[1,2;3,4]') + sage: a.type() + 't_MAT' + sage: b = a.python(); b + [1 2] + [3 4] + sage: b.parent() + Full MatrixSpace of 2 by 2 dense matrices over Integer Ring + + sage: a = pari('Vecsmall([1,2,3,4])') + sage: a.type() + 't_VECSMALL' + sage: a.python() + [1, 2, 3, 4] + + We use the locals dictionary:: + + sage: f = pari('(2/3)*x^3 + x - 5/7 + y') + sage: x,y=var('x,y') + sage: from sage.libs.pari.gen import gentoobj + sage: gentoobj(f, {'x':x, 'y':y}) + 2/3*x^3 + x + y - 5/7 + sage: gentoobj(f) + Traceback (most recent call last): + ... + NameError: name 'x' is not defined + + Conversion of p-adics:: + + sage: K = Qp(11,5) + sage: x = K(11^-10 + 5*11^-7 + 11^-6); x + 11^-10 + 5*11^-7 + 11^-6 + O(11^-5) + sage: y = pari(x); y + 11^-10 + 5*11^-7 + 11^-6 + O(11^-5) + sage: y.sage() + 11^-10 + 5*11^-7 + 11^-6 + O(11^-5) + sage: pari(K(11^-5)).sage() + 11^-5 + O(11^0) + """ + return gentoobj(self, locals) - _sage_ = _eval_ = python + sage = _sage_ = _eval_ = python def __long__(gen self): """ @@ -8197,7 +8307,7 @@ cdef class gen(gen_auto): Now it takes much less than a second:: sage: pari.allocatemem(200000) - PARI stack size set to 200000 bytes + PARI stack size set to 200000 bytes, maximum size set to ... sage: x = polygen(ZpFM(3,10)) sage: pol = ((x-1)^50 + x) sage: pari(pol).poldisc() @@ -9573,6 +9683,100 @@ cdef class gen(gen_auto): cpdef gen objtogen(s): """ Convert any Sage/Python object to a PARI gen. + + For Sage types, this uses the `_pari_()` method on the object. + Basic Python types like ``int`` are converted directly. For other + types, the string representation is used. + + EXAMPLES:: + + sage: pari([2,3,5]) + [2, 3, 5] + sage: pari(Matrix(2,2,range(4))) + [0, 1; 2, 3] + sage: pari(x^2-3) + x^2 - 3 + + :: + + sage: a = pari(1); a, a.type() + (1, 't_INT') + sage: a = pari(1/2); a, a.type() + (1/2, 't_FRAC') + sage: a = pari(1/2); a, a.type() + (1/2, 't_FRAC') + + Conversion from reals uses the real's own precision:: + + sage: a = pari(1.2); a, a.type(), a.precision() + (1.20000000000000, 't_REAL', 4) # 32-bit + (1.20000000000000, 't_REAL', 3) # 64-bit + + Conversion from strings uses the current PARI real precision. + By default, this is 64 bits:: + + sage: a = pari('1.2'); a, a.type(), a.precision() + (1.20000000000000, 't_REAL', 4) # 32-bit + (1.20000000000000, 't_REAL', 3) # 64-bit + + But we can change this precision:: + + sage: pari.set_real_precision(35) # precision in decimal digits + 15 + sage: a = pari('1.2'); a, a.type(), a.precision() + (1.2000000000000000000000000000000000, 't_REAL', 6) # 32-bit + (1.2000000000000000000000000000000000, 't_REAL', 4) # 64-bit + + Set the precision to 15 digits for the remaining tests:: + + sage: pari.set_real_precision(15) + 35 + + Conversion from matrices and vectors is supported:: + + sage: a = pari(matrix(2,3,[1,2,3,4,5,6])); a, a.type() + ([1, 2, 3; 4, 5, 6], 't_MAT') + sage: v = vector([1.2, 3.4, 5.6]) + sage: pari(v) + [1.20000000000000, 3.40000000000000, 5.60000000000000] + + Some more exotic examples:: + + sage: K. = NumberField(x^3 - 2) + sage: pari(K) + [y^3 - 2, [1, 1], -108, 1, [[1, 1.25992104989487, 1.58740105196820; 1, -0.629960524947437 + 1.09112363597172*I, -0.793700525984100 - 1.37472963699860*I], [1, 1.25992104989487, 1.58740105196820; 1, 0.461163111024285, -2.16843016298270; 1, -1.72108416091916, 0.581029111014503], [1, 1, 2; 1, 0, -2; 1, -2, 1], [3, 0, 0; 0, 0, 6; 0, 6, 0], [6, 0, 0; 0, 6, 0; 0, 0, 3], [2, 0, 0; 0, 0, 1; 0, 1, 0], [2, [0, 0, 2; 1, 0, 0; 0, 1, 0]], []], [1.25992104989487, -0.629960524947437 + 1.09112363597172*I], [1, y, y^2], [1, 0, 0; 0, 1, 0; 0, 0, 1], [1, 0, 0, 0, 0, 2, 0, 2, 0; 0, 1, 0, 1, 0, 0, 0, 0, 2; 0, 0, 1, 0, 1, 0, 1, 0, 0]] + + sage: E = EllipticCurve('37a1') + sage: pari(E) + [0, 0, 1, -1, 0, 0, -2, 1, -1, 48, -216, 37, 110592/37, Vecsmall([1]), [Vecsmall([64, 1])], [0, 0, 0, 0, 0, 0, 0, 0]] + + Conversion from basic Python types:: + + sage: pari(int(-5)) + -5 + sage: pari(long(2**150)) + 1427247692705959881058285969449495136382746624 + sage: pari(float(pi)) + 3.14159265358979 + sage: pari(complex(exp(pi*I/4))) + 0.707106781186548 + 0.707106781186548*I + sage: pari(False) + 0 + sage: pari(True) + 1 + + Some commands are just executed without returning a value:: + + sage: pari("dummy = 0; kill(dummy)") + sage: type(pari("dummy = 0; kill(dummy)")) + + + TESTS:: + + sage: pari(None) + Traceback (most recent call last): + ... + ValueError: Cannot convert None to pari """ cdef GEN g cdef Py_ssize_t length, i @@ -9635,6 +9839,73 @@ cpdef gen objtogen(s): return objtogen(str(s)) +cpdef gentoobj(gen z, locals={}): + """ + Convert a PARI gen to a Sage/Python object. + + See the ``python`` method of :class:`gen` for documentation and + examples. + """ + cdef GEN g = z.g + cdef long t = typ(g) + cdef long tx, ty + cdef gen real, imag + cdef Py_ssize_t i, j, nr, nc + + if t == t_INT: + return Integer(z) + elif t == t_FRAC: + return Rational(z) + elif t == t_REAL: + from sage.rings.all import RealField + prec = prec_words_to_bits(z.precision()) + return RealField(prec)(z) + elif t == t_COMPLEX: + real = z.real() + imag = z.imag() + tx = typ(real.g) + ty = typ(imag.g) + if tx in [t_INTMOD, t_PADIC] or ty in [t_INTMOD, t_PADIC]: + raise NotImplementedError("No conversion to python available for t_COMPLEX with t_INTMOD or t_PADIC components") + if tx == t_REAL or ty == t_REAL: + xprec = real.precision() # will be 0 if exact + yprec = imag.precision() # will be 0 if exact + if xprec == 0: + prec = prec_words_to_bits(yprec) + elif yprec == 0: + prec = prec_words_to_bits(xprec) + else: + prec = max(prec_words_to_bits(xprec), prec_words_to_bits(yprec)) + + from sage.rings.all import RealField, ComplexField + R = RealField(prec) + C = ComplexField(prec) + return C(R(real), R(imag)) + else: + from sage.rings.all import QuadraticField + K = QuadraticField(-1, 'i') + return K([gentoobj(real), gentoobj(imag)]) + elif t == t_VEC or t == t_COL: + return [gentoobj(x, locals) for x in z.python_list()] + elif t == t_VECSMALL: + return z.python_list_small() + elif t == t_MAT: + nc = lg(g)-1 + nr = 0 if nc == 0 else lg(gel(g,1))-1 + L = [gentoobj(z[i,j], locals) for i in range(nr) for j in range(nc)] + from sage.matrix.constructor import matrix + return matrix(nr, nc, L) + elif t == t_PADIC: + from sage.rings.padics.factory import Qp + p = z.padicprime() + K = Qp(Integer(p), precp(g)) + return K(z.lift()) + + # Fallback (e.g. polynomials): use string representation + from sage.misc.sage_eval import sage_eval + return sage_eval(str(z), locals=locals) + + cdef GEN _Vec_append(GEN v, GEN a, long n): """ This implements appending zeros (or another constant GEN ``a``) to diff --git a/src/sage/libs/pari/gen_py.py b/src/sage/libs/pari/gen_py.py index ef81e47b7fe..c507a80a0b7 100644 --- a/src/sage/libs/pari/gen_py.py +++ b/src/sage/libs/pari/gen_py.py @@ -1,104 +1,8 @@ -"Pari objects" - -from sage.rings.all import * - def pari(x): """ Return the PARI object constructed from a Sage/Python object. - For Sage types, this uses the `_pari_()` method on the object if - possible and otherwise it uses the string representation. - - EXAMPLES:: - - sage: pari([2,3,5]) - [2, 3, 5] - sage: pari(Matrix(2,2,range(4))) - [0, 1; 2, 3] - sage: pari(x^2-3) - x^2 - 3 - - :: - - sage: a = pari(1); a, a.type() - (1, 't_INT') - sage: a = pari(1/2); a, a.type() - (1/2, 't_FRAC') - sage: a = pari(1/2); a, a.type() - (1/2, 't_FRAC') - - Conversion from reals uses the real's own precision:: - - sage: a = pari(1.2); a, a.type(), a.precision() - (1.20000000000000, 't_REAL', 4) # 32-bit - (1.20000000000000, 't_REAL', 3) # 64-bit - - Conversion from strings uses the current PARI real precision. - By default, this is 96 bits on 32-bit systems and 128 bits on - 64-bit systems:: - - sage: a = pari('1.2'); a, a.type(), a.precision() - (1.20000000000000, 't_REAL', 5) # 32-bit - (1.20000000000000, 't_REAL', 4) # 64-bit - - But we can change this precision:: - - sage: pari.set_real_precision(35) # precision in decimal digits - 15 - sage: a = pari('1.2'); a, a.type(), a.precision() - (1.2000000000000000000000000000000000, 't_REAL', 6) # 32-bit - (1.2000000000000000000000000000000000, 't_REAL', 4) # 64-bit - - Set the precision to 15 digits for the remaining tests:: - - sage: pari.set_real_precision(15) - 35 - - Conversion from matrices and vectors is supported:: - - sage: a = pari(matrix(2,3,[1,2,3,4,5,6])); a, a.type() - ([1, 2, 3; 4, 5, 6], 't_MAT') - sage: v = vector([1.2, 3.4, 5.6]) - sage: pari(v) - [1.20000000000000, 3.40000000000000, 5.60000000000000] - - Some more exotic examples:: - - sage: K. = NumberField(x^3 - 2) - sage: pari(K) - [y^3 - 2, [1, 1], -108, 1, [[1, 1.25992104989487, 1.58740105196820; 1, -0.629960524947437 + 1.09112363597172*I, -0.793700525984100 - 1.37472963699860*I], [1, 1.25992104989487, 1.58740105196820; 1, 0.461163111024285, -2.16843016298270; 1, -1.72108416091916, 0.581029111014503], [1, 1, 2; 1, 0, -2; 1, -2, 1], [3, 0, 0; 0, 0, 6; 0, 6, 0], [6, 0, 0; 0, 6, 0; 0, 0, 3], [2, 0, 0; 0, 0, 1; 0, 1, 0], [2, [0, 0, 2; 1, 0, 0; 0, 1, 0]], []], [1.25992104989487, -0.629960524947437 + 1.09112363597172*I], [1, y, y^2], [1, 0, 0; 0, 1, 0; 0, 0, 1], [1, 0, 0, 0, 0, 2, 0, 2, 0; 0, 1, 0, 1, 0, 0, 0, 0, 2; 0, 0, 1, 0, 1, 0, 1, 0, 0]] - - sage: E = EllipticCurve('37a1') - sage: pari(E) - [0, 0, 1, -1, 0, 0, -2, 1, -1, 48, -216, 37, 110592/37, Vecsmall([1]), [Vecsmall([64, 1])], [0, 0, 0, 0, 0, 0, 0, 0]] - - Conversion from basic Python types:: - - sage: pari(int(-5)) - -5 - sage: pari(long(2**150)) - 1427247692705959881058285969449495136382746624 - sage: pari(float(pi)) - 3.14159265358979 - sage: pari(complex(exp(pi*I/4))) - 0.707106781186548 + 0.707106781186548*I - sage: pari(False) - 0 - sage: pari(True) - 1 - - Some commands are just executed without returning a value:: - - sage: pari("dummy = 0; kill(dummy)") - sage: type(pari("dummy = 0; kill(dummy)")) - - - TESTS:: - - sage: pari(None) - Traceback (most recent call last): - ... - ValueError: Cannot convert None to pari + This is deprecated, import ``pari`` from ``sage.libs.pari.all``. """ from sage.misc.superseded import deprecation deprecation(17451, 'gen_py.pari is deprecated, use sage.libs.pari.all.pari instead') @@ -110,170 +14,18 @@ def python(z, locals=None): """ Return the closest Python/Sage equivalent of the given PARI object. - INPUT: - - - `z` -- PARI ``gen`` - - - `locals` -- optional dictionary used in fallback cases that - involve sage_eval - - The component parts of a t_COMPLEX may be t_INT, t_REAL, t_INTMOD, - t_FRAC, t_PADIC. The components need not have the same type - (e.g. if z=2+1.2*I then z.real() is t_INT while z.imag() is - t_REAL(). They are converted as follows: - - t_INT: ZZ[i] - t_FRAC: QQ(i) - t_REAL: ComplexField(prec) for equivalent precision - t_INTMOD, t_PADIC: raise NotImplementedError - - EXAMPLES:: - - sage: a = pari('(3+I)').python(); a - i + 3 - sage: a.parent() - Maximal Order in Number Field in i with defining polynomial x^2 + 1 - - sage: a = pari('2^31-1').python(); a - 2147483647 - sage: a.parent() - Integer Ring - - sage: a = pari('12/34').python(); a - 6/17 - sage: a.parent() - Rational Field - - sage: a = pari('1.234').python(); a - 1.23400000000000000 - sage: a.parent() - Real Field with 64 bits of precision - - sage: a = pari('(3+I)/2').python(); a - 1/2*i + 3/2 - sage: a.parent() - Number Field in i with defining polynomial x^2 + 1 - - Conversion of complex numbers: the next example is converting from - an element of the Symbolic Ring, which goes via the string - representation:: + This is deprecated, use the ``python`` method of :class:`gen` + instead. - sage: I = SR(I) - sage: a = pari(1.0+2.0*I).python(); a - 1.00000000000000000 + 2.00000000000000000*I - sage: type(a) - - sage: a.parent() - Complex Field with 64 bits of precision - - For architecture-independent complex numbers, start from a - suitable ComplexField:: - - sage: z = pari(CC(1.0+2.0*I)); z - 1.00000000000000 + 2.00000000000000*I - sage: a=z.python(); a - 1.00000000000000000 + 2.00000000000000000*I - sage: a.parent() - Complex Field with 64 bits of precision - - Vectors and matrices:: - - sage: a = pari('[1,2,3,4]') - sage: a - [1, 2, 3, 4] - sage: a.type() - 't_VEC' - sage: b = a.python(); b - [1, 2, 3, 4] - sage: type(b) - - - sage: a = pari('[1,2;3,4]') - sage: a.type() - 't_MAT' - sage: b = a.python(); b - [1 2] - [3 4] - sage: b.parent() - Full MatrixSpace of 2 by 2 dense matrices over Integer Ring - - sage: a = pari('Vecsmall([1,2,3,4])') - sage: a.type() - 't_VECSMALL' - sage: a.python() - [1, 2, 3, 4] - - We use the locals dictionary:: - - sage: f = pari('(2/3)*x^3 + x - 5/7 + y') - sage: x,y=var('x,y') - sage: import sage.libs.pari.gen_py - sage: sage.libs.pari.gen_py.python(f, {'x':x, 'y':y}) - 2/3*x^3 + x + y - 5/7 - sage: sage.libs.pari.gen_py.python(f) - Traceback (most recent call last): - ... - NameError: name 'x' is not defined - - Conversion of p-adics:: + TESTS:: - sage: K = Qp(11,5) - sage: x = K(11^-10 + 5*11^-7 + 11^-6); x - 11^-10 + 5*11^-7 + 11^-6 + O(11^-5) - sage: y = pari(x); y - 11^-10 + 5*11^-7 + 11^-6 + O(11^-5) - sage: y.sage() - 11^-10 + 5*11^-7 + 11^-6 + O(11^-5) - sage: pari(K(11^-5)).sage() - 11^-5 + O(11^0) + sage: from sage.libs.pari.gen_py import python + sage: python(pari(3)) + doctest:...: DeprecationWarning: gen_py.python is deprecated, use sage.libs.pari.gen.gentoobj or the .python() method instead + See http://trac.sagemath.org/19888 for details. + 3 """ - from sage.libs.pari.pari_instance import prec_words_to_bits - - t = z.type() - if t == "t_REAL": - return RealField(prec_words_to_bits(z.precision()))(z) - elif t == "t_FRAC": - Q = RationalField() - return Q(z) - elif t == "t_INT": - Z = IntegerRing() - return Z(z) - elif t == "t_COMPLEX": - tx = z.real().type() - ty = z.imag().type() - if tx in ["t_INTMOD", "t_PADIC"] or ty in ["t_INTMOD", "t_PADIC"]: - raise NotImplementedError("No conversion to python available for t_COMPLEX with t_INTMOD or t_PADIC components") - if tx == "t_REAL" or ty == "t_REAL": - xprec = z.real().precision() # will be 0 if exact - yprec = z.imag().precision() # will be 0 if exact - if xprec == 0: - prec = prec_words_to_bits(yprec) - elif yprec == 0: - prec = prec_words_to_bits(xprec) - else: - prec = max(prec_words_to_bits(xprec), prec_words_to_bits(yprec)) - R = RealField(prec) - C = ComplexField(prec) - return C(R(z.real()), R(z.imag())) - if tx == "t_FRAC" or ty == "t_FRAC": - return QuadraticField(-1,'i')([python(c) for c in list(z)]) - if tx == "t_INT" or ty == "t_INT": - return QuadraticField(-1,'i').ring_of_integers()([python(c) for c in list(z)]) - raise NotImplementedError("No conversion to python available for t_COMPLEX with components %s"%(tx,ty)) - elif t == "t_VEC": - return [python(x) for x in z.python_list()] - elif t == "t_VECSMALL": - return z.python_list_small() - elif t == "t_MAT": - from sage.matrix.constructor import matrix - return matrix(z.nrows(),z.ncols(),[python(z[i,j]) for i in range(z.nrows()) for j in range(z.ncols())]) - elif t == "t_PADIC": - from sage.rings.padics.factory import Qp - Z = IntegerRing() - p = z.padicprime() - rprec = Z(z.padicprec(p)) - Z(z._valp()) - K = Qp(Z(p), rprec) - return K(z.lift()) - else: - from sage.misc.sage_eval import sage_eval - return sage_eval(str(z), locals=locals) + from sage.misc.superseded import deprecation + deprecation(19888, 'gen_py.python is deprecated, use sage.libs.pari.gen.gentoobj or the .python() method instead') + from sage.libs.pari.gen import gentoobj + return gentoobj(z, locals) diff --git a/src/sage/libs/pari/handle_error.pyx b/src/sage/libs/pari/handle_error.pyx index 9dfaeffb619..9183ff0defc 100644 --- a/src/sage/libs/pari/handle_error.pyx +++ b/src/sage/libs/pari/handle_error.pyx @@ -159,18 +159,20 @@ cdef int _pari_err_handle(GEN E) except 0: """ cdef long errnum = E[1] - if errnum == e_STACK: - # PARI is out of memory. We double the size of the PARI stack - # and retry the computation. - pari_instance.allocatemem(silent=True) - return 0 sig_block() cdef char* errstr cdef char* s try: - errstr = pari_err2str(E) - pari_error_string = errstr.decode('ascii') + if errnum == e_STACK: + # Custom error message for PARI stack overflow + pari_error_string = "the PARI stack overflows (current size: {}; maximum size: {})\n" + pari_error_string += "You can use pari.allocatemem() to change the stack size and try again" + pari_error_string = pari_error_string.format(pari_mainstack.size, pari_mainstack.vsize) + else: + errstr = pari_err2str(E) + pari_error_string = errstr.decode('ascii') + pari_free(errstr) s = closure_func_err() if s is not NULL: @@ -178,7 +180,6 @@ cdef int _pari_err_handle(GEN E) except 0: raise PariError(errnum, pari_error_string, pari_instance.new_gen_noclear(E)) finally: - pari_free(errstr) sig_unblock() @@ -193,19 +194,13 @@ cdef void _pari_err_recover(long errnum): Perform a computation that requires doubling the default stack several times:: - sage: pari.allocatemem(2^12) - PARI stack size set to 4096 bytes + sage: pari.allocatemem(2^12, 2^26) + PARI stack size set to 4096 bytes, maximum size set to 67108864 sage: x = pari('2^(2^26)') sage: x == 2^(2^26) True """ - if not PyErr_Occurred(): - # No exception was raised => retry the computation starting - # from sig_on(). This can happen if we successfully enlarged the - # PARI stack in _pari_handle_exception(). - sig_retry() - else: - # An exception was raised. Jump to the signal-handling code - # which will cause sig_on() to see the exception. - sig_error() + # An exception was raised. Jump to the signal-handling code + # which will cause sig_on() to see the exception. + sig_error() diff --git a/src/sage/libs/pari/pari_instance.pxd b/src/sage/libs/pari/pari_instance.pxd index 0550ded0ee9..ec23376b8e8 100644 --- a/src/sage/libs/pari/pari_instance.pxd +++ b/src/sage/libs/pari/pari_instance.pxd @@ -7,6 +7,7 @@ cimport cython from sage.libs.pari.gen cimport gen cpdef long prec_bits_to_words(unsigned long prec_in_bits) +cpdef long prec_words_to_bits(long prec_in_words) cdef class PariInstance_auto(ParentWithBase): pass diff --git a/src/sage/libs/pari/pari_instance.pyx b/src/sage/libs/pari/pari_instance.pyx index b27e83ca8c2..020f3614607 100644 --- a/src/sage/libs/pari/pari_instance.pyx +++ b/src/sage/libs/pari/pari_instance.pyx @@ -299,7 +299,7 @@ cpdef long prec_bits_to_words(unsigned long prec_in_bits): # This equals ceil(prec_in_bits/wordsize) + 2 return (prec_in_bits - 1)//wordsize + 3 -def prec_words_to_bits(long prec_in_words): +cpdef long prec_words_to_bits(long prec_in_words): r""" Convert from pari real precision expressed in words to precision expressed in bits. Note: this adjusts for the two codewords of a @@ -437,10 +437,25 @@ cdef class PariInstance(PariInstance_auto): if avma: return # pari already initialized. - # The size here doesn't really matter, because we will allocate - # our own stack anyway. We ask PARI not to set up signal and - # error handlers. - pari_init_opts(10000, maxprime, INIT_DFTm) + # PARI has a "real" stack size (parisize) and a "virtual" stack + # size (parisizemax). The idea is that the real stack will be + # used if possible, but the stack might be increased up to + # the complete virtual stack. Therefore, it is not a problem to + # set the virtual stack size to a large value. There are two + # constraints for the virtual stack size: + # 1) on 32-bit systems, even virtual memory can be a scarce + # resource since it is limited by 4GB (of which the kernel + # needs a significant part) + # 2) the system should actually be able to handle a stack size + # as large as the complete virtual stack. + # As a simple heuristic, we set the virtual stack to 1/4 of the + # virtual memory. + + from sage.misc.memory_info import MemoryInfo + mem = MemoryInfo() + + pari_init_opts(size, maxprime, INIT_DFTm) + paristack_setsize(size, mem.virtual_memory_limit() // 4) # Disable PARI's stack overflow checking which is incompatible # with multi-threading. @@ -452,11 +467,6 @@ cdef class PariInstance(PariInstance_auto): # so we need to reset them. init_memory_functions() - # Free the PARI stack and allocate our own (using Cython) - pari_mainstack_free(pari_mainstack) - pari_mainstack.vbot = 0 - init_stack(size) - # Set printing functions global pariOut, pariErr @@ -517,13 +527,8 @@ cdef class PariInstance(PariInstance_auto): indirect doctest. See the discussion at :trac:`13741`. """ - sage_free(pari_mainstack.vbot) - pari_mainstack.rsize = 0 - pari_mainstack.vsize = 0 - pari_mainstack.bot = 0 - pari_mainstack.vbot = 0 - pari_mainstack.top = 0 - pari_close() + if avma: + pari_close() def __repr__(self): return "Interface to the PARI C library" @@ -1108,71 +1113,56 @@ cdef class PariInstance(PariInstance_auto): def stacksize(self): r""" - Returns the current size of the PARI stack, which is `10^6` + Return the current size of the PARI stack, which is `10^6` by default. However, the stack size is automatically doubled - when needed. It can also be set directly using - ``pari.allocatemem()``. + when needed up to some maximum. + + .. SEEALSO:: + + - :meth:`stacksizemax` to get the maximum stack size + - :meth:`allocatemem` to change the current or maximum + stack size EXAMPLES:: sage: pari.stacksize() 1000000 - + sage: pari.allocatemem(2^18, silent=True) + sage: pari.stacksize() + 262144 """ - return pari_mainstack.rsize + return pari_mainstack.size - def _allocate_huge_mem(self): + def stacksizemax(self): r""" - This function tries to allocate an impossibly large amount of - PARI stack in order to test ``init_stack()`` failing. - - TESTS:: - - sage: pari.allocatemem(10^6, silent=True) - sage: pari._allocate_huge_mem() - Traceback (most recent call last): - ... - MemoryError: Unable to allocate ... (instead, allocated 1000000 bytes) + Return the maximum size of the PARI stack, which is determined + at startup in terms of available memory. Usually, the PARI + stack size is (much) smaller than this maximum but the stack + will be increased up to this maximum if needed. - Test that the PARI stack is sane after this failure:: + .. SEEALSO:: - sage: a = pari('2^10000000') - sage: pari.allocatemem(10^6, silent=True) - """ - # Since size_t is unsigned, this should wrap over to a very - # large positive number. - init_stack((-4096)) - - def _setup_failed_retry(self): - r""" - This function pretends that the PARI stack is larger than it - actually is such that allocatemem(0) will allocate much more - than double the current stack. That allocation will then fail. - This function is meant to be used only in this doctest. + - :meth:`stacksize` to get the current stack size + - :meth:`allocatemem` to change the current or maximum + stack size - TESTS:: + EXAMPLES:: - sage: pari.allocatemem(4096, silent=True) - sage: pari._setup_failed_retry() - sage: a = pari('2^1000000') - Traceback (most recent call last): - ... - MemoryError: Unable to enlarge PARI stack (instead, kept the stack at ... bytes) - sage: pari.allocatemem(10^6, silent=True) + sage: pari.allocatemem(2^18, 2^26, silent=True) + sage: pari.stacksizemax() + 67108864 """ - # Pretend that the stack is much larger than it actually is, - # JUST FOR TESTING! - pari_mainstack.rsize = (-16) + return pari_mainstack.vsize - def allocatemem(self, long s=0, silent=False): + def allocatemem(self, size_t s=0, size_t sizemax=0, *, silent=False): r""" - Change the PARI stack space to the given size (or double the - current size if ``s`` is `0`). + Change the PARI stack space to the given size ``s`` (or double + the current size if ``s`` is `0`) and change the maximum stack + size to ``sizemax``. - If `s = 0` and insufficient memory is avaible to double, the - PARI stack will be enlarged by a smaller amount. In any case, - a ``MemoryError`` will be raised if the requested memory cannot - be allocated. + PARI tries to use only its current stack (the size which is set + by ``s``), but it will increase its stack if needed up to the + maximum size which is set by ``sizemax``. The PARI stack is never automatically shrunk. You can use the command ``pari.allocatemem(10^6)`` to reset the size to `10^6`, @@ -1183,26 +1173,28 @@ cdef class PariInstance(PariInstance_auto): It does no real harm to set this to a small value as the PARI stack will be automatically doubled when we run out of memory. - However, it could make some calculations slower (since they have - to be redone from the start after doubling the stack until the - stack is big enough). INPUT: - - ``s`` - an integer (default: 0). A non-zero argument should - be the size in bytes of the new PARI stack. If `s` is zero, - try to double the current stack size. + - ``s`` - an integer (default: 0). A non-zero argument is the + size in bytes of the new PARI stack. If `s` is zero, double + the current stack size. + + - ``sizemax`` - an integer (default: 0). A non-zero argument + is the maximum size in bytes of the PARI stack. If + ``sizemax`` is 0, the maximum of the current maximum and + ``s`` is taken. EXAMPLES:: sage: pari.allocatemem(10^7) - PARI stack size set to 10000000 bytes + PARI stack size set to 10000000 bytes, maximum size set to 67108864 sage: pari.allocatemem() # Double the current size - PARI stack size set to 20000000 bytes + PARI stack size set to 20000000 bytes, maximum size set to 67108864 sage: pari.stacksize() 20000000 sage: pari.allocatemem(10^6) - PARI stack size set to 1000000 bytes + PARI stack size set to 1000000 bytes, maximum size set to 67108864 The following computation will automatically increase the PARI stack size:: @@ -1215,27 +1207,56 @@ cdef class PariInstance(PariInstance_auto): sage: pari.stacksize() 16000000 - sage: pari.allocatemem(10^6) - PARI stack size set to 1000000 bytes - sage: pari.stacksize() - 1000000 + + Setting a small maximum size makes this fail:: + + sage: pari.allocatemem(10^6, 2^22) + PARI stack size set to 1000000 bytes, maximum size set to 4194304 + sage: a = pari('2^100000000') + Traceback (most recent call last): + ... + PariError: _^s: the PARI stack overflows (current size: 1000000; maximum size: 4194304) + You can use pari.allocatemem() to change the stack size and try again TESTS: Do the same without using the string interface and starting from a very small stack size:: - sage: pari.allocatemem(1) - PARI stack size set to 1024 bytes + sage: pari.allocatemem(1, 2^26) + PARI stack size set to 1024 bytes, maximum size set to 67108864 sage: a = pari(2)^100000000 sage: pari.stacksize() 16777216 - """ - if s < 0: - raise ValueError("Stack size must be nonnegative") - init_stack(s) + + We do not allow ``sizemax`` less than ``s``:: + + sage: pari.allocatemem(10^7, 10^6) + Traceback (most recent call last): + ... + ValueError: the maximum size (10000000) should be at least the stack size (1000000) + """ + if s == 0: + s = pari_mainstack.size * 2 + if s < pari_mainstack.size: + raise OverflowError("cannot double stack size") + elif s < 1024: + s = 1024 # arbitrary minimum size + if sizemax == 0: + # For the default sizemax, use the maximum of current + # sizemax and the given size s. + if pari_mainstack.vsize > s: + sizemax = pari_mainstack.vsize + else: + sizemax = s + elif sizemax < s: + raise ValueError("the maximum size ({}) should be at least the stack size ({})".format(s, sizemax)) + pari_catch_sig_on() + paristack_setsize(s, sizemax) + pari_catch_sig_off() if not silent: - print "PARI stack size set to", self.stacksize(), "bytes" + print("PARI stack size set to {} bytes, maximum size set to {}". + format(self.stacksize(), self.stacksizemax())) def pari_version(self): return str(PARIVERSION) @@ -1673,97 +1694,6 @@ cdef class PariInstance(PariInstance_auto): return self.new_gen(genus2red(t0.g, NULL)) -cdef int init_stack(size_t requested_size) except -1: - r""" - Low-level Cython function to allocate the PARI stack. This - function should not be called directly, use ``pari.allocatemem()`` - instead. - """ - global avma - - cdef size_t old_size = pari_mainstack.rsize - - cdef size_t new_size - cdef size_t max_size = (-1) - if (requested_size == 0): - if old_size < max_size/2: - # Double the stack - new_size = 2*old_size - elif old_size < 4*(max_size/5): - # We cannot possibly double since we already use over half - # the addressable memory: take the average of current and - # maximum size - new_size = max_size/2 + old_size/2 - else: - # We already use 80% of the addressable memory => give up - raise MemoryError("Unable to enlarge PARI stack (instead, kept the stack at %s bytes)"%(old_size)) - else: - new_size = requested_size - - # Align size to 16 bytes and take 1024 bytes as a minimum - new_size = (new_size/16)*16 - if (new_size < 1024): - new_size = 1024 - - # Disable interrupts - sig_on() - sig_block() - - # If this is non-zero, the size we failed to allocate - cdef size_t failed_size = 0 - - cdef pari_sp bot # New stack - try: - # Free the current stack - libc.stdlib.free(pari_mainstack.vbot) - pari_mainstack.rsize = 0 - pari_mainstack.vsize = 0 - pari_mainstack.bot = 0 - pari_mainstack.vbot = 0 - pari_mainstack.top = 0 - - # Allocate memory for new stack. - bot = libc.stdlib.malloc(new_size) - - # If doubling failed, instead add 25% to the current stack size. - # We already checked that we use less than 80% of the maximum value - # for s, so this will not overflow. - if (bot == 0) and (requested_size == 0): - new_size = (old_size/64)*80 - bot = libc.stdlib.malloc(new_size) - - if not bot: - failed_size = new_size - # We lost our PARI stack and are not able to allocate the - # requested size. If we just raise an exception now, we end up - # *without* a PARI stack which is not good. We will raise an - # exception later, after allocating *some* PARI stack. - new_size = old_size - while new_size >= 1024: # hope this never fails! - bot = libc.stdlib.malloc(new_size) - if bot: break - new_size = (new_size/32)*16 - - if not bot: - avma = 0 - raise SystemError("Unable to allocate PARI stack, all subsequent PARI computations will crash") - - pari_mainstack.rsize = new_size - pari_mainstack.vsize = 0 - pari_mainstack.bot = bot - pari_mainstack.vbot = bot - pari_mainstack.top = bot + new_size - avma = pari_mainstack.top - - if failed_size: - raise MemoryError("Unable to allocate %s bytes for the PARI stack (instead, allocated %s bytes)"%(failed_size, new_size)) - - return 0 - finally: - sig_unblock() - sig_off() - - cdef inline void INT_to_mpz(mpz_ptr value, GEN g): """ Store a PARI ``t_INT`` as an ``mpz_t``. diff --git a/src/sage/libs/pari/paridecl.pxd b/src/sage/libs/pari/paridecl.pxd index d60565b02f5..f6650d5b0a5 100644 --- a/src/sage/libs/pari/paridecl.pxd +++ b/src/sage/libs/pari/paridecl.pxd @@ -3143,9 +3143,6 @@ cdef extern from "sage/libs/pari/parisage.h": void pari_thread_valloc(pari_thread *t, size_t s, size_t v, GEN arg) GEN pari_version() void pari_warn(int numerr, ...) - void * pari_mainstack_malloc(size_t size) - void pari_mainstack_mfree(void *s, size_t size) - void pari_mainstack_free(_pari_mainstack *st) void paristack_alloc(size_t rsize, size_t vsize) void paristack_newrsize(ulong newsize) void paristack_resize(ulong newsize) diff --git a/src/sage/libs/singular/singular.pyx b/src/sage/libs/singular/singular.pyx index 8401b9d3c4e..a0dd0729f35 100644 --- a/src/sage/libs/singular/singular.pyx +++ b/src/sage/libs/singular/singular.pyx @@ -47,7 +47,6 @@ from sage.rings.finite_rings.finite_field_ntl_gf2e import FiniteField_ntl_gf2e from sage.libs.pari.all import pari from sage.libs.gmp.all cimport * -from sage.structure.parent_base cimport ParentWithBase from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular _saved_options = (int(0),0,0) diff --git a/src/sage/manifolds/__init__.py b/src/sage/manifolds/__init__.py new file mode 100644 index 00000000000..932b79829cf --- /dev/null +++ b/src/sage/manifolds/__init__.py @@ -0,0 +1 @@ +# Empty file diff --git a/src/sage/manifolds/all.py b/src/sage/manifolds/all.py new file mode 100644 index 00000000000..990657e143b --- /dev/null +++ b/src/sage/manifolds/all.py @@ -0,0 +1,3 @@ +from sage.misc.lazy_import import lazy_import +lazy_import('sage.manifolds.manifold', 'Manifold') + diff --git a/src/sage/manifolds/chart.py b/src/sage/manifolds/chart.py new file mode 100644 index 00000000000..6a6194bbc57 --- /dev/null +++ b/src/sage/manifolds/chart.py @@ -0,0 +1,2103 @@ +r""" +Coordinate Charts + +The class :class:`Chart` implements coordinate charts on a topological +manifold over a topological field `K`. The subclass :class:`RealChart` +is devoted to the case `K=\RR`, for which the concept of coordinate +range is meaningful. + +Transition maps between charts are implemented via the class +:class:`CoordChange`. + +AUTHORS: + +- Eric Gourgoulhon, Michal Bejger (2013-2015) : initial version +- Travis Scrimshaw (2015): review tweaks + +REFERENCES: + +- Chap. 2 of [Lee11]_ J.M. Lee: *Introduction to Topological Manifolds*, + 2nd ed., Springer (New York) (2011) + +- Chap. 1 of [Lee13]_ J.M. Lee : *Introduction to Smooth Manifolds*, + 2nd ed., Springer (New York) (2013) +""" + +#***************************************************************************** +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger +# Copyright (C) 2015 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.structure.sage_object import SageObject +from sage.structure.unique_representation import UniqueRepresentation +from sage.symbolic.ring import SR +from sage.rings.infinity import Infinity +from sage.misc.latex import latex + +class Chart(UniqueRepresentation, SageObject): + r""" + Chart on a topological manifold. + + Given a topological manifold `M` of dimension `n` over a topological + field `K`, a *chart* on `M` is a pair `(U, \varphi)`, where `U` is an + open subset of `M` and `\varphi : U \rightarrow V \subset K^n` is a + homeomorphism from `U` to an open subset `V` of `K^n`. + + The components `(x^1, \ldots, x^n)` of `\varphi`, defined by + `\varphi(p) = (x^1(p), \ldots, x^n(p)) \in K^n` for any point + `p \in U`, are called the *coordinates* of the chart `(U, \varphi)`. + + INPUT: + + - ``domain`` -- open subset `U` on which the chart is defined (must be + an instance of :class:`~sage.manifolds.manifold.TopologicalManifold`) + - ``coordinates`` -- (default: ``''`` (empty string)) the string + defining the coordinate symbols, see below + - ``names`` -- (default: ``None``) unused argument, except if + ``coordinates`` is not provided; it must then be a tuple containing + the coordinate symbols (this is guaranteed if the shortcut operator + ``<,>`` is used) + + The string ``coordinates`` has the space ``' '`` as a separator and each + item has at most two fields, separated by a colon (``:``): + + 1. the coordinate symbol (a letter or a few letters); + 2. (optional) the LaTeX spelling of the coordinate, if not provided the + coordinate symbol given in the first field will be used. + + If it contains any LaTeX expression, the string ``coordinates`` must be + declared with the prefix 'r' (for "raw") to allow for a proper treatment + of LaTeX's backslash character (see examples below). + If no LaTeX spelling is to be set for any coordinate, the argument + ``coordinates`` can be omitted when the shortcut operator ``<,>`` is + used via Sage preparser (see examples below). + + EXAMPLES: + + A chart on a complex 2-dimensional topological manifold:: + + sage: M = Manifold(2, 'M', field='complex', structure='topological') + sage: X = M.chart('x y'); X + Chart (M, (x, y)) + sage: latex(X) + \left(M,(x, y)\right) + sage: type(X) + + + To manipulate the coordinates `(x,y)` as global variables, + one has to set:: + + sage: x,y = X[:] + + However, a shortcut is to use the declarator ```` in the left-hand + side of the chart declaration (there is then no need to pass the string + ``'x y'`` to ``chart()``):: + + sage: M = Manifold(2, 'M', field='complex', structure='topological') + sage: X. = M.chart(); X + Chart (M, (x, y)) + + The coordinates are then immediately accessible:: + + sage: y + y + sage: x is X[0] and y is X[1] + True + + Note that ``x`` and ``y`` declared in ```` are mere Python variable + names and do not have to coincide with the coordinate symbols; + for instance, one may write:: + + sage: M = Manifold(2, 'M', field='complex', structure='topological') + sage: X. = M.chart('x y'); X + Chart (M, (x, y)) + + Then ``y`` is not known as a global Python variable and the + coordinate `y` is accessible only through the global variable ``y1``:: + + sage: y1 + y + sage: latex(y1) + y + sage: y1 is X[1] + True + + However, having the name of the Python variable coincide with the + coordinate symbol is quite convenient; so it is recommended to declare:: + + sage: M = Manifold(2, 'M', field='complex', structure='topological') + sage: X. = M.chart() + + In the above example, the chart X covers entirely the manifold ``M``:: + + sage: X.domain() + Complex 2-dimensional topological manifold M + + Of course, one may declare a chart only on an open subset of ``M``:: + + sage: U = M.open_subset('U') + sage: Y. = U.chart(r'z1:\zeta_1 z2:\zeta_2'); Y + Chart (U, (z1, z2)) + sage: Y.domain() + Open subset U of the Complex 2-dimensional topological manifold M + + In the above declaration, we have also specified some LaTeX writing + of the coordinates different from the text one:: + + sage: latex(z1) + {\zeta_1} + + Note the prefix ``r`` in front of the string ``r'z1:\zeta_1 z2:\zeta_2'``; + it makes sure that the backslash character is treated as an ordinary + character, to be passed to the LaTeX interpreter. + + Coordinates are Sage symbolic variables (see + :mod:`sage.symbolic.expression`):: + + sage: type(z1) + + + In addition to the Python variable name provided in the operator ``<.,.>``, + the coordinates are accessible by their indices:: + + sage: Y[0], Y[1] + (z1, z2) + + The index range is that declared during the creation of the manifold. By + default, it starts at 0, but this can be changed via the parameter + ``start_index``:: + + sage: M1 = Manifold(2, 'M_1', field='complex', structure='topological', + ....: start_index=1) + sage: Z. = M1.chart() + sage: Z[1], Z[2] + (u, v) + + The full set of coordinates is obtained by means of the slice + operator ``[:]``:: + + sage: Y[:] + (z1, z2) + + Some partial sets of coordinates:: + + sage: Y[:1] + (z1,) + sage: Y[1:] + (z2,) + + Each constructed chart is automatically added to the manifold's user + atlas:: + + sage: M.atlas() + [Chart (M, (x, y)), Chart (U, (z1, z2))] + + and to the atlas of the chart's domain:: + + sage: U.atlas() + [Chart (U, (z1, z2))] + + Manifold subsets have a *default chart*, which, unless changed via the + method + :meth:`~sage.manifolds.manifold.TopologicalManifold.set_default_chart`, + is the first defined chart on the subset (or on a open subset of it):: + + sage: M.default_chart() + Chart (M, (x, y)) + sage: U.default_chart() + Chart (U, (z1, z2)) + + The default charts are not privileged charts on the manifold, but rather + charts whose name can be skipped in the argument list of functions having + an optional ``chart=`` argument. + + The chart map `\varphi` acting on a point is obtained by passing + it as an input to the map:: + + sage: p = M.point((1+i, 2), chart=X); p + Point on the Complex 2-dimensional topological manifold M + sage: X(p) + (I + 1, 2) + sage: X(p) == p.coord(X) + True + + .. SEEALSO:: + + :class:`sage.manifolds.chart.RealChart` for charts on topological + manifolds over `\RR`. + + """ + def __init__(self, domain, coordinates='', names=None): + r""" + Construct a chart. + + TESTS:: + + sage: M = Manifold(2, 'M', field='complex', structure='topological') + sage: X. = M.chart() + sage: X + Chart (M, (x, y)) + sage: type(X) + + sage: assumptions() # no assumptions on x,y set by X._init_coordinates + [] + sage: TestSuite(X).run() + + """ + from sage.manifolds.manifold import TopologicalManifold + if not isinstance(domain, TopologicalManifold): + raise TypeError("the first argument must be an open subset of " + + "a topological manifold") + if coordinates == '': + for x in names: + coordinates += x + ' ' + coordinates = coordinates[:-1] + self._manifold = domain.manifold() + self._domain = domain + # Treatment of the coordinates: + if ' ' in coordinates: + coord_list = coordinates.split() + else: + coord_list = [coordinates] + if len(coord_list) != self._manifold.dim(): + raise ValueError("the list of coordinates must contain " + + "{} elements".format(self._manifold.dim())) + # The treatment of coordinates is performed by a seperate method, + # _init_coordinates, which sets self._xx and + # which may be redefined for subclasses (for instance RealChart). + self._init_coordinates(coord_list) + coord_string = ' '.join(str(x) for x in self._xx) + if coord_string in self._domain._charts_by_coord: + raise ValueError("the chart with coordinates " + coord_string + + " has already been declared on " + + "the {}".format(self._domain)) + self._domain._charts_by_coord[coord_string] = self + # + # Additional restrictions on the coordinates + self._restrictions = [] # to be set with method add_restrictions() + # + # The chart is added to the domain's atlas, as well as to all the + # atlases of the domain's supersets; moreover the fist defined chart + # is considered as the default chart + for sd in self._domain._supersets: + # the chart is added in the top charts only if its coordinates have + # not been used: + for chart in sd._atlas: + if self._xx == chart._xx: + break + else: + sd._top_charts.append(self) + sd._atlas.append(self) + if sd._def_chart is None: + sd._def_chart = self + # The chart is added to the list of the domain's covering charts: + self._domain._covering_charts.append(self) + # Initialization of the set of charts that are restrictions of the + # current chart to subsets of the chart domain: + self._subcharts = set([self]) + # Initialization of the set of charts which the current chart is a + # restriction of: + self._supercharts = set([self]) + # + self._dom_restrict = {} # dict. of the restrictions of self to + # subsets of self._domain, with the + # subsets as keys + + def _init_coordinates(self, coord_list): + r""" + Initialization of the coordinates as symbolic variables. + + This method must be redefined by derived classes in order to take + into account specificities (e.g. enforcing real coordinates). + + INPUT: + + - ``coord_list`` -- list of coordinate fields, which items in each + field separated by ":"; there are at most 2 items per field: + the coordinate name and the coordinate LaTeX symbol + + TESTS:: + + sage: M = Manifold(2, 'M', field='complex', structure='topological') + sage: X. = M.chart() + sage: X._init_coordinates(['z1', 'z2']) + sage: X + Chart (M, (z1, z2)) + sage: X._init_coordinates([r'z1:\zeta_1', r'z2:\zeta_2']) + sage: X + Chart (M, (z1, z2)) + sage: latex(X) + \left(M,({\zeta_1}, {\zeta_2})\right) + + """ + xx_list = [] # will contain the coordinates as Sage symbolic variables + for coord_field in coord_list: + coord_properties = coord_field.split(':') + coord_symb = coord_properties[0].strip() # the coordinate symbol + # LaTeX symbol: + coord_latex = None + for prop in coord_properties[1:]: + coord_latex = prop.strip() + # Construction of the coordinate as some Sage's symbolic variable: + coord_var = SR.var(coord_symb, latex_name=coord_latex) + xx_list.append(coord_var) + self._xx = tuple(xx_list) + + def _repr_(self): + r""" + String representation of the object. + + TESTS:: + + sage: M = Manifold(2, 'M', field='complex', structure='topological') + sage: X. = M.chart() + sage: X + Chart (M, (x, y)) + + """ + return 'Chart ({}, {})'.format(self._domain._name, self._xx) + + def _latex_(self): + r""" + LaTeX representation of the object. + + TESTS:: + + sage: M = Manifold(2, 'M', field='complex', structure='topological') + sage: X. = M.chart() + sage: X._latex_() + '\\left(M,(x, y)\\right)' + sage: Y. = M.chart(r'z1:\zeta_1 z2:\zeta2') + sage: Y._latex_() + '\\left(M,({\\zeta_1}, {\\zeta2})\\right)' + sage: latex(Y) + \left(M,({\zeta_1}, {\zeta2})\right) + + """ + description = r'\left(' + latex(self._domain).strip() + ',(' + n = len(self._xx) + for i in range(n-1): + description += latex(self._xx[i]).strip() + ', ' + description += latex(self._xx[n-1]).strip() + r')\right)' + return description + + def _first_ngens(self, n): + r""" + Return the list of coordinates. + + This is useful only for the use of Sage preparser:: + + sage: preparse("c_cart. = M.chart()") + "c_cart = M.chart(names=('x', 'y', 'z',)); (x, y, z,) = c_cart._first_ngens(3)" + + """ + return self[:] + + def __getitem__(self, i): + r""" + Access to the coordinates. + + INPUT: + + - ``i`` -- index of the coordinate; if the slice ``[:]``, then all + the coordinates are returned + + OUTPUT: + + - the coordinate of index ``i`` or all the coordinates (as a tuple) + if ``i`` is the slice ``[:]`` + + EXAMPLES:: + + sage: M = Manifold(2, 'M', field='complex', structure='topological') + sage: X. = M.chart() + sage: X[0] + x + sage: X[1] + y + sage: X[:] + (x, y) + + The index range is controlled by the parameter ``start_index``:: + + sage: M = Manifold(2, 'M', field='complex', structure='topological', + ....: start_index=1) + sage: X. = M.chart() + sage: X[1] + x + sage: X[2] + y + sage: X[:] + (x, y) + + We check that slices are properly shifted as well:: + + sage: X[2:] + (y,) + sage: X[:2] + (x,) + """ + if isinstance(i, slice): + start,stop = i.start,i.stop + if start is not None: + start -= self._manifold._sindex + if stop is not None: + stop -= self._manifold._sindex + return self._xx[start:stop:i.step] + else: + return self._xx[i-self._manifold._sindex] + + def __call__(self, point): + r""" + Return the coordinates of a given point. + + INPUT: + + - ``point`` -- point in the domain of the chart + + OUTPUT: + + - tuple of the coordinates of the point + + EXAMPLES:: + + sage: M = Manifold(2, 'M', field='complex', structure='topological') + sage: X. = M.chart() + sage: p = M.point((1+i, 2-i), chart=X) + sage: X(p) + (I + 1, -I + 2) + sage: X(M.an_element()) + (0, 0) + + """ + return point.coord(self) + + def domain(self): + r""" + Return the open subset on which the chart is defined. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: X.domain() + 2-dimensional topological manifold M + sage: U = M.open_subset('U') + sage: Y. = U.chart() + sage: Y.domain() + Open subset U of the 2-dimensional topological manifold M + + """ + return self._domain + + def manifold(self): + r""" + Return the manifold on which the chart is defined. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: X. = U.chart() + sage: X.manifold() + 2-dimensional topological manifold M + sage: X.domain() + Open subset U of the 2-dimensional topological manifold M + + """ + return self._manifold + + def add_restrictions(self, restrictions): + r""" + Add some restrictions on the coordinates. + + INPUT: + + - ``restrictions`` -- list of restrictions on the + coordinates, in addition to the ranges declared by the intervals + specified in the chart constructor + + A restriction can be any symbolic equality or inequality involving + the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items + of the list ``restrictions`` are combined with the ``and`` operator; + if some restrictions are to be combined with the ``or`` operator + instead, they have to be passed as a tuple in some single item + of the list ``restrictions``. For example:: + + restrictions = [x > y, (x != 0, y != 0), z^2 < x] + + means (``x > y``) and ((``x != 0``) or (``y != 0``)) and + (``z^2 < x``). If the list ``restrictions`` contains only one + item, this item can be passed as such, i.e. writing ``x > y`` + instead of the single element list ``[x > y]``. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', field='complex', structure='topological') + sage: X. = M.chart() + sage: X.add_restrictions(abs(x) > 1) + sage: X.valid_coordinates(2+i, 1) + True + sage: X.valid_coordinates(i, 1) + False + + """ + if not isinstance(restrictions, list): + # case of a single condition or conditions to be combined by "or" + restrictions = [restrictions] + self._restrictions.extend(restrictions) + + def restrict(self, subset, restrictions=None): + r""" + Return the restriction of the chart to some open subset of its domain. + + If the current chart is `(U,\varphi)`, a *restriction* (or *subchart*) + is a chart `(V,\psi)` such that `V\subset U` and `\psi = \varphi |_V`. + + If such subchart has not been defined yet, it is constructed here. + + The coordinates of the subchart bare the same names as the coordinates + of the current chart. + + INPUT: + + - ``subset`` -- open subset `V` of the chart domain `U` (must be an + instance of :class:`~sage.manifolds.manifold.TopologicalManifold`) + - ``restrictions`` -- (default: ``None``) list of coordinate + restrictions defining the subset `V` + + A restriction can be any symbolic equality or inequality involving + the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items + of the list ``restrictions`` are combined with the ``and`` operator; + if some restrictions are to be combined with the ``or`` operator + instead, they have to be passed as a tuple in some single item + of the list ``restrictions``. For example:: + + restrictions = [x > y, (x != 0, y != 0), z^2 < x] + + means (``x > y``) and ((``x != 0``) or (``y != 0``)) and + (``z^2 < x``). If the list ``restrictions`` contains only one + item, this item can be passed as such, i.e. writing ``x > y`` + instead of the single element list ``[x > y]``. + + OUTPUT: + + - chart `(V,\psi)`, as an instance of :class:`Chart`. + + EXAMPLES: + + Coordinates on the unit open ball of `\CC^2` as a subchart + of the global coordinates of `\CC^2`:: + + sage: M = Manifold(2, 'C^2', field='complex', structure='topological') + sage: X. = M.chart() + sage: B = M.open_subset('B') + sage: X_B = X.restrict(B, abs(z1)^2 + abs(z2)^2 < 1); X_B + Chart (B, (z1, z2)) + + """ + if subset == self._domain: + return self + if subset not in self._dom_restrict: + if not subset.is_subset(self._domain): + raise ValueError("the specified subset is not a subset " + + "of the domain of definition of the chart") + coordinates = "" + for coord in self._xx: + coordinates += repr(coord) + ' ' + res = type(self)(subset, coordinates) + res._restrictions.extend(self._restrictions) + # The coordinate restrictions are added to the result chart and + # possibly transformed into coordinate bounds: + if restrictions is not None: + res.add_restrictions(restrictions) + # Update of supercharts and subcharts: + res._supercharts.update(self._supercharts) + for schart in self._supercharts: + schart._subcharts.add(res) + schart._dom_restrict[subset] = res + # Update of domain restrictions: + self._dom_restrict[subset] = res + return self._dom_restrict[subset] + + def valid_coordinates(self, *coordinates, **kwds): + r""" + Check whether a tuple of coordinates can be the coordinates of a + point in the chart domain. + + INPUT: + + - ``*coordinates`` -- coordinate values + - ``**kwds`` -- options: + + - ``parameters=None``, dictionary to set numerical values to + some parameters (see example below) + + OUTPUT: + + - ``True`` if the coordinate values are admissible in the chart + image, ``False`` otherwise + + EXAMPLES:: + + sage: M = Manifold(2, 'M', field='complex', structure='topological') + sage: X. = M.chart() + sage: X.add_restrictions([abs(x)<1, y!=0]) + sage: X.valid_coordinates(0, i) + True + sage: X.valid_coordinates(i, 1) + False + sage: X.valid_coordinates(i/2, 1) + True + sage: X.valid_coordinates(i/2, 0) + False + sage: X.valid_coordinates(2, 0) + False + + Example of use with the keyword ``parameters`` to set a specific value + to a parameter appearing in the coordinate restrictions:: + + sage: var('a') # the parameter is a symbolic variable + a + sage: Y. = M.chart() + sage: Y.add_restrictions(abs(v) y`` or ``x^2 + y^2 != 0``. The items + of the list ``restrictions`` are combined with the ``and`` operator; + if some restrictions are to be combined with the ``or`` operator + instead, they have to be passed as a tuple in some single item + of the list ``restrictions``. For example:: + + restrictions = [x > y, (x != 0, y != 0), z^2 < x] + + means (``x > y``) and ((``x != 0``) or (``y != 0``)) and + (``z^2 < x``). If the list ``restrictions`` contains only one + item, this item can be passed as such, i.e. writing ``x > y`` + instead of the single element list ``[x > y]``. + + OUTPUT: + + - the transition map `\psi \circ \varphi^{-1}` defined on + `U \cap V`, as an instance of :class:`CoordChange` + + EXAMPLES: + + Transition map between two stereographic charts on the circle `S^1`:: + + sage: M = Manifold(1, 'S^1', structure='topological') + sage: U = M.open_subset('U') # Complement of the North pole + sage: cU. = U.chart() # Stereographic chart from the North pole + sage: V = M.open_subset('V') # Complement of the South pole + sage: cV. = V.chart() # Stereographic chart from the South pole + sage: M.declare_union(U,V) # S^1 is the union of U and V + sage: trans = cU.transition_map(cV, 1/x, intersection_name='W', + ....: restrictions1= x!=0, restrictions2 = y!=0) + sage: trans + Change of coordinates from Chart (W, (x,)) to Chart (W, (y,)) + sage: trans.display() + y = 1/x + + The subset `W`, intersection of `U` and `V`, has been created by + ``transition_map()``:: + + sage: M.list_of_subsets() + [1-dimensional topological manifold S^1, + Open subset U of the 1-dimensional topological manifold S^1, + Open subset V of the 1-dimensional topological manifold S^1, + Open subset W of the 1-dimensional topological manifold S^1] + sage: W = M.list_of_subsets()[3] + sage: W is U.intersection(V) + True + sage: M.atlas() + [Chart (U, (x,)), Chart (V, (y,)), Chart (W, (x,)), Chart (W, (y,))] + + Transition map between the spherical chart and the Cartesian + one on `\RR^2`:: + + sage: M = Manifold(2, 'R^2', structure='topological') + sage: c_cart. = M.chart() + sage: U = M.open_subset('U') # the complement of the half line {y=0, x >= 0} + sage: c_spher. = U.chart(r'r:(0,+oo) phi:(0,2*pi):\phi') + sage: trans = c_spher.transition_map(c_cart, (r*cos(phi), r*sin(phi)), + ....: restrictions2=(y!=0, x<0)) + sage: trans + Change of coordinates from Chart (U, (r, phi)) to Chart (U, (x, y)) + sage: trans.display() + x = r*cos(phi) + y = r*sin(phi) + + In this case, no new subset has been created since `U \cap M = U`:: + + sage: M.list_of_subsets() + [2-dimensional topological manifold R^2, + Open subset U of the 2-dimensional topological manifold R^2] + + but a new chart has been created: `(U, (x, y))`:: + + sage: M.atlas() + [Chart (R^2, (x, y)), Chart (U, (r, phi)), Chart (U, (x, y))] + + """ + dom1 = self._domain + dom2 = other._domain + dom = dom1.intersection(dom2, name=intersection_name) + if dom is dom1: + chart1 = self + else: + chart1 = self.restrict(dom, restrictions1) + if dom is dom2: + chart2 = other + else: + chart2 = other.restrict(dom, restrictions2) + if not isinstance(transformations, (tuple, list)): + transformations = [transformations] + return CoordChange(chart1, chart2, *transformations) + +#***************************************************************************** + +class RealChart(Chart): + r""" + Chart on a topological manifold over `\RR`. + + Given a topological manifold `M` of dimension `n` over `\RR`, a *chart* + on `M` is a pair `(U,\varphi)`, where `U` is an open subset of `M` and + `\varphi : U \to V \subset \RR^n` is a homeomorphism from `U` to + an open subset `V` of `\RR^n`. + + The components `(x^1, \ldots, x^n)` of `\varphi`, defined by + `\varphi(p) = (x^1(p), \ldots, x^n(p))\in \RR^n` for any point + `p \in U`, are called the *coordinates* of the chart `(U, \varphi)`. + + INPUT: + + - ``domain`` -- open subset `U` on which the chart is defined + - ``coordinates`` -- (default: ``''`` (empty string)) string defining + the coordinate symbols and ranges, see below + - ``names`` -- (default: ``None``) unused argument, except if + ``coordinates`` is not provided; it must then be a tuple containing + the coordinate symbols (this is guaranteed if the shortcut operator + ``<,>`` is used) + + The string ``coordinates`` has the space ``' '`` as a separator and each + item has at most three fields, separated by a colon (``:``): + + 1. The coordinate symbol (a letter or a few letters). + 2. (optional) The interval `I` defining the coordinate range: if not + provided, the coordinate is assumed to span all `\RR`; otherwise + `I` must be provided in the form ``(a,b)`` (or equivalently + ``]a,b[``). The bounds ``a`` and ``b`` can be ``+/-Infinity``, + ``Inf``, ``infinity``, ``inf`` or ``oo``. + For *singular* coordinates, non-open intervals such as ``[a,b]`` and + ``(a,b]`` (or equivalently ``]a,b]``) are allowed. + Note that the interval declaration must not contain any whitespace. + 3. (optional) The LaTeX spelling of the coordinate; if not provided the + coordinate symbol given in the first field will be used. + + The order of the fields 2 and 3 does not matter and each of them can be + omitted. If it contains any LaTeX expression, the string ``coordinates`` + must be declared with the prefix 'r' (for "raw") to allow for a proper + treatment of LaTeX backslash characters (see examples below). If no + interval range and no LaTeX spelling is to be set for any coordinate, + the argument ``coordinates`` can be omitted when the shortcut + operator ``<,>`` is used via Sage preparser (see examples below). + + EXAMPLES: + + Cartesian coordinates on `\RR^3`:: + + sage: M = Manifold(3, 'R^3', r'\RR^3', structure='topological', + ....: start_index=1) + sage: c_cart = M.chart('x y z'); c_cart + Chart (R^3, (x, y, z)) + sage: type(c_cart) + + + To have the coordinates accessible as global variables, one has to set:: + + sage: (x,y,z) = c_cart[:] + + However, a shortcut is to use the declarator ```` in the left-hand + side of the chart declaration (there is then no need to pass the string + ``'x y z'`` to ``chart()``):: + + sage: M = Manifold(3, 'R^3', r'\RR^3', structure='topological', + ....: start_index=1) + sage: c_cart. = M.chart(); c_cart + Chart (R^3, (x, y, z)) + + The coordinates are then immediately accessible:: + + sage: y + y + sage: y is c_cart[2] + True + + Note that ``x, y, z`` declared in ```` are mere Python variable + names and do not have to coincide with the coordinate symbols; for + instance, one may write:: + + sage: M = Manifold(3, 'R^3', r'\RR^3', structure='topological', start_index=1) + sage: c_cart. = M.chart('x y z'); c_cart + Chart (R^3, (x, y, z)) + + Then ``y`` is not known as a global variable and the coordinate `y` + is accessible only through the global variable ``y1``:: + + sage: y1 + y + sage: y1 is c_cart[2] + True + + However, having the name of the Python variable coincide with the + coordinate symbol is quite convenient; so it is recommended to declare:: + + sage: forget() # for doctests only + sage: M = Manifold(3, 'R^3', r'\RR^3', structure='topological', start_index=1) + sage: c_cart. = M.chart() + + Spherical coordinates on the subset `U` of `\RR^3` that is the + complement of the half-plane `\{y=0, x \geq 0\}`:: + + sage: U = M.open_subset('U') + sage: c_spher. = U.chart(r'r:(0,+oo) th:(0,pi):\theta ph:(0,2*pi):\phi') + sage: c_spher + Chart (U, (r, th, ph)) + + Note the prefix 'r' for the string defining the coordinates in the + arguments of ``chart``. + + Coordinates are Sage symbolic variables (see + :mod:`sage.symbolic.expression`):: + + sage: type(th) + + sage: latex(th) + {\theta} + sage: assumptions(th) + [th is real, th > 0, th < pi] + + Coordinate are also accessible by their indices:: + + sage: x1 = c_spher[1]; x2 = c_spher[2]; x3 = c_spher[3] + sage: print x1, x2, x3 + r th ph + sage: (x1, x2, x3) == (r, th, ph) + True + + The full set of coordinates is obtained by means of the slice ``[:]``:: + + sage: c_cart[:] + (x, y, z) + sage: c_spher[:] + (r, th, ph) + + Let us check that the declared coordinate ranges have been taken into + account:: + + sage: c_cart.coord_range() + x: (-oo, +oo); y: (-oo, +oo); z: (-oo, +oo) + sage: c_spher.coord_range() + r: (0, +oo); th: (0, pi); ph: (0, 2*pi) + sage: bool(th>0 and th 0, th is real, + th > 0, th < pi, ph is real, ph > 0, ph < 2*pi] + + The coordinate ranges are used for simplifications:: + + sage: simplify(abs(r)) # r has been declared to lie in the interval (0,+oo) + r + sage: simplify(abs(x)) # no positive range has been declared for x + abs(x) + + Each constructed chart is automatically added to the manifold's + user atlas:: + + sage: M.atlas() + [Chart (R^3, (x, y, z)), Chart (U, (r, th, ph))] + + and to the atlas of its domain:: + + sage: U.atlas() + [Chart (U, (r, th, ph))] + + Manifold subsets have a *default chart*, which, unless changed + via the method + :meth:`~sage.manifolds.manifold.TopologicalManifold.set_default_chart`, + is the first defined chart on the subset (or on a open subset of it):: + + sage: M.default_chart() + Chart (R^3, (x, y, z)) + sage: U.default_chart() + Chart (U, (r, th, ph)) + + The default charts are not privileged charts on the manifold, but rather + charts whose name can be skipped in the argument list of functions having + an optional ``chart=`` argument. + + The chart map `\varphi` acting on a point is obtained by means of the + call operator, i.e. the operator ``()``:: + + sage: p = M.point((1,0,-2)); p + Point on the 3-dimensional topological manifold R^3 + sage: c_cart(p) + (1, 0, -2) + sage: c_cart(p) == p.coord(c_cart) + True + sage: q = M.point((2,pi/2,pi/3), chart=c_spher) # point defined by its spherical coordinates + sage: c_spher(q) + (2, 1/2*pi, 1/3*pi) + sage: c_spher(q) == q.coord(c_spher) + True + sage: a = U.point((1,pi/2,pi)) # the default coordinates on U are the spherical ones + sage: c_spher(a) + (1, 1/2*pi, pi) + sage: c_spher(a) == a.coord(c_spher) + True + + Cartesian coordinates on `U` as an example of chart construction with + coordinate restrictions: since `U` is the complement of the half-plane + `\{y = 0, x \geq 0\}`, we must have `y \neq 0` or `x < 0` on U. + Accordingly, we set:: + + sage: c_cartU. = U.chart() + sage: c_cartU.add_restrictions((y!=0, x<0)) + sage: U.atlas() + [Chart (U, (r, th, ph)), Chart (U, (x, y, z))] + sage: M.atlas() + [Chart (R^3, (x, y, z)), Chart (U, (r, th, ph)), Chart (U, (x, y, z))] + sage: c_cartU.valid_coordinates(-1,0,2) + True + sage: c_cartU.valid_coordinates(1,0,2) + False + sage: c_cart.valid_coordinates(1,0,2) + True + + Note that, as an example, the following would have meant `y \neq 0` + *and* `x < 0`:: + + c_cartU.add_restrictions([y!=0, x<0]) + """ + def __init__(self, domain, coordinates='', names=None): + r""" + Construct a chart on a real topological manifold. + + TESTS:: + + sage: forget() # for doctests only + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: X + Chart (M, (x, y)) + sage: type(X) + + sage: assumptions() # assumptions set in X._init_coordinates + [x is real, y is real] + sage: TestSuite(X).run() + + """ + Chart.__init__(self, domain, coordinates=coordinates, names=names) + + def _init_coordinates(self, coord_list): + r""" + Initialization of the coordinates as symbolic variables. + + This method must be redefined by derived classes in order to take + into account specificities (e.g. enforcing real coordinates). + + INPUT: + + - ``coord_list`` -- list of coordinate fields, which items in each + field separated by ":"; there are at most 3 items per field: + the coordinate name, the coordinate LaTeX symbol and the + coordinate range + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: X._init_coordinates(['x', 'y']) + sage: X + Chart (M, (x, y)) + sage: latex(X) + \left(M,(x, y)\right) + sage: X.coord_range() + x: (-oo, +oo); y: (-oo, +oo) + sage: X._init_coordinates([r'x1:\xi:(0,1)', r'y1:\eta']) + sage: X + Chart (M, (x1, y1)) + sage: latex(X) + \left(M,({\xi}, {\eta})\right) + sage: X.coord_range() + x1: (0, 1); y1: (-oo, +oo) + + """ + from sage.symbolic.assumptions import assume + xx_list = [] # will contain the coordinates as Sage symbolic variables + bounds_list = [] # will contain the coordinate bounds + for coord_field in coord_list: + coord_properties = coord_field.split(':') + coord_symb = coord_properties[0].strip() # the coordinate symbol + # default values, possibly redefined below: + coord_latex = None + xmin = -Infinity; xmin_included = False + xmax = +Infinity; xmax_included = False + # scan of the properties other than the symbol: + for prop in coord_properties[1:]: + prop1 = prop.strip() + delim_min = prop1[0] + if delim_min in ['[', ']', '(']: + # prop1 is the coordinate's range + xmin_str, xmax_str = prop1[1:len(prop1)-1].split(',') + if xmin_str not in ['-inf', '-Inf', '-infinity', + '-Infinity', '-oo']: + xmin = SR(xmin_str) + xmin_included = ( delim_min == '[' ) + if xmax_str not in ['inf', '+inf', 'Inf', '+Inf', + 'infinity', '+infinity', 'Infinity', + '+Infinity', 'oo', '+oo']: + xmax = SR(xmax_str) + xmax_included = ( prop1[-1] == ']' ) + else: + # prop1 is the coordinate's LaTeX symbol + coord_latex = prop1 + # Construction of the coordinate as some Sage's symbolic variable: + coord_var = SR.var(coord_symb, domain='real', + latex_name=coord_latex) + assume(coord_var, 'real') + if xmin != -Infinity: + if xmin_included: + assume(coord_var >= xmin) + else: + assume(coord_var > xmin) + if xmax != Infinity: + if xmax_included: + assume(coord_var <= xmax) + else: + assume(coord_var < xmax) + xx_list.append(coord_var) + bounds_list.append(((xmin, xmin_included), (xmax, xmax_included))) + self._xx = tuple(xx_list) + self._bounds = tuple(bounds_list) + + def coord_bounds(self, i=None): + r""" + Return the lower and upper bounds of the range of a coordinate. + + For a nicely formatted output, use :meth:`coord_range` instead. + + INPUT: + + - ``i`` -- (default: ``None``) index of the coordinate; if ``None``, + the bounds of all the coordinates are returned + + OUTPUT: + + - the coordinate bounds as the tuple + ``((xmin, min_included), (xmax, max_included))`` where + + - ``xmin`` is the coordinate lower bound + - ``min_included`` is a boolean, indicating whether the coordinate + can take the value ``xmin``, i.e. ``xmin`` is a strict lower + bound iff ``min_included`` is ``False`` + - ``xmin`` is the coordinate upper bound + - ``max_included`` is a boolean, indicating whether the coordinate + can take the value ``xmax``, i.e. ``xmax`` is a strict upper + bound iff ``max_included`` is ``False`` + + EXAMPLES: + + Some coordinate bounds on a 2-dimensional manifold:: + + sage: forget() # for doctests only + sage: M = Manifold(2, 'M', structure='topological') + sage: c_xy. = M.chart('x y:[0,1)') + sage: c_xy.coord_bounds(0) # x in (-oo,+oo) (the default) + ((-Infinity, False), (+Infinity, False)) + sage: c_xy.coord_bounds(1) # y in [0,1) + ((0, True), (1, False)) + sage: c_xy.coord_bounds() + (((-Infinity, False), (+Infinity, False)), ((0, True), (1, False))) + sage: c_xy.coord_bounds() == (c_xy.coord_bounds(0), c_xy.coord_bounds(1)) + True + + The coordinate bounds can also be recovered via the method + :meth:`coord_range`:: + + sage: c_xy.coord_range() + x: (-oo, +oo); y: [0, 1) + sage: c_xy.coord_range(y) + y: [0, 1) + + or via Sage's function + :func:`sage.symbolic.assumptions.assumptions`:: + + sage: assumptions(x) + [x is real] + sage: assumptions(y) + [y is real, y >= 0, y < 1] + + """ + if i is None: + return self._bounds + else: + return self._bounds[i-self._manifold._sindex] + + def coord_range(self, xx=None): + r""" + Display the range of a coordinate (or all coordinates), as an + interval. + + INPUT: + + - ``xx`` -- (default: ``None``) symbolic expression corresponding + to a coordinate of the current chart; if ``None``, the ranges of + all coordinates are displayed + + EXAMPLES: + + Ranges of coordinates on a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: X.coord_range() + x: (-oo, +oo); y: (-oo, +oo) + sage: X.coord_range(x) + x: (-oo, +oo) + sage: U = M.open_subset('U', coord_def={X: [x>1, y y`` or ``x^2 + y^2 != 0``. The items + of the list ``restrictions`` are combined with the ``and`` operator; + if some restrictions are to be combined with the ``or`` operator + instead, they have to be passed as a tuple in some single item + of the list ``restrictions``. For example:: + + restrictions = [x > y, (x != 0, y != 0), z^2 < x] + + means (``x > y``) and ((``x != 0``) or (``y != 0``)) and + (``z^2 < x``). If the list ``restrictions`` contains only one + item, this item can be passed as such, i.e. writing ``x > y`` + instead of the single element list ``[x > y]``. + + EXAMPLES: + + Cartesian coordinates on the open unit disc in `\RR^2`:: + + sage: M = Manifold(2, 'M', structure='topological') # the open unit disc + sage: X. = M.chart() + sage: X.add_restrictions(x^2+y^2<1) + sage: X.valid_coordinates(0,2) + False + sage: X.valid_coordinates(0,1/3) + True + + The restrictions are transmitted to subcharts:: + + sage: A = M.open_subset('A') # annulus 1/2 < r < 1 + sage: X_A = X.restrict(A, x^2+y^2 > 1/4) + sage: X_A._restrictions + [x^2 + y^2 < 1, x^2 + y^2 > (1/4)] + sage: X_A.valid_coordinates(0,1/3) + False + sage: X_A.valid_coordinates(2/3,1/3) + True + + If appropriate, the restrictions are transformed into bounds on + the coordinate ranges:: + + sage: U = M.open_subset('U') + sage: X_U = X.restrict(U) + sage: X_U.coord_range() + x: (-oo, +oo); y: (-oo, +oo) + sage: X_U.add_restrictions([x<0, y>1/2]) + sage: X_U.coord_range() + x: (-oo, 0); y: (1/2, +oo) + + """ + import operator + if not isinstance(restrictions, list): + # case of a single condition or conditions to be combined by "or" + restrictions = [restrictions] + self._restrictions.extend(restrictions) + # Update of the coordinate bounds from the restrictions: + bounds = list(self._bounds) # convert to a list for modifications + new_restrictions = [] + for restrict in self._restrictions: + restrict_used = False # determines whether restrict is used + # to set some coordinate bound + if not isinstance(restrict, tuple): # case of 'or' conditions + # excluded + operands = restrict.operands() + left = operands[0] + right = operands[1] + right_var = right.variables() + if left in self._xx: + # the l.h.s. of the restriction is a single + # coordinate + right_coord = [coord for coord in self._xx + if coord in right_var] + if not right_coord: + # there is no other coordinate in the r.h.s. + ind = self._xx.index(left) + left_bounds = list(bounds[ind]) + oper = restrict.operator() + oinf = left_bounds[0][0] # old coord inf + osup = left_bounds[1][0] # old coord sup + if oper == operator.lt: + if osup == Infinity or right <= osup: + left_bounds[1] = (right, False) + restrict_used = True + elif oper == operator.le: + if osup == Infinity or right < osup: + left_bounds[1] = (right, True) + restrict_used = True + elif oper == operator.gt: + if oinf == -Infinity or right >= oinf: + left_bounds[0] = (right, False) + restrict_used = True + elif oper == operator.ge: + if oinf == -Infinity or right > oinf: + left_bounds[0] = (right, True) + restrict_used = True + bounds[ind] = tuple(left_bounds) + if not restrict_used: + # if restrict has not been used to set a coordinate bound + # it is maintained in the list of restrictions: + new_restrictions.append(restrict) + self._bounds = tuple(bounds) + self._restrictions = new_restrictions + + def restrict(self, subset, restrictions=None): + r""" + Return the restriction of the chart to some open subset of its domain. + + If the current chart is `(U,\varphi)`, a *restriction* (or *subchart*) + is a chart `(V,\psi)` such that `V\subset U` and `\psi = \varphi |_V`. + + If such subchart has not been defined yet, it is constructed here. + + The coordinates of the subchart bare the same names as the coordinates + of the current chart. + + INPUT: + + - ``subset`` -- open subset `V` of the chart domain `U` (must be an + instance of :class:`~sage.manifolds.manifold.TopologicalManifold`) + - ``restrictions`` -- (default: ``None``) list of coordinate + restrictions defining the subset `V` + + A restriction can be any symbolic equality or inequality involving + the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items + of the list ``restrictions`` are combined with the ``and`` operator; + if some restrictions are to be combined with the ``or`` operator + instead, they have to be passed as a tuple in some single item + of the list ``restrictions``. For example:: + + restrictions = [x > y, (x != 0, y != 0), z^2 < x] + + means (``x > y``) and ((``x != 0``) or (``y != 0``)) and + (``z^2 < x``). If the list ``restrictions`` contains only one + item, this item can be passed as such, i.e. writing ``x > y`` + instead of the single element list ``[x > y]``. + + OUTPUT: + + - chart `(V,\psi)`, as an instance of :class:`RealChart`. + + EXAMPLES: + + Cartesian coordinates on the unit open disc in `\RR^2` as a subchart + of the global Cartesian coordinates:: + + sage: M = Manifold(2, 'R^2', structure='topological') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: D = M.open_subset('D') # the unit open disc + sage: c_cart_D = c_cart.restrict(D, x^2+y^2<1) + sage: p = M.point((1/2, 0)) + sage: p in D + True + sage: q = M.point((1, 2)) + sage: q in D + False + + Cartesian coordinates on the annulus `1 < \sqrt{x^2+y^2} < 2`:: + + sage: A = M.open_subset('A') + sage: c_cart_A = c_cart.restrict(A, [x^2+y^2>1, x^2+y^2<4]) + sage: p in A, q in A + (False, False) + sage: a = M.point((3/2,0)) + sage: a in A + True + + """ + if subset == self._domain: + return self + if subset not in self._dom_restrict: + if not subset.is_subset(self._domain): + raise ValueError("the specified subset is not a subset " + + "of the domain of definition of the chart") + coordinates = "" + for coord in self._xx: + coordinates += repr(coord) + ' ' + res = type(self)(subset, coordinates) + res._bounds = self._bounds + res._restrictions.extend(self._restrictions) + # The coordinate restrictions are added to the result chart and + # possibly transformed into coordinate bounds: + if restrictions is not None: + res.add_restrictions(restrictions) + # Update of supercharts and subcharts: + res._supercharts.update(self._supercharts) + for schart in self._supercharts: + schart._subcharts.add(res) + schart._dom_restrict[subset] = res + # Update of domain restrictions: + self._dom_restrict[subset] = res + return self._dom_restrict[subset] + + def valid_coordinates(self, *coordinates, **kwds): + r""" + Check whether a tuple of coordinates can be the coordinates of a + point in the chart domain. + + INPUT: + + - ``*coordinates`` -- coordinate values + - ``**kwds`` -- options: + + - ``tolerance=0``, to set the absolute tolerance in the test of + coordinate ranges + - ``parameters=None``, to set some numerical values to parameters + + + OUTPUT: + + - ``True`` if the coordinate values are admissible in the chart range + and ``False`` otherwise + + EXAMPLES: + + Cartesian coordinates on a square interior:: + + sage: forget() # for doctest only + sage: M = Manifold(2, 'M', structure='topological') # the square interior + sage: X. = M.chart('x:(-2,2) y:(-2,2)') + sage: X.valid_coordinates(0,1) + True + sage: X.valid_coordinates(-3/2,5/4) + True + sage: X.valid_coordinates(0,3) + False + + The unit open disk inside the square:: + + sage: D = M.open_subset('D', coord_def={X: x^2+y^2<1}) + sage: XD = X.restrict(D) + sage: XD.valid_coordinates(0,1) + False + sage: XD.valid_coordinates(-3/2,5/4) + False + sage: XD.valid_coordinates(-1/2,1/2) + True + sage: XD.valid_coordinates(0,0) + True + + """ + n = len(coordinates) + if n != self._manifold._dim: + return False + if 'tolerance' in kwds: + tolerance = kwds['tolerance'] + else: + tolerance = 0 + if 'parameters' in kwds: + parameters = kwds['parameters'] + else: + parameters = None + # Check of the coordinate ranges: + for x, bounds in zip(coordinates, self._bounds): + xmin = bounds[0][0] - tolerance + min_included = bounds[0][1] + xmax = bounds[1][0] + tolerance + max_included = bounds[1][1] + if parameters: + xmin = xmin.subs(parameters) + xmax = xmax.subs(parameters) + if min_included: + if x < xmin: + return False + else: + if x <= xmin: + return False + if max_included: + if x > xmax: + return False + else: + if x >= xmax: + return False + # Check of additional restrictions: + if self._restrictions != []: + substitutions = dict(zip(self._xx, coordinates)) + if parameters: + substitutions.update(parameters) + for restrict in self._restrictions: + if isinstance(restrict, tuple): # case of or conditions + combine = False + for expr in restrict: + combine = combine or bool(expr.subs(substitutions)) + if not combine: + return False + else: + if not bool(restrict.subs(substitutions)): + return False + # All tests have been passed: + return True + + +#***************************************************************************** + +class CoordChange(SageObject): + r""" + Transition map between two charts of a topological manifold. + + Giving two coordinate charts `(U, \varphi)` and `(V, \psi)` on a + topological manifold `M` of dimension `n` over a topological field `K`, + the *transition map from* `(U, \varphi)` *to* `(V, \psi)` is the map + + .. MATH:: + + \psi\circ\varphi^{-1}: \varphi(U\cap V) \subset K^n + \rightarrow \psi(U\cap V) \subset K^n. + + In other words, the transition map `\psi \circ \varphi^{-1}` expresses + the coordinates `(y^1, \ldots, y^n)` of `(V, \psi)` in terms of the + coordinates `(x^1, \ldots, x^n)` of `(U, \varphi)` on the open subset + where the two charts intersect, i.e. on `U \cap V`. + + INPUT: + + - ``chart1`` -- chart `(U, \varphi)` + - ``chart2`` -- chart `(V, \psi)` + - ``transformations`` -- tuple (or list) `(Y_1, \ldots, Y_2)`, where + `Y_i` is the symbolic expression of the coordinate `y^i` in terms + of the coordinates `(x^1, \ldots, x^n)` + + EXAMPLES: + + Transition map on a 2-dimensional topological manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: Y. = M.chart() + sage: X_to_Y = X.transition_map(Y, [x+y, x-y]) + sage: X_to_Y + Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v)) + sage: type(X_to_Y) + + sage: X_to_Y.display() + u = x + y + v = x - y + + """ + def __init__(self, chart1, chart2, *transformations): + r""" + Construct a transition map. + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: Y. = M.chart() + sage: X_to_Y = X.transition_map(Y, [x+y, x-y]) + sage: X_to_Y + Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v)) + sage: type(X_to_Y) + + sage: TestSuite(X_to_Y).run() + + """ + self._n1 = len(chart1._xx) + self._n2 = len(chart2._xx) + if len(transformations) != self._n2: + raise ValueError("{} coordinate transformations ".format(self._n2) + + "must be provided") + self._chart1 = chart1 + self._chart2 = chart2 + #*# when MultiCoordFunction will be implemented (trac #18640): + # self._transf = chart1.multifunction(*transformations) + #*# for now: + self._transf = transformations + self._inverse = None + # If the two charts are on the same open subset, the coordinate change + # is added to the subset (and supersets) dictionary: + if chart1._domain == chart2._domain: + domain = chart1._domain + for sdom in domain._supersets: + sdom._coord_changes[(chart1, chart2)] = self + + def _repr_(self): + r""" + String representation of the transition map. + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: Y. = M.chart() + sage: X_to_Y = X.transition_map(Y, [x+y, x-y]) + sage: X_to_Y._repr_() + 'Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))' + sage: repr(X_to_Y) # indirect doctest + 'Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))' + sage: X_to_Y # indirect doctest + Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v)) + + """ + return "Change of coordinates from {} to {}".format(self._chart1, + self._chart2) + + def _latex_(self): + r""" + LaTeX representation of the transition map. + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: Y. = M.chart() + sage: X_to_Y = X.transition_map(Y, [x+y, x-y]) + sage: X_to_Y._latex_() + \left(M,(x, y)\right) \rightarrow \left(M,(u, v)\right) + sage: latex(X_to_Y) # indirect doctest + \left(M,(x, y)\right) \rightarrow \left(M,(u, v)\right) + + """ + return latex(self._chart1) + r' \rightarrow ' + latex(self._chart2) + + def __eq__(self, other): + r""" + Equality operator. + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: Y. = M.chart() + sage: X_to_Y = X.transition_map(Y, [x+y, x-y]) + sage: X_to_Y == X_to_Y + True + sage: X_to_Y1 = X.transition_map(Y, [x+y, x-y]) + sage: X_to_Y == X_to_Y1 + True + sage: X_to_Y2 = X.transition_map(Y, [2*y, -x]) + sage: X_to_Y == X_to_Y2 + False + sage: Z. = M.chart() + sage: X_to_Z = X.transition_map(Z, [x+y, x-y]) + sage: X_to_Y == X_to_Z + False + + """ + if other is self: + return True + if not isinstance(other, CoordChange): + return False + return ((self._chart1 == other._chart1) + and (self._chart2 == other._chart2) + and (self._transf == other._transf)) + + def __ne__(self, other): + r""" + Unequality operator. + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: Y. = M.chart() + sage: X_to_Y = X.transition_map(Y, [x+y, x-y]) + sage: X_to_Y2 = X.transition_map(Y, [2*y, -x]) + sage: X_to_Y != X_to_Y2 + True + + """ + return not (self == other) + + def __call__(self, *coords): + r""" + Compute the new coordinates from old ones. + + INPUT: + + - ``coords`` -- values of coordinates of ``chart1`` + + OUTPUT: + + - tuple of values of coordinates of ``chart2`` + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: Y. = M.chart() + sage: X_to_Y = X.transition_map(Y, [x+y, x-y]) + sage: X_to_Y(1,2) + (3, -1) + + """ + #*# When MultiCoordFunction is implemented (trac #18640): + # return self._transf(*coords) + #*# for now: + substitutions = {self._chart1._xx[j]: coords[j] for j in range(self._n1)} + return tuple([self._transf[i].subs(substitutions).simplify_full() + for i in range(self._n2)]) + + def inverse(self): + r""" + Compute the inverse coordinate transformation. + + OUTPUT: + + - an instance of :class:`CoordChange` representing the inverse of + the current coordinate transformation + + EXAMPLES: + + Inverse of a coordinate transformation corresponding to a + `\pi/3`-rotation in the plane:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: c_xy. = M.chart() + sage: c_uv. = M.chart() + sage: xy_to_uv = c_xy.transition_map(c_uv, ((x - sqrt(3)*y)/2, (sqrt(3)*x + y)/2)) + sage: M.coord_changes() + {(Chart (M, (x, y)), + Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))} + sage: uv_to_xy = xy_to_uv.inverse(); uv_to_xy + Change of coordinates from Chart (M, (u, v)) to Chart (M, (x, y)) + sage: uv_to_xy.display() + x = 1/2*sqrt(3)*v + 1/2*u + y = -1/2*sqrt(3)*u + 1/2*v + sage: M.coord_changes() # random (dictionary output) + {(Chart (M, (u, v)), + Chart (M, (x, y))): Change of coordinates from Chart (M, (u, v)) to Chart (M, (x, y)), + (Chart (M, (x, y)), + Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))} + + """ + from sage.symbolic.relation import solve + if self._inverse is not None: + return self._inverse + # The computation is necessary: + x1 = self._chart1._xx # list of coordinates in chart1 + x2 = self._chart2._xx # list of coordinates in chart2 + n1 = self._n1 + n2 = self._n2 + if n1 != n2: + raise ValueError("the change of coordinates is not invertible " + + "(different number of coordinates in the two " + + "charts)") + # New symbolic variables (different from x2 to allow for a + # correct solution even when chart2 = chart1): + base_field = self._chart1.domain().base_field_type() + if base_field == 'real': + coord_domain = ['real' for i in range(n2)] + elif base_field == 'complex': + coord_domain = ['complex' for i in range(n2)] + else: + coord_domain = [None for i in range(n2)] + for i in range(n2): + if x2[i].is_positive(): + coord_domain[i] = 'positive' + xp2 = [ SR.var('xxxx' + str(i), domain=coord_domain[i]) + for i in range(n2) ] + #*# when MultiCoordFunction will be implemented (trac #18640): + # xx2 = self._transf.expr() + #*# for now: + xx2 = self._transf + equations = [xp2[i] == xx2[i] for i in range(n2)] + try: + solutions = solve(equations, *x1, solution_dict=True) + except RuntimeError: + raise RuntimeError("the system could not be solved; use " + + "set_inverse() to set the inverse manually") + substitutions = dict(zip(xp2, x2)) + if len(solutions) == 1: + x2_to_x1 = [solutions[0][x1[i]].subs(substitutions) + for i in range(n1)] + else: + list_x2_to_x1 = [] + for sol in solutions: + if x2[0] in sol: + raise ValueError("the system could not be solved; use " + + "set_inverse() to set the inverse " + + "manually") + x2_to_x1 = [sol[x1[i]].subs(substitutions) for i in range(n1)] + if self._chart1.valid_coordinates(*x2_to_x1): + list_x2_to_x1.append(x2_to_x1) + if len(list_x2_to_x1) == 0: + raise ValueError("no solution found; use set_inverse() to " + + "set the inverse manually") + if len(list_x2_to_x1) > 1: + print "Multiple solutions found: " + print list_x2_to_x1 + raise ValueError( + "non-unique solution to the inverse coordinate " + + "transformation; use set_inverse() to set the inverse " + + "manually") + x2_to_x1 = list_x2_to_x1[0] + self._inverse = type(self)(self._chart2, self._chart1, *x2_to_x1) + return self._inverse + + def set_inverse(self, *transformations, **kwds): + r""" + Sets the inverse of the coordinate transformation. + + This is useful when the automatic computation via :meth:`inverse()` + fails. + + INPUT: + + - ``transformations`` -- the inverse transformations expressed as a + list of the expressions of the "old" coordinates in terms of the + "new" ones + - ``kwds`` -- keyword arguments: only ``verbose=True`` or + ``verbose=False`` (default) are meaningful; it determines whether + the provided transformations are checked to be indeed the inverse + coordinate transformations + + EXAMPLES: + + From spherical coordinates to Cartesian ones in the plane:: + + sage: M = Manifold(2, 'R^2', structure='topological') + sage: U = M.open_subset('U') # the complement of the half line {y=0, x>= 0} + sage: c_cart. = U.chart() + sage: c_spher. = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi') + sage: spher_to_cart = c_spher.transition_map(c_cart, [r*cos(ph), r*sin(ph)]) + sage: spher_to_cart.set_inverse(sqrt(x^2+y^2), atan2(y,x)) + sage: spher_to_cart.inverse() + Change of coordinates from Chart (U, (x, y)) to Chart (U, (r, ph)) + sage: spher_to_cart.inverse().display() + r = sqrt(x^2 + y^2) + ph = arctan2(y, x) + sage: M.coord_changes() # random (dictionary output) + {(Chart (U, (r, ph)), + Chart (U, (x, y))): Change of coordinates from Chart (U, (r, ph)) to Chart (U, (x, y)), + (Chart (U, (x, y)), + Chart (U, (r, ph))): Change of coordinates from Chart (U, (x, y)) to Chart (U, (r, ph))} + + Introducing a wrong inverse transformation (note the ``x^3`` typo) is + revealed by setting ``verbose`` to ``True``:: + + sage: spher_to_cart.set_inverse(sqrt(x^3+y^2), atan2(y,x), verbose=True) + Check of the inverse coordinate transformation: + r == sqrt(r^3*cos(ph)^3 + r^2*sin(ph)^2) + ph == arctan2(r*sin(ph), r*cos(ph)) + x == sqrt(x^3 + y^2)*x/sqrt(x^2 + y^2) + y == sqrt(x^3 + y^2)*y/sqrt(x^2 + y^2) + + """ + verbose = kwds.get('verbose', False) + self._inverse = type(self)(self._chart2, self._chart1, + *transformations) + if verbose: + print("Check of the inverse coordinate transformation:") + x1 = self._chart1._xx + x2 = self._chart2._xx + n1 = len(x1) + for i in range(n1): + print(" {} == {}".format(x1[i], self._inverse(*(self(*x1)))[i])) + for i in range(n1): + print(" {} == {}".format(x2[i], self(*(self._inverse(*x2)))[i])) + + def __mul__(self, other): + r""" + Composition with another change of coordinates. + + INPUT: + + - ``other`` -- another change of coordinate, the final chart of + it is the initial chart of ``self`` + + OUTPUT: + + - the change of coordinates `X_1 \to X_3`, where `X_1` is the initial + chart of ``other`` and `X_3` is the final chart of ``self`` + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: U. = M.chart() + sage: X_to_U = X.transition_map(U, (x+y, x-y)) + sage: W. = M.chart() + sage: U_to_W = U.transition_map(W, (u+cos(u)/2, v-sin(v)/2)) + sage: X_to_W = U_to_W * X_to_U; X_to_W + Change of coordinates from Chart (M, (x, y)) to Chart (M, (w, z)) + sage: X_to_W.display() + w = 1/2*cos(x)*cos(y) - 1/2*sin(x)*sin(y) + x + y + z = -1/2*cos(y)*sin(x) + 1/2*cos(x)*sin(y) + x - y + + """ + if not isinstance(other, CoordChange): + raise TypeError("{} is not a change of coordinate".format(other)) + if other._chart2 != self._chart1: + raise ValueError("composition not possible: " + + "{} is different from {}".format(other._chart2, + other._chart1)) + #*# when MultiCoordFunction will be implemented (trac #18640): + # transf = self._transf(*(other._transf.expr())) + #*# for now: + transf = self(*(other._transf)) + return type(self)(other._chart1, self._chart2, *transf) + + def restrict(self, dom1, dom2=None): + r""" + Restriction to subsets. + + INPUT: + + - ``dom1`` -- open subset of the domain of ``chart1`` + - ``dom2`` -- (default: ``None``) open subset of the domain of + ``chart2``; if ``None``, ``dom1`` is assumed + + OUTPUT: + + - the transition map between the charts restricted to the + specified subsets + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: Y. = M.chart() + sage: X_to_Y = X.transition_map(Y, [x+y, x-y]) + sage: U = M.open_subset('U', coord_def={X: x>0, Y: u+v>0}) + sage: X_to_Y_U = X_to_Y.restrict(U); X_to_Y_U + Change of coordinates from Chart (U, (x, y)) to Chart (U, (u, v)) + sage: X_to_Y_U.display() + u = x + y + v = x - y + + The result is cached:: + + sage: X_to_Y.restrict(U) is X_to_Y_U + True + + """ + if dom2 is None: + dom2 = dom1 + ch1 = self._chart1.restrict(dom1) + ch2 = self._chart2.restrict(dom2) + if (ch1, ch2) in dom1.coord_changes(): + return dom1.coord_changes()[(ch1,ch2)] + #*# when MultiCoordFunction will be implemented (trac #18640): + # return type(self)(self._chart1.restrict(dom1), + # self._chart2.restrict(dom2), *(self._transf.expr())) + #*# for now: + return type(self)(self._chart1.restrict(dom1), + self._chart2.restrict(dom2), *(self._transf)) + + def display(self): + r""" + Display of the coordinate transformation. + + The output is either text-formatted (console mode) or LaTeX-formatted + (notebook mode). + + EXAMPLES: + + From spherical coordinates to Cartesian ones in the plane:: + + sage: M = Manifold(2, 'R^2', structure='topological') + sage: U = M.open_subset('U') # the complement of the half line {y=0, x>= 0} + sage: c_cart. = U.chart() + sage: c_spher. = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi') + sage: spher_to_cart = c_spher.transition_map(c_cart, [r*cos(ph), r*sin(ph)]) + sage: spher_to_cart.display() + x = r*cos(ph) + y = r*sin(ph) + sage: latex(spher_to_cart.display()) + \left\{\begin{array}{lcl} x & = & r \cos\left({\phi}\right) \\ + y & = & r \sin\left({\phi}\right) \end{array}\right. + + A shortcut is ``disp()``:: + + sage: spher_to_cart.disp() + x = r*cos(ph) + y = r*sin(ph) + + """ + from sage.misc.latex import latex + from sage.tensor.modules.format_utilities import FormattedExpansion + coords2 = self._chart2[:] + n2 = len(coords2) + #*# when MultiCoordFunction will be implemented (trac #18640): + # expr = self._transf.expr() + #*# for now: + expr = self._transf + rtxt = "" + if n2 == 1: + rlatex = r"\begin{array}{lcl}" + else: + rlatex = r"\left\{\begin{array}{lcl}" + for i in range(n2): + x2 = coords2[i] + x2f = expr[i] + rtxt += repr(x2) + " = " + repr(x2f) + "\n" + rlatex += latex(x2) + r" & = & " + latex(x2f) + r"\\" + rtxt = rtxt[:-1] # remove the last new line + rlatex = rlatex[:-2] + r"\end{array}" + if n2 > 1: + rlatex += r"\right." + return FormattedExpansion(rtxt, rlatex) + + disp = display + diff --git a/src/sage/manifolds/manifold.py b/src/sage/manifolds/manifold.py new file mode 100644 index 00000000000..218c54fba75 --- /dev/null +++ b/src/sage/manifolds/manifold.py @@ -0,0 +1,1659 @@ +r""" +Topological Manifolds + +Given a topological field `K` (in most applications, `K = \RR` or +`K = \CC`) and a non-negative integer `n`, a *topological manifold of +dimension* `n` *over K* is a topological space `M` such that + +- `M` is a Hausdorff space, +- `M` is second countable, +- every point in `M` has a neighborhood homeomorphic to `K^n`. + +Topological manifolds are implemented via the class +:class:`TopologicalManifold`. Open subsets of topological manifolds +are also implemented via :class:`TopologicalManifold`, since they are +topological manifolds by themselves. + +In the current setting, topological manifolds are mostly described by +means of charts (see :class:`~sage.manifolds.chart.Chart`). + +:class:`TopologicalManifold` serves as a base class for more specific +manifold classes. + +The user interface is provided by the generic function +:func:`~sage.manifolds.manifold.Manifold`, with +with the argument ``structure`` set to ``'topological'``. + +.. RUBRIC:: Example 1: the 2-sphere as a topological manifold of dimension + 2 over `\RR` + +One starts by declaring `S^2` as a 2-dimensional topological manifold:: + + sage: M = Manifold(2, 'S^2', structure='topological') + sage: M + 2-dimensional topological manifold S^2 + +Since the base topological field has not been specified in the argument list +of ``Manifold``, `\RR` is assumed:: + + sage: M.base_field() + Real Field with 53 bits of precision + sage: dim(M) + 2 + +Let us consider the complement of a point, the "North pole" say; this is an +open subset of `S^2`, which we call `U`:: + + sage: U = M.open_subset('U'); U + Open subset U of the 2-dimensional topological manifold S^2 + +A standard chart on `U` is provided by the stereographic projection from the +North pole to the equatorial plane:: + + sage: stereoN. = U.chart(); stereoN + Chart (U, (x, y)) + +Thanks to the operator ```` on the left-hand side, the coordinates +declared in a chart (here `x` and `y`), are accessible by their names; +they are Sage's symbolic variables:: + + sage: y + y + sage: type(y) + + +The South pole is the point of coordinates `(x, y) = (0, 0)` in the above +chart:: + + sage: S = U.point((0,0), chart=stereoN, name='S'); S + Point S on the 2-dimensional topological manifold S^2 + +Let us call `V` the open subset that is the complement of the South pole and +let us introduce on it the chart induced by the stereographic projection from +the South pole to the equatorial plane:: + + sage: V = M.open_subset('V'); V + Open subset V of the 2-dimensional topological manifold S^2 + sage: stereoS. = V.chart(); stereoS + Chart (V, (u, v)) + +The North pole is the point of coordinates `(u, v) = (0, 0)` in this chart:: + + sage: N = V.point((0,0), chart=stereoS, name='N'); N + Point N on the 2-dimensional topological manifold S^2 + +To fully construct the manifold, we declare that it is the union of `U` +and `V`:: + + sage: M.declare_union(U,V) + +and we provide the transition map between the charts ``stereoN`` = +`(U, (x, y))` and ``stereoS`` = `(V, (u, v))`, denoting by `W` the +intersection of `U` and `V` (`W` is the subset of `U` defined by +`x^2 + y^2 \neq 0`, as well as the subset of `V` defined by +`u^2 + v^2 \neq 0`):: + + sage: stereoN_to_S = stereoN.transition_map(stereoS, [x/(x^2+y^2), y/(x^2+y^2)], + ....: intersection_name='W', restrictions1= x^2+y^2!=0, + ....: restrictions2= u^2+v^2!=0) + sage: stereoN_to_S + Change of coordinates from Chart (W, (x, y)) to Chart (W, (u, v)) + sage: stereoN_to_S.display() + u = x/(x^2 + y^2) + v = y/(x^2 + y^2) + +We give the name ``W`` to the Python variable representing `W = U \cap V`:: + + sage: W = U.intersection(V) + +The inverse of the transition map is computed by the method +:meth:`sage.manifolds.chart.CoordChange.inverse`:: + + sage: stereoN_to_S.inverse() + Change of coordinates from Chart (W, (u, v)) to Chart (W, (x, y)) + sage: stereoN_to_S.inverse().display() + x = u/(u^2 + v^2) + y = v/(u^2 + v^2) + +At this stage, we have four open subsets on `S^2`:: + + sage: M.list_of_subsets() + [2-dimensional topological manifold S^2, + Open subset U of the 2-dimensional topological manifold S^2, + Open subset V of the 2-dimensional topological manifold S^2, + Open subset W of the 2-dimensional topological manifold S^2] + +`W` is the open subset that is the complement of the two poles:: + + sage: N in W or S in W + False + +The North pole lies in `V` and the South pole in `U`:: + + sage: N in V, N in U + (True, False) + sage: S in U, S in V + (True, False) + +The manifold's (user) atlas contains four charts, two of them +being restrictions of charts to a smaller domain:: + + sage: M.atlas() + [Chart (U, (x, y)), Chart (V, (u, v)), + Chart (W, (x, y)), Chart (W, (u, v))] + +Let us consider the point of coordinates `(1, 2)` in the chart ``stereoN``:: + + sage: p = M.point((1,2), chart=stereoN, name='p'); p + Point p on the 2-dimensional topological manifold S^2 + sage: p.parent() + 2-dimensional topological manifold S^2 + sage: p in W + True + +The coordinates of `p` in the chart ``stereoS`` are computed by letting +the chart act on the point:: + + sage: stereoS(p) + (1/5, 2/5) + +Given the definition of `p`, we have of course:: + + sage: stereoN(p) + (1, 2) + +Similarly:: + + sage: stereoS(N) + (0, 0) + sage: stereoN(S) + (0, 0) + + +.. RUBRIC:: Example 2: the Riemann sphere as a topological manifold of + dimension 1 over `\CC` + +We declare the Riemann sphere `\CC^*` as a 1-dimensional topological manifold +over `\CC`:: + + sage: M = Manifold(1, 'C*', structure='topological', field='complex'); M + Complex 1-dimensional topological manifold C* + +We introduce a first open subset, which is actually +`\CC = \CC^*\setminus\{\infty\}` if we interpret `\CC^*` as the +Alexandroff one-point compactification of `\CC`:: + + sage: U = M.open_subset('U') + +A natural chart on `U` is then nothing but the identity map of `\CC`, hence +we denote the associated coordinate by `z`:: + + sage: Z. = U.chart() + +The origin of the complex plane is the point of coordinate `z = 0`:: + + sage: O = U.point((0,), chart=Z, name='O'); O + Point O on the Complex 1-dimensional topological manifold C* + +Another open subset of `\CC^*` is `V = \CC^*\setminus\{O\}`:: + + sage: V = M.open_subset('V') + +We define a chart on `V` such that the point at infinity is the point of +coordinate `0` in this chart:: + + sage: W. = V.chart(); W + Chart (V, (w,)) + sage: inf = M.point((0,), chart=W, name='inf', latex_name=r'\infty') + sage: inf + Point inf on the Complex 1-dimensional topological manifold C* + +To fully construct the Riemann sphere, we declare that it is the union +of `U` and `V`:: + + sage: M.declare_union(U,V) + +and we provide the transition map between the two charts as `w = 1 / z` +on `A = U \cap V`:: + + sage: Z_to_W = Z.transition_map(W, 1/z, intersection_name='A', + ....: restrictions1= z!=0, restrictions2= w!=0) + sage: Z_to_W + Change of coordinates from Chart (A, (z,)) to Chart (A, (w,)) + sage: Z_to_W.display() + w = 1/z + sage: Z_to_W.inverse() + Change of coordinates from Chart (A, (w,)) to Chart (A, (z,)) + sage: Z_to_W.inverse().display() + z = 1/w + +Let consider the complex number `i` as a point of the Riemann sphere:: + + sage: i = M((I,), chart=Z, name='i'); i + Point i on the Complex 1-dimensional topological manifold C* + +Its coordinates w.r.t. the charts ``Z`` and ``W`` are:: + + sage: Z(i) + (I,) + sage: W(i) + (-I,) + +and we have:: + + sage: i in U + True + sage: i in V + True + +The following subsets and charts have been defined:: + + sage: M.list_of_subsets() + [Open subset A of the Complex 1-dimensional topological manifold C*, + Complex 1-dimensional topological manifold C*, + Open subset U of the Complex 1-dimensional topological manifold C*, + Open subset V of the Complex 1-dimensional topological manifold C*] + sage: M.atlas() + [Chart (U, (z,)), Chart (V, (w,)), Chart (A, (z,)), Chart (A, (w,))] + + +AUTHORS: + +- Eric Gourgoulhon (2015): initial version +- Travis Scrimshaw (2015): structure described via + :class:`~sage.manifolds.structure.TopologicalStructure` or + :class:`~sage.manifolds.structure.RealTopologicalStructure` + + +REFERENCES: + +.. [Lee11] J.M. Lee : *Introduction to Topological Manifolds*, + 2nd ed., Springer (New York) (2011). +.. [Lee13] J.M. Lee : *Introduction to Smooth Manifolds*, + 2nd ed., Springer (New York) (2013) +.. [KN63] S. Kobayashi & K. Nomizu : *Foundations of Differential Geometry*, + vol. 1, Interscience Publishers (New York) (1963). +.. [Huybrechts05] D. Huybrechts : *Complex Geometry*, + Springer (Berlin) (2005). +""" + +#***************************************************************************** +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.categories.fields import Fields +from sage.categories.manifolds import Manifolds +from sage.rings.all import CC +from sage.rings.real_mpfr import RR, RealField_class +from sage.rings.complex_field import ComplexField_class +from sage.misc.prandom import getrandbits +from sage.rings.integer import Integer +from sage.manifolds.subset import ManifoldSubset +from sage.manifolds.structure import TopologicalStructure, \ + RealTopologicalStructure + +############################################################################# +## Class + +class TopologicalManifold(ManifoldSubset): + r""" + Topological manifold over a topological field `K`. + + Given a topological field `K` (in most applications, `K = \RR` or + `K = \CC`) and a non-negative integer `n`, a *topological manifold of + dimension* `n` *over K* is a topological space `M` such that + + - `M` is a Hausdorff space, + - `M` is second countable, and + - every point in `M` has a neighborhood homeomorphic to `K^n`. + + This is a Sage *parent* class, the corresponding *element* + class being :class:`~sage.manifolds.point.ManifoldPoint`. + + INPUT: + + - ``n`` -- positive integer; dimension of the manifold + - ``name`` -- string; name (symbol) given to the manifold + - ``field`` -- field `K` on which the manifold is + defined; allowed values are + + - ``'real'`` or an object of type ``RealField`` (e.g., ``RR``) for + a manifold over `\RR` + - ``'complex'`` or an object of type ``ComplexField`` (e.g., ``CC``) + for a manifold over `\CC` + - an object in the category of topological fields (see + :class:`~sage.categories.fields.Fields` and + :class:`~sage.categories.topological_spaces.TopologicalSpaces`) + for other types of manifolds + + - ``structure`` -- manifold structure (see + :class:`~sage.manifolds.structure.TopologicalStructure` or + :class:`~sage.manifolds.structure.RealTopologicalStructure`) + - ``ambient`` -- (default: ``None``) if not ``None``, must be a + topological manifold; the created object is then an open subset of + ``ambient`` + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to + denote the manifold; if none are provided, it is set to ``name`` + - ``start_index`` -- (default: 0) integer; lower value of the range of + indices used for "indexed objects" on the manifold, e.g., coordinates + in a chart + - ``category`` -- (default: ``None``) to specify the category; if + ``None``, ``Manifolds(field)`` is assumed (see the category + :class:`~sage.categories.manifolds.Manifolds`) + - ``unique_tag`` -- (default: ``None``) tag used to force the construction + of a new object when all the other arguments have been used previously + (without ``unique_tag``, the + :class:`~sage.structure.unique_representation.UniqueRepresentation` + behavior inherited from + :class:`~sage.manifolds.subset.ManifoldSubset` + would return the previously constructed object corresponding to these + arguments) + + EXAMPLES: + + A 4-dimensional topological manifold (over `\RR`):: + + sage: M = Manifold(4, 'M', latex_name=r'\mathcal{M}', structure='topological') + sage: M + 4-dimensional topological manifold M + sage: latex(M) + \mathcal{M} + sage: type(M) + + sage: M.base_field() + Real Field with 53 bits of precision + sage: dim(M) + 4 + + The input parameter ``start_index`` defines the range of indices + on the manifold:: + + sage: M = Manifold(4, 'M', structure='topological') + sage: list(M.irange()) + [0, 1, 2, 3] + sage: M = Manifold(4, 'M', structure='topological', start_index=1) + sage: list(M.irange()) + [1, 2, 3, 4] + sage: list(Manifold(4, 'M', structure='topological', start_index=-2).irange()) + [-2, -1, 0, 1] + + A complex manifold:: + + sage: N = Manifold(3, 'N', structure='topological', field='complex'); N + Complex 3-dimensional topological manifold N + + A manifold over `\QQ`:: + + sage: N = Manifold(6, 'N', structure='topological', field=QQ); N + 6-dimensional topological manifold N over the Rational Field + + A manifold over `\QQ_5`, the field of 5-adic numbers:: + + sage: N = Manifold(2, 'N', structure='topological', field=Qp(5)); N + 2-dimensional topological manifold N over the 5-adic Field with capped + relative precision 20 + + A manifold is a Sage *parent* object, in the category of topological + manifolds over a given topological field (see + :class:`~sage.categories.manifolds.Manifolds`):: + + sage: isinstance(M, Parent) + True + sage: M.category() + Category of manifolds over Real Field with 53 bits of precision + sage: from sage.categories.manifolds import Manifolds + sage: M.category() is Manifolds(RR) + True + sage: M.category() is Manifolds(M.base_field()) + True + sage: M in Manifolds(RR) + True + sage: N in Manifolds(Qp(5)) + True + + The corresponding Sage *elements* are points:: + + sage: X. = M.chart() + sage: p = M.an_element(); p + Point on the 4-dimensional topological manifold M + sage: p.parent() + 4-dimensional topological manifold M + sage: M.is_parent_of(p) + True + sage: p in M + True + + The manifold's points are instances of class + :class:`~sage.manifolds.point.ManifoldPoint`:: + + sage: isinstance(p, sage.manifolds.point.ManifoldPoint) + True + + Since an open subset of a topological manifold `M` is itself a + topological manifold, open subsets of `M` are instances of the class + :class:`TopologicalManifold`:: + + sage: U = M.open_subset('U'); U + Open subset U of the 4-dimensional topological manifold M + sage: isinstance(U, sage.manifolds.manifold.TopologicalManifold) + True + sage: U.base_field() == M.base_field() + True + sage: dim(U) == dim(M) + True + sage: U.category() + Join of Category of subobjects of sets and Category of manifolds over + Real Field with 53 bits of precision + + The manifold passes all the tests of the test suite relative to its + category:: + + sage: TestSuite(M).run() + + .. SEEALSO:: + + :mod:`sage.manifolds.manifold` + """ + def __init__(self, n, name, field, structure, ambient=None, + latex_name=None, start_index=0, category=None, + unique_tag=None): + r""" + Construct a topological manifold. + + TESTS:: + + sage: M = Manifold(3, 'M', latex_name=r'\mathbb{M}', + ....: structure='topological', start_index=1) + sage: M + 3-dimensional topological manifold M + sage: latex(M) + \mathbb{M} + sage: dim(M) + 3 + sage: X. = M.chart() + sage: TestSuite(M).run() + + Tests for open subsets:: + + sage: U = M.open_subset('U', coord_def={X: x>0}) + sage: TestSuite(U).run() + sage: U.category() is M.category().Subobjects() + True + + """ + # Initialization of the attributes _dim, _field, _field_type: + self._dim = n + if field == 'real': + self._field = RR + self._field_type = 'real' + elif field == 'complex': + self._field = CC + self._field_type = 'complex' + else: + if field not in Fields(): + raise TypeError("the argument 'field' must be a field") + self._field = field + if isinstance(field, RealField_class): + self._field_type = 'real' + elif isinstance(field, ComplexField_class): + self._field_type = 'complex' + else: + self._field_type = 'neither_real_nor_complex' + # Structure and category: + self._structure = structure + if ambient is None: + ambient = self + category = Manifolds(self._field).or_subcategory(category) + category = self._structure.subcategory(category) + else: + category = ambient.category().Subobjects() + # Initialization as a manifold set: + ManifoldSubset.__init__(self, ambient, name, latex_name=latex_name, + category=category) + self._is_open = True + self._open_covers.append([self]) # list of open covers of self + # + if not isinstance(start_index, (int, Integer)): + raise TypeError("the starting index must be an integer") + self._sindex = start_index + # + self._atlas = [] # list of charts defined on subsets of self + self._top_charts = [] # list of charts defined on subsets of self + # that are not subcharts of charts on larger subsets + self._def_chart = None # default chart + self._charts_by_coord = {} # dictionary of charts whose domain is self + # (key: string formed by the coordinate + # symbols separated by a white space) + self._coord_changes = {} # dictionary of transition maps (key: pair of + # of charts) + # List of charts that individually cover self, i.e. whose + # domains are self (if non-empty, self is a coordinate domain): + self._covering_charts = [] + + def _repr_(self): + r""" + Return a string representation of the manifold. + + TESTS:: + + sage: M = Manifold(3, 'M', structure='topological') + sage: M._repr_() + '3-dimensional topological manifold M' + sage: repr(M) # indirect doctest + '3-dimensional topological manifold M' + sage: M # indirect doctest + 3-dimensional topological manifold M + sage: M = Manifold(3, 'M', structure='topological', field='complex') + sage: M._repr_() + 'Complex 3-dimensional topological manifold M' + sage: M = Manifold(3, 'M', structure='topological', field=QQ) + sage: M._repr_() + '3-dimensional topological manifold M over the Rational Field' + + If the manifold is actually an open subset of a larger manifold, the + string representation is different:: + + sage: U = M.open_subset('U') + sage: U._repr_() + 'Open subset U of the 3-dimensional topological manifold M + over the Rational Field' + """ + if self is self._manifold: + if self._field_type == 'real': + return "{}-dimensional {} manifold {}".format(self._dim, + self._structure.name, + self._name) + elif self._field_type == 'complex': + return "Complex {}-dimensional {} manifold {}".format(self._dim, + self._structure.name, + self._name) + return "{}-dimensional {} manifold {} over the {}".format(self._dim, + self._structure.name, + self._name, + self._field) + else: + return "Open subset {} of the {}".format(self._name, self._manifold) + + def _an_element_(self): + r""" + Construct some point on the manifold. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: p = M._an_element_(); p + Point on the 2-dimensional topological manifold M + sage: p.coord() + (0, 0) + sage: U = M.open_subset('U', coord_def={X: y>1}); U + Open subset U of the 2-dimensional topological manifold M + sage: p = U._an_element_(); p + Point on the 2-dimensional topological manifold M + sage: p in U + True + sage: p.coord() + (0, 2) + sage: V = U.open_subset('V', coord_def={X.restrict(U): x<-pi}) + sage: p = V._an_element_(); p + Point on the 2-dimensional topological manifold M + sage: p in V + True + sage: p.coord() + (-pi - 1, 2) + + """ + from sage.rings.infinity import Infinity + if self._def_chart is None: + return self.element_class(self) + # Attempt to construct a point in the domain of the default chart + chart = self._def_chart + if self._field_type == 'real': + coords = [] + for coord_range in chart._bounds: + xmin = coord_range[0][0] + xmax = coord_range[1][0] + if xmin == -Infinity: + if xmax == Infinity: + x = 0 + else: + x = xmax - 1 + else: + if xmax == Infinity: + x = xmin + 1 + else: + x = (xmin + xmax)/2 + coords.append(x) + else: + coords = self._dim*[0] + if not chart.valid_coordinates(*coords): + # Attempt to construct a point in the domain of other charts + if self._field_type == 'real': + for ch in self._atlas: + if ch is self._def_chart: + continue # since this case has already been attempted + coords = [] + for coord_range in ch._bounds: + xmin = coord_range[0][0] + xmax = coord_range[1][0] + if xmin == -Infinity: + if xmax == Infinity: + x = 0 + else: + x = xmax - 1 + else: + if xmax == Infinity: + x = xmin + 1 + else: + x = (xmin + xmax)/2 + coords.append(x) + if ch.valid_coordinates(*coords): + chart = ch + break + else: + # A generic element with specific coordinates could not be + # automatically generated, due to too complex cooordinate + # conditions. An element without any coordinate set is + # returned instead: + return self.element_class(self) + else: + # Case of manifolds over a field different from R + for ch in self._atlas: + if ch is self._def_chart: + continue # since this case has already been attempted + if ch.valid_coordinates(*coords): + chart = ch + break + else: + return self.element_class(self) + # The point is constructed with check_coords=False since the check + # has just been performed above: + return self.element_class(self, coords=coords, chart=chart, + check_coords=False) + + def __contains__(self, point): + r""" + Check whether a point is contained in the manifold. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: p = M.point((1,2), chart=X) + sage: M.__contains__(p) + True + sage: p in M # indirect doctest + True + sage: U = M.open_subset('U', coord_def={X: x>0}) + sage: U.__contains__(p) + True + sage: p in U # indirect doctest + True + sage: V = U.open_subset('V', coord_def={X.restrict(U): y<0}) + sage: V.__contains__(p) + False + sage: p in V # indirect doctest + False + + """ + # for efficiency, a quick test first: + if point.parent() is self: + return True + if point.parent().is_subset(self): + return True + for chart in self._atlas: + if chart in point._coordinates: + if chart.valid_coordinates( *(point._coordinates[chart]) ): + return True + for chart in point._coordinates: + for schart in chart._subcharts: + if schart in self._atlas and schart.valid_coordinates( + *(point._coordinates[chart]) ): + return True + return False + + def open_subset(self, name, latex_name=None, coord_def={}): + r""" + Create an open subset of the manifold. + + An open subset is a set that is (i) included in the manifold and (ii) + open with respect to the manifold's topology. It is a topological + manifold by itself. Hence the returned object is an instance of + :class:`TopologicalManifold`. + + INPUT: + + - ``name`` -- name given to the open subset + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote + the subset; if none are provided, it is set to ``name`` + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts on the manifold and values the symbolic expressions formed + by the coordinates to define the subset + + OUTPUT: + + - the open subset, as an instance of :class:`TopologicalManifold` + + EXAMPLES: + + Creating an open subset of a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.open_subset('A'); A + Open subset A of the 2-dimensional topological manifold M + + As an open subset of a topological manifold, ``A`` is itself a + topological manifold, on the same topological field and of the same + dimension as ``M``:: + + sage: isinstance(A, sage.manifolds.manifold.TopologicalManifold) + True + sage: A.base_field() == M.base_field() + True + sage: dim(A) == dim(M) + True + sage: A.category() is M.category().Subobjects() + True + + Creating an open subset of ``A``:: + + sage: B = A.open_subset('B'); B + Open subset B of the 2-dimensional topological manifold M + + We have then:: + + sage: A.subsets() # random (set output) + {Open subset B of the 2-dimensional topological manifold M, + Open subset A of the 2-dimensional topological manifold M} + sage: B.is_subset(A) + True + sage: B.is_subset(M) + True + + Defining an open subset by some coordinate restrictions: the open + unit disk in `\RR^2`:: + + sage: M = Manifold(2, 'R^2', structure='topological') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: U = M.open_subset('U', coord_def={c_cart: x^2+y^2<1}); U + Open subset U of the 2-dimensional topological manifold R^2 + + Since the argument ``coord_def`` has been set, ``U`` is automatically + provided with a chart, which is the restriction of the Cartesian one + to ``U``:: + + sage: U.atlas() + [Chart (U, (x, y))] + + Therefore, one can immediately check whether a point belongs + to ``U``:: + + sage: M.point((0,0)) in U + True + sage: M.point((1/2,1/3)) in U + True + sage: M.point((1,2)) in U + False + + """ + resu = TopologicalManifold(self._dim, name, self._field, + self._structure, ambient=self._manifold, + latex_name=latex_name, + start_index=self._sindex) + resu._supersets.update(self._supersets) + for sd in self._supersets: + sd._subsets.add(resu) + self._top_subsets.add(resu) + # Charts on the result from the coordinate definition: + for chart, restrictions in coord_def.iteritems(): + if chart not in self._atlas: + raise ValueError("the {} does not belong to ".format(chart) + + "the atlas of {}".format(self)) + chart.restrict(resu, restrictions) + # Transition maps on the result inferred from those of self: + for chart1 in coord_def: + for chart2 in coord_def: + if chart2 != chart1 and (chart1, chart2) in self._coord_changes: + self._coord_changes[(chart1, chart2)].restrict(resu) + return resu + + def get_chart(self, coordinates, domain=None): + r""" + Get a chart from its coordinates. + + The chart must have been previously created by the method + :meth:`chart`. + + INPUT: + + - ``coordinates`` -- single string composed of the coordinate symbols + separated by a space + - ``domain`` -- (default: ``None``) string containing the name of the + chart's domain, which must be a subset of the current manifold; if + ``None``, the current manifold is assumed + + OUTPUT: + + - instance of + :class:`~sage.manifolds.chart.Chart` (or of the subclass + :class:`~sage.manifolds.chart.RealChart`) representing the chart + corresponding to the above specifications + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: M.get_chart('x y') + Chart (M, (x, y)) + sage: M.get_chart('x y') is X + True + sage: U = M.open_subset('U', coord_def={X: (y!=0,x<0)}) + sage: Y. = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi') + sage: M.atlas() + [Chart (M, (x, y)), Chart (U, (x, y)), Chart (U, (r, ph))] + sage: M.get_chart('x y', domain='U') + Chart (U, (x, y)) + sage: M.get_chart('x y', domain='U') is X.restrict(U) + True + sage: U.get_chart('r ph') + Chart (U, (r, ph)) + sage: M.get_chart('r ph', domain='U') + Chart (U, (r, ph)) + sage: M.get_chart('r ph', domain='U') is Y + True + + """ + if domain is None: + dom = self + else: + dom = self.get_subset(domain) + try: + return dom._charts_by_coord[coordinates] + except KeyError: + raise KeyError("the coordinates '{}' ".format(coordinates) + + "do not correspond to any chart with " + + "the {} as domain".format(dom)) + + def dimension(self): + r""" + Return the dimension of the manifold over its base field. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: M.dimension() + 2 + + A shortcut is ``dim()``:: + + sage: M.dim() + 2 + + The Sage global function ``dim`` can also be used:: + + sage: dim(M) + 2 + + """ + return self._dim + + dim = dimension + + def base_field(self): + r""" + Return the field on which the manifold is defined. + + OUTPUT: + + - a topological field + + EXAMPLES:: + + sage: M = Manifold(3, 'M', structure='topological') + sage: M.base_field() + Real Field with 53 bits of precision + sage: M = Manifold(3, 'M', structure='topological', field='complex') + sage: M.base_field() + Complex Field with 53 bits of precision + sage: M = Manifold(3, 'M', structure='topological', field=QQ) + sage: M.base_field() + Rational Field + + """ + return self._field + + def base_field_type(self): + r""" + Return the type of topological field on which the manifold is defined. + + OUTPUT: + + - a string describing the field, with three possible values: + + - ``'real'`` for the real field `\RR` + - ``'complex'`` for the complex field `\CC` + - ``'neither_real_nor_complex'`` for a field different from `\RR` + and `\CC` + + EXAMPLES:: + + sage: M = Manifold(3, 'M', structure='topological') + sage: M.base_field_type() + 'real' + sage: M = Manifold(3, 'M', structure='topological', field='complex') + sage: M.base_field_type() + 'complex' + sage: M = Manifold(3, 'M', structure='topological', field=QQ) + sage: M.base_field_type() + 'neither_real_nor_complex' + + """ + return self._field_type + + def start_index(self): + r""" + Return the first value of the index range used on the manifold. + + This is the parameter ``start_index`` passed at the construction of + the manifold. + + OUTPUT: + + - the integer `i_0` such that all indices of indexed objects on the + manifold range from `i_0` to `i_0 + n - 1`, where `n` is the + manifold's dimension + + EXAMPLES:: + + sage: M = Manifold(3, 'M', structure='topological') + sage: M.start_index() + 0 + sage: M = Manifold(3, 'M', structure='topological', start_index=1) + sage: M.start_index() + 1 + + """ + return self._sindex + + def irange(self, start=None): + r""" + Single index generator. + + INPUT: + + - ``start`` -- (default: ``None``) initial value `i_0` of the index; + if none are provided, the value returned by :meth:`start_index()` + is assumed + + OUTPUT: + + - an iterable index, starting from `i_0` and ending at + `i_0 + n - 1`, where `n` is the manifold's dimension + + EXAMPLES: + + Index range on a 4-dimensional manifold:: + + sage: M = Manifold(4, 'M', structure='topological') + sage: for i in M.irange(): + ....: print i, + ....: + 0 1 2 3 + sage: for i in M.irange(2): + ....: print i, + ....: + 2 3 + sage: list(M.irange()) + [0, 1, 2, 3] + + Index range on a 4-dimensional manifold with starting index=1:: + + sage: M = Manifold(4, 'M', structure='topological', start_index=1) + sage: for i in M.irange(): + ....: print i, + ....: + 1 2 3 4 + sage: for i in M.irange(2): + ....: print i, + ....: + 2 3 4 + + In general, one has always:: + + sage: M.irange().next() == M.start_index() + True + + """ + si = self._sindex + imax = self._dim + si + if start is None: + i = si + else: + i = start + while i < imax: + yield i + i += 1 + + def index_generator(self, nb_indices): + r""" + Generator of index series. + + INPUT: + + - ``nb_indices`` -- number of indices in a series + + OUTPUT: + + - an iterable index series for a generic component with the specified + number of indices + + EXAMPLES: + + Indices on a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological', start_index=1) + sage: for ind in M.index_generator(2): + ....: print ind + ....: + (1, 1) + (1, 2) + (2, 1) + (2, 2) + + Loops can be nested:: + + sage: for ind1 in M.index_generator(2): + ....: print ind1, " : ", + ....: for ind2 in M.index_generator(2): + ....: print ind2, + ....: print "" + ....: + (1, 1) : (1, 1) (1, 2) (2, 1) (2, 2) + (1, 2) : (1, 1) (1, 2) (2, 1) (2, 2) + (2, 1) : (1, 1) (1, 2) (2, 1) (2, 2) + (2, 2) : (1, 1) (1, 2) (2, 1) (2, 2) + + """ + si = self._sindex + imax = self._dim - 1 + si + ind = [si for k in range(nb_indices)] + ind_end = [si for k in range(nb_indices)] + ind_end[0] = imax+1 + while ind != ind_end: + yield tuple(ind) + ret = 1 + for pos in range(nb_indices-1,-1,-1): + if ind[pos] != imax: + ind[pos] += ret + ret = 0 + elif ret == 1: + if pos == 0: + ind[pos] = imax + 1 # end point reached + else: + ind[pos] = si + ret = 1 + + def atlas(self): + r""" + Return the list of charts that have been defined on the manifold. + + EXAMPLES: + + Let us consider `\RR^2` as a 2-dimensional manifold:: + + sage: M = Manifold(2, 'R^2', structure='topological') + + Immediately after the manifold creation, the atlas is empty, since no + chart has been defined yet:: + + sage: M.atlas() + [] + + Let us introduce the chart of Cartesian coordinates:: + + sage: c_cart. = M.chart() + sage: M.atlas() + [Chart (R^2, (x, y))] + + The complement of the half line `\{y = 0, x \geq 0\}`:: + + sage: U = M.open_subset('U', coord_def={c_cart: (y!=0,x<0)}) + sage: U.atlas() + [Chart (U, (x, y))] + sage: M.atlas() + [Chart (R^2, (x, y)), Chart (U, (x, y))] + + Spherical (polar) coordinates on ``U``:: + + sage: c_spher. = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi') + sage: U.atlas() + [Chart (U, (x, y)), Chart (U, (r, ph))] + sage: M.atlas() + [Chart (R^2, (x, y)), Chart (U, (x, y)), Chart (U, (r, ph))] + + .. SEEALSO:: + + :meth:`top_charts` + + """ + return list(self._atlas) # Make a (shallow) copy + + def top_charts(self): + r""" + Return the list of charts defined on subsets of the current manifold + that are not subcharts of charts on larger subsets. + + OUTPUT: + + - list of charts defined on open subsets of the manifold but not on + larger subsets + + EXAMPLES: + + Charts on a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: U = M.open_subset('U', coord_def={X: x>0}) + sage: Y. = U.chart() + sage: M.top_charts() + [Chart (M, (x, y)), Chart (U, (u, v))] + + Note that the (user) atlas contains one more chart: ``(U, (x,y))``, + which is not a "top" chart:: + + sage: M.atlas() + [Chart (M, (x, y)), Chart (U, (x, y)), Chart (U, (u, v))] + + .. SEEALSO:: + + :meth:`atlas` for the complete list of charts defined on the + manifold. + + """ + return list(self._top_charts) # Make a (shallow) copy + + def default_chart(self): + r""" + Return the default chart defined on the manifold. + + Unless changed via :meth:`set_default_chart`, the *default chart* + is the first one defined on a subset of the manifold (possibly itself). + + OUTPUT: + + - instance of :class:`~sage.manifolds.chart.Chart` + representing the default chart + + EXAMPLES: + + Default chart on a 2-dimensional manifold and on some subsets:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: M.chart('x y') + Chart (M, (x, y)) + sage: M.chart('u v') + Chart (M, (u, v)) + sage: M.default_chart() + Chart (M, (x, y)) + sage: A = M.open_subset('A') + sage: A.chart('t z') + Chart (A, (t, z)) + sage: A.default_chart() + Chart (A, (t, z)) + + """ + return self._def_chart + + def set_default_chart(self, chart): + r""" + Changing the default chart on ``self``. + + INPUT: + + - ``chart`` -- a chart (must be defined on some subset ``self``) + + EXAMPLES: + + Charts on a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: c_xy. = M.chart() + sage: c_uv. = M.chart() + sage: M.default_chart() + Chart (M, (x, y)) + sage: M.set_default_chart(c_uv) + sage: M.default_chart() + Chart (M, (u, v)) + + """ + from chart import Chart + if not isinstance(chart, Chart): + raise TypeError("{} is not a chart".format(chart)) + if chart._domain is not self: + if self.is_manifestly_coordinate_domain(): + raise TypeError("the chart domain must coincide with " + + "the {}".format(self)) + if chart not in self._atlas: + raise ValueError("the chart must be defined on the " + + "{}".format(self)) + self._def_chart = chart + + def coord_change(self, chart1, chart2): + r""" + Return the change of coordinates (transition map) between two charts + defined on the manifold. + + The change of coordinates must have been defined previously, for + instance by the method + :meth:`~sage.manifolds.chart.Chart.transition_map`. + + INPUT: + + - ``chart1`` -- chart 1 + - ``chart2`` -- chart 2 + + OUTPUT: + + - instance of :class:`~sage.manifolds.chart.CoordChange` + representing the transition map from chart 1 to chart 2 + + EXAMPLES: + + Change of coordinates on a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: c_xy. = M.chart() + sage: c_uv. = M.chart() + sage: c_xy.transition_map(c_uv, (x+y, x-y)) # defines the coord. change + Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v)) + sage: M.coord_change(c_xy, c_uv) # returns the coord. change defined above + Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v)) + + """ + if (chart1, chart2) not in self._coord_changes: + raise TypeError("the change of coordinates from " + + "{} to {}".format(chart1, chart2) + " has not " + + "been defined on the {}".format(self)) + return self._coord_changes[(chart1, chart2)] + + def coord_changes(self): + r""" + Return the changes of coordinates (transition maps) defined on + subsets of the manifold. + + OUTPUT: + + - dictionary of changes of coordinates, with pairs of charts as keys + + EXAMPLES: + + Various changes of coordinates on a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: c_xy. = M.chart() + sage: c_uv. = M.chart() + sage: xy_to_uv = c_xy.transition_map(c_uv, [x+y, x-y]) + sage: M.coord_changes() + {(Chart (M, (x, y)), + Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))} + sage: uv_to_xy = xy_to_uv.inverse() + sage: M.coord_changes() # random (dictionary output) + {(Chart (M, (u, v)), + Chart (M, (x, y))): Change of coordinates from Chart (M, (u, v)) to Chart (M, (x, y)), + (Chart (M, (x, y)), + Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))} + sage: c_rs. = M.chart() + sage: uv_to_rs = c_uv.transition_map(c_rs, [-u+2*v, 3*u-v]) + sage: M.coord_changes() # random (dictionary output) + {(Chart (M, (u, v)), + Chart (M, (r, s))): Change of coordinates from Chart (M, (u, v)) to Chart (M, (r, s)), + (Chart (M, (u, v)), + Chart (M, (x, y))): Change of coordinates from Chart (M, (u, v)) to Chart (M, (x, y)), + (Chart (M, (x, y)), + Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))} + sage: xy_to_rs = uv_to_rs * xy_to_uv + sage: M.coord_changes() # random (dictionary output) + {(Chart (M, (u, v)), + Chart (M, (r, s))): Change of coordinates from Chart (M, (u, v)) to Chart (M, (r, s)), + (Chart (M, (u, v)), + Chart (M, (x, y))): Change of coordinates from Chart (M, (u, v)) to Chart (M, (x, y)), + (Chart (M, (x, y)), + Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v)), + (Chart (M, (x, y)), + Chart (M, (r, s))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (r, s))} + + """ + return self._coord_changes + + def is_manifestly_coordinate_domain(self): + r""" + Return ``True`` if the manifold is known to be the domain of some + coordinate chart and ``False`` otherwise. + + If ``False`` is returned, either the manifold cannot be the domain of + some coordinate chart or no such chart has been declared yet. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: X. = U.chart() + sage: U.is_manifestly_coordinate_domain() + True + sage: M.is_manifestly_coordinate_domain() + False + sage: Y. = M.chart() + sage: M.is_manifestly_coordinate_domain() + True + + """ + return bool(self._covering_charts) + + def chart(self, coordinates='', names=None): + r""" + Define a chart, the domain of which is the manifold. + + A *chart* is a pair `(U, \varphi)`, where `U` is the current + manifold and `\varphi: U \rightarrow V \subset K^n` + is a homeomorphism from `U` to an open subset `V` of `K^n`, `K` + being the field on which the manifold is defined. + + The components `(x^1, \ldots, x^n)` of `\varphi`, defined by + `\varphi(p) = (x^1(p), \ldots, x^n(p)) \in K^n` for any point + `p \in U`, are called the *coordinates* of the chart `(U, \varphi)`. + + See :class:`~sage.manifolds.chart.Chart` for a complete + documentation. + + INPUT: + + - ``coordinates`` -- (default: ``''`` (empty string)) string + defining the coordinate symbols and ranges, see below + - ``names`` -- (default: ``None``) unused argument, except if + ``coordinates`` is not provided; it must then be a tuple containing + the coordinate symbols (this is guaranteed if the shortcut operator + ``<,>`` is used) + + The coordinates declared in the string ``coordinates`` are + separated by ``' '`` (whitespace) and each coordinate has at most three + fields, separated by a colon (``':'``): + + 1. The coordinate symbol (a letter or a few letters). + 2. (optional, only for manifolds over `\RR`) The interval `I` + defining the coordinate range: if not provided, the coordinate + is assumed to span all `\RR`; otherwise `I` must be provided + in the form ``(a,b)`` (or equivalently ``]a,b[``) + The bounds ``a`` and ``b`` can be ``+/-Infinity``, ``Inf``, + ``infinity``, ``inf`` or ``oo``. For *singular* coordinates, + non-open intervals such as ``[a,b]`` and + ``(a,b]`` (or equivalently ``]a,b]``) are allowed. Note that + the interval declaration must not contain any space character. + 3. (optional) The LaTeX spelling of the coordinate; if not provided + the coordinate symbol given in the first field will be used. + + The order of the fields 2 and 3 does not matter and each of them can + be omitted. If it contains any LaTeX expression, the string + ``coordinates`` must be declared with the prefix 'r' (for "raw") to + allow for a proper treatment of the backslash character (see + examples below). If no interval range and no LaTeX spelling is to + be provided for any coordinate, the argument ``coordinates`` can be + omitted when the shortcut operator ``<,>`` is used via Sage + preparser (see examples below). + + OUTPUT: + + - the created chart, as an instance of + :class:`~sage.manifolds.chart.Chart` or of the subclass + :class:`~sage.manifolds.chart.RealChart` for manifolds over `\RR`. + + EXAMPLES: + + Chart on a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: X = U.chart('x y'); X + Chart (U, (x, y)) + sage: X[0] + x + sage: X[1] + y + sage: X[:] + (x, y) + + The declared coordinates are not known at the global level:: + + sage: y + Traceback (most recent call last): + ... + NameError: name 'y' is not defined + + They can be recovered by the operator ``[:]`` applied to the chart:: + + sage: (x, y) = X[:] + sage: y + y + sage: type(y) + + + But a shorter way to proceed is to use the operator ``<,>`` in the + left-hand side of the chart declaration (there is then no need to + pass the string 'x y' to chart()):: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: X. = U.chart(); X + Chart (U, (x, y)) + + Indeed, the declared coordinates are then known at the global level:: + + sage: y + y + sage: (x,y) == X[:] + True + + Actually the instruction ``X. = U.chart()`` is + equivalent to the combination of the two instructions + ``X = U.chart('x y')`` and ``(x,y) = X[:]``. + + See the documentation of class + :class:`~sage.manifolds.chart.Chart` for more examples, + especially regarding the coordinates ranges and restrictions. + + """ + return self._structure.chart(self, coordinates=coordinates, names=names) + + def is_open(self): + """ + Return if ``self`` is an open set. + + In the present case (manifold or open subset of it), always + return ``True``. + + TEST:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: M.is_open() + True + + """ + return True + +############################################################################## +## Constructor function + +def Manifold(dim, name, latex_name=None, field='real', structure='smooth', + start_index=0, **extra_kwds): + r""" + Construct a manifold of a given type over a topological field `K`. + + Given a topological field `K` (in most applications, `K = \RR` or + `K = \CC`) and a non-negative integer `n`, a *topological manifold of + dimension* `n` *over K* is a topological space `M` such that + + - `M` is a Hausdorff space, + - `M` is second countable, and + - every point in `M` has a neighborhood homeomorphic to `K^n`. + + A *real manifold* is a manifold over `\RR`. A *differentiable* (resp. + *smooth*, resp. *analytic*) is a real manifold such that all transition + maps are *differentiable* (resp. *smooth*, resp. *analytic*). + + INPUT: + + - ``dim`` -- positive integer; dimension of the manifold + - ``name`` -- string; name (symbol) given to the manifold + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to + denote the manifold; if none are provided, it is set to ``name`` + - ``field`` -- (default: ``'real'``) field `K` on which the + manifold is defined; allowed values are + + - ``'real'`` or an object of type ``RealField`` (e.g. ``RR``) for a + manifold over `\RR` + - ``'complex'`` or an object of type ``ComplexField`` (e.g. ``CC``) + for a manifold over `\CC` + - an object in the category of topological fields (see + :class:`~sage.categories.fields.Fields` and + :class:`~sage.categories.topological_spaces.TopologicalSpaces`) + for other types of manifolds + + - ``structure`` -- (default: ``'smooth'``) to specify the structure or + type of manifold; allowed values are + + - ``'topological'`` or ``'top'`` for a topological manifold + - ``'differentiable'`` or ``'diff'`` for a differentiable manifold + - ``'smooth'`` for a smooth manifold + - ``'analytic'`` for an analytic manifold + + - ``start_index`` -- (default: 0) integer; lower value of the range of + indices used for "indexed objects" on the manifold, e.g. coordinates + in a chart + - ``extra_kwds`` -- keywords meaningful only for some specific types + of manifolds + + OUTPUT: + + - a manifold of the specified type + + EXAMPLES: + + A 3-dimensional real topological manifold:: + + sage: M = Manifold(3, 'M', structure='topological'); M + 3-dimensional topological manifold M + + Given the default value of the parameter ``field``, the above is + equivalent to:: + + sage: M = Manifold(3, 'M', structure='topological', field='real'); M + 3-dimensional topological manifold M + + A complex topological manifold:: + + sage: M = Manifold(3, 'M', structure='topological', field='complex'); M + Complex 3-dimensional topological manifold M + + A topological manifold over `\QQ`:: + + sage: M = Manifold(3, 'M', structure='topological', field=QQ); M + 3-dimensional topological manifold M over the Rational Field + + See the documentation of class + :class:`~sage.manifolds.manifold.TopologicalManifold` for more + detailed examples. + + .. RUBRIC:: Uniqueness of manifold objects + + Suppose we construct a manifold named `M`:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + + At some point, we change our mind and would like to restart with a new + manifold, using the same name `M` and keeping the previous manifold for + reference:: + + sage: M_old = M # for reference + sage: M = Manifold(2, 'M', structure='topological') + + This results in a brand new object:: + + sage: M.atlas() + [] + + The object ``M_old`` is intact:: + + sage: M_old.atlas() + [Chart (M, (x, y))] + + Both objects have the same display:: + + sage: M + 2-dimensional topological manifold M + sage: M_old + 2-dimensional topological manifold M + + but they are different:: + + sage: M != M_old + True + + Let us introduce a chart on ``M``, using the same coordinate symbols + as for ``M_old``:: + + sage: X. = M.chart() + + The charts are displayed in the same way:: + + sage: M.atlas() + [Chart (M, (x, y))] + sage: M_old.atlas() + [Chart (M, (x, y))] + + but they are actually different:: + + sage: M.atlas()[0] != M_old.atlas()[0] + True + + Moreover, the two manifolds ``M`` and ``M_old`` are still considered + distinct:: + + sage: M != M_old + True + + This reflects the fact that the equality of manifold objects holds only + for identical objects, i.e. one has ``M1 == M2`` if, and only if, + ``M1 is M2``. Actually, the manifold classes inherit from + :class:`~sage.misc.fast_methods.WithEqualityById`:: + + sage: isinstance(M, sage.misc.fast_methods.WithEqualityById) + True + """ + from time import time + # Some sanity checks + if not isinstance(dim, (int, Integer)): + raise TypeError("the manifold dimension must be an integer") + if dim < 1: + raise ValueError("the manifold dimension must be strictly positive") + + if structure in ['topological', 'top']: + if field == 'real' or isinstance(field, RealField_class): + structure = RealTopologicalStructure() + else: + structure = TopologicalStructure() + else: + raise NotImplementedError("manifolds of type {} are not ".format(structure) + + "implemented") + return TopologicalManifold(dim, name, field, structure, + latex_name=latex_name, start_index=start_index, + unique_tag=getrandbits(128)*time()) + diff --git a/src/sage/manifolds/point.py b/src/sage/manifolds/point.py new file mode 100644 index 00000000000..8fdb6d4d576 --- /dev/null +++ b/src/sage/manifolds/point.py @@ -0,0 +1,684 @@ +r""" +Points of Topological Manifolds + +The class :class:`ManifoldPoint` implements points of a +topological manifold. + +A :class:`ManifoldPoint` object can have coordinates in +various charts defined on the manifold. Two points are declared +equal if they have the same coordinates in the same chart. + +AUTHORS: + +- Eric Gourgoulhon, Michal Bejger (2013-2015) : initial version + +REFERENCES: + +- [Lee11]_ J.M. Lee : *Introduction to Topological Manifolds*, 2nd ed., + Springer (New York) (2011) +- [Lee13]_ J.M. Lee : *Introduction to Smooth Manifolds*, 2nd ed., + Springer (New York, 2013) + +EXAMPLES: + +Defining a point in `\RR^3` by its spherical coordinates:: + + sage: M = Manifold(3, 'R^3', structure='topological') + sage: U = M.open_subset('U') # the complement of the half-plane (y=0, x>=0) + sage: c_spher. = U.chart(r'r:(0,+oo) th:(0,pi):\theta ph:(0,2*pi):\phi') + +We construct the point in the coordinates in the default chart of ``U`` +(``c_spher``):: + + sage: p = U((1, pi/2, pi), name='P') + sage: p + Point P on the 3-dimensional topological manifold R^3 + sage: latex(p) + P + sage: p in U + True + sage: p.parent() + Open subset U of the 3-dimensional topological manifold R^3 + sage: c_spher(p) + (1, 1/2*pi, pi) + sage: p.coordinates(c_spher) # equivalent to above + (1, 1/2*pi, pi) + +Computing the coordinates of ``p`` in a new chart:: + + sage: c_cart. = U.chart() # Cartesian coordinates on U + sage: spher_to_cart = c_spher.transition_map(c_cart, + ....: [r*sin(th)*cos(ph), r*sin(th)*sin(ph), r*cos(th)]) + sage: c_cart(p) # evaluate P's Cartesian coordinates + (-1, 0, 0) + +Points can be compared:: + + sage: p1 = U((1, pi/2, pi)) + sage: p == p1 + True + sage: q = U((1,2,3), chart=c_cart, name='Q') # point defined by its Cartesian coordinates + sage: p == q + False + +""" + +#***************************************************************************** +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.structure.element import Element + +class ManifoldPoint(Element): + r""" + Point of a topological manifold. + + This is a Sage *element* class, the corresponding *parent* class + being :class:`~sage.manifolds.manifold.TopologicalManifold` + or :class:`~sage.manifolds.subset.ManifoldSubset`. + + INPUT: + + - ``parent`` -- the manifold subset to which the point belongs + - ``coords`` -- (default: ``None``) the point coordinates (as a tuple + or a list) in the chart ``chart`` + - ``chart`` -- (default: ``None``) chart in which the coordinates are + given; if ``None``, the coordinates are assumed to refer to the + default chart of ``parent`` + - ``name`` -- (default: ``None``) name given to the point + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the point; + if ``None``, the LaTeX symbol is set to ``name`` + - ``check_coords`` -- (default: ``True``) determines whether ``coords`` + are valid coordinates for the chart ``chart``; for symbolic + coordinates, it is recommended to set ``check_coords`` to ``False`` + + EXAMPLES: + + A point on a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: c_xy. = M.chart() + sage: (a, b) = var('a b') # generic coordinates for the point + sage: p = M.point((a, b), name='P'); p + Point P on the 2-dimensional topological manifold M + sage: p.coordinates() # coordinates of P in the subset's default chart + (a, b) + + Since points are Sage *elements*, the *parent* of which being the + subset on which they are defined, it is equivalent to write:: + + sage: p = M((a, b), name='P'); p + Point P on the 2-dimensional topological manifold M + + A point is an element of the manifold subset in which it has + been defined:: + + sage: p in M + True + sage: p.parent() + 2-dimensional topological manifold M + sage: U = M.open_subset('U', coord_def={c_xy: x>0}) + sage: q = U.point((2,1), name='q') + sage: q.parent() + Open subset U of the 2-dimensional topological manifold M + sage: q in U + True + sage: q in M + True + + By default, the LaTeX symbol of the point is deduced from its name:: + + sage: latex(p) + P + + But it can be set to any value:: + + sage: p = M.point((a, b), name='P', latex_name=r'\mathcal{P}') + sage: latex(p) + \mathcal{P} + + Points can be drawn in 2D or 3D graphics thanks to the + method :meth:`plot`. + """ + def __init__(self, parent, coords=None, chart=None, name=None, + latex_name=None, check_coords=True): + r""" + Construct a manifold point. + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: p = M((2,3), name='p'); p + Point p on the 2-dimensional topological manifold M + sage: TestSuite(p).run() + sage: U = M.open_subset('U', coord_def={X: x<0}) + sage: q = U((-1,2), name='q'); q + Point q on the 2-dimensional topological manifold M + sage: TestSuite(q).run() + + """ + Element.__init__(self, parent) + self._manifold = parent.manifold() # a useful shortcut + self._coordinates = {} # dictionary of the point coordinates in various + # charts, with the charts as keys + if coords is not None: + if len(coords) != parent.manifold().dimension(): + raise ValueError("the number of coordinates must be equal " + + "to the manifold's dimension") + from sage.manifolds.manifold import TopologicalManifold + if chart is None: + chart = parent._def_chart + elif isinstance(parent, TopologicalManifold): + if chart not in parent._atlas: + raise ValueError("the {} has not been".format(chart) + + "defined on the {}".format(parent)) + if check_coords: + if not chart.valid_coordinates(*coords): + raise ValueError("the coordinates {}".format(coords) + + " are not valid on the {}".format(chart)) + for schart in chart._supercharts: + self._coordinates[schart] = tuple(coords) + for schart in chart._subcharts: + if schart != chart: + if schart.valid_coordinates(*coords): + self._coordinates[schart] = tuple(coords) + self._name = name + if latex_name is None: + self._latex_name = self._name + else: + self._latex_name = latex_name + + def _repr_(self): + r""" + Return a string representation of the point. + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: p = M((2,-3)) + sage: p._repr_() + 'Point on the 2-dimensional topological manifold M' + sage: p = M((2,-3), name='p') + sage: p._repr_() + 'Point p on the 2-dimensional topological manifold M' + sage: repr(p) # indirect doctest + 'Point p on the 2-dimensional topological manifold M' + + """ + description = "Point" + if self._name is not None: + description += " " + self._name + description += " on the {}".format(self._manifold) + return description + + def _latex_(self): + r""" + Return a LaTeX representation of the point. + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: p = M((2,-3)) + sage: p._latex_() + '\\mbox{Point on the 2-dimensional topological manifold M}' + sage: p = M((2,-3), name='p') + sage: p._latex_() + 'p' + sage: p = M((2,-3), name='p', latex_name=r'\mathcal{P}') + sage: p._latex_() + '\\mathcal{P}' + sage: latex(p) # indirect doctest + \mathcal{P} + + """ + if self._latex_name is None: + return r'\mbox{' + str(self) + r'}' + return self._latex_name + + def coordinates(self, chart=None, old_chart=None): + r""" + Return the point coordinates in the specified chart. + + If these coordinates are not already known, they are computed from + known ones by means of change-of-chart formulas. + + An equivalent way to get the coordinates of a point is to let the + chart acting on the point, i.e. if ``X`` is a chart and ``p`` a + point, one has ``p.coordinates(chart=X) == X(p)``. + + INPUT: + + - ``chart`` -- (default: ``None``) chart in which the coordinates + are given; if none are provided, the coordinates are assumed to + refer to the subset's default chart + - ``old_chart`` -- (default: ``None``) chart from which the + coordinates in ``chart`` are to be computed; if ``None``, a chart + in which the point's coordinates are already known will be picked, + privileging the subset's default chart + + EXAMPLES: + + Spherical coordinates of a point on `\RR^3`:: + + sage: M = Manifold(3, 'M', structure='topological') + sage: c_spher. = M.chart(r'r:(0,+oo) th:(0,pi):\theta ph:(0,2*pi):\phi') # spherical coordinates + sage: p = M.point((1, pi/2, pi)) + sage: p.coordinates() # coordinates in the manifold's default chart + (1, 1/2*pi, pi) + + Since the default chart of ``M`` is ``c_spher``, it is equivalent to + write:: + + sage: p.coordinates(c_spher) + (1, 1/2*pi, pi) + + An alternative way to get the coordinates is to let the chart act + on the point (from the very definition of a chart):: + + sage: c_spher(p) + (1, 1/2*pi, pi) + + A shortcut for ``coordinates`` is ``coord``:: + + sage: p.coord() + (1, 1/2*pi, pi) + + Computing the Cartesian coordinates from the spherical ones:: + + sage: c_cart. = M.chart() # Cartesian coordinates + sage: c_spher.transition_map(c_cart, [r*sin(th)*cos(ph), + ....: r*sin(th)*sin(ph), r*cos(th)]) + Change of coordinates from Chart (M, (r, th, ph)) to Chart (M, (x, y, z)) + + The computation is performed by means of the above change + of coordinates:: + + sage: p.coord(c_cart) + (-1, 0, 0) + sage: p.coord(c_cart) == c_cart(p) + True + + Coordinates of a point on a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: c_xy. = M.chart() + sage: (a, b) = var('a b') # generic coordinates for the point + sage: P = M.point((a, b), name='P') + + Coordinates of ``P`` in the manifold's default chart:: + + sage: P.coord() + (a, b) + + Coordinates of ``P`` in a new chart:: + + sage: c_uv. = M.chart() + sage: ch_xy_uv = c_xy.transition_map(c_uv, [x-y, x+y]) + sage: P.coord(c_uv) + (a - b, a + b) + + Coordinates of ``P`` in a third chart:: + + sage: c_wz. = M.chart() + sage: ch_uv_wz = c_uv.transition_map(c_wz, [u^3, v^3]) + sage: P.coord(c_wz, old_chart=c_uv) + (a^3 - 3*a^2*b + 3*a*b^2 - b^3, a^3 + 3*a^2*b + 3*a*b^2 + b^3) + + Actually, in the present case, it is not necessary to specify + ``old_chart='uv'``. Note that the first command erases all + the coordinates except those in the chart ``c_uv``:: + + sage: P.set_coord((a-b, a+b), c_uv) + sage: P._coordinates + {Chart (M, (u, v)): (a - b, a + b)} + sage: P.coord(c_wz) + (a^3 - 3*a^2*b + 3*a*b^2 - b^3, a^3 + 3*a^2*b + 3*a*b^2 + b^3) + sage: P._coordinates # random (dictionary output) + {Chart (M, (u, v)): (a - b, a + b), + Chart (M, (w, z)): (a^3 - 3*a^2*b + 3*a*b^2 - b^3, + a^3 + 3*a^2*b + 3*a*b^2 + b^3)} + + """ + if chart is None: + dom = self.parent() + chart = dom._def_chart + def_chart = chart + else: + dom = chart._domain + def_chart = dom._def_chart + if self not in dom: + raise ValueError("the point does not belong to the domain " + + "of {}".format(chart)) + if chart not in self._coordinates: + # Check whether chart corresponds to a superchart of a chart + # in which the coordinates are known: + for ochart in self._coordinates: + if chart in ochart._supercharts or chart in ochart._subcharts: + self._coordinates[chart] = self._coordinates[ochart] + return self._coordinates[chart] + # If this point is reached, some change of coordinates must be + # performed + if old_chart is not None: + s_old_chart = old_chart + s_chart = chart + else: + # A chart must be found as a starting point of the computation + # The domain's default chart is privileged: + if (def_chart in self._coordinates + and (def_chart, chart) in dom._coord_changes): + old_chart = def_chart + s_old_chart = def_chart + s_chart = chart + else: + for ochart in self._coordinates: + for subchart in ochart._subcharts: + if (subchart, chart) in dom._coord_changes: + old_chart = ochart + s_old_chart = subchart + s_chart = chart + break + if old_chart is not None: + break + if old_chart is None: + # Some search involving the subcharts of chart is + # performed: + for schart in chart._subcharts: + for ochart in self._coordinates: + for subchart in ochart._subcharts: + if (subchart, schart) in dom._coord_changes: + old_chart = ochart + s_old_chart = subchart + s_chart = schart + break + if old_chart is not None: + break + if old_chart is not None: + break + if old_chart is None: + raise ValueError("the coordinates of {}".format(self) + + " in the {}".format(chart) + " cannot be computed " + + "by means of known changes of charts.") + else: + chcoord = dom._coord_changes[(s_old_chart, s_chart)] + self._coordinates[chart] = chcoord(*self._coordinates[old_chart]) + return self._coordinates[chart] + + coord = coordinates + + def set_coordinates(self, coords, chart=None): + r""" + Sets the point coordinates in the specified chart. + + Coordinates with respect to other charts are deleted, in order to + avoid any inconsistency. To keep them, use the method :meth:`add_coord` + instead. + + INPUT: + + - ``coords`` -- the point coordinates (as a tuple or a list) + - ``chart`` -- (default: ``None``) chart in which the coordinates + are given; if none are provided, the coordinates are assumed to + refer to the subset's default chart + + EXAMPLES: + + Setting coordinates to a point on a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: p = M.point() + + We set the coordinates in the manifold's default chart:: + + sage: p.set_coordinates((2,-3)) + sage: p.coordinates() + (2, -3) + sage: X(p) + (2, -3) + + A shortcut for ``set_coordinates`` is ``set_coord``:: + + sage: p.set_coord((2,-3)) + sage: p.coord() + (2, -3) + + Let us introduce a second chart on the manifold:: + + sage: Y. = M.chart() + sage: X_to_Y = X.transition_map(Y, [x+y, x-y]) + + If we set the coordinates of ``p`` in chart ``Y``, those in chart ``X`` + are lost:: + + sage: Y(p) + (-1, 5) + sage: p.set_coord(Y(p), chart=Y) + sage: p._coordinates + {Chart (M, (u, v)): (-1, 5)} + + """ + self._coordinates.clear() + self.add_coord(coords, chart) + + set_coord = set_coordinates + + def add_coordinates(self, coords, chart=None): + r""" + Adds some coordinates in the specified chart. + + The previous coordinates with respect to other charts are kept. To + clear them, use :meth:`set_coord` instead. + + INPUT: + + - ``coords`` -- the point coordinates (as a tuple or a list) + - ``chart`` -- (default: ``None``) chart in which the coordinates + are given; if none are provided, the coordinates are assumed to + refer to the subset's default chart + + .. WARNING:: + + If the point has already coordinates in other charts, it + is the user's responsibility to make sure that the coordinates + to be added are consistent with them. + + EXAMPLES: + + Setting coordinates to a point on a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: p = M.point() + + We give the point some coordinates in the manifold's default chart:: + + sage: p.add_coordinates((2,-3)) + sage: p.coordinates() + (2, -3) + sage: X(p) + (2, -3) + + A shortcut for ``add_coordinates`` is ``add_coord``:: + + sage: p.add_coord((2,-3)) + sage: p.coord() + (2, -3) + + Let us introduce a second chart on the manifold:: + + sage: Y. = M.chart() + sage: X_to_Y = X.transition_map(Y, [x+y, x-y]) + + If we add coordinates for ``p`` in chart ``Y``, those in chart ``X`` + are kept:: + + sage: p.add_coordinates((-1,5), chart=Y) + sage: p._coordinates # random (dictionary output) + {Chart (M, (u, v)): (-1, 5), Chart (M, (x, y)): (2, -3)} + + On the contrary, with the method :meth:`set_coordinates`, the + coordinates in charts different from ``Y`` would be lost:: + + sage: p.set_coordinates((-1,5), chart=Y) + sage: p._coordinates + {Chart (M, (u, v)): (-1, 5)} + + """ + if len(coords) != self.parent().manifold()._dim: + raise ValueError("the number of coordinates must be equal to " + + "the manifold's dimension.") + if chart is None: + chart = self.parent()._def_chart + else: + if chart not in self.parent()._atlas: + raise ValueError("the {}".format(chart) + " has not been " + + "defined on the {}".format(self.parent())) + self._coordinates[chart] = coords + + add_coord = add_coordinates + + def __eq__(self, other): + r""" + Compares the current point with another one. + + EXAMPLES: + + Comparison with coordinates in the same chart:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: p = M((2,-3), chart=X) + sage: q = M((2,-3), chart=X) + sage: p == q + True + sage: q = M((-2,-3), chart=X) + sage: p == q + False + + Comparison with coordinates of other in a subchart:: + + sage: U = M.open_subset('U', coord_def={X: x>0}) + sage: XU = X.restrict(U) + sage: q = U((2,-3), chart=XU) + sage: p == q and q == p + True + sage: q = U((1,-3), chart=XU) + sage: p == q or q == p + False + + Comparison requiring a change of chart:: + + sage: Y. = U.chart() + sage: XU_to_Y = XU.transition_map(Y, (ln(x), x+y)) + sage: XU_to_Y.inverse()(u,v) + (e^u, v - e^u) + sage: q = U((ln(2),-1), chart=Y) + sage: p == q and q == p + True + sage: q = U((ln(3),1), chart=Y) + sage: p == q or q == p + False + + """ + if other is self: + return True + if not isinstance(other, ManifoldPoint): + return False + if other.parent().manifold() != self.parent().manifold(): + return False + # Search for a common chart to compare the coordinates + common_chart = None + # the subset's default chart is privileged: + # FIXME: Make this a better test + if hasattr(self.parent(), '_def_chart'): # self.parent() is open + def_chart = self.parent()._def_chart + else: + def_chart = self.parent().manifold()._def_chart + if def_chart in self._coordinates and def_chart in other._coordinates: + common_chart = def_chart + else: + for chart in self._coordinates: + if chart in other._coordinates: + common_chart = chart + break + if common_chart is None: + # At this stage, a commont chart is searched via a coordinate + # transformation: + for chart in self._coordinates: + try: + other.coordinates(chart) + common_chart = chart + break + except ValueError: + pass + else: + # Attempt a coordinate transformation in the reverse way: + for chart in other._coordinates: + try: + self.coordinates(chart) + common_chart = chart + break + except ValueError: + pass + if common_chart is None: + return False + #!# Another option would be: + # raise ValueError("no common chart has been found to compare " + + # "{} and {}".format(self, other)) + return self._coordinates[common_chart] == other._coordinates[common_chart] + + def __ne__(self, other): + r""" + Non-equality operator. + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: p = M((2,-3), chart=X) + sage: q = M((0,1), chart=X) + sage: p != q + True + sage: p != M((2,-3), chart=X) + False + + """ + return not (self == other) + + def __hash__(self): + r""" + Return the hash of ``self``. + + This hash function is set to constant on a given manifold, to fulfill + Python's credo:: + + p == q ==> hash(p) == hash(q) + + This is necessary since ``p`` and ``q`` may be created in + different coordinate systems and nevertheless be equal. + + .. TODO:: + + Find a better hash function. + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: p = M((2,-3), chart=X) + sage: hash(p) == hash(M) + True + + """ + return hash(self.parent().manifold()) + diff --git a/src/sage/manifolds/structure.py b/src/sage/manifolds/structure.py new file mode 100644 index 00000000000..170bf46c330 --- /dev/null +++ b/src/sage/manifolds/structure.py @@ -0,0 +1,71 @@ +r""" +Manifold Structures + +These classes encode the structure of a manifold. + +AUTHORS: + +- Travis Scrimshaw (2015-11-25): Initial version +""" + +#***************************************************************************** +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger +# Copyright (C) 2015 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.fast_methods import Singleton +from sage.manifolds.chart import Chart, RealChart + +# This is a slight abuse by making this a Singleton, but there is no +# need to have different copies of this object. +class TopologicalStructure(Singleton): + """ + The structure of a topological manifold over a general topological field. + """ + chart = Chart + name = "topological" + + def subcategory(self, cat): + """ + Return the subcategory of ``cat`` corresponding to the structure + of ``self``. + + EXAMPLES:: + + sage: from sage.manifolds.structure import TopologicalStructure + sage: from sage.categories.manifolds import Manifolds + sage: TopologicalStructure().subcategory(Manifolds(RR)) + Category of manifolds over Real Field with 53 bits of precision + + """ + return cat + +class RealTopologicalStructure(Singleton): + """ + The structure of a topological manifold over `\RR`. + """ + chart = RealChart + name = "topological" + + def subcategory(self, cat): + """ + Return the subcategory of ``cat`` corresponding to the structure + of ``self``. + + EXAMPLES:: + + sage: from sage.manifolds.structure import RealTopologicalStructure + sage: from sage.categories.manifolds import Manifolds + sage: RealTopologicalStructure().subcategory(Manifolds(RR)) + Category of manifolds over Real Field with 53 bits of precision + + """ + return cat + diff --git a/src/sage/manifolds/subset.py b/src/sage/manifolds/subset.py new file mode 100644 index 00000000000..9ada6377340 --- /dev/null +++ b/src/sage/manifolds/subset.py @@ -0,0 +1,1149 @@ +r""" +Subsets of Topological Manifolds + +The class :class:`ManifoldSubset` implements generic subsets of a +topological manifold. Open subsets are implemented by the class +:class:`~sage.manifolds.manifold.TopologicalManifold` (since an +open subset of a manifold is a manifold by itself), which inherits +from :class:`ManifoldSubset`. + +AUTHORS: + +- Eric Gourgoulhon, Michal Bejger (2013-2015): initial version +- Travis Scrimshaw (2015): review tweaks; removal of facade parents + +REFERENCES: + +- [Lee11]_ J.M. Lee : *Introduction to Topological Manifolds*, 2nd ed., + Springer (New York) (2011) + +EXAMPLES: + +Two subsets on a manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: a = M.subset('A'); a + Subset A of the 2-dimensional topological manifold M + sage: b = M.subset('B'); b + Subset B of the 2-dimensional topological manifold M + sage: M.list_of_subsets() + [Subset A of the 2-dimensional topological manifold M, + Subset B of the 2-dimensional topological manifold M, + 2-dimensional topological manifold M] + +The intersection of the two subsets:: + + sage: c = a.intersection(b); c + Subset A_inter_B of the 2-dimensional topological manifold M + +Their union:: + + sage: d = a.union(b); d + Subset A_union_B of the 2-dimensional topological manifold M + +Lists of subsets after the above operations:: + + sage: M.list_of_subsets() + [Subset A of the 2-dimensional topological manifold M, + Subset A_inter_B of the 2-dimensional topological manifold M, + Subset A_union_B of the 2-dimensional topological manifold M, + Subset B of the 2-dimensional topological manifold M, + 2-dimensional topological manifold M] + sage: a.list_of_subsets() + [Subset A of the 2-dimensional topological manifold M, + Subset A_inter_B of the 2-dimensional topological manifold M] + sage: c.list_of_subsets() + [Subset A_inter_B of the 2-dimensional topological manifold M] + sage: d.list_of_subsets() + [Subset A of the 2-dimensional topological manifold M, + Subset A_inter_B of the 2-dimensional topological manifold M, + Subset A_union_B of the 2-dimensional topological manifold M, + Subset B of the 2-dimensional topological manifold M] + +""" +#***************************************************************************** +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger +# Copyright (C) 2015 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.categories.sets_cat import Sets +from sage.manifolds.point import ManifoldPoint + +class ManifoldSubset(UniqueRepresentation, Parent): + r""" + Subset of a topological manifold. + + The class :class:`ManifoldSubset` inherits from the generic + class :class:`~sage.structure.parent.Parent`. + The corresponding element class is + :class:`~sage.manifolds.point.ManifoldPoint`. + + Note that open subsets are not implemented directly by this class, but + by the derived class :class:`~sage.manifolds.manifold.TopologicalManifold` + (an open subset of a topological manifold being itself a topological + manifold). + + INPUT: + + - ``manifold`` -- topological manifold on which the subset is defined + - ``name`` -- string; name (symbol) given to the subset + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to + denote the subset; if none are provided, it is set to ``name`` + - ``category`` -- (default: ``None``) to specify the categeory; + if ``None``, the category for generic subsets is used + + EXAMPLES: + + A subset of a manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: from sage.manifolds.subset import ManifoldSubset + sage: A = ManifoldSubset(M, 'A', latex_name=r'\mathcal{A}') + sage: A + Subset A of the 2-dimensional topological manifold M + sage: latex(A) + \mathcal{A} + sage: A.is_subset(M) + True + + Instead of importing :class:`ManifoldSubset` in the global + namespace, it is recommended to use the method + :meth:`~sage.manifolds.subset.ManifoldSubset.subset` to create a new + subset:: + + sage: B = M.subset('B', latex_name=r'\mathcal{B}'); B + Subset B of the 2-dimensional topological manifold M + sage: M.list_of_subsets() + [Subset A of the 2-dimensional topological manifold M, + Subset B of the 2-dimensional topological manifold M, + 2-dimensional topological manifold M] + + The manifold is itself a subset:: + + sage: isinstance(M, ManifoldSubset) + True + sage: M in M.subsets() + True + + Instances of :class:`ManifoldSubset` are parents:: + + sage: isinstance(A, Parent) + True + sage: A.category() + Category of subobjects of sets + sage: p = A.an_element(); p + Point on the 2-dimensional topological manifold M + sage: p.parent() + Subset A of the 2-dimensional topological manifold M + sage: p in A + True + sage: p in M + True + + """ + + Element = ManifoldPoint + + def __init__(self, manifold, name, latex_name=None, category=None): + r""" + Construct a manifold subset. + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A'); A + Subset A of the 2-dimensional topological manifold M + sage: type(A) + + sage: A.category() + Category of subobjects of sets + sage: TestSuite(A).run(skip='_test_elements') + + .. NOTE:: + + ``_test_elements`` cannot be passed without a proper + coordinate definition of the subset. + + """ + if not isinstance(name, str): + raise TypeError("{} is not a string".format(name)) + self._name = name + if latex_name is None: + self._latex_name = self._name + else: + if not isinstance(latex_name, str): + raise TypeError("{} is not a string".format(latex_name)) + self._latex_name = latex_name + if category is None: + category = Sets().Subobjects() + base = None + else: + base = manifold._field + Parent.__init__(self, base=base, category=category) + if self is not manifold: + for dom in manifold._subsets: + if name == dom._name: + raise ValueError("the name '" + name + + "' is already used for another " + + "subset of the {}".format(manifold)) + manifold._subsets.add(self) + self._supersets = set([manifold, self]) # subsets containing self + self._subsets = set([self]) # subsets of self + self._top_subsets = set([self]) # subsets contained in self but not + # in another strict subset of self + self._intersections = {} # dict. of intersections with other subsets + # (key: subset name) + self._unions = {} # dict. of unions with other subsets (key: subset + # name) + self._open_covers = [] # list of open covers of self + self._is_open = False # a priori (may be redifined by subclasses) + self._manifold = manifold # the ambient manifold + + def _repr_(self): + r""" + String representation of the object. + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: A._repr_() + 'Subset A of the 2-dimensional topological manifold M' + sage: repr(A) # indirect doctest + 'Subset A of the 2-dimensional topological manifold M' + + """ + return "Subset {} of the {}".format(self._name, self._manifold) + + def _latex_(self): + r""" + LaTeX representation of ``self``. + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: A._latex_() + 'A' + sage: B = A.subset('B', latex_name=r'\mathcal{B}') + sage: B._latex_() + '\\mathcal{B}' + sage: latex(B) # indirect doctest + \mathcal{B} + + sage: M = Manifold(3, 'M', structure='topological') + sage: M._latex_() + 'M' + sage: latex(M) + M + sage: M = Manifold(3, 'M', latex_name=r'\mathcal{M}', + ....: structure='topological') + sage: M._latex_() + '\\mathcal{M}' + sage: latex(M) # indirect doctest + \mathcal{M} + """ + return self._latex_name + + #### Methods required for any Parent in the category of sets: + + def _element_constructor_(self, coords=None, chart=None, name=None, + latex_name=None, check_coords=True): + r""" + Construct a point in the subset from its coordinates in some chart. + + INPUT: + + - ``coords`` -- (default: ``None``) either (i) the point coordinates + (as a tuple or a list) in the chart ``chart`` or (ii) another point + in the subset + - ``chart`` -- (default: ``None``) chart in which the coordinates + are given; if none are provided, the coordinates are assumed to + refer to the subset's default chart + - ``name`` -- (default: ``None``) name given to the point + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + point; if none are provided, the LaTeX symbol is set to ``name`` + - ``check_coords`` -- (default: ``True``) determines whether + ``coords`` are valid coordinates for the chart ``chart``; + for symbolic coordinates, it is recommended to set ``check_coords`` + to ``False`` + + OUTPUT: + + - an instance of :class:`~sage.manifolds.point.ManifoldPoint` + representing a point in the current subset. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: p = M((-2,3)); p # coord in the default chart + Point on the 2-dimensional topological manifold M + sage: X(p) + (-2, 3) + + A generic subset has no default chart, so the chart must + be explicitly given:: + + sage: A = M.subset('A') + sage: p = A((-2,3), chart=X); p + Point on the 2-dimensional topological manifold M + sage: X(p) + (-2, 3) + sage: p.parent() + Subset A of the 2-dimensional topological manifold M + sage: p in A + True + + Coordinates in a chart with some coordinate restrictions:: + + sage: Y. = M.chart('u:(-1,1) v:(-1,1)') + sage: p = A((0,1/2), chart=Y); p + Point on the 2-dimensional topological manifold M + sage: Y(p) + (0, 1/2) + sage: p = A((0,1/2), chart=Y, check_coords=False); p + Point on the 2-dimensional topological manifold M + sage: Y(p) + (0, 1/2) + sage: p = A((3,1/2), chart=Y) + Traceback (most recent call last): + ... + ValueError: the coordinates (3, 1/2) are not valid on the Chart (M, (u, v)) + + Specifying the name of the point:: + + sage: p = A((-2,3), chart=X, name='p'); p + Point p on the 2-dimensional topological manifold M + + A point as entry:: + + sage: q = A(p); q + Point p on the 2-dimensional topological manifold M + sage: X(q) + (-2, 3) + + """ + if isinstance(coords, ManifoldPoint): + point = coords # for readability + # This should actually never happen by the coercion framework... + if point.parent() is self: + return point + if point in self: + resu = self.element_class(self, name=point._name, + latex_name=point._latex_name) + for chart, coords in point._coordinates.iteritems(): + resu._coordinates[chart] = coords + return resu + else: + raise ValueError("the {}".format(point) + + " is not in {}".format(self)) + return self.element_class(self, coords=coords, chart=chart, + name=name, latex_name=latex_name, + check_coords=check_coords) + + def _an_element_(self): + r""" + Construct some point in the subset. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: A = M.subset('A') + sage: p = A._an_element_(); p + Point on the 2-dimensional topological manifold M + sage: p in A + True + + """ + #!# should be improved... + return self.element_class(self) + + #### End of methods required for any Parent in the category of sets + + def __contains__(self, point): + r""" + Check whether ``point`` is contained in ``self``. + + TESTS:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: A = M.subset('A') + sage: p = A((-2,3), chart=X); p + Point on the 2-dimensional topological manifold M + sage: A.__contains__(p) + True + sage: p in A # indirect doctest + True + sage: A.__contains__(A.an_element()) + True + sage: q = M((0,0), chart=X); q + Point on the 2-dimensional topological manifold M + sage: A.__contains__(q) + False + """ + # for efficiency, a quick test first: + if point.parent() is self: + return True + if point.parent().is_subset(self): + return True + #!# should be improved once coordinate definition have been introduced + # in ManifoldSubset + return False + + def lift(self, p): + r""" + Return the lift of ``p`` to the ambient manifold of ``self``. + + INPUT: + + - ``p`` -- point of the subset + + OUTPUT: + + - the same point, considered as a point of the ambient manifold + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: A = M.open_subset('A', coord_def={X: x>0}) + sage: p = A((1, -2)); p + Point on the 2-dimensional topological manifold M + sage: p.parent() + Open subset A of the 2-dimensional topological manifold M + sage: q = A.lift(p); q + Point on the 2-dimensional topological manifold M + sage: q.parent() + 2-dimensional topological manifold M + sage: q.coord() + (1, -2) + sage: (p == q) and (q == p) + True + + """ + return self._manifold(p) + + def retract(self, p): + r""" + Return the retract of ``p`` to ``self``. + + INPUT: + + - ``p`` -- point of the ambient manifold + + OUTPUT: + + - the same point, considered as a point of the subset + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: A = M.open_subset('A', coord_def={X: x>0}) + sage: p = M((1, -2)); p + Point on the 2-dimensional topological manifold M + sage: p.parent() + 2-dimensional topological manifold M + sage: q = A.retract(p); q + Point on the 2-dimensional topological manifold M + sage: q.parent() + Open subset A of the 2-dimensional topological manifold M + sage: q.coord() + (1, -2) + sage: (q == p) and (p == q) + True + + Of course, if the point does not belong to ``A``, the ``retract`` + method fails:: + + sage: p = M((-1, 3)) # x < 0, so that p is not in A + sage: q = A.retract(p) + Traceback (most recent call last): + ... + ValueError: the Point on the 2-dimensional topological manifold M + is not in Open subset A of the 2-dimensional topological manifold M + + """ + return self(p) + + #### Accessors + + def manifold(self): + r""" + Return the ambient manifold of ``self``. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: A.manifold() + 2-dimensional topological manifold M + sage: A.manifold() is M + True + sage: B = A.subset('B') + sage: B.manifold() is M + True + + An alias is ``ambient``:: + + sage: A.ambient() is A.manifold() + True + + """ + return self._manifold + + ambient = manifold + + def is_open(self): + """ + Return if ``self`` is an open set. + + This method always returns ``False``, since open subsets must be + constructed as instances of the subclass + :class:`~sage.manifolds.manifold.TopologicalManifold` + (which redefines ``is_open``) + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: A.is_open() + False + + """ + return False + + def open_covers(self): + r""" + Return the list of open covers of the current subset. + + If the current subset, `A` say, is a subset of the manifold `M`, an + *open cover* of `A` is list (indexed set) `(U_i)_{i\in I}` of + open subsets of `M` such that + + .. MATH:: + + A \subset \bigcup_{i \in I} U_i. + + If `A` is open, we ask that the above inclusion is actually an + identity: + + .. MATH:: + + A = \bigcup_{i \in I} U_i. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: M.open_covers() + [[2-dimensional topological manifold M]] + sage: U = M.open_subset('U') + sage: U.open_covers() + [[Open subset U of the 2-dimensional topological manifold M]] + sage: A = U.open_subset('A') + sage: B = U.open_subset('B') + sage: U.declare_union(A,B) + sage: U.open_covers() + [[Open subset U of the 2-dimensional topological manifold M], + [Open subset A of the 2-dimensional topological manifold M, + Open subset B of the 2-dimensional topological manifold M]] + sage: V = M.open_subset('V') + sage: M.declare_union(U,V) + sage: M.open_covers() + [[2-dimensional topological manifold M], + [Open subset U of the 2-dimensional topological manifold M, + Open subset V of the 2-dimensional topological manifold M], + [Open subset A of the 2-dimensional topological manifold M, + Open subset B of the 2-dimensional topological manifold M, + Open subset V of the 2-dimensional topological manifold M]] + + """ + return self._open_covers + + def subsets(self): + r""" + Return the set of subsets that have been defined on the + current subset. + + OUTPUT: + + - a Python set containing all the subsets that have been defined on + the current subset + + .. NOTE:: + + To get the subsets as a list, used the method + :meth:`list_of_subsets` instead. + + EXAMPLES: + + Subsets of a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: V = M.subset('V') + sage: M.subsets() # random (set output) + {Subset V of the 2-dimensional topological manifold M, + 2-dimensional topological manifold M, + Open subset U of the 2-dimensional topological manifold M} + sage: type(M.subsets()) + + sage: U in M.subsets() + True + + The method :meth:`list_of_subsets` returns a list (sorted + alphabetically by the subset names) instead of a set:: + + sage: M.list_of_subsets() + [2-dimensional topological manifold M, + Open subset U of the 2-dimensional topological manifold M, + Subset V of the 2-dimensional topological manifold M] + + """ + return frozenset(self._subsets) + + def list_of_subsets(self): + r""" + Return the list of subsets that have been defined on the current + subset. + + The list is sorted by the alphabetical names of the subsets. + + OUTPUT: + + - a list containing all the subsets that have been defined on + the current subset + + .. NOTE:: + + To get the subsets as a Python set, used the method + :meth:`subsets` instead. + + EXAMPLES: + + Subsets of a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: V = M.subset('V') + sage: M.list_of_subsets() + [2-dimensional topological manifold M, + Open subset U of the 2-dimensional topological manifold M, + Subset V of the 2-dimensional topological manifold M] + + The method :meth:`subsets` returns a set instead of a list:: + + sage: M.subsets() # random (set output) + {Subset V of the 2-dimensional topological manifold M, + 2-dimensional topological manifold M, + Open subset U of the 2-dimensional topological manifold M} + + """ + return sorted(self._subsets, key=lambda x: x._name) + + def get_subset(self, name): + r""" + Get a subset by its name. + + The subset must have been previously created by the method + :meth:`subset` (or + :meth:`~sage.manifolds.manifold.TopologicalManifold.open_subset`) + + INPUT: + + - ``name`` -- (string) name of the subset + + OUTPUT: + + - instance of :class:`~sage.manifolds.subset.ManifoldSubset` (or + of the derived class + :class:`~sage.manifolds.manifold.TopologicalManifold` for an open + subset) representing the subset whose name is ``name`` + + EXAMPLES:: + + sage: M = Manifold(4, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = A.subset('B') + sage: U = M.open_subset('U') + sage: M.list_of_subsets() + [Subset A of the 4-dimensional topological manifold M, + Subset B of the 4-dimensional topological manifold M, + 4-dimensional topological manifold M, + Open subset U of the 4-dimensional topological manifold M] + sage: M.get_subset('A') + Subset A of the 4-dimensional topological manifold M + sage: M.get_subset('A') is A + True + sage: M.get_subset('B') is B + True + sage: A.get_subset('B') is B + True + sage: M.get_subset('U') + Open subset U of the 4-dimensional topological manifold M + sage: M.get_subset('U') is U + True + + """ + for ss in self._subsets: + if ss._name == name: + return ss + raise ValueError("no subset of name '{}' found".format(name)) + + #### End of accessors + + def is_subset(self, other): + r""" + Return ``True`` if and only if ``self`` is included in ``other``. + + EXAMPLES: + + Subsets on a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: a = M.subset('A') + sage: b = a.subset('B') + sage: c = M.subset('C') + sage: a.is_subset(M) + True + sage: b.is_subset(a) + True + sage: b.is_subset(M) + True + sage: a.is_subset(b) + False + sage: c.is_subset(a) + False + """ + return self in other._subsets + + def declare_union(self, dom1, dom2): + r""" + Declare that the current subset is the union of two subsets. + + Suppose `U` is the current subset, then this method declares + that `U` + + .. MATH:: + + U = U_1 \cup U_2, + + where `U_1 \subset U` and `U_2 \subset U`. + + INPUT: + + - ``dom1`` -- the subset `U_1` + - ``dom2`` -- the subset `U_2` + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: M.declare_union(A, B) + sage: A.union(B) + 2-dimensional topological manifold M + + """ + if dom1 == dom2: + if dom1 != self: + raise ValueError("the union of two identical sets must be " + + "this set") + return + if not dom1.is_subset(self): + raise TypeError("the {} is not a subset of ".format(dom1) + + "the {}".format(self)) + if not dom2.is_subset(self): + raise TypeError("the {} is not a subset of ".format(dom2) + + "the {}".format(self)) + dom1._unions[dom2._name] = self + dom2._unions[dom1._name] = self + for oc1 in dom1._open_covers: + for oc2 in dom2._open_covers: + oc = oc1[:] + for s in oc2: + if s not in oc: + oc.append(s) + self._open_covers.append(oc) + + def point(self, coords=None, chart=None, name=None, latex_name=None): + r""" + Define a point in ``self``. + + See :class:`~sage.manifolds.point.ManifoldPoint` for a + complete documentation. + + INPUT: + + - ``coords`` -- the point coordinates (as a tuple or a list) in the + chart specified by ``chart`` + - ``chart`` -- (default: ``None``) chart in which the point + coordinates are given; if ``None``, the coordinates are assumed + to refer to the default chart of the current subset + - ``name`` -- (default: ``None``) name given to the point + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + point; if ``None``, the LaTeX symbol is set to ``name`` + + OUTPUT: + + - the declared point, as an instance of + :class:`~sage.manifolds.point.ManifoldPoint` + + EXAMPLES: + + Points on a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: c_xy. = M.chart() + sage: p = M.point((1,2), name='p'); p + Point p on the 2-dimensional topological manifold M + sage: p in M + True + sage: a = M.open_subset('A') + sage: c_uv. = a.chart() + sage: q = a.point((-1,0), name='q'); q + Point q on the 2-dimensional topological manifold M + sage: q in a + True + sage: p._coordinates + {Chart (M, (x, y)): (1, 2)} + sage: q._coordinates + {Chart (A, (u, v)): (-1, 0)} + """ + return self.element_class(self, coords=coords, chart=chart, + name=name, latex_name=latex_name) + + #### Construction of new sets from self: + + def subset(self, name, latex_name=None, is_open=False): + r""" + Create a subset of the current subset. + + INPUT: + + - ``name`` -- name given to the subset + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote + the subset; if none are provided, it is set to ``name`` + - ``is_open`` -- (default: ``False``) if ``True``, the created subset + is assumed to be open with respect to the manifold's topology + + OUTPUT: + + - the subset, as an instance of :class:`ManifoldSubset`, or + of the derived class + :class:`~sage.manifolds.manifold.TopologicalManifold` + if ``is_open`` is ``True`` + + EXAMPLES: + + Creating a subset of a manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: a = M.subset('A'); a + Subset A of the 2-dimensional topological manifold M + + Creating a subset of ``A``:: + + sage: b = a.subset('B', latex_name=r'\mathcal{B}'); b + Subset B of the 2-dimensional topological manifold M + sage: latex(b) + \mathcal{B} + + We have then:: + + sage: b.is_subset(a) + True + sage: b in a.subsets() + True + """ + if is_open: + return self.open_subset(name, latex_name=latex_name) + res = ManifoldSubset(self._manifold, name, latex_name=latex_name) + res._supersets.update(self._supersets) + for sd in self._supersets: + sd._subsets.add(res) + self._top_subsets.add(res) + return res + + def superset(self, name, latex_name=None, is_open=False): + r""" + Create a superset of the current subset. + + A *superset* is a manifold subset in which the current subset is + included. + + INPUT: + + - ``name`` -- name given to the superset + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote + the superset; if none are provided, it is set to ``name`` + - ``is_open`` -- (default: ``False``) if ``True``, the created subset + is assumed to be open with respect to the manifold's topology + + OUTPUT: + + - the superset, as an instance of :class:`ManifoldSubset` or + of the derived class + :class:`~sage.manifolds.manifold.TopologicalManifold` + if ``is_open`` is ``True`` + + EXAMPLES: + + Creating some superset of a given subset:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: a = M.subset('A') + sage: b = a.superset('B'); b + Subset B of the 2-dimensional topological manifold M + sage: b.list_of_subsets() + [Subset A of the 2-dimensional topological manifold M, + Subset B of the 2-dimensional topological manifold M] + sage: a._supersets # random (set output) + {Subset B of the 2-dimensional topological manifold M, + Subset A of the 2-dimensional topological manifold M, + 2-dimensional topological manifold M} + + The superset of the whole manifold is itself:: + + sage: M.superset('SM') is M + True + + Two supersets of a given subset are a priori different:: + + sage: c = a.superset('C') + sage: c == b + False + + """ + if self is self._manifold: + return self + if is_open: + res = self._manifold.open_subset(name, latex_name=latex_name) + else: + res = ManifoldSubset(self._manifold, name, latex_name=latex_name) + res._subsets.update(self._subsets) + for sd in self._subsets: + sd._supersets.add(res) + if is_open and self._is_open: + res._atlas = list(self._atlas) + res._top_charts = list(self._top_charts) + res._coord_changes = dict(self._coord_changes) + res._def_chart = self._def_chart + return res + + def intersection(self, other, name=None, latex_name=None): + r""" + Return the intersection of the current subset with another subset. + + INPUT: + + - ``other`` -- another subset of the same manifold + - ``name`` -- (default: ``None``) name given to the intersection + in the case the latter has to be created; the default is + ``self._name`` inter ``other._name`` + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + intersection in the case the latter has to be created; the default + is built upon the symbol `\cap` + + OUTPUT: + + - instance of :class:`ManifoldSubset` representing the + subset that is the intersection of the current subset with ``other`` + + EXAMPLES: + + Intersection of two subsets:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: a = M.subset('A') + sage: b = M.subset('B') + sage: c = a.intersection(b); c + Subset A_inter_B of the 2-dimensional topological manifold M + sage: a.list_of_subsets() + [Subset A of the 2-dimensional topological manifold M, + Subset A_inter_B of the 2-dimensional topological manifold M] + sage: b.list_of_subsets() + [Subset A_inter_B of the 2-dimensional topological manifold M, + Subset B of the 2-dimensional topological manifold M] + sage: c._supersets # random (set output) + {Subset B of the 2-dimensional topological manifold M, + Subset A_inter_B of the 2-dimensional topological manifold M, + Subset A of the 2-dimensional topological manifold M, + 2-dimensional topological manifold M} + + Some checks:: + + sage: (a.intersection(b)).is_subset(a) + True + sage: (a.intersection(b)).is_subset(a) + True + sage: a.intersection(b) is b.intersection(a) + True + sage: a.intersection(a.intersection(b)) is a.intersection(b) + True + sage: (a.intersection(b)).intersection(a) is a.intersection(b) + True + sage: M.intersection(a) is a + True + sage: a.intersection(M) is a + True + + """ + if other._manifold != self._manifold: + raise ValueError("the two subsets do not belong to the same manifold") + # Particular cases: + if self is self._manifold: + return other + if other is self._manifold: + return self + if self in other._subsets: + return self + if other in self._subsets: + return other + # Generic case: + if other._name in self._intersections: + # the intersection has already been created: + return self._intersections[other._name] + else: + # the intersection must be created: + if latex_name is None: + if name is None: + latex_name = self._latex_name + r'\cap ' + other._latex_name + else: + latex_name = name + if name is None: + name = self._name + "_inter_" + other._name + if self._is_open and other._is_open: + res = self.open_subset(name, latex_name=latex_name) + else: + res = self.subset(name, latex_name=latex_name) + res._supersets.update(other._supersets) + for sd in other._supersets: + sd._subsets.add(res) + other._top_subsets.add(res) + self._intersections[other._name] = res + other._intersections[self._name] = res + return res + + def union(self, other, name=None, latex_name=None): + r""" + Return the union of the current subset with another subset. + + INPUT: + + - ``other`` -- another subset of the same manifold + - ``name`` -- (default: ``None``) name given to the union in the + case the latter has to be created; the default is + ``self._name`` union ``other._name`` + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + union in the case the latter has to be created; the default + is built upon the symbol `\cup` + + OUTPUT: + + - instance of :class:`ManifoldSubset` representing the + subset that is the union of the current subset with ``other`` + + EXAMPLES: + + Union of two subsets:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: a = M.subset('A') + sage: b = M.subset('B') + sage: c = a.union(b); c + Subset A_union_B of the 2-dimensional topological manifold M + sage: a._supersets # random (set output) + set([subset 'A_union_B' of the 2-dimensional manifold 'M', + 2-dimensional manifold 'M', + subset 'A' of the 2-dimensional manifold 'M']) + sage: b._supersets # random (set output) + set([subset 'B' of the 2-dimensional manifold 'M', + 2-dimensional manifold 'M', + subset 'A_union_B' of the 2-dimensional manifold 'M']) + sage: c._subsets # random (set output) + set([subset 'A_union_B' of the 2-dimensional manifold 'M', + subset 'A' of the 2-dimensional manifold 'M', + subset 'B' of the 2-dimensional manifold 'M']) + + Some checks:: + + sage: a.is_subset(a.union(b)) + True + sage: b.is_subset(a.union(b)) + True + sage: a.union(b) is b.union(a) + True + sage: a.union(a.union(b)) is a.union(b) + True + sage: (a.union(b)).union(a) is a.union(b) + True + sage: a.union(M) is M + True + sage: M.union(a) is M + True + + """ + if other._manifold != self._manifold: + raise ValueError("the two subsets do not belong to the same manifold") + # Particular cases: + if (self is self._manifold) or (other is self._manifold): + return self._manifold + if self in other._subsets: + return other + if other in self._subsets: + return self + # Generic case: + if other._name in self._unions: + # the union has already been created: + return self._unions[other._name] + else: + # the union must be created: + if latex_name is None: + if name is None: + latex_name = self._latex_name + r'\cup ' + other._latex_name + else: + latex_name = name + if name is None: + name = self._name + "_union_" + other._name + res_open = self._is_open and other._is_open + res = self.superset(name, latex_name, is_open=res_open) + res._subsets.update(other._subsets) + res._top_subsets.add(self) + res._top_subsets.add(other) + for sd in other._subsets: + sd._supersets.add(res) + if res._is_open: + for chart in other._atlas: + if chart not in res._atlas: + res._atlas.append(chart) + for chart in other._top_charts: + if chart not in res._top_charts: + res._top_charts.append(chart) + res._coord_changes.update(other._coord_changes) + self._unions[other._name] = res + other._unions[self._name] = res + # Open covers of the union: + for oc1 in self._open_covers: + for oc2 in other._open_covers: + oc = oc1[:] + for s in oc2: + if s not in oc: + oc.append(s) + res._open_covers.append(oc) + return res + + #### End of construction of new sets from self + diff --git a/src/sage/matrix/constructor.py b/src/sage/matrix/constructor.py index b6a93e5abd6..84c22c038ab 100644 --- a/src/sage/matrix/constructor.py +++ b/src/sage/matrix/constructor.py @@ -21,6 +21,7 @@ from sage.rings.ring import is_Ring import sage.matrix.matrix_space as matrix_space from sage.modules.free_module_element import vector +from sage.structure.coerce import py_scalar_parent from sage.structure.element import is_Vector from sage.structure.sequence import Sequence from sage.rings.real_double import RDF @@ -792,18 +793,22 @@ def prepare(w): Traceback (most recent call last): ... TypeError: unable to find a common ring for all elements + + TESTS: + + Check that :trac:`19920` is fixed:: + + sage: import numpy + sage: matrix([[numpy.int8(1)]]) + [1] """ - if 0 == len(w): + if not w: return Sequence([], rings.ZZ), rings.ZZ entries = Sequence(w) ring = entries.universe() - if ring is int or ring is long: - ring = rings.ZZ - elif ring is float: - ring = rings.RDF - elif ring is complex: - ring = rings.CDF - elif not is_Ring(ring): + if isinstance(ring,type): + ring = py_scalar_parent(ring) + if not is_Ring(ring): raise TypeError("unable to find a common ring for all elements") return entries, ring @@ -1018,7 +1023,7 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', *args, **kwds) eigenvectors, if computed by hand, will have only integer entries. - - ``*args, **kwds`` - arguments and keywords to describe additional + - ``*args, **kwds`` - arguments and keywords to describe additional properties. See more detailed documentation below. .. warning:: @@ -1175,9 +1180,14 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', *args, **kwds) The default implementation of :meth:`~sage.matrix.matrix2.randomize` relies on the ``random_element()`` method for the base ring. The ``density`` and - ``sparse`` keywords behave as described above. :: + ``sparse`` keywords behave as described above. Since we have a different + randomisation when using the optional meataxe package, we have to make sure + that we use the default implementation in this test:: sage: K.=FiniteField(3^2) + sage: from sage.matrix.matrix_generic_dense import Matrix_generic_dense + sage: MS = MatrixSpace(K, 2, 5) + sage: MS._MatrixSpace__matrix_class = Matrix_generic_dense sage: random_matrix(K, 2, 5) [ 1 a 1 2*a + 1 2] [ 2*a a + 2 0 2 1] diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index f418222ebe3..0c04197c300 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -31,9 +31,11 @@ include "sage/ext/python.pxi" include "sage/ext/interrupt.pxi" from sage.misc.randstate cimport randstate, current_randstate +from sage.structure.coerce cimport py_scalar_parent from sage.structure.sequence import Sequence from sage.structure.element import is_Vector from sage.misc.misc import verbose, get_verbose +from sage.rings.ring import is_Ring 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 @@ -779,7 +781,7 @@ cdef class Matrix(matrix1.Matrix): 36.0000000000000 The permanent above is directed to the Sloane's sequence :oeis:`A079908` - ("The Dancing School Problems") for which the third term is 36: + ("The Dancing School Problems") for which the third term is 36: :: @@ -1876,6 +1878,12 @@ cdef class Matrix(matrix1.Matrix): sage: m.apply_map(lambda x: x*x, sparse=True).parent() Full MatrixSpace of 0 by 0 sparse matrices over Integer Ring + + Check that :trac:`19920` is fixed:: + + sage: matrix.ones(2).apply_map(lambda x: int(-3)) + [-3 -3] + [-3 -3] """ if self._nrows == 0 or self._ncols == 0: if sparse is None or self.is_sparse() is sparse: @@ -1894,6 +1902,11 @@ cdef class Matrix(matrix1.Matrix): if R is None: R = sage.structure.sequence.Sequence(values).universe() + if isinstance(R, type): + R = py_scalar_parent(R) + if not is_Ring(R): + raise TypeError("unable to find a common ring for all elements") + if sparse is None or sparse is self.is_sparse(): M = self.parent().change_ring(R) else: @@ -3352,8 +3365,6 @@ cdef class Matrix(matrix1.Matrix): verbose ... verbose 1 () computing right kernel matrix over an arbitrary field for 3x4 matrix ... - verbose 1 () done computing right kernel matrix over an arbitrary field for 3x4 matrix - ... Vector space of degree 4 and dimension 2 over Finite Field in a of size 5^2 Basis matrix: [ 1 0 3*a + 4 2*a + 2] @@ -3800,13 +3811,25 @@ cdef class Matrix(matrix1.Matrix): [ 0 1 2*a 3*a + 3] sage: A*K.basis_matrix().transpose() == zero_matrix(F, 3, 2) True - sage: B = copy(A) + + In the following test, we have to force usage of + :class:`~sage.matrix.matrix_generic_dense.Matrix_generic_dense`, + since the option ``basis = 'pivot'`` would simply yield the same + result as the previous test, if the optional meataxe package is + installed. :: + + sage: from sage.matrix.matrix_generic_dense import Matrix_generic_dense + sage: B = Matrix_generic_dense(A.parent(), A.list(), False, False) sage: P = B.right_kernel(basis = 'pivot'); P Vector space of degree 4 and dimension 2 over Finite Field in a of size 5^2 User basis matrix: [ 4 4 1 0] [ a + 2 3*a + 3 0 1] - sage: B*P.basis_matrix().transpose() == zero_matrix(F, 3, 2) + + If the optional meataxe package is installed, we again have to make sure + to work with a copy of B that has the same type as ``P.basis_matrix()``:: + + sage: B.parent()(B.list())*P.basis_matrix().transpose() == zero_matrix(F, 3, 2) True sage: K == P True @@ -8479,7 +8502,7 @@ explicitly setting the argument to `True` or `False` will avoid this message.""" sage: filename = tmp_filename(ext='.png') sage: img.save(filename) - sage: open(filename).read().startswith('\x89PNG') + sage: open(filename).read().startswith('\x89PNG') True """ cdef int x, y, _x, _y, v, bi, bisq @@ -12200,7 +12223,7 @@ explicitly setting the argument to `True` or `False` will avoid this message.""" r""" Determines if a real or symmetric matrix is positive definite. - A square matrix `A` is postive definite if it is + A square matrix `A` is positive definite if it is symmetric with real entries or Hermitan with complex entries, and for every non-zero vector `\vec{x}` @@ -12239,7 +12262,7 @@ explicitly setting the argument to `True` or `False` will avoid this message.""" A real symmetric matrix that is positive definite, as evidenced by the positive entries for the diagonal - matrix of the indefinite factorization and the postive + matrix of the indefinite factorization and the positive determinants of the leading principal submatrices. :: sage: A = matrix(QQ, [[ 4, -2, 4, 2], diff --git a/src/sage/matrix/matrix_gfpn_dense.pxd b/src/sage/matrix/matrix_gfpn_dense.pxd new file mode 100644 index 00000000000..34487536b14 --- /dev/null +++ b/src/sage/matrix/matrix_gfpn_dense.pxd @@ -0,0 +1,29 @@ +#***************************************************************************** +# Copyright (C) 2015 Simon King +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +cdef class FieldConverter_class: + cdef object field # that's a function converting an int to a field element + cpdef object int_to_field(self, int x) + cpdef int field_to_int(self, x) + +from sage.matrix.matrix_dense cimport Matrix_dense +from sage.structure.element cimport Matrix +from sage.libs.meataxe cimport * + +cdef class Matrix_gfpn_dense(Matrix_dense): + cdef Matrix_t *Data + cdef FieldConverter_class _converter + cdef Matrix_gfpn_dense _new(self, Py_ssize_t nrows, Py_ssize_t ncols) + cdef set_unsafe_int(self, Py_ssize_t i, Py_ssize_t j, int value) + cdef inline int get_unsafe_int(self, Py_ssize_t i, Py_ssize_t j) + cdef list _rowlist_(self, i, j=*) + cdef Matrix _matrix_times_matrix_(self, Matrix right) + cpdef Matrix_gfpn_dense _multiply_classical(Matrix_gfpn_dense self, Matrix_gfpn_dense right) + cpdef Matrix_gfpn_dense _multiply_strassen(Matrix_gfpn_dense self, Matrix_gfpn_dense right, cutoff=*) diff --git a/src/sage/matrix/matrix_gfpn_dense.pyx b/src/sage/matrix/matrix_gfpn_dense.pyx new file mode 100644 index 00000000000..7f5c1103316 --- /dev/null +++ b/src/sage/matrix/matrix_gfpn_dense.pyx @@ -0,0 +1,1630 @@ +r""" +Dense Matrices over `\mathbb F_q`, with `q<255` + +This module is a wrapper for version 2.4.24 of the Aachen +`C-MeatAxe `_, +improved by an implementation of the Winograd-Strassen multiplication +algorithm. It provides matrices over the finite field `\mathbb F_q`, +where `q\le 255`. + +By default, it is only used when `q` is odd and not prime, because other +matrix implementations in SageMath perform better for prime fields or in +characteristic two. + +AUTHORS: + +- Simon King (2015-09): initial version + +""" + +#***************************************************************************** +# Copyright (C) 2015 Simon King +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + + +## Define an environment variable that enables MeatAxe to find +## its multiplication tables. + +from sage.env import DOT_SAGE +import os +cdef extern from "Python.h": + object PyString_FromStringAndSize(char *s, Py_ssize_t len) + char* PyString_AsString(object string) +MtxLibDir = PyString_AsString(os.path.join(DOT_SAGE,'meataxe')) + +#################### +# +# import sage types +# +#################### + +from sage.rings.integer import Integer +from sage.rings.finite_rings.finite_field_constructor import GF +from sage.rings.finite_rings.integer_mod import IntegerMod_int +from sage.matrix.constructor import random_matrix +from sage.rings.arith import is_prime_power, factor +from sage.matrix.matrix_space import MatrixSpace +from sage.misc.randstate import current_randstate +from sage.misc.cachefunc import cached_method, cached_function +from sage.structure.element cimport Element, ModuleElement, RingElement, Matrix + +from libc.stdlib cimport free +from sage.ext.memory cimport check_realloc +from libc.string cimport memset, memcpy + +cimport sage.matrix.matrix0 + +include 'sage/ext/stdsage.pxi' + +#################### +# +# auxiliary functions +# +#################### +import sys +from libc.string cimport memcpy + +# Fast conversion from field to int and int to field +cdef class FieldConverter_class: + """ + An auxiliary class, used to convert between and finite field element + + This class is for non-prime fields only. The method + :meth:`int_to_field` exists for speed. The method + :meth:`field_to_int` exists in order to have a common interface + for elements of prime and non-prime fields; see + :class:`PrimeFieldConverter_class`. + + EXAMPLE:: + + sage: from sage.matrix.matrix_gfpn_dense import FieldConverter_class # optional: meataxe + sage: F. = GF(125) + sage: C = FieldConverter_class(F) # optional: meataxe + sage: C.int_to_field(15) # optional: meataxe + 3*y + sage: F.fetch_int(15) # optional: meataxe + 3*y + sage: %timeit C.int_to_field(15) # not tested + 625 loops, best of 3: 1.04 µs per loop + sage: %timeit F.fetch_int(15) # not tested + 625 loops, best of 3: 3.97 µs per loop + sage: C.field_to_int(y) # optional: meataxe + 5 + sage: y.integer_representation() + 5 + + """ + def __init__(self, field): + """ + INPUT: + + A finite *non-prime* field. This assumption is not tested. + + EXAMPLE:: + + sage: from sage.matrix.matrix_gfpn_dense import FieldConverter_class # optional: meataxe + sage: F. = GF(125) + sage: C = FieldConverter_class(F) # optional: meataxe + sage: C.int_to_field(15) # optional: meataxe + 3*y + sage: F.fetch_int(15) + 3*y + sage: C.field_to_int(y) # optional: meataxe + 5 + sage: y.integer_representation() + 5 + + """ + self.field = field._cache.fetch_int + cpdef object int_to_field(self, int x): + """ + Fetch a python int into the field. + + EXAMPLE:: + + sage: from sage.matrix.matrix_gfpn_dense import FieldConverter_class # optional: meataxe + sage: F. = GF(125) + sage: C = FieldConverter_class(F) # optional: meataxe + sage: C.int_to_field(15) # optional: meataxe + 3*y + sage: F.fetch_int(15) + 3*y + + """ + return self.field(x) + cpdef int field_to_int(self, x): + """ + Represent a field element by a python int. + + EXAMPLE:: + + sage: from sage.matrix.matrix_gfpn_dense import FieldConverter_class # optional: meataxe + sage: F. = GF(125) + sage: C = FieldConverter_class(F) # optional: meataxe + sage: C.field_to_int(y) # optional: meataxe + 5 + sage: y.integer_representation() + 5 + + """ + return x.integer_representation() + +cdef class PrimeFieldConverter_class(FieldConverter_class): + """ + An auxiliary class, used to convert between and finite field element + + This class is for prime fields only. The methods + :meth:`int_to_field` and :meth:`field_to_int` exist in order to + have a common interface for elements of prime and non-prime fields; + see :class:`FieldConverter_class`. + + EXAMPLE:: + + sage: from sage.matrix.matrix_gfpn_dense import PrimeFieldConverter_class # optional: meataxe + sage: F = GF(5) + sage: C = PrimeFieldConverter_class(F) # optional: meataxe + sage: C.int_to_field(int(2)) # optional: meataxe + 2 + sage: F(2) + 2 + sage: C.field_to_int(F(2)) # optional: meataxe + 2 + sage: int(F(2)) + 2 + + """ + def __init__(self, field): + """ + INPUT: + + A finite *prime* field. This assumption is not tested. + + EXAMPLE:: + + sage: from sage.matrix.matrix_gfpn_dense import PrimeFieldConverter_class # optional: meataxe + sage: F = GF(5) + sage: C = PrimeFieldConverter_class(F) # optional: meataxe + sage: C.int_to_field(int(2)) # optional: meataxe + 2 + sage: F(2) + 2 + sage: C.field_to_int(F(2)) # optional: meataxe + 2 + sage: int(F(2)) + 2 + + """ + self.field = field + cpdef object int_to_field(self, int x): + """ + Fetch a python int into the field. + + EXAMPLE:: + + sage: from sage.matrix.matrix_gfpn_dense import PrimeFieldConverter_class # optional: meataxe + sage: F = GF(5) + sage: C = PrimeFieldConverter_class(F) # optional: meataxe + sage: C.int_to_field(int(2)) # optional: meataxe + 2 + sage: F(2) + 2 + + """ + return IntegerMod_int(self.field, x) + cpdef int field_to_int(self, x): + """ + Represent a field element by a python int. + + EXAMPLE:: + + sage: from sage.matrix.matrix_gfpn_dense import PrimeFieldConverter_class # optional: meataxe + sage: F = GF(5) + sage: C = PrimeFieldConverter_class(F) # optional: meataxe + sage: C.field_to_int(F(2)) # optional: meataxe + 2 + sage: int(F(2)) + 2 + + """ + return int(x) + +cdef dict _converter_cache = {} +cdef FieldConverter_class FieldConverter(field): + """ + Return a :class:`FieldConverter_class` or :class:`PrimeFieldConverter_class` instance, + depending whether the field is prime or not. + + EXAMPLE:: + + sage: MS = MatrixSpace(GF(5^3,'y'),2) + sage: A = MS.random_element() + sage: A*2 == A+A # indirect doctest + True + sage: A = MS.random_element() + sage: A*2 == A+A + True + + """ + try: + return _converter_cache[field] + except KeyError: + if field.is_prime_field(): + return _converter_cache.setdefault(field, PrimeFieldConverter_class(field)) + return _converter_cache.setdefault(field, FieldConverter_class(field)) + +###################################### +## Error handling for MeatAxe, to prevent immediate exit of the program + +cdef dict ErrMsg = { + "Not enough memory": MemoryError, + "Time limit exceeded": RuntimeError, + "Division by zero": ZeroDivisionError, + "Bad file format": IOError, + "Bad argument": ValueError, + "Argument out of range": IndexError, + + "Matrix not in echelon form": ValueError, + "Matrix not square": ArithmeticError, + "Incompatible objects": TypeError, + + "Bad syntax, try `-help'": SyntaxError, + "Bad usage of option, try `-help'": ValueError, + "Bad number of arguments, try `-help'": ValueError, + + "Not a matrix": TypeError, + "Not a permutation": TypeError +} + +from cpython.exc cimport PyErr_SetObject + +cdef void ErrorHandler(MtxErrorRecord_t *err): + PyErr_SetObject(ErrMsg.get(err.Text, SystemError), "{} in file {} (line {})".format(err.Text, err.FileInfo.BaseName, err.LineNo)) + +MtxSetErrorHandler(ErrorHandler) + +###################################### +## +## Wrapper for MeatAxe matrices +## +###################################### + +cdef class Matrix_gfpn_dense(Matrix_dense): + r""" + Dense matrices over `\mathbb F_q`, `q<255` odd and not prime. + + NOTE: + + This class uses a major modification of the Aachen C-MeatAxe + as backend. In principle, it would also work for prime fields + and in characteristic two. However, other matrices in Sage, + relying on linbox, m4ri or m4rie, are more efficient in these + cases. + + EXAMPLES:: + + sage: M = MatrixSpace(GF(25,'z'),2,3)([1,2,3,4,5,6]) + sage: print M + [1 2 3] + [4 0 1] + sage: type(M) # optional: meataxe + + + The documentation of the ``__init__`` methods shows further + ways of creating a :class:`Matrix_gfpn_dense` instance. + However, these should only be of internal use. + + """ +################## +## Init, Dealloc, Copy + def __cinit__(self, parent=None, entries=None, *args, **kwds): + """ + TESTS:: + + sage: from sage.matrix.matrix_gfpn_dense import Matrix_gfpn_dense # optional: meataxe + sage: Matrix_gfpn_dense.__new__(Matrix_gfpn_dense) # optional: meataxe + [] + sage: Matrix_gfpn_dense(MatrixSpace(GF(64,'z'),4), None) # optional: meataxe + [0 0 0 0] + [0 0 0 0] + [0 0 0 0] + [0 0 0 0] + + """ + if parent is None: # this makes Matrix_gfpn_dense.__new__(Matrix_gfpn_dense) work, + # returning a non-initialised matrix + return + if isinstance(parent, basestring): # this allows to provide a file when initialising a matrix + return + cdef int f = parent.base_ring().order() + cdef int nrows = parent.nrows() + cdef int ncols = parent.ncols() + self.Data = MatAlloc(f, nrows, ncols) + + def __dealloc__(self): + """ + TESTS:: + + sage: from sage.matrix.matrix_gfpn_dense import Matrix_gfpn_dense # optional: meataxe + sage: Matrix_gfpn_dense.__new__(Matrix_gfpn_dense) # optional: meataxe + [] + sage: M = None + sage: M = Matrix_gfpn_dense(MatrixSpace(GF(64,'z'),4), None) # optional: meataxe + sage: del M # indirect doctest + """ + if self.Data != NULL: + MatFree(self.Data) + self.Data = NULL + + def __init__(self, parent, data=None, mutable=True, copy=False, coerce=False): + """ + Matrix extension class using libmeataxe as backend + + INPUT: + + Instances of this class can be created by providing one of + the following input data, where ``q<255`` is a prime power, + ``m,n`` are non-negative integers, and `a_{11},...,a_{mn}` + can be coerced into ``GF(q)``. Note that a user should + create these instances via the matrix constructors; what + we explain here is for internal use only! + + - None => empty matrix over an unspecified field (used for unpickling) + - a string ``f`` ==> load matrix from the file named ``f`` + - A matrix space of `m\\times n` matrices over GF(q) and either + + - a list `[a_{11},a_{12},...,a_{1n},a_{21},...,a_{m1},...,a_{mn}]`, + which results in a matrix with the given marks + - ``None``, which is the fastest way to creata a zero matrix. + - an element of GF(q), which results in a diagonal matrix with the + given element on the diagonal. + + If the optional parameter ``mutable`` is ``False`` (by default, + it is ``True``), the resulting matrix can not be changed, and + it can be used as key in a Python dictionary. + + The arguments ``copy`` and ``coerce`` are ignored, they are only + here for a common interface with :class:`~sage.matrix.matrix.Matrix`. + + EXAMPLES:: + + sage: from sage.matrix.matrix_gfpn_dense import Matrix_gfpn_dense # optional: meataxe + + 1. Creating an empty matrix:: + + sage: Matrix_gfpn_dense(None) # optional: meataxe + [] + + 2. Creating a zero (3x2)-matrix:: + + sage: Matrix_gfpn_dense(MatrixSpace(GF(4,'z'),3,2)) # optional: meataxe + [0 0] + [0 0] + [0 0] + + 3. Creating a matrix from a list or list of lists:: + + sage: Matrix_gfpn_dense(MatrixSpace(GF(5),2,3),[1,2,3,4,5,6]) # optional: meataxe + [1 2 3] + [4 0 1] + sage: Matrix_gfpn_dense(MatrixSpace(GF(5),2,3),[[1,2,3],[4,5,6]]) # optional: meataxe + [1 2 3] + [4 0 1] + + 4. Creating a diagonal matrix:: + + sage: M = Matrix_gfpn_dense(MatrixSpace(GF(7),5),2); M # optional: meataxe + [2 0 0 0 0] + [0 2 0 0 0] + [0 0 2 0 0] + [0 0 0 2 0] + [0 0 0 0 2] + + 5. Creating a matrix from a file in MeatAxe format. + + This is not tested. + + TESTS:: + + sage: MS = MatrixSpace(GF(125,'y'),2) # indirect doctest + sage: A = MS(0) + sage: A.left_kernel() + Vector space of degree 2 and dimension 2 over Finite Field in y of size 5^3 + Basis matrix: + [1 0] + [0 1] + sage: A.right_kernel() + Vector space of degree 2 and dimension 2 over Finite Field in y of size 5^3 + Basis matrix: + [1 0] + [0 1] + + """ + if parent is None: + self._is_immutable = False + self._ncols = 0 + self._nrows = 0 + self._cache = {} + return + if isinstance(parent, basestring): # load from file + FILE = os.path.realpath(parent) + try: + fsock = open(FILE,"rb",0) + fsock.close() + except (OSError,IOError): + return + self.Data = MatLoad(FILE) + FfSetField(self.Data.Field) + B = GF(self.Data.Field, 'z') + parent = MatrixSpace(B, self.Data.Nor, self.Data.Noc) + self._is_immutable = False + self._parent = parent + self._base_ring = B + self._converter = FieldConverter(B) + self._ncols = self.Data.Noc + self._nrows = self.Data.Nor + self._cache = {} + return + + if not self.Data: # should have been initialised by __cinit__ + raise MemoryError, "Error allocating memory for MeatAxe matrix" + Matrix_dense.__init__(self, parent) + self._is_immutable = not mutable + B = self._base_ring + self._converter = FieldConverter(B) + if data is None: + return + + cdef int i,j + cdef FEL f + cdef PTR x + if not isinstance(data,list): + if not data: + return + if self._nrows != self._ncols: + raise ValueError("Cannot initialise non-square matrix from {}".format(data)) + f = FfFromInt(self._converter.field_to_int(self._coerce_element(data))) + x = self.Data.Data + for j from 0 <= j < self.Data.Noc: + FfInsert(x,j,f) + FfStepPtr(&x) + return + + x = self.Data.Data + cdef int nr = self.Data.Nor + cdef int nc = self.Data.Noc + assert self._ncols == nc + assert self._nrows == nr + if nr==0 or nc==0: + return + if len(data)self.Data.Data,self.Data.RowSize * self.Data.Nor), + not self._is_immutable) # for backward compatibility with the group cohomology package + else: + return mtx_unpickle, (0, 0, 0, '', not self._is_immutable) + + cdef get_unsafe(self, Py_ssize_t i, Py_ssize_t j): + """ + Get an element without checking. + + TEST:: + + sage: F. = GF(9) + sage: M = MatrixSpace(F,3)(sorted(list(F))) + sage: type(M) # optional: meataxe + + sage: M # indirect doctest + [ 0 1 2] + [ z z + 1 z + 2] + [ 2*z 2*z + 1 2*z + 2] + + """ + if self.Data == NULL: + raise IndexError, "Matrix is empty" + FfSetField(self.Data.Field) + return self._converter.int_to_field(FfToInt(FfExtract(MatGetPtr(self.Data,i), j))) + + cdef inline int get_unsafe_int(self, Py_ssize_t i, Py_ssize_t j): + # NOTE: + # It is essential that you call FfSetField and FfSetNoc YOURSELF + # and that you assert that the matrix is not empty! + # This method is here for speed! + return FfToInt(FfExtract(MatGetPtr(self.Data,i), j)) + + cdef set_unsafe(self, Py_ssize_t i, Py_ssize_t j, value): + """ + Set values without bound checking. + + TESTS: + + The following test would have failed in a preliminary version + of this MeatAxe wrapper:: + + sage: K. = GF(125) + sage: M = MatrixSpace(K,9,9)() + sage: N = MatrixSpace(GF(9,'x'),20).random_element() + sage: M[2,2] = x + sage: M + [0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0] + [0 0 x 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 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 0] + [0 0 0 0 0 0 0 0 0] + + """ + # ASSUMPTION: value's parent is the base ring + if self.Data == NULL: + raise IndexError, "Matrix is empty" + FfSetField(self.Data.Field) + FfInsert(MatGetPtr(self.Data,i), j, FfFromInt(self._converter.field_to_int(value))) + + cdef set_unsafe_int(self, Py_ssize_t i, Py_ssize_t j, int value): + # NOTE: + # It is essential that you call FfSetField and FfSetNoc YOURSELF + # and that you assert that the matrix is not empty! + # This method is here for speed! + FfInsert(FfGetPtr(self.Data.Data,i), j, FfFromInt(value)) + + def randomize(self, density=None, nonzero=False, *args, **kwds): + """ + Fill the matrix with random values. + + INPUT: + + - ``density`` (optional real number between zero and one) -- + the expected density of the resulting matrix + - ``nonzero`` (optional bool, default ``False``) -- + If true, all inserted marks are non-zero. + + EXAMPLE:: + + sage: MS = MatrixSpace(GF(27,'z'),6,6) + sage: M = MS.random_element() # indirect doctest + sage: M # optional: meataxe + [ 1 z + 1 z^2 + z + 1 z^2 2*z^2 + z z + 1] + [2*z^2 + 2*z + 2 2*z^2 + z + 2 z^2 + 1 2*z^2 + 2*z + 2 z^2 + z 2*z^2 + z + 1] + [ 2*z + 2 z^2 + z + 2 z + 2 2*z^2 + 2*z + 2 2*z^2 2*z^2] + [ 2*z^2 + z + 2 z^2 z + 2 z^2 + z 2*z^2 + 2 z^2 + 2] + [ 2*z^2 + z 2*z 2*z^2 + 2*z + 1 2*z^2 + 1 2*z^2 + 2*z + 1 2*z^2 + z] + [ 2*z + 1 z^2 + z z^2 z^2 2*z^2 + 2*z z + 1] + sage: type(M) # optional: meataxe + + sage: MS.random_element(nonzero=True) # optional: meataxe + [ 2*z 1 z^2 + 2*z + 1 2*z^2 + z + 1 z^2 z^2 + z + 1] + [ 2*z^2 + 2*z 2*z^2 + z + 2 2*z + 1 z^2 + 2*z 2*z^2 + 2*z z^2] + [ z^2 + z z^2 + z + 2 2*z^2 + 2*z + 1 z^2 + 2 1 2*z^2] + [ z 2*z^2 + 2*z 2*z^2 2*z + 1 z + 2 z + 2] + [ z^2 + z z^2 z + 2 2*z^2 + 2*z 2*z + 1 z^2 + z] + [ z^2 + z + 2 2*z^2 + z z^2 z + 1 2*z^2 + 2*z z^2 + 2*z + 1] + sage: MS.random_element(density=0.5) # optional: meataxe + [ z^2 + 2 0 z^2 + 2*z + 2 2*z^2 + z 0 z^2 + z + 2] + [ 0 1 0 0 0 0] + [ 2*z^2 + z + 1 2*z^2 + z + 2 0 z^2 + z + 2 0 z^2 + z + 1] + [ 0 0 0 0 0 0] + [2*z^2 + 2*z + 2 0 0 2*z^2 + z + 2 0 2*z + 1] + [ 0 2*z^2 + z 0 1 0 2*z^2 + z + 1] + + """ + self.check_mutability() + cdef int fl = self.Data.Field + density = float(density) + if density <= 0: + return + if density > 1: + density = float(1) + + self.clear_cache() + + cdef PTR x + cdef unsigned char *y + x = self.Data.Data + cdef int nr = self.Data.Nor + cdef int nc = self.Data.Noc + cdef int i, j, k + + FfSetField(fl) + FfSetNoc(nc) + cdef int O, MPB, tmp + randint = current_randstate().c_random + randdouble = current_randstate().c_rand_double + + if not nonzero: + if density == 1: + MPB = 0 + tmp = fl + while tmp <= 256: + MPB += 1 + tmp *= fl + O = (fl**MPB) + sig_on() + if nc%MPB: + for i from 0 <= i < nr: + y = x + for j from 0 <= j < FfCurrentRowSizeIo-1: + y[j] = randint()%O + y[FfCurrentRowSizeIo-1] = randint()%(fl**(nc%MPB)) + FfStepPtr(&(x)) + else: + for i from 0 <= i < nr: + y = x + for j from 0 <= j < FfCurrentRowSizeIo: + y[j] = randint()%O + FfStepPtr(&(x)) + sig_off() + else: + sig_on() + for i from 0 <= i < nr: + for j from 0 <= j < nc: + if randdouble() < density: + FfInsert(x, j, FfFromInt( (randint()%fl) )) + FfStepPtr(&(x)) + sig_off() + else: + if density == 1: + fl -= 1 + sig_on() + for i from 0 <= i < nr: + for j from 0 <= j < nc: + FfInsert(x, j, FfFromInt( (randint()%fl)+1 )) + FfStepPtr(&(x)) + sig_off() + else: + fl -= 1 + sig_on() + for i from 0 <= i < nr: + for j from 0 <= j < nc: + if randdouble() < density: + FfInsert(x, j, FfFromInt( (randint()%fl)+1 )) + FfStepPtr(&(x)) + sig_off() + +## Debugging +# def show_contents(self, r=None): +# FfSetField(self.Data.Field) +# FfSetNoc(self.Data.Noc) +# cdef PTR p +# cdef size_t i, j +# if r is not None: +# r_min = r +# r_max = r+1 +# else: +# r_min = 0 +# r_max = self.Data.Nor +# for i in range(r_min, r_max): +# p = FfGetPtr(self.Data.Data, i) +# for j from 0<=j' doesn't make much sense for matrices. + + EXAMPLES:: + + sage: M = MatrixSpace(GF(125,'x'),3,20)([20*[0],20*[0],[1]+19*[0]]) + sage: N = copy(M) + sage: M == N + True + sage: M != N + False + sage: M < N + False + sage: N[2,19] = 1 + sage: M == N + False + sage: M != N + True + """ + cdef Matrix_gfpn_dense self = left + cdef Matrix_gfpn_dense N = right + if self is None or N is None: + return -1 + cdef char* d1 + cdef char* d2 + if self.Data == NULL: + if N.Data == NULL: + return 0 + else: + return 1 + elif N.Data == NULL: + return -1 + if self.Data.Field != N.Data.Field: + if self.Data.Field > N.Data.Field: + return 1 + return -1 + if self.Data.Noc != N.Data.Noc: + if self.Data.Noc > N.Data.Noc: + return 1 + return -1 + if self.Data.Nor != N.Data.Nor: + if self.Data.Nor > N.Data.Nor: + return 1 + return -1 + d1 = (self.Data.Data) + d2 = (N.Data.Data) + cdef str s1 = PyString_FromStringAndSize(d1,self.Data.RowSize * self.Data.Nor) + cdef str s2 = PyString_FromStringAndSize(d2,N.Data.RowSize * N.Data.Nor) + if s1 != s2: + if s1 > s2: + return 1 + return -1 + return 0 + + cdef list _rowlist_(self, i, j=-1): + "M._rowlist_(i): Return row as a list of python ints" + cdef int k + if self.Data: + FfSetField(self.Data.Field) + FfSetNoc(self.Data.Noc) + else: + raise ValueError("Matrix is empty") + if (i<0) or (i>=self.Data.Nor): + raise IndexError("Index {} out of range 0..{}",format(i,self.Data.Nor-1)) + cdef PTR p + p = MatGetPtr(self.Data,i) + L = [FfToInt(FfExtract(p,k)) for k in range(self.Data.Noc)] + if j!=-1: + if not(isinstance(j,int) or isinstance(j,Integer)): + raise TypeError, "Second index must be an integer" + if j >= self.Data.Nor: + raise IndexError, "Index out of range" + for k from i < k <= j: + FfStepPtr(&(p)) # This is only called after MatGetPtr, hence, after FfSetNoc. + L.extend([FfToInt(FfExtract(p,l)) for l in range(self.Data.Noc)]) + return L + + def _list(self): + """ + Return a flat list of all entries of this matrix. + + The result is cached. + + EXAMPLES:: + + sage: MatrixSpace(GF(9,'x'),3)(sorted(list(GF(9,'x')))).list() # indirect doctest + [0, 1, 2, x, x + 1, x + 2, 2*x, 2*x + 1, 2*x + 2] + + """ + cdef list x = self.fetch('list') + if not x is None: + return x + x = [] + cdef int i + if self.Data: + FfSetField(self.Data.Field) + FfSetNoc(self.Data.Noc) + else: + raise IndexError, "Matrix is empty" + cdef PTR p + p = self.Data.Data + sig_on() + for i from 1<=i = GF(25) + sage: M = MatrixSpace(K,5,5)(sorted(list(K))) + sage: M + [ 0 1 2 3 4] + [ x x + 1 x + 2 x + 3 x + 4] + [ 2*x 2*x + 1 2*x + 2 2*x + 3 2*x + 4] + [ 3*x 3*x + 1 3*x + 2 3*x + 3 3*x + 4] + [ 4*x 4*x + 1 4*x + 2 4*x + 3 4*x + 4] + sage: M.rescale_row(1, 3) # indirect doctest + sage: M + [ 0 1 2 3 4] + [ 3*x 3*x + 3 3*x + 1 3*x + 4 3*x + 2] + [ 2*x 2*x + 1 2*x + 2 2*x + 3 2*x + 4] + [ 3*x 3*x + 1 3*x + 2 3*x + 3 3*x + 4] + [ 4*x 4*x + 1 4*x + 2 4*x + 3 4*x + 4] + sage: M.rescale_row(4, x) + sage: M + [ 0 1 2 3 4] + [ 3*x 3*x + 3 3*x + 1 3*x + 4 3*x + 2] + [ 2*x 2*x + 1 2*x + 2 2*x + 3 2*x + 4] + [ 3*x 3*x + 1 3*x + 2 3*x + 3 3*x + 4] + [4*x + 2 2 x + 2 2*x + 2 3*x + 2] + + """ + if start_col != 0 or self.Data == NULL: + raise ValueError("We can only rescale a full row of a non-empty matrix") + FfMulRow(MatGetPtr(self.Data, i), FfFromInt(self._converter.field_to_int(self._base_ring(s)))) + + cdef add_multiple_of_row_c(self, Py_ssize_t row_to, Py_ssize_t row_from, multiple, Py_ssize_t start_col): + """ + Add the ``multiple``-fold of row ``row_from`` in-place to row ``row_to``. + + EXAMPLES:: + + sage: K. = GF(25) + sage: M = MatrixSpace(K,5,5)(sorted(list(K))) + sage: M + [ 0 1 2 3 4] + [ x x + 1 x + 2 x + 3 x + 4] + [ 2*x 2*x + 1 2*x + 2 2*x + 3 2*x + 4] + [ 3*x 3*x + 1 3*x + 2 3*x + 3 3*x + 4] + [ 4*x 4*x + 1 4*x + 2 4*x + 3 4*x + 4] + sage: M.add_multiple_of_row(2, 4, x) # indirect doctest + sage: M + [ 0 1 2 3 4] + [ x x + 1 x + 2 x + 3 x + 4] + [ x + 2 2*x + 3 3*x + 4 4*x 1] + [ 3*x 3*x + 1 3*x + 2 3*x + 3 3*x + 4] + [ 4*x 4*x + 1 4*x + 2 4*x + 3 4*x + 4] + + """ + if start_col != 0 or self.Data == NULL: + raise ValueError("We can only rescale a full row of a non-empty matrix") + FfAddMulRow(MatGetPtr(self.Data, row_to), MatGetPtr(self.Data, row_from), FfFromInt(self._converter.field_to_int(self._base_ring(multiple)))) + + cdef swap_rows_c(self, Py_ssize_t row1, Py_ssize_t row2): + """ + Swap the rows ``row1`` and ``row2`` in-place. + + EXAMPLES:: + + sage: K. = GF(25) + sage: M = MatrixSpace(K,5,5)(sorted(list(K))) + sage: M + [ 0 1 2 3 4] + [ x x + 1 x + 2 x + 3 x + 4] + [ 2*x 2*x + 1 2*x + 2 2*x + 3 2*x + 4] + [ 3*x 3*x + 1 3*x + 2 3*x + 3 3*x + 4] + [ 4*x 4*x + 1 4*x + 2 4*x + 3 4*x + 4] + sage: M.swap_rows(1, 3) # indirect doctest + sage: M + [ 0 1 2 3 4] + [ 3*x 3*x + 1 3*x + 2 3*x + 3 3*x + 4] + [ 2*x 2*x + 1 2*x + 2 2*x + 3 2*x + 4] + [ x x + 1 x + 2 x + 3 x + 4] + [ 4*x 4*x + 1 4*x + 2 4*x + 3 4*x + 4] + + """ + FfSwapRows(MatGetPtr(self.Data, row1), MatGetPtr(self.Data, row2)) + + def trace(self): + """ + Trace of this matrix, i.e., the sum of diagonal elements. + + EXAMPLES:: + + sage: K. = GF(125) + sage: MatrixSpace(K,7,7)(x).trace() + 2*x + + """ + if self._nrows != self._ncols: + raise ValueError, "self must be a square matrix" + return self._converter.int_to_field(FfToInt(MatTrace(self.Data))) + + def stack(self, Matrix_gfpn_dense other): + """ + Stack two matrices of the same number of columns. + + EXAMPLES:: + + sage: M = MatrixSpace(GF(9,'x'),1,9)(sorted(list(GF(9,'x')))) + sage: M + [ 0 1 2 x x + 1 x + 2 2*x 2*x + 1 2*x + 2] + sage: M.stack(M) + [ 0 1 2 x x + 1 x + 2 2*x 2*x + 1 2*x + 2] + [ 0 1 2 x x + 1 x + 2 2*x 2*x + 1 2*x + 2] + + """ + if self._ncols != other._ncols: + raise TypeError("Both numbers of columns must match.") + if self._nrows == 0 or self.Data == NULL: + return other.__copy__() + if other._nrows == 0 or other.Data == NULL: + return self.__copy__() + cdef Matrix_gfpn_dense OUT = self._new(self._nrows+other._nrows, self._ncols) + OUT.Data = MatAlloc(self.Data.Field, self.Data.Nor+other.Data.Nor, self.Data.Noc) + memcpy(OUT.Data.Data, self.Data.Data, FfCurrentRowSize*self.Data.Nor) + memcpy(MatGetPtr(OUT.Data, self.Data.Nor), other.Data.Data, FfCurrentRowSize*other.Data.Nor) + return OUT + + cpdef ModuleElement _add_(self, ModuleElement right): + """ + TESTS:: + + sage: K. = GF(9) + sage: M = MatrixSpace(K,3,3)(sorted(list(K))) + sage: N = MatrixSpace(K,3,3)(2*x) + sage: M+N # indirect doctest + [ 2*x 1 2] + [ x 1 x + 2] + [ 2*x 2*x + 1 x + 2] + + """ + cdef Matrix_gfpn_dense Self = self + cdef Matrix_gfpn_dense Right = right + assert Self is not None + assert Right is not None + if Self.Data == NULL or Right.Data == NULL: + raise NotImplementedError, "The matrices must not be empty" + cdef Matrix_gfpn_dense Left = Self.__copy__() + Left._cache = {} + MatAdd(Left.Data, Right.Data) + return Left + + cpdef ModuleElement _sub_(self, ModuleElement right): + """ + TESTS:: + + sage: K. = GF(9) + sage: M = MatrixSpace(K,3,3)(sorted(list(K))) + sage: N = MatrixSpace(K,3,3)(2*x) + sage: M-N # indirect doctest + [ x 1 2] + [ x 2*x + 1 x + 2] + [ 2*x 2*x + 1 2] + + """ + cdef Matrix_gfpn_dense Self = self + cdef Matrix_gfpn_dense Right = right + assert Self is not None + assert Right is not None + if Self.Data == NULL or Right.Data == NULL: + raise NotImplementedError, "The matrices must not be empty" + cdef Matrix_gfpn_dense Left = Self.__copy__() + Left._is_immutable = False + Left._cache = {} + MatAddMul(Left.Data, Right.Data, mtx_taddinv[1]) + return Left + + def __neg__(self): + """ + TESTS:: + + sage: M = MatrixSpace(GF(9,'x'),3,3)(sorted(list(GF(9,'x')))) + sage: -M + [ 0 2 1] + [ 2*x 2*x + 2 2*x + 1] + [ x x + 2 x + 1] + + :: + + sage: M = MatrixSpace(GF(125,'x'),10,30).random_element() + sage: N = MatrixSpace(GF(125,'x'),10,30).random_element() + sage: M + (-N) == M - N == -(N - M) + True + + """ + if self.Data == NULL: + raise ValueError("The matrix must not be empty") + return self._rmul_(self._base_ring(-1)) + + cpdef ModuleElement _rmul_(self, RingElement left): + """ + EXAMPLES:: + + sage: M = MatrixSpace(GF(9,'x'),3,3)(sorted(list(GF(9,'x')))) + sage: K. = GF(9) + sage: M = MatrixSpace(K,3,3)(list(K)) + sage: x*M # indirect doctest + [ 0 x + 1 2*x + 1] + [ 2 2*x 2*x + 2] + [ x + 2 1 x] + sage: -M == (-1)*M + True + + """ + if self.Data == NULL: + return self.__copy__() + FfSetField(self.Data.Field) + cdef Matrix_gfpn_dense OUT = self.__copy__() + OUT._cache = {} + MatMulScalar(OUT.Data, FfFromInt(self._converter.field_to_int(left))) + return OUT + + cpdef ModuleElement _lmul_(self, RingElement right): + """ + EXAMPLES:: + + sage: M = MatrixSpace(GF(9,'x'),3,3)(sorted(list(GF(9,'x')))) + sage: K. = GF(9) + sage: M = MatrixSpace(K,3,3)(sorted(list(K))) + sage: x*M # indirect doctest + [ 0 x 2*x] + [ x + 1 2*x + 1 1] + [2*x + 2 2 x + 2] + sage: -M == (-1)*M + True + + """ + if self.Data == NULL: + return self.__copy__() + FfSetField(self.Data.Field) + cdef Matrix_gfpn_dense OUT = self.__copy__() + OUT._cache = {} + MatMulScalar(OUT.Data, FfFromInt(self._converter.field_to_int(right))) + return OUT + + cdef int _strassen_default_cutoff(self, sage.matrix.matrix0.Matrix right) except -2: + # Surprisingly, Winograd-Strassen can compete with school book + # multiplication for smallish matrices, and of course it is + # asymptotically faster. So, we used it by default. + return 0 + + cpdef Matrix_gfpn_dense _multiply_classical(Matrix_gfpn_dense self, Matrix_gfpn_dense right): + """ + Multiplication using the cubic school book multiplication algorithm. + + EXAMPLES: + + Since by default the asymptotically faster Strassen-Winograd + multiplication algorithm is used, the following is a valid + consistency check:: + + sage: M = MatrixSpace(GF(9,'x'),1000,500).random_element() + sage: N = MatrixSpace(GF(9,'x'),500,2000).random_element() + sage: M*N == M._multiply_classical(N) # optional: meataxe + True + + """ + "multiply two meataxe matrices by the school book algorithm" + if self.Data == NULL or right.Data == NULL: + raise ValueError("The matrices must not be empty") + if self._ncols != right._nrows: + raise ArithmeticError("left ncols must match right nrows") + cdef Matrix_gfpn_dense OUT = self._new(self._nrows, right._ncols) + sig_on() + OUT.Data = MatDup(self.Data) + MatMul(OUT.Data,right.Data) + sig_off() + OUT._is_immutable = False + OUT._cache = {} + return OUT + + cpdef Matrix_gfpn_dense _multiply_strassen(Matrix_gfpn_dense self, Matrix_gfpn_dense right, cutoff=0): + """ + Matrix multiplication using the asymptotically fast Strassen-Winograd algorithm. + + INPUT: + + - ``right`` -- a matrix of dimensions suitable to do multiplication + - ``cutoff`` (optional integer) -- indicates the minimal size of submatrices + that will be considered in the divide-and-conquer algorithm. The size is + *not* expressed by the number of rows/columns, but the rowsize expressed + in bytes. Depending on the base field, one byte may represent up to eight + entries in a matrix row. The default is ``sizeof(long)^2/2`` byte. + + EXAMPLES: + + We test that different cutoffs yield the same result:: + + sage: M = MatrixSpace(GF(9,'x'),1500,600).random_element() + sage: N = MatrixSpace(GF(9,'x'),600,1500).random_element() + sage: M._multiply_strassen(N) == M._multiply_strassen(N,80) == M._multiply_strassen(N,2) # optional: meataxe + True + + """ + if self.Data == NULL or right.Data == NULL: + raise ValueError("The matrices must not be empty") + if self._ncols != right._nrows: + raise ArithmeticError("left ncols must match right nrows") + MS = self.matrix_space(self._nrows, right._ncols, False) + cdef Matrix_gfpn_dense OUT = Matrix_gfpn_dense(MS, None) + # Now, OUT.Data is initialised, which is needed for MatMulStrassen to work. + cutoff = cutoff//sizeof(long) + StrassenSetCutoff(cutoff) + sig_on() + MatMulStrassen(OUT.Data, self.Data, right.Data) + sig_off() + return OUT + + cdef ModuleElement _mul_long(self, long n): + "multiply an MTX matrix with a field element represented by an integer" + if self.Data == NULL: + raise ValueError("The matrix must not be empty") + cdef Matrix_gfpn_dense left + cdef FEL r + if n < 0: + r = mtx_taddinv[FfFromInt(-n)] + else: + r = FfFromInt(n) + left = self.__copy__() + left._cache = {} + MatMulScalar(left.Data, r) + return left + + def __div__(Matrix_gfpn_dense self, p): + """ + Divide a matrix by a scalar. + + EXAMPLES:: + + sage: K. = GF(9) + sage: M = MatrixSpace(K,3,3)(sorted(list(K))) + sage: M + [ 0 1 2] + [ x x + 1 x + 2] + [ 2*x 2*x + 1 2*x + 2] + sage: M/2 # indirect doctest + [ 0 2 1] + [ 2*x 2*x + 2 2*x + 1] + [ x x + 2 x + 1] + sage: M/x + [ 0 x + 2 2*x + 1] + [ 1 x 2*x + 2] + [ 2 x + 1 2*x] + + """ + if self.Data == NULL: + return self.__copy__() + if not p: + raise ZeroDivisionError + if p not in self._base_ring: + raise ValueError("{} is not a scalar".format(p)) + p = self._base_ring(p) + FfSetField(self.Data.Field) + cdef Matrix_gfpn_dense OUT = self.__copy__() + OUT._cache = {} + cdef FEL r = mtx_tmultinv[FfFromInt(self._converter.field_to_int(p))] + MatMulScalar(OUT.Data, r) + return OUT + + def __invert__(Matrix_gfpn_dense self): + """ + Multiplicative inverse of this matrix (if available) + + TESTS:: + + sage: MS = MatrixSpace(GF(9,'x'),500) + sage: while 1: + ....: M = MS.random_element() + ....: if M.rank() == 500: + ....: break + sage: Minv = ~M # indirect doctest + sage: Minv*M == M*Minv == 1 + True + + We use the occasion to demonstrate that errors in MeatAxe are + correctly handled in Sage:: + + sage: MS = MatrixSpace(GF(25,'x'),5) + sage: while 1: + ....: M = MS.random_element(density=0.4) + ....: if M.rank() < 5: + ....: break + sage: ~M # optional: meataxe + Traceback (most recent call last): + ... + ZeroDivisionError: Division by zero in file matinv.c (line 50) + + """ + if self.Data == NULL: + raise ValueError("The matrix must not be empty") + if not self.is_square(): + raise ArithmeticError("self must be a square matrix") + cdef Matrix_gfpn_dense OUT = self._new(self._nrows, self._ncols) + OUT._is_immutable = False + OUT._cache = {} + sig_on() + try: + OUT.Data = MatInverse(self.Data) + except ZeroDivisionError: + # Attempting to invert singular matrices happens + # in the tests, and we make the special case here + # so that the sig_on/off count is fine. + sig_off() + raise + sig_off() + if OUT.Data != NULL: + return OUT + raise ArithmeticError("This matrix is not invertible") + + def transpose(Matrix_gfpn_dense self): + """ + Return the transposed matrix. + + EXAMPLES:: + + sage: K. = GF(9) + sage: M = MatrixSpace(K, 2,4)(sorted(list(K)[1:])) + sage: M + [ 1 2 x x + 1] + [ x + 2 2*x 2*x + 1 2*x + 2] + sage: M.transpose() + [ 1 x + 2] + [ 2 2*x] + [ x 2*x + 1] + [ x + 1 2*x + 2] + + """ + if self.Data == NULL: + raise ValueError("The matrix must not be empty") + cdef Matrix_gfpn_dense OUT = self._new(self._ncols, self._nrows) + OUT._is_immutable = False + OUT._cache = {} + OUT.Data = MatTransposed(self.Data) + return OUT + + def order(self): + """ + Return the multiplicative order of this matrix. + + EXAMPLES:: + + sage: K. = GF(27) + sage: M = MatrixSpace(K, 4)([2*x^2 + 2*x, 2*x^2 + x, 2*x^2 + x + 1, + ....: x^2 + x + 2, x + 2, x^2, 2*x + 2, 2*x^2 + 2*x, 2*x^2 + 1, + ....: 1, 2, x^2 + 2*x + 1, x^2 + x + 2, x + 1, 2*x^2 + 2*x, x^2 + x]) + sage: M.order() # optional: meataxe + 104 + sage: M^104 == 1 + True + sage: M^103 == 1 + False + + """ + if self.Data == NULL: + raise ValueError("The matrix must not be empty") + if (self.Data.Nor <> self.Data.Noc): + raise ValueError("only defined for square matrices") + o = MatOrder(self.Data) + if o==-1: + raise ArithmeticError("order too large") + else: + return o + +################### +## Gauss algorithm + + def left_kernel_matrix(self): + """ + Return the null space of this matrix, represented as a matrix. + + NOTE: + + - For a matrix `M`, ``M.left_kernel_matrix()*M`` is a null matrix. + - The command `M.left_kernel()` uses a generic implementation in Sage, + that relies on computing the echelon form of the transposed + matrix. This method however uses a MeatAxe function to compute + the left kernel matrix. + + EXAMPLES:: + + sage: K. = GF(25) + sage: M = MatrixSpace(K, 10)() + sage: entries = [((0, 2), x), ((0, 4), 3*x + 2), + ....: ((0, 8), 2*x), ((1, 1), x + 3), ((1, 5), 3*x), + ....: ((1, 6), x + 4), ((2, 3), 2*x), ((2, 5), 4*x + 1), + ....: ((2, 6), 4), ((3, 4), x + 4), ((3, 5), x + 1), + ....: ((5, 5), 3*x), ((5, 7), x + 3), ((6, 1), x), + ....: ((6, 2), x + 1), ((6, 5), x + 1), ((8, 2), 4), + ....: ((8, 8), 4), ((8, 9), x + 3), ((9, 8), 4*x + 2)] + sage: for (i,j),v in entries: M[i,j] = v + sage: M.left_kernel() + Vector space of degree 10 and dimension 2 over Finite Field in x of size 5^2 + Basis matrix: + [0 0 0 0 1 0 0 0 0 0] + [0 0 0 0 0 0 0 1 0 0] + sage: M.left_kernel_matrix() # optional: meataxe + [0 0 0 0 1 0 0 0 0 0] + [0 0 0 0 0 0 0 1 0 0] + + """ + cdef Matrix_gfpn_dense OUT = self.fetch("left_kernel_matrix") + if OUT is not None: + return OUT + if self.Data == NULL: + raise ValueError("The matrix must not be empty") + OUT = type(self).__new__(type(self)) + OUT.Data = MatNullSpace(self.Data) + OUT._nrows = OUT.Data.Nor + OUT._ncols = OUT.Data.Noc + OUT._is_immutable = False + OUT._parent = self.matrix_space(OUT._nrows, OUT._ncols, False) + OUT._base_ring = self._base_ring + OUT._converter = self._converter + OUT._cache = {} + self.cache("left_kernel_matrix", OUT) + return OUT + + def _echelon_in_place_classical(self, reduced=True, **kwds): + """ + Change this matrix into echelon form, using classical Gaussian elimination. + + INPUT: + + - ``reduced`` (optional, default ``True``) -- will result + in the row-reduced echelon form (otherwise, only a + semi-echelon form results). + + EXAMPLES:: + + sage: K. = GF(25) + sage: M = MatrixSpace(K, 10)() + sage: entries = [((0, 2), x), ((0, 4), 3*x + 2), + ....: ((0, 8), 2*x), ((1, 1), x + 3), ((1, 5), 3*x), + ....: ((1, 6), x + 4), ((2, 3), 2*x), ((2, 5), 4*x + 1), + ....: ((2, 6), 4), ((3, 4), x + 4), ((3, 5), x + 1), + ....: ((5, 5), 3*x), ((5, 7), x + 3), ((6, 1), x), + ....: ((6, 2), x + 1), ((6, 5), x + 1), ((8, 2), 4), + ....: ((8, 8), 4), ((8, 9), x + 3), ((9, 8), 4*x + 2)] + sage: for (i,j),v in entries: M[i,j] = v + sage: M + [ 0 0 x 0 3*x + 2 0 0 0 2*x 0] + [ 0 x + 3 0 0 0 3*x x + 4 0 0 0] + [ 0 0 0 2*x 0 4*x + 1 4 0 0 0] + [ 0 0 0 0 x + 4 x + 1 0 0 0 0] + [ 0 0 0 0 0 0 0 0 0 0] + [ 0 0 0 0 0 3*x 0 x + 3 0 0] + [ 0 x x + 1 0 0 x + 1 0 0 0 0] + [ 0 0 0 0 0 0 0 0 0 0] + [ 0 0 4 0 0 0 0 0 4 x + 3] + [ 0 0 0 0 0 0 0 0 4*x + 2 0] + sage: M.echelon_form() # indirect doctest + [ 0 1 0 0 0 0 0 0 0 4*x + 4] + [ 0 0 1 0 0 0 0 0 0 4*x + 2] + [ 0 0 0 1 0 0 0 0 0 3*x + 4] + [ 0 0 0 0 1 0 0 0 0 3*x + 3] + [ 0 0 0 0 0 1 0 0 0 2*x + 3] + [ 0 0 0 0 0 0 1 0 0 x] + [ 0 0 0 0 0 0 0 1 0 2*x + 2] + [ 0 0 0 0 0 0 0 0 1 0] + [ 0 0 0 0 0 0 0 0 0 0] + [ 0 0 0 0 0 0 0 0 0 0] + + A semi-echelon form can be produced by invoking the single-underscore + method directly:: + + sage: N = copy(M) + sage: N._echelon_in_place_classical(reduced=False) # optional: meataxe + sage: N # optional: meataxe + [ 0 0 x 0 3*x + 2 0 0 0 2*x 0] + [ 0 x + 3 0 0 0 3*x x + 4 0 0 0] + [ 0 0 0 2*x 0 4*x + 1 4 0 0 0] + [ 0 0 0 0 x + 4 x + 1 0 0 0 0] + [ 0 0 0 0 0 3*x 0 x + 3 0 0] + [ 0 0 0 0 0 0 2*x + 2 4*x 3*x + 3 0] + [ 0 0 0 0 0 0 0 x + 1 1 x + 3] + [ 0 0 0 0 0 0 0 0 4*x + 2 0] + [ 0 0 0 0 0 0 0 0 0 0] + [ 0 0 0 0 0 0 0 0 0 0] + + TESTS: + + We verify that the above echelon form is consistent with Sage's generic + implementation of dense matrices:: + + sage: type(M) # optional: meataxe + + sage: MS = M.parent() + sage: from sage.matrix.matrix_generic_dense import Matrix_generic_dense + sage: MS._MatrixSpace__matrix_class = Matrix_generic_dense + sage: X = MS(M._list()) + sage: type(X) + + sage: X.echelon_form() + [ 0 1 0 0 0 0 0 0 0 4*x + 4] + [ 0 0 1 0 0 0 0 0 0 4*x + 2] + [ 0 0 0 1 0 0 0 0 0 3*x + 4] + [ 0 0 0 0 1 0 0 0 0 3*x + 3] + [ 0 0 0 0 0 1 0 0 0 2*x + 3] + [ 0 0 0 0 0 0 1 0 0 x] + [ 0 0 0 0 0 0 0 1 0 2*x + 2] + [ 0 0 0 0 0 0 0 0 1 0] + [ 0 0 0 0 0 0 0 0 0 0] + [ 0 0 0 0 0 0 0 0 0 0] + + The following was a problem in a preliminary version of the code:: + + sage: K. = GF(25) + sage: M = MatrixSpace(K, 2, 4)([4, 4, 1, 0, 0, 2*a+1, a+2, 1]) + sage: M + [ 4 4 1 0] + [ 0 2*a + 1 a + 2 1] + sage: M.echelonize() + sage: M + [ 1 0 3*a + 4 2*a + 2] + [ 0 1 2*a 3*a + 3] + + """ + if self._nrows == 0 or self._ncols == 0: + self.cache('in_echelon_form',True) + self.cache('rank', 0) + self.cache('pivots', ()) + return self + MatEchelonize(self.Data) + self._cache = {} + # Now, self.Data is in semi-echelon form. + r = self.Data.Nor + cdef size_t i, j, pos + cdef PTR old, dest, src + cdef FEL piv + self.cache('rank', r) + # Next, we do permutations to achieve the reduced echelon form, + # if requested. + sig_on() + if reduced: + pivs = [(self.Data.PivotTable[i],i) for i in range(r)] + pivs.sort() + if pivs != [(self.Data.PivotTable[i],i) for i in range(r)] or self.Data.Nor < self._nrows: + # We copy the row one by one, sorting their pivot positions + old = self.Data.Data + self.Data.Data = FfAlloc(self._nrows) + for i, (pos,j) in enumerate(pivs): + # We have to move row j to row i + dest = self.Data.Data+FfCurrentRowSize*i + memcpy(dest, old+FfCurrentRowSize*j, FfCurrentRowSize) + self.Data.PivotTable[i] = pos + free(old) + self.Data.Nor = self._nrows + # Now, the pivot columns are strictly increasing. + # We now normalize each row, and annulate everything + # above the pivot (currently, we only know that the matrix + # is zero below the pivots). + for i from 0 <= i < r: + src = MatGetPtr(self.Data, i) + piv = FfExtract(src, self.Data.PivotTable[i]) + assert piv!=FF_ZERO + if piv != FF_ONE: + FfMulRow(src, mtx_tmultinv[piv]) + for j from 0 <= j < i: + dest = MatGetPtr(self.Data, j) + piv = FfExtract(dest, self.Data.PivotTable[i]) + if piv != FF_ONE: + FfAddMulRow(dest, src, mtx_taddinv[piv]) + else: + FfSubRow(dest, src) + elif self.Data.Nor < self._nrows: + # Some rows may have vanished. In SageMath, we + # want that the number of rows does not change, + # thus, we have to append zero rows. + self.Data.Data = check_realloc(self.Data.Data, FfCurrentRowSize*self._nrows) + memset(self.Data.Data + FfCurrentRowSize*self.Data.Nor, FF_ZERO, FfCurrentRowSize*(self._nrows-self.Data.Nor)) + self.Data.Nor = self._nrows + sig_off() + self.cache('pivots', tuple(self.Data.PivotTable[i] for i in range(r))) + self.cache('in_echelon_form',True) + +def mtx_unpickle(f, int nr, int nc, str Data, bint m): + """ + Helper function for unpickling. + + TESTS:: + + sage: M = MatrixSpace(GF(9,'x'),10,10).random_element() + sage: M == loads(dumps(M)) # indirect doctest + True + sage: M is loads(dumps(M)) + False + """ + cdef Matrix_gfpn_dense OUT + OUT = Matrix_gfpn_dense.__new__(Matrix_gfpn_dense) + if isinstance(f, (int, long)): + # This is for old pickles created with the group cohomology spkg + Matrix_dense.__init__(OUT, MatrixSpace(GF(f, 'z'), nr, nc)) + else: + Matrix_dense.__init__(OUT, f) + f = OUT._base_ring.order() + OUT.Data = MatAlloc(f, nr, nc) + OUT._is_immutable = not m + OUT._converter = FieldConverter(OUT._base_ring) + cdef char *x + if Data: + x = PyString_AsString(Data) + memcpy(OUT.Data.Data, x, OUT.Data.RowSize*OUT.Data.Nor) + return OUT diff --git a/src/sage/matrix/matrix_mod2_dense.pyx b/src/sage/matrix/matrix_mod2_dense.pyx index 6e1b3014f6f..db953abb5eb 100644 --- a/src/sage/matrix/matrix_mod2_dense.pyx +++ b/src/sage/matrix/matrix_mod2_dense.pyx @@ -2059,7 +2059,7 @@ def unpickle_matrix_mod2_dense_v1(r, c, data, size): True """ from sage.matrix.constructor import Matrix - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF cdef int i, j cdef Matrix_mod2_dense A @@ -2109,7 +2109,7 @@ def from_png(filename): True """ from sage.matrix.constructor import Matrix - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF cdef int i,j,r,c cdef Matrix_mod2_dense A diff --git a/src/sage/matrix/matrix_rational_dense.pyx b/src/sage/matrix/matrix_rational_dense.pyx index 717fd3ac525..3696d74bfbd 100644 --- a/src/sage/matrix/matrix_rational_dense.pyx +++ b/src/sage/matrix/matrix_rational_dense.pyx @@ -72,7 +72,7 @@ from sage.structure.element cimport ModuleElement, RingElement, Element, Vector from sage.rings.integer cimport Integer from sage.rings.ring import is_Ring from sage.rings.integer_ring import ZZ, is_IntegerRing -from sage.rings.finite_rings.constructor import FiniteField as GF +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.rings.finite_rings.integer_mod_ring import is_IntegerModRing from sage.rings.rational_field import QQ from sage.arith.all import gcd diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index 6336b8e1c25..f7603557180 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -56,7 +56,6 @@ import matrix_mpolynomial_dense - # Sage imports from sage.misc.superseded import deprecation import sage.structure.coerce @@ -65,7 +64,7 @@ import sage.rings.integer as integer import sage.rings.number_field.all import sage.rings.finite_rings.integer_mod_ring -import sage.rings.finite_rings.constructor +import sage.rings.finite_rings.finite_field_constructor import sage.rings.polynomial.multi_polynomial_ring_generic import sage.misc.latex as latex import sage.modules.free_module @@ -986,6 +985,12 @@ def _get_matrix_class(self): sage: type(matrix(GF(16007), 2, range(4))) + sage: type(matrix(GF(2), 2, range(4))) + + sage: type(matrix(GF(64,'z'), 2, range(4))) + + sage: type(matrix(GF(125,'z'), 2, range(4))) # optional: meataxe + """ R = self.base_ring() if self.is_dense(): @@ -1013,19 +1018,26 @@ def _get_matrix_class(self): elif R.order() < matrix_modn_dense_double.MAX_MODULUS: return matrix_modn_dense_double.Matrix_modn_dense_double return matrix_generic_dense.Matrix_generic_dense - elif sage.rings.finite_rings.constructor.is_FiniteField(R) and R.characteristic() == 2 and R.order() <= 65536: - return matrix_gf2e_dense.Matrix_gf2e_dense + elif sage.rings.finite_rings.finite_field_constructor.is_FiniteField(R): + if R.characteristic() == 2: + if R.order() <= 65536: + return matrix_gf2e_dense.Matrix_gf2e_dense + elif R.order() <= 255: + try: + import matrix_gfpn_dense + return matrix_gfpn_dense.Matrix_gfpn_dense + except ImportError: + pass elif sage.rings.polynomial.multi_polynomial_ring_generic.is_MPolynomialRing(R) and R.base_ring() in _Fields: return matrix_mpolynomial_dense.Matrix_mpolynomial_dense #elif isinstance(R, sage.rings.padics.padic_ring_capped_relative.pAdicRingCappedRelative): # return padics.matrix_padic_capped_relative_dense # the default - else: - from sage.symbolic.ring import SR # causes circular imports - if R is SR: - import matrix_symbolic_dense - return matrix_symbolic_dense.Matrix_symbolic_dense - return matrix_generic_dense.Matrix_generic_dense + from sage.symbolic.ring import SR # causes circular imports + if R is SR: + import matrix_symbolic_dense + return matrix_symbolic_dense.Matrix_symbolic_dense + return matrix_generic_dense.Matrix_generic_dense else: if sage.rings.finite_rings.integer_mod_ring.is_IntegerModRing(R) and R.order() < matrix_modn_sparse.MAX_MODULUS: diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index 66a8087aabf..e78d65fe4d5 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -136,6 +136,7 @@ additional functionality (e.g. linear extensions). - :meth:`chow_ring() ` - :meth:`matroid_polytope() ` - :meth:`independence_matroid_polytope() ` + - :meth:`orlik_solomon_algebra() ` In addition to these, all methods provided by :class:`SageObject ` are available, @@ -2813,9 +2814,10 @@ cdef class Matroid(SageObject): r""" Return the list of broken circuits of ``self``. - A *broken circuit* `B` for is a subset of the ground set under - some total ordering `<` such that `B \cup \{ u \}` is a circuit - and `u < b` for all `b \in B`. + Let `M` be a matroid with ground set `E`, and let `<` be a total + ordering on `E`. A *broken circuit* for `M` means a subset `B` of + `E` such that there exists a `u \in E` for which `B \cup \{ u \}` + is a circuit of `M` and `u < b` for all `b \in B`. INPUT: @@ -2891,6 +2893,31 @@ cdef class Matroid(SageObject): ret.append(I) return ret + def orlik_solomon_algebra(self, R, ordering=None): + """ + Return the Orlik-Solomon algebra of ``self``. + + INPUT: + + - ``R`` -- the base ring + - ``ordering`` -- (optional) an ordering of the ground set + + .. SEEALSO:: + + :class:`~sage.algebras.orlik_solomon.OrlikSolomonAlgebra` + + EXAMPLES:: + + sage: M = matroids.Uniform(3, 4) + sage: OS = M.orlik_solomon_algebra(QQ) + sage: OS + Orlik-Solomon algebra of U(3, 4): Matroid of rank 3 on 4 elements + with circuit-closures + {3: {{0, 1, 2, 3}}} + """ + from sage.algebras.orlik_solomon import OrlikSolomonAlgebra + return OrlikSolomonAlgebra(R, self, ordering) + # polytopes def matroid_polytope(self): diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index cf0c1ba5906..c32d67b55c8 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -974,6 +974,33 @@ cdef class CachedFunction(object): self.cache[k] = w return w + def cached(self, *args, **kwds): + """ + Return the result from the cache if available. If the value is + not cached, raise ``KeyError``. + + EXAMPLES:: + + sage: @cached_function + ....: def f(x): + ....: return x + sage: f.cached(5) + Traceback (most recent call last): + ... + KeyError: ((5,), ()) + sage: f(5) + 5 + sage: f.cached(5) + 5 + """ + k = self.get_key_args_kwds(args, kwds) + + try: + return self.cache[k] + except TypeError: # k is not hashable + k = dict_key(k) + return self.cache[k] + def get_cache(self): """ Returns the cache dictionary. @@ -1883,6 +1910,47 @@ cdef class CachedMethodCaller(CachedFunction): cache[k] = w return w + def cached(self, *args, **kwds): + """ + Return the result from the cache if available. If the value is + not cached, raise ``KeyError``. + + EXAMPLES:: + + sage: class CachedMethodTest(object): + ....: @cached_method + ....: def f(self, x): + ....: return x + sage: o = CachedMethodTest() + sage: CachedMethodTest.f.cached(o, 5) + Traceback (most recent call last): + ... + KeyError: ((5,), ()) + sage: o.f.cached(5) + Traceback (most recent call last): + ... + KeyError: ((5,), ()) + sage: o.f(5) + 5 + sage: CachedMethodTest.f.cached(o, 5) + 5 + sage: o.f.cached(5) + 5 + """ + if self._instance is None: + # cached method bound to a class + instance = args[0] + args = args[1:] + return self._cachedmethod.__get__(instance).cached(*args, **kwds) + + k = self.get_key_args_kwds(args, kwds) + + try: + return self.cache[k] + except TypeError: # k is not hashable + k = dict_key(k) + return self.cache[k] + def __get__(self, inst, cls): r""" Get a :class:`CachedMethodCaller` bound to a specific diff --git a/src/sage/misc/latex_macros.py b/src/sage/misc/latex_macros.py index 4feaadc205a..dc12929d955 100644 --- a/src/sage/misc/latex_macros.py +++ b/src/sage/misc/latex_macros.py @@ -73,7 +73,7 @@ def produce_latex_macro(name, *sample_args): If the Sage object is not in the global name space, describe it like so:: - sage: produce_latex_macro('sage.rings.finite_rings.constructor.FiniteField', 3) + sage: produce_latex_macro('sage.rings.finite_rings.finite_field_constructor.FiniteField', 3) '\\newcommand{\\FiniteField}[1]{\\Bold{F}_{#1}}' """ from sage.misc.latex import LatexCall diff --git a/src/sage/misc/memory_info.py b/src/sage/misc/memory_info.py index 9a05f816721..46fb3853b8b 100644 --- a/src/sage/misc/memory_info.py +++ b/src/sage/misc/memory_info.py @@ -20,15 +20,19 @@ 15340593152 """ -######################################################################## +#***************************************************************************** # Copyright (C) 2012 Volker Braun # -# Distributed under the terms of the GNU General Public License (GPL) -# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ -######################################################################## +#***************************************************************************** + import subprocess +from sys import maxsize from sage.structure.sage_object import SageObject memory_info_instance = None @@ -100,16 +104,20 @@ def rlimit_address_space(self): """ import resource try: - return resource.getrlimit(resource.RLIMIT_AS)[1] + limit = resource.getrlimit(resource.RLIMIT_AS)[0] except resource.error: return -1 + if limit == resource.RLIM_INFINITY: + return -1 + return limit def virtual_memory_limit(self): """ Return the upper limit for virtual memory usage - This is the value set by ``ulimit -v`` at the command line or - a practical limit if no limit is set. + This is the value set by ``ulimit -v`` at the command line + (bounded by ``sys.maxsize``) or a practical limit if no limit + is set. OUTPUT: @@ -121,18 +129,15 @@ def virtual_memory_limit(self): sage: mem = MemoryInfo() sage: mem.virtual_memory_limit() > 0 True + sage: mem.virtual_memory_limit() <= sys.maxsize + True """ limit = self.rlimit_address_space() - if limit >=0: - return limit - else: - avail = self.total_swap() + self.total_ram() - import platform - if platform.architecture()[0] == '32bit': - # 2GB is likely the single-process address space limit - return min(avail, 2 * 1024**3) - else: - return avail + if limit < 0: + limit = self.total_swap() + self.total_ram() + + # Use less than half of the addressable memory + return min(maxsize, limit) class MemoryInfo_proc(MemoryInfo_base): diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index 9aaf25946e5..d9d91213624 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -119,6 +119,7 @@ def foo(unsigned int x=1, a=')"', b={not (2+1==3):'bar'}, *args, **kwds): return import os import tokenize import types +import re EMBEDDED_MODE = False from sage.env import SAGE_SRC @@ -1500,6 +1501,53 @@ def foo(x, a='\')"', b={not (2+1==3):'bar'}): return defaults = None return inspect.ArgSpec(args, varargs, varkw, defaults) + +_re_address = re.compile(" *at 0x[0-9a-fA-F]+") + +def formatvalue_reproducible(obj): + """ + Format the default value for an argspec in a reproducible way: the + output should not depend on the system or the Python session. + + INPUT: + + - ``obj`` -- any object + + OUTPUT: a string + + EXAMPLES:: + + sage: from sage.misc.sageinspect import formatvalue_reproducible + sage: x = object() + sage: formatvalue_reproducible(x) + '=' + sage: formatvalue_reproducible([object(), object()]) + '=[, ]' + """ + s = _re_address.sub("", repr(obj)) + return "=" + s + + +def sage_formatargspec(*argspec): + """ + Format the argspec in a reproducible way. + + EXAMPLES:: + + sage: import inspect + sage: from sage.misc.sageinspect import sage_getargspec + sage: from sage.misc.sageinspect import sage_formatargspec + sage: def foo(f=lambda x:x): pass + sage: A = sage_getargspec(foo) + sage: print inspect.formatargspec(*A) + (f= at 0x...>) + sage: print sage_formatargspec(*A) + (f=>) + """ + s = inspect.formatargspec(*argspec, formatvalue=formatvalue_reproducible) + return s + + def sage_getdef(obj, obj_name=''): r""" Return the definition header for any callable object. @@ -1978,7 +2026,7 @@ def sage_getsourcelines(obj): sage: from sage.misc.sageinspect import sage_getsourcelines sage: sage_getsourcelines(matrix)[1] - 732 + 733 sage: sage_getsourcelines(matrix)[0][0][6:] 'MatrixFactory(object):\n' diff --git a/src/sage/modular/arithgroup/farey.cpp b/src/sage/modular/arithgroup/farey.cpp index 7c436629471..ddbea242caf 100644 --- a/src/sage/modular/arithgroup/farey.cpp +++ b/src/sage/modular/arithgroup/farey.cpp @@ -709,7 +709,7 @@ vector FareySymbol::init_cusps() const { } } } - // in earlier version: shift negative cusps to positve ones + // in earlier version: shift negative cusps to positive ones sort(c.begin(), c.end()); return c; } diff --git a/src/sage/modular/modform/eis_series.py b/src/sage/modular/modform/eis_series.py index a5283679311..4f141cdf0cc 100644 --- a/src/sage/modular/modform/eis_series.py +++ b/src/sage/modular/modform/eis_series.py @@ -18,7 +18,7 @@ from sage.modular.arithgroup.congroup_gammaH import GammaH_class from sage.rings.all import Integer, CyclotomicField, ZZ, QQ, Integer from sage.arith.all import bernoulli, divisors, is_squarefree, lcm -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.power_series_ring import PowerSeriesRing from eis_series_cython import eisenstein_series_poly, Ek_ZZ diff --git a/src/sage/modular/modform_hecketriangle/graded_ring_element.py b/src/sage/modular/modform_hecketriangle/graded_ring_element.py index 276b7fc484a..dc813360ade 100644 --- a/src/sage/modular/modform_hecketriangle/graded_ring_element.py +++ b/src/sage/modular/modform_hecketriangle/graded_ring_element.py @@ -1524,7 +1524,7 @@ def _q_expansion_cached(self, prec, fix_d, subs_d, d_num_prec, fix_prec = False) if (fix_prec == False): #if (prec <1): - # print "Warning: non-positiv precision!" + # print "Warning: non-positive precision!" if ((not self.is_zero()) and prec <= self.order_at(infinity)): from warnings import warn warn("precision too low to determine any coefficient!") diff --git a/src/sage/modular/overconvergent/hecke_series.py b/src/sage/modular/overconvergent/hecke_series.py index 3ec5e47d435..4cbd969b822 100644 --- a/src/sage/modular/overconvergent/hecke_series.py +++ b/src/sage/modular/overconvergent/hecke_series.py @@ -78,7 +78,7 @@ from sage.functions.all import floor, ceil from sage.arith.all import valuation from sage.rings.all import ZZ, Zmod, Infinity, Integer -from sage.rings.finite_rings.constructor import GF +from sage.rings.finite_rings.finite_field_constructor import GF from sage.modular.modform.all import ModularForms, ModularFormsRing, delta_qexp, eisenstein_series_qexp from sage.modular.dims import dimension_modular_forms from sage.misc.functional import dimension,transpose,charpoly diff --git a/src/sage/modules/all.py b/src/sage/modules/all.py index a01a0147e94..0be9f6b4c31 100644 --- a/src/sage/modules/all.py +++ b/src/sage/modules/all.py @@ -29,3 +29,7 @@ lazy_import("sage.modules", ("vector_symbolic_dense", "vector_callable_symbolic_dense"), deprecation=18140) + +lazy_import('sage.modules.filtered_vector_space', 'FilteredVectorSpace') +lazy_import('sage.modules.multi_filtered_vector_space', 'MultiFilteredVectorSpace') + diff --git a/src/sage/modules/filtered_vector_space.py b/src/sage/modules/filtered_vector_space.py new file mode 100644 index 00000000000..b8624985742 --- /dev/null +++ b/src/sage/modules/filtered_vector_space.py @@ -0,0 +1,1250 @@ +r""" +`\ZZ`-Filtered Vector Spaces + +This module implements filtered vector spaces, that is, a descending +sequence of vector spaces + +.. math:: + + \cdots \supset F_d \supset F_{d+1} \supset F_{d+2} \supset \cdots + +with degrees `d\in \ZZ`. It is not required that `F_d` is the entire +ambient space for `d\ll 0` (see +:meth:`~FilteredVectorSpace_class.is_exhaustive`) nor that `F_d=0` for +`d\gg 0` (see :meth:`~FilteredVectorSpace_class.is_separating`). To +construct a filtered vector space, use the :func:`FilteredVectorSpace` +command. It supports easy creation of simple filtrations, for example +the trivial one:: + + sage: FilteredVectorSpace(2, base_ring=RDF) + RDF^2 + +The next-simplest filtration has a single non-trivial inclusion +between `V_d` and `V_{d+1}`:: + + sage: d = 1 + sage: V = FilteredVectorSpace(2, d); V + QQ^2 >= 0 + sage: [V.get_degree(i).dimension() for i in range(0,4)] + [2, 2, 0, 0] + +To construct general filtrations, you need tell Sage about generating +vectors for the nested subspaces. For example, a dictionary whose keys +are the degrees and values are a list of generators:: + + sage: r1 = (1, 0, 5) + sage: r2 = (0, 1, 2) + sage: r3 = (1, 2, 1) + sage: V = FilteredVectorSpace({0:[r1, r2, r3], 1:[r1, r2], 3:[r1]}); V + QQ^3 >= QQ^2 >= QQ^1 >= QQ^1 >= 0 + +For degrees `d` that are not specified, the associated vector subspace +is the same as the next-lower degree, that is, `V_d \simeq +V_{d-1}`. In the above example, this means that + +* `V_d \simeq \QQ^3` for `d<0` +* `V_0 = \mathop{span}(r_1, r_2) \simeq \QQ^2` +* `V_1 = V_2 = \mathop{span}(r_3) \simeq \QQ` +* `V_d = 0` for `d \geq 3` + +That is:: + + sage: V.get_degree(0) == V + True + sage: V.get_degree(1) == V.span([r1, r2]) + True + sage: V.get_degree(2) == V.get_degree(3) == V.span([r1]) + True + sage: V.get_degree(4) == V.get_degree(5) == V.span([]) + True + +If you have many generators you can just pass the generators once and +then refer to them by index:: + + sage: FilteredVectorSpace([r1, r2, r3], {0:[0,1,2], 1:[1,2], 3:[1]}) + QQ^3 >= QQ^2 >= QQ^1 >= QQ^1 >= 0 + +Note that generators for the degree-`d` subspace of the filtration are +automatically generators for all lower degrees. For example, here we +do not have to specify the ray `r_2` separately in degree 1:: + + sage: FilteredVectorSpace([r1, r2, r3], {0:[0 ], 1:[1]}) + QQ^2 >= QQ^1 >= 0 in QQ^3 + sage: FilteredVectorSpace([r1, r2, r3], {0:[0, 1], 1:[1]}) + QQ^2 >= QQ^1 >= 0 in QQ^3 + +The degree can be infinite (plus infinity), this allows construction +of filtered vector spaces that are not eventually zero in high +degree:: + + sage: FilteredVectorSpace([r1, r2, r3], {0:[0,1], oo:[1]}) + QQ^2 >= QQ^1 in QQ^3 + +Any field can be used as the vector space base. For example a finite +field:: + + sage: F. = GF(5^3) + sage: r1 = (a, 0, F(5)); r1 + (a, 0, 0) + sage: FilteredVectorSpace([r1, r2, r3], {0:[0,1], oo:[1]}, base_ring=F) + GF(125)^2 >= GF(125)^1 in GF(125)^3 + +Or the algebraic field:: + + sage: r1 = (1, 0, 1+QQbar(I)); r1 + (1, 0, I + 1) + sage: FilteredVectorSpace([r1, r2, r3], {0:[0,1], oo:[1]}, base_ring=QQbar) + Vector space of dimension 2 over Algebraic Field + >= Vector space of dimension 1 over Algebraic Field + in Vector space of dimension 3 over Algebraic Field +""" + +#***************************************************************************** +# Copyright (C) 2013 Volker Braun +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.rings.all import QQ, ZZ, RDF, RR, Integer +from sage.rings.infinity import InfinityRing, infinity, minus_infinity +from sage.categories.fields import Fields +from sage.modules.free_module import FreeModule_ambient_field, VectorSpace +from sage.matrix.constructor import vector, matrix +from sage.misc.all import uniq, cached_method + + +def is_FilteredVectorSpace(X): + """ + Test whether ``X`` is a filtered vector space. + + This function is for library use only. + + INPUT: + + - ``X`` -- anything. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: from sage.modules.filtered_vector_space import is_FilteredVectorSpace + sage: V = FilteredVectorSpace(2, 1) + sage: is_FilteredVectorSpace(V) + True + sage: is_FilteredVectorSpace('ceci n\'est pas une pipe') + False + """ + return isinstance(X, FilteredVectorSpace_class) + + +def FilteredVectorSpace(arg1, arg2=None, base_ring=QQ, check=True): + """ + Construct a filtered vector space. + + INPUT: + + This function accepts various input that determines the vector space and filtration. + + - Just the dimensionFilteredVectorSpace(dimension): Return the trivial filtration + (where all vector spaces are isomorphic). + + - Dimension and maximal degree, see + :func:`constructor_from_dim_degree` for arguments. Construct a + filtration with only one non-trivial step `V\supset 0` at the + given cutoff degree. + + - A dictionary containing the degrees as keys and a list of vector + space generators as values, see + :func:`FilteredVectorSpace_from_generators` + + - Generators and a dictionary containing the degrees as keys and + the indices of vector space generators as values, see + :func:`FilteredVectorSpace_from_generators_indices` + + In addition, the following keyword arguments are supported: + + - ``base_ring`` -- a field (optional, default `\QQ`). The base + field of the vector space. Must be a field. + + EXAMPLES: + + Just the dimension for the trivial filtration:: + + sage: FilteredVectorSpace(2) + QQ^2 + + Dimension and degree:: + + sage: FilteredVectorSpace(2, 1) + QQ^2 >= 0 + + Dictionary of generators:: + + sage: FilteredVectorSpace({1:[(1,0), (0,1)], 3:[(1,0)]}) + QQ^2 >= QQ^1 >= QQ^1 >= 0 + + Generators and a dictionary referring to them by index:: + + sage: FilteredVectorSpace([(1,0), (0,1)], {1:[0,1], 3:[0]}) + QQ^2 >= QQ^1 >= QQ^1 >= 0 + """ + if base_ring not in Fields(): + raise ValueError('the base_ring argument must be a field') + if arg1 in ZZ: + return construct_from_dim_degree(arg1, arg2, base_ring, check) + elif arg2 is None: + return construct_from_generators(arg1, base_ring, check) + else: + return construct_from_generators_indices(arg1, arg2, base_ring, check) + + +def normalize_degree(deg): + """ + Normalized the degree + + - ``deg`` -- something that defines the degree (either integer or + infinity). + + OUTPUT: + + Plus/minus infinity or a Sage integer. + + EXAMPLES:: + + sage: from sage.modules.filtered_vector_space import normalize_degree + sage: type(normalize_degree(int(1))) + + sage: normalize_degree(oo) + +Infinity + """ + try: + return ZZ(deg) + except TypeError: + pass + deg = InfinityRing(deg) + if deg == infinity: + return infinity + if deg == minus_infinity: + return minus_infinity + raise ValueError('not integer or infinity') + + +def construct_from_dim_degree(dim, max_degree, base_ring, check): + """ + Construct a filtered vector space. + + INPUT: + + - ``dim`` -- integer. The dimension. + + - ``max_degree`` -- integer or infinity. The maximal degree where + the vector subspace of the filtration is still the entire space. + + EXAMPLES:: + + sage: V = FilteredVectorSpace(2, 5); V + QQ^2 >= 0 + sage: V.get_degree(5) + Vector space of degree 2 and dimension 2 over Rational Field + Basis matrix: + [1 0] + [0 1] + sage: V.get_degree(6) + Vector space of degree 2 and dimension 0 over Rational Field + Basis matrix: + [] + + sage: FilteredVectorSpace(2, oo) + QQ^2 + sage: FilteredVectorSpace(2, -oo) + 0 in QQ^2 + + TESTS:: + + sage: from sage.modules.filtered_vector_space import construct_from_dim_degree + sage: V = construct_from_dim_degree(2, 5, QQ, True); V + QQ^2 >= 0 + """ + if dim not in ZZ: + raise ValueError('dimension must be an integer') + dim = ZZ(dim) + from sage.matrix.constructor import identity_matrix + generators = identity_matrix(base_ring, dim).columns() + filtration = dict() + if max_degree is None: + max_degree = infinity + filtration[normalize_degree(max_degree)] = range(dim) + return construct_from_generators_indices(generators, filtration, base_ring, check) + + +def construct_from_generators(filtration, base_ring, check): + """ + Construct a filtered vector space. + + INPUT: + + - ``filtration`` -- a dictionary of filtration steps. Each + filtration step is a pair consisting of an integer degree and a + list/tuple/iterable of vector space generators. The integer + ``degree`` stipulates that all filtration steps of degree higher + or equal than ``degree`` (up to the next filtration step) are + said subspace. + + EXAMPLES:: + + sage: from sage.modules.filtered_vector_space import construct_from_generators + sage: r = [1, 2] + sage: construct_from_generators({1:[r]}, QQ, True) + QQ^1 >= 0 in QQ^2 + """ + def normalize_gen(v): + return tuple(map(base_ring, v)) + + # convert generator notation to generator+indices + if len(filtration) == 0: + raise ValueError('you need to specify at least one ray to deduce the dimension') + generators = [] + for gens in filtration.values(): + generators += map(normalize_gen, gens) + generators = tuple(uniq(generators)) + + # normalize filtration data + normalized = dict() + for deg, gens_deg in filtration.iteritems(): + indices = [generators.index(normalize_gen(v)) for v in gens_deg] + normalized[deg] = tuple(indices) + return construct_from_generators_indices(generators, normalized, base_ring, check) + + +def construct_from_generators_indices(generators, filtration, base_ring, check): + """ + Construct a filtered vector space. + + INPUT: + + - ``generators`` -- a list/tuple/iterable of vectors, or something + convertible to them. The generators spanning various + subspaces. + + - ``filtration`` -- a list or iterable of filtration steps. Each + filtration step is a pair ``(degree, ray_indices)``. The + ``ray_indices`` are a list or iterable of ray indices, which + span a subspace of the vector space. The integer ``degree`` + stipulates that all filtration steps of degree higher or equal + than ``degree`` (up to the next filtration step) are said + subspace. + + EXAMPLES:: + + sage: from sage.modules.filtered_vector_space import construct_from_generators_indices + sage: gens = [(1,0), (0,1), (-1,-1)] + sage: V = construct_from_generators_indices(gens, {1:[0,1], 3:[1]}, QQ, True); V + QQ^2 >= QQ^1 >= QQ^1 >= 0 + + TESTS:: + + sage: gens = [(int(1),int(0)), (0,1), (-1,-1)] + sage: construct_from_generators_indices(iter(gens), {int(0):[0, int(1)], 2:[2]}, QQ, True) + QQ^2 >= QQ^1 >= QQ^1 >= 0 + """ + # normalize generators + generators = map(list, generators) + + # deduce dimension + if len(generators) == 0: + dim = ZZ(0) + else: + dim = ZZ(len(generators[0])) + ambient = VectorSpace(base_ring, dim) + + # complete generators to a generating set + if matrix(base_ring, generators).rank() < dim: + complement = ambient.span(generators).complement() + generators = generators + list(complement.gens()) + # normalize generators II + generators = tuple(ambient(v) for v in generators) + + for v in generators: + v.set_immutable() + + # normalize filtration data + normalized = dict() + for deg, gens in filtration.iteritems(): + deg = normalize_degree(deg) + gens = map(ZZ, gens) + if any(i < 0 or i >= len(generators) for i in gens): + raise ValueError('generator index out of bounds') + normalized[deg] = tuple(sorted(gens)) + try: + del normalized[minus_infinity] + except KeyError: + pass + filtration = normalized + + return FilteredVectorSpace_class(base_ring, dim, generators, filtration, check=check) + + + + +class FilteredVectorSpace_class(FreeModule_ambient_field): + + def __init__(self, base_ring, dim, generators, filtration, check=True): + r""" + A descending filtration of a vector space + + INPUT: + + - ``base_ring`` -- a field. The base field of the ambient vector space. + + - ``dim`` -- integer. The dimension of the ambient vector space. + + - ``generators`` -- tuple of generators for the ambient vector + space. These will be used to span the subspaces of the + filtration. + + - ``filtration`` -- a dictionary of filtration steps in ray + index notation. See + :func:`construct_from_generators_indices` for details. + + - ``check`` -- boolean (optional; default: ``True``). Whether + to perform consistency checks. + + TESTS:: + + sage: from sage.modules.filtered_vector_space import FilteredVectorSpace_class + sage: gens = [(1,0,0), (1,1,0), (1,2,0), (-1,-1, 0), (0,0,1)] + sage: FilteredVectorSpace_class(QQ, 3, gens, {2:(0,1), oo:(4,)}) + QQ^3 >= QQ^1 + sage: FilteredVectorSpace_class(QQ, 3, gens, {2:(0,1), 3:(4,)}) + QQ^3 >= QQ^1 >= 0 + + The trivial filtration:: + + sage: FilteredVectorSpace_class(QQ, 3, gens, {}, QQ) + 0 in QQ^3 + + The empty vector space:: + + sage: FilteredVectorSpace_class(QQ, 0, [], {}) + 0 + + Higher-degree generators are automatically generators in lower degrees:: + + sage: FilteredVectorSpace_class(QQ, 3, gens, {2:(4,), 3:(1,)}) + QQ^2 >= QQ^1 >= 0 in QQ^3 + """ + if check: + assert isinstance(dim, Integer) + assert base_ring in Fields() + super(FilteredVectorSpace_class, self).__init__(base_ring, dim) + + if check: + assert matrix(generators).rank() == self.dimension() + assert isinstance(filtration, dict) + for degree, indices in filtration.iteritems(): + assert isinstance(degree, Integer) or degree == infinity + assert isinstance(indices, tuple) + assert all(isinstance(r, Integer) for r in indices) + + # Construct subspaces from the generators and store in self._filt + def make_subspace(indices): + return self.span([generators[i] for i in indices]) + + indices = set(filtration.pop(infinity, [])) + V = make_subspace(indices) + filtered_subspaces = [(infinity, V)] + for deg in reversed(sorted(filtration.keys())): + next_V = V + indices.update(filtration[deg]) + V = make_subspace(indices) + if V == next_V: # skip trivial filtrations + continue + filtered_subspaces.append((deg, V)) + filtered_subspaces.append((minus_infinity, V)) + filtered_subspaces.reverse() + self._filt = tuple(filtered_subspaces) + assert self._filt[0][0] is minus_infinity + + def change_ring(self, base_ring): + """ + Return the same filtration over a different base ring. + + INPUT: + + - ``base_ring`` -- a ring. The new base ring. + + OUTPUT: + + This method returns a new filtered vector space whose + subspaces are defined by the same generators but over a + different base ring. + + EXAMPLES:: + + sage: V = FilteredVectorSpace(1, 0); V + QQ^1 >= 0 + sage: V.change_ring(RDF) + RDF^1 >= 0 + """ + generators, filtration = self.presentation() + return FilteredVectorSpace(generators, filtration, base_ring=base_ring) + + def ambient_vector_space(self): + """ + Return the ambient (unfiltered) vector space. + + OUTPUT: + + A vector space. + + EXAMPLES:: + + sage: V = FilteredVectorSpace(1, 0) + sage: V.ambient_vector_space() + Vector space of dimension 1 over Rational Field + """ + return VectorSpace(self.base_ring(), self.dimension()) + + @cached_method + def is_constant(self): + """ + Return whether the filtration is constant. + + OUTPUT: + + Boolean. Whether the filtered vector spaces are identical in + all degrees. + + EXAMPLES:: + + sage: V = FilteredVectorSpace(2); V + QQ^2 + sage: V.is_constant() + True + + sage: V = FilteredVectorSpace(1, 0); V + QQ^1 >= 0 + sage: V.is_constant() + False + + sage: V = FilteredVectorSpace({0:[(1,)]}); V + QQ^1 >= 0 + sage: V.is_constant() + False + """ + f = self._filt + return (len(f) == 1) or (len(f) == 2 and f[1][0] == infinity) + + def is_exhaustive(self): + """ + Return whether the filtration is exhaustive. + + A filtration $\{F_d\}$ in an ambient vector space $V$ is + exhaustive if $\cup F_d = V$. See also :meth:`is_separating`. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: F = FilteredVectorSpace({0:[(1,1)]}); F + QQ^1 >= 0 in QQ^2 + sage: F.is_exhaustive() + False + sage: G = FilteredVectorSpace(2, 0); G + QQ^2 >= 0 + sage: G.is_exhaustive() + True + """ + return self.get_degree(minus_infinity).dimension() == \ + self.ambient_vector_space().dimension() + + def is_separating(self): + """ + Return whether the filtration is separating. + + A filtration $\{F_d\}$ in an ambient vector space $V$ is + exhaustive if $\cap F_d = 0$. See also :meth:`is_exhaustive`. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: F = FilteredVectorSpace({0:[(1,1)]}); F + QQ^1 >= 0 in QQ^2 + sage: F.is_separating() + True + sage: G = FilteredVectorSpace({0:[(1,1,0)], oo:[(0,0,1)]}); G + QQ^2 >= QQ^1 in QQ^3 + sage: G.is_separating() + False + """ + return self.get_degree(infinity).dimension() == 0 + + @cached_method + def support(self): + """ + Return the degrees in which there are non-trivial generators. + + OUTPUT: + + A tuple of integers (and plus infinity) in ascending + order. The last entry is plus infinity if and only if the + flitration is not separating (see :meth:`is_separating`). + + EXAMPLES:: + + sage: G = FilteredVectorSpace({0:[(1,1,0)], 3:[(0,1,0)]}); G + QQ^2 >= QQ^1 >= QQ^1 >= QQ^1 >= 0 in QQ^3 + sage: G.support() + (0, 3) + + sage: G = FilteredVectorSpace({0:[(1,1,0)], 3:[(0,1,0)], oo:[(0,0,1)]}); G + QQ^3 >= QQ^2 >= QQ^2 >= QQ^2 >= QQ^1 + sage: G.support() + (0, 3, +Infinity) + """ + if self.is_separating(): + filt = self._filt[1:-1] + else: + filt = self._filt[1:] + return tuple(f[0] for f in filt) + + @cached_method + def min_degree(self): + r""" + Return the lowest degree of the filtration. + + OUTPUT: + + Integer or plus infinity. The largest degree `d` of the + (descending) filtration such that the filtered vector space + `F_d` is still equal to `F_{-\infty}`. + + EXAMPLES:: + + sage: FilteredVectorSpace(1, 3).min_degree() + 3 + sage: FilteredVectorSpace(2).min_degree() + +Infinity + """ + if self.is_constant(): + return infinity + return self._filt[1][0] + + @cached_method + def max_degree(self): + r""" + Return the highest degree of the filtration. + + OUTPUT: + + Integer or minus infinity. The smallest degree of the + filtration such that the filtration is constant to the right. + + EXAMPLES:: + + sage: FilteredVectorSpace(1, 3).max_degree() + 4 + sage: FilteredVectorSpace({0:[[1]]}).max_degree() + 1 + sage: FilteredVectorSpace(3).max_degree() + -Infinity + """ + f = self._filt + if len(f) == 1: + return minus_infinity + d = f[-1][0] + if d == infinity: + if len(f) == 1: + return minus_infinity + else: + return f[-2][0] + 1 + else: + return d + 1 + + def get_degree(self, d): + r""" + Return the degree-``d`` entry of the filtration. + + INPUT: + + - ``d`` -- Integer. The desired degree of the filtration. + + OUTPUT: + + The degree-``d`` vector space in the filtration as subspace of + the ambient space. + + EXAMPLES:: + + sage: rays = [(1,0), (1,1), (1,2), (-1,-1)] + sage: F = FilteredVectorSpace(rays, {3:[1], 1:[1,2]}) + sage: F.get_degree(2) + Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [1 1] + sage: F.get_degree(oo) + Vector space of degree 2 and dimension 0 over Rational Field + Basis matrix: + [] + sage: F.get_degree(-oo) + Vector space of degree 2 and dimension 2 over Rational Field + Basis matrix: + [1 0] + [0 1] + """ + d = normalize_degree(d) + for deg, Vdeg in self._filt: + if d <= deg: + return Vdeg + assert False # unreachable + + def graded(self, d): + r""" + Return the associated graded vectorspace. + + INPUT: + + - ``d`` -- integer. The degree. + + OUTPUT: + + The quotient `G_d = F_d / F_{d+1}`. + + EXAMPLES:: + + sage: rays = [(1,0), (1,1), (1,2)] + sage: F = FilteredVectorSpace(rays, {3:[1], 1:[1,2]}) + sage: F.graded(1) + Vector space quotient V/W of dimension 1 over Rational Field where + V: Vector space of degree 2 and dimension 2 over Rational Field + Basis matrix: + [1 0] + [0 1] + W: Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [1 1] + """ + return self.get_degree(d).quotient(self.get_degree(d+1)) + + def presentation(self): + """ + Return a presentation in term of generators of various degrees. + + OUTPUT: + + A pair consisting of generators and a filtration suitable as + input to :func:`~construct_from_generators_indices`. + + EXAMPLES:: + + sage: rays = [(1,0), (1,1), (1,2), (-1,-1)] + sage: F = FilteredVectorSpace(rays, {0:[1, 2], 2:[3]}); F + QQ^2 >= QQ^1 >= QQ^1 >= 0 + sage: F.presentation() + (((0, 1), (1, 0), (1, 1)), {0: (1, 0), 2: (2,), +Infinity: ()}) + """ + # this could be done more efficiently with (potentially) less generators + generators = set() + filt = self._filt[1:] + for d, V in filt: + generators.update(V.echelonized_basis()) + generators = tuple(generators) + + filtration = dict() + for d, V in filt: + indices = [ZZ(generators.index(v)) for v in V.echelonized_basis()] + filtration[d] = tuple(indices) + return generators, filtration + + def _repr_field_name(self): + """ + Return an abbreviated field name as string + + RAISES: + + ``NotImplementedError``: The field does not have an + abbreviated name defined. + + EXAMPLES:: + + sage: FilteredVectorSpace(2, base_ring=QQ)._repr_field_name() + 'QQ' + + sage: F. = GF(9) + sage: FilteredVectorSpace(2, base_ring=F)._repr_field_name() + 'GF(9)' + + sage: FilteredVectorSpace(2, base_ring=AA)._repr_field_name() + Traceback (most recent call last): + ... + NotImplementedError + """ + if self.base_ring() == QQ: + return 'QQ' + elif self.base_ring() == RDF: + return 'RDF' + elif self.base_ring() == RR: + return 'RR' + from sage.categories.finite_fields import FiniteFields + if self.base_ring() in FiniteFields(): + return 'GF({0})'.format(len(self.base_ring())) + else: + raise NotImplementedError() + + def _repr_vector_space(self, dim): + """ + Return a string representation of the vector space of given dimension + + INPUT: + + - ``dim`` -- integer. + + OUTPUT: + + String representation of the vector space of dimension ``dim``. + + EXAMPLES:: + + sage: F = FilteredVectorSpace(3, base_ring=RDF) + sage: F._repr_vector_space(1234) + 'RDF^1234' + sage: F3 = FilteredVectorSpace(3, base_ring=GF(3)) + sage: F3._repr_vector_space(1234) + 'GF(3)^1234' + sage: F3 = FilteredVectorSpace(3, base_ring=AA) + sage: F3._repr_vector_space(1234) + 'Vector space of dimension 1234 over Algebraic Real Field' + """ + if dim == 0: + return '0' + try: + return self._repr_field_name() + '^' + str(dim) + except NotImplementedError: + return repr(VectorSpace(self.base_ring(), dim)) + + def _repr_degrees(self, min_deg, max_deg): + """ + Return a string representation + + This method is like :meth:`_repr_` except that the user can + select the range of degrees to be shown in the output. + + INPUT: + + - ``min_deg``, ``max_deg`` -- two integers. + + EXAMPLES:: + + sage: rays = [(1,0), (1,1), (1,2), (-1,-1)] + sage: F = FilteredVectorSpace(rays, {0:[1, 2], 2:[3]}) + sage: F._repr_degrees(-2, 4) + ['QQ^2', 'QQ^2', 'QQ^2', 'QQ^1', 'QQ^1', '0', '0', '0'] + """ + degrees = range(min_deg, max_deg+1) + dims = [] + for i in degrees + [infinity]: + d = self.get_degree(i).dimension() + dims.append(self._repr_vector_space(d)) + return dims + + def _repr_(self): + r""" + Return as string representation of ``self``. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: rays = [(1,0), (1,1), (1,2), (-1,-1)] + sage: FilteredVectorSpace(rays, {0:[1, 2], 2:[3]})._repr_() + 'QQ^2 >= QQ^1 >= QQ^1 >= 0' + sage: FilteredVectorSpace(rays, {0:[1, 2], oo:[3]}) + QQ^2 >= QQ^1 + sage: FilteredVectorSpace(rays, {oo:[3]}) + QQ^1 in QQ^2 + sage: FilteredVectorSpace(rays, {0:[3]}) + QQ^1 >= 0 in QQ^2 + sage: FilteredVectorSpace({1:[(1,0), (-1,1)], 3:[(1,0)]}, base_ring=GF(3)) + GF(3)^2 >= GF(3)^1 >= GF(3)^1 >= 0 + sage: FilteredVectorSpace({1:[(1,0), (-1,1)], 3:[(1,0)]}, base_ring=AA) + Vector space of dimension 2 over Algebraic Real Field + >= Vector space of dimension 1 over Algebraic Real Field + >= Vector space of dimension 1 over Algebraic Real Field >= 0 + """ + finite_support = [d for d in self.support() if d != infinity] + if len(finite_support) == 0: + dims = self._repr_degrees(0, -1) + else: + min_deg = finite_support[0] + max_deg = finite_support[-1] + dims = self._repr_degrees(min_deg, max_deg) + s = ' >= '.join(dims) + if not self.is_exhaustive(): + s += ' in ' + self._repr_vector_space(self.degree()) + return s + + def __cmp__(self, other): + """ + Compare two filtered vector spaces. + + EXAMPLES:: + + sage: V = FilteredVectorSpace(2, 0) + sage: W = FilteredVectorSpace([(1,0),(0,1)], {0:[0, 1]}) + sage: V == W + True + sage: V is W + False + + sage: W = FilteredVectorSpace([(1,0),(1,1)], {0:[1]}) + sage: V == W + False + + TESTS:: + + sage: P = toric_varieties.P2() + sage: T_P = P.sheaves.tangent_bundle() + sage: O_P = P.sheaves.trivial_bundle(1) + sage: S1 = T_P + O_P + sage: S2 = O_P + T_P + sage: S1._filt[0].is_isomorphic(S2._filt[0]) # known bug + True + sage: FilteredVectorSpace(2, base_ring=QQ) == FilteredVectorSpace(2, base_ring=GF(5)) + False + """ + c = cmp(type(self), type(other)) + if c!=0: return c + c = cmp(self.base_ring(), other.base_ring()) + if c!=0: return c + c = cmp(self.dimension(), other.dimension()) + if c!=0: return c + c = cmp(len(self._filt), len(other._filt)) + if c!=0: return c + for self_filt, other_filt in zip(self._filt, other._filt): + c = cmp(self_filt[0], other_filt[0]) # compare degree + if c!=0: return c + c = cmp(self_filt[1].echelonized_basis_matrix(), # compare vector subspace + other_filt[1].echelonized_basis_matrix()) + if c!=0: return c + return 0 + + def direct_sum(self, other): + """ + Return the direct sum. + + INPUT: + + - ``other`` -- a filtered vector space. + + OUTPUT: + + The direct sum as a filtered vector space. + + EXAMPLES:: + + sage: V = FilteredVectorSpace(2, 0) + sage: W = FilteredVectorSpace({0:[(1,-1),(2,1)], 1:[(1,1)]}) + sage: V.direct_sum(W) + QQ^4 >= QQ^1 >= 0 + sage: V + W # syntactic sugar + QQ^4 >= QQ^1 >= 0 + sage: V + V == FilteredVectorSpace(4, 0) + True + + sage: W = FilteredVectorSpace([(1,-1),(2,1)], {1:[0,1], 2:[1]}) + sage: V + W + QQ^4 >= QQ^2 >= QQ^1 >= 0 + + A suitable base ring is chosen if they do not match:: + + sage: v = [(1,0), (0,1)] + sage: F1 = FilteredVectorSpace(v, {0:[0], 1:[1]}, base_ring=QQ) + sage: F2 = FilteredVectorSpace(v, {0:[0], 1:[1]}, base_ring=RDF) + sage: F1 + F2 + RDF^4 >= RDF^2 >= 0 + """ + from sage.structure.element import get_coercion_model + base_ring = get_coercion_model().common_parent(self.base_ring(), other.base_ring()) + # construct the generators + self_gens, self_filt = self.presentation() + other_gens, other_filt = other.presentation() + generators = \ + [ list(v) + [base_ring.zero()]*other.dimension() for v in self_gens ] + \ + [ [base_ring.zero()]*self.dimension() + list(v) for v in other_gens ] + # construct the filtration dictionary + def join_indices(self_indices, other_indices): + self_indices = tuple(self_indices) + other_indices = tuple(i + len(self_gens) for i in other_indices) + return self_indices + other_indices + filtration = dict() + self_indices = set() + other_indices = set() + for deg in reversed(uniq(self_filt.keys() + other_filt.keys())): + self_indices.update(self_filt.get(deg, [])) + other_indices.update(other_filt.get(deg, [])) + gens = join_indices(self_indices, other_indices) + filtration[deg] = gens + return FilteredVectorSpace(generators, filtration, base_ring=base_ring) + + __add__ = direct_sum + + def tensor_product(self, other): + r""" + Return the graded tensor product. + + INPUT: + + - ``other`` -- a filtered vector space. + + OUTPUT: + + The graded tensor product, that is, the tensor product of a + generator of degree `d_1` with a generator in degree `d_2` has + degree `d_1 + d_2`. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(1, 1) + sage: F2 = FilteredVectorSpace(1, 2) + sage: F1.tensor_product(F2) + QQ^1 >= 0 + sage: F1 * F2 + QQ^1 >= 0 + + sage: F1.min_degree() + 1 + sage: F2.min_degree() + 2 + sage: (F1*F2).min_degree() + 3 + + A suitable base ring is chosen if they do not match:: + + sage: v = [(1,0), (0,1)] + sage: F1 = FilteredVectorSpace(v, {0:[0], 1:[1]}, base_ring=QQ) + sage: F2 = FilteredVectorSpace(v, {0:[0], 1:[1]}, base_ring=RDF) + sage: F1 * F2 + RDF^4 >= RDF^3 >= RDF^1 >= 0 + """ + V = self + W = other + from sage.structure.element import get_coercion_model + base_ring = get_coercion_model().common_parent(V.base_ring(), W.base_ring()) + from sage.modules.tensor_operations import VectorCollection, TensorOperation + V_generators, V_indices = V.presentation() + W_generators, W_indices = W.presentation() + V_coll = VectorCollection(V_generators, base_ring, V.dimension()) + W_coll = VectorCollection(W_generators, base_ring, W.dimension()) + T = TensorOperation([V_coll, W_coll], 'product') + + filtration = dict() + for V_deg in V.support(): + for W_deg in W.support(): + deg = V_deg + W_deg + indices = filtration.get(deg, set()) + for i in V_indices[V_deg]: + for j in W_indices[W_deg]: + i_tensor_j = T.index_map(i, j) + indices.add(i_tensor_j) + filtration[deg] = indices + return FilteredVectorSpace(T.vectors(), filtration, base_ring=base_ring) + + __mul__ = tensor_product + + def _power_operation(self, n, operation): + """ + Return tensor power operation. + + INPUT: + + - ``n`` -- integer. the number of factors of ``self``. + + - ``operation`` -- string. See + :class:`~sage.modules.tensor_operations.TensorOperation` for + details. + + EXAMPLES:: + + sage: F = FilteredVectorSpace(1, 1) + FilteredVectorSpace(1, 2); F + QQ^2 >= QQ^1 >= 0 + sage: F._power_operation(2, 'symmetric') + QQ^3 >= QQ^2 >= QQ^1 >= 0 + sage: F._power_operation(2, 'antisymmetric') + QQ^1 >= 0 + """ + from sage.modules.tensor_operations import VectorCollection, TensorOperation + generators, indices = self.presentation() + V = VectorCollection(generators, self.base_ring(), self.dimension()) + T = TensorOperation([V] * n, operation) + + iters = [self.support()] * n + filtration = dict() + from sage.categories.cartesian_product import cartesian_product + for degrees in cartesian_product(iters): + deg = sum(degrees) + filt_deg = filtration.get(deg, set()) + for i in cartesian_product([indices.get(d) for d in degrees]): + pow_i = T.index_map(*i) + if pow_i is not None: + filt_deg.add(pow_i) + filtration[deg] = filt_deg + return FilteredVectorSpace(T.vectors(), filtration, base_ring=self.base_ring()) + + + def exterior_power(self, n): + """ + Return the `n`-th graded exterior power. + + INPUT: + + - ``n`` -- integer. Exterior product of how many copies of + ``self``. + + OUTPUT: + + The graded exterior product, that is, the wedge product of a + generator of degree `d_1` with a generator in degree `d_2` has + degree `d_1 + d_2`. + + EXAMPLES:: + + sage: F = FilteredVectorSpace(1, 1) + FilteredVectorSpace(1, 2); F + QQ^2 >= QQ^1 >= 0 + sage: F.exterior_power(1) + QQ^2 >= QQ^1 >= 0 + sage: F.exterior_power(2) + QQ^1 >= 0 + sage: F.exterior_power(3) + 0 + sage: F.wedge(2) + QQ^1 >= 0 + """ + return self._power_operation(n, 'antisymmetric') + + wedge = exterior_power + + def symmetric_power(self, n): + """ + Return the `n`-th graded symmetric power. + + INPUT: + + - ``n`` -- integer. Symmetric product of how many copies of + ``self``. + + OUTPUT: + + The graded symmetric product, that is, the symmetrization of a + generator of degree `d_1` with a generator in degree `d_2` has + degree `d_1 + d_2`. + + EXAMPLES:: + + sage: F = FilteredVectorSpace(1, 1) + FilteredVectorSpace(1, 2); F + QQ^2 >= QQ^1 >= 0 + sage: F.symmetric_power(2) + QQ^3 >= QQ^2 >= QQ^1 >= 0 + """ + return self._power_operation(n, 'symmetric') + + def dual(self): + """ + Return the dual filtered vector space. + + OUTPUT: + + The graded dual, that is, the dual of a degree-`d` subspace is + a set of linear constraints in degree `-d+1`. That is, the + dual generators live in degree `-d`. + + EXAMPLES:: + + sage: gens = identity_matrix(3).rows() + sage: F = FilteredVectorSpace(gens, {0:[0,1,2], 2:[0]}); F + QQ^3 >= QQ^1 >= QQ^1 >= 0 + sage: F.support() + (0, 2) + + sage: F.dual() + QQ^3 >= QQ^2 >= QQ^2 >= 0 + sage: F.dual().support() + (-2, 0) + """ + filtration = dict() + prev_deg = minus_infinity + for deg, V in self._filt[1:]: + filtration[-prev_deg] = V.complement().echelonized_basis() + prev_deg = deg + return FilteredVectorSpace(filtration, base_ring=self.base_ring()) + + def shift(self, deg): + """ + Return a filtered vector space with degrees shifted by a constant. + + EXAMPLES:: + + sage: gens = identity_matrix(3).rows() + sage: F = FilteredVectorSpace(gens, {0:[0,1,2], 2:[0]}); F + QQ^3 >= QQ^1 >= QQ^1 >= 0 + sage: F.support() + (0, 2) + sage: F.shift(-5).support() + (-5, -3) + """ + generators, filtration = self.presentation() + shifted = dict() + for d, indices in filtration.iteritems(): + shifted[d + deg] = indices + return FilteredVectorSpace(generators, shifted, base_ring=self.base_ring()) + + def random_deformation(self, epsilon=None): + """ + Return a random deformation + + INPUT: + + - ``epsilon`` -- a number in the base ring. + + OUTPUT: + + A new filtered vector space where the generators of the + subspaces are moved by ``epsilon`` times a random vector. + + EXAMPLES:: + + sage: gens = identity_matrix(3).rows() + sage: F = FilteredVectorSpace(gens, {0:[0,1,2], 2:[0]}); F + QQ^3 >= QQ^1 >= QQ^1 >= 0 + sage: F.get_degree(2) + Vector space of degree 3 and dimension 1 over Rational Field + Basis matrix: + [1 0 0] + sage: G = F.random_deformation(1/50); G + QQ^3 >= QQ^1 >= QQ^1 >= 0 + sage: G.get_degree(2) + Vector space of degree 3 and dimension 1 over Rational Field + Basis matrix: + [ 1 -15/304 0] + """ + from sage.modules.free_module_element import random_vector + R = self.base_ring() + if epsilon is None: + epsilon = R.one() + filtration = dict() + for deg, filt in self._filt[1:]: + generators = [v + epsilon * random_vector(R, self.rank()) + for v in filt.echelonized_basis()] + filtration[deg] = generators + return FilteredVectorSpace(filtration, base_ring=R, check=True) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 970c3da35ba..08dd7827c46 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -167,7 +167,7 @@ import sage.misc.latex as latex from sage.modules.module import Module -import sage.rings.finite_rings.constructor as finite_field +import sage.rings.finite_rings.finite_field_constructor as finite_field import sage.rings.integral_domain as integral_domain import sage.rings.ring as ring import sage.rings.integer_ring @@ -3038,8 +3038,7 @@ def _Hom_(self, Y, category): Returns a homspace whose morphisms have this vector space as domain. This is called by the general methods such as - :meth:`sage.structure.parent.Parent.Hom` and - :meth:`sage.structure.parent_base.ParentWithBase.Hom`. + :meth:`sage.structure.parent.Parent.Hom`. INPUT: diff --git a/src/sage/modules/free_module_integer.py b/src/sage/modules/free_module_integer.py index a57b3a09df7..d5dc47bc883 100644 --- a/src/sage/modules/free_module_integer.py +++ b/src/sage/modules/free_module_integer.py @@ -457,7 +457,7 @@ def BKZ(self, *args, **kwds): .. NOTE:: - If ``block_size == L.rank()`` where ``L`` is this latice, then + If ``block_size == L.rank()`` where ``L`` is this lattice, then this function performs Hermite-Korkine-Zolotareff (HKZ) reduction. """ basis = self.reduced_basis diff --git a/src/sage/modules/multi_filtered_vector_space.py b/src/sage/modules/multi_filtered_vector_space.py new file mode 100644 index 00000000000..650a7ce721f --- /dev/null +++ b/src/sage/modules/multi_filtered_vector_space.py @@ -0,0 +1,712 @@ +r""" +Multiple `\ZZ`-Graded Filtrations of a Single Vector Space + +See :mod:`filtered_vector_space` for simply graded vector spaces. This +module implements the analog but for a collection of filtrations of +the same vector space. + +The basic syntax to use it is a dictionary whose keys are some +arbitrary indexing set and values are +:func:`~sage.modules.filtered_vector_space.FilteredVectorSpace` :: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace({0:[(1,0)], 2:[(2,3)]}) + sage: V = MultiFilteredVectorSpace({'first':F1, 'second':F2}) + sage: V + Filtrations + first: QQ^2 >= QQ^2 >= 0 >= 0 + second: QQ^2 >= QQ^1 >= QQ^1 >= 0 + + sage: V.index_set() # random output + {'second', 'first'} + sage: sorted(V.index_set()) + ['first', 'second'] + + sage: V.get_filtration('first') + QQ^2 >= 0 + sage: V.get_degree('second', 1) + Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [ 1 3/2] +""" + +#***************************************************************************** +# Copyright (C) 2013 Volker Braun +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.rings.all import QQ, ZZ, RDF, RR, Integer +from sage.rings.infinity import InfinityRing, infinity, minus_infinity +from sage.categories.fields import Fields +from sage.modules.free_module import FreeModule_ambient_field, VectorSpace +from sage.matrix.constructor import vector, matrix, block_matrix, zero_matrix, identity_matrix +from sage.misc.all import uniq, cached_method, prod +from sage.modules.filtered_vector_space import FilteredVectorSpace + + +def MultiFilteredVectorSpace(arg, base_ring=None, check=True): + """ + Contstruct a multi-filtered vector space. + + INPUT: + + - ``arg`` -- either a non-empty dictionary of filtrations or an + integer. The latter is interpreted as the vector space + dimension, and the indexing set of the filtrations is empty. + + - ``base_ring`` -- a field (optional, default ``'None'``). The + base field of the vector space. Must be a field. If not + specified, the base field is derived from the filtrations. + + - ``check`` -- boolean (optional; default: ``True``). Whether + to perform consistency checks. + + EXAMPLES:: + + sage: MultiFilteredVectorSpace(3, QQ) + Unfiltered QQ^3 + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(2, 3) + sage: V = MultiFilteredVectorSpace({1:F1, 2:F2}); V + Filtrations + 1: QQ^2 >= 0 >= 0 >= 0 + 2: QQ^2 >= QQ^2 >= QQ^2 >= 0 + """ + if arg in ZZ: + dim = ZZ(arg) + filtration = {} + if base_ring is None: + base_ring = QQ + else: + filtration = dict(arg) + F = arg.values()[0] # the first filtration + dim = F.dimension() + if base_ring is None: + base_ring = F.base_ring() + for deg in filtration.keys(): + filt = filtration[deg] + if filt.base_ring() != base_ring: + filt = filt.change_ring(base_ring) + filtration[deg] = filt + return MultiFilteredVectorSpace_class(base_ring, dim, filtration) + + +class MultiFilteredVectorSpace_class(FreeModule_ambient_field): + + def __init__(self, base_ring, dim, filtrations, check=True): + """ + Python constructor. + + .. warning:: + + Use :func:`MultiFilteredVectorSpace` to construct + multi-filtered vector spaces. + + INPUT: + + - ``base_ring`` -- a ring. the base ring. + + - ``dim`` -- integer. The dimension of the ambient vector space. + + - ``filtrations`` -- a dictionary whose values are + filtrations. + + - ``check`` -- boolean (optional). Whether to perform + additional consistency checks. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(2, 3) + sage: V = MultiFilteredVectorSpace({1:F1, 2:F2}); V + Filtrations + 1: QQ^2 >= 0 >= 0 >= 0 + 2: QQ^2 >= QQ^2 >= QQ^2 >= 0 + """ + if check: + assert isinstance(dim, Integer) + assert base_ring in Fields() + assert all(base_ring == f.base_ring() for f in filtrations.values()) + assert all(dim == f.dimension() for f in filtrations.values()) + super(MultiFilteredVectorSpace_class, self).__init__(base_ring, dim) + self._filt = dict(filtrations) + + @cached_method + def index_set(self): + """ + Return the allowed indices for the different filtrations. + + OUTPUT: + + Set. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(2, 3) + sage: V = MultiFilteredVectorSpace({1:F1, 2:F2}) + sage: V.index_set() + {1, 2} + """ + from sage.sets.set import Set + return Set(self._filt.keys()) + + def change_ring(self, base_ring): + """ + Return the same multi-filtration over a different base ring. + + INPUT: + + - ``base_ring`` -- a ring. The new base ring. + + OUTPUT: + + This method returns a new multi-filtered vector space whose + subspaces are defined by the same generators but over a + different base ring. + + EXAMPLES:: + + sage: V = FilteredVectorSpace(2, 0) + sage: W = FilteredVectorSpace(2, 2) + sage: F = MultiFilteredVectorSpace({'a':V, 'b':W}); F + Filtrations + a: QQ^2 >= 0 >= 0 >= 0 + b: QQ^2 >= QQ^2 >= QQ^2 >= 0 + sage: F.change_ring(RDF) + Filtrations + a: RDF^2 >= 0 >= 0 >= 0 + b: RDF^2 >= RDF^2 >= RDF^2 >= 0 + + sage: MultiFilteredVectorSpace(3, base_ring=QQ).change_ring(RR) + Unfiltered RR^3 + """ + if len(self._filt) == 0: + return MultiFilteredVectorSpace(self.dimension(), base_ring=base_ring) + filtrations = dict() + for key, F in self._filt.iteritems(): + filtrations[key] = F.change_ring(base_ring) + return MultiFilteredVectorSpace(filtrations, base_ring=base_ring) + + def ambient_vector_space(self): + """ + Return the ambient (unfiltered) vector space. + + OUTPUT: + + A vector space. + + EXAMPLES:: + + sage: V = FilteredVectorSpace(2, 0) + sage: W = FilteredVectorSpace(2, 2) + sage: F = MultiFilteredVectorSpace({'a':V, 'b':W}) + sage: F.ambient_vector_space() + Vector space of dimension 2 over Rational Field + """ + return VectorSpace(self.base_ring(), self.dimension()) + + @cached_method + def is_constant(self): + """ + Return whether the multi-filtration is constant. + + OUTPUT: + + Boolean. Whether the each filtration is constant, see + :meth:`~sage.modules.filtered_vector_space.FilteredVectorSpace_class.is_constant`. + + EXAMPLES:: + + sage: V = FilteredVectorSpace(2, 0) + sage: W = FilteredVectorSpace(2, 2) + sage: F = MultiFilteredVectorSpace({'a':V, 'b':W}); F + Filtrations + a: QQ^2 >= 0 >= 0 >= 0 + b: QQ^2 >= QQ^2 >= QQ^2 >= 0 + sage: F.is_constant() + False + """ + return all(F.is_constant() for F in self._filt.values()) + + def is_exhaustive(self): + """ + Return whether the multi-filtration is exhaustive. + + A filtration $\{F_d\}$ in an ambient vector space $V$ is + exhaustive if $\cup F_d = V$. See also :meth:`is_separating`. + + OUTPUT: + + Boolean. Whether each filtration is constant, see + :meth:`~sage.modules.filtered_vector_space.FilteredVectorSpace_class.is_exhaustive`. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(2, 3) + sage: V = MultiFilteredVectorSpace({1:F1, 2:F2}) + sage: V.is_exhaustive() + True + """ + return all(F.is_exhaustive() for F in self._filt.values()) + + def is_separating(self): + """ + Return whether the multi-filtration is separating. + + A filtration $\{F_d\}$ in an ambient vector space $V$ is + exhaustive if $\cap F_d = 0$. See also :meth:`is_exhaustive`. + + OUTPUT: + + Boolean. Whether each filtration is separating, see + :meth:`~sage.modules.filtered_vector_space.FilteredVectorSpace_class.is_separating`. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(2, 3) + sage: V = MultiFilteredVectorSpace({1:F1, 2:F2}) + sage: V.is_separating() + True + """ + return all(F.is_separating() for F in self._filt.values()) + + @cached_method + def support(self): + """ + Return the degrees in which there are non-trivial generators. + + OUTPUT: + + A tuple of integers (and plus infinity) in ascending + order. The last entry is plus infinity if and only if the + flitration is not separating (see :meth:`is_separating`). + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(2, 3) + sage: V = MultiFilteredVectorSpace({1:F1, 2:F2}) + sage: V.support() + (1, 3) + """ + support = set() + for F in self._filt.values(): + support.update(F.support()) + return tuple(sorted(support)) + + @cached_method + def min_degree(self): + r""" + Return the lowest degree of the filtration. + + OUTPUT: + + Integer or plus infinity. The largest degree `d` of the + (descending) filtrations such that, for each individual + filtration, the filtered vector space `F_d` still equal to + `F_{-\infty}`. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(2, 3) + sage: V = MultiFilteredVectorSpace({1:F1, 2:F2}) + sage: V.min_degree() + 1 + """ + if len(self._filt) == 0: + return infinity + return min(F.min_degree() for F in self._filt.values()) + + @cached_method + def max_degree(self): + r""" + Return the highest degree of the filtration. + + OUTPUT: + + Integer or minus infinity. The smallest degree of the + filtrations such that the filtrations are constant to the + right. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(2, 3) + sage: V = MultiFilteredVectorSpace({1:F1, 2:F2}) + sage: V.max_degree() + 4 + """ + if len(self._filt) == 0: + return minus_infinity + return max(F.max_degree() for F in self._filt.values()) + + def get_filtration(self, key): + """ + Return the filtration indexed by ``key``. + + OUTPUT: + + A filtered vectior space. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(2, 3) + sage: V = MultiFilteredVectorSpace({1:F1, 2:F2}) + sage: V.get_filtration(2) + QQ^2 >= 0 + """ + return self._filt[key] + + def get_degree(self, key, deg): + r""" + Return one filtered vector space. + + INPUT: + + - ``key`` -- an element of the :meth:`index_set`. Specifies + which filtration. + + - ``d`` -- Integer. The desired degree of the filtration. + + OUTPUT: + + The vector space of degree ``deg`` in the filtration indexed + by ``key`` as subspace of the ambient space. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(2, 3) + sage: V = MultiFilteredVectorSpace({1:F1, 2:F2}) + sage: V.get_degree(2, 0) + Vector space of degree 2 and dimension 2 over Rational Field + Basis matrix: + [1 0] + [0 1] + """ + return self._filt[key].get_degree(deg) + + def graded(self, key, deg): + r""" + Return the associated graded vector space. + + INPUT: + + - ``key`` -- an element of the :meth:`index_set`. Specifies + which filtration. + + - ``d`` -- Integer. The desired degree of the filtration. + + OUTPUT: + + The quotient `G_d = F_d / F_{d+1}` of the filtration `F` + corresponding to ``key``. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(1, 3) + FilteredVectorSpace(1,0) + sage: V = MultiFilteredVectorSpace({1:F1, 2:F2}) + sage: V.graded(2, 3) + Vector space quotient V/W of dimension 1 over Rational Field where + V: Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [1 0] + W: Vector space of degree 2 and dimension 0 over Rational Field + Basis matrix: + [] + """ + return self.get_degree(key, deg).quotient(self.get_degree(key, deg + 1)) + + def _repr_(self): + r""" + Return as string representation of ``self``. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: rays = [(1,0), (1,1), (1,2), (-1,-1)] + sage: F1 = FilteredVectorSpace(rays, {0:[1, 2], 2:[3]}) + sage: F2 = FilteredVectorSpace(rays, {0:[1, 2], oo:[3]}) + sage: F3 = FilteredVectorSpace(rays, {oo:[3]}) + sage: F4 = FilteredVectorSpace(rays, {0:[3]}) + sage: MultiFilteredVectorSpace({'a':F1, 'b':F2, 'c': F3, 'd': F4}) + Filtrations + a: QQ^2 >= QQ^1 >= QQ^1 >= 0 + b: QQ^2 >= QQ^1 >= QQ^1 >= QQ^1 + c: QQ^1 >= QQ^1 >= QQ^1 >= QQ^1 + d: QQ^1 >= 0 >= 0 >= 0 + + sage: MultiFilteredVectorSpace(123, base_ring=RR) + Unfiltered RR^123 + """ + if len(self._filt) == 0: + F = FilteredVectorSpace(self.dimension(), base_ring=self.base_ring()) + return 'Unfiltered ' + repr(F) + rows = [] + support = self.support() + min_deg, max_deg = self.min_degree(), self.max_degree() + for key in sorted(self.index_set()): + F = self.get_filtration(key) + r = [str(key)] + F._repr_degrees(min_deg, max_deg-1) + rows.append(r) + from sage.misc.table import table + t = table(rows) + w = t._widths() + lines = ['Filtrations'] + for r in rows: + s = ' ' + s += r[0].rjust(w[0]) + ': ' + s += ' >= '.join(r[i].center(w[i]) for i in range(1, len(w))) + lines.append(s) + return '\n'.join(lines) + + def __cmp__(self, other): + """ + Compare two multi-filtered vector spaces. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(1, 3) + FilteredVectorSpace(1,0) + sage: V = MultiFilteredVectorSpace({1:F1, 2:F2}) + sage: V == MultiFilteredVectorSpace({2:F2, 1:F1}) + True + sage: V == MultiFilteredVectorSpace({'a':F1, 'b':F2}) + False + """ + return cmp(self._filt, other._filt) + + def direct_sum(self, other): + """ + Return the direct sum. + + INPUT: + + - ``other`` -- a multi-filtered vector space with the same + :meth:`index_set`. + + OUTPUT: + + The direct sum as a multi-filtered vector space. See + :meth:`~sage.modules.filtered_vector_space.FilteredVectorSpace_class.direct_sum`. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(1, 3) + FilteredVectorSpace(1,0) + sage: V = MultiFilteredVectorSpace({'a':F1, 'b':F2}) + sage: G1 = FilteredVectorSpace(1, 1) + sage: G2 = FilteredVectorSpace(1, 3) + sage: W = MultiFilteredVectorSpace({'a':G1, 'b':G2}) + sage: V.direct_sum(W) + Filtrations + a: QQ^3 >= QQ^3 >= 0 >= 0 >= 0 + b: QQ^3 >= QQ^2 >= QQ^2 >= QQ^2 >= 0 + sage: V + W # syntactic sugar + Filtrations + a: QQ^3 >= QQ^3 >= 0 >= 0 >= 0 + b: QQ^3 >= QQ^2 >= QQ^2 >= QQ^2 >= 0 + """ + if not self.index_set() == other.index_set(): + raise ValueError('the index sets of the two summands must be the same') + filtrations = dict() + for key in self.index_set(): + filtrations[key] = self._filt[key] + other._filt[key] + return MultiFilteredVectorSpace(filtrations) + + __add__ = direct_sum + + def tensor_product(self, other): + r""" + Return the graded tensor product. + + INPUT: + + - ``other`` -- a multi-filtered vector space with the same + :meth:`index_set`. + + OUTPUT: + + The tensor product of ``self`` and ``other`` as a + multi-filtered vector space. See + :meth:`~sage.modules.filtered_vector_space.FilteredVectorSpace_class.tensor_product`. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(1, 3) + FilteredVectorSpace(1,0) + sage: V = MultiFilteredVectorSpace({'a':F1, 'b':F2}) + sage: G1 = FilteredVectorSpace(1, 1) + sage: G2 = FilteredVectorSpace(1, 3) + sage: W = MultiFilteredVectorSpace({'a':G1, 'b':G2}) + sage: V.tensor_product(W) + Filtrations + a: QQ^2 >= 0 >= 0 >= 0 >= 0 >= 0 + b: QQ^2 >= QQ^2 >= QQ^1 >= QQ^1 >= QQ^1 >= 0 + sage: V * W # syntactic sugar + Filtrations + a: QQ^2 >= 0 >= 0 >= 0 >= 0 >= 0 + b: QQ^2 >= QQ^2 >= QQ^1 >= QQ^1 >= QQ^1 >= 0 + """ + if not self.index_set() == other.index_set(): + raise ValueError('the index sets of the two summands must be the same') + filtrations = dict() + for key in self.index_set(): + filtrations[key] = self._filt[key] * other._filt[key] + return MultiFilteredVectorSpace(filtrations) + + __mul__ = tensor_product + + def exterior_power(self, n): + """ + Return the `n`-th graded exterior power. + + INPUT: + + - ``n`` -- integer. Exterior product of how many copies of + ``self``. + + OUTPUT: + + The exterior power as a multi-filtered vector space. See + :meth:`~sage.modules.filtered_vector_space.FilteredVectorSpace_class.exterior_power`. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(1, 3) + FilteredVectorSpace(1,0) + sage: V = MultiFilteredVectorSpace({'a':F1, 'b':F2}) + sage: V.exterior_power(2) + Filtrations + a: QQ^1 >= 0 >= 0 + b: QQ^1 >= QQ^1 >= 0 + """ + filtrations = dict() + for key in self.index_set(): + filtrations[key] = self._filt[key].exterior_power(n) + return MultiFilteredVectorSpace(filtrations) + + wedge = exterior_power + + def symmetric_power(self, n): + """ + Return the `n`-th graded symmetric power. + + INPUT: + + - ``n`` -- integer. Symmetric product of how many copies of + ``self``. + + OUTPUT: + + The symmetric power as a multi-filtered vector space. See + :meth:`~sage.modules.filtered_vector_space.FilteredVectorSpace_class.symmetric_power`. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(1, 3) + FilteredVectorSpace(1,0) + sage: V = MultiFilteredVectorSpace({'a':F1, 'b':F2}) + sage: V.symmetric_power(2) + Filtrations + a: QQ^3 >= QQ^3 >= QQ^3 >= 0 >= 0 >= 0 >= 0 >= 0 + b: QQ^3 >= QQ^2 >= QQ^2 >= QQ^2 >= QQ^1 >= QQ^1 >= QQ^1 >= 0 + """ + filtrations = dict() + for key in self.index_set(): + filtrations[key] = self._filt[key].symmetric_power(n) + return MultiFilteredVectorSpace(filtrations) + + def dual(self): + """ + Return the dual. + + OUTPUT: + + The dual as a multi-filtered vector space. See + :meth:`~sage.modules.filtered_vector_space.FilteredVectorSpace_class.dual`. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(1, 3) + FilteredVectorSpace(1,0) + sage: V = MultiFilteredVectorSpace({'a':F1, 'b':F2}) + sage: V.dual() + Filtrations + a: QQ^2 >= QQ^2 >= QQ^2 >= 0 >= 0 + b: QQ^2 >= QQ^1 >= QQ^1 >= QQ^1 >= 0 + """ + filtrations = dict() + for key in self.index_set(): + filtrations[key] = self._filt[key].dual() + return MultiFilteredVectorSpace(filtrations) + + def shift(self, deg): + """ + Return a filtered vector space with degrees shifted by a constant. + + OUTPUT: + + The shift of ``self``. See + :meth:`~sage.modules.filtered_vector_space.FilteredVectorSpace_class.shift`. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(1, 3) + FilteredVectorSpace(1,0) + sage: V = MultiFilteredVectorSpace({'a':F1, 'b':F2}) + sage: V.support() + (0, 1, 3) + sage: V.shift(-5).support() + (-5, -4, -2) + """ + filtrations = dict() + for key in self.index_set(): + filtrations[key] = self._filt[key].shift(deg) + return MultiFilteredVectorSpace(filtrations) + + def random_deformation(self, epsilon=None): + """ + Return a random deformation + + INPUT: + + - ``epsilon`` -- a number in the base ring. + + OUTPUT: + + A new multi-filtered vector space where the generating vectors + of subspaces are moved by ``epsilon`` times a random vector. + + EXAMPLES:: + + sage: F1 = FilteredVectorSpace(2, 1) + sage: F2 = FilteredVectorSpace(1, 3) + FilteredVectorSpace(1,0) + sage: V = MultiFilteredVectorSpace({'a':F1, 'b':F2}) + sage: V.get_degree('b',1) + Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [1 0] + sage: V.random_deformation(1/100).get_degree('b',1) + Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [ 1 8/1197] + """ + filtrations = dict() + for key in self.index_set(): + filtrations[key] = self._filt[key].random_deformation(epsilon) + return MultiFilteredVectorSpace(filtrations) diff --git a/src/sage/modules/tensor_operations.py b/src/sage/modules/tensor_operations.py new file mode 100644 index 00000000000..7e6c5e73ede --- /dev/null +++ b/src/sage/modules/tensor_operations.py @@ -0,0 +1,582 @@ +""" +Helper Classes to implement Tensor Operations + +.. warning:: + + This module is not meant to be used directly. It just provides + functionality for other classes to implement tensor operations. + +The :class:`VectorCollection` constructs the basis of tensor products +(and symmetric/exterior powers) in terms of a chosen collection of +vectors that generate the vector space(s). + +EXAMPLES:: + + sage: from sage.modules.tensor_operations import VectorCollection, TensorOperation + sage: V = VectorCollection([(1,0), (-1, 0), (1,2)], QQ, 2) + sage: W = VectorCollection([(1,1), (1,-1), (-1, 1)], QQ, 2) + sage: VW = TensorOperation([V, W], operation='product') + +Here is the tensor product of two vectors:: + + sage: V.vectors()[0] + (1, 0) + sage: W.vectors()[1] + (1, -1) + +In a convenient choice of basis, the tensor product is +$(a,b)\otimes(c,d)=(ac,ad,bc,bd)$. In this example, it is one of the +vectors of the vector collection ``VW`` :: + + sage: VW.index_map(0, 1) + 1 + sage: VW.vectors()[VW.index_map(0, 1)] + (1, -1, 0, 0) + + sage: rows = [] + sage: for i, j in cartesian_product((range(3), range(3))): + ....: v = V.vectors()[i] + ....: w = W.vectors()[j] + ....: i_tensor_j = VW.index_map(i, j) + ....: vw = VW.vectors()[i_tensor_j] + ....: rows.append([i, v, j, w, i_tensor_j, vw]) + sage: table(rows) + 0 (1, 0) 0 (1, 1) 0 (1, 1, 0, 0) + 0 (1, 0) 1 (1, -1) 1 (1, -1, 0, 0) + 0 (1, 0) 2 (-1, 1) 2 (-1, 1, 0, 0) + 1 (-1, 0) 0 (1, 1) 3 (-1, -1, 0, 0) + 1 (-1, 0) 1 (1, -1) 2 (-1, 1, 0, 0) + 1 (-1, 0) 2 (-1, 1) 1 (1, -1, 0, 0) + 2 (1, 2) 0 (1, 1) 4 (1, 1, 2, 2) + 2 (1, 2) 1 (1, -1) 5 (1, -1, 2, -2) + 2 (1, 2) 2 (-1, 1) 6 (-1, 1, -2, 2) +""" + +#***************************************************************************** +# Copyright (C) 2013 Volker Braun +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.structure.sage_object import SageObject +from sage.modules.free_module import FreeModule_ambient_field, VectorSpace +from sage.misc.all import cached_method, prod +from sage.matrix.constructor import vector, matrix +from sage.rings.all import ZZ + + + +def symmetrized_coordinate_sums(dim, n): + """ + Return formal symmetrized sum of multi-indices + + INPUT: + + - ``dim`` -- integer. The dimension (range of each index). + + - ``n`` -- integer. The total number of indices. + + OUTPUT: + + A symmetrized formal sum of multi-indices (tuples of integers) + + EXAMPLES:: + + sage: from sage.modules.tensor_operations import symmetrized_coordinate_sums + sage: symmetrized_coordinate_sums(2, 2) + ((0, 1) + (1, 0), (0, 0), (1, 1)) + """ + from sage.structure.formal_sum import FormalSum + coordinates = [range(dim) for i in range(n)] + table = dict() + from sage.categories.cartesian_product import cartesian_product + for i in cartesian_product(coordinates): + sort_i = tuple(sorted(i)) + x = table.get(sort_i, []) + x.append([+1, tuple(i)]) + table[sort_i] = x + return tuple(FormalSum(x) for x in table.values()) + + +def antisymmetrized_coordinate_sums(dim, n): + """ + Return formal anti-symmetrized sum of multi-indices + + INPUT: + + - ``dim`` -- integer. The dimension (range of each index). + + - ``n`` -- integer. The total number of indices. + + OUTPUT: + + An anti-symmetrized formal sum of multi-indices (tuples of integers) + + EXAMPLES:: + + sage: from sage.modules.tensor_operations import antisymmetrized_coordinate_sums + sage: antisymmetrized_coordinate_sums(3, 2) + ((0, 1) - (1, 0), (0, 2) - (2, 0), (1, 2) - (2, 1)) + """ + from sage.structure.formal_sum import FormalSum + table = [] + from sage.groups.perm_gps.permgroup_named import SymmetricGroup + S_d = SymmetricGroup(n) + from sage.combinat.combination import Combinations + for i in Combinations(range(dim), n): + i = tuple(i) + x = [] + for g in S_d: + x.append([g.sign(), g(i)]) + x = FormalSum(x) + table.append(x) + return tuple(table) + + +class VectorCollection(FreeModule_ambient_field): + """ + An ordered collection of generators of a vector space. + + This is like a list of vectors, but with extra argument checking. + + .. warning:: + + This class is only used as a base class for filtered vector + spaces. You should not use it yourself. + + INPUT: + + - ``dim`` -- integer. The dimension of the ambient vector space. + + - ``base_ring`` -- a field. The base field of the ambient vector space. + + - ``rays`` -- any list/iterable of things than can be converted + into vectors of the ambient vector space. These will be used to + span the subspaces of the filtration. Must span the ambient + vector space. + + EXAMPLES:: + + sage: from sage.modules.tensor_operations import VectorCollection + sage: R = VectorCollection([(1,0), (0,1), (1,2)], QQ, 2); R + Vector space of dimension 2 over Rational Field + + TESTS:: + + sage: R.vectors() + ((1, 0), (0, 1), (1, 2)) + sage: r = R._vectors[0] + sage: type(r) + + sage: r.parent() is R + True + sage: r.is_immutable() + True + """ + def __init__(self, vector_collection, base_ring, dim): + """ + EXAMPLES:: + + sage: from sage.modules.tensor_operations import VectorCollection + sage: VectorCollection([(1,0), (4,1), (1,2)], QQ, 2) + Vector space of dimension 2 over Rational Field + """ + super(VectorCollection, self).__init__(base_ring, dim) + self._n_vectors = len(vector_collection) + self._vectors = tuple(self(r) for r in vector_collection) + for r in self._vectors: + r.set_immutable() + if matrix(base_ring, self._vectors).rank() != self.degree(): + raise ValueError('the vectors must span the ambient vector space') + self._all_indices = tuple(map(ZZ, range(0, self._n_vectors))) + + def vectors(self): + """ + Return the collection of vectors + + OUTPUT: + + A tuple of vectors. The vectors that were specified in the + constructor, in the same order. + + EXAMPLES:: + + sage: from sage.modules.tensor_operations import VectorCollection + sage: V = VectorCollection([(1,0), (0,1), (1,2)], QQ, 2) + sage: V.vectors() + ((1, 0), (0, 1), (1, 2)) + """ + return self._vectors + + def n_vectors(self): + """ + Return the number of vectors + + OUTPUT: + + Integer. + + EXAMPLES:: + + sage: from sage.modules.tensor_operations import VectorCollection + sage: V = VectorCollection([(1,0), (0,1), (1,2)], QQ, 2) + sage: V.n_vectors() + 3 + """ + return len(self._vectors) + + +class TensorOperation(VectorCollection): + """ + Auxiliary class to compute the tensor product of two + :class:`VectorCollection` objects. + + .. warning:: + + This class is only used as a base class for filtered vector + spaces. You should not use it yourself. + + INPUT: + + - ``vector_collections`` -- a nonempty list/tuple/iterable of + :class:`VectorCollection` objects. + + - ``operation`` -- string. The tensor operation. Currently allowed + values are ``product``, ``symmetric``, and ``antisymmetric``. + + .. todo:: + + More general tensor operations (specified by Young tableaux) + should be implemented. + + EXAMPLES:: + + sage: from sage.modules.tensor_operations import VectorCollection, TensorOperation + sage: R = VectorCollection([(1,0), (1,2), (-1,-2)], QQ, 2) + sage: S = VectorCollection([(1,), (-1,)], QQ, 1) + sage: R_tensor_S = TensorOperation([R, S]) + sage: R_tensor_S.index_map(0, 0) + 0 + sage: matrix(ZZ, 3, 2, lambda i,j: R_tensor_S.index_map(i, j)) + [0 1] + [2 3] + [3 2] + sage: R_tensor_S.vectors() + ((1, 0), (-1, 0), (1, 2), (-1, -2)) + """ + def __init__(self, vector_collections, operation='product'): + """ + EXAMPLES:: + + sage: from sage.modules.tensor_operations import VectorCollection, TensorOperation + sage: R = VectorCollection([(1,0), (5,2), (-1,-2)], QQ, 2) + sage: S = VectorCollection([(1,), (-1,)], QQ, 1) + sage: TensorOperation([S, R]) + Vector space of dimension 2 over Rational Field + """ + assert all(isinstance(V, VectorCollection) for V in vector_collections) + self._base_ring = base_ring = vector_collections[0].base_ring() + assert all(V.base_ring() is base_ring for V in vector_collections) + self._V = tuple(vector_collections) + self._vectors = [] + self._index_map = dict() + if operation == 'product': + self._init_product() + elif operation == 'symmetric': + assert all(V is self._V[0] for V in self._V) + self._init_symmetric() + elif operation == 'antisymmetric': + assert all(V is self._V[0] for V in self._V) + self._init_antisymmetric() + else: + raise ValueError('invalid operation') + vectors = self._vectors + dim = 0 if len(vectors) == 0 else len(vectors[0]) + del self._vectors + del self._base_ring + super(TensorOperation, self).__init__(vectors, base_ring, dim) + + def _init_product_vectors(self, i): + r""" + Helper to build up ``self._vectors`` incrementally during the + constructor. + + INPUT: + + - `i` -- list/tuple of integers. Multi-index of length equal + to the number of constituent vector collections. The $j$-th + entry $i[j]$ indexes a ray in the $j$-th vector + collection. Hence, $i$ specifies one element in each vector + collection. + + OUTPUT: + + This method mutates the :class:`TensorOperation` instance. In + particular, the tensor product of the vectors of the vector + collection is computed, and added to the elements of the + tensor operation if it has not been encountered before. + + The index of this tensor product vector is returned as an + integer. + + .. NOTE:: + + In a convenient choice of coordinates the tensor product + of, say, two vectors $(a,b)$ and $(c,d)$, is $(ac, ad, bc, + bd)$. + + EXAMPLES:: + + sage: from sage.modules.tensor_operations import \ + ....: VectorCollection, TensorOperation + sage: R = VectorCollection([(1,0), (1,2), (-1,-2)], QQ, 2) + sage: S = VectorCollection([(1,), (-1,)], QQ, 1) + sage: R_tensor_S = TensorOperation([R,S]) + sage: R_tensor_S.index_map(1, 1) + 3 + sage: R_tensor_S.index_map(2, 0) + 3 + sage: R_tensor_S.vectors() # indirect doctest + ((1, 0), (-1, 0), (1, 2), (-1, -2)) + """ + # Pick out the i[j]-th vector + rays = [list(self._V[j].vectors()[k]) for j, k in enumerate(i)] + v = [] + # Note: convert to list, as cartesian_product of vectors is unrelated + from sage.categories.cartesian_product import cartesian_product + for r in cartesian_product(map(list, rays)): + v.append(prod(r)) # build up the tensor product + v = tuple(v) + # Use index of pre-existing tensor product vector if there is one + try: + result = self._vectors.index(v) + except ValueError: + self._vectors.append(v) + result = len(self._vectors) - 1 + return result + + def _init_power_operation_vectors(self, i, linear_combinations): + """ + Helper to build up ``self._vectors`` incrementally during the constructor. + + INPUT: + + - `i` -- list/tuple of integers. Specifies one element + (vector) in each vector collection as in + :meth:`_init_product_vector`. + + - ``linear_combination`` -- formal linear combination of + vector indices in the vectors specified by $i$. + + EXAMPLES:: + + sage: from sage.modules.tensor_operations import \ + ....: VectorCollection, TensorOperation + sage: R = VectorCollection([(1,0), (1,2), (-1,-2)], QQ, 2) + sage: Sym2_R = TensorOperation([R,R], operation='symmetric') + sage: Sym2_R.vectors() # indirect doctest + ((0, 1, 0), (2, 1, 0), (-2, -1, 0), (4, 1, 4), (-4, -1, -4)) + sage: Alt2_R = TensorOperation([R, R], operation='antisymmetric') + sage: Alt2_R.vectors() # indirect doctest + ((2), (-2)) + """ + rays = [self._V[j].vectors()[k] for j, k in enumerate(i)] + v = [] + for coordinate_linear_combination in linear_combinations: + v_entry = self._base_ring.zero() + for coeff, index in coordinate_linear_combination: + v_entry += coeff * prod(rays[j][k] for j, k in enumerate(index)) + v.append(v_entry) + v = tuple(v) + if all(vi == 0 for vi in v): + return None + try: + result = self._vectors.index(v) + except ValueError: + self._vectors.append(v) + result = len(self._vectors) - 1 + return result + + def _init_product(self): + """ + Initialization for the tensor product + + EXAMPLES:: + + sage: from sage.modules.tensor_operations import \ + ....: VectorCollection, TensorOperation + sage: R = VectorCollection([(1,0), (1,2), (-1,-2)], QQ, 2) + sage: S = VectorCollection([(1,), (-1,)], QQ, 1) + sage: R_tensor_S = TensorOperation([R,S], operation='product') + sage: sorted(R_tensor_S._index_map.iteritems()) # indirect doctest + [((0, 0), 0), ((0, 1), 1), ((1, 0), 2), ((1, 1), 3), ((2, 0), 3), ((2, 1), 2)] + """ + V_list_indices = [range(V.n_vectors()) for V in self._V] + from sage.categories.cartesian_product import cartesian_product + for i in cartesian_product(V_list_indices): + self._index_map[tuple(i)] = self._init_product_vectors(i) + self._symmetrize_indices = False + + def _init_symmetric(self): + """ + Initialization for the symmetric product. + + EXAMPLES:: + + sage: from sage.modules.tensor_operations import \ + ....: VectorCollection, TensorOperation + sage: R = VectorCollection([(1,0), (1,2), (-1,-2)], QQ, 2) + sage: Sym2_R = TensorOperation([R,R], operation='symmetric') # indirect doctest + sage: sorted(Sym2_R._index_map.iteritems()) + [((0, 0), 0), ((0, 1), 1), ((0, 2), 2), ((1, 1), 3), ((1, 2), 4), ((2, 2), 3)] + """ + V_list_indices = [range(V.n_vectors()) for V in self._V] + Sym = symmetrized_coordinate_sums(self._V[0].dimension(), len(self._V)) + from sage.categories.cartesian_product import cartesian_product + N = len(V_list_indices) + for i in cartesian_product(V_list_indices): + if any(i[j - 1] > i[j] for j in range(1, N)): + continue + self._index_map[tuple(i)] = self._init_power_operation_vectors(i, Sym) + self._symmetrize_indices = True + + def _init_antisymmetric(self): + """ + Initialization for the antisymmetric product + + EXAMPLES:: + + sage: from sage.modules.tensor_operations import \ + ....: VectorCollection, TensorOperation + sage: R = VectorCollection([(1,0), (1,2), (-1,-2)], QQ, 2) + sage: Alt2_R = TensorOperation([R, R], operation='antisymmetric') # indirect doctest + sage: sorted(Alt2_R._index_map.iteritems()) + [((0, 1), 0), ((0, 2), 1)] + """ + n = len(self._V) + dim = self._V[0].degree() + Alt = antisymmetrized_coordinate_sums(dim, n) + from sage.combinat.combination import Combinations + for i in Combinations(range(self._V[0].n_vectors()), n): + ray = self._init_power_operation_vectors(i, Alt) + if ray is not None: + self._index_map[tuple(i)] = ray + self._symmetrize_indices = True + + def index_map(self, *i): + """ + Return the result of the tensor operation. + + INPUT: + + - ``*i`` -- list of integers. The indices (in the + corresponding factor of the tensor operation) of the domain + vector. + + OUTPUT: + + The index (in :meth:`vectors`) of the image of the tensor + product/operation acting on the domain vectors indexed by `i`. + + ``None`` is returned if the tensor operation maps the + generators to zero (usually because of antisymmetry). + + EXAMPLES:: + + sage: from sage.modules.tensor_operations import \ + ....: VectorCollection, TensorOperation + sage: R = VectorCollection([(1,0), (1,2), (-1,-2)], QQ, 2) + sage: Sym3_R = TensorOperation([R]*3, 'symmetric') + + The symmetric product of the first vector ``(1,0)``, the + second vector ``(1,2)``, and the third vector ``(-1,-2)`` + equals the vector with index number 4 (that is, the fifth) in + the symmetric product vector collection:: + + sage: Sym3_R.index_map(0, 1, 2) + 4 + + In suitable coordinates, this is the vector:: + + sage: Sym3_R.vectors()[4] + (-4, 0, -1, -4) + + The product is symmetric:: + + sage: Sym3_R.index_map(2, 0, 1) + 4 + sage: Sym3_R.index_map(2, 1, 0) + 4 + + As another example, here is the rank-2 determinant:: + + sage: from sage.modules.tensor_operations import \ + ....: VectorCollection, TensorOperation + sage: R = VectorCollection([(1,0), (0,1), (-2,-3)], QQ, 2) + sage: detR = TensorOperation([R]*2, 'antisymmetric') + sage: detR.index_map(1, 0) + 0 + sage: detR.index_map(0, 1) + 0 + + TESTS:: + + sage: sorted(detR._index_map.iteritems()) + [((0, 1), 0), ((0, 2), 1), ((1, 2), 2)] + sage: detR.vectors() + ((1), (-3), (2)) + """ + if len(i) == 1 and isinstance(i[0], (list, tuple)): + i = tuple(i[0]) + if self._symmetrize_indices: + i = tuple(sorted(i)) + try: + return self._index_map[i] + except KeyError: + return None + + def preimage(self): + """ + A choice of pre-image multi-indices. + + OUTPUT: + + A list of multi-indices (tuples of integers) whose image is + the entire image under the :meth:`index_map`. + + EXAMPLES:: + + sage: from sage.modules.tensor_operations import \ + ....: VectorCollection, TensorOperation + sage: R = VectorCollection([(1,0), (0,1), (-2,-3)], QQ, 2) + sage: detR = TensorOperation([R]*2, 'antisymmetric') + sage: sorted(detR.preimage()) + [(0, 1), (0, 2), (1, 2)] + sage: sorted(detR.codomain()) + [0, 1, 2] + """ + return self._index_map.keys() + + def codomain(self): + """ + The codomain of the index map. + + OUTPUT: + + A list of integers. The image of :meth:`index_map`. + + EXAMPLES:: + + sage: from sage.modules.tensor_operations import \ + ....: VectorCollection, TensorOperation + sage: R = VectorCollection([(1,0), (0,1), (-2,-3)], QQ, 2) + sage: detR = TensorOperation([R]*2, 'antisymmetric') + sage: sorted(detR.preimage()) + [(0, 1), (0, 2), (1, 2)] + sage: sorted(detR.codomain()) + [0, 1, 2] + """ + return self._index_map.values() diff --git a/src/sage/plot/plot3d/shapes2.py b/src/sage/plot/plot3d/shapes2.py index eb4d3edab00..ff6af59d5ec 100644 --- a/src/sage/plot/plot3d/shapes2.py +++ b/src/sage/plot/plot3d/shapes2.py @@ -799,16 +799,22 @@ class Line(PrimitiveObject): INPUT: - - ``points`` -- list of points to pass through + - ``points`` -- list of points to pass through - - ``thickness`` -- diameter of the line + - ``thickness`` -- (optional, default 5) diameter of the line - - ``corner_cutoff`` -- threshold for smoothing (see - the corners() method) this is the minimum cosine between adjacent - segments to smooth + - ``corner_cutoff`` -- (optional, default 0.5) threshold for + smoothing (see :meth:`corners`). - - ``arrow_head`` -- if True make this curve into an - arrow + - ``arrow_head`` -- (optional, default ``False``) if ``True`` make + this curve into an arrow + + The parameter ``corner_cutoff`` is a bound for the cosine of the + angle made by two successive segments. This angle is close to `0` + (and the cosine close to 1) if the two successive segments are + almost aligned and close to `\pi` (and the cosine close to -1) if + the path has a strong peak. If the cosine is smaller than the + bound (which means a sharper peak) then no smoothing is done. EXAMPLES:: @@ -820,8 +826,19 @@ class Line(PrimitiveObject): sage: Line([(0,0,0),(1,0,0),(2,1,0),(0,1,0)], corner_cutoff=0) Graphics3d Object + + Make sure that the ``corner_cutoff`` keyword works (:trac:`3859`):: + + sage: N = 11 + sage: c = 0.4 + sage: sum([Line([(i,1,0), (i,0,0), (i,cos(2*pi*i/N), sin(2*pi*i/N))], + ....: corner_cutoff=c, + ....: color='red' if -cos(2*pi*i/N)<=c else 'blue') + ....: for i in range(N+1)]) + Graphics3d Object """ - def __init__(self, points, thickness=5, corner_cutoff=.5, arrow_head=False, **kwds): + def __init__(self, points, thickness=5, corner_cutoff=0.5, + arrow_head=False, **kwds): """ Create the graphics primitive :class:`Line` in 3-D. @@ -855,7 +872,8 @@ def bounding_box(self): sage: from sage.plot.plot3d.shapes2 import Line sage: L = Line([(i,i^2-1,-2*ln(i)) for i in [10,20,30]]) sage: L.bounding_box() - ((10.0, 99.0, -6.802394763324311), (30.0, 899.0, -4.605170185988092)) + ((10.0, 99.0, -6.802394763324311), + (30.0, 899.0, -4.605170185988092)) """ try: return self.__bounding_box @@ -863,7 +881,6 @@ def bounding_box(self): self.__bounding_box = point_list_bounding_box(self.points) return self.__bounding_box - def tachyon_repr(self, render_params): """ Return representation of the line suitable for plotting @@ -927,7 +944,7 @@ def jmol_repr(self, render_params): 'draw line_1 diameter 1 curve {1.0 0.0 0.0}' """ T = render_params.transform - corners = self.corners(max_len=255) # hardcoded limit in jmol + corners = self.corners(max_len=255) # hardcoded limit in jmol last_corner = corners[-1] corners = set(corners) cmds = [] @@ -954,8 +971,23 @@ def corners(self, corner_cutoff=None, max_len=None): INPUT: - Maximum cosine of angle between adjacent line segments before - adding a corner + - ``corner_cutoff`` -- (optional, default ``None``) If the + cosine of the angle between adjacent line segments is smaller than + this bound, then there will be a sharp corner in the path. + Otherwise, the path is smoothed. If ``None``, + then the default value 0.5 is used. + + - ``max_len`` -- (optional, default ``None``) Maximum number + of points allowed in a single path. If this is set, this + creates corners at smooth points in order to break the path + into smaller pieces. + + The parameter ``corner_cutoff`` is a bound for the cosine of the + angle made by two successive segments. This angle is close to `0` + (and the cosine close to 1) if the two successive segments are + almost aligned and close to `\pi` (and the cosine close to -1) if + the path has a strong peak. If the cosine is smaller than the + bound (which means a sharper peak) then there must be a corner. OUTPUT: @@ -964,39 +996,40 @@ def corners(self, corner_cutoff=None, max_len=None): EXAMPLES: - Every point:: + No corners, always smooth:: sage: from sage.plot.plot3d.shapes2 import Line - sage: Line([(0,0,0),(1,0,0),(2,1,0),(0,1,0)], corner_cutoff=1).corners() - [(0, 0, 0), (1, 0, 0), (2, 1, 0)] + sage: Line([(0,0,0),(1,0,0),(2,1,0),(0,1,0)], corner_cutoff=-1).corners() + [(0, 0, 0)] - Greater than 90 degrees:: + Smooth if the angle is greater than 90 degrees:: sage: Line([(0,0,0),(1,0,0),(2,1,0),(0,1,0)], corner_cutoff=0).corners() [(0, 0, 0), (2, 1, 0)] - No corners:: + Every point (corners everywhere):: - sage: Line([(0,0,0),(1,0,0),(2,1,0),(0,1,0)], corner_cutoff=-1).corners() - (0, 0, 0) - - An intermediate value:: - - sage: Line([(0,0,0),(1,0,0),(2,1,0),(0,1,0)], corner_cutoff=.5).corners() - [(0, 0, 0), (2, 1, 0)] + sage: Line([(0,0,0),(1,0,0),(2,1,0),(0,1,0)], corner_cutoff=1).corners() + [(0, 0, 0), (1, 0, 0), (2, 1, 0)] """ if corner_cutoff is None: corner_cutoff = self.corner_cutoff + if corner_cutoff >= 1: - if max_len: - self.points[:-1][::max_len-1] - else: - return self.points[:-1] + # corners everywhere + return self.points[:-1] + elif corner_cutoff <= -1: - return self.points[0] + # no corners + if not(max_len is None): + # forced by the maximal number of consecutive smooth points + return self.points[:-1][::max_len - 1] + else: + return [self.points[0]] + else: - if not max_len: - max_len = len(self.points)+1 + if max_len is None: + max_len = len(self.points) + 1 count = 2 # ... -- prev -- cur -- next -- ... cur = self.points[0] @@ -1009,7 +1042,7 @@ def corners(self, corner_cutoff=None, max_len=None): def dot(x0_y0_z0, x1_y1_z1): (x0, y0, z0) = x0_y0_z0 (x1, y1, z1) = x1_y1_z1 - return x0*x1 + y0*y1 + z0*z1 + return x0 * x1 + y0 * y1 + z0 * z1 for next in self.points[2:]: if next == cur: @@ -1018,8 +1051,10 @@ def dot(x0_y0_z0, x1_y1_z1): count = 1 continue next_dir = [next[i] - cur[i] for i in range(3)] - cos_angle = dot(prev_dir, next_dir) / math.sqrt(dot(prev_dir, prev_dir) * dot(next_dir, next_dir)) - if cos_angle <= corner_cutoff or count > max_len-1: + cos_angle = (dot(prev_dir, next_dir) / + math.sqrt(dot(prev_dir, prev_dir) * + dot(next_dir, next_dir))) + if cos_angle <= corner_cutoff or count > max_len - 1: corners.append(cur) count = 1 cur, prev_dir = next, next_dir diff --git a/src/sage/quadratic_forms/genera/genus.py b/src/sage/quadratic_forms/genera/genus.py index 19cb036af87..b9ef30a0fab 100644 --- a/src/sage/quadratic_forms/genera/genus.py +++ b/src/sage/quadratic_forms/genera/genus.py @@ -15,7 +15,7 @@ from sage.rings.integer_ring import IntegerRing from sage.rings.rational_field import RationalField from sage.rings.integer import Integer -from sage.rings.finite_rings.constructor import FiniteField +from sage.rings.finite_rings.finite_field_constructor import FiniteField def Genus(A): diff --git a/src/sage/repl/interface_magic.py b/src/sage/repl/interface_magic.py new file mode 100644 index 00000000000..a2b99883152 --- /dev/null +++ b/src/sage/repl/interface_magic.py @@ -0,0 +1,299 @@ +""" +Magics for each of the Sage interfaces + +This module defines magic functions for interpreters. As an example, +consider the GAP interpreter which can evaluate a gap command given as +a string:: + + sage: gap('SymmetricGroup(4)') # not tested + SymmetricGroup( [ 1 .. 4 ] ) + +Magics are syntactic sugar to avoid writing the Python string. They +are either called as line magics:: + + sage: %gap SymmetricGroup(4) # not tested + +or as cell magics, that is, spanning multiple lines:: + + sage: %%gap # not tested + ....: G := SymmetricGroup(4); + ....: Display(G); + +Note that the cell magic needs semicolons, this is required by the GAP +language to separate multiple commands. +""" +# Note: no magics in doctests, hence # not tested + + +#***************************************************************************** +# Copyright (C) 2016 Volker Braun +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + + +from sage.repl.rich_output.display_manager import get_display_manager + + +LINE_DOCSTRING = """ +Interact with {name} + +The line magic %{name} sends a single line to the {name} interface. + +INPUT: + +Single {name} command + +OUTPUT: + +The result of the evaluated {name} command as an interface wrapper +object. + +EXAMPLES:: + + sage: %{name} 1 + 2 + 3 # not tested +""" + + +CELL_DOCSTRING = """ +Interact with {name} + +The cell magic %%{name} sends multiple lines to the {name} interface. + +INPUT: + +Multiple lines of valid {name}-commands + +OUTPUT: + +The result of the evaluated {name}-commands is printed. + +EXAMPLES:: + + sage: %%{name} # not tested + ....: 1 + 2 + 3; + ....: some_{name}_command(); +""" + + + +class InterfaceMagic(object): + + @classmethod + def all_iter(cls): + """ + Iterate over the available interfaces + + EXAMPLES:: + + sage: from sage.repl.interface_magic import InterfaceMagic + sage: next(InterfaceMagic.all_iter()) + + """ + import sage.interfaces.all + for name, obj in sage.interfaces.all.__dict__.items(): + if isinstance(obj, sage.interfaces.interface.Interface): + yield cls(name, obj) + + @classmethod + def register_all(cls, shell=None): + """ + Register all available interfaces + + EXAMPLES:: + + sage: class MockShell(): + ....: magics = set() + ....: def register_magic_function(self, fn, magic_name, magic_kind): + ....: self.magics.add(magic_name) + ....: print(magic_name, magic_kind) + sage: from sage.repl.interface_magic import InterfaceMagic + sage: InterfaceMagic.register_all(MockShell()) # random output + ('gp', 'line') + ('gp', 'cell') + ('mwrank', 'line') + ('mwrank', 'cell') + ... + ('maxima', 'line') + ('maxima', 'cell') + sage: 'gap' in MockShell.magics + True + sage: 'maxima' in MockShell.magics + True + """ + if shell is None: + shell = get_ipython() + for interface in cls.all_iter(): + shell.register_magic_function( + interface.line_magic_factory(), + magic_name=interface._name, + magic_kind='line' + ) + shell.register_magic_function( + interface.cell_magic_factory(), + magic_name=interface._name, + magic_kind='cell' + ) + + @classmethod + def find(cls, name): + """ + Find a particular magic by name + + This method is for doctesting purposes only. + + INPUT: + + - ``name`` -- string. The name of the interface magic to + search for. + + OUTPUT: + + The corresponding :class:`InterfaceMagic` instance. + + EXAMPLES:: + + sage: from sage.repl.interface_magic import InterfaceMagic + sage: InterfaceMagic.find('gap') + + """ + for magic in cls.all_iter(): + if magic._name == name: + return magic + + def __init__(self, name, interface): + """ + Interface Magic + + This class is a wrapper around interface objects to provide + them with magics. + + INPUT: + + - ``name`` -- string. The interface name + + - ``interface`` -- :class:`sage.interfaces.expect.Expect`. The + interface to wrap. + + EXAMPLES:: + + sage: from sage.repl.interface_magic import InterfaceMagic + sage: InterfaceMagic.find('gap') + + """ + self._name = name + self._interface = interface + + def line_magic_factory(self): + """ + Factory for line magic + + OUTPUT: + + A function suitable to be used as line magic. + + EXAMPLES:: + + sage: from sage.repl.interface_magic import InterfaceMagic + sage: line_magic = InterfaceMagic.find('gap').line_magic_factory() + sage: output = line_magic('1+1') + sage: output + 2 + sage: type(output) + + + This is how the built line magic is used in practice:: + + sage: from sage.repl.interpreter import get_test_shell + sage: shell = get_test_shell() + sage: shell.run_cell('%gap 1+1') + 2 + sage: shell.run_cell('%gap?') + Docstring: + Interact with gap + + The line magic %gap sends a single line to the gap interface. + ... + """ + terminal = get_display_manager().is_in_terminal() + def line_magic(line): + if line: + return self._interface(line) + else: + if terminal: + self._interface.interact() + else: + raise SyntaxError('{0} command required'.format(self._name)) + line_magic.__doc__ = LINE_DOCSTRING.format(name=self._name) + return line_magic + + def cell_magic_factory(self): + r""" + Factory for cell magic + + OUTPUT: + + A function suitable to be used as cell magic. + + EXAMPLES:: + + sage: from sage.repl.interface_magic import InterfaceMagic + sage: cell_magic = InterfaceMagic.find('gap').cell_magic_factory() + sage: output = cell_magic('', '1+1;') + 2 + sage: output is None + True + sage: cell_magic('foo', '1+1;') + Traceback (most recent call last): + ... + SyntaxError: Interface magics have no options, got "foo" + + This is how the built cell magic is used in practice:: + + sage: from sage.repl.interpreter import get_test_shell + sage: shell = get_test_shell() + sage: shell.run_cell('%%gap\nG:=SymmetricGroup(5);\n1+1;Order(G);') + Sym( [ 1 .. 5 ] ) + 2 + 120 + sage: shell.run_cell('%%gap foo\n1+1;\n') + File "", line unknown + SyntaxError: Interface magics have no options, got "foo" + + sage: shell.run_cell('%%gap?') + Docstring: + Interact with gap + + The cell magic %%gap sends multiple lines to the gap interface. + ... + """ + def cell_magic(line, cell): + """ + Evaluate cell magic + + Docstring is overwritten in the instance + + INPUT: + + - ``line`` -- string. The option part of the cell magic. + + - ``cell`` -- string. The lines of the cell magic. + + OUTPUT: + + Prints the interface output. + + RAISES: + + ``SyntaxError`` if a line is specified; Interfaces have no + options. + """ + if line: + raise SyntaxError('Interface magics have no options, got "{0}"'.format(line)) + output = self._interface.eval(cell) + print(output) + cell_magic.__doc__ = CELL_DOCSTRING.format(name=self._name) + return cell_magic diff --git a/src/sage/repl/ipython_extension.py b/src/sage/repl/ipython_extension.py index 4a0b2e2aaca..fb7abc80d8a 100644 --- a/src/sage/repl/ipython_extension.py +++ b/src/sage/repl/ipython_extension.py @@ -352,31 +352,12 @@ def __init__(self, shell=None): if SAGE_IMPORTALL == 'yes': self.init_environment() - def register_interface_magics(self): """ Register magics for each of the Sage interfaces """ - from sage.misc.superseded import deprecation - import sage.interfaces.all - interfaces = [(name, obj) - for name, obj in sage.interfaces.all.__dict__.items() - if isinstance(obj, sage.interfaces.interface.Interface)] - - for real_name, obj in interfaces: - def tmp(line, name=real_name): - self.shell.run_cell('%s.interact()' % name) - tmp.__doc__ = "Interact with %s" % real_name - self.shell.register_magic_function(tmp, magic_name=real_name) - - obj_name = obj.name() - if real_name != obj_name: - def tmp_deprecated(line, name=real_name, badname=obj_name): - deprecation(6288, 'Use %%%s instead of %%%s.' % (name, - badname)) - self.shell.run_cell('%s.interact()' % name) - tmp_deprecated.__doc__ = "Interact with %s" % real_name - self.shell.register_magic_function(tmp_deprecated, magic_name=obj_name) + from sage.repl.interface_magic import InterfaceMagic + InterfaceMagic.register_all(self.shell) def set_quit_hook(self): """ diff --git a/src/sage/repl/ipython_kernel/kernel.py b/src/sage/repl/ipython_kernel/kernel.py index eb61032dbd7..9639322050a 100644 --- a/src/sage/repl/ipython_kernel/kernel.py +++ b/src/sage/repl/ipython_kernel/kernel.py @@ -81,22 +81,27 @@ def help_links(self): See the Jupyter documentation. + .. NOTE:: + + Urls starting with "kernelspecs" are prepended by the + browser with the appropriate path. + EXAMPLES:: sage: from sage.repl.ipython_kernel.kernel import SageKernel sage: sk = SageKernel.__new__(SageKernel) sage: sk.help_links [{'text': 'Sage Documentation', - 'url': '../kernelspecs/sagemath/doc/index.html'}, + 'url': 'kernelspecs/sagemath/doc/index.html'}, ...] """ from sage.repl.ipython_kernel.install import SageKernelSpec identifier = SageKernelSpec.identifier() - kernel_url = lambda x: '../kernelspecs/{0}/{1}'.format(identifier, x) + kernel_url = lambda x: 'kernelspecs/{0}/{1}'.format(identifier, x) return [ { 'text': 'Sage Documentation', - 'url': kernel_url('doc/index.html') + 'url': kernel_url('doc/index.html'), }, { 'text': 'Sage Tutorial', diff --git a/src/sage/repl/rich_output/backend_base.py b/src/sage/repl/rich_output/backend_base.py index 623772fd2f1..b177f25973b 100644 --- a/src/sage/repl/rich_output/backend_base.py +++ b/src/sage/repl/rich_output/backend_base.py @@ -186,6 +186,27 @@ def supported_output(self): """ raise NotImplementedError('derived classes must implement this method') + def is_in_terminal(self): + """ + Test whether the UI is meant to run in a terminal + + See + :meth:`sage.repl.rich_output.display_manager.DisplayManager.is_in_terminal` + for details. + + OUTPUT: + + Defaults to ``False``. + + EXAMPLES:: + + sage: from sage.repl.rich_output.backend_base import BackendBase + sage: backend = BackendBase() + sage: backend.is_in_terminal() + False + """ + return False + def max_width(self): """ Return the number of characters that fit into one output line diff --git a/src/sage/repl/rich_output/backend_ipython.py b/src/sage/repl/rich_output/backend_ipython.py index fbfd386baab..7b25028f4e0 100644 --- a/src/sage/repl/rich_output/backend_ipython.py +++ b/src/sage/repl/rich_output/backend_ipython.py @@ -371,6 +371,26 @@ def launch_jmol(self, output_jmol, plain_text): .format(jmol_cmd, launch_script)) return 'Launched jmol viewer for {0}'.format(plain_text) + def is_in_terminal(self): + """ + Test whether the UI is meant to run in a terminal + + See + :meth:`sage.repl.rich_output.display_manager.DisplayManager.is_in_terminal` + for details. + + OUTPUT: + + ``True`` for the IPython commandline. + + EXAMPLES:: + + sage: from sage.repl.rich_output.backend_ipython import BackendIPythonCommandline + sage: backend = BackendIPythonCommandline() + sage: backend.is_in_terminal() + True + """ + return True class BackendIPythonNotebook(BackendIPython): """ diff --git a/src/sage/repl/rich_output/display_manager.py b/src/sage/repl/rich_output/display_manager.py index c2164cc0b25..2be66dddef4 100644 --- a/src/sage/repl/rich_output/display_manager.py +++ b/src/sage/repl/rich_output/display_manager.py @@ -343,6 +343,26 @@ def preferences(self): """ return self._preferences + def is_in_terminal(self): + """ + Test whether the UI is meant to run in a terminal + + When this method returns ``True``, you can assume that it is + possible to use ``raw_input`` or launch external programs that + take over the input. + + Otherwise, you should assume that the backend runs remotely or + in a pty controlled by another program. Then you should not + launch external programs with a (text or graphical) UI. + + This is used to enable/disable interpreter consoles. + + OUTPUT: + + Boolean. + """ + return self._backend.is_in_terminal() + def check_backend_class(self, backend_class): """ Check that the current backend is an instance of diff --git a/src/sage/rings/algebraic_closure_finite_field.py b/src/sage/rings/algebraic_closure_finite_field.py index 422819f1299..aa30b1e4934 100644 --- a/src/sage/rings/algebraic_closure_finite_field.py +++ b/src/sage/rings/algebraic_closure_finite_field.py @@ -700,7 +700,7 @@ def _subfield(self, n): if n == 1: return self.base_ring() else: - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField return FiniteField(self.base_ring().cardinality() ** n, name=self.variable_name() + str(n), modulus=self._get_polynomial(n), diff --git a/src/sage/rings/asymptotic/all.py b/src/sage/rings/asymptotic/all.py index daf2b157307..9353f267624 100644 --- a/src/sage/rings/asymptotic/all.py +++ b/src/sage/rings/asymptotic/all.py @@ -1,2 +1,3 @@ from sage.misc.lazy_import import lazy_import lazy_import('sage.rings.asymptotic.asymptotic_ring', 'AsymptoticRing') +lazy_import('sage.rings.asymptotic.asymptotic_expansion_generators', 'asymptotic_expansions') diff --git a/src/sage/rings/asymptotic/asymptotic_expansion_generators.py b/src/sage/rings/asymptotic/asymptotic_expansion_generators.py new file mode 100644 index 00000000000..92d1e17b331 --- /dev/null +++ b/src/sage/rings/asymptotic/asymptotic_expansion_generators.py @@ -0,0 +1,298 @@ +r""" +Common Asymptotic Expansions + +.. WARNING:: + + As this code is experimental, a warning is thrown when an + asymptotic ring (or an associated structure) is created for the + first time in a session (see + :class:`sage.misc.superseded.experimental`). + + TESTS:: + + sage: AsymptoticRing(growth_group='z^ZZ * log(z)^QQ', coefficient_ring=ZZ) + doctest:...: FutureWarning: This class/method/function is marked as + experimental. It, its functionality or its interface might change + without a formal deprecation. + See http://trac.sagemath.org/17601 for details. + doctest:...: FutureWarning: This class/method/function is marked as + experimental. It, its functionality or its interface might change + without a formal deprecation. + See http://trac.sagemath.org/17601 for details. + Asymptotic Ring over Integer Ring + + +Asymptotic expansions in SageMath can be built through the +``asymptotic_expansions`` object. It contains generators for common +asymptotic expressions. For example, +:: + + sage: asymptotic_expansions.Stirling('n', precision=5) + sqrt(2)*sqrt(pi)*e^(n*log(n))*(e^n)^(-1)*n^(1/2) + + 1/12*sqrt(2)*sqrt(pi)*e^(n*log(n))*(e^n)^(-1)*n^(-1/2) + + 1/288*sqrt(2)*sqrt(pi)*e^(n*log(n))*(e^n)^(-1)*n^(-3/2) + + O(e^(n*log(n))*(e^n)^(-1)*n^(-5/2)) + +generates the first 5 summands of Stirling's approximation formula for +factorials. + +To construct an asymptotic expression manually, you can use the class +:class:`~sage.rings.asymptotic.asymptotic_ring.AsymptoticRing`. See +:doc:`asymptotic ring ` for more details and a lot of +examples. + + +**Asymptotic Expansions** + +.. list-table:: + :class: contentstable + :widths: 4 12 + :header-rows: 0 + + * - :meth:`~AsymptoticExpansionGenerators.Stirling` + - Stirling's approximation formula for factorials + + * - :meth:`~AsymptoticExpansionGenerators.log_Stirling` + - the logarithm of Stirling's approximation formula for factorials + + +AUTHORS: + +- Daniel Krenn (2015) + + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + + +Classes and Methods +=================== +""" + +#***************************************************************************** +# Copyright (C) 2015 Daniel Krenn +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.structure.sage_object import SageObject + +class AsymptoticExpansionGenerators(SageObject): + r""" + A collection of constructors for several common asymptotic expansions. + + A list of all asymptotic expansions in this database is available via tab + completion. Type "``asymptotic_expansions.``" and then hit tab to see which + expansions are available. + + The asymptotic expansions currently in this class include: + + - :meth:`~Stirling` + - :meth:`~log_Stirling` + """ + + @staticmethod + def Stirling(var, precision=None, skip_constant_factor=False): + r""" + Return Stirling's approximation formula for factorials. + + INPUT: + + - ``var`` -- a string for the variable name. + + - ``precision`` -- (default: ``None``) an integer `\ge 3`. If ``None``, then + the default precision of the asymptotic ring is used. + + - ``skip_constant_factor`` -- (default: ``False``) a + boolean. If set, then the constant factor is left out. + As a consequence, the coefficient ring of the output changes + from ``Symbolic Constants Subring`` (if ``False``) to + ``Rational Field`` (if ``True``). + + OUTPUT: + + An asymptotic expansion. + + EXAMPLES:: + + sage: asymptotic_expansions.Stirling('n', precision=5) + sqrt(2)*sqrt(pi)*e^(n*log(n))*(e^n)^(-1)*n^(1/2) + + 1/12*sqrt(2)*sqrt(pi)*e^(n*log(n))*(e^n)^(-1)*n^(-1/2) + + 1/288*sqrt(2)*sqrt(pi)*e^(n*log(n))*(e^n)^(-1)*n^(-3/2) + + O(e^(n*log(n))*(e^n)^(-1)*n^(-5/2)) + sage: _.parent() + Asymptotic Ring <(e^(n*log(n)))^QQ * (e^n)^QQ * n^QQ * log(n)^QQ> + over Symbolic Constants Subring + + TESTS:: + + sage: asymptotic_expansions.Stirling('n', precision=5, + ....: skip_constant_factor=True) + e^(n*log(n))*(e^n)^(-1)*n^(1/2) + + 1/12*e^(n*log(n))*(e^n)^(-1)*n^(-1/2) + + 1/288*e^(n*log(n))*(e^n)^(-1)*n^(-3/2) + + O(e^(n*log(n))*(e^n)^(-1)*n^(-5/2)) + sage: _.parent() + Asymptotic Ring <(e^(n*log(n)))^QQ * (e^n)^QQ * n^QQ * log(n)^QQ> + over Rational Field + sage: asymptotic_expansions.Stirling('m', precision=4) + sqrt(2)*sqrt(pi)*e^(m*log(m))*(e^m)^(-1)*m^(1/2) + + O(e^(m*log(m))*(e^m)^(-1)*m^(-1/2)) + sage: asymptotic_expansions.Stirling('m', precision=3) + O(e^(m*log(m))*(e^m)^(-1)*m^(1/2)) + sage: asymptotic_expansions.Stirling('m', precision=2) + Traceback (most recent call last): + ... + ValueError: precision must be at least 3 + """ + if precision < 3: + raise ValueError("precision must be at least 3") + log_Stirling = AsymptoticExpansionGenerators.log_Stirling( + var, precision=precision, skip_constant_summand=True) + + P = log_Stirling.parent().change_parameter( + growth_group='(e^({n}*log({n})))^QQ * (e^{n})^QQ * {n}^QQ * log({n})^QQ'.format(n=var)) + from sage.functions.log import exp + result = exp(P(log_Stirling)) + + if not skip_constant_factor: + from sage.symbolic.ring import SR + SCR = SR.subring(no_variables=True) + result *= (2*SCR('pi')).sqrt() + + return result + + + @staticmethod + def log_Stirling(var, precision=None, skip_constant_summand=False): + r""" + Return the logarithm of Stirling's approximation formula + for factorials. + + INPUT: + + - ``var`` -- a string for the variable name. + + - ``precision`` -- (default: ``None``) an integer. If ``None``, then + the default precision of the asymptotic ring is used. + + - ``skip_constant_summand`` -- (default: ``False``) a + boolean. If set, then the constant summand is left out. + As a consequence, the coefficient ring of the output changes + from ``Symbolic Constants Subring`` (if ``False``) to + ``Rational Field`` (if ``True``). + + OUTPUT: + + An asymptotic expansion. + + EXAMPLES:: + + sage: asymptotic_expansions.log_Stirling('n', precision=7) + n*log(n) - n + 1/2*log(n) + 1/2*log(2*pi) + 1/12*n^(-1) + - 1/360*n^(-3) + 1/1260*n^(-5) + O(n^(-7)) + + TESTS:: + + sage: asymptotic_expansions.log_Stirling('n') + n*log(n) - n + 1/2*log(n) + 1/2*log(2*pi) + 1/12*n^(-1) + - 1/360*n^(-3) + 1/1260*n^(-5) - 1/1680*n^(-7) + 1/1188*n^(-9) + - 691/360360*n^(-11) + 1/156*n^(-13) - 3617/122400*n^(-15) + + 43867/244188*n^(-17) - 174611/125400*n^(-19) + 77683/5796*n^(-21) + - 236364091/1506960*n^(-23) + 657931/300*n^(-25) + - 3392780147/93960*n^(-27) + 1723168255201/2492028*n^(-29) + - 7709321041217/505920*n^(-31) + O(n^(-33)) + sage: _.parent() + Asymptotic Ring over Symbolic Constants Subring + + :: + + sage: asymptotic_expansions.log_Stirling( + ....: 'n', precision=7, skip_constant_summand=True) + n*log(n) - n + 1/2*log(n) + 1/12*n^(-1) - 1/360*n^(-3) + + 1/1260*n^(-5) + O(n^(-7)) + sage: _.parent() + Asymptotic Ring over Rational Field + sage: asymptotic_expansions.log_Stirling( + ....: 'n', precision=0) + O(n*log(n)) + sage: asymptotic_expansions.log_Stirling( + ....: 'n', precision=1) + n*log(n) + O(n) + sage: asymptotic_expansions.log_Stirling( + ....: 'n', precision=2) + n*log(n) - n + O(log(n)) + sage: asymptotic_expansions.log_Stirling( + ....: 'n', precision=3) + n*log(n) - n + 1/2*log(n) + O(1) + sage: asymptotic_expansions.log_Stirling( + ....: 'n', precision=4) + n*log(n) - n + 1/2*log(n) + 1/2*log(2*pi) + O(n^(-1)) + sage: asymptotic_expansions.log_Stirling( + ....: 'n', precision=5) + n*log(n) - n + 1/2*log(n) + 1/2*log(2*pi) + 1/12*n^(-1) + + O(n^(-3)) + sage: asymptotic_expansions.log_Stirling( + ....: 'm', precision=7, skip_constant_summand=True) + m*log(m) - m + 1/2*log(m) + 1/12*m^(-1) - 1/360*m^(-3) + + 1/1260*m^(-5) + O(m^(-7)) + """ + if not skip_constant_summand: + from sage.symbolic.ring import SR + coefficient_ring = SR.subring(no_variables=True) + else: + from sage.rings.rational_field import QQ + coefficient_ring = QQ + + from asymptotic_ring import AsymptoticRing + A = AsymptoticRing(growth_group='{n}^ZZ * log({n})^ZZ'.format(n=var), + coefficient_ring=coefficient_ring) + n = A.gen() + + if precision is None: + precision = A.default_prec + + from sage.functions.log import log + result = A.zero() + if precision >= 1: + result += n * log(n) + if precision >= 2: + result += -n + if precision >= 3: + result += log(n) / 2 + if precision >= 4 and not skip_constant_summand: + result += log(2*coefficient_ring('pi')) / 2 + + from sage.misc.misc import srange + from sage.arith.all import bernoulli + for k in srange(2, 2*precision - 6, 2): + result += bernoulli(k) / k / (k-1) / n**(k-1) + + if precision < 1: + result += (n * log(n)).O() + elif precision == 1: + result += n.O() + elif precision == 2: + result += log(n).O() + elif precision == 3: + result += A(1).O() + else: + result += (1 / n**(2*precision - 7)).O() + + return result + + +# Easy access to the asymptotic expansions generators from the command line: +asymptotic_expansions = AsymptoticExpansionGenerators() +r""" +A collection of several common asymptotic expansions. + +This is an instance of :class:`AsymptoticExpansionGenerators` whose documentation +provides more details. +""" diff --git a/src/sage/rings/asymptotic/asymptotic_ring.py b/src/sage/rings/asymptotic/asymptotic_ring.py index 779504429a7..3ae2617fb75 100644 --- a/src/sage/rings/asymptotic/asymptotic_ring.py +++ b/src/sage/rings/asymptotic/asymptotic_ring.py @@ -283,6 +283,36 @@ sage: (1 + 1/z + O(1/z^5))^(1 + 1/z) 1 + z^(-1) + z^(-2) + 1/2*z^(-3) + 1/3*z^(-4) + O(z^(-5)) +.. NOTE:: + + In the asymptotic ring + :: + + sage: M. = AsymptoticRing(growth_group='QQ^n * n^QQ', coefficient_ring=ZZ) + + the operation + :: + + sage: (1/2)^n + Traceback (most recent call last): + ... + ValueError: 1/2 is not in Exact Term Monoid QQ^n * n^QQ + with coefficients in Integer Ring. ... + + fails, since the rational `1/2` is not contained in `M`. You can use + :: + + sage: n.rpow(1/2) + (1/2)^n + + instead. (See also the examples in + :meth:`ExactTerm.rpow() ` + for a detailed explanation.) + Another way is to use a larger coefficent ring:: + + sage: M_QQ. = AsymptoticRing(growth_group='QQ^n * n^QQ', coefficient_ring=QQ) + sage: (1/2)^n + (1/2)^n Multivariate Arithmetic ^^^^^^^^^^^^^^^^^^^^^^^ @@ -441,6 +471,7 @@ from sage.structure.element import CommutativeAlgebraElement from sage.structure.unique_representation import UniqueRepresentation from sage.misc.superseded import experimental +from sage.rings.all import RIF class AsymptoticExpansion(CommutativeAlgebraElement): r""" @@ -654,6 +685,16 @@ def __init__(self, parent, summands, simplify=True, convert=True): Asymptotic Ring over Integer Ring > *previous* ValueError: 1/2 is not a coefficient in Exact Term Monoid x^QQ with coefficients in Integer Ring. + + Check :trac:`19921`:: + + sage: CR. = QQ['Z'] + sage: CR_mod = CR.quotient((Z^2 - 1)*CR) + sage: R. = AsymptoticRing(growth_group='x^NN', coefficient_ring=CR) + sage: R_mod = R.change_parameter(coefficient_ring=CR_mod) + sage: e = 1 + x*(Z^2-1) + sage: R_mod(e) + 1 """ super(AsymptoticExpansion, self).__init__(parent=parent) @@ -664,11 +705,13 @@ def __init__(self, parent, summands, simplify=True, convert=True): if convert: from misc import combine_exceptions - from term_monoid import TermMonoid + from term_monoid import TermMonoid, ZeroCoefficientError def convert_terms(element): - T = TermMonoid(term=element.parent(), asymptotic_ring=parent) + T = TermMonoid(term_monoid=element.parent(), asymptotic_ring=parent) try: return T(element) + except ZeroCoefficientError: + return None except (ValueError, TypeError) as e: raise combine_exceptions( ValueError('Cannot include %s with parent %s in %s' % @@ -1160,6 +1203,14 @@ def _div_(self, other): Traceback (most recent call last): ... ZeroDivisionError: Cannot invert O(x). + + TESTS: + + See :trac:`19521`:: + + sage: A. = AsymptoticRing('n^ZZ', SR.subring(no_variables=True)) + sage: (A.one() / 1).parent() + Asymptotic Ring over Symbolic Constants Subring """ return self * ~other @@ -1313,13 +1364,51 @@ def convert_terms(element): if convert_terms.count < precision: convert_terms.count += 1 return element - T = TermMonoid(term='O', asymptotic_ring=self.parent()) + T = TermMonoid(term_monoid='O', asymptotic_ring=self.parent()) return T(element) convert_terms.count = 0 summands.map(convert_terms, topological=True, reverse=True) return self.parent()(summands, simplify=True, convert=False) + def exact_part(self): + r""" + Return the expansion consisting of all exact terms of this + expansion. + + INPUT: + + Nothing + + OUTPUT: + + An asymptotic expansion. + + EXAMPLES:: + + sage: R. = AsymptoticRing('x^QQ * log(x)^QQ', QQ) + sage: (x^2 + O(x)).exact_part() + x^2 + sage: (x + log(x)/2 + O(log(x)/x)).exact_part() + x + 1/2*log(x) + + TESTS:: + + sage: R. = AsymptoticRing('x^QQ * y^QQ', QQ) + sage: (x + y + O(1/(x*y))).exact_part() + x + y + sage: O(x).exact_part() + 0 + """ + from term_monoid import ExactTerm + exact_terms = self.summands.copy() + for term in self.summands.elements_topological(): + if not isinstance(term, ExactTerm): + exact_terms.remove(term.growth) + + return self.parent(exact_terms) + + def __pow__(self, exponent, precision=None): r""" Calculate the power of this asymptotic expansion to the given ``exponent``. @@ -1408,6 +1497,18 @@ def __pow__(self, exponent, precision=None): ValueError: Cannot take s + t to the exponent s. > *previous* ValueError: log(s + t) cannot be constructed since there are several maximal elements s, t. + + Check that :trac:`19946` is fixed:: + + sage: A. = AsymptoticRing('QQ^n * n^QQ', SR) + sage: e = 2^n; e + 2^n + sage: e.parent() + Asymptotic Ring over Symbolic Ring + sage: e = A(e); e + 2^n + sage: e.parent() + Asymptotic Ring over Symbolic Ring """ if not self.summands: if exponent == 0: @@ -1666,6 +1767,14 @@ def rpow(self, base, precision=None): ArithmeticError: Cannot construct y^x in Growth Group x^ZZ > *previous* TypeError: unsupported operand parent(s) for '*': 'Growth Group x^ZZ' and 'Growth Group SR^x' + + Check that :trac:`19946` is fixed:: + + sage: A. = AsymptoticRing('QQ^n * n^QQ', SR) + sage: n.rpow(2) + 2^n + sage: _.parent() + Asymptotic Ring over Symbolic Ring """ if isinstance(base, AsymptoticExpansion): return base.__pow__(self, precision=precision) @@ -1787,6 +1896,12 @@ def exp(self, precision=None): sage: exp(x+1) e*e^x + + See :trac:`19521`:: + + sage: A. = AsymptoticRing('n^ZZ', SR.subring(no_variables=True)) + sage: exp(O(n^(-3))).parent() + Asymptotic Ring over Symbolic Constants Subring """ return self.rpow('e', precision=precision) @@ -2052,6 +2167,236 @@ def _substitute_(self, rules): substitute_raise_exception(self, e) + def compare_with_values(self, variable, function, values, + rescaled=True, ring=RIF): + """ + Compute the (rescaled) difference between this asymptotic + expansion and the given values. + + INPUT: + + - ``variable`` -- an asymptotic expansion or a string. + + - ``function`` -- a callable or symbolic expression giving the + comparison values. + + - ``values`` -- a list or iterable of values where the comparison + shall be carried out. + + - ``rescaled`` -- (default: ``True``) determines whether + the difference is divided by the error term of the asymptotic + expansion. + + - ``ring`` -- (default: ``RIF``) the parent into which the + difference is converted. + + OUTPUT: + + A list of pairs containing comparison points and (rescaled) + difference values. + + EXAMPLES:: + + sage: A. = AsymptoticRing('QQ^n * n^ZZ', SR) + sage: catalan = binomial(2*x, x)/(x+1) + sage: expansion = 4^n*(1/sqrt(pi)*n^(-3/2) + ....: - 9/8/sqrt(pi)*n^(-5/2) + ....: + 145/128/sqrt(pi)*n^(-7/2) + O(n^(-9/2))) + sage: expansion.compare_with_values(n, catalan, srange(5, 10)) + [(5, 0.5303924444775?), + (6, 0.5455279498787?), + (7, 0.556880411050?), + (8, 0.565710587724?), + (9, 0.572775029098?)] + sage: expansion.compare_with_values(n, catalan, [5, 10, 20], rescaled=False) + [(5, 0.3886263699387?), (10, 19.1842458318?), (20, 931314.63637?)] + sage: expansion.compare_with_values(n, catalan, [5, 10, 20], rescaled=False, ring=SR) + [(5, 168/5*sqrt(5)/sqrt(pi) - 42), + (10, 1178112/125*sqrt(10)/sqrt(pi) - 16796), + (20, 650486218752/125*sqrt(5)/sqrt(pi) - 6564120420)] + + Instead of a symbolic expression, a callable function can + be specified as well:: + + sage: A. = AsymptoticRing('n^ZZ * log(n)^ZZ', SR) + sage: def H(n): + ....: return sum(1/k for k in srange(1, n+1)) + sage: H_expansion = (log(n) + euler_gamma + 1/(2*n) + ....: - 1/(12*n^2) + O(n^-4)) + sage: H_expansion.compare_with_values(n, H, srange(25, 30)) # rel tol 1e-6 + [(25, -0.008326995?), + (26, -0.008327472?), + (27, -0.008327898?), + (28, -0.00832828?), + (29, -0.00832862?)] + + .. SEEALSO:: + + :meth:`plot_comparison` + + TESTS:: + + sage: A. = AsymptoticRing('x^ZZ*y^ZZ', QQ) + sage: expansion = x^2 + O(x) + O(y) + sage: expansion.compare_with_values(y, lambda z: z^2, srange(20, 30)) + Traceback (most recent call last): + .... + NotImplementedError: exactly one error term required + sage: expansion = x^2 + sage: expansion.compare_with_values(y, lambda z: z^2, srange(20, 30)) + Traceback (most recent call last): + .... + NotImplementedError: exactly one error term required + sage: expansion = x^2 + O(x) + sage: expansion.compare_with_values(y, lambda z: z^2, srange(20, 30)) + Traceback (most recent call last): + .... + NameError: name 'x' is not defined + sage: expansion.compare_with_values(x, lambda z: z^2, srange(20, 30)) + [(20, 0), (21, 0), ..., (29, 0)] + sage: expansion.compare_with_values(x, SR('x*y'), srange(20, 30)) + Traceback (most recent call last): + .... + NotImplementedError: expression x*y has more than one variable + """ + from term_monoid import OTerm + from sage.rings.integer_ring import ZZ + + main = self.exact_part() + error = self - main + error_terms = list(error.summands) + if len(error_terms) != 1: + raise NotImplementedError("exactly one error term required") + if not isinstance(error_terms[0], OTerm): + raise NotImplementedError("{} is not an O term".format(error)) + error_growth = error_terms[0].growth + + if hasattr(function, 'variables'): + expr = function + vars = expr.variables() + if len(vars) > 1: + raise NotImplementedError("expression {} has more than one " + "variable".format(expr)) + elif len(vars) == 1: + v = vars[0] + def function(arg): + return expr.subs({v: arg}) + else: + def function(arg): + return expr + + if rescaled: + points = list( + (k, ring((main.subs({variable: k}) - function(k)) / + error_growth._substitute_({str(variable): k, + '_one_': ZZ(1)}))) + for k in values) + else: + points = list( + (k, ring(main.subs({variable: k}) - function(k))) + for k in values) + + return points + + + def plot_comparison(self, variable, function, values, rescaled=True, + ring=RIF, relative_tolerance=0.025, **kwargs): + r""" + Plot the (rescaled) difference between this asymptotic + expansion and the given values. + + INPUT: + + - ``variable`` -- an asymptotic expansion or a string. + + - ``function`` -- a callable or symbolic expression giving the + comparison values. + + - ``values`` -- a list or iterable of values where the comparison + shall be carried out. + + - ``rescaled`` -- (default: ``True``) determines whether + the difference is divided by the error term of the asymptotic + expansion. + + - ``ring`` -- (default: ``RIF``) the parent into which the + difference is converted. + + - ``relative_tolerance`` -- (default: ``0.025``). Raise error + when relative error exceeds this tolerance. + + Other keyword arguments are passed to :func:`list_plot`. + + OUTPUT: + + A graphics object. + + .. NOTE:: + + If rescaled (i.e. divided by the error term), the output + should be bounded. + + This method is mainly meant to have an easily usable + plausability check for asymptotic expansion created in + some way. + + EXAMPLES: + + We want to check the quality of the asymptotic expansion of + the harmonic numbers:: + + sage: A. = AsymptoticRing('n^ZZ * log(n)^ZZ', SR) + sage: def H(n): + ....: return sum(1/k for k in srange(1, n+1)) + sage: H_expansion = (log(n) + euler_gamma + 1/(2*n) + ....: - 1/(12*n^2) + O(n^-4)) + sage: H_expansion.plot_comparison(n, H, srange(1, 30)) + Graphics object consisting of 1 graphics primitive + + Alternatively, the unscaled (absolute) difference can be + plotted as well:: + + sage: H_expansion.plot_comparison(n, H, srange(1, 30), + ....: rescaled=False) + Graphics object consisting of 1 graphics primitive + + Additional keywords are passed to :func:`list_plot`:: + + sage: H_expansion.plot_comparison(n, H, srange(1, 30), + ....: plotjoined=True, marker='o', + ....: color='green') + Graphics object consisting of 1 graphics primitive + + .. SEEALSO:: + + :meth:`compare_with_values` + + TESTS:: + + sage: H_expansion.plot_comparison(n, H, [600]) + Traceback (most recent call last): + ... + ValueError: Numerical noise is too high, the comparison is inaccurate + sage: H_expansion.plot_comparison(n, H, [600], relative_tolerance=2) + Graphics object consisting of 1 graphics primitive + """ + from sage.plot.plot import list_plot + points = self.compare_with_values(variable, function, + values, rescaled=rescaled, ring=ring) + + from sage.rings.real_mpfi import RealIntervalField_class + if isinstance(ring, RealIntervalField_class): + if not all(p[1].relative_diameter() <= relative_tolerance for p in points): + raise ValueError('Numerical noise is too high, the ' + 'comparison is inaccurate') + + # RIFs cannot be plotted, they need to be converted to RR + # (see #15011). + points = [(p[0], p[1].center()) for p in points] + + return list_plot(points, **kwargs) + + def symbolic_expression(self, R=None): r""" Return this asymptotic expansion as a symbolic expression. @@ -2105,6 +2450,59 @@ def symbolic_expression(self, R=None): _symbolic_ = symbolic_expression # will be used by SR._element_constructor_ + def map_coefficients(self, f, new_coefficient_ring=None): + r""" + Return the asymptotic expansion obtained by applying ``f`` to + each coefficient of this asymptotic expansion. + + INPUT: + + - ``f`` -- a callable. A coefficient `c` will be mapped to `f(c)`. + + - ``new_coefficient_ring`` -- (default: ``None``) a ring. + + OUTPUT: + + An asymptotic expansion. + + EXAMPLES:: + + sage: A. = AsymptoticRing(growth_group='n^ZZ', coefficient_ring=ZZ) + sage: a = n^4 + 2*n^3 + 3*n^2 + O(n) + sage: a.map_coefficients(lambda c: c+1) + 2*n^4 + 3*n^3 + 4*n^2 + O(n) + sage: a.map_coefficients(lambda c: c-2) + -n^4 + n^2 + O(n) + + TESTS:: + + sage: a.map_coefficients(lambda c: 1/c, new_coefficient_ring=QQ) + n^4 + 1/2*n^3 + 1/3*n^2 + O(n) + sage: _.parent() + Asymptotic Ring over Rational Field + sage: a.map_coefficients(lambda c: 1/c) + Traceback (most recent call last): + ... + ValueError: ... is not a coefficient in + Exact Term Monoid n^ZZ with coefficients in Integer Ring. + """ + def mapping(term): + T = term.parent().change_parameter( + coefficient_ring=new_coefficient_ring) + if hasattr(term, 'coefficient'): + c = f(term.coefficient) + if c.is_zero(): + return None + return T(term.growth, c) + else: + return T(term.growth) + + P = self.parent().change_parameter(coefficient_ring=new_coefficient_ring) + S = self.summands.copy() + S.map(mapping) + return P(S, simplify=False, convert=False) + + class AsymptoticRing(Algebra, UniqueRepresentation): r""" A ring consisting of :class:`asymptotic expansions `. @@ -2439,11 +2837,16 @@ def change_parameter(self, **kwds): sage: A.change_parameter(coefficient_ring=ZZ) is A True + sage: A.change_parameter(coefficient_ring=None) is A + True """ parameters = ('growth_group', 'coefficient_ring', 'default_prec') values = dict() for parameter in parameters: - values[parameter] = kwds.get(parameter, getattr(self, parameter)) + default = getattr(self, parameter) + values[parameter] = kwds.get(parameter, default) + if values[parameter] is None: + values[parameter] = default values['category'] = self.category() if isinstance(values['growth_group'], str): from growth_group import GrowthGroup @@ -3024,6 +3427,12 @@ def create_summand(self, type, data=None, **kwds): sage: R.create_summand('exact', data=12) 12 + :: + + sage: Z = R.change_parameter(coefficient_ring=Zmod(3)) + sage: Z.create_summand('exact', data=42) + 0 + :: sage: R.create_summand('O', growth=42*x^2, coefficient=1) @@ -3041,7 +3450,7 @@ def create_summand(self, type, data=None, **kwds): TypeError: Cannot create exact term: only 'growth' but no 'coefficient' specified. """ - from term_monoid import TermMonoid + from term_monoid import TermMonoid, ZeroCoefficientError TM = TermMonoid(type, asymptotic_ring=self) if data is None: @@ -3053,12 +3462,11 @@ def create_summand(self, type, data=None, **kwds): raise TypeError("Cannot create exact term: only 'growth' " "but no 'coefficient' specified.") - - if type == 'exact' and kwds.get('coefficient') == 0: + try: + return self(TM(data, **kwds), simplify=False, convert=False) + except ZeroCoefficientError: return self.zero() - return self(TM(data, **kwds), simplify=False, convert=False) - def variable_names(self): r""" diff --git a/src/sage/rings/asymptotic/growth_group.py b/src/sage/rings/asymptotic/growth_group.py index cdf54df97a2..6c0efa735b1 100644 --- a/src/sage/rings/asymptotic/growth_group.py +++ b/src/sage/rings/asymptotic/growth_group.py @@ -873,6 +873,14 @@ def _rpow_(self, base): sage: x = G('x') sage: (x * log(x)).rpow(2) # indirect doctest 2^(x*log(x)) + + :: + + sage: n = GrowthGroup('QQ^n * n^QQ')('n') + sage: n.rpow(2) + 2^n + sage: _.parent() + Growth Group QQ^n * n^QQ """ if base == 0: raise ValueError('%s is not an allowed base for calculating the ' @@ -2556,7 +2564,7 @@ def __invert__(self): sage: e2 == ~e1 True sage: Q = GrowthGroup('x^NN'); Q - Growth Group x^((Non negative integer semiring)) + Growth Group x^(Non negative integer semiring) sage: e3 = ~Q('x'); e3 x^(-1) sage: e3.parent() diff --git a/src/sage/rings/asymptotic/growth_group_cartesian.py b/src/sage/rings/asymptotic/growth_group_cartesian.py index ed5ff1f83aa..64ab8347d13 100644 --- a/src/sage/rings/asymptotic/growth_group_cartesian.py +++ b/src/sage/rings/asymptotic/growth_group_cartesian.py @@ -692,11 +692,17 @@ def _pushout_(self, other): Growth Group QQ^x * x^QQ sage: cm.common_parent(GrowthGroup('QQ^x * x^ZZ'), GrowthGroup('ZZ^x * x^QQ')) Growth Group QQ^x * x^QQ + + :: + + sage: pushout(GrowthGroup('QQ^n * n^QQ'), GrowthGroup('SR^n')) + Growth Group SR^n * n^QQ """ from growth_group import GenericGrowthGroup, AbstractGrowthGroupFunctor from misc import merge_overlapping from misc import underlying_class + Sfactors = self.cartesian_factors() if isinstance(other, GenericProduct): Ofactors = other.cartesian_factors() elif isinstance(other, GenericGrowthGroup): @@ -759,7 +765,7 @@ def next(self): self.factors = tuple() from itertools import groupby - S = it(groupby(self.cartesian_factors(), key=lambda k: k.variable_names())) + S = it(groupby(Sfactors, key=lambda k: k.variable_names())) O = it(groupby(Ofactors, key=lambda k: k.variable_names())) newS = [] @@ -786,9 +792,9 @@ def next(self): assert(len(newS) == len(newO)) - if (len(self.cartesian_factors()) == len(newS) and - len(other.cartesian_factors()) == len(newO)): - # We had already all factors in each of the self and + if (len(Sfactors) == len(newS) and + len(Ofactors) == len(newO)): + # We had already all factors in each of self and # other, thus splitting it in subproblems (one for # each factor) is the strategy to use. If a pushout is # possible :func:`sage.categories.pushout.pushout` diff --git a/src/sage/rings/asymptotic/misc.py b/src/sage/rings/asymptotic/misc.py index 0b142d7698d..6fffb5142a1 100644 --- a/src/sage/rings/asymptotic/misc.py +++ b/src/sage/rings/asymptotic/misc.py @@ -107,15 +107,15 @@ def parent_to_repr_short(P): sage: parent_to_repr_short(ZZ['x']) 'ZZ[x]' sage: parent_to_repr_short(QQ['d, k']) - '(QQ[d, k])' + 'QQ[d, k]' sage: parent_to_repr_short(QQ['e']) 'QQ[e]' sage: parent_to_repr_short(SR[['a, r']]) - '(SR[[a, r]])' + 'SR[[a, r]]' sage: parent_to_repr_short(Zmod(3)) - '(Ring of integers modulo 3)' + 'Ring of integers modulo 3' sage: parent_to_repr_short(Zmod(3)['g']) - '(Univariate Polynomial Ring in g over Ring of integers modulo 3)' + 'Univariate Polynomial Ring in g over Ring of integers modulo 3' """ def abbreviate(P): if P is sage.rings.integer_ring.ZZ: @@ -147,8 +147,6 @@ def abbreviate(P): except ValueError: s = str(P) - if ' ' in s: - s = '(' + s + ')' return s @@ -212,7 +210,23 @@ def split_str_by_op(string, op, strip_parentheses=True): ('(t)s',) sage: split_str_by_op(' ( t ) s', op=None) ('t', 's') + + :: + + sage: split_str_by_op('(e^(n*log(n)))^SR.subring(no_variables=True)', '*') + ('(e^(n*log(n)))^SR.subring(no_variables=True)',) """ + def is_balanced(s): + open = 0 + for l in s: + if l == '(': + open += 1 + elif l == ')': + open -= 1 + if open < 0: + return False + return bool(open == 0) + factors = list() balanced = True if string and op is not None and string.startswith(op): @@ -228,7 +242,7 @@ def split_str_by_op(string, op, strip_parentheses=True): (string, op, op)) if not balanced: s = factors.pop() + (op if op else '') + s - balanced = s.count('(') == s.count(')') + balanced = is_balanced(s) factors.append(s) if not balanced: @@ -239,7 +253,9 @@ def strip(s): if not s: return s if strip_parentheses and s[0] == '(' and s[-1] == ')': - s = s[1:-1] + t = s[1:-1] + if is_balanced(t): + s = t return s.strip() return tuple(strip(f) for f in factors) diff --git a/src/sage/rings/asymptotic/term_monoid.py b/src/sage/rings/asymptotic/term_monoid.py index c8a2486d2d8..c26da973a59 100644 --- a/src/sage/rings/asymptotic/term_monoid.py +++ b/src/sage/rings/asymptotic/term_monoid.py @@ -222,6 +222,8 @@ import sage +class ZeroCoefficientError(ValueError): + pass def absorption(left, right): r""" @@ -1388,6 +1390,52 @@ def coefficient_ring(self): return self._coefficient_ring_ + def change_parameter(self, growth_group=None, coefficient_ring=None): + r""" + Return a term monoid with a change in one or more of the + given parameters. + + INPUT: + + - ``growth_group`` -- (default: ``None``) the new growth group. + + - ``coefficient_ring`` -- (default: ``None``) the new coefficient ring. + + OUTPUT: + + A term monoid. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: E = TermMonoid('exact', GrowthGroup('n^ZZ'), ZZ) + sage: E.change_parameter(coefficient_ring=QQ) + Exact Term Monoid n^ZZ with coefficients in Rational Field + sage: E.change_parameter(growth_group=GrowthGroup('n^QQ')) + Exact Term Monoid n^QQ with coefficients in Integer Ring + + TESTS:: + + sage: E.change_parameter() is E + True + sage: E.change_parameter(growth_group=None) is E + True + sage: E.change_parameter(coefficient_ring=None) is E + True + sage: E.change_parameter(growth_group=None, coefficient_ring=None) is E + True + """ + if growth_group is None: + growth_group = self.growth_group + if coefficient_ring is None: + coefficient_ring = self.coefficient_ring + if self.growth_group is growth_group and \ + self.coefficient_ring is coefficient_ring: + return self + return TermMonoid(self, growth_group, coefficient_ring) + + def _repr_(self): r""" A representation string for this generic term monoid. @@ -2502,7 +2550,7 @@ def __init__(self, parent, growth, coefficient): sage: t = CT_ZZ(x^42, 0) Traceback (most recent call last): ... - ValueError: Zero coefficient 0 is not allowed in + ZeroCoefficientError: Zero coefficient 0 is not allowed in Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring. The conversion of growth elements also works for the creation @@ -2519,8 +2567,9 @@ def __init__(self, parent, growth, coefficient): raise ValueError('%s is not a coefficient in %s.' % (coefficient, parent)) if coefficient == 0: - raise ValueError('Zero coefficient %s is not allowed in %s.' % - (coefficient, parent)) + raise ZeroCoefficientError( + 'Zero coefficient %s is not allowed in %s.' % + (coefficient, parent)) self.coefficient = coefficient super(TermWithCoefficient, self).__init__(parent=parent, growth=growth) @@ -2988,6 +3037,16 @@ def _repr_(self): -x^2 sage: ET(x^0, 42) 42 + + Check that :trac:`19576` is fixed:: + + sage: C. = AsymptoticRing('c^ZZ', SR) + sage: (1+pi)*c + (pi + 1)*c + sage: R. = QQ[] + sage: S. = AsymptoticRing('n^QQ', R) + sage: (1+a)/n + (a + 1)*n^(-1) """ g = repr(self.growth) c = repr(self.coefficient) @@ -2997,8 +3056,12 @@ def _repr_(self): return '%s' % (g,) elif c == '-1': return '-%s' % (g,) - else: + elif self.coefficient._is_atomic() or (-self.coefficient)._is_atomic(): + # note that -pi/2 is not atomic, but -5 is. As subtractions are handeled + # in the asymptotic ring, we ignore such non-atomicity. return '%s*%s' % (c, g) + else: + return '(%s)*%s' % (c, g) def __invert__(self): @@ -3308,6 +3371,32 @@ def rpow(self, base): Growth Group QQ^x * x^ZZ * log(x)^ZZ > *previous* TypeError: unsupported operand parent(s) for '*': 'Growth Group QQ^x * x^ZZ * log(x)^ZZ' and 'Growth Group ZZ^(x^2)' + + :: + + sage: T = TermMonoid('exact', GrowthGroup('QQ^n * n^QQ'), SR) + sage: n = T('n') + sage: n.rpow(2) + 2^n + sage: _.parent() + Exact Term Monoid QQ^n * n^SR with coefficients in Symbolic Ring + + Above, we get ``QQ^n * n^SR``. The reason is the following: + Since $n = 1_{SR} \cdot (1_{\QQ})^n \cdot n^{1_{\QQ}}$, we have + + .. MATH:: + + 2^n = (2_{\QQ})^{1_{SR} \cdot (1_{\QQ})^n \cdot n^{1_{\QQ}}} + = \left( (2_{\QQ})^n \cdot n^{0_{\QQ}} \right)^{1_{SR}} + = \left((2_{\QQ})^{1_{SR}}\right)^n \cdot n^{0_{\QQ} 1_{SR}} + = (2_{\QQ})^n \cdot n^{0_{SR}} + + where :: + + sage: (QQ(2)^SR(1)).parent(), (QQ(0)*SR(1)).parent() + (Rational Field, Symbolic Ring) + + was used. """ P = self.parent() @@ -3463,9 +3552,9 @@ class TermMonoidFactory(sage.structure.factory.UniqueFactory): INPUT: - - ``term`` -- the kind of term that shall be created. Either a string - ``'exact'`` or ``'O'`` (capital letter ``O``), - or an existing instance of a term. + - ``term_monoid`` -- the kind of terms held in the new term monoid. + Either a string ``'exact'`` or ``'O'`` (capital letter ``O``), + or an existing instance of a term monoid. - ``growth_group`` -- a growth group. @@ -3556,7 +3645,7 @@ class TermMonoidFactory(sage.structure.factory.UniqueFactory): running ._test_prod() . . . pass running ._test_some_elements() . . . pass """ - def create_key_and_extra_args(self, term, + def create_key_and_extra_args(self, term_monoid, growth_group=None, coefficient_ring=None, asymptotic_ring=None, **kwds): @@ -3593,16 +3682,16 @@ def create_key_and_extra_args(self, term, ... ValueError: Integer Ring has to be an asymptotic growth group """ - if isinstance(term, GenericTermMonoid): + if isinstance(term_monoid, GenericTermMonoid): from misc import underlying_class - term_class = underlying_class(term) - elif term == 'O': + term_class = underlying_class(term_monoid) + elif term_monoid == 'O': term_class = OTermMonoid - elif term == 'exact': + elif term_monoid == 'exact': term_class = ExactTermMonoid else: raise ValueError("Term specification '%s' has to be either 'exact' or 'O' " - "or an instance of an existing term." % term) + "or an instance of an existing term." % term_monoid) if asymptotic_ring is not None and \ (growth_group is not None or coefficient_ring is not None): @@ -3621,7 +3710,7 @@ def create_key_and_extra_args(self, term, if coefficient_ring is None: raise ValueError("A coefficient ring has to be specified to " - "create a term monoid of type '%s'" % (term,)) + "create a term monoid of type '%s'" % (term_monoid,)) return (term_class, growth_group, coefficient_ring), kwds diff --git a/src/sage/rings/bernmm.pyx b/src/sage/rings/bernmm.pyx index 5942ca9611e..3c6479ae538 100644 --- a/src/sage/rings/bernmm.pyx +++ b/src/sage/rings/bernmm.pyx @@ -125,6 +125,14 @@ def bernmm_bern_modp(long p, long k): sage: bernmm_bern_modp(p, k) 1972762 + TESTS: + + Check that bernmm works with the new NTL single precision modular + arithmetic from :trac:`19874`:: + + sage: from sage.rings.bernmm import bernmm_bern_modp + sage: bernmm_bern_modp(7, 128) == bernoulli(128) % 7 + True """ cdef long x diff --git a/src/sage/rings/bernmm/bern_modp.cpp b/src/sage/rings/bernmm/bern_modp.cpp index 197c372e9cb..b46a7de18bc 100644 --- a/src/sage/rings/bernmm/bern_modp.cpp +++ b/src/sage/rings/bernmm/bern_modp.cpp @@ -40,17 +40,17 @@ namespace bernmm { PRECONDITIONS: 5 <= p < NTL_SP_BOUND, p prime 2 <= k <= p-3, k even - pinv = 1 / ((double) p) + pinv = PrepMulMod(p) g = a multiplicative generator of GF(p), in [0, p) */ -long bernsum_powg(long p, double pinv, long k, long g) +long bernsum_powg(long p, mulmod_t pinv, long k, long g) { long half_gm1 = (g + ((g & 1) ? 0 : p) - 1) / 2; // (g-1)/2 mod p long g_to_jm1 = 1; long g_to_km1 = PowerMod(g, k-1, p, pinv); long g_to_km1_to_j = g_to_km1; long sum = 0; - double g_pinv = ((double) g) / ((double) p); + muldivrem_t g_pinv = PrepMulDivRem(g, p); mulmod_precon_t g_to_km1_pinv = PrepMulModPrecon(g_to_km1, p, pinv); for (long j = 1; j <= (p-1)/2; j++) @@ -210,7 +210,7 @@ class Expander PRECONDITIONS: 5 <= p < NTL_SP_BOUND, p prime 2 <= k <= p-3, k even - pinv = 1 / ((double) p) + pinv = PrepMulMod(p) g = a multiplicative generator of GF(p), in [0, p) n = multiplicative order of 2 in GF(p) */ @@ -224,7 +224,7 @@ class Expander #error Number of bits in a long must be divisible by TABLE_LG_SIZE #endif -long bernsum_pow2(long p, double pinv, long k, long g, long n) +long bernsum_pow2(long p, mulmod_t pinv, long k, long g, long n) { // In the main summation loop we accumulate data into the _tables_ array; // tables[y][z] contributes to the final answer with a weight of @@ -481,7 +481,7 @@ long PrepRedc(long n) (See bernsum_pow2() for code comments; we only add comments here where something is different from bernsum_pow2()) */ -long bernsum_pow2_redc(long p, double pinv, long k, long g, long n) +long bernsum_pow2_redc(long p, mulmod_t pinv, long k, long g, long n) { long pinv2 = PrepRedc(p); long F = (1L << (ULONG_BITS/2)) % p; @@ -651,11 +651,11 @@ long bernsum_pow2_redc(long p, double pinv, long k, long g, long n) PRECONDITIONS: 5 <= p < NTL_SP_BOUND, p prime 2 <= k <= p-3, k even - pinv = 1 / ((double) p) + pinv = PrepMulMod(p) Algorithm: uses bernsum_powg() to compute the main sum. */ -long _bern_modp_powg(long p, double pinv, long k) +long _bern_modp_powg(long p, mulmod_t pinv, long k) { Factorisation F(p-1); long g = primitive_root(p, pinv, F); @@ -679,13 +679,13 @@ long _bern_modp_powg(long p, double pinv, long k) PRECONDITIONS: 5 <= p < NTL_SP_BOUND, p prime 2 <= k <= p-3, k even - pinv = 1 / ((double) p) + pinv = PrepMulMod(p) 2^k != 1 mod p Algorithm: uses bernsum_pow2() (or bernsum_pow2_redc() if p is small enough) to compute the main sum. */ -long _bern_modp_pow2(long p, double pinv, long k) +long _bern_modp_pow2(long p, mulmod_t pinv, long k) { Factorisation F(p-1); long g = primitive_root(p, pinv, F); @@ -715,9 +715,9 @@ long _bern_modp_pow2(long p, double pinv, long k) PRECONDITIONS: 5 <= p < NTL_SP_BOUND, p prime 2 <= k <= p-3, k even - pinv = 1 / ((double) p) + pinv = PrepMulMod(p) */ -long _bern_modp(long p, double pinv, long k) +long _bern_modp(long p, mulmod_t pinv, long k) { if (PowerMod(2, k, p, pinv) != 1) // 2^k != 1 mod p, so we use the faster version @@ -765,9 +765,9 @@ long bern_modp(long p, long k) if (m == 0) return -1; - double pinv = 1 / ((double) p); + mulmod_t pinv = PrepMulMod(p); long x = _bern_modp(p, pinv, m); // = B_m/m mod p - return MulMod(x, k, p, pinv); + return MulMod(x, k%p, p, pinv); } diff --git a/src/sage/rings/bernmm/bern_modp.h b/src/sage/rings/bernmm/bern_modp.h index 7f2df380508..81bb98dfa82 100644 --- a/src/sage/rings/bernmm/bern_modp.h +++ b/src/sage/rings/bernmm/bern_modp.h @@ -12,6 +12,7 @@ #ifndef BERNMM_BERN_MODP_H #define BERNMM_BERN_MODP_H +#include namespace bernmm { @@ -29,8 +30,8 @@ long bern_modp(long p, long k); /* Exported for testing. */ -long _bern_modp_powg(long p, double pinv, long k); -long _bern_modp_pow2(long p, double pinv, long k); +long _bern_modp_powg(long p, NTL::mulmod_t pinv, long k); +long _bern_modp_pow2(long p, NTL::mulmod_t pinv, long k); }; diff --git a/src/sage/rings/bernmm/bern_modp_util.cpp b/src/sage/rings/bernmm/bern_modp_util.cpp index 9e37b4c86a1..f0e0b309ec6 100644 --- a/src/sage/rings/bernmm/bern_modp_util.cpp +++ b/src/sage/rings/bernmm/bern_modp_util.cpp @@ -20,7 +20,7 @@ NTL_CLIENT; namespace bernmm { -long PowerMod(long a, long ee, long n, double ninv) +long PowerMod(long a, long ee, long n, mulmod_t ninv) { long x, y; @@ -89,7 +89,7 @@ PrimeTable::PrimeTable(long bound) } -long order(long x, long p, double pinv, const Factorisation& F) +long order(long x, long p, mulmod_t pinv, const Factorisation& F) { // in the loop below, m is always some multiple of the order of x long m = p - 1; @@ -113,7 +113,7 @@ long order(long x, long p, double pinv, const Factorisation& F) -long primitive_root(long p, double pinv, const Factorisation& F) +long primitive_root(long p, mulmod_t pinv, const Factorisation& F) { if (p == 2) return 1; diff --git a/src/sage/rings/bernmm/bern_modp_util.h b/src/sage/rings/bernmm/bern_modp_util.h index 24c128cef9b..e232a47ae8e 100644 --- a/src/sage/rings/bernmm/bern_modp_util.h +++ b/src/sage/rings/bernmm/bern_modp_util.h @@ -18,6 +18,7 @@ #include #include +#include #if ULONG_MAX == 4294967295U #define ULONG_BITS 32 @@ -35,11 +36,11 @@ namespace bernmm { /* Same as NTL's PowerMod, but also accepts an _ninv_ parameter, which is the same as the ninv parameter for NTL's MulMod routines, i.e. should have - ninv = 1 / ((double) n). + ninv = PrepMulMod(n). (Implementation is adapted from ZZ.c in NTL 5.4.1.) */ -long PowerMod(long a, long ee, long n, double ninv); +long PowerMod(long a, long ee, long n, NTL::mulmod_t ninv); /* @@ -123,13 +124,13 @@ long next_prime(long p); /* Computes order of x mod p, given the factorisation F of p-1. */ -long order(long x, long p, double pinv, const Factorisation& F); +long order(long x, long p, NTL::mulmod_t pinv, const Factorisation& F); /* Finds the smallest primitive root mod p, given the factorisation F of p-1. */ -long primitive_root(long p, double pinv, const Factorisation& F); +long primitive_root(long p, NTL::mulmod_t pinv, const Factorisation& F); }; // end namespace diff --git a/src/sage/rings/bernmm/bernmm-test.cpp b/src/sage/rings/bernmm/bernmm-test.cpp index da8f754fb5d..06f381500de 100644 --- a/src/sage/rings/bernmm/bernmm-test.cpp +++ b/src/sage/rings/bernmm/bernmm-test.cpp @@ -70,7 +70,7 @@ void bern_naive(mpq_t* res, long n) */ int testcase__bern_modp_powg(long p, long k, mpq_t b) { - double pinv = 1 / ((double) p); + mulmod_t pinv = PrepMulMod(p); // compute B_k mod p using _bern_modp_powg() long x = _bern_modp_powg(p, pinv, k); @@ -147,7 +147,7 @@ int test__bern_modp_powg() */ int testcase__bern_modp_pow2(long p, long k) { - double pinv = 1 / ((double) p); + mulmod_t pinv = PrepMulMod(p); if (PowerMod(2, k, p, pinv) == 1) return 1; diff --git a/src/sage/rings/complex_arb.pyx b/src/sage/rings/complex_arb.pyx index 43b130dc06b..c96d8f5043a 100644 --- a/src/sage/rings/complex_arb.pyx +++ b/src/sage/rings/complex_arb.pyx @@ -31,6 +31,13 @@ conveniently by splitting into ball operations on the real and imaginary parts. It also allows tracking when complex numbers have an exact (for example exactly zero) real part and an inexact imaginary part, or vice versa. +The parents of complex balls are instances of :class:`ComplexBallField`. +The name ``CBF`` is bound to the complex ball field with the default precision +of 53 bits:: + + sage: CBF is ComplexBallField() is ComplexBallField(53) + True + Comparison ========== @@ -114,6 +121,8 @@ Classes and Methods include 'sage/ext/interrupt.pxi' include "sage/ext/stdsage.pxi" +import operator + import sage.categories.fields cimport sage.rings.integer @@ -131,8 +140,10 @@ from sage.libs.arb.arf cimport arf_init, arf_get_mpfr, arf_set_mpfr, arf_clear, from sage.libs.arb.mag cimport mag_init, mag_clear, mag_add, mag_set_d, MAG_BITS, mag_is_inf, mag_is_finite, mag_zero from sage.libs.flint.fmpz cimport fmpz_t, fmpz_init, fmpz_get_mpz, fmpz_set_mpz, fmpz_clear from sage.libs.flint.fmpq cimport fmpq_t, fmpq_init, fmpq_set_mpq, fmpq_clear +from sage.libs.gmp.mpz cimport mpz_fits_ulong_p, mpz_fits_slong_p, mpz_get_ui, mpz_get_si from sage.rings.complex_field import ComplexField from sage.rings.complex_interval_field import ComplexIntervalField +from sage.rings.integer_ring import ZZ from sage.rings.real_arb cimport mpfi_to_arb, arb_to_mpfi from sage.rings.real_arb import RealBallField from sage.rings.real_mpfr cimport RealField_class, RealField, RealNumber @@ -195,7 +206,6 @@ class ComplexBallField(UniqueRepresentation, Field): EXAMPLES:: - sage: CBF = ComplexBallField() # indirect doctest sage: CBF(1) 1.000000000000000 @@ -234,7 +244,6 @@ class ComplexBallField(UniqueRepresentation, Field): EXAMPLES:: - sage: CBF = ComplexBallField() sage: CBF(1) 1.000000000000000 @@ -286,7 +295,6 @@ class ComplexBallField(UniqueRepresentation, Field): base_ring=real_field, category=category or sage.categories.fields.Fields().Infinite()) self._prec = precision - from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.rings.real_lazy import CLF self._populate_coercion_lists_([ZZ, QQ, real_field, CLF]) @@ -938,7 +946,6 @@ cdef class ComplexBall(RingElement): EXAMPLES:: - sage: CBF = ComplexBallField() sage: a = CBF(1/3, 1/5) sage: a.real() [0.3333333333333333 +/- 7.04e-17] @@ -957,7 +964,6 @@ cdef class ComplexBall(RingElement): EXAMPLES:: - sage: CBF = ComplexBallField() sage: a = CBF(1/3, 1/5) sage: a.imag() [0.2000000000000000 +/- 4.45e-17] @@ -1282,7 +1288,6 @@ cdef class ComplexBall(RingElement): EXAMPLES:: - sage: CBF = ComplexBallField() sage: CBF(0).is_zero() True sage: CBF(RIF(-0.5, 0.5)).is_zero() @@ -1308,7 +1313,6 @@ cdef class ComplexBall(RingElement): EXAMPLES:: - sage: CBF = ComplexBallField() sage: CBF(pi, 1/3).is_nonzero() True sage: CBF(RIF(-0.5, 0.5), 1/3).is_nonzero() @@ -1354,7 +1358,6 @@ cdef class ComplexBall(RingElement): EXAMPLES:: - sage: CBF = ComplexBallField() sage: CBF(1).is_exact() True sage: CBF(1/3, 1/3).is_exact() @@ -1385,7 +1388,6 @@ cdef class ComplexBall(RingElement): EXAMPLES:: - sage: CBF = ComplexBallField() sage: a = CBF(1) sage: b = CBF(1) sage: a is b @@ -1842,4 +1844,803 @@ cdef class ComplexBall(RingElement): if _do_sig(prec(self)): sig_off() return res + def __pow__(base, expo, _): + """ + EXAMPLES:: + + sage: CBF(-1)**(1/2) + [+/- 2.84e-16] + [1.00000000000000 +/- 4.45e-16]*I + sage: CBF(e)**CBF(i*pi) + [-1.0000000000000 +/- 1.98e-15] + [+/- 2.32e-15]*I + sage: CBF(0, 1)**AA(2)**(1/2) + [-0.60569986707881 +/- 4.36e-15] + [0.79569320156748 +/- 2.53e-15]*I + + sage: CBF(i)**RBF(2**1000) + [+/- 2.51] + [+/- 2.87]*I + sage: CBF(i)**(2**1000) + 1.000000000000000 + + sage: CBF(0)^(1/3) + nan + nan*I + sage: CBF(0)^(-1) + [+/- inf] + sage: CBF(0)^(-2) + [+/- inf] + [+/- inf]*I + + TESTS:: + + sage: (CBF(e)**CBF(i))**RBF(pi) + [-1.0000000000000 +/- 5.48e-15] + [+/- 4.14e-15]*I + sage: CBF(2*i)**10r + -1024.000000000000 + """ + cdef fmpz_t tmpz + if not isinstance(base, ComplexBall): + return sage.structure.element.bin_op(base, expo, operator.pow) + cdef ComplexBall self = base + cdef ComplexBall res = self._new() + if isinstance(expo, int): + if _do_sig(prec(self)): sig_on() + acb_pow_ui(res.value, self.value, PyInt_AS_LONG(expo), prec(self)) + if _do_sig(prec(self)): sig_off() + elif isinstance(expo, sage.rings.integer.Integer): + if _do_sig(prec(self)): sig_on() + fmpz_init(tmpz) + fmpz_set_mpz(tmpz, ( expo).value) + acb_pow_fmpz(res.value, self.value, tmpz, prec(self)) + fmpz_clear(tmpz) + if _do_sig(prec(self)): sig_off() + elif isinstance(expo, ComplexBall): + if _do_sig(prec(self)): sig_on() + acb_pow(res.value, self.value, ( expo).value, prec(self)) + if _do_sig(prec(self)): sig_off() + elif isinstance(expo, RealBall): + if _do_sig(prec(self)): sig_on() + acb_pow_arb(res.value, self.value, ( expo).value, prec(self)) + if _do_sig(prec(self)): sig_off() + else: + return sage.structure.element.bin_op(base, expo, operator.pow) + return res + + def sqrt(self): + """ + Return the square root of this ball. + + If either the real or imaginary part is exactly zero, only a single + real square root is needed. + + EXAMPLES:: + + sage: CBF(-2).sqrt() + [1.414213562373095 +/- 2.99e-16]*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_sqrt(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def rsqrt(self): + """ + Return the reciprocal square root of ``self``. + + If either the real or imaginary part is exactly zero, only a single + real reciprocal square root is needed. + + EXAMPLES:: + + sage: CBF(-2).rsqrt() + [-0.707106781186547 +/- 5.73e-16]*I + sage: CBF(0, 1/2).rsqrt() + 1.000000000000000 - 1.000000000000000*I + sage: CBF(0).rsqrt() + nan + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_rsqrt(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def cube(self): + """ + Return the cube of this ball. + + The result is computed efficiently using two real squarings, two real + multiplications, and scalar operations. + + EXAMPLES:: + + sage: CBF(1, 1).cube() + -2.000000000000000 + 2.000000000000000*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_cube(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def rising_factorial(self, n): + """ + Return the ``n``-th rising factorial of this ball. + + The `n`-th rising factorial of `x` is equal to `x (x+1) \cdots (x+n-1)`. + + EXAMPLES:: + + sage: CBF(1).rising_factorial(5) + 120.0000000000000 + sage: CBF(1/3, 1/2).rising_factorial(300) + [-3.87949484513701e+612 +/- 8.46e+597] + [-3.52042209762719e+612 +/- 7.70e+597]*I + + sage: CBF(1).rising_factorial(-1) + Traceback (most recent call last): + ... + ValueError: expected a nonnegative index + sage: CBF(1).rising_factorial(2**64) + Traceback (most recent call last): + ... + OverflowError: index too large + """ + cdef ComplexBall res = self._new() + cdef sage.rings.integer.Integer n_as_Integer = ZZ.coerce(n) + if mpz_fits_ulong_p(n_as_Integer.value): + if _do_sig(prec(self)): sig_on() + acb_rising_ui(res.value, self.value, mpz_get_ui(n_as_Integer.value), prec(self)) + if _do_sig(prec(self)): sig_off() + return res + elif n_as_Integer < 0: + raise ValueError("expected a nonnegative index") + else: + raise OverflowError("index too large") + + # Elementary functions + + def log(self, base=None): + """ + General logarithm (principal branch). + + INPUT: + + - ``base`` (optional, complex ball or number) -- if ``None``, return + the principal branch of the natural logarithm ``ln(self)``, + otherwise, return the general logarithm ``ln(self)/ln(base)`` + + EXAMPLES:: + + sage: CBF(2*i).log() + [0.6931471805599453 +/- 4.16e-17] + [1.570796326794897 +/- 6.65e-16]*I + sage: CBF(-1).log() + [3.141592653589793 +/- 5.61e-16]*I + + sage: CBF(2*i).log(2) + [1.000000000000000 +/- 8.01e-17] + [2.26618007091360 +/- 4.23e-15]*I + sage: CBF(2*i).log(CBF(i)) + [1.000000000000000 +/- 2.83e-16] + [-0.441271200305303 +/- 2.82e-16]*I + + sage: CBF('inf').log() + nan + nan*I + sage: CBF(2).log(0) + nan + nan*I + """ + cdef ComplexBall cst + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_log(res.value, self.value, prec(self)) + if base is not None: + cst = self._parent.coerce(base).log() + if _do_sig(prec(self)): sig_on() + acb_div(res.value, res.value, cst.value, prec(self)) + if _do_sig(prec(self)): sig_off() + if _do_sig(prec(self)): sig_off() + return res + + def log1p(self): + """ + Return ``log(1 + self)``, computed accurately when ``self`` is close to + zero. + + EXAMPLES:: + + sage: eps = RBF(1e-50) + sage: CBF(1+eps, eps).log() + [+/- 2.23e-16] + [1.000000000000000e-50 +/- 2.30e-66]*I + sage: CBF(eps, eps).log1p() + [1.000000000000000e-50 +/- 7.63e-68] + [1.00000000000000e-50 +/- 2.30e-66]*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_log1p(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def exp(self): + """ + Return the exponential of this ball. + + .. SEEALSO:: :meth:`exppii` + + EXAMPLES:: + + sage: CBF(i*pi).exp() + [-1.00000000000000 +/- 6.67e-16] + [+/- 5.68e-16]*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_exp(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def exppii(self): + """ + Return ``exp(pi*i*self)``. + + EXAMPLES:: + + sage: CBF(1/2).exppii() + 1.000000000000000*I + sage: CBF(0, -1/pi).exppii() + [2.71828182845904 +/- 6.05e-15] + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_exp_pi_i(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def sin(self): + """ + Return the sine of this ball. + + EXAMPLES:: + + sage: CBF(i*pi).sin() + [11.5487393572577 +/- 5.34e-14]*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_sin(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def cos(self): + """ + Return the cosine of this ball. + + EXAMPLES:: + + sage: CBF(i*pi).cos() + [11.59195327552152 +/- 8.38e-15] + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_cos(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def tan(self): + """ + Return the tangent of this ball. + + EXAMPLES:: + + sage: CBF(pi/2, 1/10).tan() + [+/- 4.00e-14] + [10.0333111322540 +/- 3.60e-14]*I + sage: CBF(pi/2).tan() + [+/- inf] + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_tan(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def cot(self): + """ + Return the cotangent of this ball. + + EXAMPLES:: + + sage: CBF(pi, 1/10).cot() + [+/- 5.74e-14] + [-10.0333111322540 +/- 4.05e-14]*I + sage: CBF(pi).cot() + [+/- inf] + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_cot(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def arctan(self): + """ + Return the arctangent of this ball. + + EXAMPLES:: + + sage: CBF(1+i).arctan() + [1.017221967897851 +/- 4.93e-16] + [0.4023594781085251 +/- 8.52e-17]*I + sage: CBF(i).arctan() + nan + nan*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_atan(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + # Special functions + + def gamma(self, z=None): + """ + Return the image of this ball by the Euler Gamma function (if + ``z = None``) or the incomplete Gamma function (otherwise). + + EXAMPLES:: + + sage: CBF(1, 1).gamma() + [0.49801566811836 +/- 4.98e-15] + [-0.154949828301811 +/- 7.67e-16]*I + sage: CBF(-1).gamma() + nan + sage: CBF(1, 1).gamma(0) + [0.49801566811836 +/- 4.98e-15] + [-0.154949828301811 +/- 7.67e-16]*I + sage: CBF(1, 1).gamma(100) + [-3.6143867454139e-45 +/- 7.26e-59] + [-3.7022961377791e-44 +/- 4.71e-58]*I + sage: CBF(1, 1).gamma(CLF(i)) + [0.32886684193500 +/- 5.49e-15] + [-0.18974945045621 +/- 1.49e-15]*I + """ + cdef ComplexBall my_z + cdef ComplexBall res = self._new() + if z is None: + if _do_sig(prec(self)): sig_on() + acb_gamma(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + else: + my_z = self._parent.coerce(z) + if _do_sig(prec(self)): sig_on() + acb_hypgeom_gamma_upper(res.value, self.value, my_z.value, 0, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def log_gamma(self): + r""" + Return the image of this ball by the logarithmic Gamma function. + + The branch cut of the logarithmic gamma function is placed on the + negative half-axis, which means that + ``log_gamma(z) + log z = log_gamma(z+1)`` holds for all `z`, + whereas ``log_gamma(z) != log(gamma(z))`` in general. + + EXAMPLES:: + + sage: CBF(1000, 1000).log_gamma() + [5466.22252162990 +/- 3.05e-12] + [7039.33429191119 +/- 3.81e-12]*I + sage: CBF(-1/2).log_gamma() + [1.265512123484645 +/- 8.82e-16] + [-3.141592653589793 +/- 5.68e-16]*I + sage: CBF(-1).log_gamma() + nan + [-3.141592653589793 +/- 5.68e-16]*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_lgamma(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def psi(self): + """ + Compute the digamma function with argument ``self``. + + EXAMPLES:: + + sage: CBF(1, 1).psi() + [0.0946503206224770 +/- 7.34e-17] + [1.076674047468581 +/- 2.63e-16]*I + sage: CBF(-1).psi() + nan + """ + + cdef ComplexBall result = self._new() + if _do_sig(prec(self)): sig_on() + acb_digamma(result.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return result + + def zeta(self, a=None): + """ + Return the image of this ball by the Hurwitz zeta function. + + For ``a = None``, this computes the Riemann zeta function. + + EXAMPLES:: + + sage: CBF(1, 1).zeta() + [0.5821580597520036 +/- 5.27e-17] + [-0.9268485643308071 +/- 2.81e-17]*I + sage: CBF(1, 1).zeta(1) + [0.5821580597520036 +/- 5.27e-17] + [-0.9268485643308071 +/- 2.81e-17]*I + sage: CBF(1, 1).zeta(1/2) + [1.497919876084167 +/- 2.91e-16] + [0.2448655353684164 +/- 4.22e-17]*I + sage: CBF(1, 1).zeta(CBF(1, 1)) + [-0.3593983122202835 +/- 3.01e-17] + [-2.875283329756940 +/- 4.52e-16]*I + sage: CBF(1, 1).zeta(-1) + nan + nan*I + """ + cdef ComplexBall a_ball + cdef ComplexBall res = self._new() + if a is None: + if _do_sig(prec(self)): sig_on() + acb_zeta(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + else: + a_ball = self._parent.coerce(a) + if _do_sig(prec(self)): sig_on() + acb_hurwitz_zeta(res.value, self.value, a_ball.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def polylog(self, s): + """ + Return the polylogarithm `\operatorname{Li}_s(\mathrm{self})`. + + EXAMPLES:: + + sage: CBF(2).polylog(1) + [+/- 4.65e-15] + [-3.14159265358979 +/- 8.15e-15]*I + sage: CBF(1, 1).polylog(CBF(1, 1)) + [0.3708160030469 +/- 2.38e-14] + [2.7238016577979 +/- 4.22e-14]*I + + TESTS:: + + sage: CBF(2).polylog(1r) + [+/- 4.65e-15] + [-3.14159265358979 +/- 8.15e-15]*I + """ + cdef ComplexBall s_as_ball + cdef sage.rings.integer.Integer s_as_Integer + cdef ComplexBall res = self._new() + try: + s_as_Integer = ZZ.coerce(s) + if mpz_fits_slong_p(s_as_Integer.value): + if _do_sig(prec(self)): sig_on() + acb_polylog_si(res.value, mpz_get_si(s_as_Integer.value), self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + except TypeError: + pass + s_as_ball = self._parent.coerce(s) + if _do_sig(prec(self)): sig_on() + acb_polylog(res.value, s_as_ball.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def agm1(self): + """ + Return the arithmetic-geometric mean of 1 and ``self``. + + The arithmetic-geometric mean is defined such that the function is + continuous in the complex plane except for a branch cut along the + negative half axis (where it is continuous from above). This + corresponds to always choosing an "optimal" branch for the square root + in the arithmetic-geometric mean iteration. + + EXAMPLES:: + + sage: CBF(0, -1).agm1() + [0.5990701173678 +/- 1.14e-14] + [-0.5990701173678 +/- 1.22e-14]*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_agm1(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def hypergeometric(self, a, b): + r""" + Return the generalized hypergeometric function of ``self``. + + INPUT: + + - ``a`` -- upper parameters, list of complex numbers that coerce into + this ball's parent; + + - ``b`` -- lower parameters, list of complex numbers that coerce into + this ball's parent. + + OUTPUT: + + The generalized hypergeometric function defined by + + .. math:: + + {}_pF_q(a_1,\ldots,a_p;b_1,\ldots,b_q;z) + = \sum_{k=0}^\infty \frac{(a_1)_k\dots(a_p)_k}{(b_1)_k\dots(b_q)_k} \frac {z^k} {k!} + + extended using analytic continuation or regularization when the sum + does not converge. + + EXAMPLES:: + + sage: CBF(1, pi/2).hypergeometric([], []) + [+/- 3.57e-15] + [2.7182818284590 +/- 5.37e-14]*I + + sage: CBF(1, pi).hypergeometric([1/4], [1/4]) + [-2.7182818284590 +/- 8.63e-14] + [+/- 3.69e-14]*I + + sage: CBF(1000, 1000).hypergeometric([100], [AA(sqrt(2))]) + [1.2796735556e+590 +/- 4.04e+579] + [-9.3233349199e+590 +/- 3.30e+579]*I + + sage: CBF(0, 1).hypergeometric([], [1/2, 1/3, 1/4]) + [-3.7991962344383 +/- 4.98e-14] + [23.8780971778049 +/- 5.40e-14]*I + + sage: CBF(0).hypergeometric([1], []) + 1.000000000000000 + sage: CBF(1, 1).hypergeometric([1], []) + [+/- inf] + [+/- inf]*I + + TESTS:: + + sage: CBF(0, 1).hypergeometric([QQbar(sqrt(2)), RLF(pi)], [1r, 1/2]) + [-8.7029449215408 +/- 6.89e-14] + [-0.8499070546106 +/- 4.98e-14]*I + """ + cdef ComplexBall tmp, my_a, my_b + cdef ComplexBall res = self._new() + cdef long p = len(a) + cdef long q = len(b) + if p == q == 1: + my_a = self._parent.coerce(a[0]) + my_b = self._parent.coerce(b[0]) + if _do_sig(prec(self)): sig_on() + acb_hypgeom_m(res.value, my_a.value, my_b.value, self.value, 0, + prec(self)) + if _do_sig(prec(self)): sig_off() + return res + cdef long i1 = -1 + cdef long s + try: + i1 = a.index(1) + s = 1 + except ValueError: + s = 0 + cdef acb_ptr vec_a = _acb_vec_init(p - s) + cdef acb_ptr vec_b = _acb_vec_init(q + 1 - s) + cdef long j = 0 + for i in xrange(p): + if i != i1: + tmp = self._parent.coerce(a[i]) + acb_set(&(vec_a[j]), tmp.value) + j += 1 + for i in range(q): + tmp = self._parent.coerce(b[i]) + acb_set(&(vec_b[i]), tmp.value) + if s == 0: + acb_one(&(vec_b[q])) + if _do_sig(prec(self)): sig_on() + acb_hypgeom_pfq_direct(res.value, vec_a, p - s, vec_b, q + 1 - s, + self.value, -1, prec(self)) + if _do_sig(prec(self)): sig_off() + _acb_vec_clear(vec_b, q + 1 - s) + _acb_vec_clear(vec_a, p - s) + return res + + def hypergeometric_U(self, a, b): + """ + Return the Tricomi confluent hypergeometric function U(a, b, self) of + this ball. + + EXAMPLES:: + + sage: CBF(1000, 1000).hypergeometric_U(RLF(pi), -100) + [-7.261605907166e-11 +/- 4.89e-24] + [-7.928136216391e-11 +/- 5.36e-24]*I + sage: CBF(1000, 1000).hypergeometric_U(0, -100) + 1.000000000000000 + """ + cdef ComplexBall res = self._new() + cdef ComplexBall my_a = self._parent.coerce(a) + cdef ComplexBall my_b = self._parent.coerce(b) + if _do_sig(prec(self)): sig_on() + acb_hypgeom_u(res.value, my_a.value, my_b.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def erf(self): + """ + Return the error function with argument ``self``. + + EXAMPLES:: + + sage: CBF(1, 1).erf() + [1.31615128169795 +/- 8.80e-15] + [0.19045346923783 +/- 9.19e-15]*I + """ + + cdef ComplexBall result = self._new() + if _do_sig(prec(self)): sig_on() + acb_hypgeom_erf(result.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return result + + def erfc(self): + """ + Compute the complementary error function with argument ``self``. + + EXAMPLES:: + + sage: CBF(20).erfc() + [5.3958656116079e-176 +/- 1.08e-190] + sage: CBF(100, 100).erfc() + [0.00065234366376858 +/- 6.52e-18] + [-0.00393572636292141 +/- 5.16e-18]*I + """ + + cdef ComplexBall result = self._new() + if _do_sig(prec(self)): sig_on() + acb_hypgeom_erfc(result.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return result + + def bessel_J(self, nu): + """ + Return the Bessel function of the first kind with argument ``self`` + and index ``nu``. + + EXAMPLES:: + + sage: CBF(1, 1).bessel_J(1) + [0.614160334922903 +/- 8.48e-16] + [0.365028028827088 +/- 6.62e-16]*I + sage: CBF(100, -100).bessel_J(1/3) + [1.108431870251e+41 +/- 5.53e+28] + [-8.952577603125e+41 +/- 2.91e+28]*I + """ + cdef ComplexBall result = self._new() + cdef ComplexBall my_nu = self._parent.coerce(nu) + if _do_sig(prec(self)): sig_on() + acb_hypgeom_bessel_j(result.value, my_nu.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return result + + def bessel_K(self, nu): + """ + Return the modified Bessel function of the second kind with argument + ``self`` and index ``nu``. + + EXAMPLES:: + + sage: CBF(1, 1).bessel_K(0) + [0.08019772694652 +/- 3.19e-15] + [-0.35727745928533 +/- 1.08e-15]*I + sage: CBF(1, 1).bessel_K(1) + [0.02456830552374 +/- 6.22e-15] + [-0.45971947380119 +/- 6.74e-15]*I + sage: CBF(100, 100).bessel_K(QQbar(i)) + [3.8693896656383e-45 +/- 2.38e-59] + [5.5071004234177e-46 +/- 5.86e-60]*I + """ + cdef ComplexBall result = self._new() + cdef ComplexBall my_nu = self._parent.coerce(nu) + if _do_sig(prec(self)): sig_on() + acb_hypgeom_bessel_k(result.value, my_nu.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return result + + def exp_integral_e(self, s): + """ + Return the image of this ball by the generalized exponential integral + with index ``s``. + + EXAMPLES:: + + sage: CBF(1+i).exp_integral_e(1) + [0.00028162445198 +/- 2.78e-15] + [-0.17932453503936 +/- 2.56e-15]*I + sage: CBF(1+i).exp_integral_e(QQbar(i)) + [-0.10396361883964 +/- 4.92e-15] + [-0.16268401277783 +/- 4.78e-15]*I + """ + cdef ComplexBall res = self._new() + cdef ComplexBall my_s = self._parent.coerce(s) + if _do_sig(prec(self)): sig_on() + acb_hypgeom_expint(res.value, my_s.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def ei(self): + """ + Return the exponential integral with argument ``self``. + + EXAMPLES:: + + sage: CBF(1, 1).ei() + [1.76462598556385 +/- 6.65e-15] + [2.38776985151052 +/- 4.34e-15]*I + sage: CBF(0).ei() + nan + """ + cdef ComplexBall result = self._new() + if _do_sig(prec(self)): sig_on() + acb_hypgeom_ei(result.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return result + + def si(self): + """ + Return the sine integral with argument ``self``. + + EXAMPLES:: + + sage: CBF(1, 1).si() + [1.10422265823558 +/- 2.16e-15] + [0.88245380500792 +/- 3.15e-15]*I + sage: CBF(0).si() + 0 + """ + cdef ComplexBall result = self._new() + if _do_sig(prec(self)): sig_on() + acb_hypgeom_si(result.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return result + + def ci(self): + """ + Return the cosine integral with argument ``self``. + + EXAMPLES:: + + sage: CBF(1, 1).ci() + [0.882172180555936 +/- 4.85e-16] + [0.287249133519956 +/- 3.47e-16]*I + sage: CBF(0).ci() + nan + nan*I + """ + cdef ComplexBall result = self._new() + if _do_sig(prec(self)): sig_on() + acb_hypgeom_ci(result.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return result + + def shi(self): + """ + Return the hyperbolic sine integral with argument ``self``. + + EXAMPLES:: + + sage: CBF(1, 1).shi() + [0.88245380500792 +/- 3.15e-15] + [1.10422265823558 +/- 2.16e-15]*I + sage: CBF(0).shi() + 0 + """ + + cdef ComplexBall result = self._new() + if _do_sig(prec(self)): sig_on() + acb_hypgeom_shi(result.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return result + + def chi(self): + """ + Return the hyperbolic cosine integral with argument ``self``. + + EXAMPLES:: + + sage: CBF(1, 1).chi() + [0.882172180555936 +/- 4.85e-16] + [1.28354719327494 +/- 1.05e-15]*I + sage: CBF(0).chi() + nan + nan*I + """ + cdef ComplexBall result = self._new() + if _do_sig(prec(self)): sig_on() + acb_hypgeom_chi(result.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return result + + def li(self, bint offset=False): + """ + Return the logarithmic integral with argument ``self``. + + If ``offset`` is True, return the offset logarithmic integral. + + EXAMPLES:: + + sage: CBF(1, 1).li() + [0.61391166922119 +/- 7.03e-15] + [2.05958421419258 +/- 8.25e-15]*I + sage: CBF(0).li() + 0 + sage: CBF(0).li(offset=True) + [-1.045163780117493 +/- 5.54e-16] + sage: li(0).n() + 0.000000000000000 + sage: Li(0).n() + -1.04516378011749 + """ + cdef ComplexBall result = self._new() + if _do_sig(prec(self)): sig_on() + acb_hypgeom_li(result.value, self.value, offset, prec(self)) + if _do_sig(prec(self)): sig_off() + return result + CBF = ComplexBallField() diff --git a/src/sage/rings/complex_interval_field.py b/src/sage/rings/complex_interval_field.py index e147a410be5..5dea64c6f43 100644 --- a/src/sage/rings/complex_interval_field.py +++ b/src/sage/rings/complex_interval_field.py @@ -216,6 +216,39 @@ def __reduce__(self): """ return ComplexIntervalField, (self._prec, ) + def construction(self): + """ + Returns the functorial construction of this complex interval field, + namely as the algebraic closure of the real interval field with + the same precision. + + EXAMPLES:: + + sage: c, S = CIF.construction(); c, S + (AlgebraicClosureFunctor, + Real Interval Field with 53 bits of precision) + sage: CIF == c(S) + True + + TESTS: + + Test that :trac:`19922` is fixed:: + + sage: c = ComplexIntervalField(128).an_element() + sage: r = RealIntervalField(64).an_element() + sage: c + r + 1 + 1*I + sage: r + c + 1 + 1*I + sage: parent(c+r) + Complex Interval Field with 64 bits of precision + sage: R = ComplexIntervalField(128)['x'] + sage: (R.gen() * RIF.one()).parent() + Univariate Polynomial Ring in x over Complex Interval Field with 53 bits of precision + """ + from sage.categories.pushout import AlgebraicClosureFunctor + return (AlgebraicClosureFunctor(), self._real_field()) + def is_exact(self): """ The complex interval field is not exact. diff --git a/src/sage/rings/field.py b/src/sage/rings/field.py index 9dda311dfb9..31bdd7f0378 100644 --- a/src/sage/rings/field.py +++ b/src/sage/rings/field.py @@ -47,7 +47,7 @@ def is_PrimeField(R): sage: sage.rings.field.is_PrimeField(GF(7^2,'t')) False """ - from finite_rings.constructor import is_FiniteField + from finite_rings.finite_field_constructor import is_FiniteField from rational_field import is_RationalField if is_RationalField(R): diff --git a/src/sage/rings/finite_rings/all.py b/src/sage/rings/finite_rings/all.py index df3a9d2b69c..cee946d1518 100644 --- a/src/sage/rings/finite_rings/all.py +++ b/src/sage/rings/finite_rings/all.py @@ -18,6 +18,6 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from constructor import FiniteField +from finite_field_constructor import FiniteField from conway_polynomials import conway_polynomial, exists_conway_polynomial GF = FiniteField diff --git a/src/sage/rings/finite_rings/constructor.py b/src/sage/rings/finite_rings/constructor.py index 564facec580..ed71084b2fc 100644 --- a/src/sage/rings/finite_rings/constructor.py +++ b/src/sage/rings/finite_rings/constructor.py @@ -1,668 +1,4 @@ -r""" -Finite Fields +from sage.misc.superseded import deprecation +deprecation(19941,"This module has been renamed to sage.rings.finite_rings.finite_field_constructor") -Sage supports arithmetic in finite prime and extension fields. -Several implementation for prime fields are implemented natively in -Sage for several sizes of primes `p`. These implementations -are - - -- ``sage.rings.finite_rings.integer_mod.IntegerMod_int``, - -- ``sage.rings.finite_rings.integer_mod.IntegerMod_int64``, and - -- ``sage.rings.finite_rings.integer_mod.IntegerMod_gmp``. - - -Small extension fields of cardinality `< 2^{16}` are -implemented using tables of Zech logs via the Givaro C++ library -(``sage.rings.finite_rings.finite_field_givaro.FiniteField_givaro``). -While this representation is very fast it is limited to finite -fields of small cardinality. Larger finite extension fields of -order `q >= 2^{16}` are internally represented as -polynomials over smaller finite prime fields. If the -characteristic of such a field is 2 then NTL is used internally to -represent the field -(``sage.rings.finite_rings.finite_field_ntl_gf2e.FiniteField_ntl_gf2e``). -In all other case the PARI C library is used -(``sage.rings.finite_rings.finite_field_pari_ffelt.FiniteField_pari_ffelt``). - -However, this distinction is internal only and the user usually -does not have to worry about it because consistency across all -implementations is aimed for. In all extension field -implementations the user may either specify a minimal polynomial or -leave the choice to Sage. - -For small finite fields the default choice are Conway polynomials. - -The Conway polynomial `C_n` is the lexicographically first -monic irreducible, primitive polynomial of degree `n` over -`GF(p)` with the property that for a root `\alpha` -of `C_n` we have that -`\beta= -\alpha^{(p^n - 1)/(p^m - 1)}` is a root of -`C_m` for all `m` dividing `n`. Sage -contains a database of Conway polynomials which also can be queried -independently of finite field construction. - -While Sage supports basic arithmetic in finite fields some more -advanced features for computing with finite fields are still not -implemented. For instance, Sage does not calculate embeddings of -finite fields yet. - -EXAMPLES:: - - sage: k = GF(5); type(k) - - -:: - - sage: k = GF(5^2,'c'); type(k) - - -:: - - sage: k = GF(2^16,'c'); type(k) - - -:: - - sage: k = GF(3^16,'c'); type(k) - - -Finite Fields support iteration, starting with 0. - -:: - - sage: k = GF(9, 'a') - sage: for i,x in enumerate(k): print i,x - 0 0 - 1 a - 2 a + 1 - 3 2*a + 1 - 4 2 - 5 2*a - 6 2*a + 2 - 7 a + 2 - 8 1 - sage: for a in GF(5): - ... print a - 0 - 1 - 2 - 3 - 4 - -We output the base rings of several finite fields. - -:: - - sage: k = GF(3); type(k) - - sage: k.base_ring() - Finite Field of size 3 - -:: - - sage: k = GF(9,'alpha'); type(k) - - sage: k.base_ring() - Finite Field of size 3 - -:: - - sage: k = GF(3^40,'b'); type(k) - - sage: k.base_ring() - Finite Field of size 3 - -Further examples:: - - sage: GF(2).is_field() - True - sage: GF(next_prime(10^20)).is_field() - True - sage: GF(19^20,'a').is_field() - True - sage: GF(8,'a').is_field() - True - -AUTHORS: - -- William Stein: initial version - -- Robert Bradshaw: prime field implementation - -- Martin Albrecht: Givaro and ntl.GF2E implementations -""" - -#***************************************************************************** -# Copyright (C) 2006 William Stein -# -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# The full text of the GPL is available at: -# -# http://www.gnu.org/licenses/ -#***************************************************************************** - -import random - -from sage.rings.finite_rings.finite_field_base import is_FiniteField -from sage.structure.category_object import normalize_names - -from sage.rings.integer import Integer - -import sage.rings.polynomial.polynomial_element as polynomial_element -import sage.rings.polynomial.multi_polynomial_element as multi_polynomial_element -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - -# We don't late import this because this means trouble with the Givaro library -# On a Macbook Pro OSX 10.5.8, this manifests as a Bus Error on exiting Sage. -# TODO: figure out why -from finite_field_givaro import FiniteField_givaro - -import sage.interfaces.gap - -from sage.structure.factory import UniqueFactory - -class FiniteFieldFactory(UniqueFactory): - """ - Return the globally unique finite field of given order with - generator labeled by the given name and possibly with given - modulus. - - INPUT: - - - ``order`` -- a prime power - - - ``name`` -- string; must be specified unless ``order`` is prime. - - - ``modulus`` -- (optional) either a defining polynomial for the - field, or a string specifying an algorithm to use to generate - such a polynomial. If ``modulus`` is a string, it is passed to - :meth:`~sage.rings.polynomial.irreducible_element()` as the - parameter ``algorithm``; see there for the permissible values of - this parameter. In particular, you can specify - ``modulus="primitive"`` to get a primitive polynomial. - - - ``impl`` -- (optional) a string specifying the implementation of - the finite field. Possible values are: - - - ``'modn'`` -- ring of integers modulo `p` (only for prime - fields). - - - ``'givaro'`` -- Givaro, which uses Zech logs (only for fields - of at most 65521 elements). - - - ``'ntl'`` -- NTL using GF2X (only in characteristic 2). - - - ``'pari_ffelt'`` -- PARI's ``FFELT`` type (only for extension - fields). - - - ``'pari_mod'`` -- Older PARI implementation using ``POLMOD``s - (slower than ``'pari_ffelt'``, only for extension fields). - - - ``elem_cache`` -- cache all elements to avoid creation time - (default: order < 500) - - - ``check_irreducible`` -- verify that the polynomial modulus is - irreducible - - - ``proof`` -- bool (default: ``True``): if ``True``, use provable - primality test; otherwise only use pseudoprimality test. - - - ``args`` -- additional parameters passed to finite field - implementations - - - ``kwds`` -- additional keyword parameters passed to finite field - implementations - - ALIAS: You can also use ``GF`` instead of ``FiniteField`` -- they - are identical. - - EXAMPLES:: - - sage: k. = FiniteField(9); k - Finite Field in a of size 3^2 - sage: parent(a) - Finite Field in a of size 3^2 - sage: charpoly(a, 'y') - y^2 + 2*y + 2 - - We illustrate the proof flag. The following example would hang - for a very long time if we didn't use ``proof=False``. - - .. NOTE:: - - Magma only supports ``proof=False`` for making finite fields, - so falsely appears to be faster than Sage -- see :trac:`10975`. - - :: - - sage: k = FiniteField(10^1000 + 453, proof=False) - sage: k = FiniteField((10^1000 + 453)^2, 'a', proof=False) # long time -- about 5 seconds - - :: - - sage: F. = GF(5)[] - sage: K. = GF(5**5, name='a', modulus=x^5 - x +1 ) - sage: f = K.modulus(); f - x^5 + 4*x + 1 - sage: type(f) - - - By default, the given generator is not guaranteed to be primitive - (a generator of the multiplicative group), use - ``modulus="primitive"`` if you need this:: - - sage: K. = GF(5^40) - sage: a.multiplicative_order() - 4547473508864641189575195312 - sage: a.is_square() - True - sage: K. = GF(5^40, modulus="primitive") - sage: b.multiplicative_order() - 9094947017729282379150390624 - - The modulus must be irreducible:: - - sage: K. = GF(5**5, name='a', modulus=x^5 - x) - Traceback (most recent call last): - ... - ValueError: finite field modulus must be irreducible but it is not - - You can't accidentally fool the constructor into thinking the - modulus is irreducible when it is not, since it actually tests - irreducibility modulo `p`. Also, the modulus has to be of the - right degree (this is always checked):: - - sage: F. = QQ[] - sage: factor(x^5 + 2) - x^5 + 2 - sage: K. = GF(5^5, modulus=x^5 + 2) - Traceback (most recent call last): - ... - ValueError: finite field modulus must be irreducible but it is not - sage: K. = GF(5^5, modulus=x^3 + 3*x + 3, check_irreducible=False) - Traceback (most recent call last): - ... - ValueError: the degree of the modulus does not equal the degree of the field - - Any type which can be converted to the polynomial ring `GF(p)[x]` - is accepted as modulus:: - - sage: K. = GF(13^3, modulus=[1,0,0,2]) - sage: K. = GF(13^10, modulus=pari("ffinit(13,10)")) - sage: var('x') - x - sage: K. = GF(13^2, modulus=x^2 - 2) - sage: K. = GF(13^2, modulus=sin(x)) - Traceback (most recent call last): - ... - TypeError: unable to convert sin(x) to an integer - - If you wish to live dangerously, you can tell the constructor not - to test irreducibility using ``check_irreducible=False``, but this - can easily lead to crashes and hangs -- so do not do it unless you - know that the modulus really is irreducible! - - :: - - sage: K. = GF(5**2, name='a', modulus=x^2 + 2, check_irreducible=False) - - Even for prime fields, you can specify a modulus. This will not - change how Sage computes in this field, but it will change the - result of the :meth:`modulus` and :meth:`gen` methods:: - - sage: k. = GF(5, modulus="primitive") - sage: k.modulus() - x + 3 - sage: a - 2 - - The order of a finite field must be a prime power:: - - sage: GF(1) - Traceback (most recent call last): - ... - ValueError: the order of a finite field must be at least 2 - sage: GF(100) - Traceback (most recent call last): - ... - ValueError: the order of a finite field must be a prime power - - Finite fields with explicit random modulus are not cached:: - - sage: k. = GF(5**10, modulus='random') - sage: n. = GF(5**10, modulus='random') - sage: n is k - False - sage: GF(5**10, 'a') is GF(5**10, 'a') - True - - We check that various ways of creating the same finite field yield - the same object, which is cached:: - - sage: K = GF(7, 'a') - sage: L = GF(7, 'b') - sage: K is L # name is ignored for prime fields - True - sage: K is GF(7, modulus=K.modulus()) - True - sage: K = GF(4,'a'); K.modulus() - x^2 + x + 1 - sage: L = GF(4,'a', K.modulus()) - sage: K is L - True - sage: M = GF(4,'a', K.modulus().change_variable_name('y')) - sage: K is M - True - - You may print finite field elements as integers. This currently - only works if the order of field is `<2^{16}`, though:: - - sage: k. = GF(2^8, repr='int') - sage: a - 2 - - The following demonstrate coercions for finite fields using Conway - polynomials:: - - sage: k = GF(5^2, conway=True, prefix='z'); a = k.gen() - sage: l = GF(5^5, conway=True, prefix='z'); b = l.gen() - sage: a + b - 3*z10^5 + z10^4 + z10^2 + 3*z10 + 1 - - Note that embeddings are compatible in lattices of such finite - fields:: - - sage: m = GF(5^3, conway=True, prefix='z'); c = m.gen() - sage: (a+b)+c == a+(b+c) - True - sage: (a*b)*c == a*(b*c) - True - sage: from sage.categories.pushout import pushout - sage: n = pushout(k, l) - sage: o = pushout(l, m) - sage: q = pushout(n, o) - sage: q(o(b)) == q(n(b)) - True - - Another check that embeddings are defined properly:: - - sage: k = GF(3**10, conway=True, prefix='z') - sage: l = GF(3**20, conway=True, prefix='z') - sage: l(k.gen()**10) == l(k.gen())**10 - True - - Check that :trac:`16934` has been fixed:: - - sage: k1. = GF(17^14, impl="pari_ffelt") - sage: _ = a/2 - sage: k2. = GF(17^14, impl="pari_ffelt") - sage: k1 is k2 - True - - """ - def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, - impl=None, proof=None, check_irreducible=True, **kwds): - """ - EXAMPLES:: - - sage: GF.create_key_and_extra_args(9, 'a') - ((9, ('a',), x^2 + 2*x + 2, 'givaro', '{}', 3, 2, True), {}) - sage: GF.create_key_and_extra_args(9, 'a', foo='value') - ((9, ('a',), x^2 + 2*x + 2, 'givaro', "{'foo': 'value'}", 3, 2, True), {'foo': 'value'}) - """ - import sage.arith.all - from sage.structure.proof.all import WithProof, arithmetic - if proof is None: - proof = arithmetic() - with WithProof('arithmetic', proof): - order = Integer(order) - if order <= 1: - raise ValueError("the order of a finite field must be at least 2") - - if order.is_prime(): - p = order - n = Integer(1) - if impl is None: - impl = 'modn' - name = ('x',) # Ignore name - # Every polynomial of degree 1 is irreducible - check_irreducible = False - elif order.is_prime_power(): - if names is not None: - name = names - if name is not None: - name = normalize_names(1, name) - - p, n = order.factor()[0] - - # The following is a temporary solution that allows us - # to construct compatible systems of finite fields - # until algebraic closures of finite fields are - # implemented in Sage. It requires the user to - # specify two parameters: - # - # - `conway` -- boolean; if True, this field is - # constructed to fit in a compatible system using - # a Conway polynomial. - # - `prefix` -- a string used to generate names for - # automatically constructed finite fields - # - # See the docstring of FiniteFieldFactory for examples. - # - # Once algebraic closures of finite fields are - # implemented, this syntax should be superseded by - # something like the following: - # - # sage: Fpbar = GF(5).algebraic_closure('z') - # sage: F, e = Fpbar.subfield(3) # e is the embedding into Fpbar - # sage: F - # Finite field in z3 of size 5^3 - # - # This temporary solution only uses actual Conway - # polynomials (no pseudo-Conway polynomials), since - # pseudo-Conway polynomials are not unique, and until - # we have algebraic closures of finite fields, there - # is no good place to store a specific choice of - # pseudo-Conway polynomials. - if name is None: - if not ('conway' in kwds and kwds['conway']): - raise ValueError("parameter 'conway' is required if no name given") - if 'prefix' not in kwds: - raise ValueError("parameter 'prefix' is required if no name given") - name = kwds['prefix'] + str(n) - - if 'conway' in kwds and kwds['conway']: - from conway_polynomials import conway_polynomial - if 'prefix' not in kwds: - raise ValueError("a prefix must be specified if conway=True") - if modulus is not None: - raise ValueError("no modulus may be specified if conway=True") - # The following raises a RuntimeError if no polynomial is found. - modulus = conway_polynomial(p, n) - - if impl is None: - if order < zech_log_bound: - impl = 'givaro' - elif p == 2: - impl = 'ntl' - else: - impl = 'pari_ffelt' - else: - raise ValueError("the order of a finite field must be a prime power") - - # Determine modulus. - # For the 'modn' implementation, we use the following - # optimization which we also need to avoid an infinite loop: - # a modulus of None is a shorthand for x-1. - if modulus is not None or impl != 'modn': - R = PolynomialRing(FiniteField(p), 'x') - if modulus is None: - modulus = R.irreducible_element(n) - if isinstance(modulus, str): - # A string specifies an algorithm to find a suitable modulus. - if modulus == "default": - from sage.misc.superseded import deprecation - deprecation(16983, "the modulus 'default' is deprecated, use modulus=None instead (which is the default)") - modulus = None - modulus = R.irreducible_element(n, algorithm=modulus) - else: - if sage.rings.polynomial.polynomial_element.is_Polynomial(modulus): - modulus = modulus.change_variable_name('x') - modulus = R(modulus).monic() - - if modulus.degree() != n: - raise ValueError("the degree of the modulus does not equal the degree of the field") - if check_irreducible and not modulus.is_irreducible(): - raise ValueError("finite field modulus must be irreducible but it is not") - # If modulus is x - 1 for impl="modn", set it to None - if impl == 'modn' and modulus[0] == -1: - modulus = None - - return (order, name, modulus, impl, str(kwds), p, n, proof), kwds - - def create_object(self, version, key, **kwds): - """ - EXAMPLES:: - - sage: K = GF(19) # indirect doctest - sage: TestSuite(K).run() - - We try to create finite fields with various implementations:: - - sage: k = GF(2, impl='modn') - sage: k = GF(2, impl='givaro') - sage: k = GF(2, impl='ntl') - sage: k = GF(2, impl='pari_ffelt') - Traceback (most recent call last): - ... - ValueError: the degree must be at least 2 - sage: k = GF(2, impl='pari_mod') - Traceback (most recent call last): - ... - ValueError: The size of the finite field must not be prime. - sage: k = GF(2, impl='supercalifragilisticexpialidocious') - Traceback (most recent call last): - ... - ValueError: no such finite field implementation: 'supercalifragilisticexpialidocious' - sage: k. = GF(2^15, impl='modn') - Traceback (most recent call last): - ... - ValueError: the 'modn' implementation requires a prime order - sage: k. = GF(2^15, impl='givaro') - sage: k. = GF(2^15, impl='ntl') - sage: k. = GF(2^15, impl='pari_ffelt') - sage: k. = GF(2^15, impl='pari_mod') - sage: k. = GF(3^60, impl='modn') - Traceback (most recent call last): - ... - ValueError: the 'modn' implementation requires a prime order - sage: k. = GF(3^60, impl='givaro') - Traceback (most recent call last): - ... - ValueError: q must be < 2^16 - sage: k. = GF(3^60, impl='ntl') - Traceback (most recent call last): - ... - ValueError: q must be a 2-power - sage: k. = GF(3^60, impl='pari_ffelt') - sage: k. = GF(3^60, impl='pari_mod') - """ - # IMPORTANT! If you add a new class to the list of classes - # that get cached by this factor object, then you *must* add - # the following method to that class in order to fully support - # pickling: - # - # def __reduce__(self): # and include good doctests, please! - # return self._factory_data[0].reduce_data(self) - # - # This is not in the base class for finite fields, since some finite - # fields need not be created using this factory object, e.g., residue - # class fields. - - if len(key) == 5: - # for backward compatibility of pickles (see trac 10975). - order, name, modulus, impl, _ = key - p, n = Integer(order).factor()[0] - proof = True - else: - order, name, modulus, impl, _, p, n, proof = key - - if impl == 'modn': - if n != 1: - raise ValueError("the 'modn' implementation requires a prime order") - from finite_field_prime_modn import FiniteField_prime_modn - # Using a check option here is probably a worthwhile - # compromise since this constructor is simple and used a - # huge amount. - K = FiniteField_prime_modn(order, check=False, modulus=modulus) - else: - # We have to do this with block so that the finite field - # constructors below will use the proof flag that was - # passed in when checking for primality, factoring, etc. - # Otherwise, we would have to complicate all of their - # constructors with check options. - from sage.structure.proof.all import WithProof - with WithProof('arithmetic', proof): - if impl == 'givaro': - repr = kwds.get('repr', 'poly') - elem_cache = kwds.get('elem_cache', order < 500) - K = FiniteField_givaro(order, name, modulus, repr=repr, cache=elem_cache) - elif impl == 'ntl': - from finite_field_ntl_gf2e import FiniteField_ntl_gf2e - K = FiniteField_ntl_gf2e(order, name, modulus) - elif impl == 'pari_ffelt': - from finite_field_pari_ffelt import FiniteField_pari_ffelt - K = FiniteField_pari_ffelt(p, modulus, name) - elif (impl == 'pari_mod' - or impl == 'pari'): # for unpickling old pickles - # This implementation is deprecated, a warning will - # be given when this field is created. - # See http://trac.sagemath.org/ticket/17297 - from finite_field_ext_pari import FiniteField_ext_pari - K = FiniteField_ext_pari(order, name, modulus) - else: - raise ValueError("no such finite field implementation: %r" % impl) - - # Temporary; see create_key_and_extra_args() above. - if 'prefix' in kwds: - K._prefix = kwds['prefix'] - - return K - - -GF = FiniteField = FiniteFieldFactory("FiniteField") - - -def is_PrimeFiniteField(x): - """ - Returns True if x is a prime finite field. - - EXAMPLES:: - - sage: from sage.rings.finite_rings.constructor import is_PrimeFiniteField - sage: is_PrimeFiniteField(QQ) - False - sage: is_PrimeFiniteField(GF(7)) - True - sage: is_PrimeFiniteField(GF(7^2,'a')) - False - sage: is_PrimeFiniteField(GF(next_prime(10^90,proof=False))) - True - """ - from finite_field_prime_modn import FiniteField_prime_modn - from sage.rings.finite_rings.finite_field_base import FiniteField as FiniteField_generic - - return isinstance(x, FiniteField_prime_modn) or \ - (isinstance(x, FiniteField_generic) and x.degree() == 1) - -zech_log_bound = 2**16 +from finite_field_constructor import * diff --git a/src/sage/rings/finite_rings/conway_polynomials.py b/src/sage/rings/finite_rings/conway_polynomials.py index e9059d29524..03a58343135 100644 --- a/src/sage/rings/finite_rings/conway_polynomials.py +++ b/src/sage/rings/finite_rings/conway_polynomials.py @@ -11,7 +11,7 @@ """ from sage.misc.fast_methods import WithEqualityById from sage.structure.sage_object import SageObject -from sage.rings.finite_rings.constructor import FiniteField +from sage.rings.finite_rings.finite_field_constructor import FiniteField import sage.databases.conway def conway_polynomial(p, n): diff --git a/src/sage/rings/finite_rings/element_givaro.pyx b/src/sage/rings/finite_rings/element_givaro.pyx index 96690cbae0c..cec0e95ad95 100644 --- a/src/sage/rings/finite_rings/element_givaro.pyx +++ b/src/sage/rings/finite_rings/element_givaro.pyx @@ -63,15 +63,13 @@ from element_pari_ffelt import FiniteFieldElement_pari_ffelt from sage.structure.sage_object cimport SageObject import operator import sage.arith.all -import constructor as finite_field +import finite_field_constructor as finite_field import sage.interfaces.gap from sage.libs.pari.all import pari from sage.libs.pari.gen cimport gen from sage.structure.parent cimport Parent -from sage.structure.parent_base cimport ParentWithBase -from sage.structure.parent_gens cimport ParentWithGens from sage.misc.superseded import deprecated_function_alias @@ -112,7 +110,7 @@ cdef void late_import(): import sage.databases.conway ConwayPolynomials = sage.databases.conway.ConwayPolynomials - import sage.rings.finite_rings.constructor + import sage.rings.finite_rings.finite_field_constructor conway_polynomial = sage.rings.finite_rings.conway_polynomials.conway_polynomial import sage.rings.polynomial.multi_polynomial_element diff --git a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx index 19127de725a..9951f037cc7 100644 --- a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx +++ b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx @@ -29,8 +29,6 @@ from sage.structure.sage_object cimport SageObject from sage.structure.element cimport Element, ModuleElement, RingElement from sage.structure.parent cimport Parent -from sage.structure.parent_base cimport ParentWithBase -from sage.structure.parent_gens cimport ParentWithGens from sage.rings.ring cimport Ring @@ -114,8 +112,8 @@ cdef int late_import() except -1: import sage.modules.free_module_element FreeModuleElement = sage.modules.free_module_element.FreeModuleElement - import sage.rings.finite_rings.constructor - GF = sage.rings.finite_rings.constructor.FiniteField + import sage.rings.finite_rings.finite_field_constructor + GF = sage.rings.finite_rings.finite_field_constructor.FiniteField GF2 = GF(2) GF2_0 = GF2(0) GF2_1 = GF2(1) diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index fb2a990a9f5..12c4a7cdc43 100644 --- a/src/sage/rings/finite_rings/finite_field_base.pyx +++ b/src/sage/rings/finite_rings/finite_field_base.pyx @@ -862,7 +862,7 @@ cdef class FiniteField(Field): pass from sage.rings.all import PolynomialRing - from constructor import GF + from finite_field_constructor import GF R = PolynomialRing(GF(self.characteristic()), 'x') self._modulus = R((-1,1)) # Polynomial x - 1 return self._modulus @@ -979,7 +979,7 @@ cdef class FiniteField(Field): Univariate Polynomial Ring in alpha over Finite Field of size 3 """ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - from sage.rings.finite_rings.constructor import GF + from sage.rings.finite_rings.finite_field_constructor import GF if variable_name is None and self.__polynomial_ring is not None: return self.__polynomial_ring @@ -1172,7 +1172,7 @@ cdef class FiniteField(Field): sage: F.extension(int(3), 'aa') Finite Field in aa of size 2^12 """ - from constructor import GF + from finite_field_constructor import GF from sage.rings.polynomial.polynomial_element import is_Polynomial from sage.rings.integer import Integer if name is None and names is not None: @@ -1255,7 +1255,7 @@ cdef class FiniteField(Field): Defn: z21 |--> a)] """ from sage.rings.integer import Integer - from constructor import GF + from finite_field_constructor import GF p = self.characteristic() n = self.degree() if degree != 0: diff --git a/src/sage/rings/finite_rings/finite_field_constructor.py b/src/sage/rings/finite_rings/finite_field_constructor.py new file mode 100644 index 00000000000..7e3647dc5b6 --- /dev/null +++ b/src/sage/rings/finite_rings/finite_field_constructor.py @@ -0,0 +1,668 @@ +r""" +Finite Fields + +Sage supports arithmetic in finite prime and extension fields. +Several implementation for prime fields are implemented natively in +Sage for several sizes of primes `p`. These implementations +are + + +- ``sage.rings.finite_rings.integer_mod.IntegerMod_int``, + +- ``sage.rings.finite_rings.integer_mod.IntegerMod_int64``, and + +- ``sage.rings.finite_rings.integer_mod.IntegerMod_gmp``. + + +Small extension fields of cardinality `< 2^{16}` are +implemented using tables of Zech logs via the Givaro C++ library +(``sage.rings.finite_rings.finite_field_givaro.FiniteField_givaro``). +While this representation is very fast it is limited to finite +fields of small cardinality. Larger finite extension fields of +order `q >= 2^{16}` are internally represented as +polynomials over smaller finite prime fields. If the +characteristic of such a field is 2 then NTL is used internally to +represent the field +(``sage.rings.finite_rings.finite_field_ntl_gf2e.FiniteField_ntl_gf2e``). +In all other case the PARI C library is used +(``sage.rings.finite_rings.finite_field_pari_ffelt.FiniteField_pari_ffelt``). + +However, this distinction is internal only and the user usually +does not have to worry about it because consistency across all +implementations is aimed for. In all extension field +implementations the user may either specify a minimal polynomial or +leave the choice to Sage. + +For small finite fields the default choice are Conway polynomials. + +The Conway polynomial `C_n` is the lexicographically first +monic irreducible, primitive polynomial of degree `n` over +`GF(p)` with the property that for a root `\alpha` +of `C_n` we have that +`\beta= +\alpha^{(p^n - 1)/(p^m - 1)}` is a root of +`C_m` for all `m` dividing `n`. Sage +contains a database of Conway polynomials which also can be queried +independently of finite field construction. + +While Sage supports basic arithmetic in finite fields some more +advanced features for computing with finite fields are still not +implemented. For instance, Sage does not calculate embeddings of +finite fields yet. + +EXAMPLES:: + + sage: k = GF(5); type(k) + + +:: + + sage: k = GF(5^2,'c'); type(k) + + +:: + + sage: k = GF(2^16,'c'); type(k) + + +:: + + sage: k = GF(3^16,'c'); type(k) + + +Finite Fields support iteration, starting with 0. + +:: + + sage: k = GF(9, 'a') + sage: for i,x in enumerate(k): print i,x + 0 0 + 1 a + 2 a + 1 + 3 2*a + 1 + 4 2 + 5 2*a + 6 2*a + 2 + 7 a + 2 + 8 1 + sage: for a in GF(5): + ... print a + 0 + 1 + 2 + 3 + 4 + +We output the base rings of several finite fields. + +:: + + sage: k = GF(3); type(k) + + sage: k.base_ring() + Finite Field of size 3 + +:: + + sage: k = GF(9,'alpha'); type(k) + + sage: k.base_ring() + Finite Field of size 3 + +:: + + sage: k = GF(3^40,'b'); type(k) + + sage: k.base_ring() + Finite Field of size 3 + +Further examples:: + + sage: GF(2).is_field() + True + sage: GF(next_prime(10^20)).is_field() + True + sage: GF(19^20,'a').is_field() + True + sage: GF(8,'a').is_field() + True + +AUTHORS: + +- William Stein: initial version + +- Robert Bradshaw: prime field implementation + +- Martin Albrecht: Givaro and ntl.GF2E implementations +""" + +#***************************************************************************** +# Copyright (C) 2006 William Stein +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# The full text of the GPL is available at: +# +# http://www.gnu.org/licenses/ +#***************************************************************************** + +import random + +from sage.rings.finite_rings.finite_field_base import is_FiniteField +from sage.structure.category_object import normalize_names + +from sage.rings.integer import Integer + +import sage.rings.polynomial.polynomial_element as polynomial_element +import sage.rings.polynomial.multi_polynomial_element as multi_polynomial_element +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + +# We don't late import this because this means trouble with the Givaro library +# On a Macbook Pro OSX 10.5.8, this manifests as a Bus Error on exiting Sage. +# TODO: figure out why +from finite_field_givaro import FiniteField_givaro + +import sage.interfaces.gap + +from sage.structure.factory import UniqueFactory + +class FiniteFieldFactory(UniqueFactory): + """ + Return the globally unique finite field of given order with + generator labeled by the given name and possibly with given + modulus. + + INPUT: + + - ``order`` -- a prime power + + - ``name`` -- string; must be specified unless ``order`` is prime. + + - ``modulus`` -- (optional) either a defining polynomial for the + field, or a string specifying an algorithm to use to generate + such a polynomial. If ``modulus`` is a string, it is passed to + :meth:`~sage.rings.polynomial.irreducible_element()` as the + parameter ``algorithm``; see there for the permissible values of + this parameter. In particular, you can specify + ``modulus="primitive"`` to get a primitive polynomial. + + - ``impl`` -- (optional) a string specifying the implementation of + the finite field. Possible values are: + + - ``'modn'`` -- ring of integers modulo `p` (only for prime + fields). + + - ``'givaro'`` -- Givaro, which uses Zech logs (only for fields + of at most 65521 elements). + + - ``'ntl'`` -- NTL using GF2X (only in characteristic 2). + + - ``'pari_ffelt'`` -- PARI's ``FFELT`` type (only for extension + fields). + + - ``'pari_mod'`` -- Older PARI implementation using ``POLMOD``s + (slower than ``'pari_ffelt'``, only for extension fields). + + - ``elem_cache`` -- cache all elements to avoid creation time + (default: order < 500) + + - ``check_irreducible`` -- verify that the polynomial modulus is + irreducible + + - ``proof`` -- bool (default: ``True``): if ``True``, use provable + primality test; otherwise only use pseudoprimality test. + + - ``args`` -- additional parameters passed to finite field + implementations + + - ``kwds`` -- additional keyword parameters passed to finite field + implementations + + ALIAS: You can also use ``GF`` instead of ``FiniteField`` -- they + are identical. + + EXAMPLES:: + + sage: k. = FiniteField(9); k + Finite Field in a of size 3^2 + sage: parent(a) + Finite Field in a of size 3^2 + sage: charpoly(a, 'y') + y^2 + 2*y + 2 + + We illustrate the proof flag. The following example would hang + for a very long time if we didn't use ``proof=False``. + + .. NOTE:: + + Magma only supports ``proof=False`` for making finite fields, + so falsely appears to be faster than Sage -- see :trac:`10975`. + + :: + + sage: k = FiniteField(10^1000 + 453, proof=False) + sage: k = FiniteField((10^1000 + 453)^2, 'a', proof=False) # long time -- about 5 seconds + + :: + + sage: F. = GF(5)[] + sage: K. = GF(5**5, name='a', modulus=x^5 - x +1 ) + sage: f = K.modulus(); f + x^5 + 4*x + 1 + sage: type(f) + + + By default, the given generator is not guaranteed to be primitive + (a generator of the multiplicative group), use + ``modulus="primitive"`` if you need this:: + + sage: K. = GF(5^40) + sage: a.multiplicative_order() + 4547473508864641189575195312 + sage: a.is_square() + True + sage: K. = GF(5^40, modulus="primitive") + sage: b.multiplicative_order() + 9094947017729282379150390624 + + The modulus must be irreducible:: + + sage: K. = GF(5**5, name='a', modulus=x^5 - x) + Traceback (most recent call last): + ... + ValueError: finite field modulus must be irreducible but it is not + + You can't accidentally fool the constructor into thinking the + modulus is irreducible when it is not, since it actually tests + irreducibility modulo `p`. Also, the modulus has to be of the + right degree (this is always checked):: + + sage: F. = QQ[] + sage: factor(x^5 + 2) + x^5 + 2 + sage: K. = GF(5^5, modulus=x^5 + 2) + Traceback (most recent call last): + ... + ValueError: finite field modulus must be irreducible but it is not + sage: K. = GF(5^5, modulus=x^3 + 3*x + 3, check_irreducible=False) + Traceback (most recent call last): + ... + ValueError: the degree of the modulus does not equal the degree of the field + + Any type which can be converted to the polynomial ring `GF(p)[x]` + is accepted as modulus:: + + sage: K. = GF(13^3, modulus=[1,0,0,2]) + sage: K. = GF(13^10, modulus=pari("ffinit(13,10)")) + sage: var('x') + x + sage: K. = GF(13^2, modulus=x^2 - 2) + sage: K. = GF(13^2, modulus=sin(x)) + Traceback (most recent call last): + ... + TypeError: unable to convert sin(x) to an integer + + If you wish to live dangerously, you can tell the constructor not + to test irreducibility using ``check_irreducible=False``, but this + can easily lead to crashes and hangs -- so do not do it unless you + know that the modulus really is irreducible! + + :: + + sage: K. = GF(5**2, name='a', modulus=x^2 + 2, check_irreducible=False) + + Even for prime fields, you can specify a modulus. This will not + change how Sage computes in this field, but it will change the + result of the :meth:`modulus` and :meth:`gen` methods:: + + sage: k. = GF(5, modulus="primitive") + sage: k.modulus() + x + 3 + sage: a + 2 + + The order of a finite field must be a prime power:: + + sage: GF(1) + Traceback (most recent call last): + ... + ValueError: the order of a finite field must be at least 2 + sage: GF(100) + Traceback (most recent call last): + ... + ValueError: the order of a finite field must be a prime power + + Finite fields with explicit random modulus are not cached:: + + sage: k. = GF(5**10, modulus='random') + sage: n. = GF(5**10, modulus='random') + sage: n is k + False + sage: GF(5**10, 'a') is GF(5**10, 'a') + True + + We check that various ways of creating the same finite field yield + the same object, which is cached:: + + sage: K = GF(7, 'a') + sage: L = GF(7, 'b') + sage: K is L # name is ignored for prime fields + True + sage: K is GF(7, modulus=K.modulus()) + True + sage: K = GF(4,'a'); K.modulus() + x^2 + x + 1 + sage: L = GF(4,'a', K.modulus()) + sage: K is L + True + sage: M = GF(4,'a', K.modulus().change_variable_name('y')) + sage: K is M + True + + You may print finite field elements as integers. This currently + only works if the order of field is `<2^{16}`, though:: + + sage: k. = GF(2^8, repr='int') + sage: a + 2 + + The following demonstrate coercions for finite fields using Conway + polynomials:: + + sage: k = GF(5^2, conway=True, prefix='z'); a = k.gen() + sage: l = GF(5^5, conway=True, prefix='z'); b = l.gen() + sage: a + b + 3*z10^5 + z10^4 + z10^2 + 3*z10 + 1 + + Note that embeddings are compatible in lattices of such finite + fields:: + + sage: m = GF(5^3, conway=True, prefix='z'); c = m.gen() + sage: (a+b)+c == a+(b+c) + True + sage: (a*b)*c == a*(b*c) + True + sage: from sage.categories.pushout import pushout + sage: n = pushout(k, l) + sage: o = pushout(l, m) + sage: q = pushout(n, o) + sage: q(o(b)) == q(n(b)) + True + + Another check that embeddings are defined properly:: + + sage: k = GF(3**10, conway=True, prefix='z') + sage: l = GF(3**20, conway=True, prefix='z') + sage: l(k.gen()**10) == l(k.gen())**10 + True + + Check that :trac:`16934` has been fixed:: + + sage: k1. = GF(17^14, impl="pari_ffelt") + sage: _ = a/2 + sage: k2. = GF(17^14, impl="pari_ffelt") + sage: k1 is k2 + True + + """ + def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, + impl=None, proof=None, check_irreducible=True, **kwds): + """ + EXAMPLES:: + + sage: GF.create_key_and_extra_args(9, 'a') + ((9, ('a',), x^2 + 2*x + 2, 'givaro', '{}', 3, 2, True), {}) + sage: GF.create_key_and_extra_args(9, 'a', foo='value') + ((9, ('a',), x^2 + 2*x + 2, 'givaro', "{'foo': 'value'}", 3, 2, True), {'foo': 'value'}) + """ + import sage.arith.all + from sage.structure.proof.all import WithProof, arithmetic + if proof is None: + proof = arithmetic() + with WithProof('arithmetic', proof): + order = Integer(order) + if order <= 1: + raise ValueError("the order of a finite field must be at least 2") + + if order.is_prime(): + p = order + n = Integer(1) + if impl is None: + impl = 'modn' + name = ('x',) # Ignore name + # Every polynomial of degree 1 is irreducible + check_irreducible = False + elif order.is_prime_power(): + if names is not None: + name = names + if name is not None: + name = normalize_names(1, name) + + p, n = order.factor()[0] + + # The following is a temporary solution that allows us + # to construct compatible systems of finite fields + # until algebraic closures of finite fields are + # implemented in Sage. It requires the user to + # specify two parameters: + # + # - `conway` -- boolean; if True, this field is + # constructed to fit in a compatible system using + # a Conway polynomial. + # - `prefix` -- a string used to generate names for + # automatically constructed finite fields + # + # See the docstring of FiniteFieldFactory for examples. + # + # Once algebraic closures of finite fields are + # implemented, this syntax should be superseded by + # something like the following: + # + # sage: Fpbar = GF(5).algebraic_closure('z') + # sage: F, e = Fpbar.subfield(3) # e is the embedding into Fpbar + # sage: F + # Finite field in z3 of size 5^3 + # + # This temporary solution only uses actual Conway + # polynomials (no pseudo-Conway polynomials), since + # pseudo-Conway polynomials are not unique, and until + # we have algebraic closures of finite fields, there + # is no good place to store a specific choice of + # pseudo-Conway polynomials. + if name is None: + if not ('conway' in kwds and kwds['conway']): + raise ValueError("parameter 'conway' is required if no name given") + if 'prefix' not in kwds: + raise ValueError("parameter 'prefix' is required if no name given") + name = kwds['prefix'] + str(n) + + if 'conway' in kwds and kwds['conway']: + from conway_polynomials import conway_polynomial + if 'prefix' not in kwds: + raise ValueError("a prefix must be specified if conway=True") + if modulus is not None: + raise ValueError("no modulus may be specified if conway=True") + # The following raises a RuntimeError if no polynomial is found. + modulus = conway_polynomial(p, n) + + if impl is None: + if order < zech_log_bound: + impl = 'givaro' + elif p == 2: + impl = 'ntl' + else: + impl = 'pari_ffelt' + else: + raise ValueError("the order of a finite field must be a prime power") + + # Determine modulus. + # For the 'modn' implementation, we use the following + # optimization which we also need to avoid an infinite loop: + # a modulus of None is a shorthand for x-1. + if modulus is not None or impl != 'modn': + R = PolynomialRing(FiniteField(p), 'x') + if modulus is None: + modulus = R.irreducible_element(n) + if isinstance(modulus, str): + # A string specifies an algorithm to find a suitable modulus. + if modulus == "default": + from sage.misc.superseded import deprecation + deprecation(16983, "the modulus 'default' is deprecated, use modulus=None instead (which is the default)") + modulus = None + modulus = R.irreducible_element(n, algorithm=modulus) + else: + if sage.rings.polynomial.polynomial_element.is_Polynomial(modulus): + modulus = modulus.change_variable_name('x') + modulus = R(modulus).monic() + + if modulus.degree() != n: + raise ValueError("the degree of the modulus does not equal the degree of the field") + if check_irreducible and not modulus.is_irreducible(): + raise ValueError("finite field modulus must be irreducible but it is not") + # If modulus is x - 1 for impl="modn", set it to None + if impl == 'modn' and modulus[0] == -1: + modulus = None + + return (order, name, modulus, impl, str(kwds), p, n, proof), kwds + + def create_object(self, version, key, **kwds): + """ + EXAMPLES:: + + sage: K = GF(19) # indirect doctest + sage: TestSuite(K).run() + + We try to create finite fields with various implementations:: + + sage: k = GF(2, impl='modn') + sage: k = GF(2, impl='givaro') + sage: k = GF(2, impl='ntl') + sage: k = GF(2, impl='pari_ffelt') + Traceback (most recent call last): + ... + ValueError: the degree must be at least 2 + sage: k = GF(2, impl='pari_mod') + Traceback (most recent call last): + ... + ValueError: The size of the finite field must not be prime. + sage: k = GF(2, impl='supercalifragilisticexpialidocious') + Traceback (most recent call last): + ... + ValueError: no such finite field implementation: 'supercalifragilisticexpialidocious' + sage: k. = GF(2^15, impl='modn') + Traceback (most recent call last): + ... + ValueError: the 'modn' implementation requires a prime order + sage: k. = GF(2^15, impl='givaro') + sage: k. = GF(2^15, impl='ntl') + sage: k. = GF(2^15, impl='pari_ffelt') + sage: k. = GF(2^15, impl='pari_mod') + sage: k. = GF(3^60, impl='modn') + Traceback (most recent call last): + ... + ValueError: the 'modn' implementation requires a prime order + sage: k. = GF(3^60, impl='givaro') + Traceback (most recent call last): + ... + ValueError: q must be < 2^16 + sage: k. = GF(3^60, impl='ntl') + Traceback (most recent call last): + ... + ValueError: q must be a 2-power + sage: k. = GF(3^60, impl='pari_ffelt') + sage: k. = GF(3^60, impl='pari_mod') + """ + # IMPORTANT! If you add a new class to the list of classes + # that get cached by this factor object, then you *must* add + # the following method to that class in order to fully support + # pickling: + # + # def __reduce__(self): # and include good doctests, please! + # return self._factory_data[0].reduce_data(self) + # + # This is not in the base class for finite fields, since some finite + # fields need not be created using this factory object, e.g., residue + # class fields. + + if len(key) == 5: + # for backward compatibility of pickles (see trac 10975). + order, name, modulus, impl, _ = key + p, n = Integer(order).factor()[0] + proof = True + else: + order, name, modulus, impl, _, p, n, proof = key + + if impl == 'modn': + if n != 1: + raise ValueError("the 'modn' implementation requires a prime order") + from finite_field_prime_modn import FiniteField_prime_modn + # Using a check option here is probably a worthwhile + # compromise since this constructor is simple and used a + # huge amount. + K = FiniteField_prime_modn(order, check=False, modulus=modulus) + else: + # We have to do this with block so that the finite field + # constructors below will use the proof flag that was + # passed in when checking for primality, factoring, etc. + # Otherwise, we would have to complicate all of their + # constructors with check options. + from sage.structure.proof.all import WithProof + with WithProof('arithmetic', proof): + if impl == 'givaro': + repr = kwds.get('repr', 'poly') + elem_cache = kwds.get('elem_cache', order < 500) + K = FiniteField_givaro(order, name, modulus, repr=repr, cache=elem_cache) + elif impl == 'ntl': + from finite_field_ntl_gf2e import FiniteField_ntl_gf2e + K = FiniteField_ntl_gf2e(order, name, modulus) + elif impl == 'pari_ffelt': + from finite_field_pari_ffelt import FiniteField_pari_ffelt + K = FiniteField_pari_ffelt(p, modulus, name) + elif (impl == 'pari_mod' + or impl == 'pari'): # for unpickling old pickles + # This implementation is deprecated, a warning will + # be given when this field is created. + # See http://trac.sagemath.org/ticket/17297 + from finite_field_ext_pari import FiniteField_ext_pari + K = FiniteField_ext_pari(order, name, modulus) + else: + raise ValueError("no such finite field implementation: %r" % impl) + + # Temporary; see create_key_and_extra_args() above. + if 'prefix' in kwds: + K._prefix = kwds['prefix'] + + return K + + +GF = FiniteField = FiniteFieldFactory("FiniteField") + + +def is_PrimeFiniteField(x): + """ + Returns True if x is a prime finite field. + + EXAMPLES:: + + sage: from sage.rings.finite_rings.finite_field_constructor import is_PrimeFiniteField + sage: is_PrimeFiniteField(QQ) + False + sage: is_PrimeFiniteField(GF(7)) + True + sage: is_PrimeFiniteField(GF(7^2,'a')) + False + sage: is_PrimeFiniteField(GF(next_prime(10^90,proof=False))) + True + """ + from finite_field_prime_modn import FiniteField_prime_modn + from sage.rings.finite_rings.finite_field_base import FiniteField as FiniteField_generic + + return isinstance(x, FiniteField_prime_modn) or \ + (isinstance(x, FiniteField_generic) and x.degree() == 1) + +zech_log_bound = 2**16 diff --git a/src/sage/rings/finite_rings/finite_field_ext_pari.py b/src/sage/rings/finite_rings/finite_field_ext_pari.py index b2c60d49ead..1eeaf4284ce 100644 --- a/src/sage/rings/finite_rings/finite_field_ext_pari.py +++ b/src/sage/rings/finite_rings/finite_field_ext_pari.py @@ -176,7 +176,7 @@ def __init__(self, q, name, modulus=None): deprecation(17297, 'The "pari_mod" finite field implementation is deprecated') if element_ext_pari.dynamic_FiniteField_ext_pariElement is None: element_ext_pari._late_import() - from constructor import FiniteField as GF + from finite_field_constructor import FiniteField as GF q = integer.Integer(q) if q < 2: raise ArithmeticError("q must be a prime power") diff --git a/src/sage/rings/finite_rings/finite_field_givaro.py b/src/sage/rings/finite_rings/finite_field_givaro.py index d96fbf75299..27c0dab749a 100644 --- a/src/sage/rings/finite_rings/finite_field_givaro.py +++ b/src/sage/rings/finite_rings/finite_field_givaro.py @@ -146,7 +146,7 @@ def __init__(self, q, name="a", modulus=None, repr="poly", cache=False): if q >= 1<<16: raise ValueError("q must be < 2^16") - from constructor import GF + from finite_field_constructor import GF FiniteField.__init__(self, GF(p), name, normalize=False) self._kwargs['repr'] = repr @@ -441,7 +441,7 @@ def prime_subfield(self): try: return self._prime_subfield except AttributeError: - from constructor import GF + from finite_field_constructor import GF self._prime_subfield = GF(self.characteristic()) return self._prime_subfield diff --git a/src/sage/rings/finite_rings/finite_field_ntl_gf2e.py b/src/sage/rings/finite_rings/finite_field_ntl_gf2e.py index 8946bf74662..db4044763f4 100644 --- a/src/sage/rings/finite_rings/finite_field_ntl_gf2e.py +++ b/src/sage/rings/finite_rings/finite_field_ntl_gf2e.py @@ -54,8 +54,8 @@ def late_import(): import sage.rings.finite_rings.element_ntl_gf2e Cache_ntl_gf2e = sage.rings.finite_rings.element_ntl_gf2e.Cache_ntl_gf2e - import sage.rings.finite_rings.constructor - GF = sage.rings.finite_rings.constructor.GF + import sage.rings.finite_rings.finite_field_constructor + GF = sage.rings.finite_rings.finite_field_constructor.GF GF2 = GF(2) import sage.rings.polynomial.polynomial_element diff --git a/src/sage/rings/finite_rings/finite_field_pari_ffelt.py b/src/sage/rings/finite_rings/finite_field_pari_ffelt.py index dbc5d8e45af..f11802b8507 100644 --- a/src/sage/rings/finite_rings/finite_field_pari_ffelt.py +++ b/src/sage/rings/finite_rings/finite_field_pari_ffelt.py @@ -19,7 +19,7 @@ from element_pari_ffelt import FiniteFieldElement_pari_ffelt from finite_field_base import FiniteField -from constructor import GF +from finite_field_constructor import GF class FiniteField_pari_ffelt(FiniteField): """ diff --git a/src/sage/rings/finite_rings/finite_field_prime_modn.py b/src/sage/rings/finite_rings/finite_field_prime_modn.py index 1dcee12e521..534ac5e377a 100644 --- a/src/sage/rings/finite_rings/finite_field_prime_modn.py +++ b/src/sage/rings/finite_rings/finite_field_prime_modn.py @@ -185,7 +185,7 @@ def polynomial(self, name=None): try: return self.__polynomial[name] except AttributeError: - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField R = FiniteField(self.characteristic())[name] f = self[name]([0,1]) try: diff --git a/src/sage/rings/finite_rings/hom_finite_field.pyx b/src/sage/rings/finite_rings/hom_finite_field.pyx index 1ca9fd576c1..97bc170e5d5 100644 --- a/src/sage/rings/finite_rings/hom_finite_field.pyx +++ b/src/sage/rings/finite_rings/hom_finite_field.pyx @@ -109,7 +109,7 @@ from sage.structure.element cimport Element from sage.rings.finite_rings.finite_field_base import is_FiniteField from sage.rings.morphism cimport RingHomomorphism, RingHomomorphism_im_gens, FrobeniusEndomorphism_generic -from sage.rings.finite_rings.constructor import FiniteField +from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.categories.map cimport Section from sage.categories.morphism cimport Morphism diff --git a/src/sage/rings/finite_rings/hom_finite_field_givaro.pyx b/src/sage/rings/finite_rings/hom_finite_field_givaro.pyx index cbb1f0d3655..c3460440d11 100644 --- a/src/sage/rings/finite_rings/hom_finite_field_givaro.pyx +++ b/src/sage/rings/finite_rings/hom_finite_field_givaro.pyx @@ -25,7 +25,7 @@ AUTHOR: #**************************************************************************** -from sage.rings.finite_rings.constructor import FiniteField +from sage.rings.finite_rings.finite_field_constructor import FiniteField from hom_finite_field cimport SectionFiniteFieldHomomorphism_generic from hom_finite_field cimport FiniteFieldHomomorphism_generic diff --git a/src/sage/rings/finite_rings/integer_mod.pxd b/src/sage/rings/finite_rings/integer_mod.pxd index 7375b2223a0..9471aa6add6 100644 --- a/src/sage/rings/finite_rings/integer_mod.pxd +++ b/src/sage/rings/finite_rings/integer_mod.pxd @@ -31,7 +31,6 @@ cdef class IntegerMod_int(IntegerMod_abstract): cdef int_fast32_t get_int_value(IntegerMod_int self) cdef IntegerMod_int _new_c(self, int_fast32_t value) cdef shift(IntegerMod_int self, int k) - #cdef Element _make_new_with_parent_c(self, ParentWithBase parent) cdef class IntegerMod_int64(IntegerMod_abstract): cdef int_fast64_t ivalue diff --git a/src/sage/rings/finite_rings/integer_mod.pyx b/src/sage/rings/finite_rings/integer_mod.pyx index 2b73ef60696..4e252aa9993 100644 --- a/src/sage/rings/finite_rings/integer_mod.pyx +++ b/src/sage/rings/finite_rings/integer_mod.pyx @@ -984,8 +984,8 @@ cdef class IntegerMod_abstract(FiniteRingElement): R = self.parent()['x'] modulus = R.gen()**2 - R(self) if self._parent.is_field(): - import constructor - Q = constructor.FiniteField(self.__modulus.sageInteger**2, y, modulus) + from finite_field_constructor import FiniteField + Q = FiniteField(self.__modulus.sageInteger**2, y, modulus) else: R = self.parent()['x'] Q = R.quotient(modulus, names=(y,)) @@ -1652,21 +1652,16 @@ cdef class IntegerMod_abstract(FiniteRingElement): return infinity return r - def __floordiv__(self, other): + cpdef RingElement _floordiv_(self, RingElement right): """ Exact division for prime moduli, for compatibility with other fields. - EXAMPLES: - sage: GF(7)(3) // GF(7)(5) - 2 + EXAMPLES:: + + sage: GF(7)(3) // 5 + 2 """ - # needs to be rewritten for coercion - if other.parent() is not self.parent(): - other = self.parent().coerce(other) - if self.parent().is_field(): - return self / other - else: - raise TypeError, "Floor division not defined for non-prime modulus" + return self._mul_(~right) def _repr_(self): return str(self.lift()) @@ -2195,13 +2190,6 @@ cdef class IntegerMod_int(IntegerMod_abstract): z = sage.rings.integer_ring.Z(value) self.set_from_mpz(z.value) - def _make_new_with_parent_c(self, parent): #ParentWithBase parent): - cdef IntegerMod_int x = IntegerMod_int.__new__(IntegerMod_int) - x._parent = parent - x.__modulus = parent._pyx_order - x.ivalue = self.ivalue - return x - cdef IntegerMod_int _new_c(self, int_fast32_t value): if self.__modulus.table is not None: return self.__modulus.lookup(value) diff --git a/src/sage/rings/finite_rings/integer_mod_ring.py b/src/sage/rings/finite_rings/integer_mod_ring.py index 223ba3f43de..0197b371df3 100644 --- a/src/sage/rings/finite_rings/integer_mod_ring.py +++ b/src/sage/rings/finite_rings/integer_mod_ring.py @@ -68,7 +68,6 @@ import sage.rings.integer as integer import sage.rings.integer_ring as integer_ring import sage.rings.quotient_ring as quotient_ring -from sage.structure.parent_gens import ParentWithGens from sage.libs.pari.all import pari, PariError @@ -124,7 +123,7 @@ class IntegerModFactory(UniqueFactory): Testing whether a quotient ring `\ZZ / n\ZZ` is a field can of course be very costly. By default, it is not tested whether `n` is prime or not, in contrast to - :func:`~sage.rings.finite_rings.constructor.GF`. If the user + :func:`~sage.rings.finite_rings.finite_field_constructor.GF`. If the user is sure that the modulus is prime and wants to avoid a primality test, (s)he can provide ``category=Fields()`` when constructing the quotient ring, and then the result will behave like a field. @@ -781,8 +780,8 @@ def field(self): except AttributeError: if not self.is_field(): raise ValueError("self must be a field") - import constructor - k = constructor.FiniteField(self.order()) + import finite_field_constructor + k = finite_field_constructor.FiniteField(self.order()) self.__field = k return k diff --git a/src/sage/rings/finite_rings/residue_field.pyx b/src/sage/rings/finite_rings/residue_field.pyx index 6c8fb487ce0..adb81039791 100644 --- a/src/sage/rings/finite_rings/residue_field.pyx +++ b/src/sage/rings/finite_rings/residue_field.pyx @@ -152,7 +152,7 @@ from sage.rings.integer cimport Integer from sage.rings.rational cimport Rational from sage.categories.homset import Hom from sage.rings.all import ZZ, QQ, Integers -from sage.rings.finite_rings.constructor import zech_log_bound, FiniteField as GF +from sage.rings.finite_rings.finite_field_constructor import zech_log_bound, FiniteField as GF from sage.rings.finite_rings.finite_field_givaro import FiniteField_givaro from sage.rings.finite_rings.finite_field_ntl_gf2e import FiniteField_ntl_gf2e from sage.rings.finite_rings.finite_field_prime_modn import FiniteField_prime_modn diff --git a/src/sage/rings/ideal_monoid.py b/src/sage/rings/ideal_monoid.py index b1d9a85a86e..df604b9721c 100644 --- a/src/sage/rings/ideal_monoid.py +++ b/src/sage/rings/ideal_monoid.py @@ -3,7 +3,6 @@ """ from commutative_ring import is_CommutativeRing -#from sage.structure.parent_base import ParentWithBase from sage.structure.parent import Parent import sage.rings.integer_ring import ideal diff --git a/src/sage/rings/infinity.py b/src/sage/rings/infinity.py index 64b5d78c147..7f4b3dedea9 100644 --- a/src/sage/rings/infinity.py +++ b/src/sage/rings/infinity.py @@ -207,8 +207,18 @@ [(+Infinity)] """ +#***************************************************************************** +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + + +from sys import maxsize from sage.rings.ring import Ring -from sage.structure.element import RingElement, InfinityElement, PlusInfinityElement, MinusInfinityElement +from sage.structure.element import RingElement, InfinityElement from sage.structure.parent_gens import ParentWithGens import sage.rings.integer import sage.rings.rational @@ -862,6 +872,16 @@ def __init__(self): """ InfinityElement.__init__(self, UnsignedInfinityRing) + def __hash__(self): + r""" + TESTS:: + + sage: hash(unsigned_infinity) + 9223372036854775806 # 64-bit + 2147483646 # 32-bit + """ + return maxsize-1 + def _mul_(self, other): """ Can't rule out an attempt at multiplication by 0. @@ -1102,12 +1122,11 @@ def _element_constructor_(self, x): x = x._value # Handle all ways to represent infinity first - if isinstance(x, PlusInfinityElement): - return self.gen(0) - elif isinstance(x, MinusInfinityElement): - return self.gen(1) - elif isinstance(x, InfinityElement): - return self.gen(0) + if isinstance(x, InfinityElement): + if x < 0: + return self.gen(1) + else: + return self.gen(0) elif isinstance(x, float): if x == float('+inf'): return self.gen(0) @@ -1434,7 +1453,7 @@ def sqrt(self): raise SignError("cannot take square root of a negative number") return self -class MinusInfinity(_uniq, AnInfinity, MinusInfinityElement): +class MinusInfinity(_uniq, AnInfinity, InfinityElement): _sign = -1 _sign_char = '-' @@ -1450,6 +1469,16 @@ def __init__(self): """ InfinityElement.__init__(self, InfinityRing) + def __hash__(self): + r""" + TESTS:: + + sage: hash(-infinity) + -9223372036854775808 # 64-bit + -2147483648 # 32-bit + """ + return ~maxsize + def _neg_(self): """ EXAMPLES:: @@ -1504,7 +1533,7 @@ def _gap_init_(self): """ return '-infinity' -class PlusInfinity(_uniq, AnInfinity, PlusInfinityElement): +class PlusInfinity(_uniq, AnInfinity, InfinityElement): _sign = 1 _sign_char = '+' @@ -1520,6 +1549,16 @@ def __init__(self): """ InfinityElement.__init__(self, InfinityRing) + def __hash__(self): + r""" + TESTS:: + + sage: hash(+infinity) + 9223372036854775807 # 64-bit + 2147483647 # 32-bit + """ + return maxsize + def _neg_(self): """ TESTS:: diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index 2f212ee6965..b5b899bdeef 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -1732,7 +1732,7 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): # we can't cimport rationals. return the_integer_ring._div(self, right) - def __floordiv__(x, y): + cpdef RingElement _floordiv_(self, RingElement right): r""" Computes the whole part of `\frac{x}{y}`. @@ -1769,34 +1769,17 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): sage: [int(a) // b for a,b in signs] == control True """ - cdef Integer z = PY_NEW(Integer) - cdef long yy, res - if type(x) is type(y): - if not mpz_sgn((y).value): - raise ZeroDivisionError, "Integer division by zero" - if mpz_size((x).value) > 100000: - sig_on() - mpz_fdiv_q(z.value, (x).value, (y).value) - sig_off() - else: - mpz_fdiv_q(z.value, (x).value, (y).value) - return z - - elif PyInt_CheckExact(y): - yy = PyInt_AS_LONG(y) - if yy > 0: - mpz_fdiv_q_ui(z.value, (x).value, yy) - elif yy == 0: - raise ZeroDivisionError, "Integer division by zero" - else: - res = mpz_fdiv_q_ui(z.value, (x).value, -yy) - mpz_neg(z.value, z.value) - if res: - mpz_sub_ui(z.value, z.value, 1) - return z + if not mpz_sgn((right).value): + raise ZeroDivisionError("Integer division by zero") + cdef Integer z = PY_NEW(Integer) + if mpz_size(self.value) > 1000: + sig_on() + mpz_fdiv_q(z.value, self.value, (right).value) + sig_off() else: - return bin_op(x, y, operator.floordiv) + mpz_fdiv_q(z.value, self.value, (right).value) + return z def __pow__(self, n, modulus): r""" diff --git a/src/sage/rings/laurent_series_ring.py b/src/sage/rings/laurent_series_ring.py index d64288a10dd..620bd496916 100644 --- a/src/sage/rings/laurent_series_ring.py +++ b/src/sage/rings/laurent_series_ring.py @@ -27,10 +27,7 @@ import integral_domain import ring -from sage.structure.parent_gens import ParentWithGens from sage.libs.pari.all import pari_gen - -from sage.structure.category_object import check_default_category from sage.categories.fields import Fields from sage.categories.complete_discrete_valuation import CompleteDiscreteValuationFields diff --git a/src/sage/rings/laurent_series_ring_element.pyx b/src/sage/rings/laurent_series_ring_element.pyx index 44401ba7ef0..8e27adf8f85 100644 --- a/src/sage/rings/laurent_series_ring_element.pyx +++ b/src/sage/rings/laurent_series_ring_element.pyx @@ -385,7 +385,12 @@ cdef class LaurentSeries(AlgebraElement): 0 sage: f = -5/t^(10) + 1/3 + t + t^2 - 10/3*t^3 + O(t^5); f -5*t^-10 + 1/3 + t + t^2 - 10/3*t^3 + O(t^5) + + Slicing is deprecated:: + sage: f[-10:2] + doctest:...: DeprecationWarning: polynomial slicing with a start index is deprecated, use list() and slice the resulting list instead + See http://trac.sagemath.org/18940 for details. -5*t^-10 + 1/3 + t + O(t^5) sage: f[0:] 1/3 + t + t^2 - 10/3*t^3 + O(t^5) @@ -394,14 +399,12 @@ cdef class LaurentSeries(AlgebraElement): start, stop, step = i.start, i.stop, i.step if start is None: start = 0 - if step is None: - step = 1 if stop > self.__u.degree() or stop is None: stop = self.__u.degree() - f = self.__u[start-self.__n:stop-self.__n:step] + f = self.__u[start-self.__n:stop-self.__n:step] # deprecation(18940) return LaurentSeries(self._parent, f, self.__n) - else: - return self.__u[i-self.__n] + + return self.__u[i - self.__n] def __iter__(self): """ diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx index d9151a9c114..fb635b12bcf 100644 --- a/src/sage/rings/number_field/number_field_element.pyx +++ b/src/sage/rings/number_field/number_field_element.pyx @@ -2121,23 +2121,6 @@ cdef class NumberFieldElement(FieldElement): sig_off() return x - def __floordiv__(self, other): - """ - Return the quotient of self and other. Since these are field - elements the floor division is exactly the same as usual division. - - EXAMPLES:: - - sage: m. = NumberField(x^4 + x^2 + 2/3) - sage: c = (1+b) // (1-b); c - 3/4*b^3 + 3/4*b^2 + 3/2*b + 1/2 - sage: (1+b) / (1-b) == c - True - sage: c * (1-b) - b + 1 - """ - return self / other - def __nonzero__(self): """ Return True if this number field element is nonzero. diff --git a/src/sage/rings/number_field/number_field_ideal.py b/src/sage/rings/number_field/number_field_ideal.py index af4997c31c9..b534aee1ee1 100644 --- a/src/sage/rings/number_field/number_field_ideal.py +++ b/src/sage/rings/number_field/number_field_ideal.py @@ -47,7 +47,7 @@ import sage.rings.integer_ring as integer_ring import sage.arith.all as arith import sage.misc.misc as misc -from sage.rings.finite_rings.constructor import FiniteField +from sage.rings.finite_rings.finite_field_constructor import FiniteField import number_field diff --git a/src/sage/rings/padics/factory.py b/src/sage/rings/padics/factory.py index 0e0f841c63f..70abdcf4d85 100644 --- a/src/sage/rings/padics/factory.py +++ b/src/sage/rings/padics/factory.py @@ -1003,7 +1003,7 @@ def Qq(q, prec = DEFAULT_PREC, type = 'capped-rel', modulus = None, names=None, res_name = names + '0' if modulus is None: - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF modulus = PolynomialRing(base, 'x')(GF(p**k, res_name).modulus().change_ring(ZZ)) return ExtensionFactory(base=base, premodulus=modulus, prec=prec, print_mode=print_mode, halt=halt, names=names, res_name=res_name, ram_name=ram_name, print_pos=print_pos, print_sep=print_sep, print_max_ram_terms=print_max_ram_terms, print_max_unram_terms=print_max_unram_terms, print_max_terse_terms=print_max_terse_terms, check=check, unram=True) @@ -2003,7 +2003,7 @@ def Zq(q, prec = DEFAULT_PREC, type = 'capped-abs', modulus = None, names=None, if res_name is None: res_name = names + '0' if modulus is None: - from sage.rings.finite_rings.constructor import FiniteField as GF + from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF if ram_name is None: ram_name = str(F[0][0]) modulus = PolynomialRing(base, 'x')(GF(q, res_name).modulus().change_ring(ZZ)) diff --git a/src/sage/rings/padics/padic_base_generic.py b/src/sage/rings/padics/padic_base_generic.py index 48ff3ef2418..08ab098135d 100644 --- a/src/sage/rings/padics/padic_base_generic.py +++ b/src/sage/rings/padics/padic_base_generic.py @@ -310,7 +310,7 @@ def zeta(self, n=None): else: raise ValueError("No, %sth root of unity in self"%n) else: - from sage.rings.finite_rings.constructor import GF + from sage.rings.finite_rings.finite_field_constructor import GF return self.teichmuller(GF(self.prime()).zeta(n).lift()) def zeta_order(self): diff --git a/src/sage/rings/padics/padic_generic.py b/src/sage/rings/padics/padic_generic.py index 02d56d65d96..0a57afcb397 100644 --- a/src/sage/rings/padics/padic_generic.py +++ b/src/sage/rings/padics/padic_generic.py @@ -313,7 +313,7 @@ def residue_class_field(self): sage: k Finite Field of size 3 """ - from sage.rings.finite_rings.constructor import GF + from sage.rings.finite_rings.finite_field_constructor import GF return GF(self.prime()) def residue_field(self): diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index ce5f60b0771..73521baeb32 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -269,7 +269,7 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: (a // b) * b + a % b 3 + 2*5^4 + 5^5 + 3*5^6 + 5^7 + O(5^16) - The alternative definition: + The alternative definition:: sage: a 3 + 2*5^4 + 5^5 + 3*5^6 + 5^7 + O(5^20) diff --git a/src/sage/rings/padics/unramified_extension_generic.py b/src/sage/rings/padics/unramified_extension_generic.py index d731901b789..4c5d5961833 100644 --- a/src/sage/rings/padics/unramified_extension_generic.py +++ b/src/sage/rings/padics/unramified_extension_generic.py @@ -21,7 +21,7 @@ from padic_extension_generic import pAdicExtensionGeneric -from sage.rings.finite_rings.constructor import GF +from sage.rings.finite_rings.finite_field_constructor import GF class UnramifiedExtensionGeneric(pAdicExtensionGeneric): """ diff --git a/src/sage/rings/polynomial/evaluation.pxd b/src/sage/rings/polynomial/evaluation.pxd new file mode 100644 index 00000000000..5aa6928dfb0 --- /dev/null +++ b/src/sage/rings/polynomial/evaluation.pxd @@ -0,0 +1,11 @@ +from sage.libs.flint.fmpz_poly cimport fmpz_poly_t +from sage.libs.ntl.types cimport ZZX_c +from sage.libs.mpfr cimport mpfr_t +from sage.libs.mpfi cimport mpfi_t + +cdef fmpz_poly_evaluation_mpfr(mpfr_t res, const fmpz_poly_t poly, const mpfr_t a) +cdef fmpz_poly_evaluation_mpfi(mpfi_t res, const fmpz_poly_t poly, const mpfi_t a) + +cdef ZZX_evaluation_mpfr(mpfr_t res, ZZX_c poly, const mpfr_t a) +cdef ZZX_evaluation_mpfi(mpfi_t res, ZZX_c poly, const mpfi_t a) + diff --git a/src/sage/rings/polynomial/evaluation.pyx b/src/sage/rings/polynomial/evaluation.pyx new file mode 100644 index 00000000000..986fbf78aa9 --- /dev/null +++ b/src/sage/rings/polynomial/evaluation.pyx @@ -0,0 +1,98 @@ +r""" +Fast evaluation of polynomials (Horner's rule) + +This file provides fast evaluation of integer polynomials with a real value. We +consider flint and NTL polynomials and values mpfr_t and mpfi_t. If you intend +to implement more it would be better to find a template strategy instead of +duplicating the code. + +The code in this file is mostly Sage agnostic and only does library calls. + +For appropriate testing see +:mod:`~sage.rings.polynomial.polynomial_integer_dense_flint` and +:mod:`~sage.rings.polynomial.polynomial_integer_dense_ntl`. + +.. TODO:: + + Integrate these functions into + :mod:`~sage.rings.polynomial.polynomial_compiled` +""" +#***************************************************************************** +# Copyright (C) 2016 Vincent Delecroix <20100.delecroix@gmail.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.libs.mpfr cimport * +from sage.libs.mpfi cimport * +from sage.libs.gmp.mpz cimport * +from sage.libs.gmp.mpq cimport * +from sage.libs.flint.fmpz cimport * +from sage.libs.flint.fmpz_poly cimport * +from sage.libs.ntl.ZZ cimport * +from sage.libs.ntl.ZZX cimport * + +cdef fmpz_poly_evaluation_mpfr(mpfr_t res, const fmpz_poly_t poly, const mpfr_t a): + cdef mpz_t c + cdef long i + + mpfr_set_ui(res, 0, MPFR_RNDN) + mpz_init(c) + + for i in range(fmpz_poly_degree(poly), -1, -1): + mpfr_mul(res, res, a, MPFR_RNDN) + if not fmpz_is_zero(fmpz_poly_get_coeff_ptr(poly, i)): + fmpz_poly_get_coeff_mpz(c, poly, i) + mpfr_add_z(res, res, c, MPFR_RNDN) + + mpz_clear(c) + +cdef fmpz_poly_evaluation_mpfi(mpfi_t res, const fmpz_poly_t poly, const mpfi_t a): + cdef mpz_t c + cdef long i + + mpfi_set_ui(res, 0) + mpz_init(c) + + for i in range(fmpz_poly_degree(poly), -1, -1): + mpfi_mul(res, res, a) + if not fmpz_is_zero(fmpz_poly_get_coeff_ptr(poly, i)): + fmpz_poly_get_coeff_mpz(c, poly, i) + mpfi_add_z(res, res, c) + + mpz_clear(c) + + +cdef ZZX_evaluation_mpfr(mpfr_t res, ZZX_c poly, const mpfr_t a): + cdef mpz_t c + cdef long i + + mpfr_set_ui(res, 0, MPFR_RNDN) + mpz_init(c) + + for i in range(ZZX_deg(poly), -1, -1): + mpfr_mul(res, res, a, MPFR_RNDN) + if not ZZ_IsZero(ZZX_coeff(poly, i)): + ZZX_getitem_as_mpz(c, &poly, i) + mpfr_add_z(res, res, c, MPFR_RNDN) + + mpz_clear(c) + +cdef ZZX_evaluation_mpfi(mpfi_t res, ZZX_c poly, const mpfi_t a): + cdef mpz_t c + cdef long i + + mpfi_set_ui(res, 0) + mpz_init(c) + + for i in range(ZZX_deg(poly), -1, -1): + mpfi_mul(res, res, a) + if not ZZ_IsZero(ZZX_coeff(poly, i)): + ZZX_getitem_as_mpz(c, &poly, i) + mpfi_add_z(res, res, c) + + mpz_clear(c) diff --git a/src/sage/rings/polynomial/laurent_polynomial.pyx b/src/sage/rings/polynomial/laurent_polynomial.pyx index 75ac398f472..a2740da751e 100644 --- a/src/sage/rings/polynomial/laurent_polynomial.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial.pyx @@ -2,7 +2,15 @@ r""" Elements of Laurent polynomial rings """ -from sage.rings.integer import Integer +#***************************************************************************** +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.rings.integer cimport Integer from sage.structure.element import is_Element, coerce_binop from sage.misc.latex import latex import sage.misc.latex @@ -320,10 +328,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial_generic): def __getitem__(self, i): """ - With a tuple (i,j) as argument, - return the Laurent polynomial `\sum_{k=i}^{j-1} c_k t^k` - where ``self`` is `\sum_k c_k t^k`, - otherwise return the coefficient of `t^i`. + Return the `i`-th coefficient of ``self``. EXAMPLES:: @@ -340,18 +345,29 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial_generic): 0 sage: f = -5/t^(10) + 1/3 + t + t^2 - 10/3*t^3; f -5*t^-10 + 1/3 + t + t^2 - 10/3*t^3 + + Slicing is deprecated:: + sage: f[-10:2] + doctest:...: DeprecationWarning: polynomial slicing with a start index is deprecated, use list() and slice the resulting list instead + See http://trac.sagemath.org/18940 for details. -5*t^-10 + 1/3 + t sage: f[0:] 1/3 + t + t^2 - 10/3*t^3 + sage: f[:3] + -5*t^-10 + 1/3 + t + t^2 + sage: f[-14:5:2] + Traceback (most recent call last): + ... + NotImplementedError: polynomial slicing with a step is not defined """ if isinstance(i, slice): - start = i.start if i.start is not None else 0 - stop = i.stop if i.stop is not None else self.__u.degree() - f = self.__u[start-self.__n:stop-self.__n] + start = i.start - self.__n if i.start is not None else 0 + stop = i.stop - self.__n if i.stop is not None else self.__u.degree() + 1 + f = self.__u[start:stop:i.step] # deprecation(18940) return LaurentPolynomial_univariate(self._parent, f, self.__n) - else: - return self.__u[i-self.__n] + + return self.__u[i - self.__n] def __iter__(self): """ @@ -641,19 +657,23 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial_generic): raise ValueError("exponent must be an integer") return LaurentPolynomial_univariate(self._parent, self.__u**right, self.__n*right) - def __floordiv__(LaurentPolynomial_univariate self, RingElement rhs): + cpdef RingElement _floordiv_(self, RingElement rhs): """ Perform division with remainder and return the quotient. EXAMPLES:: sage: L. = LaurentPolynomialRing(QQ) - sage: f = x**3 + x^-3 + sage: f = x^3 + x^-3 sage: g = x^-1 + x sage: f // g x^-2 - 1 + x^2 sage: g * (f // g) == f True + sage: f // 1 + x^-3 + x^3 + sage: 1 // f + 0 """ cdef LaurentPolynomial_univariate right = rhs return LaurentPolynomial_univariate(self._parent, @@ -2029,23 +2049,27 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial_generic): ans._poly = self._poly * (right)._poly return ans - def __floordiv__(LaurentPolynomial_mpair self, RingElement right): + cpdef RingElement _floordiv_(self, RingElement right): """ Perform division with remainder and return the quotient. EXAMPLES:: sage: L. = LaurentPolynomialRing(QQ) - sage: f = x**3 + y^-3 + sage: f = x^3 + y^-3 sage: g = y + x sage: f // g x^5*y^-3 - x^4*y^-2 + x^3*y^-1 - sage: h = x + y**(-1) + sage: h = x + y^(-1) sage: f // h x^2 - x*y^-1 + y^-2 sage: h * (f // h) == f True + sage: f // 1 + x^3 + y^-3 + sage: 1 // f + 0 TESTS: diff --git a/src/sage/rings/polynomial/multi_polynomial_element.py b/src/sage/rings/polynomial/multi_polynomial_element.py index 2b236665a4e..59f4494f876 100644 --- a/src/sage/rings/polynomial/multi_polynomial_element.py +++ b/src/sage/rings/polynomial/multi_polynomial_element.py @@ -1443,7 +1443,7 @@ def __nonzero__(self): """ return self._MPolynomial_element__element.dict()!={} - def __floordiv__(self,right): + def _floordiv_(self, right): r""" Quotient of division of self by other. This is denoted //. @@ -1464,9 +1464,6 @@ def __floordiv__(self,right): sage: type(0//y) """ - if type(self) is not type(right) or self.parent() is not right.parent(): - self, right = canonical_coercion(self, right) - return self // right # this looks like recursion, but, in fact, it may be that self, right are a totally new composite type # handle division by monomials without using Singular if len(right.dict()) == 1: P = self.parent() diff --git a/src/sage/rings/polynomial/multi_polynomial_ideal_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_ideal_libsingular.pyx index d3dcf92883f..6bc895724fc 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ideal_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ideal_libsingular.pyx @@ -38,21 +38,13 @@ Two examples from the Mathematica documentation (done in Sage): """ #***************************************************************************** -# -# Sage: System for Algebra and Geometry Experimentation -# # Copyright (C) 2007 Martin Albrecht # Copyright (C) 2007 William Stein # -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# The full text of the GPL is available at: -# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** @@ -66,8 +58,6 @@ from sage.libs.singular.decl cimport OPT_REDTAIL, singular_options, kInterRed, t from sage.libs.singular.decl cimport pp_Mult_nn, p_Delete, n_Delete from sage.libs.singular.decl cimport rIsPluralRing -from sage.structure.parent_base cimport ParentWithBase - from sage.rings.polynomial.multi_polynomial_libsingular cimport new_MP from sage.rings.polynomial.plural cimport new_NCP diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index 00b2e6d1dc6..b086636bbba 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -222,8 +222,6 @@ from sage.arith.all import gcd from sage.structure.element import coerce_binop from sage.structure.parent cimport Parent -from sage.structure.parent_base cimport ParentWithBase -from sage.structure.parent_gens cimport ParentWithGens from sage.structure.category_object cimport CategoryObject from sage.structure.element cimport EuclideanDomainElement @@ -1634,9 +1632,9 @@ cdef class MPolynomialRing_libsingular(MPolynomialRing_generic): cdef number *n cdef number *denom - if not self is f._parent: + if self is not f._parent: f = self._coerce_c(f) - if not self is g._parent: + if self is not g._parent: g = self._coerce_c(g) if not f._poly: @@ -1737,9 +1735,9 @@ cdef class MPolynomialRing_libsingular(MPolynomialRing_generic): """ cdef poly *m = p_ISet(1,self._ring) - if not self is f._parent: + if self is not f._parent: f = self._coerce_c(f) - if not self is g._parent: + if self is not g._parent: g = self._coerce_c(g) if f._poly == NULL: @@ -1939,7 +1937,7 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn 0 """ self._poly = NULL - self._parent = parent + self._parent = parent self._parent_ring = singular_ring_reference(parent._ring) def __dealloc__(self): @@ -3889,7 +3887,7 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn else: return False - def __floordiv__(MPolynomial_libsingular self, right): + cpdef RingElement _floordiv_(self, RingElement right): """ Perform division with remainder and return the quotient. @@ -3935,20 +3933,15 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn cdef poly *temp cdef poly *p - _self = self - - if not isinstance(right, MPolynomial_libsingular) \ - or (parent is not (right)._parent): - _right = parent._coerce_c(right) - else: - _right = right - if right.is_zero(): raise ZeroDivisionError if self._parent._base.is_finite() and self._parent._base.characteristic() > 1<<29: raise NotImplementedError, "Division of multivariate polynomials over prime fields with characteristic > 2^29 is not implemented." + _self = self + _right = right + if r.ringtype != 0: if r.ringtype == 4: P = parent.change_ring(RationalField()) @@ -5402,7 +5395,7 @@ cdef inline MPolynomial_libsingular new_MP(MPolynomialRing_libsingular parent, p Singular data structure is used elsewhere. """ cdef MPolynomial_libsingular p = MPolynomial_libsingular.__new__(MPolynomial_libsingular) - p._parent = parent + p._parent = parent p._parent_ring = singular_ring_reference(parent._ring) p._poly = juice p_Normalize(p._poly, p._parent_ring) diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx index 40cf4d02d69..f25a79df93b 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx @@ -2,9 +2,9 @@ r""" Base class for multivariate polynomial rings """ -from sage.structure.parent_gens cimport ParentWithGens import sage.misc.latex import multi_polynomial_ideal +from sage.structure.parent cimport Parent from term_order import TermOrder from sage.rings.integer_ring import ZZ from sage.rings.polynomial.polydict import PolyDict @@ -332,7 +332,7 @@ cdef class MPolynomialRing_generic(sage.rings.ring.CommutativeRing): return D def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) + return (left)._richcmp(right, op) cpdef int _cmp_(left, right) except -2: if not is_MPolynomialRing(right): diff --git a/src/sage/rings/polynomial/multi_polynomial_sequence.py b/src/sage/rings/polynomial/multi_polynomial_sequence.py index 8a96961aaca..955d809f102 100644 --- a/src/sage/rings/polynomial/multi_polynomial_sequence.py +++ b/src/sage/rings/polynomial/multi_polynomial_sequence.py @@ -163,7 +163,7 @@ from sage.structure.sequence import Sequence, Sequence_generic from sage.rings.infinity import Infinity -from sage.rings.finite_rings.constructor import FiniteField as GF +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.rings.finite_rings.finite_field_base import FiniteField from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.quotient_ring import is_QuotientRing 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 b44b9e2dfd4..93a023ac9d5 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 @@ -203,14 +203,13 @@ def _comp_list(self): sage: K = Qp(13,7) sage: R. = K[] - sage: a = t[0:1] + sage: a = t[:1] sage: a._comp_list() sage: a 0 """ if self.degree() == -1 and self._valbase == infinity: self._list = [] - return self._list polylist = self._poly.list() polylen = len(polylist) self._list = [self.base_ring()(polylist[i], absprec = self._relprecs[i]) << self._valbase for i in range(polylen)] \ @@ -380,8 +379,7 @@ def lift(self): def __getitem__(self, n): """ - Returns the coefficient of x^n if `n` is an integer, - returns the monomials of self of degree in slice `n` if `n` is a slice. + Return the `n`-th coefficient of ``self``. EXAMPLES:: @@ -390,40 +388,53 @@ def __getitem__(self, n): sage: a = 13^7*t^3 + K(169,4)*t - 13^4 sage: a[1] 13^2 + O(13^4) - sage: a[1:2] + + Slices can be used to truncate polynomials:: + + sage: a[:2] + (13^2 + O(13^4))*t + (12*13^4 + 12*13^5 + 12*13^6 + 12*13^7 + 12*13^8 + 12*13^9 + 12*13^10 + O(13^11)) + + Any other kind of slicing is deprecated or an error, see + :trac:`18940`:: + + sage: a[1:3] + doctest:...: DeprecationWarning: polynomial slicing with a start index is deprecated, use list() and slice the resulting list instead + See http://trac.sagemath.org/18940 for details. (13^2 + O(13^4))*t + sage: a[1:3:2] + Traceback (most recent call last): + ... + NotImplementedError: polynomial slicing with a step is not defined """ + d = len(self._relprecs) # = degree + 1 if isinstance(n, slice): - start, stop = n.start, n.stop + start, stop, step = n.start, n.stop, n.step + if step is not None: + raise NotImplementedError("polynomial slicing with a step is not defined") if start is None: start = 0 - elif start < 0: - start = len(self._relprecs) + start - if start < 0: - raise IndexError("list index out of range") - if stop > len(self._relprecs) or stop is None: - stop = len(self._relprecs) - elif stop < 0: - stop = len(self._relprecs) + stop - if stop < 0: - raise IndexError("list index out of range") - if start >= stop: - return Polynomial_padic_capped_relative_dense(self.parent(), []) else: - return Polynomial_padic_capped_relative_dense(self.parent(), - (self._poly[start:stop], self._valbase, - [infinity]*start + self._relprecs[start:stop], False, - None if self._valaddeds is None else [infinity]*start - + self._valaddeds[start:stop], - None if self._list is None else [self.base_ring()(0)] - * start + self._list[start:stop]), construct = True) - else: - if n >= len(self._relprecs): - return self.base_ring()(0) - if not self._list is None: - return self._list[n] - return self.base_ring()(self.base_ring().prime_pow(self._valbase) - * self._poly[n], absprec = self._valbase + self._relprecs[n]) + if start < 0: + start = 0 + from sage.misc.superseded import deprecation + deprecation(18940, "polynomial slicing with a start index is deprecated, use list() and slice the resulting list instead") + if stop is None or stop > d: + stop = d + values = ([self.base_ring().zero()] * start + + [self[i] for i in xrange(start, stop)]) + return self.parent()(values) + + try: + n = n.__index__() + except AttributeError: + raise TypeError("list indices must be integers, not {0}".format(type(n).__name__)) + + if n < 0 or n >= d: + return self.base_ring().zero() + if self._list is not None: + return self._list[n] + return self.base_ring()(self.base_ring().prime_pow(self._valbase) + * self._poly[n], absprec = self._valbase + self._relprecs[n]) def _add_(self, right): """ diff --git a/src/sage/rings/polynomial/pbori.pxd b/src/sage/rings/polynomial/pbori.pxd index 7879f567871..89e9217c604 100644 --- a/src/sage/rings/polynomial/pbori.pxd +++ b/src/sage/rings/polynomial/pbori.pxd @@ -1,6 +1,3 @@ - -from sage.structure.parent_base cimport ParentWithBase -from sage.structure.parent_gens cimport ParentWithGens from sage.rings.polynomial.multi_polynomial_ring_generic cimport \ MPolynomialRing_generic from sage.rings.polynomial.multi_polynomial cimport MPolynomial diff --git a/src/sage/rings/polynomial/pbori.pyx b/src/sage/rings/polynomial/pbori.pyx index 393aadfa015..7dcd1f42265 100644 --- a/src/sage/rings/polynomial/pbori.pyx +++ b/src/sage/rings/polynomial/pbori.pyx @@ -194,7 +194,7 @@ from sage.misc.randstate import current_randstate from sage.misc.long cimport pyobject_to_long import sage.misc.weak_dict from sage.rings.integer import Integer -from sage.rings.finite_rings.constructor import FiniteField as GF +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.rings.polynomial.polynomial_element cimport Polynomial from sage.rings.polynomial.multi_polynomial_ideal import MPolynomialIdeal @@ -1771,7 +1771,7 @@ def get_var_mapping(ring, other): """ my_names = list(ring._names) # we need .index(.) - if isinstance(other, (ParentWithGens,BooleanMonomialMonoid)): + if isinstance(other, (Parent, BooleanMonomialMonoid)): indices = range(other.ngens()) ovar_names = other._names else: @@ -2196,8 +2196,6 @@ cdef class BooleanMonomial(MonoidElement): See class documentation for parameters. """ - - _parent = parent self._ring = parent._ring self._pbmonom = PBMonom_Constructor((self._ring)._pbring) @@ -2898,10 +2896,9 @@ cdef class BooleanPolynomial(MPolynomial): use the appropriate ``__call__`` method in the parent. """ def __init__(self, parent): - self._parent = parent + self._parent = parent self._pbpoly = PBPoly_Constructor_ring((parent)._pbring) - def _repr_(self): """ EXAMPLE:: diff --git a/src/sage/rings/polynomial/plural.pyx b/src/sage/rings/polynomial/plural.pyx index 650918aa013..88586a685be 100644 --- a/src/sage/rings/polynomial/plural.pyx +++ b/src/sage/rings/polynomial/plural.pyx @@ -131,7 +131,6 @@ from sage.rings.ring import check_default_category from sage.structure.element cimport CommutativeRingElement, Element, ModuleElement from sage.structure.factory import UniqueFactory from sage.structure.parent cimport Parent -from sage.structure.parent_base cimport ParentWithBase from sage.structure.parent_gens cimport ParentWithGens from sage.rings.polynomial.term_order import TermOrder @@ -977,9 +976,9 @@ cdef class NCPolynomialRing_plural(Ring): cdef number *n cdef number *denom - if not self is f._parent: + if self is not f._parent: f = self._coerce_c(f) - if not self is g._parent: + if self is not g._parent: g = self._coerce_c(g) if(r != currRing): rChangeCurrRing(r) @@ -1106,9 +1105,9 @@ cdef class NCPolynomialRing_plural(Ring): """ cdef poly *m = p_ISet(1,self._ring) - if not self is f._parent: + if self is not f._parent: f = self._coerce_c(f) - if not self is g._parent: + if self is not g._parent: g = self._coerce_c(g) if f._poly == NULL: @@ -1363,12 +1362,12 @@ cdef class NCPolynomial_plural(RingElement): 0 """ self._poly = NULL - self._parent = parent + self._parent = parent def __dealloc__(self): # TODO: Warn otherwise! # for some mysterious reason, various things may be NULL in some cases - if self._parent is not None and (self._parent)._ring != NULL and self._poly != NULL: + if self._parent is not None and (self._parent)._ring != NULL and self._poly != NULL: p_Delete(&self._poly, (self._parent)._ring) # def __call__(self, *x, **kwds): # ? @@ -2673,7 +2672,7 @@ cdef inline NCPolynomial_plural new_NCP(NCPolynomialRing_plural parent, """ cdef NCPolynomial_plural p = NCPolynomial_plural.__new__(NCPolynomial_plural) - p._parent = parent + p._parent = parent p._poly = juice p_Normalize(p._poly, parent._ring) return p diff --git a/src/sage/rings/polynomial/polynomial_element.pxd b/src/sage/rings/polynomial/polynomial_element.pxd index c9eedac5fb0..677138ae93d 100644 --- a/src/sage/rings/polynomial/polynomial_element.pxd +++ b/src/sage/rings/polynomial/polynomial_element.pxd @@ -23,6 +23,8 @@ cdef class Polynomial(CommutativeAlgebraElement): # may return a new element if not possible to modify inplace cdef _inplace_truncate(self, long n) + cdef get_unsafe(self, Py_ssize_t i) + cdef class Polynomial_generic_dense(Polynomial): cdef Polynomial_generic_dense _new_c(self, list coeffs, Parent P) cdef list __coeffs diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index e6b71f2c9aa..5fc51e5ce59 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -43,6 +43,7 @@ TESTS:: cdef is_FractionField, is_RealField, is_ComplexField cdef ZZ, QQ, RR, CC, RDF, CDF +cimport cython from cpython.number cimport PyNumber_TrueDivide import operator, copy, re @@ -58,6 +59,7 @@ import sage.rings.fraction_field_element import sage.rings.infinity as infinity from sage.misc.sage_eval import sage_eval from sage.misc.latex import latex +from sage.misc.long cimport pyobject_to_long from sage.structure.factorization import Factorization from sage.structure.element import coerce_binop @@ -74,7 +76,7 @@ from sage.rings.complex_double import is_ComplexDoubleField, CDF from sage.rings.real_mpfi import is_RealIntervalField from sage.structure.element import generic_power -from sage.structure.element cimport parent_c as parent +from sage.structure.element cimport parent_c as parent, have_same_parent_c from sage.structure.element cimport (Element, RingElement, ModuleElement, MonoidElement, coercion_model) @@ -86,7 +88,6 @@ from sage.rings.padics.generic_nodes import is_pAdicRing, is_pAdicField from sage.rings.integral_domain import is_IntegralDomain from sage.structure.category_object cimport normalize_names -from sage.structure.parent_gens cimport ParentWithGens from sage.misc.derivative import multi_derivative @@ -806,7 +807,7 @@ cdef class Polynomial(CommutativeAlgebraElement): ['push 0.0'] """ from sage.ext.fast_eval import fast_float_arg, fast_float_constant - var = (self._parent)._names[0] + var = self._parent._names[0] if len(vars) == 0: x = fast_float_arg(0) elif var in vars: @@ -942,6 +943,79 @@ cdef class Polynomial(CommutativeAlgebraElement): return self.degree() >= 0 def __getitem__(self, n): + r""" + Return the `n`-th coefficient of ``self``. + + .. WARNING:: + + If `P` is a polynomial of degree `d`, then ``P[i]`` + returns `0` when `i < 0` or `i > d`. This behaviour + intentionally differs from that of lists: if `L` is a list + of length `n`, then Python defines ``L[-i] = L[n - i]`` + for `0 < i \le n``. The definition used here is more + meaningful for polynomials, since it can be extended + immediately to Laurent series, for example. + + EXAMPLES: + + We illustrate the difference between polynomials and lists + when negative indices are involved:: + + sage: R. = QQ[] + sage: f = x + 2 + sage: f[-1] + 0 + sage: list(f)[-1] + 1 + + Slices can be used to truncate polynomials:: + + sage: pol = R(range(8)); pol + 7*x^7 + 6*x^6 + 5*x^5 + 4*x^4 + 3*x^3 + 2*x^2 + x + sage: pol[:6] + 5*x^5 + 4*x^4 + 3*x^3 + 2*x^2 + x + + Any other kind of slicing is deprecated or an error, see + :trac:`18940`:: + + sage: f[1:3] + doctest:...: DeprecationWarning: polynomial slicing with a start index is deprecated, use list() and slice the resulting list instead + See http://trac.sagemath.org/18940 for details. + x + sage: f[1:3:2] + Traceback (most recent call last): + ... + NotImplementedError: polynomial slicing with a step is not defined + """ + cdef Py_ssize_t d = self.degree() + 1 + if isinstance(n, slice): + start, stop, step = n.start, n.stop, n.step + if step is not None: + raise NotImplementedError("polynomial slicing with a step is not defined") + if start is None: + start = 0 + else: + if start < 0: + start = 0 + from sage.misc.superseded import deprecation + deprecation(18940, "polynomial slicing with a start index is deprecated, use list() and slice the resulting list instead") + if stop is None or stop > d: + stop = d + values = ([self.base_ring().zero()] * start + + [self.get_unsafe(i) for i in xrange(start, stop)]) + return self.parent()(values) + + cdef long k = pyobject_to_long(n) + if k < 0 or k >= d: + return self.base_ring().zero() + return self.get_unsafe(k) + + cdef get_unsafe(self, Py_ssize_t i): + """ + Return the `i`-th coefficient of ``self``. + + Used as building block for a generic :meth:`__getitem__`. + """ raise NotImplementedError def __iter__(self): @@ -1020,7 +1094,7 @@ cdef class Polynomial(CommutativeAlgebraElement): for i from 0<= i <= self.degree(): if i == 1: # we delay the hashing until now to not waste it on a constant poly - var_name_hash = hash((self._parent)._names[0]) + var_name_hash = hash(self._parent._names[0]) # I'm assuming (incorrectly) that hashes of zero indicate that the element is 0. # This assumption is not true, but I think it is true enough for the purposes and it # it allows us to write fast code that omits terms with 0 coefficients. This is @@ -2269,8 +2343,7 @@ cdef class Polynomial(CommutativeAlgebraElement): """ raise IndexError("polynomials are immutable") - - def __floordiv__(self,right): + cpdef RingElement _floordiv_(self, RingElement right): """ Quotient of division of self by other. This is denoted //. @@ -3692,7 +3765,7 @@ cdef class Polynomial(CommutativeAlgebraElement): from sage.rings.number_field.number_field_base import is_NumberField from sage.rings.number_field.number_field_rel import is_RelativeNumberField from sage.rings.number_field.all import NumberField - from sage.rings.finite_rings.constructor import is_FiniteField + from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.finite_rings.integer_mod_ring import is_IntegerModRing from sage.rings.integer_ring import is_IntegerRing @@ -7949,8 +8022,12 @@ cdef class Polynomial_generic_dense(Polynomial): def __hash__(self): return self._hash_c() - def __getitem__(self, n): + @cython.boundscheck(False) + @cython.wraparound(False) + cdef get_unsafe(self, Py_ssize_t n): """ + Return the `n`-th coefficient of ``self``. + EXAMPLES:: sage: R. = RDF[] @@ -7964,25 +8041,8 @@ cdef class Polynomial_generic_dense(Polynomial): 0.0 sage: f[:3] 40.0*x^2 + 10.0*x + 1.0 - sage: f[2:5] - 80.0*x^4 + 80.0*x^3 + 40.0*x^2 - sage: f[2:] - 32.0*x^5 + 80.0*x^4 + 80.0*x^3 + 40.0*x^2 """ - if isinstance(n, slice): - start, stop = n.start, n.stop - if start <= 0: - start = 0 - zeros = [] - elif start > 0: - zeros = [self._parent.base_ring().zero()] * start - if stop is None: - stop = len(self.__coeffs) - return self._parent(zeros + self.__coeffs[start:stop]) - else: - if n < 0 or n >= len(self.__coeffs): - return self.base_ring().zero() - return self.__coeffs[n] + return self.__coeffs[n] def _unsafe_mutate(self, n, value): """ @@ -8035,18 +8095,25 @@ cdef class Polynomial_generic_dense(Polynomial): TESTS: - Check that #13048 has been fixed:: + Check that :trac:`13048` and :trac:`2034` are fixed:: sage: R. = QQbar[] - sage: x//x + sage: x // x 1 - sage: x//1 + sage: x // 1 x - + sage: x // int(1) + x + sage: x //= int(1); x + x + sage: int(1) // x # check that this doesn't segfault + Traceback (most recent call last): + ... + AttributeError: type object 'int' has no attribute 'base_ring' """ - P = (self)._parent - if right.parent() == P: - return Polynomial.__floordiv__(self, right) + if have_same_parent_c(self, right): + return (self)._floordiv_(right) + P = parent(self) d = P.base_ring()(right) cdef Polynomial_generic_dense res = (self)._new_c([c // d for c in (self).__coeffs], P) res.__normalize() diff --git a/src/sage/rings/polynomial/polynomial_element_generic.py b/src/sage/rings/polynomial/polynomial_element_generic.py index eddd02fbb0f..ca82acfa529 100644 --- a/src/sage/rings/polynomial/polynomial_element_generic.py +++ b/src/sage/rings/polynomial/polynomial_element_generic.py @@ -372,8 +372,7 @@ def __normalize(self): def __getitem__(self,n): """ - Return the `n`-th coefficient of this polynomial if `n` is an integer, - returns the monomials of self of degree in slice `n` if `n` is a slice. + Return the `n`-th coefficient of this polynomial. Negative indexes are allowed and always return 0 (so you can view the polynomial as embedding Laurent series). @@ -393,30 +392,53 @@ def __getitem__(self,n): sage: R. = PolynomialRing(RealField(19), sparse=True) sage: f = (2-3.5*x)^3; f -42.875*x^3 + 73.500*x^2 - 42.000*x + 8.0000 - sage: f[1:3] - 73.500*x^2 - 42.000*x + + Using slices, we can truncate polynomials:: + sage: f[:2] -42.000*x + 8.0000 - sage: f[2:] - -42.875*x^3 + 73.500*x^2 + + Any other kind of slicing is deprecated or an error:: + + sage: f[1:3] + doctest:...: DeprecationWarning: polynomial slicing with a start index is deprecated, use list() and slice the resulting list instead + See http://trac.sagemath.org/18940 for details. + 73.500*x^2 - 42.000*x + sage: f[1:3:2] + Traceback (most recent call last): + ... + NotImplementedError: polynomial slicing with a step is not defined + sage: f["hello"] + Traceback (most recent call last): + ... + TypeError: list indices must be integers, not str """ if isinstance(n, slice): - start, stop = n.start, n.stop - if start < 0: + d = self.degree() + 1 + start, stop, step = n.start, n.stop, n.step + if step is not None: + raise NotImplementedError("polynomial slicing with a step is not defined") + if start is None: start = 0 - if stop is None: - stop = len(self.__coeffs) + 1 - v = {} + else: + if start < 0: + start = 0 + from sage.misc.superseded import deprecation + deprecation(18940, "polynomial slicing with a start index is deprecated, use list() and slice the resulting list instead") + if stop is None or stop > d: + stop = d x = self.__coeffs - for k in x.keys(): - if start <= k and k < stop: - v[k] = x[k] - P = self.parent() - return P(v) - else: - if n not in self.__coeffs: - return self.base_ring()(0) + v = {k: x[k] for k in x.keys() if start <= k < stop} + return self.parent()(v) + + try: + n = n.__index__() + except AttributeError: + raise TypeError("list indices must be integers, not {0}".format(type(n).__name__)) + try: return self.__coeffs[n] + except KeyError: + return self.base_ring().zero() def _unsafe_mutate(self, n, value): r""" diff --git a/src/sage/rings/polynomial/polynomial_gf2x.pyx b/src/sage/rings/polynomial/polynomial_gf2x.pyx index fce912da12a..c802beea58b 100644 --- a/src/sage/rings/polynomial/polynomial_gf2x.pyx +++ b/src/sage/rings/polynomial/polynomial_gf2x.pyx @@ -62,8 +62,10 @@ cdef class Polynomial_GF2X(Polynomial_template): pass Polynomial_template.__init__(self, parent, x, check, is_gen, construct) - def __getitem__(self, i): + cdef get_unsafe(self, Py_ssize_t i): """ + Return the `i`-th coefficient of ``self``. + EXAMPLES:: sage: P. = GF(2)[] @@ -73,31 +75,13 @@ cdef class Polynomial_GF2X(Polynomial_template): 1 sage: f[1] 0 - sage: f[-5:50] == f + sage: f[:50] == f True - sage: f[1:] - x^3 + x^2 + sage: f[:3] + x^2 + 1 """ - cdef type t - cdef long c = 0 - cdef Polynomial_template r - if isinstance(i, slice): - start, stop = i.start, i.stop - if start < 0: - start = 0 - if stop > celement_len(&self.x, (self)._cparent) or stop is None: - stop = celement_len(&self.x, (self)._cparent) - x = (self)._parent.gen() - v = [self[t] for t from start <= t < stop] - - t = type(self) - r = t.__new__(t) - Polynomial_template.__init__(r, (self)._parent, v) - return r << start - else: - if 0 <= i < GF2X_NumBits(self.x): - c = GF2_conv_to_long(GF2X_coeff(self.x, i)) - return self._parent.base_ring()(c) + cdef long c = GF2_conv_to_long(GF2X_coeff(self.x, i)) + return self._parent._base(c) def _pari_(self, variable=None): """ @@ -303,7 +287,7 @@ def GF2X_BuildIrred_list(n): sage: GF(2)['x'](GF2X_BuildIrred_list(33)) x^33 + x^6 + x^3 + x + 1 """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField cdef GF2X_c f GF2 = FiniteField(2) GF2X_BuildIrred(f, int(n)) @@ -323,7 +307,7 @@ def GF2X_BuildSparseIrred_list(n): sage: GF(2)['x'](GF2X_BuildSparseIrred_list(33)) x^33 + x^10 + 1 """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField cdef GF2X_c f GF2 = FiniteField(2) GF2X_BuildSparseIrred(f, int(n)) @@ -343,7 +327,7 @@ def GF2X_BuildRandomIrred_list(n): True """ from sage.misc.randstate import current_randstate - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField cdef GF2X_c tmp, f GF2 = FiniteField(2) current_randstate().set_seed_ntl(False) diff --git a/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx b/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx index 46943a56c4e..18ed1a1732a 100644 --- a/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx @@ -58,7 +58,10 @@ from sage.libs.flint.fmpz_poly cimport fmpz_poly_reverse, fmpz_poly_revert_serie from sage.libs.flint.ntl_interface cimport fmpz_set_ZZ, fmpz_poly_set_ZZX, fmpz_poly_get_ZZX from sage.libs.ntl.ZZX cimport * from sage.rings.integer cimport smallInteger +from sage.rings.real_mpfr cimport RealNumber, RealField_class +from sage.rings.real_mpfi cimport RealIntervalFieldElement +from sage.rings.polynomial.evaluation cimport fmpz_poly_evaluation_mpfr, fmpz_poly_evaluation_mpfi cdef extern from "limits.h": long LONG_MAX @@ -280,6 +283,65 @@ cdef class Polynomial_integer_dense_flint(Polynomial): fmpz_poly_set_coeff_mpz(self.__poly, i, (a).value) sig_off() + def _eval_mpfr_(self, RealNumber a): + r""" + Evaluate this polynomial on the real number element ``a``. + + This method uses Horner's rule and might not be appropriate for + polynomials of large degree. + + TESTS:: + + sage: R. = PolynomialRing(ZZ, implementation='FLINT') + sage: (x+1)._eval_mpfr_(RR(1.2)) + 2.20000000000000 + sage: (x^2)._eval_mpfr_(RR(2.2)) + 4.84000000000000 + sage: R.zero()._eval_mpfr_(RR(2.1)) + 0.000000000000000 + sage: R.one()._eval_mpfr_(RR(2.1)) + 1.00000000000000 + + sage: p = x^3 - 2*x^2 + x -1 + sage: p._eval_mpfr_(RR(1.3)) + -0.883000000000000 + """ + cdef RealNumber res = a._new() + sig_on() + fmpz_poly_evaluation_mpfr(res.value, self.__poly, a.value) + sig_off() + return res + + def _eval_mpfi_(self, RealIntervalFieldElement a): + r""" + Evaluate this polynomial on the real interval ``a``. + + This method uses Horner's rule and might not be appropriate for + polynomials of large degree. + + TESTS:: + + sage: R. = PolynomialRing(ZZ, implementation='FLINT') + sage: (x+1)._eval_mpfi_(RIF(1.5)) + 2.5000000000000000? + sage: (x^2)._eval_mpfi_(RIF(1.333,1.334)) + 1.78? + sage: R.zero()._eval_mpfi_(RIF(2.1)) + 0 + sage: R.one()._eval_mpfi_(RIF(2.1)) + 1 + + sage: p = x^3 - x^2 - x - 1 + sage: r = p.roots(RIF, multiplicities=False)[0] + sage: p._eval_mpfi_(r) + 0.?e-27 + """ + cdef RealIntervalFieldElement res = a._new() + sig_on() + fmpz_poly_evaluation_mpfi(res.value, self.__poly, a.value) + sig_off() + return res + def __call__(self, *x, **kwds): """ Calls this polynomial with the given parameters, which can be @@ -287,8 +349,9 @@ cdef class Polynomial_integer_dense_flint(Polynomial): method. If the argument is not simply an integer (``int``, ``long`` or - ``Integer``) or a polynomial (of the same type as ``self``), - the call is passed on to the generic implementation in the + ``Integer``) a real number (``RealNumber``) a real interval + (``RealIntervalFieldElement``) or a polynomial (of the same type as + ``self``), the call is passed on to the generic implementation in the ``Polynomial`` class. EXAMPLES: @@ -347,6 +410,11 @@ cdef class Polynomial_integer_dense_flint(Polynomial): return z + if isinstance(x0, RealNumber): + return self._eval_mpfr_( x0) + if isinstance(x0, RealIntervalFieldElement): + return self._eval_mpfi_( x0) + return Polynomial.__call__(self, *x, **kwds) cpdef Integer content(self): @@ -412,9 +480,9 @@ cdef class Polynomial_integer_dense_flint(Polynomial): return Polynomial_integer_dense_flint, \ (self.parent(), self.list(), False, self.is_gen()) - def __getitem__(self, n): - r""" - Returns coefficient of x^n, or zero if n is negative. + cdef get_unsafe(self, Py_ssize_t n): + """ + Return the `n`-th coefficient of ``self``. EXAMPLES:: @@ -431,29 +499,14 @@ cdef class Polynomial_integer_dense_flint(Polynomial): sage: f[-1] 0 sage: f = 1 + x + 2*x^2 + 3*x^3 + 4*x^4 + 5*x^5 - sage: f[2:4] - 3*x^3 + 2*x^2 - sage: f[-2:4] + sage: f[:4] 3*x^3 + 2*x^2 + x + 1 - sage: f[4:100] - 5*x^5 + 4*x^4 + sage: f[:100] + 5*x^5 + 4*x^4 + 3*x^3 + 2*x^2 + x + 1 """ - cdef long k cdef Integer z = PY_NEW(Integer) - if isinstance(n, slice): - start = max(0, n.start) - stop = n.stop - if stop is None or stop > self.degree()+1: - stop = self.degree() + 1 - v = [self[k] for k from start <= k < stop] - P = self.parent() - return P([0] * int(start) + v) - else: - if n < 0 or n > fmpz_poly_degree(self.__poly): - return z - else: - fmpz_poly_get_coeff_mpz(z.value, self.__poly, n) - return z + fmpz_poly_get_coeff_mpz(z.value, self.__poly, n) + return z def _repr(self, name=None, bint latex=False): """ @@ -846,8 +899,8 @@ cdef class Polynomial_integer_dense_flint(Polynomial): EXAMPLES:: sage: x = polygen(ZZ) - sage: p1 = 1 + x + x**2 + x**4 - sage: p2 = -2 + 3*x**2 + 5*x**4 + sage: p1 = 1 + x + x^2 + x^4 + sage: p2 = -2 + 3*x^2 + 5*x^4 sage: p1._mul_trunc_(p2, 4) 3*x^3 + x^2 - 2*x - 2 sage: (p1*p2).truncate(4) @@ -1026,7 +1079,7 @@ cdef class Polynomial_integer_dense_flint(Polynomial): EXAMPLES:: sage: x = polygen(ZZ) - sage: p = 1+x+2*x**2 + sage: p = 1+x+2*x^2 sage: q5 = p.inverse_series_trunc(5) sage: q5 -x^4 + 3*x^3 - x^2 - x + 1 @@ -1443,7 +1496,7 @@ cdef class Polynomial_integer_dense_flint(Polynomial): sage: f.factor_mod(7) (2) * x * (x + 5)^2 """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField p = Integer(p) if not p.is_prime(): raise ValueError, "p must be prime" diff --git a/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx b/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx index dea138cc76f..7e1f7e3bdff 100644 --- a/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx +++ b/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx @@ -52,6 +52,8 @@ from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.rings.integer import Integer from sage.rings.integer cimport Integer +from sage.rings.real_mpfr cimport RealNumber, RealField_class +from sage.rings.real_mpfi cimport RealIntervalFieldElement from sage.libs.all import pari, pari_gen from sage.structure.factorization import Factorization @@ -63,6 +65,8 @@ import sage.rings.polynomial.polynomial_ring from sage.libs.ntl.ZZX cimport * +from sage.rings.polynomial.evaluation cimport ZZX_evaluation_mpfr, ZZX_evaluation_mpfi + cdef class Polynomial_integer_dense_ntl(Polynomial): r""" A dense polynomial over the integers, implemented via NTL. @@ -253,6 +257,64 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): ZZ_to_mpz(z.value, &y) return z + def _eval_mpfr_(self, RealNumber a): + r""" + Evaluate this polynomial on the real number element ``a``. + + This method uses Horner's rule and might not be appropriate for + polynomials of large degree. + + TESTS:: + + sage: R. = PolynomialRing(ZZ, implementation='NTL') + sage: (x+1)._eval_mpfr_(RR(1.2)) + 2.20000000000000 + sage: (x^2)._eval_mpfr_(RR(2.2)) + 4.84000000000000 + sage: R.zero()._eval_mpfr_(RR(2.1)) + 0.000000000000000 + sage: R.one()._eval_mpfr_(RR(2.1)) + 1.00000000000000 + + sage: p = x^3 - 2*x^2 + x -1 + sage: p._eval_mpfr_(RR(1.3)) + -0.883000000000000 + """ + cdef RealNumber res = a._new() + sig_on() + ZZX_evaluation_mpfr(res.value, self.__poly, a.value) + sig_off() + return res + + def _eval_mpfi_(self, RealIntervalFieldElement a): + r""" + Evaluate this polynomial on the real interval ``a``. + + This method uses Horner's rule and might not be appropriate for + polynomials of large degree. + + TESTS:: + + sage: R. = PolynomialRing(ZZ, implementation='NTL') + sage: (x+1)._eval_mpfi_(RIF(1.5)) + 2.5000000000000000? + sage: (x^2)._eval_mpfi_(RIF(1.333,1.334)) + 1.78? + sage: R.zero()._eval_mpfi_(RIF(2.1)) + 0 + sage: R.one()._eval_mpfi_(RIF(2.1)) + 1 + + sage: p = x^3 - x^2 - x - 1 + sage: r = p.roots(RIF, multiplicities=False)[0] + sage: p._eval_mpfi_(r) + 0.?e-27 + """ + cdef RealIntervalFieldElement res = a._new() + sig_on() + ZZX_evaluation_mpfi(res.value, self.__poly, a.value) + sig_off() + return res def __reduce__(self): r""" @@ -270,10 +332,9 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): return Polynomial_integer_dense_ntl, \ (self.parent(), self.list(), False, self.is_gen()) - def __getitem__(self, n): - r""" - Returns coefficient of the monomial of degree `n` if `n` is an integer, - returns the monomials of self of degree in slice `n` if `n` is a slice. + cdef get_unsafe(self, Py_ssize_t n): + """ + Return the `n`-th coefficient of ``self``. EXAMPLES:: @@ -290,31 +351,14 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): sage: f[-1] 0 sage: f = 1 + x + 2*x^2 + 3*x^3 + 4*x^4 + 5*x^5 - sage: f[2:4] - 3*x^3 + 2*x^2 - sage: f[-2:4] + sage: f[:4] 3*x^3 + 2*x^2 + x + 1 - sage: f[4:100] - 5*x^5 + 4*x^4 + sage: f[:100] + 5*x^5 + 4*x^4 + 3*x^3 + 2*x^2 + x + 1 """ cdef Integer z = PY_NEW(Integer) - cdef long k - if isinstance(n, slice): - start, stop = n.start, n.stop - if stop > self.degree() + 1 or stop is None: - stop = self.degree() + 1 - start = max(0, start) - v = [self[k] for k from start <= k < stop] - P = self.parent() - return P([0] * int(start) + v) - else: - if n < 0 or n > ZZX_deg(self.__poly): - return z - else: - # Note that the NTL documentation blesses this direct access of the "rep" member in ZZX.txt. - # Check the "Miscellany" section. - ZZ_to_mpz(z.value, &self.__poly.rep.elts()[n]) - return z + ZZ_to_mpz(z.value, &self.__poly.rep.elts()[n]) + return z def _repr(self, name=None, bint latex=False): """ @@ -974,7 +1018,7 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): sage: f.factor_mod(7) (2) * x * (x + 5)^2 """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField p = Integer(p) if not p.is_prime(): raise ValueError, "p must be prime" diff --git a/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx b/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx index 4f1d449ae57..f5bf5d46f87 100644 --- a/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx +++ b/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx @@ -180,10 +180,9 @@ cdef class Polynomial_dense_mod_n(Polynomial): """ return self.__poly - def __getitem__(self, n): + cdef get_unsafe(self, Py_ssize_t n): """ - Returns coefficient of the monomial of degree `n` if `n` is an integer, - returns the monomials of self of degree in slice `n` if `n` is a slice. + Return the `n`-th coefficient of ``self``. EXAMPLES:: @@ -193,20 +192,10 @@ cdef class Polynomial_dense_mod_n(Polynomial): 4*x^4 + x^3 + 13*x^2 + 10*x + 5 sage: f[2] 13 - sage: f[1:3] - 13*x^2 + 10*x - """ - if isinstance(n, slice): - start, stop = n.start, n.stop - R = self.base_ring() - if start < 0: - start = 0 - if stop > self.__poly.degree()+1 or stop is None: - stop = self.__poly.degree()+1 - v = [R(self.__poly[k]._sage_()) for k in range(start,stop)] - return self.parent()([0]*int(start) + v) - else: - return self.parent().base_ring()(self.__poly[n]._sage_()) + sage: f[:3] + 13*x^2 + 10*x + 5 + """ + return self._parent._base(self.__poly[n]._sage_()) def _unsafe_mutate(self, n, value): n = int(n) @@ -657,10 +646,9 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): cdef long i return [ zz_p_rep(zz_pX_GetCoeff(self.x, i)) for i from 0 <= i <= zz_pX_deg(self.x) ] - def __getitem__(self, n): + cdef get_unsafe(self, Py_ssize_t n): """ - Returns coefficient of the monomial of degree `n` if `n` is an integer, - returns the monomials of self of degree in slice `n` if `n` is a slice. + Return the `n`-th coefficient of ``self``. EXAMPLES:: @@ -669,28 +657,12 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): sage: f = Polynomial_dense_modn_ntl_zz(R,[2, 1])^7 sage: f[3] 60 - sage: f[3:6] - 84*x^5 + 80*x^4 + 60*x^3 - sage: f[-5:50] == f + sage: f[:6] + 84*x^5 + 80*x^4 + 60*x^3 + 72*x^2 + 48*x + 28 + sage: f[:50] == f True - sage: f[6:] - x^7 + 14*x^6 - """ - if isinstance(n, slice): - start, stop = n.start, n.stop - R = self.base_ring() - if start < 0: - start = 0 - if stop > zz_pX_deg(self.x)+1 or stop is None: - stop = zz_pX_deg(self.x)+1 - v = [ zz_p_rep(zz_pX_GetCoeff(self.x, t)) for t from start <= t < stop ] - return Polynomial_dense_modn_ntl_zz(self._parent, v, check=False) << start - else: - R = self._parent._base - if n < 0 or n > zz_pX_deg(self.x): - return R(0) - else: - return R(zz_p_rep(zz_pX_GetCoeff(self.x, n))) + """ + return self._parent._base(zz_p_rep(zz_pX_GetCoeff(self.x, n))) def _unsafe_mutate(self, n, value): self.c.restore_c() @@ -914,7 +886,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): sig_off() return q, r - def __floordiv__(self, right): + cpdef RingElement _floordiv_(self, RingElement right): """ Returns the whole part of self/right, without remainder. @@ -929,9 +901,6 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): sage: f - q*g x + 1 """ - if not have_same_parent_c(self, right): - self, right = canonical_coercion(self, right) - return self // right cdef Polynomial_dense_modn_ntl_zz numer = self cdef Polynomial_dense_modn_ntl_zz denom = right cdef Polynomial_dense_modn_ntl_zz q = numer._new() @@ -1218,10 +1187,9 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): def list(self): return [self._parent._base(self[n]) for n from 0 <= n <= self.degree()] - def __getitem__(self, n): + cdef get_unsafe(self, Py_ssize_t n): """ - Returns coefficient of the monomial of degree `n` if `n` is an integer, - returns the monomials of self of degree in slice `n` if `n` is a slice. + Return the `n`-th coefficient of ``self``. EXAMPLES:: @@ -1230,34 +1198,16 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): sage: f = Polynomial_dense_modn_ntl_ZZ(R,[2,1])^7 sage: f[3] 560 - sage: f[3:6] - 84*x^5 + 280*x^4 + 560*x^3 - sage: f[-5:50] == f + sage: f[:6] + 84*x^5 + 280*x^4 + 560*x^3 + 672*x^2 + 448*x + 128 + sage: f[:50] == f True - sage: f[6:] - x^7 + 14*x^6 - """ - if isinstance(n, slice): - start, stop = n.start, n.stop - R = self.base_ring() - if start < 0: - start = 0 - if stop > ZZ_pX_deg(self.x)+1 or stop is None: - stop = ZZ_pX_deg(self.x)+1 - v = [ self[t] for t from start <= t < stop ] - return Polynomial_dense_modn_ntl_ZZ(self._parent, v, check=False) << start - else: - R = self._parent._base - if n < 0 or n > ZZ_pX_deg(self.x): - return R(0) - + """ self.c.restore_c() - cdef Integer z - # TODO, make this faster cdef ntl_ZZ_p ntl = ntl_ZZ_p(0, self.c) ntl.x = ZZ_pX_coeff(self.x, n) - return R(ntl._integer_()) + return self._parent._base(ntl._integer_()) def _unsafe_mutate(self, n, value): self.c.restore_c() @@ -1472,7 +1422,7 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): sig_off() return q, r - def __floordiv__(self, right): + cpdef RingElement _floordiv_(self, RingElement right): """ Returns the whole part of self/right, without remainder. @@ -1487,9 +1437,6 @@ cdef class Polynomial_dense_modn_ntl_ZZ(Polynomial_dense_mod_n): sage: f - q*g x + 1 """ - if not have_same_parent_c(self, right): - self, right = canonical_coercion(self, right) - return self // right cdef Polynomial_dense_modn_ntl_ZZ numer = self cdef Polynomial_dense_modn_ntl_ZZ denom = right cdef Polynomial_dense_modn_ntl_ZZ q = numer._new() diff --git a/src/sage/rings/polynomial/polynomial_quotient_ring.py b/src/sage/rings/polynomial/polynomial_quotient_ring.py index 6567a86199e..57414c4669a 100644 --- a/src/sage/rings/polynomial/polynomial_quotient_ring.py +++ b/src/sage/rings/polynomial/polynomial_quotient_ring.py @@ -14,14 +14,26 @@ False sage: 1 in S True + +TESTS:: + + sage: Pol. = CBF[] + sage: Quo. = Pol.quotient(y^3) + sage: CBF.zero()*y + 0 + sage: ((x - 1)/(x + 1))(1 + y) + -0.2500000000000000*y^2 + 0.5000000000000000*y """ -################################################################################ +#***************************************************************************** # Copyright (C) 2005, 2006 William Stein -# Distributed under the terms of the GNU General Public License (GPL) -# The full text of the GPL is available at: +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ -################################################################################ +#***************************************************************************** import six import sage.rings.number_field.all @@ -40,11 +52,10 @@ from sage.categories.commutative_algebras import CommutativeAlgebras from sage.structure.category_object import normalize_names -from sage.structure.parent_gens import ParentWithGens from sage.rings.polynomial.infinite_polynomial_ring import GenDictWithBasering from sage.all import sage_eval, parent -from sage.structure.element import Element + def PolynomialQuotientRing(ring, polynomial, names=None): r""" @@ -637,7 +648,7 @@ def construction(self): -- Simon King (2010-05) """ from sage.categories.pushout import QuotientFunctor - return QuotientFunctor([self.modulus()]*self.base(),self.variable_names(),self.is_field()), self.base() + return QuotientFunctor([self.modulus()]*self.base(),self.variable_names()), self.base() @cached_method def base_ring(self): diff --git a/src/sage/rings/polynomial/polynomial_rational_flint.pyx b/src/sage/rings/polynomial/polynomial_rational_flint.pyx index 37d2dfa9678..7747fb235ec 100644 --- a/src/sage/rings/polynomial/polynomial_rational_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_rational_flint.pyx @@ -376,38 +376,27 @@ cdef class Polynomial_rational_flint(Polynomial): """ return smallInteger(fmpq_poly_degree(self.__poly)) - def __getitem__(self, n): + cdef get_unsafe(self, Py_ssize_t n): """ - Returns coefficient of the monomial of degree `n` if `n` is an integer, - returns the monomials of self of degree in slice `n` if `n` is a slice. + Return the `n`-th coefficient of ``self``. INPUT: - - ``n`` - Degree of the monomial whose coefficient is to be returned - or a slice. + - ``n`` -- Degree of the monomial whose coefficient is to be + returned. EXAMPLES:: sage: R. = QQ[] sage: f = 1 + t + t^2/2 + t^3/3 + t^4/4 - sage: f[-1], f[0], f[3], f[5] # indirect doctest + sage: f[-1], f[0], f[3], f[5] # indirect doctest (0, 1, 1/3, 0) - sage: f[1:3] # indirect doctest - 1/2*t^2 + t + sage: f[:3] # indirect doctest + 1/2*t^2 + t + 1 """ cdef Rational z = Rational.__new__(Rational) - cdef Polynomial_rational_flint res = self._new() - cdef bint do_sig = _do_sig(self.__poly) - if isinstance(n, slice): - start, stop, step = n.indices(self.degree() + 1) - if do_sig: sig_str("FLINT exception") - fmpq_poly_get_slice(res.__poly, self.__poly, start, stop) - if do_sig: sig_off() - return res - else: - if 0 <= n and n < fmpq_poly_length(self.__poly): - fmpq_poly_get_coeff_mpq(z.value, self.__poly, n) - return z + fmpq_poly_get_coeff_mpq(z.value, self.__poly, n) + return z cpdef _unsafe_mutate(self, unsigned long n, value): """ @@ -829,8 +818,6 @@ cdef class Polynomial_rational_flint(Polynomial): sage: f = R.random_element(2000) sage: f - f/2 == 1/2 * f # indirect doctest True - sage: f[:1000] == f - f[1000:] # indirect doctest - True """ cdef Polynomial_rational_flint op2 = right cdef Polynomial_rational_flint res = self._new() @@ -2103,7 +2090,7 @@ cdef class Polynomial_rational_flint(Polynomial): sage: (x^5 + 2).factor_mod(5) (x + 2)^5 """ - from sage.rings.finite_rings.constructor import FiniteField + from sage.rings.finite_rings.finite_field_constructor import FiniteField p = Integer(p) if not p.is_prime(): diff --git a/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx b/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx index 193ef9922c2..3291e232174 100644 --- a/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx +++ b/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx @@ -181,8 +181,10 @@ cdef class PolynomialRealDense(Polynomial): self._coeffs = check_reallocarray(self._coeffs, i+1, sizeof(mpfr_t)) self._degree = i - def __getitem__(self, ix): + cdef get_unsafe(self, Py_ssize_t i): """ + Return the `i`-th coefficient of ``self``. + EXAMPLES:: sage: from sage.rings.polynomial.polynomial_real_mpfr_dense import PolynomialRealDense @@ -202,27 +204,9 @@ cdef class PolynomialRealDense(Polynomial): x^5 + 5.0*x^4 + 10.*x^3 + 10.*x^2 + 5.0*x + 1.0 sage: f[:3] 10.*x^2 + 5.0*x + 1.0 - sage: f[3:] - x^5 + 5.0*x^4 + 10.*x^3 - sage: f[1:4] - 10.*x^3 + 10.*x^2 + 5.0*x - """ - if isinstance(ix, slice): - if ix.stop is None: - chopped = self - else: - chopped = self.truncate(ix.stop) - if ix.start is None: - return chopped - else: - return (chopped >> ix.start) << ix.start - cdef RealNumber r = RealNumber(self._base_ring) - cdef Py_ssize_t i = ix - if 0 <= i <= self._degree: - mpfr_set(r.value, self._coeffs[i], self._base_ring.rnd) - else: - mpfr_set_ui(r.value, 0, self._base_ring.rnd) + cdef RealNumber r = RealNumber.__new__(RealNumber, self._base_ring) + mpfr_set(r.value, self._coeffs[i], self._base_ring.rnd) return r cdef PolynomialRealDense _new(self, Py_ssize_t degree): diff --git a/src/sage/rings/polynomial/polynomial_ring_constructor.py b/src/sage/rings/polynomial/polynomial_ring_constructor.py index d4aa4428241..d4e641c9f24 100644 --- a/src/sage/rings/polynomial/polynomial_ring_constructor.py +++ b/src/sage/rings/polynomial/polynomial_ring_constructor.py @@ -29,7 +29,7 @@ import sage.rings.padics.padic_base_leaves as padic_base_leaves from sage.rings.integer import Integer -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.finite_rings.integer_mod_ring import is_IntegerModRing from sage.misc.cachefunc import weak_cached_function diff --git a/src/sage/rings/polynomial/polynomial_singular_interface.py b/src/sage/rings/polynomial/polynomial_singular_interface.py index 6e7e53b3f9a..47846731560 100644 --- a/src/sage/rings/polynomial/polynomial_singular_interface.py +++ b/src/sage/rings/polynomial/polynomial_singular_interface.py @@ -53,7 +53,7 @@ from sage.rings.integer_ring import ZZ import sage.arith.all -import sage.rings.finite_rings.constructor +import sage.rings.finite_rings.finite_field_constructor class PolynomialRing_singular_repr: @@ -203,7 +203,7 @@ def _singular_(self, singular=singular): R._check_valid() if self.base_ring() is ZZ or self.base_ring().is_prime_field(): return R - if sage.rings.finite_rings.constructor.is_FiniteField(self.base_ring()) or\ + if sage.rings.finite_rings.finite_field_constructor.is_FiniteField(self.base_ring()) or\ (number_field.number_field_base.is_NumberField(self.base_ring()) and self.base_ring().is_absolute()): R.set_ring() #sorry for that, but needed for minpoly if singular.eval('minpoly') != "(" + self.__minpoly + ")": @@ -268,7 +268,7 @@ def _singular_init_(self, singular=singular): elif base_ring.is_prime_field(): self.__singular = singular.ring(self.characteristic(), _vars, order=order, check=False) - elif sage.rings.finite_rings.constructor.is_FiniteField(base_ring): + elif sage.rings.finite_rings.finite_field_constructor.is_FiniteField(base_ring): # not the prime field! gen = str(base_ring.gen()) r = singular.ring( "(%s,%s)"%(self.characteristic(),gen), _vars, order=order, check=False) @@ -355,7 +355,7 @@ def can_convert_to_singular(R): return False; base_ring = R.base_ring() - return ( sage.rings.finite_rings.constructor.is_FiniteField(base_ring) + return ( sage.rings.finite_rings.finite_field_constructor.is_FiniteField(base_ring) or is_RationalField(base_ring) or (base_ring.is_prime_field() and base_ring.characteristic() <= 2147483647) or is_RealField(base_ring) diff --git a/src/sage/rings/polynomial/polynomial_template.pxi b/src/sage/rings/polynomial/polynomial_template.pxi index 57f4550b27d..436df32923c 100644 --- a/src/sage/rings/polynomial/polynomial_template.pxi +++ b/src/sage/rings/polynomial/polynomial_template.pxi @@ -1,11 +1,15 @@ """ Polynomial Template for C/C++ Library Interfaces """ + #***************************************************************************** # Copyright (C) 2008 Martin Albrecht # Copyright (C) 2008 Robert Bradshaw # -# Distributed under the terms of the GNU General Public License (GPL) +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** @@ -75,7 +79,7 @@ cdef class Polynomial_template(Polynomial): .. note:: Implementations using this template MUST implement coercion from base - ring elements and ``__getitem__``. See + ring elements and :meth:`get_unsafe`. See :class:`~sage.rings.polynomial.polynomial_gf2x.Polynomial_GF2X` for an example. """ @@ -408,21 +412,28 @@ cdef class Polynomial_template(Polynomial): #assert(t._parent(tp) == t) return r,s,t - def __floordiv__(self, right): + cpdef RingElement _floordiv_(self, RingElement right): """ - EXAMPLE:: + EXAMPLES:: sage: P. = GF(2)[] sage: x//(x + 1) 1 sage: (x + 1)//x 1 + sage: F = GF(47) + sage: R. = F[] + sage: x // 1 + x + sage: x // F(1) + x + sage: 1 // x + 0 + sage: parent(x // 1) + Univariate Polynomial Ring in x over Finite Field of size 47 + sage: parent(1 // x) + Univariate Polynomial Ring in x over Finite Field of size 47 """ - # We can't use @coerce_binop for operators in cython classes, - # so we use sage.structure.element.bin_op to handle coercion. - if type(self) is not type(right) or \ - (self)._parent is not (right)._parent: - return bin_op(self, right, operator.mod) cdef Polynomial_template _right = right if celement_is_zero(&_right.x, (self)._cparent): diff --git a/src/sage/rings/polynomial/polynomial_zmod_flint.pyx b/src/sage/rings/polynomial/polynomial_zmod_flint.pyx index 1ac2804d7b2..ebf2a204f0f 100644 --- a/src/sage/rings/polynomial/polynomial_zmod_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_zmod_flint.pyx @@ -228,8 +228,10 @@ cdef class Polynomial_zmod_flint(Polynomial_template): sig_off() return 0 - def __getitem__(self, i): + cdef get_unsafe(self, Py_ssize_t i): """ + Return the `i`-th coefficient of ``self``. + EXAMPLES:: sage: P. = GF(32003)[] @@ -242,31 +244,13 @@ cdef class Polynomial_zmod_flint(Polynomial_template): 2252 sage: f[-1] 0 - sage: f[1:3] - 24998*x^2 + 29761*x - sage: f[-5:50] == f + sage: f[:2] + 29761*x + 2252 + sage: f[:50] == f True """ - cdef type t - cdef unsigned long c = 0 - cdef Polynomial_template r - if isinstance(i, slice): - start, stop = i.start, i.stop - if start < 0: - start = 0 - if stop > celement_len(&self.x, (self)._cparent) or stop is None: - stop = celement_len(&self.x, (self)._cparent) - x = (self)._parent.gen() - v = [self[t] for t from start <= t < stop] - - t = type(self) - r = t.__new__(t) - Polynomial_template.__init__(r, (self)._parent, v) - return r << start - else: - if 0 <= i < nmod_poly_length(&self.x): - c = nmod_poly_get_coeff_ui(&self.x, i) - return self._parent.base_ring()(c) + cdef unsigned long c = nmod_poly_get_coeff_ui(&self.x, i) + return self._parent.base_ring()(c) def __call__(self, *x, **kwds): """ @@ -518,10 +502,14 @@ cdef class Polynomial_zmod_flint(Polynomial_template): sage: P.=GF(7)[] sage: b = P(range(10)); c = P(range(5, 15)) - sage: (b._mul_trunc_opposite(c, 10))[10:18] - 5*a^17 + 2*a^16 + 6*a^15 + 4*a^14 + 4*a^13 + 5*a^10 - sage: (b._mul_trunc_opposite(c, 18))[18:] - 0 + sage: b._mul_trunc_opposite(c, 10) + 5*a^17 + 2*a^16 + 6*a^15 + 4*a^14 + 4*a^13 + 5*a^10 + 2*a^9 + 5*a^8 + 4*a^5 + 4*a^4 + 6*a^3 + 2*a^2 + 5*a + sage: list(b._mul_trunc_opposite(c, 10))[10:18] + [5, 0, 0, 4, 4, 6, 2, 5] + sage: list(b*c)[10:18] + [5, 0, 0, 4, 4, 6, 2, 5] + sage: list(b._mul_trunc_opposite(c, 18))[18:] + [] TESTS:: diff --git a/src/sage/rings/polynomial/polynomial_zz_pex.pyx b/src/sage/rings/polynomial/polynomial_zz_pex.pyx index 9fa9111fb62..02ad5f9b334 100644 --- a/src/sage/rings/polynomial/polynomial_zz_pex.pyx +++ b/src/sage/rings/polynomial/polynomial_zz_pex.pyx @@ -147,8 +147,10 @@ cdef class Polynomial_ZZ_pEX(Polynomial_template): Polynomial_template.__init__(self, parent, x, check, is_gen, construct) - def __getitem__(self,i): + cdef get_unsafe(self, Py_ssize_t i): """ + Return the `i`-th coefficient of ``self``. + EXAMPLES:: sage: K.=GF(next_prime(2**60)**3) @@ -160,33 +162,14 @@ cdef class Polynomial_ZZ_pEX(Polynomial_template): 2*a + 1 sage: f[2] 0 - sage: f[1:4] - x^3 + (2*a + 1)*x - sage: f[-5:50] == f + sage: f[:2] + (2*a + 1)*x + a + sage: f[:50] == f True """ - cdef type t - cdef ZZ_pE_c c_pE - cdef Polynomial_template r - if isinstance(i, slice): - start, stop = i.start, i.stop - if start < 0: - start = 0 - if stop > celement_len(&self.x, (self)._cparent) or stop is None: - stop = celement_len(&self.x, (self)._cparent) - x = (self)._parent.gen() - v = [self[t] for t from start <= t < stop] - - t = type(self) - r = t.__new__(t) - Polynomial_template.__init__(r, (self)._parent, v) - return r << start - else: - self._parent._modulus.restore() - c_pE = ZZ_pEX_coeff(self.x, i) - - K = self._parent.base_ring() - return K(ZZ_pE_c_to_list(c_pE)) + self._parent._modulus.restore() + cdef ZZ_pE_c c_pE = ZZ_pEX_coeff(self.x, i) + return self._parent._base(ZZ_pE_c_to_list(c_pE)) def list(self): """ diff --git a/src/sage/rings/power_series_poly.pyx b/src/sage/rings/power_series_poly.pyx index 1f28994cdf1..e6756aa8e2f 100644 --- a/src/sage/rings/power_series_poly.pyx +++ b/src/sage/rings/power_series_poly.pyx @@ -415,63 +415,28 @@ cdef class PowerSeries_poly(PowerSeries): ... IndexError: coefficient not known sage: f[1:4] + doctest:...: DeprecationWarning: polynomial slicing with a start index is deprecated, use list() and slice the resulting list instead + See http://trac.sagemath.org/18940 for details. -17/5*t^3 + O(t^5) sage: R. = ZZ[[]] sage: f = (2-t)^5; f 32 - 80*t + 80*t^2 - 40*t^3 + 10*t^4 - t^5 - sage: f[2:4] - 80*t^2 - 40*t^3 - sage: f[5:9] - -t^5 - sage: f[2:7:2] - 80*t^2 + 10*t^4 - sage: f[10:20] - 0 - sage: f[10:] - 0 sage: f[:4] 32 - 80*t + 80*t^2 - 40*t^3 - sage: f = 1 + t^3 - 4*t^4 + O(t^7) ; f 1 + t^3 - 4*t^4 + O(t^7) - sage: f[2:4] - t^3 + O(t^7) - sage: f[4:9] - -4*t^4 + O(t^7) - sage: f[2:7:2] - -4*t^4 + O(t^7) - sage: f[10:20] - O(t^7) - sage: f[10:] - O(t^7) sage: f[:4] 1 + t^3 + O(t^7) """ if isinstance(n, slice): - # get values from slice object - start = n.start if n.start is not None else 0 - stop = self.prec() if n.stop is None else n.stop - if stop is infinity: stop = self.degree()+1 - step = 1 if n.step is None else n.step - - # find corresponding polynomial - poly = self.__f[start:stop] - if step is not None: - coeffs = poly.padded_list(stop) - for i in range(start, stop): - if (i-start) % step: - coeffs[i] = 0 - poly = self.__f.parent()(coeffs) - - # return the power series - return PowerSeries_poly(self._parent, poly, + return PowerSeries_poly(self._parent, self.polynomial()[n], prec=self._prec, check=False) elif n < 0: - return self.base_ring()(0) + return self.base_ring().zero() elif n > self.__f.degree(): if self._prec > n: - return self.base_ring()(0) + return self.base_ring().zero() else: raise IndexError("coefficient not known") return self.__f[n] diff --git a/src/sage/rings/power_series_ring_element.pyx b/src/sage/rings/power_series_ring_element.pyx index e2e07924116..d3cac0a1125 100644 --- a/src/sage/rings/power_series_ring_element.pyx +++ b/src/sage/rings/power_series_ring_element.pyx @@ -1401,7 +1401,7 @@ cdef class PowerSeries(AlgebraElement): sage: p.O(-5) Traceback (most recent call last): ... - ValueError: n must be at least 0 + ValueError: prec (= -5) must be non-negative """ if prec is infinity or prec >= self.prec(): return self diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 16fe40f13f6..aceb833f12e 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -487,7 +487,6 @@ import sage.rings.ring from sage.misc.fast_methods import Singleton from sage.structure.sage_object import SageObject -from sage.structure.parent_gens import ParentWithGens from sage.rings.real_mpfr import RR from sage.rings.real_mpfi import RealIntervalField, RIF, is_RealIntervalFieldElement from sage.rings.complex_field import ComplexField @@ -495,7 +494,6 @@ from sage.rings.complex_interval import is_ComplexIntervalFieldElement from sage.rings.polynomial.all import PolynomialRing from sage.rings.polynomial.polynomial_element import is_Polynomial -from sage.rings.polynomial.multi_polynomial import is_MPolynomial from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.rings.number_field.number_field import NumberField, QuadraticField, CyclotomicField diff --git a/src/sage/rings/real_arb.pyx b/src/sage/rings/real_arb.pyx index bea9afbc1b4..b38eb202a2e 100644 --- a/src/sage/rings/real_arb.pyx +++ b/src/sage/rings/real_arb.pyx @@ -2672,21 +2672,40 @@ cdef class RealBall(RingElement): # Elementary functions - def log(self): + def log(self, base=None): """ - Return the natural logarithm of this ball. + Return the logarithm of this ball. + + INPUT: + + - ``base`` (optional, positive real ball or number) -- if ``None``, + return the natural logarithm ``ln(self)``, otherwise, return the + general logarithm ``ln(self)/ln(base)`` EXAMPLES:: sage: RBF(3).log() [1.098612288668110 +/- 6.63e-16] + sage: RBF(3).log(2) + [1.584962500721156 +/- 7.53e-16] + sage: RBF(-1/3).log() nan + sage: RBF(3).log(-1) + nan + sage: RBF(2).log(0) + nan """ + cdef RealBall cst cdef RealBall res = self._new() if _do_sig(prec(self)): sig_on() arb_log(res.value, self.value, prec(self)) if _do_sig(prec(self)): sig_off() + if base is not None: + cst = self._parent.coerce(base).log() + if _do_sig(prec(self)): sig_on() + arb_div(res.value, res.value, cst.value, prec(self)) + if _do_sig(prec(self)): sig_off() return res def log1p(self): diff --git a/src/sage/rings/real_double.pyx b/src/sage/rings/real_double.pyx index 54eb73ed439..e1e2f5bae2d 100644 --- a/src/sage/rings/real_double.pyx +++ b/src/sage/rings/real_double.pyx @@ -2775,28 +2775,6 @@ def time_alloc_list(n): return l -def time_alloc(n): - """ - Allocate ``n`` :class:`RealDoubleElement` instances. - - EXAMPLES: - - Since this does not store anything in a python object, the created - elements will not be sent to the garbage collector. Therefore they - remain in the pool:: - - sage: from sage.rings.real_double import time_alloc, pool_stats - sage: pool_stats() - Used pool 0 / 0 times - Pool contains 7 / 50 items - sage: time_alloc(25) - sage: pool_stats() - Used pool 0 / 0 times - Pool contains 7 / 50 items - """ - cdef int i - for i from 0 <= i < n: - z = PY_NEW(RealDoubleElement) def pool_stats(): """ diff --git a/src/sage/rings/real_mpfi.pyx b/src/sage/rings/real_mpfi.pyx index 9c87df698a2..ddf0aadbfbb 100644 --- a/src/sage/rings/real_mpfi.pyx +++ b/src/sage/rings/real_mpfi.pyx @@ -668,6 +668,22 @@ cdef class RealIntervalField_class(sage.rings.ring.Field): x = (x,y) return RealIntervalFieldElement(self, x, base) + def algebraic_closure(self): + """ + Return the algebraic closure of this interval field, i.e., the + complex interval field with the same precision. + + EXAMPLES:: + + sage: RIF.algebraic_closure() + Complex Interval Field with 53 bits of precision + sage: RIF.algebraic_closure() is CIF + True + sage: RealIntervalField(100).algebraic_closure() + Complex Interval Field with 100 bits of precision + """ + return self.complex_field() + def construction(self): r""" Returns the functorial construction of ``self``, namely, completion of diff --git a/src/sage/rings/ring.pyx b/src/sage/rings/ring.pyx index 1d0dcfecddb..1636a0f6abc 100644 --- a/src/sage/rings/ring.pyx +++ b/src/sage/rings/ring.pyx @@ -69,7 +69,6 @@ AUTHORS: from sage.misc.cachefunc import cached_method from sage.structure.element cimport coercion_model -from sage.structure.parent_gens cimport ParentWithGens from sage.structure.parent cimport Parent from sage.structure.category_object import check_default_category from sage.misc.prandom import randint @@ -2179,7 +2178,7 @@ cdef class Field(PrincipalIdealDomain): import sage.rings.rational_field return sage.rings.rational_field.RationalField() else: - from sage.rings.finite_rings.constructor import GF + from sage.rings.finite_rings.finite_field_constructor import GF return GF(self.characteristic()) def algebraic_closure(self): diff --git a/src/sage/rings/universal_cyclotomic_field.py b/src/sage/rings/universal_cyclotomic_field.py index 53cf088e099..14c33abed0e 100644 --- a/src/sage/rings/universal_cyclotomic_field.py +++ b/src/sage/rings/universal_cyclotomic_field.py @@ -241,6 +241,11 @@ def _call_(self, x): sage: UCFtoQQbar = UCF.coerce_embedding() sage: UCFtoQQbar(UCF.gen(3)) # indirect doctest -0.500000000000000? + 0.866025403784439?*I + + Test that the bug reported in :trac:`19912` has been fixed:: + + sage: UCFtoQQbar(UCF.gen(4)+1) + I + 1 """ obj = x._obj QQbar = self.codomain() @@ -249,7 +254,7 @@ def _call_(self, x): k = obj.Conductor().sage() coeffs = obj.CoeffsCyc(k).sage() zeta = QQbar.zeta(k) - return QQbar(sum(coeffs[a] * zeta**a for a in range(1,k))) + return QQbar(sum(coeffs[a] * zeta**a for a in range(k))) class UniversalCyclotomicFieldElement(FieldElement): def __init__(self, parent, obj): @@ -457,13 +462,18 @@ def _symbolic_(self, R): e^(2/7*I*pi) sage: SR(E(5) + 2*E(5,2) + 3*E(5,3)) -sqrt(5) + 1/4*I*sqrt(2*sqrt(5) + 10) - 1/4*I*sqrt(-2*sqrt(5) + 10) - 3/2 + + Test that the bug reported in :trac:`19912` has been fixed:: + + sage: SR(1+E(4)) + I + 1 """ from sage.symbolic.constants import pi from sage.symbolic.all import i as I k = self._obj.Conductor().sage() coeffs = self._obj.CoeffsCyc(k).sage() s = R.zero() - for a in range(1,k): + for a in range(k): if coeffs[a]: s += coeffs[a] * (2*a*I*pi/k).exp() return s @@ -514,6 +524,13 @@ def to_cyclotomic_field(self, R=None): 0.309016994374947 + 0.951056516295154*I sage: CC(CF(x)) 0.309016994374947 + 0.951056516295154*I + + Test that the bug reported in :trac:`19912` has been fixed:: + + sage: a = 1+E(4); a + 1 + E(4) + sage: a.to_cyclotomic_field() + zeta4 + 1 """ from sage.rings.number_field.number_field import CyclotomicField k = self._obj.Conductor().sage() @@ -525,7 +542,7 @@ def to_cyclotomic_field(self, R=None): return R(obj.sage()) zeta = Rcan.gen() coeffs = obj.CoeffsCyc(k).sage() - return R(sum(coeffs[a] * zeta**a for a in range(1,k))) + return R(sum(coeffs[a] * zeta**a for a in range(k))) def __hash__(self): r""" diff --git a/src/sage/schemes/affine/affine_homset.py b/src/sage/schemes/affine/affine_homset.py index 5fe57cbbc62..c58379ff094 100644 --- a/src/sage/schemes/affine/affine_homset.py +++ b/src/sage/schemes/affine/affine_homset.py @@ -36,7 +36,7 @@ from sage.rings.rational_field import is_RationalField from sage.categories.fields import Fields from sage.categories.number_fields import NumberFields -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing import sage.schemes.generic.homset diff --git a/src/sage/schemes/affine/affine_morphism.py b/src/sage/schemes/affine/affine_morphism.py index 4107fcda2c9..927b6489d9c 100644 --- a/src/sage/schemes/affine/affine_morphism.py +++ b/src/sage/schemes/affine/affine_morphism.py @@ -37,7 +37,7 @@ from sage.rings.all import Integer from sage.arith.all import lcm, gcd from sage.rings.complex_field import ComplexField -from sage.rings.finite_rings.constructor import GF, is_PrimeFiniteField +from sage.rings.finite_rings.finite_field_constructor import GF, is_PrimeFiniteField from sage.rings.fraction_field import FractionField from sage.rings.fraction_field_element import FractionFieldElement from sage.rings.integer_ring import ZZ diff --git a/src/sage/schemes/affine/affine_space.py b/src/sage/schemes/affine/affine_space.py index f5eac681837..631fb17e050 100644 --- a/src/sage/schemes/affine/affine_space.py +++ b/src/sage/schemes/affine/affine_space.py @@ -18,7 +18,7 @@ from sage.rings.ring import is_Ring from sage.rings.rational_field import is_RationalField from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.categories.fields import Fields _Fields = Fields() diff --git a/src/sage/schemes/elliptic_curves/constructor.py b/src/sage/schemes/elliptic_curves/constructor.py index 4f58129adb0..bce0431a878 100644 --- a/src/sage/schemes/elliptic_curves/constructor.py +++ b/src/sage/schemes/elliptic_curves/constructor.py @@ -29,7 +29,7 @@ from sage.rings.finite_rings.integer_mod_ring import is_IntegerModRing from sage.rings.rational_field import is_RationalField from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.number_field.number_field import is_NumberField from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial from sage.rings.ring import is_Ring diff --git a/src/sage/schemes/elliptic_curves/ell_generic.py b/src/sage/schemes/elliptic_curves/ell_generic.py index 7e752b218b4..b3fa3f78755 100644 --- a/src/sage/schemes/elliptic_curves/ell_generic.py +++ b/src/sage/schemes/elliptic_curves/ell_generic.py @@ -552,23 +552,28 @@ def __call__(self, *args, **kwds): def _reduce_point(self, R, p): r""" - Reduces a point R on an ellipitc curve to the corresponding point on - the elliptic curve reduced modulo p. Used to coerce points between + Reduces a point R on an elliptic curve to the corresponding point on + the elliptic curve reduced modulo p. + + Used to coerce points between curves when p is a factor of the denominator of one of the coordinates. - This functionality is used internally in the \code{call} method for + This functionality is used internally in the ``call`` method for elliptic curves. INPUT: - R -- a point on an elliptic curve - p -- a prime + + - R -- a point on an elliptic curve + - p -- a prime OUTPUT: - S -- the corresponding point of the elliptic curve containing R, but - reduced modulo p + + S -- the corresponding point of the elliptic curve containing + R, but reduced modulo p EXAMPLES: + Suppose we have a point with large height on a rational elliptic curve whose denominator contains a factor of 11:: diff --git a/src/sage/schemes/elliptic_curves/ell_number_field.py b/src/sage/schemes/elliptic_curves/ell_number_field.py index 35d63eaa3cd..d8658eadaf3 100644 --- a/src/sage/schemes/elliptic_curves/ell_number_field.py +++ b/src/sage/schemes/elliptic_curves/ell_number_field.py @@ -909,7 +909,7 @@ def _scale_by_units(self): OUTPUT: - A model for this elliptic curve, optimally scaled with repect + A model for this elliptic curve, optimally scaled with respect to scaling by units, with respect to the logarithmic embedding of |c4|^(1/4)+|c6|^(1/6). No scaling by roots of unity is carried out, so there is no change when the unit rank is 0. diff --git a/src/sage/schemes/elliptic_curves/gal_reps_number_field.py b/src/sage/schemes/elliptic_curves/gal_reps_number_field.py index 88a1fc56847..c18a5b507fa 100644 --- a/src/sage/schemes/elliptic_curves/gal_reps_number_field.py +++ b/src/sage/schemes/elliptic_curves/gal_reps_number_field.py @@ -55,7 +55,7 @@ from sage.schemes.elliptic_curves.cm import cm_j_invariants from sage.rings.rational_field import QQ from sage.modules.free_module import VectorSpace -from sage.rings.finite_rings.constructor import GF +from sage.rings.finite_rings.finite_field_constructor import GF from sage.rings.integer import Integer from sage.misc.functional import cyclotomic_polynomial from sage.arith.all import legendre_symbol diff --git a/src/sage/schemes/elliptic_curves/isogeny_small_degree.py b/src/sage/schemes/elliptic_curves/isogeny_small_degree.py index 286315679da..e3723185996 100644 --- a/src/sage/schemes/elliptic_curves/isogeny_small_degree.py +++ b/src/sage/schemes/elliptic_curves/isogeny_small_degree.py @@ -838,6 +838,16 @@ def isogenies_5_1728(E): sage: isogenies_5_1728(E) [Isogeny of degree 5 from Elliptic Curve defined by y^2 = x^3 + x over Number Field in a with defining polynomial x^4 + 20*x^2 - 80 to Elliptic Curve defined by y^2 = x^3 + (-753/4*a^2-4399)*x + (2779*a^3+65072*a) over Number Field in a with defining polynomial x^4 + 20*x^2 - 80, Isogeny of degree 5 from Elliptic Curve defined by y^2 = x^3 + x over Number Field in a with defining polynomial x^4 + 20*x^2 - 80 to Elliptic Curve defined by y^2 = x^3 + (-753/4*a^2-4399)*x + (-2779*a^3-65072*a) over Number Field in a with defining polynomial x^4 + 20*x^2 - 80] + + See :trac:`19840`:: + + sage: K. = NumberField(x^4 - 5*x^2 + 5) + sage: E = EllipticCurve([a^2 + a + 1, a^3 + a^2 + a + 1, a^2 + a, 17*a^3 + 34*a^2 - 16*a - 37, 54*a^3 + 105*a^2 - 66*a - 135]) + sage: len(E.isogenies_prime_degree(5)) + 2 + sage: from sage.schemes.elliptic_curves.isogeny_small_degree import isogenies_5_1728 + sage: [phi.codomain().j_invariant() for phi in isogenies_5_1728(E)] + [19691491018752*a^2 - 27212977933632, 19691491018752*a^2 - 27212977933632] """ F = E.base_field() if E.j_invariant() != 1728: @@ -865,7 +875,7 @@ def isogenies_5_1728(E): # Type 2: if 5 is a square we have up to 4 (non-endomorphism) isogenies if square5: betas = sorted((x**4+20*a*x**2-80*a**2).roots(multiplicities=False)) - gammas = [a*(beta**2-2)/6 for beta in betas] + gammas = [(beta**2-2*a)/6 for beta in betas] isogs += [Ew.isogeny(x**2+beta*x+gamma, model=model) for beta,gamma in zip(betas,gammas)] [isog.set_pre_isomorphism(iso) for isog in isogs] return isogs diff --git a/src/sage/schemes/elliptic_curves/lseries_ell.py b/src/sage/schemes/elliptic_curves/lseries_ell.py index 551984a5506..11eb6e10f5a 100644 --- a/src/sage/schemes/elliptic_curves/lseries_ell.py +++ b/src/sage/schemes/elliptic_curves/lseries_ell.py @@ -79,8 +79,6 @@ def taylor_series(self, a=1, prec=53, series_prec=6, var='z'): sage: L.taylor_series(series_prec=3) -1.27685190980159e-23 + (7.23588070754027e-24)*z + 0.759316500288427*z^2 + O(z^3) # 32-bit -2.72911738151096e-23 + (1.54658247036311e-23)*z + 0.759316500288427*z^2 + O(z^3) # 64-bit - sage: L.taylor_series(series_prec=3)[2:] - 0.000000000000000 + 0.000000000000000*z + 0.759316500288427*z^2 + O(z^3) """ D = self.dokchitser(prec) return D.taylor_series(a, series_prec, var) diff --git a/src/sage/schemes/generic/algebraic_scheme.py b/src/sage/schemes/generic/algebraic_scheme.py index 7db1e72c54a..ce7f7d78a4c 100644 --- a/src/sage/schemes/generic/algebraic_scheme.py +++ b/src/sage/schemes/generic/algebraic_scheme.py @@ -138,7 +138,7 @@ from sage.rings.rational_field import is_RationalField from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.misc.cachefunc import cached_method from sage.misc.latex import latex diff --git a/src/sage/schemes/generic/homset.py b/src/sage/schemes/generic/homset.py index e7628c1c640..c50b1d16345 100644 --- a/src/sage/schemes/generic/homset.py +++ b/src/sage/schemes/generic/homset.py @@ -45,7 +45,7 @@ from sage.rings.morphism import is_RingHomomorphism from sage.rings.rational_field import is_RationalField -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.commutative_ring import is_CommutativeRing from sage.schemes.generic.scheme import AffineScheme, is_AffineScheme diff --git a/src/sage/schemes/hyperelliptic_curves/constructor.py b/src/sage/schemes/hyperelliptic_curves/constructor.py index 69678d4201d..c0ec6f2b656 100644 --- a/src/sage/schemes/hyperelliptic_curves/constructor.py +++ b/src/sage/schemes/hyperelliptic_curves/constructor.py @@ -22,7 +22,7 @@ from sage.rings.padics.all import is_pAdicField from sage.rings.rational_field import is_RationalField -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.polynomial.polynomial_element import is_Polynomial diff --git a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py index 703ffb8cf5e..94a01cecc6b 100644 --- a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py +++ b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py @@ -747,7 +747,7 @@ def points(self): $\mathbb{P}(1,3,1)$. The latter model has two points at infinity: $(1:1:0)$ and $(1:-1:0)$. """ - from sage.rings.finite_rings.constructor import zech_log_bound + from sage.rings.finite_rings.finite_field_constructor import zech_log_bound try: return self.__points except AttributeError: pass diff --git a/src/sage/schemes/plane_conics/constructor.py b/src/sage/schemes/plane_conics/constructor.py index fa3e0e42bd1..af27a507a1e 100644 --- a/src/sage/schemes/plane_conics/constructor.py +++ b/src/sage/schemes/plane_conics/constructor.py @@ -28,11 +28,11 @@ from sage.modules.free_module_element import vector from sage.quadratic_forms.quadratic_form import is_QuadraticForm from sage.rings.all import PolynomialRing -from sage.rings.finite_rings.constructor import is_PrimeFiniteField +from sage.rings.finite_rings.finite_field_constructor import is_PrimeFiniteField from sage.rings.integral_domain import is_IntegralDomain from sage.rings.rational_field import is_RationalField -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial from sage.rings.number_field.number_field import is_NumberField diff --git a/src/sage/schemes/plane_curves/constructor.py b/src/sage/schemes/plane_curves/constructor.py index 884fc87403f..7531bba9cbc 100644 --- a/src/sage/schemes/plane_curves/constructor.py +++ b/src/sage/schemes/plane_curves/constructor.py @@ -25,7 +25,7 @@ from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.structure.all import Sequence diff --git a/src/sage/schemes/product_projective/morphism.py b/src/sage/schemes/product_projective/morphism.py index 70fcb0c3d3f..109acb50ce4 100644 --- a/src/sage/schemes/product_projective/morphism.py +++ b/src/sage/schemes/product_projective/morphism.py @@ -9,7 +9,7 @@ sage: H = End(P1xP1) sage: H([x^2*u, y^2*v, x*v^2, y*u^2]) Scheme endomorphism of Product of projective spaces P^1 x P^1 over Rational Field - Defn: Defined by sending (x : y , u : v) to + Defn: Defined by sending (x : y , u : v) to (x^2*u : y^2*v , x*v^2 : y*u^2). """ #***************************************************************************** @@ -36,11 +36,11 @@ class ProductProjectiveSpaces_morphism_ring(SchemeMorphism_polynomial): sage: H = T.Hom(T) sage: H([x^2,y^2,z^2,w^2,u^2]) Scheme endomorphism of Product of projective spaces P^2 x P^1 over Rational Field - Defn: Defined by sending (x : y : z , w : u) to + Defn: Defined by sending (x : y : z , w : u) to (x^2 : y^2 : z^2 , w^2 : u^2). """ - def __init__(self, parent, polys, check = True): + def __init__(self, parent, polys, check=True): r""" The Python constructor. @@ -59,7 +59,7 @@ def __init__(self, parent, polys, check = True): sage: H = T.Hom(T) sage: H([x^2*u,y^2*w,z^2*u,w^2,u^2]) Scheme endomorphism of Product of projective spaces P^2 x P^1 over Rational Field - Defn: Defined by sending (x : y : z , w : u) to + Defn: Defined by sending (x : y : z , w : u) to (x^2*u : y^2*w : z^2*u , w^2 : u^2). :: @@ -71,6 +71,30 @@ def __init__(self, parent, polys, check = True): ... TypeError: polys (=[x^2*u, y^2*w, z^2*u, w^2, z*u]) must be multi-homogeneous of the same degrees (by component) + + :: + + sage: R. = PolynomialRing(QQ) + sage: Z. = ProductProjectiveSpaces([1,2],QQ) + sage: P. = ProductProjectiveSpaces([3,1],QQ) + sage: H = Hom(Z,P) + sage: f = H([a^2,b^2,a^2,a*b,a*x,b*z]); f + Scheme morphism: + From: Product of projective spaces P^1 x P^2 over Rational Field + To: Product of projective spaces P^3 x P^1 over Rational Field + Defn: Defined by sending (a : b , x : y : z) to + (a^2 : b^2 : a^2 : a*b , a*x : b*z). + + :: + + sage: Z. = ProductProjectiveSpaces([1,3],QQ) + sage: P. = ProductProjectiveSpaces([2,2],QQ) + sage: H = Hom(Z,P) + sage: f = H([a^2,b^2,c^2,x^2,y^2,z^2]) + Traceback (most recent call last): + ... + TypeError: polys (=[a^2, b^2, c^2, x^2, y^2, z^2]) must be + multi-homogeneous of the same degrees (by component) """ if check: #check multi-homogeneous @@ -81,12 +105,13 @@ def __init__(self, parent, polys, check = True): polys = [f.lift() for f in polys] target = parent.codomain().ambient_space() + dom = parent.domain().ambient_space() from sage.schemes.product_projective.space import is_ProductProjectiveSpaces if is_ProductProjectiveSpaces(target): splitpolys = target._factors(polys) for m in range(len(splitpolys)): - d = target._degree(splitpolys[m][0]) - if not all(d == target._degree(f) for f in splitpolys[m]): + d = dom._degree(splitpolys[m][0]) + if not all(d == dom._degree(f) for f in splitpolys[m]): raise TypeError("polys (=%s) must be multi-homogeneous of the same degrees (by component)"%polys) else: #we are mapping into some other kind of space @@ -121,7 +146,7 @@ def _repr_defn(self): Return a string representation of ``self``. OUTPUT: - + String. EXAMPLES:: @@ -139,7 +164,7 @@ def _repr_defn(self): s += '.' return s - def __call__(self, P, check = True): + def __call__(self, P, check=True): r""" Make morphisms of products of projective spaces callable. @@ -161,9 +186,52 @@ def __call__(self, P, check = True): sage: F = H([x^2*u,y^2*w,z^2*u,w^2,u^2]) sage: F(T([2,1,3,0,1])) (4/9 : 0 : 1 , 0 : 1) + + :: + + sage: PP. = ProductProjectiveSpaces(QQ,[1,1,1]) + sage: HP = End(PP) + sage: f = HP([v*x^2,w*y^2,z^2,u^2,v^2,w^2]) + sage: Q = PP([0,1,1,1,1,1]) + sage: f(Q) + (0 : 1 , 1 : 1 , 1 : 1) + + :: + + sage: PP. = ProductProjectiveSpaces([2,1], ZZ) + sage: Q = PP([1,1,1,2,1]) + sage: Z. = ProductProjectiveSpaces([1,2], ZZ) + sage: H = End(Z) + sage: f = H([a^3, b^3+a*b^2, x^2, y^2-z^2, z*y]) + sage: f(Q) + Traceback (most recent call last): + ... + TypeError: (1 : 1 : 1 , 2 : 1) fails to convert into the map's domain + Product of projective spaces P^1 x P^2 over Integer Ring, but a + `pushforward` method is not properly implemented + sage: f([1,1,1,2,1]) + (1 : 2 , 1 : 3 : 2) + + :: + + sage: PP. = ProductProjectiveSpaces(ZZ, [1,1]) + sage: HP = End(PP) + sage: g = HP([x^2, y^2, u^2, v^2]) + sage: g([0,0,0,0],check=False) + (0 : 0 , 0 : 0) """ + from sage.schemes.product_projective.point import ProductProjectiveSpaces_point_ring + if check: + if not isinstance(P, ProductProjectiveSpaces_point_ring): + try: + P = self.domain()(P) + except (TypeError, NotImplementedError): + raise TypeError("%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(P, self.domain())) + elif self.domain()!= P.codomain(): + raise TypeError("%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(P, self.domain())) + A = self.codomain() - Q = P[0]._coords + P[1]._coords + Q = list(P) newP = [f(Q) for f in self.defining_polynomials()] return(A.point(newP, check)) @@ -198,7 +266,7 @@ def is_morphism(self): sage: P. = ProductProjectiveSpaces([2,1],QQ) sage: Q. = ProductProjectiveSpaces([1,2],QQ) sage: H = Hom(P,Q) - sage: f = H([x^2,y^2,z^3,w^3,u^3]) + sage: f = H([x^2,y^2,u^3,w^3,u^3]) sage: f.is_morphism() False """ @@ -317,7 +385,7 @@ def nth_iterate_map(self, n): sage: f.nth_iterate_map(3) Scheme endomorphism of Product of projective spaces P^1 x P^2 over Rational Field - Defn: Defined by sending (a : b , x : y : z) to + Defn: Defined by sending (a : b , x : y : z) to (a^27 : b^27 , x^8 : y^8 : z^8). """ if not self.is_endomorphism(): diff --git a/src/sage/schemes/product_projective/wehlerK3.py b/src/sage/schemes/product_projective/wehlerK3.py index 005b26b9676..9141d56655d 100644 --- a/src/sage/schemes/product_projective/wehlerK3.py +++ b/src/sage/schemes/product_projective/wehlerK3.py @@ -39,7 +39,7 @@ from sage.misc.mrange import xmrange from sage.rings.all import Integer from sage.rings.commutative_ring import is_CommutativeRing -from sage.rings.finite_rings.constructor import GF +from sage.rings.finite_rings.finite_field_constructor import GF from sage.rings.finite_rings.finite_field_base import is_FiniteField from sage.rings.fraction_field import FractionField from sage.rings.integer_ring import ZZ diff --git a/src/sage/schemes/projective/endPN_automorphism_group.py b/src/sage/schemes/projective/endPN_automorphism_group.py index 0bc5850bdca..19d1dc41d93 100644 --- a/src/sage/schemes/projective/endPN_automorphism_group.py +++ b/src/sage/schemes/projective/endPN_automorphism_group.py @@ -28,7 +28,7 @@ from sage.matrix.matrix import is_Matrix from sage.misc.functional import squarefree_part from sage.misc.misc_c import prod -from sage.rings.finite_rings.constructor import GF +from sage.rings.finite_rings.finite_field_constructor import GF from sage.rings.finite_rings.integer_mod_ring import Integers from sage.rings.fraction_field import FractionField from sage.rings.integer_ring import ZZ diff --git a/src/sage/schemes/projective/projective_homset.py b/src/sage/schemes/projective/projective_homset.py index fe3870736d1..1b29ffd53cf 100644 --- a/src/sage/schemes/projective/projective_homset.py +++ b/src/sage/schemes/projective/projective_homset.py @@ -42,7 +42,7 @@ from sage.rings.rational_field import is_RationalField from sage.categories.fields import Fields from sage.categories.number_fields import NumberFields -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField #******************************************************************* # Projective varieties diff --git a/src/sage/schemes/projective/projective_morphism.py b/src/sage/schemes/projective/projective_morphism.py index 86c7b382f61..992fa170e30 100644 --- a/src/sage/schemes/projective/projective_morphism.py +++ b/src/sage/schemes/projective/projective_morphism.py @@ -54,7 +54,7 @@ from sage.arith.all import gcd, lcm, next_prime, binomial, primes, moebius from sage.rings.complex_field import ComplexField_class,ComplexField from sage.rings.complex_interval_field import ComplexIntervalField_class -from sage.rings.finite_rings.constructor import GF, is_PrimeFiniteField +from sage.rings.finite_rings.finite_field_constructor import GF, is_PrimeFiniteField from sage.rings.finite_rings.integer_mod_ring import Zmod from sage.rings.fraction_field import FractionField from sage.rings.fraction_field_element import is_FractionFieldElement, FractionFieldElement @@ -3396,7 +3396,7 @@ def rational_periodic_points(self, **kwds): Must be defined over `\QQ`. The default parameter values are typically good choices for `\mathbb{P}^1`. If you are having - trouble getting a partiuclar map to finish, try first computing the possible periods, then + trouble getting a particular map to finish, try first computing the possible periods, then try various different ``lifting_prime``. ALGORITHM: diff --git a/src/sage/schemes/projective/projective_morphism_helper.pyx b/src/sage/schemes/projective/projective_morphism_helper.pyx index e0add19c5ce..0c260c676f9 100644 --- a/src/sage/schemes/projective/projective_morphism_helper.pyx +++ b/src/sage/schemes/projective/projective_morphism_helper.pyx @@ -20,7 +20,7 @@ AUTHORS: #***************************************************************************** from sage.arith.all import lcm -from sage.rings.finite_rings.constructor import GF +from sage.rings.finite_rings.finite_field_constructor import GF from sage.sets.all import Set from sage.misc.misc import subsets diff --git a/src/sage/schemes/projective/projective_space.py b/src/sage/schemes/projective/projective_space.py index 61b42ed1120..8c14ed2365a 100644 --- a/src/sage/schemes/projective/projective_space.py +++ b/src/sage/schemes/projective/projective_space.py @@ -84,7 +84,7 @@ from sage.rings.ring import is_Ring from sage.rings.rational_field import is_RationalField from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing -from sage.rings.finite_rings.constructor import is_FiniteField +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.commutative_ring import is_CommutativeRing from sage.categories.fields import Fields diff --git a/src/sage/schemes/toric/sheaf/__init__.py b/src/sage/schemes/toric/sheaf/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/sage/schemes/toric/sheaf/constructor.py b/src/sage/schemes/toric/sheaf/constructor.py new file mode 100644 index 00000000000..cf2e4a35463 --- /dev/null +++ b/src/sage/schemes/toric/sheaf/constructor.py @@ -0,0 +1,321 @@ +r""" +Construct sheaves on toric varieties. + +A toric vector bundle (on a toric variety) is a vector bundle that is +equivariant with respect to the algebraic torus action. +""" + + +#***************************************************************************** +# Copyright (C) 2013 Volker Braun +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.schemes.toric.variety import is_ToricVariety +from sage.modules.filtered_vector_space import FilteredVectorSpace + + +def TangentBundle(X): + r""" + Construct the tangent bundle of a toric variety. + + INPUT: + + - ``X`` -- a toric variety. The base space of the bundle. + + OUTPUT: + + The tangent bundle as a Klyachko bundle. + + EXAMPLES:: + + sage: dP7 = toric_varieties.dP7() + sage: from sage.schemes.toric.sheaf.constructor import TangentBundle + sage: TangentBundle(dP7) + Rank 2 bundle on 2-d CPR-Fano toric variety covered by 5 affine patches. + """ + if not is_ToricVariety(X): + raise ValueError('not a toric variety') + base_ring = X.base_ring() + fan = X.fan() + filtrations = dict() + from sage.modules.filtered_vector_space import FilteredVectorSpace + for i, ray in enumerate(fan.rays()): + F = FilteredVectorSpace(fan.rays(), {0:range(fan.nrays()), 1:[i]}) + filtrations[ray] = F + import klyachko + return klyachko.Bundle(X, filtrations, check=True) + + +def CotangentBundle(X): + r""" + Construct the cotangent bundle of a toric variety. + + INPUT: + + - ``X`` -- a toric variety. The base space of the bundle. + + OUTPUT: + + The cotangent bundle as a Klyachko bundle. + + EXAMPLES:: + + sage: dP7 = toric_varieties.dP7() + sage: from sage.schemes.toric.sheaf.constructor import CotangentBundle + sage: CotangentBundle(dP7) + Rank 2 bundle on 2-d CPR-Fano toric variety covered by 5 affine patches. + """ + return TangentBundle(X).dual() + + +def TrivialBundle(X, rank=1): + r""" + Return the trivial bundle of rank ``r``. + + INPUT: + + - ``X`` -- a toric variety. The base space of the bundle. + + - ``rank`` -- the rank of the bundle. + + OUTPUT: + + The trivial bundle as a Klyachko bundle. + + EXAMPLES:: + + sage: P2 = toric_varieties.P2() + sage: from sage.schemes.toric.sheaf.constructor import TrivialBundle + sage: I3 = TrivialBundle(P2, 3); I3 + Rank 3 bundle on 2-d CPR-Fano toric variety covered by 3 affine patches. + sage: I3.cohomology(weight=(0,0), dim=True) + (3, 0, 0) + """ + if not is_ToricVariety(X): + raise ValueError('not a toric variety') + from sage.modules.free_module import VectorSpace + base_ring = X.base_ring() + filtrations = dict([ray, FilteredVectorSpace(rank, 0, base_ring=base_ring)] + for ray in X.fan().rays()) + import klyachko + return klyachko.Bundle(X, filtrations, check=True) + + +def LineBundle(X, D): + """ + Construct the rank-1 bundle `O(D)`. + + INPUT: + + - ``X`` -- a toric variety. The base space of the bundle. + + - ``D`` -- a toric divisor. + + OUTPUT: + + The line bundle `O(D)` as a Klyachko bundle of rank 1. + + EXAMPLES:: + + sage: X = toric_varieties.dP8() + sage: D = X.divisor(0) + sage: from sage.schemes.toric.sheaf.constructor import LineBundle + sage: O_D = LineBundle(X, D) + sage: O_D.cohomology(dim=True, weight=(0,0)) + (1, 0, 0) + """ + if not is_ToricVariety(X): + raise ValueError('not a toric variety') + from sage.modules.free_module import VectorSpace + base_ring = X.base_ring() + filtrations = dict([X.fan().ray(i), + FilteredVectorSpace(1, D.function_value(i), base_ring=base_ring)] + for i in range(X.fan().nrays())) + import klyachko + return klyachko.Bundle(X, filtrations, check=True) + + + +class SheafLibrary(object): + + def __init__(self, toric_variety): + """ + Utility object to construct sheaves on toric varieties. + + .. warning:: + + You should never constuct instances manually. Can be + accessed from a toric variety via the + :attr:`sage.schemes.toric.variety.ToricVariety_field.sheaves` + attribute. + + EXAMPLES:: + + sage: type(toric_varieties.P2().sheaves) + + """ + self._variety = toric_variety + + def __repr__(self): + """ + Return a string representation. + + OUTPUT: + + String. + + EXAMPLES:: + + sage: toric_varieties.P2().sheaves # indirect doctest + Sheaf constructor on 2-d CPR-Fano toric variety covered by 3 affine patches + """ + return 'Sheaf constructor on ' + repr(self._variety) + + def trivial_bundle(self, rank=1): + r""" + Return the trivial bundle of rank ``r``. + + INPUT: + + - ``rank`` -- integer (optional; default: `1`). The rank of + the bundle. + + OUTPUT: + + The trivial bundle as a Klyachko bundle. + + EXAMPLES:: + + sage: P2 = toric_varieties.P2() + sage: I3 = P2.sheaves.trivial_bundle(3); I3 + Rank 3 bundle on 2-d CPR-Fano toric variety covered by 3 affine patches. + sage: I3.cohomology(weight=(0,0), dim=True) + (3, 0, 0) + """ + return TrivialBundle(self._variety, rank) + + def line_bundle(self, divisor): + """ + Construct the rank-1 bundle `O(D)`. + + INPUT: + + - ``divisor`` -- a toric divisor. + + OUTPUT: + + The line bundle `O(D)` for the given divisor as a Klyachko + bundle of rank 1. + + EXAMPLES:: + + sage: X = toric_varieties.dP8() + sage: D = X.divisor(0) + sage: O_D = X.sheaves.line_bundle(D) + sage: O_D.cohomology(dim=True, weight=(0,0)) + (1, 0, 0) + """ + return LineBundle(self._variety, divisor) + + def tangent_bundle(self): + r""" + Return the tangent bundle of the toric variety. + + OUTPUT: + + The tangent bundle as a Klyachko bundle. + + EXAMPLES:: + + sage: toric_varieties.dP6().sheaves.tangent_bundle() + Rank 2 bundle on 2-d CPR-Fano toric variety covered by 6 affine patches. + """ + return TangentBundle(self._variety) + + def cotangent_bundle(self): + r""" + Return the cotangent bundle of the toric variety. + + OUTPUT: + + The cotangent bundle as a Klyachko bundle. + + EXAMPLES:: + + sage: dP6 = toric_varieties.dP6() + sage: TX = dP6.sheaves.tangent_bundle() + sage: TXdual = dP6.sheaves.cotangent_bundle() + sage: TXdual == TX.dual() + True + """ + return CotangentBundle(self._variety) + + def Klyachko(self, multi_filtration): + """ + Construct a Klyachko bundle (sheaf) from filtration data. + + INPUT: + + - ``multi_filtration`` -- a multi-filtered vectors space with + multiple filtrations being indexed by the rays of the + fan. Either an instance of + :func:`~sage.modules.multi_filtered_vector_space.MultiFilteredVectorSpace` + or something (like a dictionary of ordinary filtered vector + spaces). + + OUTPUT: + + The Klyachko bundle defined by the filtrations, one for each + ray, of a vector space. + + EXAMPLES:: + + sage: P1 = toric_varieties.P1() + sage: v1, v2, v3 = [(1,0,0),(0,1,0),(0,0,1)] + sage: F1 = FilteredVectorSpace({1:[v1, v2, v3], 3:[v1]}) + sage: F2 = FilteredVectorSpace({0:[v1, v2, v3], 2:[v2, v3]}) + sage: P1 = toric_varieties.P1() + sage: r1, r2 = P1.fan().rays() + sage: F = MultiFilteredVectorSpace({r1:F1, r2:F2}); F + Filtrations + N(-1): QQ^3 >= QQ^2 >= QQ^2 >= 0 >= 0 + N(1): QQ^3 >= QQ^3 >= QQ^1 >= QQ^1 >= 0 + sage: P1.sheaves.Klyachko(F) + Rank 3 bundle on 1-d CPR-Fano toric variety covered by 2 affine patches. + """ + from klyachko import Bundle + return Bundle(self._variety, multi_filtration, check=True) + + def divisor(self, *args, **kwds): + """ + Return a toric divisor. + + INPUT: + + This is just an alias for + :meth:`sage.schemes.toric.variety.ToricVariety_field.divisor`, + see there for details. + + By abuse of notation, you can usually use the divisor `D` + interchangeably with the line bundle `O(D)`. + + OUTPUT: + + A toric divisor. + + EXAMPLES:: + + sage: dP6 = toric_varieties.dP6() + sage: dP6.inject_variables() + Defining x, u, y, v, z, w + sage: D = dP6.sheaves.divisor(x*u^3); D + V(x) + 3*V(u) + sage: D == dP6.divisor(x*u^3) + True + """ + return self._variety.divisor(*args, **kwds) diff --git a/src/sage/schemes/toric/sheaf/klyachko.py b/src/sage/schemes/toric/sheaf/klyachko.py new file mode 100644 index 00000000000..09a97328cce --- /dev/null +++ b/src/sage/schemes/toric/sheaf/klyachko.py @@ -0,0 +1,965 @@ +""" +Klyachko Bundles and Sheaves. + +Klyachko bundles are torus-equivariant bundles on toric +varieties. That is, the action of the maximal torus on the toric +variety lifts to an action on the bundle. There is an equivalence of +categories between [Klyachko]_ bundles and multiple filtrations (one for +each ray of the fan) of a vector space. The multi-filtrations are +implemented in :mod:`sage.modules.multi_filtered_vector_space`. + +EXAMPLES:: + + sage: X = toric_varieties.dP6xdP6() + sage: TX = X.sheaves.tangent_bundle() + sage: Alt2TX = TX.exterior_power(2); Alt2TX + Rank 6 bundle on 4-d CPR-Fano toric variety covered by 36 affine patches. + + sage: K = X.sheaves.line_bundle(X.K()) + sage: antiK = X.sheaves.line_bundle(-X.K()) + sage: (Alt2TX * K).cohomology(dim=True, weight=(0,0,0,0)) # long time + (0, 0, 18, 0, 0) + + sage: G_sum = TX + X.sheaves.trivial_bundle(2) + sage: V_sum = G_sum.wedge(2) * K # long time + sage: V_sum.cohomology(dim=True, weight=(0,0,0,0)) # long time + (0, 0, 18, 16, 1) + sage: Gtilde = G_sum.random_deformation() + sage: V = Gtilde.wedge(2) * K # long time + sage: V.cohomology(dim=True, weight=(0,0,0,0)) # long time + (0, 0, 3, 0, 0) + +REFERENCES: + +.. [Klyachko] + Klyachko, Aleksandr Anatolevich: + Equivariant Bundles on Toral Varieties, + Math USSR Izv. 35 (1990), 337-375. + http://iopscience.iop.org/0025-5726/35/2/A04/pdf/0025-5726_35_2_A04.pdf + +.. [BirknerIltenPetersen] + Rene Birkner, Nathan Owen Ilten, and Lars Petersen: + Computations with equivariant toric vector bundles, + The Journal of Software for Algebra and Geometry: Macaulay2. + http://msp.org/jsag/2010/2-1/p03.xhtml + http://www.math.uiuc.edu/Macaulay2/doc/Macaulay2-1.8.2/share/doc/Macaulay2/ToricVectorBundles/html/ +""" + +#***************************************************************************** +# Copyright (C) 2013 Volker Braun +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + + +from sage.structure.all import SageObject +from sage.rings.all import QQ, ZZ +from sage.misc.all import uniq, cached_method +from sage.matrix.constructor import vector, matrix, block_matrix, zero_matrix +from sage.geometry.cone import is_Cone, IntegralRayCollection + +from sage.modules.filtered_vector_space import FilteredVectorSpace, is_FilteredVectorSpace +from sage.modules.multi_filtered_vector_space import MultiFilteredVectorSpace + + +def is_KlyachkoBundle(X): + """ + Test whether ``X`` is a Klyachko bundle + + INPUT: + + - ``X`` -- anything. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: from sage.schemes.toric.sheaf.klyachko import is_KlyachkoBundle + sage: is_KlyachkoBundle('test') + False + """ + return isinstance(X, KlyachkoBundle_class) + + +def Bundle(toric_variety, multi_filtration, check=True): + r""" + Construct a Klyacho bundle + + INPUT: + + - ``toric_variety`` -- a toric variety. The base space of the bundle. + + - ``multi_filtration`` -- a multi-filtered vectors space with + multiple filtrations being indexed by the one-dimensional cones + of the fan. Either an instance of + :func:`~sage.modules.multi_filtered_vector_space.MultiFilteredVectorSpace` + or something (like a dictionary of ordinary filtered vector + spaces). + + EXAMPLES:: + + sage: P1 = toric_varieties.P1() + sage: v1, v2, v3 = [(1,0,0),(0,1,0),(0,0,1)] + sage: F1 = FilteredVectorSpace({1:[v1, v2, v3], 3:[v1]}) + sage: F2 = FilteredVectorSpace({0:[v1, v2, v3], 2:[v2, v3]}) + sage: P1 = toric_varieties.P1() + sage: r1, r2 = P1.fan().rays() + sage: F = MultiFilteredVectorSpace({r1:F1, r2:F2}); F + Filtrations + N(-1): QQ^3 >= QQ^2 >= QQ^2 >= 0 >= 0 + N(1): QQ^3 >= QQ^3 >= QQ^1 >= QQ^1 >= 0 + + You should use the + :meth:`~sage.schemes.toric.sheaf.constructor.SheafLibrary.Klyachko` + method to construct instances:: + + sage: P1.sheaves.Klyachko(F) + Rank 3 bundle on 1-d CPR-Fano toric variety covered by 2 affine patches. + + sage: P1.sheaves.Klyachko({r1:F1, r2:F2}) # alternative + Rank 3 bundle on 1-d CPR-Fano toric variety covered by 2 affine patches. + + The above is just a shorthand for:: + + sage: from sage.schemes.toric.sheaf.klyachko import Bundle + sage: Bundle(P1, F) + Rank 3 bundle on 1-d CPR-Fano toric variety covered by 2 affine patches. + """ + base_ring = toric_variety.base_ring() + if not hasattr(multi_filtration, 'get_filtration'): + # try to construct a MultiFilteredVectorSpace + multi_filtration = MultiFilteredVectorSpace( + multi_filtration, base_ring=base_ring, check=check) + if multi_filtration.base_ring() != base_ring: + multi_filtration = multi_filtration.change_ring(base_ring) + return KlyachkoBundle_class(toric_variety, multi_filtration, check=check) + + + +class KlyachkoBundle_class(SageObject): + + def __init__(self, toric_variety, multi_filtration, check=True): + r""" + A toric bundle using Klyachko's representation. + + .. warning:: + + You should always use the :func:`Bundle` factory function + to construct instances. + + INPUT: + + - ``toric_variety`` -- a toric variety. The base space of the bundle. + + - ``multi_filtration`` -- a + :func:`~sage.modules.multi_filtered_vector_space.MultiFilteredVectorSpace` + with index set the rays of the fan. + + - ``check`` -- boolean (default: ``True``). Whether to perform + consistency checks. + + EXAMPLES:: + + sage: P1 = toric_varieties.P1() + sage: r1, r2 = P1.fan().rays() + sage: F = MultiFilteredVectorSpace({ + ....: r1:FilteredVectorSpace(3,1), + ....: r2:FilteredVectorSpace(3,0)}); F + Filtrations + N(-1): QQ^3 >= 0 >= 0 + N(1): QQ^3 >= QQ^3 >= 0 + sage: from sage.schemes.toric.sheaf.klyachko import Bundle + sage: Bundle(P1, F) + Rank 3 bundle on 1-d CPR-Fano toric variety covered by 2 affine patches. + """ + self._variety = toric_variety + self._filt = multi_filtration + if not check: return + from sage.sets.set import Set + if multi_filtration.index_set() != Set(list(toric_variety.fan().rays())): + raise ValueError('the index set of the multi-filtration must be' + ' all rays of the fan.') + if not multi_filtration.is_exhaustive(): + raise ValueError('multi-filtration must be exhaustive') + if not multi_filtration.is_separating(): + raise ValueError('multi-filtration must be separating') + + def variety(self): + r""" + Return the base toric variety. + + OUTPUT: + + A toric variety. + + EXAMPLES:: + + sage: X = toric_varieties.P2() + sage: V = X.sheaves.tangent_bundle(); V + Rank 2 bundle on 2-d CPR-Fano toric variety covered by 3 affine patches. + sage: V.variety() is X + True + """ + return self._variety + + def base_ring(self): + r""" + Return the base field. + + OUTPUT: + + A field. + + EXAMPLES:: + + sage: T_P2 = toric_varieties.P2().sheaves.tangent_bundle() + sage: T_P2.base_ring() + Rational Field + """ + return self._filt.base_ring() + + def fiber(self): + r""" + Return the generic fiber of the vector bundle. + + OUTPUT: + + A vector space over :meth:`base_ring`. + + EXAMPLES:: + + sage: T_P2 = toric_varieties.P2().sheaves.tangent_bundle() + sage: T_P2.fiber() + Vector space of dimension 2 over Rational Field + """ + from sage.modules.all import VectorSpace + return VectorSpace(self.base_ring(), self.rank()) + + def rank(self): + r""" + Return the rank of the vector bundle. + + OUTPUT: + + Integer. + + EXAMPLES:: + + sage: T_P2 = toric_varieties.P2().sheaves.tangent_bundle() + sage: T_P2.rank() + 2 + """ + return self._filt.dimension() + + def _repr_(self): + r""" + Return a string representation. + + OUTPUT: + + String. + + EXAMPLES:: + + sage: toric_varieties.P2().sheaves.tangent_bundle() + Rank 2 bundle on 2-d CPR-Fano toric variety covered by 3 affine patches. + """ + s = 'Rank '+str(self.rank())+' bundle on '+str(self._variety)+'.' + return s + + def get_filtration(self, ray=None): + r""" + Return the filtration associated to the ``ray``. + + INPUT: + + - ``ray`` -- Integer, a `N`-lattice point, a one-dimensional + cone, or ``None`` (default). Specifies a ray of the fan of + the toric variety, either via its index or its generator. + + OUTPUT: + + The filtered vector space associated to the given ``ray``. If + no ray is specified, all filtrations are returned. + + EXAMPLES:: + + sage: TX = toric_varieties.dP6().sheaves.tangent_bundle() + sage: TX.get_filtration(0) + QQ^2 >= QQ^1 >= 0 + sage: TX.get_filtration([-1, -1]) + QQ^2 >= QQ^1 >= 0 + sage: TX.get_filtration(TX.variety().fan(1)[0]) + QQ^2 >= QQ^1 >= 0 + sage: TX.get_filtration() + Filtrations + N(-1, -1): QQ^2 >= QQ^1 >= 0 + N(-1, 0): QQ^2 >= QQ^1 >= 0 + N(0, -1): QQ^2 >= QQ^1 >= 0 + N(0, 1): QQ^2 >= QQ^1 >= 0 + N(1, 0): QQ^2 >= QQ^1 >= 0 + N(1, 1): QQ^2 >= QQ^1 >= 0 + """ + if ray is None: + return self._filt + X = self.variety() + fan = X.fan() + if is_Cone(ray): + if ray.dim() != 1: + raise ValueError('not a one-dimensional cone') + ray = ray.ray(0) + elif ray in ZZ: + ray = fan.ray(ray) + else: + N = fan.lattice() + ray = N(ray) + ray.set_immutable() + return self._filt.get_filtration(ray) + + def get_degree(self, ray, i): + r""" + Return the vector subspace ``E^\alpha(i)``. + + - ``ray`` -- Integer, a `N`-lattice point, a one-dimensional + cone, or ``None`` (default). Specifies a ray of the fan of + the toric variety, either via its index or its generator. + + - ``i`` -- integer. The filtration degree. + + OUTPUT: + + A subspace of the :meth:`fiber` vector space. The defining + data of a Klyachko bundle. + + EXAMPLES:: + + sage: TX = toric_varieties.dP6().sheaves.tangent_bundle() + sage: TX.get_degree(0, 1) + Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [0 1] + """ + return self.get_filtration(ray).get_degree(i) + + def filtration_intersection(self, sigma, i): + r""" + Return the intersection of the filtered subspaces. + + INPUT: + + - ``sigma`` -- a cone of the fan of the base toric variety. + + - ``i`` -- integer. The filtration degree. + + OUPUT: + + Let the cone be spanned by the rays `\sigma=\langle r_1,\dots, + r_k\rangle`. This method returns the intersection + + .. math:: + + \bigcap_{r\in \{r_1,\dots,r_k\}} + E^{r}(i) + + EXAMPLES:: + + sage: X = toric_varieties.P2() + sage: fan = X.fan() + sage: V = X.sheaves.tangent_bundle() + sage: V.filtration_intersection(fan(1)[0], 1) + Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [1 0] + sage: V.filtration_intersection(fan(2)[0], 1) + Vector space of degree 2 and dimension 0 over Rational Field + Basis matrix: + [] + """ + sigma = self._variety.fan().embed(sigma) + V = self.fiber() + for alpha in sigma.ambient_ray_indices(): + V = V.intersection(self.get_degree(alpha, i)) + return V + + def E_degree(self, alpha, m): + r""" + Return the vector subspace `E^\alpha(m)`. + + INPUT: + + - ``alpha`` -- a ray of the fan. Can be specified by its index + (an integer), a one-dimensional cone, or a `N`-lattice + point. + + - ``m`` -- tuple of integers or `M`-lattice point. A point in + the dual lattice of the fan. + + OUTPUT: + + The subspace $E^\alpha(\alpha m)$ of the filtration indexed by + the ray $\alpha$ and at the filtration degree $\alpha * m$ + + EXAMPLES:: + + sage: X = toric_varieties.P2() + sage: M = X.fan().dual_lattice() + sage: V = X.sheaves.tangent_bundle() + sage: V.E_degree(X.fan().ray(0), (1,0)) + Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [1 0] + sage: V.E_degree(X.fan(1)[0], (1,0)) + Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [1 0] + sage: V.E_degree(0, (1,0)) + Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [1 0] + """ + fan = self.variety().fan() + N = fan.lattice() + M = fan.dual_lattice() + m = M(m) + if alpha in ZZ: + ray = fan.ray(alpha) + elif alpha in N: + ray = alpha + else: + cone = fan.cone_containing(alpha) + if cone.dim() != 1: + raise ValueError('does not determine one-dimensional cone') + ray = cone.ray(0) + return self.get_degree(ray, ray*m) + + @cached_method + def E_intersection(self, sigma, m): + r""" + Return the vector subspace ``E^\sigma(m)``. + + See [Klyachko]_, equation 4.1. + + INPUT: + + - ``sigma`` -- a cone of the fan of the base toric variety. + + - ``m`` -- tuple of integers or `M`-lattice point. A point in + the dual lattice of the fan. Must be immutable. + + OUPUT: + + The subspace `E^\sigma(m)` + + EXAMPLES:: + + sage: X = toric_varieties.P2() + sage: fan = X.fan() + sage: V = X.sheaves.tangent_bundle() + sage: V.E_intersection(fan(1)[0], (1,0)) + Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [1 0] + sage: V.E_intersection(fan(2)[0], (-1,1)) + Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [0 1] + + For the empty cone, this is always the whole vector space:: + + sage: V.E_intersection(fan(0)[0], (1,0)) + Vector space of dimension 2 over Rational Field + """ + sigma = self._variety.fan().embed(sigma) + V = self.fiber() + for alpha in sigma.rays(): + V = V.intersection(self.E_degree(alpha, m)) + return V + + @cached_method + def E_quotient(self, sigma, m): + r""" + Return the vector space quotient `E_\sigma(m)`. + + See [Klyachko]_, equation 4.1. + + INPUT: + + - ``sigma`` -- a cone of the fan of the base toric variety. + + - ``m`` -- tuple of integers or `M`-lattice point. A point in + the dual lattice of the fan. Must be immutable. + + OUPUT: + + The subspace `E_\sigma(m)` + + EXAMPLES:: + + sage: X = toric_varieties.P2() + sage: fan = X.fan() + sage: M = fan.dual_lattice() + sage: cone = fan(1)[0] + sage: V = X.sheaves.tangent_bundle() + sage: m = M(1, 0) + sage: m.set_immutable() + sage: V.E_quotient(cone, m) + Vector space quotient V/W of dimension 1 over Rational Field where + V: Vector space of dimension 2 over Rational Field + W: Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [1 0] + sage: V.E_quotient(fan(2)[0], (-1,1)) + Vector space quotient V/W of dimension 0 over Rational Field where + V: Vector space of dimension 2 over Rational Field + W: Vector space of degree 2 and dimension 2 over Rational Field + Basis matrix: + [1 0] + [0 1] + """ + sigma = self._variety.fan().embed(sigma) + V = self.fiber() + generators = [] + for alpha in sigma.rays(): + generators.extend(self.E_degree(alpha, m).gens()) + return V.quotient(V.span(generators)) + + @cached_method + def E_quotient_projection(self, sigma, tau, m): + r""" + Return the projection map `E_\sigma(m) \to E_\tau(m)` where + `\sigma` is a face of `\tau`. + + INPUT: + + - ``sigma`` -- a cone of the fan of the base toric variety. + + - ``tau`` -- a cone of the fan containing ``sigma``. + + - ``m`` -- tuple of integers or `M`-lattice point. A point in + the dual lattice of the fan. Must be immutable. + + OUTPUT: + + The restriction map + + .. math:: + + E_\sigma(m) \to E_\tau(m) + + EXAMPLES:: + + sage: P3 = toric_varieties.P(3) + sage: rays = [(1,0,0), (0,1,0), (0,0,1)] + sage: F1 = FilteredVectorSpace(rays, {0:[0], 1:[2], 2:[1]}) + sage: F2 = FilteredVectorSpace(3, 0) + sage: r = P3.fan().rays() + sage: V = P3.sheaves.Klyachko({r[0]:F1, r[1]:F2, r[2]:F2, r[3]:F2}) + sage: tau = Cone([(1,0,0), (0,1,0)]) + sage: sigma = Cone([(1,0,0)]) + sage: M = P3.fan().dual_lattice() + sage: m = M(2,1,0) + sage: m.set_immutable() + sage: V.E_quotient(sigma, m) + Vector space quotient V/W of dimension 2 over Rational Field where + V: Vector space of dimension 3 over Rational Field + W: Vector space of degree 3 and dimension 1 over Rational Field + Basis matrix: + [0 1 0] + sage: V.E_quotient(tau, m) + Vector space quotient V/W of dimension 2 over Rational Field where + V: Vector space of dimension 3 over Rational Field + W: Vector space of degree 3 and dimension 1 over Rational Field + Basis matrix: + [0 1 0] + sage: V.E_quotient_projection(sigma, tau, m) + Vector space morphism represented by the matrix: + [1 0] + [0 1] + Domain: Vector space quotient V/W of dimension 2 over Rational Field where + V: Vector space of dimension 3 over Rational Field + W: Vector space of degree 3 and dimension 1 over Rational Field + Basis matrix: + [0 1 0] + Codomain: Vector space quotient V/W of dimension 2 over Rational Field where + V: Vector space of dimension 3 over Rational Field + W: Vector space of degree 3 and dimension 1 over Rational Field + Basis matrix: + [0 1 0] + """ + if not sigma.is_face_of(tau): + raise ValueError('the cone sigma is not a face of the cone tau') + E_sigma = self.E_quotient(sigma, m) + E_tau = self.E_quotient(tau, m) + images = [E_tau(E_sigma.lift(g)) for g in E_sigma.gens()] + return E_sigma.hom(images, codomain=E_tau) + + def cohomology_complex(self, m): + r""" + Return the "cohomology complex" `C^*(m)` + + See [Klyachko]_, equation 4.2. + + INPUT: + + - ``m`` -- tuple of integers or `M`-lattice point. A point in + the dual lattice of the fan. Must be immutable. + + OUTPUT: + + The "cohomology complex" as a chain complex over the + :meth:`base_ring`. + + EXAMPLES:: + + sage: P3 = toric_varieties.P(3) + sage: rays = [(1,0,0), (0,1,0), (0,0,1)] + sage: F1 = FilteredVectorSpace(rays, {0:[0], 1:[2], 2:[1]}) + sage: F2 = FilteredVectorSpace(rays, {0:[1,2], 1:[0]}) + sage: r = P3.fan().rays() + sage: V = P3.sheaves.Klyachko({r[0]:F1, r[1]:F2, r[2]:F2, r[3]:F2}) + sage: tau = Cone([(1,0,0), (0,1,0)]) + sage: sigma = Cone([(1, 0, 0)]) + sage: M = P3.fan().dual_lattice() + sage: m = M(1, 1, 0); m.set_immutable() + sage: V.cohomology_complex(m) + Chain complex with at most 2 nonzero terms over Rational Field + + sage: F = CyclotomicField(3) + sage: P3 = toric_varieties.P(3).change_ring(F) + sage: V = P3.sheaves.Klyachko({r[0]:F1, r[1]:F2, r[2]:F2, r[3]:F2}) + sage: V.cohomology_complex(m) + Chain complex with at most 2 nonzero terms over Cyclotomic + Field of order 3 and degree 2 + """ + fan = self._variety.fan() + C = fan.complex() + CV = [] + F = self.base_ring() + for dim in range(1,fan.dim()+1): + codim = fan.dim() - dim + d_C = C.differential(codim) + d_V = [] + for j in range(0, d_C.ncols()): + tau = fan(dim)[j] + d_V_row = [] + for i in range(0, d_C.nrows()): + sigma = fan(dim-1)[i] + if sigma.is_face_of(tau): + pr = self.E_quotient_projection(sigma, tau, m) + d = d_C[i,j] * pr.matrix().transpose() + else: + E_sigma = self.E_quotient(sigma, m) + E_tau = self.E_quotient(tau, m) + d = zero_matrix(F, E_tau.dimension(), E_sigma.dimension()) + d_V_row.append(d) + d_V.append(d_V_row) + # print dim, ':\n', d_V, '\n' + d_V = block_matrix(d_V, ring=F) + CV.append(d_V) + # print dim, ': ', d_V.nrows(), 'x', d_V.ncols(), '\n', d_V + from sage.homology.chain_complex import ChainComplex + return ChainComplex(CV, base_ring=self.base_ring()) + + def cohomology(self, degree=None, weight=None, dim=False): + r""" + Return the bundle cohomology groups. + + INPUT: + + - ``degree`` -- ``None`` (default) or an integer. The degree of + the cohomology group. + + - ``weight`` -- ``None`` (default) or a tuple of integers or a + `M`-lattice point. A point in the dual lattice of the fan + defining a torus character. The weight of the cohomology + group. + + - ``dim`` -- Boolean (default: ``False``). Whether to return + vector spaces or only their dimension. + + OUTPUT: + + The cohomology group of given cohomological ``degree`` and + torus ``weight``. + + * If no ``weight`` is specified, the unweighted group (sum + over all weights) is returned. + + * If no ``degree`` is specified, a dictionary whose keys are + integers and whose values are the cohomology groups is + returned. If, in addition, ``dim=True``, then an integral + vector of the dimensions is returned. + + EXAMPLES:: + + sage: V = toric_varieties.P2().sheaves.tangent_bundle() + sage: V.cohomology(degree=0, weight=(0,0)) + Vector space of dimension 2 over Rational Field + sage: V.cohomology(weight=(0,0), dim=True) + (2, 0, 0) + sage: for i,j in cartesian_product((range(-2,3), range(-2,3))): + ....: HH = V.cohomology(weight=(i,j), dim=True) + ....: if HH.is_zero(): continue + ....: print 'H^*i(P^2, TP^2)_M('+str(i)+','+str(j)+') =', HH + H^*i(P^2, TP^2)_M(-1,0) = (1, 0, 0) + H^*i(P^2, TP^2)_M(-1,1) = (1, 0, 0) + H^*i(P^2, TP^2)_M(0,-1) = (1, 0, 0) + H^*i(P^2, TP^2)_M(0,0) = (2, 0, 0) + H^*i(P^2, TP^2)_M(0,1) = (1, 0, 0) + H^*i(P^2, TP^2)_M(1,-1) = (1, 0, 0) + H^*i(P^2, TP^2)_M(1,0) = (1, 0, 0) + """ + from sage.modules.all import FreeModule + if weight is None: + raise NotImplementedError('sum over weights is not implemented') + else: + weight = self.variety().fan().dual_lattice()(weight) + weight.set_immutable() + if degree is not None: + return self.cohomology(weight=weight, dim=dim)[degree] + C = self.cohomology_complex(weight) + space_dim = self._variety.dimension() + C_homology = C.homology() + HH = dict() + for d in range(0, space_dim+1): + try: + HH[d] = C_homology[d] + except KeyError: + HH[d] = FreeModule(self.base_ring(), 0) + if dim: + HH = vector(ZZ, [HH[i].rank() for i in range(0, space_dim+1) ]) + return HH + + def __cmp__(self, other): + """ + Compare ``self`` and ``other`` + + .. warning:: + + This method tests whether the underlying representation is + the same. Use :meth:`is_isomorphic` to test for + mathematical equivalence. + + INPUT: + + - ``other`` -- anything. + + OUTPUT: + + `-1`, `0`, or `+1`. + + EXAMPLES:: + + sage: X = toric_varieties.P2() + sage: V1 = X.sheaves.trivial_bundle(1) + sage: V2 = X.sheaves.trivial_bundle(2) + sage: abs(cmp(V2, V1)) + 1 + sage: cmp(V2, V1+V1) + 0 + + sage: T_X = X.sheaves.tangent_bundle() + sage: O_X = X.sheaves.trivial_bundle(1) + sage: T_X + O_X == O_X + T_X + False + """ + c = cmp(type(self), type(other)) + if c!=0: return c + c = cmp(self.variety(), other.variety()) + if c!=0: return c + c = cmp(self._filt, other._filt) + if c!=0: return c + return 0 + + def is_isomorphic(self, other): + """ + Test whether two bundles are isomorphic. + + INPUT: + + - ``other`` -- anything. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: X = toric_varieties.P2() + sage: T_X = X.sheaves.tangent_bundle() + sage: O_X = X.sheaves.trivial_bundle(1) + sage: T_X + O_X == O_X + T_X + False + sage: (T_X + O_X).is_isomorphic(O_X + T_X) + Traceback (most recent call last): + ... + NotImplementedError + """ + raise NotImplementedError + + def direct_sum(self, other): + """ + Return the sum of two vector bundles. + + INPUT: + + - ``other`` -- a Klyachko bundle over the same base. + + OUTPUT: + + The direct sum as a new Klyachko bundle. + + EXAMPLES:: + + sage: X = toric_varieties.P2() + sage: V1 = X.sheaves.trivial_bundle(1) + sage: V2 = X.sheaves.trivial_bundle(2) + sage: V2.direct_sum(V1) + Rank 3 bundle on 2-d CPR-Fano toric variety covered by 3 affine patches. + + sage: V1 = X.sheaves.trivial_bundle(1) + sage: V2 = X.sheaves.trivial_bundle(2) + sage: V2 == V1 + V1 + True + """ + if not self.variety() == other.variety(): + raise ValueError('the bundles must be over the same base toric variety') + filt = self._filt + other._filt + return self.__class__(self.variety(), filt, check=True) + + __add__ = direct_sum + + def tensor_product(self, other): + """ + Return the sum of two vector bundles. + + INPUT: + + - ``other`` -- a Klyachko bundle over the same base. + + OUTPUT: + + The tensor product as a new Klyachko bundle. + + EXAMPLES:: + + sage: X = toric_varieties.P2() + sage: OX = X.sheaves.trivial_bundle(1) + sage: X.sheaves.tangent_bundle().tensor_product(OX) + Rank 2 bundle on 2-d CPR-Fano toric variety covered by 3 affine patches. + sage: OX == OX * OX + True + """ + if not self.variety() == other.variety(): + raise ValueError('the bundles must be over the same base toric variety') + filt = self._filt * other._filt + return self.__class__(self.variety(), filt, check=True) + + __mul__ = tensor_product + + def exterior_power(self, n): + """ + Return the `n`-th exterior power. + + INPUT: + + - ``n`` -- integer. + + OUTPUT: + + The `n`-th exterior power `\wedge_{i=1}^n V` of the bundle `V` + as a new Klyachko bundle. + + EXAMPLES:: + + sage: X = toric_varieties.P2_123() + sage: TX = X.sheaves.tangent_bundle() + sage: antiK = X.sheaves.line_bundle(-X.K()) + sage: TX.exterior_power(2) == antiK + True + sage: TX.wedge(2) == antiK # alias + True + """ + filt = self._filt.exterior_power(n) + return self.__class__(self.variety(), filt, check=True) + + wedge = exterior_power + + def symmetric_power(self, n): + """ + Return the `n`-th symmetric power. + + INPUT: + + - ``n`` -- integer. + + OUTPUT: + + The `n`-th symmetric power as a new Klyachko bundle. + + EXAMPLES:: + + sage: P1 = toric_varieties.P1() + sage: H = P1.divisor(0) + sage: L = P1.sheaves.line_bundle(H) + sage: (L+L).symmetric_power(2) + Rank 3 bundle on 1-d CPR-Fano toric variety covered by 2 affine patches. + sage: (L+L).symmetric_power(2) == L*L+L*L+L*L + True + """ + filt = self._filt.symmetric_power(n) + return self.__class__(self.variety(), filt, check=True) + + def dual(self): + """ + Return the dual bundle. + + OUTPUT: + + The dual bundle as a new Klyachko bundle. + + EXAMPLES:: + + sage: P1 = toric_varieties.P1() + sage: H = P1.divisor(0) + sage: L = P1.sheaves.line_bundle(H) + sage: L.dual() + Rank 1 bundle on 1-d CPR-Fano toric variety covered by 2 affine patches. + sage: L.dual() == P1.sheaves.line_bundle(-H) + True + """ + filt = self._filt.dual() + return self.__class__(self.variety(), filt, check=True) + + def random_deformation(self, epsilon=None): + """ + Return a generic torus-equivariant deformation of the bundle. + + INPUT: + + - ``epsilon`` -- an element of the base ring. Scales the + random deformation. + + OUTPUT: + + A new Klyachko bundle with randomly perturbed moduly. In + particular, the same Chern classes. + + EXAMPLES:: + + sage: P1 = toric_varieties.P1() + sage: H = P1.divisor(0) + sage: V = P1.sheaves.line_bundle(H) + P1.sheaves.line_bundle(-H) + sage: V.cohomology(dim=True, weight=(0,)) + (1, 0) + sage: Vtilde = V.random_deformation() + sage: Vtilde.cohomology(dim=True, weight=(0,)) + (1, 0) + """ + filt = self._filt.random_deformation(epsilon) + return self.__class__(self.variety(), filt, check=True) diff --git a/src/sage/schemes/toric/variety.py b/src/sage/schemes/toric/variety.py index 9b223bb152e..7b4ef03745c 100644 --- a/src/sage/schemes/toric/variety.py +++ b/src/sage/schemes/toric/variety.py @@ -2145,6 +2145,25 @@ def integrate(self, cohomology_class): if top_form.is_zero(): return 0 return top_form.lc() / self.volume_class().lc() + @property + def sheaves(self): + r""" + Return the factory object for sheaves on the toric variety. + + See :class:`sage.schemes.toric.sheaf.constructor.SheafLibrary` + for details. + + EXAMPLES:: + + sage: dP6 = toric_varieties.dP6() + sage: dP6.sheaves + Sheaf constructor on 2-d CPR-Fano toric variety covered by 6 affine patches + sage: dP6.sheaves.trivial_bundle() + Rank 1 bundle on 2-d CPR-Fano toric variety covered by 6 affine patches. + """ + from sage.schemes.toric.sheaf.constructor import SheafLibrary + return SheafLibrary(self) + @cached_method def Chern_class(self, deg=None): """ diff --git a/src/sage/structure/coerce_actions.pyx b/src/sage/structure/coerce_actions.pyx index 18f19a13d95..1ecff9c6ffe 100644 --- a/src/sage/structure/coerce_actions.pyx +++ b/src/sage/structure/coerce_actions.pyx @@ -509,6 +509,26 @@ cdef class ModuleAction(Action): with precomposition on right by Conversion via FractionFieldElement map: From: Univariate Polynomial Ring in y over Integer Ring To: Fraction Field of Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Integer Ring + + TESTS: + + See :trac:`19521`:: + + sage: Q. = SR.subring(no_variables=True)[[]] + sage: (y / 1).parent() + Power Series Ring in y over Symbolic Constants Subring + sage: R. = SR.subring(no_variables=True)[] + sage: cm = sage.structure.element.get_coercion_model() + sage: cm.explain(x, 1, operator.div) + Action discovered. + Right inverse action by Symbolic Constants Subring on + Univariate Polynomial Ring in x over Symbolic Constants Subring + with precomposition on right by Conversion map: + From: Integer Ring + To: Symbolic Constants Subring + Result lives in Univariate Polynomial Ring in x over + Symbolic Constants Subring + Univariate Polynomial Ring in x over Symbolic Constants Subring """ K = self.G._pseudo_fraction_field() if K is self.G: @@ -521,7 +541,28 @@ cdef class ModuleAction(Action): K = pushout(self.G._pseudo_fraction_field(), R) if K is None: K = R._pseudo_fraction_field() - for _, Ri in reversed(construction_tower(K)): + + # Suppose we have parents of a construction tower + # A -> B -> C <- D <- E -> F <- G -> H, + # where the arrows specify the direction of coercion (i.e. + # A -> B means A coerces to B). Note that B = FA(A), C = FB(B), ... + # As we want to find a "smallest" parent with some properties, + # we need the order + # A, B, E, D, C, G, F, H + # for our search. Thus the elements connected with a <- have to + # be reversed. See code below. + tower = [] + reversed_part = [] + for Fi, Ri in reversed(construction_tower(K)): + if Fi is not None and Fi.coercion_reversed: + reversed_part.append((Fi, Ri)) + else: + tower.append((Fi, Ri)) + if reversed_part: + tower += reversed(reversed_part) + reversed_part = [] + assert(not reversed_part) + for _, Ri in tower: if not Ri.has_coerce_map_from(self.G): continue Ki = Ri._pseudo_fraction_field() diff --git a/src/sage/structure/element.pxd b/src/sage/structure/element.pxd index 47b0903e391..87b586c7d80 100644 --- a/src/sage/structure/element.pxd +++ b/src/sage/structure/element.pxd @@ -37,7 +37,6 @@ cdef class Element(SageObject): cdef Parent _parent cpdef _richcmp_(left, Element right, int op) cpdef int _cmp_(left, Element right) except -2 - cdef _set_parent_c(self, Parent parent) cpdef base_extend(self, R) cpdef _act_on_(self, x, bint self_on_left) @@ -78,6 +77,7 @@ cdef class AdditiveGroupElement(ModuleElement): cdef class RingElement(ModuleElement): cpdef RingElement _mul_(self, RingElement right) cpdef RingElement _div_(self, RingElement right) + cpdef RingElement _floordiv_(self, RingElement right) cdef RingElement _add_long(self, long n) diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 51b02cc1192..aee57750bba 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -43,8 +43,6 @@ abstract base classes. CommutativeAlgebra ??? (should be removed from element.pxd) Matrix InfinityElement - PlusInfinityElement - MinusInfinityElement AdditiveGroupElement Vector @@ -137,8 +135,13 @@ from cpython.ref cimport PyObject from cpython.number cimport PyNumber_TrueDivide import types -cdef add, sub, mul, div, truediv, iadd, isub, imul, idiv -from operator import add, sub, mul, div, truediv, iadd, isub, imul, idiv +cdef add, sub, mul, div, truediv, floordiv +cdef iadd, isub, imul, idiv, itruediv, ifloordiv +from operator import (add, sub, mul, div, truediv, floordiv, + iadd, isub, imul, idiv, itruediv, ifloordiv) +cdef dict _coerce_op_symbols = dict( + add='+', sub='-', mul='*', div='/', truediv='/', floordiv='//', + iadd='+', isub='-', imul='*', idiv='/', itruediv='/', ifloordiv='//') cdef MethodType from types import MethodType @@ -263,8 +266,6 @@ def have_same_parent(left, right): return have_same_parent_c(left, right) -cdef dict _coerce_op_symbols = {'mul':'*', 'add':'+', 'sub':'-', 'div':'/', 'imul': '*', 'iadd': '+', 'isub':'-', 'idiv':'/'} - cdef str arith_error_message(x, y, op): name = op.__name__ try: @@ -320,14 +321,6 @@ cdef class Element(SageObject): """ self._parent = parent - cdef _set_parent_c(self, Parent parent): - self._parent = parent - - def _make_new_with_parent_c(self, Parent parent): - self._parent = parent - return self - - def __getattr__(self, str name): """ Lookup a method or attribute from the category abstract classes. @@ -1920,7 +1913,7 @@ cdef class RingElement(ModuleElement): EXAMPLES:: - 1/3*pisage: operator.truediv(2, 3) + sage: operator.truediv(2, 3) 2/3 sage: operator.truediv(pi, 3) 1/3*pi @@ -1929,6 +1922,26 @@ cdef class RingElement(ModuleElement): return (self)._div_(right) return coercion_model.bin_op(self, right, truediv) + def __itruediv__(self, right): + """ + Top-level in-place true division operator for ring elements. + See extensive documentation at the top of element.pyx. + + If two elements have the same parent, we just call ``_div_`` + because all divisions of Sage elements are really true + divisions. + + EXAMPLES:: + + sage: operator.itruediv(2, 3) + 2/3 + sage: operator.itruediv(pi, 3) + 1/3*pi + """ + if have_same_parent_c(self, right): + return (self)._div_(right) + return coercion_model.bin_op(self, right, itruediv) + def __div__(self, right): """ Top-level division operator for ring elements. @@ -1960,6 +1973,58 @@ cdef class RingElement(ModuleElement): return (self)._div_(right) return coercion_model.bin_op(self, right, idiv) + def __floordiv__(self, right): + """ + Top-level floor division operator for ring elements. + See extensive documentation at the top of element.pyx. + + EXAMPLES:: + + sage: 7 // 3 + 2 + sage: 7 // int(3) + 2 + sage: int(7) // 3 + 2 + sage: p = Parent() + sage: e = RingElement(p) + sage: e // e + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '//': '' and '' + """ + if have_same_parent_c(self, right): + return (self)._floordiv_(right) + return coercion_model.bin_op(self, right, floordiv) + + cpdef RingElement _floordiv_(self, RingElement right): + """ + Cython classes should override this function to implement floor + division. See extensive documentation at the top of element.pyx. + + EXAMPLES:: + + sage: 23._floordiv_(5) + 4 + """ + raise TypeError(arith_error_message(self, right, floordiv)) + + def __ifloordiv__(self, right): + """ + Top-level in-place floor division operator for ring elements. + See extensive documentation at the top of element.pyx. + + EXAMPLES:: + + sage: x = 23 + sage: x //= 7 + sage: x + 3 + """ + if have_same_parent_c(self, right): + return (self)._floordiv_(right) + return coercion_model.bin_op(self, right, ifloordiv) + def __invert__(self): if self.is_one(): return self @@ -3070,9 +3135,24 @@ cdef class EuclideanDomainElement(PrincipalIdealDomainElement): x, y = canonical_coercion(self, other) return x.quo_rem(y) - def __floordiv__(self,right): + cpdef RingElement _floordiv_(self, RingElement right): """ Quotient of division of ``self`` by other. This is denoted //. + + This default implementation assumes that ``quo_rem`` has been + implemented. + + EXAMPLES:: + + sage: cython(''' + ....: from sage.structure.element cimport EuclideanDomainElement + ....: cdef class MyElt(EuclideanDomainElement): + ....: def quo_rem(self, other): + ....: return self._parent.var('quo,rem') + ....: ''') + sage: e = MyElt(SR) + sage: e // e + quo """ Q, _ = self.quo_rem(right) return Q @@ -3081,13 +3161,28 @@ cdef class EuclideanDomainElement(PrincipalIdealDomainElement): """ Remainder of division of ``self`` by other. + This default implementation assumes that ``quo_rem`` has been + implemented. + EXAMPLES:: sage: R. = ZZ[] sage: x % (x+1) -1 - sage: (x**3 + x - 1) % (x**2 - 1) + sage: (x^3 + x - 1) % (x^2 - 1) 2*x - 1 + + :: + + sage: cython(''' + ....: from sage.structure.element cimport EuclideanDomainElement + ....: cdef class MyElt(EuclideanDomainElement): + ....: def quo_rem(self, other): + ....: return self._parent.var('quo,rem') + ....: ''') + sage: e = MyElt(SR) + sage: e % e + rem """ _, R = self.quo_rem(other) return R @@ -3099,9 +3194,22 @@ def is_FieldElement(x): return isinstance(x, FieldElement) cdef class FieldElement(CommutativeRingElement): + cpdef RingElement _floordiv_(self, RingElement right): + """ + Return the quotient of self and other. Since these are field + elements, the floor division is exactly the same as usual division. - def __floordiv__(self, other): - return self / other + EXAMPLES:: + + sage: K. = NumberField(x^4 + x^2 + 2/3) + sage: c = (1+b) // (1-b); c + 3/4*b^3 + 3/4*b^2 + 3/2*b + 1/2 + sage: (1+b) / (1-b) == c + True + sage: c * (1-b) + b + 1 + """ + return self._div_(right) def is_unit(self): r""" @@ -3229,28 +3337,6 @@ cdef class InfinityElement(RingElement): from sage.rings.all import ZZ return ZZ(0) -cdef class PlusInfinityElement(InfinityElement): - def __hash__(self): - r""" - TESTS:: - - sage: hash(+infinity) - 9223372036854775807 # 64-bit - 2147483647 # 32-bit - """ - return LONG_MAX - -cdef class MinusInfinityElement(InfinityElement): - def __hash__(self): - r""" - TESTS:: - - sage: hash(-infinity) - -9223372036854775808 # 64-bit - -2147483648 # 32-bit - """ - return LONG_MIN - ################################################################################# # diff --git a/src/sage/structure/factory.pyx b/src/sage/structure/factory.pyx index 74a5870d724..bfd2a185df0 100644 --- a/src/sage/structure/factory.pyx +++ b/src/sage/structure/factory.pyx @@ -151,7 +151,7 @@ cdef class UniqueFactory(SageObject): The below examples are rather artificial and illustrate particular aspects. For a "real-life" usage case of ``UniqueFactory``, see - the finite field factory in :mod:`sage.rings.finite_rings.constructor`. + the finite field factory in :mod:`sage.rings.finite_rings.finite_field_constructor`. In many cases, a factory class is implemented by providing the two methods :meth:`create_key` and :meth:`create_object`. In our example, diff --git a/src/sage/structure/formal_sum.py b/src/sage/structure/formal_sum.py index d8c0a73156e..bf820f29368 100644 --- a/src/sage/structure/formal_sum.py +++ b/src/sage/structure/formal_sum.py @@ -381,8 +381,6 @@ def _element_constructor_(self, x, check=True, reduce=True): P = x.parent() if P is self: return x - elif P == self: - return self.element_class(x._data, check=False, reduce=False, parent=self) else: x = x._data if isinstance(x, list): diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 8ae4ad0545f..d47ff7065f4 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -2031,9 +2031,10 @@ cdef class Expression(CommutativeRingElement): """ return is_a_symbol(self._gobj) - def is_constant(self): + def _is_registered_constant_(self): """ - Return True if this symbolic expression is a constant. + Return True if this symbolic expression is interally represented as + a constant. This function is intended to provide an interface to query the internal representation of the expression. In this sense, the word ``constant`` @@ -2042,22 +2043,52 @@ cdef class Expression(CommutativeRingElement): EXAMPLES:: - sage: pi.is_constant() + sage: pi._is_registered_constant_() True - sage: x.is_constant() + sage: x._is_registered_constant_() False - sage: SR(1).is_constant() + sage: SR(1)._is_registered_constant_() False Note that the complex I is not a constant:: - sage: I.is_constant() + sage: I._is_registered_constant_() False sage: I.is_numeric() True """ return is_a_constant(self._gobj) + def is_constant(self): + """ + Return whether this symbolic expression is a constant. + + A symbolic expression is constant if it does not contain + any variables. + + EXAMPLES:: + + sage: pi.is_constant() + True + sage: SR(1).is_constant() + True + sage: SR(2).is_constant() + True + sage: log(2).is_constant() + True + sage: I.is_constant() + True + sage: x.is_constant() + False + + TESTS:: + + sage: P.

= ZZ[] + sage: SR(42).is_constant() == P(2).is_constant() + True + """ + return not self.variables() + def is_numeric(self): """ A Pynac numeric is an object you can do arithmetic with @@ -2488,6 +2519,13 @@ cdef class Expression(CommutativeRingElement): sage: f(x) = matrix() sage: bool(f(x) - f(x) == 0) True + + Check that we catch exceptions from Pynac (:trac:`19904`):: + + sage: bool(SR(QQbar(I)) == I) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s)... """ if self.is_relational(): # constants are wrappers around Sage objects, compare directly diff --git a/src/sage/symbolic/ginac.pxd b/src/sage/symbolic/ginac.pxd index b384203656c..165a82b8c45 100644 --- a/src/sage/symbolic/ginac.pxd +++ b/src/sage/symbolic/ginac.pxd @@ -163,7 +163,7 @@ cdef extern from "sage/symbolic/ginac_wrap.h": bint is_negative(GEx x) except + bint is_a_relational "is_a" (GEx e) - unsigned decide_relational(GEx e) + unsigned decide_relational(GEx e) except + operators relational_operator(GEx e) operators switch_operator(operators op) GEx relational(GEx lhs, GEx rhs, operators o) @@ -367,6 +367,7 @@ cdef extern from "sage/symbolic/ginac_wrap.h": GEx g_H "GiNaC::H" (GEx m, GEx x) except + # harmonic polylogarithm GEx g_zeta "GiNaC::zeta" (GEx m) except + # Riemann's zeta function as well as multiple zeta value GEx g_zeta2 "GiNaC::zeta" (GEx m, GEx s) except + # alternating Euler sum + GEx g_stieltjes "GiNaC::stieltjes" (GEx m) except + # Stieltjes constants GEx g_zetaderiv "GiNaC::zetaderiv" (GEx n, GEx x) except + # derivatives of Riemann's zeta function GEx g_tgamma "GiNaC::tgamma" (GEx x) except + # gamma function GEx g_lgamma "GiNaC::lgamma" (GEx x) except + # logarithm of gamma function @@ -473,6 +474,7 @@ cdef extern from "sage/symbolic/ginac_wrap.h": unsigned H_serial "GiNaC::H_SERIAL::serial" # harmonic polylogarithm unsigned zeta1_serial "GiNaC::zeta1_SERIAL::serial" # Riemann's zeta function as well as multiple zeta value unsigned zeta2_serial "GiNaC::zeta2_SERIAL::serial" # alternating Euler sum + unsigned stieltjes1_serial "GiNaC::stieltjes1_SERIAL::serial" # Stieltjes constants unsigned zetaderiv_serial "GiNaC::zetaderiv_SERIAL::serial" # derivatives of Riemann's zeta function unsigned tgamma_serial "GiNaC::tgamma_SERIAL::serial" # gamma function unsigned lgamma_serial "GiNaC::lgamma_SERIAL::serial" # logarithm of gamma function @@ -527,6 +529,7 @@ cdef extern from "sage/symbolic/ginac_wrap.h": object (*py_bernoulli)(object x) except + object (*py_sin)(object x) except + object (*py_cos)(object x) except + + object (*py_stieltjes)(object x) except + object (*py_zeta)(object x) except + object (*py_exp)(object x) except + object (*py_log)(object x) except + diff --git a/src/sage/symbolic/pynac.pyx b/src/sage/symbolic/pynac.pyx index f5165bb774a..41b00d55d8b 100644 --- a/src/sage/symbolic/pynac.pyx +++ b/src/sage/symbolic/pynac.pyx @@ -1457,6 +1457,48 @@ cdef object py_cos(object x) except +: except (TypeError, ValueError): return CC(x).cos() +cdef object py_stieltjes(object x) except +: + """ + Return the Stieltjes constant of the given index. + + The value is expected to be a non-negative integer. + + TESTS:: + + sage: from sage.symbolic.pynac import py_stieltjes_for_doctests as py_stieltjes + sage: py_stieltjes(0) + 0.577215664901533 + sage: py_stieltjes(1.0) + -0.0728158454836767 + sage: py_stieltjes(RealField(100)(5)) + 0.00079332381730106270175333487744 + sage: py_stieltjes(-1) + Traceback (most recent call last): + ... + ValueError: Stieltjes constant of negative index + """ + n = ZZ(x) + if n < 0: + raise ValueError("Stieltjes constant of negative index") + import mpmath + if isinstance(x, Element) and hasattr((x)._parent, 'prec'): + prec = (x)._parent.prec() + else: + prec = 53 + return mpmath_utils.call(mpmath.stieltjes, n, prec=prec) + +def py_stieltjes_for_doctests(x): + """ + This function is for testing py_stieltjes(). + + EXAMPLES:: + + sage: from sage.symbolic.pynac import py_stieltjes_for_doctests + sage: py_stieltjes_for_doctests(0.0) + 0.577215664901533 + """ + return py_stieltjes(x) + cdef object py_zeta(object x) except +: """ Return the value of the zeta function at the given value. @@ -2264,6 +2306,7 @@ def init_function_table(): py_funcs.py_bernoulli = &py_bernoulli py_funcs.py_sin = &py_sin py_funcs.py_cos = &py_cos + py_funcs.py_stieltjes = &py_stieltjes py_funcs.py_zeta = &py_zeta py_funcs.py_exp = &py_exp py_funcs.py_log = &py_log diff --git a/src/sage/symbolic/ring.pyx b/src/sage/symbolic/ring.pyx index 46695745b5e..82b86bfb1fc 100644 --- a/src/sage/symbolic/ring.pyx +++ b/src/sage/symbolic/ring.pyx @@ -125,6 +125,15 @@ cdef class SymbolicRing(CommutativeRing): 6 sage: SR(5)-True 4 + + TESTS:: + + sage: SR.has_coerce_map_from(SR.subring(accepting_variables=('a',))) + True + sage: SR.has_coerce_map_from(SR.subring(rejecting_variables=('r',))) + True + sage: SR.has_coerce_map_from(SR.subring(no_variables=True)) + True """ if isinstance(R, type): if R in [int, float, long, complex, bool]: @@ -161,6 +170,8 @@ cdef class SymbolicRing(CommutativeRing): from sage.interfaces.maxima import Maxima + from subring import GenericSymbolicSubring + if ComplexField(mpfr_prec_min()).has_coerce_map_from(R): # Anything with a coercion into any precision of CC @@ -179,6 +190,8 @@ cdef class SymbolicRing(CommutativeRing): return True elif isinstance(R, (Maxima, PariInstance)): return False + elif isinstance(R, GenericSymbolicSubring): + return True def _element_constructor_(self, x): """ @@ -275,7 +288,6 @@ cdef class SymbolicRing(CommutativeRing): (, [x^5, log(y), 3]) """ cdef GEx exp - if is_Expression(x): if (x)._parent is self: return x @@ -841,6 +853,100 @@ cdef class SymbolicRing(CommutativeRing): return _the_element.subs(d, **kwds) + def subring(self, *args, **kwds): + r""" + Create a subring of this symbolic ring. + + INPUT: + + Choose one of the following keywords to create a subring. + + - ``accepting_variables`` (default: ``None``) -- a tuple or other + iterable of variables. If specified, then a symbolic subring of + expressions in only these variables is created. + + - ``rejecting_variables`` (default: ``None``) -- a tuple or other + iterable of variables. If specified, then a symbolic subring of + expressions in variables distinct to these variables is + created. + + - ``no_variables`` (default: ``False``) -- a boolean. If set, + then a symbolic subring of constant expressions (i.e., + expressions without a variable) is created. + + OUTPUT: + + A ring. + + EXAMPLES: + + Let us create a couple of symbolic variables first:: + + sage: V = var('a, b, r, s, x, y') + + Now we create a symbolic subring only accepting expressions in + the variables `a` and `b`:: + + sage: A = SR.subring(accepting_variables=(a, b)); A + Symbolic Subring accepting the variables a, b + + An element is + :: + + sage: A.an_element() + a + + From our variables in `V` the following are valid in `A`:: + + sage: tuple(v for v in V if v in A) + (a, b) + + Next, we create a symbolic subring rejecting expressions with + given variables:: + + sage: R = SR.subring(rejecting_variables=(r, s)); R + Symbolic Subring rejecting the variables r, s + + An element is + :: + + sage: R.an_element() + some_variable + + From our variables in `V` the following are valid in `R`:: + + sage: tuple(v for v in V if v in R) + (a, b, x, y) + + We have a third kind of subring, namely the subring of + symbolic constants:: + + sage: C = SR.subring(no_variables=True); C + Symbolic Constants Subring + + Note that this subring can be considered as a special accepting + subring; one without any variables. + + An element is + :: + + sage: C.an_element() + I*pi*e + + None of our variables in `V` is valid in `C`:: + + sage: tuple(v for v in V if v in C) + () + + .. SEEALSO:: + + :doc:`subring` + """ + if self is not SR: + raise NotImplementedError('Cannot create subring of %s.' % (self,)) + from subring import SymbolicSubring + return SymbolicSubring(*args, **kwds) + SR = SymbolicRing() cdef unsigned sage_domain_to_ginac_domain(object domain) except -1: diff --git a/src/sage/symbolic/subring.py b/src/sage/symbolic/subring.py new file mode 100644 index 00000000000..083a5ad96ce --- /dev/null +++ b/src/sage/symbolic/subring.py @@ -0,0 +1,1069 @@ +r""" +Subrings of the Symbolic Ring + +Subrings of the symbolic ring can be created via the +:meth:`~sage.symbolic.ring.SymbolicRing.subring` method of +``SR``. This will call :class:`SymbolicSubring ` +of this module. + +The following kinds of subrings are supported: + +- A symbolic subring of expressions, whose variables are contained in + a given set of symbolic variables (see + :class:`SymbolicSubringAcceptingVars`). E.g. + :: + + sage: SR.subring(accepting_variables=('a', 'b')) + Symbolic Subring accepting the variables a, b + +- A symbolic subring of expressions, whose variables are disjoint to a + given set of symbolic variables (see + :class:`SymbolicSubringRejectingVars`). E.g. + :: + + sage: SR.subring(rejecting_variables=('r', 's')) + Symbolic Subring rejecting the variables r, s + +- The subring of symbolic constants (see + :class:`SymbolicConstantsSubring`). E.g. + :: + + sage: SR.subring(no_variables=True) + Symbolic Constants Subring + + +TESTS: + +In the following we have a couple of tests to see whether the coercion +framework works properly:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: V = var('a, r, x') + sage: A = SymbolicSubring(accepting_variables=(a,)); A + Symbolic Subring accepting the variable a + sage: R = SymbolicSubring(rejecting_variables=(r,)); R + Symbolic Subring rejecting the variable r + sage: C = SymbolicSubring(no_variables=True); C + Symbolic Constants Subring + +:: + + sage: sage.categories.pushout.pushout(A, R) + Symbolic Subring rejecting the variable r + sage: sage.categories.pushout.pushout(R, C) + Symbolic Subring rejecting the variable r + sage: sage.categories.pushout.pushout(C, A) + Symbolic Subring accepting the variable a + sage: sage.categories.pushout.pushout(A, SR) + Symbolic Ring + sage: sage.categories.pushout.pushout(R, SR) + Symbolic Ring + sage: sage.categories.pushout.pushout(C, SR) + Symbolic Ring + +:: + + sage: cm = sage.structure.element.get_coercion_model() + sage: cm.common_parent(A, R) + Symbolic Subring rejecting the variable r + sage: cm.common_parent(R, C) + Symbolic Subring rejecting the variable r + sage: cm.common_parent(C, A) + Symbolic Subring accepting the variable a + sage: cm.common_parent(A, SR) + Symbolic Ring + sage: cm.common_parent(R, SR) + Symbolic Ring + sage: cm.common_parent(C, SR) + Symbolic Ring + + +AUTHORS: + +- Daniel Krenn (2015) + + +Classes and Methods +=================== +""" + +#***************************************************************************** +# Copyright (C) 2015 Daniel Krenn +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from ring import SymbolicRing, SR + + +from sage.structure.factory import UniqueFactory +class SymbolicSubringFactory(UniqueFactory): + r""" + A factory creating a symbolic subring. + + INPUT: + + Specify one of the following keywords to create a subring. + + - ``accepting_variables`` (default: ``None``) -- a tuple or other + iterable of variables. If specified, then a symbolic subring of + expressions in only these variables is created. + + - ``rejecting_variables`` (default: ``None``) -- a tuple or other + iterable of variables. If specified, then a symbolic subring of + expressions in variables distinct to these variables is + created. + + - ``no_variables`` (default: ``False``) -- a boolean. If set, + then a symbolic subring of constant expressions (i.e., + expressions without a variable) is created. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: V = var('a, b, c, r, s, t, x, y, z') + + :: + + sage: A = SymbolicSubring(accepting_variables=(a, b, c)); A + Symbolic Subring accepting the variables a, b, c + sage: tuple((v, v in A) for v in V) + ((a, True), (b, True), (c, True), + (r, False), (s, False), (t, False), + (x, False), (y, False), (z, False)) + + :: + + sage: R = SymbolicSubring(rejecting_variables=(r, s, t)); R + Symbolic Subring rejecting the variables r, s, t + sage: tuple((v, v in R) for v in V) + ((a, True), (b, True), (c, True), + (r, False), (s, False), (t, False), + (x, True), (y, True), (z, True)) + + :: + + sage: C = SymbolicSubring(no_variables=True); C + Symbolic Constants Subring + sage: tuple((v, v in C) for v in V) + ((a, False), (b, False), (c, False), + (r, False), (s, False), (t, False), + (x, False), (y, False), (z, False)) + + TESTS:: + + sage: SymbolicSubring(accepting_variables=tuple()) is C + True + + :: + + sage: SymbolicSubring(rejecting_variables=tuple()) is SR + True + """ + def create_key_and_extra_args( + self, accepting_variables=None, rejecting_variables=None, + no_variables=False, **kwds): + r""" + Given the arguments and keyword, create a key that uniquely + determines this object. + + See :class:`SymbolicSubringFactory` for details. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring.create_key_and_extra_args() + Traceback (most recent call last): + ... + ValueError: Cannot create a symbolic subring since nothing is specified. + sage: SymbolicSubring.create_key_and_extra_args( + ....: accepting_variables=('a',), rejecting_variables=('r',)) + Traceback (most recent call last): + ... + ValueError: Cannot create a symbolic subring since input is ambiguous. + sage: SymbolicSubring.create_key_and_extra_args( + ....: accepting_variables=('a',), no_variables=True) + Traceback (most recent call last): + ... + ValueError: Cannot create a symbolic subring since input is ambiguous. + sage: SymbolicSubring.create_key_and_extra_args( + ....: rejecting_variables=('r',), no_variables=True) + Traceback (most recent call last): + ... + ValueError: Cannot create a symbolic subring since input is ambiguous. + """ + if accepting_variables is None and \ + rejecting_variables is None and \ + not no_variables: + raise ValueError('Cannot create a symbolic subring ' + 'since nothing is specified.') + if accepting_variables is not None and rejecting_variables is not None or \ + rejecting_variables is not None and no_variables or \ + no_variables and accepting_variables is not None: + raise ValueError('Cannot create a symbolic subring ' + 'since input is ambiguous.') + + if accepting_variables is not None: + vars = tuple(accepting_variables) + if vars: + cls = SymbolicSubringAcceptingVars + else: + cls = SymbolicConstantsSubring + elif rejecting_variables is not None: + vars = tuple(rejecting_variables) + cls = SymbolicSubringRejectingVars + elif no_variables: + vars = tuple() + cls = SymbolicConstantsSubring + + vars = tuple(sorted(iter(SR(v) for v in vars), key=str)) + return (cls, vars), kwds + + + def create_object(self, version, key, **kwds): + r""" + Create an object from the given arguments. + + See :class:`SymbolicSubringFactory` for details. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring(rejecting_variables=tuple()) is SR # indirect doctest + True + """ + cls, vars = key + if cls is SymbolicSubringRejectingVars and not vars: + return SR + return cls(vars, **kwds) + + +SymbolicSubring = SymbolicSubringFactory("SymbolicSubring") + + +class GenericSymbolicSubring(SymbolicRing): + + def __init__(self, vars): + r""" + An abstract base class for a symbolic subring. + + INPUT: + + - ``vars`` -- a tuple of symbolic variables. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring(accepting_variables=('a',)) # indirect doctest + Symbolic Subring accepting the variable a + sage: SymbolicSubring(rejecting_variables=('r',)) # indirect doctest + Symbolic Subring rejecting the variable r + sage: SymbolicSubring(no_variables=True) # indirect doctest + Symbolic Constants Subring + sage: SymbolicSubring(rejecting_variables=tuple()) # indirect doctest + Symbolic Ring + + :: + + sage: SR.subring(accepting_variables=(0, pi, sqrt(2), 'zzz', I)) + Traceback (most recent call last): + ... + ValueError: Invalid variables: 0, I, pi, sqrt(2) + """ + super(GenericSymbolicSubring, self).__init__() + self._vars_ = set(vars) + if not all(v.is_symbol() for v in self._vars_): + raise ValueError('Invalid variables: {}'.format( + ', '.join(str(v) for v in sorted(self._vars_, key=str) + if not v.is_symbol()))) + + + def _repr_variables_(self): + r""" + Return a representation string of the variables. + + OUTPUT: + + A string. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring(accepting_variables=tuple())._repr_variables_() + 'no variable' + sage: SymbolicSubring(accepting_variables=('a',))._repr_variables_() + 'the variable a' + sage: SymbolicSubring(accepting_variables=('a', 'b'))._repr_variables_() + 'the variables a, b' + """ + if not self._vars_: + s = 'no variable' + elif len(self._vars_) == 1: + s = 'the variable ' + else: + s = 'the variables ' + return s + ', '.join(str(v) for v in sorted(self._vars_, key=str)) + + + def has_valid_variable(self, variable): + r""" + Return whether the given ``variable`` is valid in this subring. + + INPUT: + + - ``variable`` -- a symbolic variable. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.symbolic.subring import GenericSymbolicSubring + sage: GenericSymbolicSubring(vars=tuple()).has_valid_variable(x) + Traceback (most recent call last): + ... + NotImplementedError: Not implemented in this abstract base class + """ + raise NotImplementedError('Not implemented in this abstract base class') + + + def _element_constructor_(self, x): + r""" + Creates the element of this subring specified by the input ``x``. + + INPUT: + + - ``x`` -- an object. + + OUTPUT: + + An element of this symbolic subring. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: S = SymbolicSubring(accepting_variables=('a',)) + sage: S('a') # indirect doctest + a + sage: _.parent() + Symbolic Subring accepting the variable a + sage: S('x') # indirect doctest + Traceback (most recent call last): + ... + TypeError: x is not contained in Symbolic Subring accepting the variable a + """ + expression = super(GenericSymbolicSubring, self)._element_constructor_(x) + assert(expression.parent() is self) + if not all(self.has_valid_variable(var) + for var in expression.variables()): + raise TypeError('%s is not contained in %s' % (x, self)) + return expression + + + def _coerce_map_from_(self, P): + r""" + Return whether ``P`` coerces into this symbolic subring. + + INPUT: + + - ``P`` -- a parent. + + OUTPUT: + + A boolean or ``None``. + + TESTS:: + + sage: from sage.symbolic.subring import GenericSymbolicSubring + sage: GenericSymbolicSubring(vars=tuple()).has_coerce_map_from(SR) # indirect doctest # not tested see #19231 + False + + :: + sage: from sage.symbolic.subring import SymbolicSubring + sage: C = SymbolicSubring(no_variables=True) + sage: C.has_coerce_map_from(ZZ) # indirect doctest + True + sage: C.has_coerce_map_from(QQ) # indirect doctest + True + sage: C.has_coerce_map_from(RR) # indirect doctest + True + sage: C.has_coerce_map_from(RIF) # indirect doctest + True + sage: C.has_coerce_map_from(CC) # indirect doctest + True + sage: C.has_coerce_map_from(CIF) # indirect doctest + True + sage: C.has_coerce_map_from(AA) # indirect doctest + True + sage: C.has_coerce_map_from(QQbar) # indirect doctest + True + sage: C.has_coerce_map_from(SR) # indirect doctest + False + """ + if P == SR: + # Workaround; can be deleted once #19231 is fixed + return False + + from sage.rings.real_mpfr import mpfr_prec_min + from sage.rings.all import (ComplexField, + RLF, CLF, AA, QQbar, InfinityRing) + from sage.rings.real_mpfi import is_RealIntervalField + from sage.rings.complex_interval_field import is_ComplexIntervalField + + if isinstance(P, type): + return SR._coerce_map_from_(P) + + elif RLF.has_coerce_map_from(P) or \ + CLF.has_coerce_map_from(P) or \ + AA.has_coerce_map_from(P) or \ + QQbar.has_coerce_map_from(P): + return True + + elif (P is InfinityRing or + is_RealIntervalField(P) or is_ComplexIntervalField(P)): + return True + + elif ComplexField(mpfr_prec_min()).has_coerce_map_from(P): + return P not in (RLF, CLF, AA, QQbar) + + + def __cmp__(self, other): + """ + Compare two symbolic subrings. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: A = SymbolicSubring(accepting_variables=('a',)) + sage: B = SymbolicSubring(accepting_variables=('b',)) + sage: AB = SymbolicSubring(accepting_variables=('a', 'b')) + sage: A == A + True + sage: A == B + False + sage: A == AB + False + """ + c = cmp(type(self), type(other)) + if c != 0: + return c + if self._vars_ == other._vars_: + return 0 + return 1 + + +from sage.categories.pushout import ConstructionFunctor +class GenericSymbolicSubringFunctor(ConstructionFunctor): + r""" + A base class for the functors constructing symbolic subrings. + + INPUT: + + - ``vars`` -- a tuple, set, or other iterable of symbolic variables. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring(no_variables=True).construction()[0] # indirect doctest + Subring + + .. SEEALSO:: + + :class:`sage.categories.pushout.ConstructionFunctor`. + """ + + _functor_name = 'GenericSymbolicSubringFunctor' + + rank = 11 + + # The symbolic subring construction returns an object admitting a + # coercion map into the original, not vice versa. + coercion_reversed = True + + _repr_type_ = 'generic' + + + def __init__(self, vars): + r""" + See :class:`GenericSymbolicSubringFunctor` for details. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring(accepting_variables=('a',)).construction()[0] # indirect doctest + Subring + """ + self.vars = set(vars) + from sage.categories.rings import Rings + super(ConstructionFunctor, self).__init__(Rings(), Rings()) + + + def _repr_variables_(self): + r""" + Return a representation string of the variables. + + OUTPUT: + + A string. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: F = SymbolicSubring(accepting_variables=('a',)).construction()[0] + sage: F._repr_variables_() + 'a' + """ + return ', '.join(str(v) for v in sorted(self.vars, key=str)) + + + def _repr_(self): + r""" + Return a representation string of this functor. + + OUTPUT: + + A string. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring(accepting_variables=('a',)) # indirect doctest + Symbolic Subring accepting the variable a + sage: SymbolicSubring(rejecting_variables=('r',)) # indirect doctest + Symbolic Subring rejecting the variable r + sage: SymbolicSubring(no_variables=True) # indirect doctest + Symbolic Constants Subring + """ + return 'Subring<%s%s%s>' % ( + self._repr_type_, ' ' if self._repr_type_ else '', + self._repr_variables_() if self.vars else 'no variable') + + + def merge(self, other): + r""" + Merge this functor with ``other`` if possible. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A functor or ``None``. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: F = SymbolicSubring(accepting_variables=('a',)).construction()[0] + sage: F.merge(F) is F + True + """ + if self == other: + return self + + + def __eq__(self, other): + r""" + Return whether this functor is equal to ``other``. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: F = SymbolicSubring(accepting_variables=('a',)).construction()[0] + sage: F == F + True + """ + return type(self) == type(other) and self.vars == other.vars + + + def __ne__(self, other): + r""" + Return whether this functor is not equal to ``other``. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: F = SymbolicSubring(accepting_variables=('a',)).construction()[0] + sage: F != F + False + """ + return not self == other + + +class SymbolicSubringAcceptingVars(GenericSymbolicSubring): + r""" + The symbolic subring consisting of symbolic expressions in the given variables. + """ + + def _repr_(self): + r""" + Return a representation string of this symbolic subring. + + OUTPUT: + + A string. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring(accepting_variables=('a',)) # indirect doctest + Symbolic Subring accepting the variable a + """ + return 'Symbolic Subring accepting %s' % \ + (self._repr_variables_()) + + + def has_valid_variable(self, variable): + r""" + Return whether the given ``variable`` is valid in this subring. + + INPUT: + + - ``variable`` -- a symbolic variable. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: S = SymbolicSubring(accepting_variables=('a',)) + sage: S.has_valid_variable('a') + True + sage: S.has_valid_variable('r') + False + sage: S.has_valid_variable('x') + False + """ + return SR(variable) in self._vars_ + + + def construction(self): + r""" + Return the functorial construction of this symbolic subring. + + OUTPUT: + + A tuple whose first entry is a construction functor and its second + is the symbolic ring. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring(accepting_variables=('a',)).construction() + (Subring, Symbolic Ring) + """ + return (SymbolicSubringAcceptingVarsFunctor(self._vars_), SR) + + + def _coerce_map_from_(self, P): + r""" + Return whether ``P`` coerces into this symbolic subring. + + INPUT: + + - ``P`` -- a parent. + + OUTPUT: + + A boolean or ``None``. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: A = SymbolicSubring(accepting_variables=('a',)) + sage: AB = SymbolicSubring(accepting_variables=('a', 'b')) + sage: A.has_coerce_map_from(AB) # indirect doctest + False + sage: AB.has_coerce_map_from(A) # indirect doctest + True + """ + if isinstance(P, SymbolicSubringAcceptingVars): + return self._vars_ >= P._vars_ + return super(SymbolicSubringAcceptingVars, self)._coerce_map_from_(P) + + + def _an_element_(self): + r""" + Return an element of this symbolic subring. + + OUTPUT: + + A symbolic expression. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring(accepting_variables=('a',)).an_element() + a + sage: _.parent() + Symbolic Subring accepting the variable a + """ + return self(sorted(self._vars_, key=str)[0]) + + +class SymbolicSubringAcceptingVarsFunctor(GenericSymbolicSubringFunctor): + + _functor_name = 'SymbolicSubringAcceptingVarsFunctor' + + _repr_type_ = 'accepting' + + + def merge(self, other): + r""" + Merge this functor with ``other`` if possible. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A functor or ``None``. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: F = SymbolicSubring(accepting_variables=('a',)).construction()[0] + sage: G = SymbolicSubring(rejecting_variables=('r',)).construction()[0] + sage: F.merge(F) is F + True + sage: F.merge(G) is G + True + """ + if self == other: + return self + elif type(self) == type(other): + return type(self)(self.vars | other.vars) + elif isinstance(other, SymbolicSubringRejectingVarsFunctor): + if not (self.vars & other.vars): + return other + + + def _apply_functor(self, R): + """ + Apply this functor to the given symbolic ring `R`. + + INPUT: + + - ``R`` -- a symbolic ring. + + OUTPUT: + + A subring of ``R``. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: F, R = SymbolicSubring(accepting_variables=('a',)).construction() + sage: F(R) # indirect doctest + Symbolic Subring accepting the variable a + + TESTS:: + + sage: F(F(R)) + Traceback (most recent call last): + ... + NotImplementedError: This functor can only be applied on the + symbolic ring but Symbolic Subring accepting the variable a given. + """ + if R is not SR: + raise NotImplementedError('This functor can only be applied on ' + 'the symbolic ring but %s given.' % (R,)) + return SymbolicSubring(accepting_variables=self.vars) + + +class SymbolicSubringRejectingVars(GenericSymbolicSubring): + r""" + The symbolic subring consisting of symbolic expressions whose variables + are none of the given variables. + """ + + def _repr_(self): + r""" + Return a representation string of this symbolic subring. + + OUTPUT: + + A string. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring(rejecting_variables=('r',)) # indirect doctest + Symbolic Subring rejecting the variable r + """ + return 'Symbolic Subring rejecting %s' % \ + (self._repr_variables_()) + + + def has_valid_variable(self, variable): + r""" + Return whether the given ``variable`` is valid in this subring. + + INPUT: + + - ``variable`` -- a symbolic variable. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: S = SymbolicSubring(rejecting_variables=('r',)) + sage: S.has_valid_variable('a') + True + sage: S.has_valid_variable('r') + False + sage: S.has_valid_variable('x') + True + """ + return SR(variable) not in self._vars_ + + + def construction(self): + r""" + Return the functorial construction of this symbolic subring. + + OUTPUT: + + A tuple whose first entry is a construction functor and its second + is the symbolic ring. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring(rejecting_variables=('r',)).construction() + (Subring, Symbolic Ring) + """ + return (SymbolicSubringRejectingVarsFunctor(self._vars_), SR) + + + def _coerce_map_from_(self, P): + r""" + Return whether ``P`` coerces into this symbolic subring. + + INPUT: + + - ``P`` -- a parent. + + OUTPUT: + + A boolean or ``None``. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: R = SymbolicSubring(rejecting_variables=('r',)) + sage: RS = SymbolicSubring(rejecting_variables=('r', 's')) + sage: RS.has_coerce_map_from(R) # indirect doctest + False + sage: R.has_coerce_map_from(RS) # indirect doctest + True + sage: A = SymbolicSubring(accepting_variables=('a',)) + sage: R.has_coerce_map_from(A) + True + sage: A.has_coerce_map_from(R) + False + """ + if isinstance(P, SymbolicSubringRejectingVars): + return self._vars_ <= P._vars_ + elif isinstance(P, SymbolicSubringAcceptingVars): + return not (self._vars_ & P._vars_) + return super(SymbolicSubringRejectingVars, self)._coerce_map_from_(P) + + + def _an_element_(self): + r""" + Return an element of this symbolic subring. + + OUTPUT: + + A symbolic expression. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring(rejecting_variables=('r',)).an_element() + some_variable + sage: _.parent() + Symbolic Subring rejecting the variable r + sage: SymbolicSubring(rejecting_variables=('some_variable',)).an_element() + some_some_variable + sage: _.parent() + Symbolic Subring rejecting the variable some_variable + sage: SymbolicSubring(rejecting_variables=('some_some_variable',)).an_element() + some_variable + sage: _.parent() + Symbolic Subring rejecting the variable some_some_variable + sage: SymbolicSubring(rejecting_variables=('some_variable','some_some_variable')).an_element() + some_some_some_variable + sage: _.parent() + Symbolic Subring rejecting the variables some_some_variable, some_variable + """ + v = SR.an_element() + while not self.has_valid_variable(v): + v = SR('some_' + str(v)) + return self(v) + + +class SymbolicSubringRejectingVarsFunctor(GenericSymbolicSubringFunctor): + + _functor_name = 'SymbolicSubringRejectingVarsFunctor' + + _repr_type_ = 'rejecting' + + + def merge(self, other): + r""" + Merge this functor with ``other`` if possible. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A functor or ``None``. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: F = SymbolicSubring(accepting_variables=('a',)).construction()[0] + sage: G = SymbolicSubring(rejecting_variables=('r',)).construction()[0] + sage: G.merge(G) is G + True + sage: G.merge(F) is G + True + """ + if self == other: + return self + elif type(self) == type(other): + return type(self)(self.vars & other.vars) + elif isinstance(other, SymbolicSubringAcceptingVarsFunctor): + if not (self.vars & other.vars): + return self + + + def _apply_functor(self, R): + """ + Apply this functor to the given symbolic ring `R`. + + INPUT: + + - ``R`` -- a symbolic ring. + + OUTPUT: + + A subring of ``R``. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: F, R = SymbolicSubring(rejecting_variables=('r',)).construction() + sage: F(R) # indirect doctest + Symbolic Subring rejecting the variable r + + TESTS:: + + sage: F(F(R)) + Traceback (most recent call last): + ... + NotImplementedError: This functor can only be applied on the + symbolic ring but Symbolic Subring rejecting the variable r given. + """ + if R is not SR: + raise NotImplementedError('This functor can only be applied on ' + 'the symbolic ring but %s given.' % (R,)) + return SymbolicSubring(rejecting_variables=self.vars) + + +class SymbolicConstantsSubring(SymbolicSubringAcceptingVars): + r""" + The symbolic subring consisting of symbolic constants. + """ + + def _repr_(self): + r""" + Return a representation string of this symbolic subring. + + OUTPUT: + + A string. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring(no_variables=True) # indirect doctest + Symbolic Constants Subring + """ + return 'Symbolic Constants Subring' + + + def has_valid_variable(self, variable): + r""" + Return whether the given ``variable`` is valid in this subring. + + INPUT: + + - ``variable`` -- a symbolic variable. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: S = SymbolicSubring(no_variables=True) + sage: S.has_valid_variable('a') + False + sage: S.has_valid_variable('r') + False + sage: S.has_valid_variable('x') + False + """ + return False + + + def _an_element_(self): + r""" + Return an element of this symbolic subring. + + OUTPUT: + + A symbolic expression. + + TESTS:: + + sage: from sage.symbolic.subring import SymbolicSubring + sage: SymbolicSubring(no_variables=True).an_element() + I*pi*e + sage: _.parent() + Symbolic Constants Subring + """ + return self(SR('I') * SR('pi') * SR('e')) diff --git a/src/sage/version.py b/src/sage/version.py index b9dd9bf2423..5f92fd56f87 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 = '7.0' -date = '2016-01-19' +version = '7.1.beta1' +date = '2016-01-28'