diff --git a/COPYING.txt b/COPYING.txt index 4fc2393e21e..fc4dbd08c5b 100644 --- a/COPYING.txt +++ b/COPYING.txt @@ -28,6 +28,7 @@ the licenses of the components of Sage are included below as well. SOFTWARE LICENSE ----------------------------------------------------------------------- +arb GPLv2+ atlas Modified BSD boehm_gc MIT-like license (see below) backports_ssl_match_hostname Python License diff --git a/Makefile b/Makefile index a7df7e96cb9..fb8aa9887b5 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ build: all-build # If configure was run before, rerun it with the old arguments. # Otherwise, run configure with argument $PREREQ_OPTIONS. -build/make/Makefile: configure +build/make/Makefile: configure build/pkgs/*/* rm -f config.log mkdir -p logs/pkgs ln -s logs/pkgs/config.log config.log @@ -119,8 +119,7 @@ ptestoptional: ptestall # just an alias ptestoptionallong: ptestalllong # just an alias -configure: configure.ac src/bin/sage-version.sh \ - m4/ax_c_check_flag.m4 m4/ax_gcc_option.m4 m4/ax_gcc_version.m4 m4/ax_gxx_option.m4 m4/ax_gxx_version.m4 m4/ax_prog_perl_version.m4 +configure: configure.ac src/bin/sage-version.sh m4/*.m4 ./bootstrap -d install: diff --git a/VERSION.txt b/VERSION.txt index 9d90d562953..5701b3c1bf6 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -Sage version 6.10.beta0, released 2015-10-15 +Sage version 6.10.beta3, released 2015-11-05 diff --git a/build/make/deps b/build/make/deps index 3d0fb935312..009d592a273 100644 --- a/build/make/deps +++ b/build/make/deps @@ -100,7 +100,9 @@ base: $(INST)/$(BZIP2) $(INST)/$(PATCH) $(INST)/$(PKGCONF) # Sage library (e.g. CYTHON, JINJA2), and on the other hand all # dependencies for Cython files (e.g. PARI, NTL, SAGE_MP_LIBRARY). sagelib: \ + $(INST)/$(ARB) \ $(INST)/$(ATLAS) \ + $(INST)/$(BRIAL) \ $(INST)/$(CEPHES) \ $(INST)/$(CLIQUER) \ $(INST)/$(CYTHON) \ @@ -115,6 +117,7 @@ sagelib: \ $(INST)/$(GSL) \ $(INST)/$(IML) \ $(INST)/$(JINJA2) \ + $(INST)/$(JUPYTER_CORE) \ $(INST)/$(LCALC) \ $(INST)/$(LRCALC) \ $(INST)/$(LIBGAP) \ @@ -130,7 +133,6 @@ sagelib: \ $(INST)/$(NUMPY) \ $(INST)/$(PARI) \ $(INST)/$(PLANARITY) \ - $(INST)/$(BRIAL) \ $(INST)/$(PPL) \ $(INST)/$(PYNAC) \ $(INST)/$(PYTHON) \ @@ -140,7 +142,8 @@ sagelib: \ $(INST)/$(SINGULAR) \ $(INST)/$(SIX) \ $(INST)/$(SYMMETRICA) \ - $(INST)/$(ZN_POLY) + $(INST)/$(ZN_POLY) \ + $(EXTCODE) if [ -z "$$SAGE_INSTALL_FETCH_ONLY" ]; then \ cd $(SAGE_SRC) && source bin/sage-env && \ sage-logger 'time $(MAKE) sage' '$(SAGE_LOGS)/sage-$(SAGE_VERSION).log'; \ diff --git a/build/pkgs/arb/type b/build/pkgs/arb/type index 134d9bc32d5..a6a7b9cd726 100644 --- a/build/pkgs/arb/type +++ b/build/pkgs/arb/type @@ -1 +1 @@ -optional +standard diff --git a/build/pkgs/bliss/SPKG.txt b/build/pkgs/bliss/SPKG.txt index 73bd34d5183..84b97f0f8fc 100644 --- a/build/pkgs/bliss/SPKG.txt +++ b/build/pkgs/bliss/SPKG.txt @@ -7,7 +7,7 @@ forms of graphs. == License == -GPL +LGPL == Upstream Contact == diff --git a/build/pkgs/bliss/checksums.ini b/build/pkgs/bliss/checksums.ini index 740953e6b30..7fc59156c20 100644 --- a/build/pkgs/bliss/checksums.ini +++ b/build/pkgs/bliss/checksums.ini @@ -1,4 +1,4 @@ -tarball=bliss-VERSION.tar.bz2 -sha1=500796365fdf142dcfb34f979d678ffa456f1431 -md5=4e57118f9acad9a80baaefd93f967bdf -cksum=662785944 +tarball=bliss-VERSION.tar.gz +sha1=46322da1a03750e199e156d8967d88b89ebad5de +md5=b936d5d54b618a77ed59dfeeced3fa58 +cksum=76340303 diff --git a/build/pkgs/bliss/package-version.txt b/build/pkgs/bliss/package-version.txt index 615e437dd31..6ab5ccfd03e 100644 --- a/build/pkgs/bliss/package-version.txt +++ b/build/pkgs/bliss/package-version.txt @@ -1 +1 @@ -0.72.p1 +0.73 diff --git a/build/pkgs/bliss/patches/digraph_heuristic.patch b/build/pkgs/bliss/patches/digraph_heuristic.patch deleted file mode 100644 index 6e61458f424..00000000000 --- a/build/pkgs/bliss/patches/digraph_heuristic.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- src/graph.cc 2015-02-11 13:20:39.922021355 +0100 -+++ src-patched/graph_new.cc 2015-02-11 13:20:15.546020960 +0100 -@@ -1920,6 +1920,7 @@ - Digraph::Digraph(const unsigned int nof_vertices) - { - vertices.resize(nof_vertices); -+ sh = shs_f; - } - - diff --git a/build/pkgs/bliss/spkg-install b/build/pkgs/bliss/spkg-install index e3f9c7cb15a..746526d99b8 100755 --- a/build/pkgs/bliss/spkg-install +++ b/build/pkgs/bliss/spkg-install @@ -18,7 +18,7 @@ for patch in ../patches/*.patch; do done -$MAKE && mv libbliss.a "$SAGE_LOCAL/lib" && mv *.hh "$SAGE_LOCAL/include" +$MAKE && cp libbliss.a "$SAGE_LOCAL/lib" && cp *.hh "$SAGE_LOCAL/include" if [ $? -ne 0 ]; then echo "An error occurred whilst building bliss" diff --git a/build/pkgs/cliquer/spkg-install b/build/pkgs/cliquer/spkg-install index 4e7d6a5c5a8..87f23bb59c7 100755 --- a/build/pkgs/cliquer/spkg-install +++ b/build/pkgs/cliquer/spkg-install @@ -24,7 +24,6 @@ fi # Flags for building a dynamically linked shared object. if [ "$UNAME" = "Darwin" ]; then - export MACOSX_DEPLOYMENT_TARGET="10.3" SAGESOFLAGS="-dynamiclib -single_module -flat_namespace -undefined dynamic_lookup" elif [ "$UNAME" = "SunOS" ]; then SAGESOFLAGS="-shared -Wl,-h,libcliquer.so -Wl,-ztext" @@ -63,6 +62,7 @@ cp *.h "$SAGE_LOCAL/include/cliquer/" if [ "$UNAME" = "Darwin" ]; then cp -f libcliquer.so "$SAGE_LOCAL/lib/libcliquer.dylib" + install_name_tool -id "${SAGE_LOCAL}"/lib/libcliquer.dylib "${SAGE_LOCAL}"/lib/libcliquer.dylib elif [ "$UNAME" = "CYGWIN" ]; then cp -f libcliquer.so "$SAGE_LOCAL/lib/libcliquer.dll" fi diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 0c170545927..0ec9977aa41 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=3e0d10789b34d6f890e1575c2a06894a90e4807e -md5=020a9b7f31e61b57056969b6816455f1 -cksum=2662451870 +sha1=ff6a33eb58523267e51397577213b038fe81fe38 +md5=b694e1f431bb6f8fe6fbfc63b7640078 +cksum=1772005708 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 52bd8e43afb..190a18037c6 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -120 +123 diff --git a/build/pkgs/conway_polynomials/spkg-install b/build/pkgs/conway_polynomials/spkg-install index 0023233e122..441dc8e8381 100755 --- a/build/pkgs/conway_polynomials/spkg-install +++ b/build/pkgs/conway_polynomials/spkg-install @@ -1,7 +1,7 @@ #!/usr/bin/env python import os -from sage.all import save +from sage.structure.sage_object import save from sage.env import SAGE_SHARE install_root = os.path.join(SAGE_SHARE, 'conway_polynomials') diff --git a/build/pkgs/git/checksums.ini b/build/pkgs/git/checksums.ini index 88657651061..3a6e9681738 100644 --- a/build/pkgs/git/checksums.ini +++ b/build/pkgs/git/checksums.ini @@ -1,4 +1,4 @@ tarball=git-VERSION.tar.gz -sha1=150efeb9c016cb8d3e768a408f3f407d18d69661 -md5=edf994cf34cd3354dadcdfa6b4292335 -cksum=995320395 +sha1=ff32a94936309ca3f0e3d56e479e7510a3c1c925 +md5=da293290da69f45a86a311ad3cd43dc8 +cksum=2025246710 diff --git a/build/pkgs/git/package-version.txt b/build/pkgs/git/package-version.txt index 276cbf9e285..097a15a2af3 100644 --- a/build/pkgs/git/package-version.txt +++ b/build/pkgs/git/package-version.txt @@ -1 +1 @@ -2.3.0 +2.6.2 diff --git a/build/pkgs/git/spkg-install b/build/pkgs/git/spkg-install index 6331ae29f5f..f9ba3204f16 100755 --- a/build/pkgs/git/spkg-install +++ b/build/pkgs/git/spkg-install @@ -49,13 +49,10 @@ done export NO_FINK=1 export NO_DARWIN_PORTS=1 -# Darwin 8 (Tiger) does not support common crypto -if { uname -sr | grep 'Darwin 8' ;} &>/dev/null; then - export NO_APPLE_COMMON_CRYPTO=1 -fi - # OSX Git with FSF GCC is broken, disable completely for now. See #17091 -export NO_APPLE_COMMON_CRYPTO=1 +if [ "$UNAME" = "Darwin" ]; then + export NO_OPENSSL=1 +fi # First make GIT-VERSION-FILE (we patched Makefile such that configure # no longer depends on this, so it's safer to explicitly build this). diff --git a/build/pkgs/gp2c/SPKG.txt b/build/pkgs/gp2c/SPKG.txt new file mode 100644 index 00000000000..984b63c7865 --- /dev/null +++ b/build/pkgs/gp2c/SPKG.txt @@ -0,0 +1,18 @@ += gp2c = + +== Description == + +The gp2c compiler is a package for translating GP routines into the C +programming language, so that they can be compiled and used with the PARI +system or the GP calculator. + +== License == + +GPL version 2+ + +== Upstream Contact == + * http://pari.math.u-bordeaux.fr/ + +== Dependencies == + * PARI + * Perl diff --git a/build/pkgs/gp2c/checksums.ini b/build/pkgs/gp2c/checksums.ini new file mode 100644 index 00000000000..0f114ad0f51 --- /dev/null +++ b/build/pkgs/gp2c/checksums.ini @@ -0,0 +1,4 @@ +tarball=gp2c-VERSION.tar.gz +sha1=5acb1a13e1ed8ee877ffb34baa3b817e720f3e50 +md5=cb263990e399153aca6a2540930b4600 +cksum=1931194041 diff --git a/build/pkgs/gp2c/dependencies b/build/pkgs/gp2c/dependencies new file mode 100644 index 00000000000..58cfd0bc484 --- /dev/null +++ b/build/pkgs/gp2c/dependencies @@ -0,0 +1,5 @@ +$(INST)/$(PARI) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/install into SAGE_ROOT/build/Makefile. diff --git a/build/pkgs/gp2c/package-version.txt b/build/pkgs/gp2c/package-version.txt new file mode 100644 index 00000000000..6eb15535643 --- /dev/null +++ b/build/pkgs/gp2c/package-version.txt @@ -0,0 +1 @@ +0.0.9pl3 diff --git a/build/pkgs/gp2c/patches/20150326.patch b/build/pkgs/gp2c/patches/20150326.patch new file mode 100644 index 00000000000..3f7651f8162 --- /dev/null +++ b/build/pkgs/gp2c/patches/20150326.patch @@ -0,0 +1,218 @@ +Various fixes between released version 0.0.9pl3 and gp2c git repo + +diff -ru gp2c-0.0.9pl3/gp2c gp2c/gp2c +--- gp2c-0.0.9pl3/gp2c 2011-10-02 22:28:23.000000000 +0200 ++++ gp2c/gp2c 2015-07-09 14:50:05.600098572 +0200 +@@ -14,4 +14,4 @@ + EOF + exit 1; + fi +-GP2C_FUNC_DSC=desc/func.dsc src/gp2c $* ++GP2C_FUNC_DSC=desc/func.dsc exec src/gp2c "$@" +diff -ru gp2c-0.0.9pl3/gp2c-dbg gp2c/gp2c-dbg +--- gp2c-0.0.9pl3/gp2c-dbg 2011-10-02 22:28:23.000000000 +0200 ++++ gp2c/gp2c-dbg 2015-07-09 14:50:05.601098584 +0200 +@@ -13,4 +13,4 @@ + EOF + exit 1; + fi +-GP2C=./gp2c scripts/gp2c-dbg $* ++GP2C=./gp2c exec scripts/gp2c-dbg "$@" +diff -ru gp2c-0.0.9pl3/gp2c-run gp2c/gp2c-run +--- gp2c-0.0.9pl3/gp2c-run 2011-10-02 22:28:23.000000000 +0200 ++++ gp2c/gp2c-run 2015-07-09 14:50:05.601098584 +0200 +@@ -13,4 +13,4 @@ + EOF + exit 1; + fi +-GP2C=./gp2c scripts/gp2c-run $* ++GP2C=./gp2c exec scripts/gp2c-run "$@" +diff -ru gp2c-0.0.9pl3/src/funcdesc.c gp2c/src/funcdesc.c +--- gp2c-0.0.9pl3/src/funcdesc.c 2014-11-17 14:30:55.000000000 +0100 ++++ gp2c/src/funcdesc.c 2015-07-09 14:50:05.603098609 +0200 +@@ -575,7 +575,8 @@ + gpfunc *gp = lfunc + findfunction(entryname(arg[0])); + if (gp->spec==GPuser && gp->user->wrapper>=0) + { +- if (funcmode(*gp)&(1<user->wrapper; ++ if ((funcmode(*gp)&(1<fout, nb-1,arg+1,FC_tovecprec,d->nerr); + else + { +diff -ru gp2c-0.0.9pl3/src/funcspec.c gp2c/src/funcspec.c +--- gp2c-0.0.9pl3/src/funcspec.c 2014-11-22 22:28:59.000000000 +0100 ++++ gp2c/src/funcspec.c 2015-07-09 14:50:05.604098621 +0200 +@@ -432,8 +432,11 @@ + arg[0] = newleaf(pred); + } + genblock(arg[0],n); +- arg[1]=geninsertvar(arg[1],ret); +- makesubblock(arg[1]); ++ if (arg[1]!=GNOARG) ++ { ++ arg[1]=geninsertvar(arg[1],ret); ++ makesubblock(arg[1]); ++ } + if(nb==3) + { + arg[2]=geninsertvar(arg[2],ret==-1?-1:newleaf(ret)); +diff -ru gp2c-0.0.9pl3/src/genfunc.c gp2c/src/genfunc.c +--- gp2c-0.0.9pl3/src/genfunc.c 2014-12-16 10:56:21.000000000 +0100 ++++ gp2c/src/genfunc.c 2015-07-09 14:50:05.605098633 +0200 +@@ -524,13 +524,15 @@ + } + if (name) + { +- if (i>=nb) +- die(err_func,"too few arguments in lambda"); + fputs(" ",fout); + if (c=='p') + fprintf(fout,"prec"); + else ++ { ++ if (i>=nb) ++ die(err_func,"too few arguments in lambda"); + gencode(fout, name[i++]); ++ } + } + } + if (name && i=nb) +- die(err_func,"too few arguments in lambda"); + if (c=='p') +- fprintf(fout,"prec"); ++ { ++ if (mode&(1<=nb) ++ die(err_func,"too few arguments in lambda"); ++ if (!firstarg) fprintf(fout,", "); ++ firstarg=0; ++ ot = tree[name[i]].t; ++ tree[name[i]].t = t; ++ gencast(fout, name[i], ot); ++ tree[name[i]].t = ot; ++ i++; ++ } + } + if (name && iuser->defnode; + int savc=s_ctx.n; +@@ -650,7 +667,6 @@ + int res; + ctxvar *vres; + context *fc=block+gp->user->bl; +- gpfunc *wr=lfunc+wrap; + gpdescarg *rule; + if (!wr->proto.code) + die(wr->node,"Wrapper not defined"); +@@ -688,7 +704,7 @@ + fprintf(fout," = "); + } + fprintf(fout,"%s(",gp->proto.cname); +- firstarg=genwrapargs(fout, wrap, nb, stack); ++ firstarg=genwrapargs(fout, wrap, nb, stack, m); + for (i=0,d=1;is.n;i++) + { + ctxvar *v=fc->c+i; +diff -ru gp2c-0.0.9pl3/src/printnode.c gp2c/src/printnode.c +--- gp2c-0.0.9pl3/src/printnode.c 2013-10-12 20:32:23.000000000 +0200 ++++ gp2c/src/printnode.c 2015-07-09 14:50:05.606098645 +0200 +@@ -231,6 +231,10 @@ + fprintf(fout,"&"); + printnode(fout,x); + break; ++ case Fvararg: ++ printnode(fout,x); ++ fprintf(fout,"[..]"); ++ break; + case Fcall: + printnode(fout,x); + fprintf(fout,"("); +diff -ru gp2c-0.0.9pl3/src/topfunc.c gp2c/src/topfunc.c +--- gp2c-0.0.9pl3/src/topfunc.c 2014-12-16 10:53:09.000000000 +0100 ++++ gp2c/src/topfunc.c 2015-07-09 14:50:05.606098645 +0200 +@@ -109,7 +109,7 @@ + int nn = newanon(); + int nf = newnode(Fdeffunc,newnode(Ffunction,nn,x),y); + int seq = newnode(Fentry,nn,-1); +- topfunc(n,p,fun,pfun,nf,wr); ++ topfunc(nf,p,fun,pfun,nf,wr); + if (fun>=0) tree[n] = tree[seq]; + } + +diff -ru gp2c-0.0.9pl3/test2/gp/apply.gp gp2c/test2/gp/apply.gp +--- gp2c-0.0.9pl3/test2/gp/apply.gp 2013-06-11 12:02:48.000000000 +0200 ++++ gp2c/test2/gp/apply.gp 2015-07-09 14:50:05.609098682 +0200 +@@ -3,6 +3,7 @@ + f3(f,v)=apply(f,v) + g(x)=x^2+1 + f4(v)=apply(g,v) ++f5(z:small)=apply(n:small->(n+z)!,[1,2,3,4,5]) + + g1(v,z)=select(x->x>z,v) + g2(v)=select(isprime,v) +diff -ru gp2c-0.0.9pl3/test2/input/apply gp2c/test2/input/apply +--- gp2c-0.0.9pl3/test2/input/apply 2013-06-11 12:02:48.000000000 +0200 ++++ gp2c/test2/input/apply 2015-07-09 14:50:05.609098682 +0200 +@@ -2,6 +2,7 @@ + f2([1,2,3,4]) + f3(sqr,[1,2,3,4]) + f4([1,2,3,4]) ++f5(-1) + + g1([1,2,3,4],2) + g2([1,2,3,4]) +diff -ru gp2c-0.0.9pl3/test2/res/apply.res gp2c/test2/res/apply.res +--- gp2c-0.0.9pl3/test2/res/apply.res 2013-06-11 12:02:48.000000000 +0200 ++++ gp2c/test2/res/apply.res 2015-07-09 14:50:05.610098694 +0200 +@@ -2,6 +2,7 @@ + [1, 4, 9, 16] + [1, 4, 9, 16] + [2, 5, 10, 17] ++[1, 1, 2, 6, 24] + [3, 4] + [2, 3] + [2, 3] diff --git a/build/pkgs/gp2c/patches/global.patch b/build/pkgs/gp2c/patches/global.patch new file mode 100644 index 00000000000..dd0c24d0472 --- /dev/null +++ b/build/pkgs/gp2c/patches/global.patch @@ -0,0 +1,126 @@ +Patch taken from upstream to fix global() + +diff --git a/src/genblock.c b/src/genblock.c +index a3582d2..e626e0f 100644 +--- a/src/genblock.c ++++ b/src/genblock.c +@@ -251,7 +251,11 @@ int genblockdeclaration(int args, int n, int flag, int type, int *seq) + /*Make sure GEN objects are gerepilable.*/ + val = newnode(Ftag, newnode(Ftag, newsmall(0), Ggen), tv); + if (decl==global) +- fillvar(var,flag,tv,val); ++ { ++ int f=fillvar(var,flag,tv,-1); ++ if (autogc) ++ affectval(f,val,seq); ++ } + else + pushvar(var,flag,tv,val); + break; +@@ -270,10 +274,12 @@ int genblockdeclaration(int args, int n, int flag, int type, int *seq) + val=newcall("_const_quote",newstringnode(entryname(stack[i]),-1)); + affectval(fillvar(stack[i],flag,mint,-1),val,seq); + } +- else if (autogc) ++ else + { ++ int f = fillvar(stack[i],flag,mint,-1); + /*Make sure (implicitly GEN) objects are gerepilable.*/ +- fillvar(stack[i],flag,mint, newsmall(0)); ++ if (autogc) ++ affectval(f, newsmall(0), seq); + } + break; + case function: +diff --git a/src/topfunc.c b/src/topfunc.c +index 872830e..bff787f 100644 +--- a/src/topfunc.c ++++ b/src/topfunc.c +@@ -154,13 +154,12 @@ static int topfuncproto(int n, int fun, int pfun, int nf) + break; + case 'I': + case 'E': +- if (wr>=-1) +- { +- case 'J': +- seq = newnode(Flambda,var,newleaf(a)); +- tree[a] = tree[seq]; +- topfunclambda(a, n, fun, pfun, wr); +- } ++ if (wr<-1) ++ break; ++ case 'J': /* Fall through */ ++ seq = newnode(Flambda,var,newleaf(a)); ++ tree[a] = tree[seq]; ++ topfunclambda(a, n, fun, pfun, wr); + break; + } + break; +diff --git a/src/toplevel.c b/src/toplevel.c +index f5999c4..23c92a8 100644 +--- a/src/toplevel.c ++++ b/src/toplevel.c +@@ -120,7 +120,7 @@ void gentoplevel(int n) + } + else + gentoplevel(x); +- if (y>=0 && tree[y].f!=Fdeffunc) ++ if (y>=0 && tree[y].f!=Fdeffunc && tree[y].f!=Fseq) + { + int dy = detag(y); + if (isfunc(dy,"global")) +diff --git a/test/gp/initfunc.gp b/test/gp/initfunc.gp +index 03c1dd3..ed0b87d 100644 +--- a/test/gp/initfunc.gp ++++ b/test/gp/initfunc.gp +@@ -1,4 +1,4 @@ +-global(y:var,n:small=0,m:small,k=2,L:vec,T2) ++global(y:var='y,n:small=0,m:small,k=2,L:vec,T2) + T=clone([4,3,2,2,1,1,1,1,0,0,0,0,0,0,0,0]) + T2=clone([4,3,2,2,1,1,1,1,0,0,0,0,0,0,0,0]) + L=[]~; +@@ -15,3 +15,10 @@ aff()=print(n," ",m); + M=[1,2;3,4] + print(x^4+k); + pow_M(k)=M^k ++global(x1:int,x2:int); ++global(y1,y2):int; ++global(z1,z2); ++f1(x)=my(z=x^2+1);if(z==1,x1=x,x2=x);z ++f2(x)=my(z=x^2+1);if(z==1,y1=x,y2=x);z ++f3(x)=my(z=x^2+1);if(z==1,z1=x,z2=x);z ++global(t1);t1=0 +diff --git a/test/input/initfunc b/test/input/initfunc +index 8506d08..46a126d 100644 +--- a/test/input/initfunc ++++ b/test/input/initfunc +@@ -2,3 +2,9 @@ init_initfunc(); print(pow_M(2)) + vector(100,i,mybfffo(i)) + vector(31,i,mybfffo(1<<(i-1))) + for(i=1,5,count();aff()) ++init_initfunc();f1(0) ++init_initfunc();f1(1) ++init_initfunc();f2(0) ++init_initfunc();f2(1) ++init_initfunc();f3(0) ++init_initfunc();f3(1) +diff --git a/test/res/initfunc.res b/test/res/initfunc.res +index 391d09d..a1e538c 100644 +--- a/test/res/initfunc.res ++++ b/test/res/initfunc.res +@@ -13,3 +13,15 @@ x^4 + 2 + 3 30 + 4 30 + 5 29 ++x^4 + 2 ++1 ++x^4 + 2 ++2 ++x^4 + 2 ++1 ++x^4 + 2 ++2 ++x^4 + 2 ++1 ++x^4 + 2 ++2 diff --git a/build/pkgs/gp2c/spkg-check b/build/pkgs/gp2c/spkg-check new file mode 100755 index 00000000000..9adcda86f90 --- /dev/null +++ b/build/pkgs/gp2c/spkg-check @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && $MAKE check diff --git a/build/pkgs/gp2c/spkg-install b/build/pkgs/gp2c/spkg-install new file mode 100755 index 00000000000..0346d362ada --- /dev/null +++ b/build/pkgs/gp2c/spkg-install @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +if [ -z "$SAGE_LOCAL" ]; then + echo "SAGE_LOCAL undefined ... exiting" + echo "Maybe run 'sage -sh'?" + exit 1 +fi + +cd src + +for patch in ../patches/*.patch; do + [ -r "$patch" ] || continue # Skip non-existing or non-readable patches + patch -p1 <"$patch" + if [ $? -ne 0 ]; then + echo >&2 "Error applying '$patch'" + exit 1 + fi +done + +set -e + +./configure --prefix="$SAGE_LOCAL" --with-paricfg="$SAGE_LOCAL/lib/pari/pari.cfg" + +$MAKE install diff --git a/build/pkgs/gp2c/type b/build/pkgs/gp2c/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/gp2c/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/lcalc/spkg-install b/build/pkgs/lcalc/spkg-install index d1ff0c5c5e4..50bd1f97752 100755 --- a/build/pkgs/lcalc/spkg-install +++ b/build/pkgs/lcalc/spkg-install @@ -78,4 +78,9 @@ success "$MAKE" echo "Now installing lcalc binary, library and header files..." rm -fr "$SAGE_LOCAL"/include/libLfunction $MAKE install + +if [ "$UNAME" = "Darwin" ]; then + install_name_tool -id ${SAGE_LOCAL}/lib/libLfunction.dylib "${SAGE_LOCAL}"/lib/libLfunction.dylib +fi + success "$MAKE install" diff --git a/build/pkgs/mpir/package-version.txt b/build/pkgs/mpir/package-version.txt index 24ba9a38de6..9c8d6791e82 100644 --- a/build/pkgs/mpir/package-version.txt +++ b/build/pkgs/mpir/package-version.txt @@ -1 +1 @@ -2.7.0 +2.7.0.p1 diff --git a/build/pkgs/mpir/patches/fix-19280.patch b/build/pkgs/mpir/patches/fix-19280.patch new file mode 100644 index 00000000000..ced078c66ee --- /dev/null +++ b/build/pkgs/mpir/patches/fix-19280.patch @@ -0,0 +1,22 @@ +Apply the fix proposed in http://trac.sagemath.org/ticket/18546#comment:23 +to the bug reported in http://trac.sagemath.org/ticket/19280 . + +diff -ru mpir-2.7.0/gmp-impl.h mpir-2.7.0-fixed/gmp-impl.h +Index: gmp-impl.h +=================================================================== +--- mpir-2.7.0/gmp-impl.h 2015-06-10 23:02:35.000000000 +0200 ++++ mpir-2.7.0-fixed/gmp-impl.h 2015-09-29 17:04:17.746919331 +0200 +@@ -2040,11 +2040,11 @@ + #endif + + #ifndef SB_DIVAPPR_Q_SMALL_THRESHOLD +-#define SB_DIVAPPR_Q_SMALL_THRESHOLD 11 ++#define SB_DIVAPPR_Q_SMALL_THRESHOLD 0 + #endif + + #ifndef SB_DIV_QR_SMALL_THRESHOLD +-#define SB_DIV_QR_SMALL_THRESHOLD 11 ++#define SB_DIV_QR_SMALL_THRESHOLD 0 + #endif + + #ifndef DC_DIVAPPR_Q_THRESHOLD diff --git a/build/pkgs/mpir/patches/test-19280.patch b/build/pkgs/mpir/patches/test-19280.patch new file mode 100644 index 00000000000..cef193c8bbf --- /dev/null +++ b/build/pkgs/mpir/patches/test-19280.patch @@ -0,0 +1,161 @@ +Include a test for the bug reported in http://trac.sagemath.org/ticket/19280 . +diff -ruN mpir-2.7.0/tests/mpz/Makefile.am mpir-2.7.0-patched/tests/mpz/Makefile.am +--- mpir-2.7.0/tests/mpz/Makefile.am 2015-06-09 19:18:27.000000000 +0200 ++++ mpir-2.7.0-patched/tests/mpz/Makefile.am 2015-09-24 18:39:33.093974089 +0200 +@@ -26,7 +26,7 @@ + AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/tests + LDADD = $(top_builddir)/tests/libtests.la $(top_builddir)/libmpir.la + +-check_PROGRAMS = bit convert dive dive_ui io logic reuse t-addsub t-aorsmul t-bin t-cdiv_ui t-cmp t-cmp_d t-cmp_si t-cong t-cong_2exp t-div_2exp t-divis t-divis_2exp t-export t-fac_ui t-fdiv t-fdiv_ui t-fib_ui t-fits t-gcd t-gcd_ui t-get_d t-get_d_2exp t-get_si t-get_sx t-get_ux t-hamdist t-import t-inp_str t-io_raw t-jac t-lcm t-likely_prime_p t-lucnum_ui t-mfac_uiui t-mul t-mul_i t-next_prime_candidate t-oddeven t-perfpow t-perfsqr t-popcount t-pow t-powm t-powm_ui t-pprime_p t-primorial_ui t-root t-scan t-set_d t-set_f t-set_si t-set_str t-set_sx t-set_ux t-sizeinbase t-sqrtrem t-tdiv t-tdiv_ui t-trial_division ++check_PROGRAMS = bit convert dive dive_ui io logic reuse t-addsub t-aorsmul t-bin t-cdiv_ui t-cmp t-cmp_d t-cmp_si t-cong t-cong_2exp t-div_2exp t-divis t-divis_2exp t-export t-fac_ui t-fdiv t-fdiv_ui t-fib_ui t-fits t-gcd t-gcd_ui t-get_d t-get_d_2exp t-get_si t-get_sx t-get_ux t-hamdist t-import t-inp_str t-io_raw t-jac t-lcm t-likely_prime_p t-lucnum_ui t-mfac_uiui t-mul t-mul_i t-next_prime_candidate t-oddeven t-perfpow t-perfsqr t-popcount t-pow t-powm t-powm_ui t-pprime_p t-primorial_ui t-root t-scan t-set_d t-set_f t-set_si t-set_str t-set_sx t-set_ux t-sizeinbase t-sqrtrem t-tdiv t-tdiv_ui t-trial_division t-19280 + + if ENABLE_STATIC + if ENABLE_SHARED +diff -ruN mpir-2.7.0/tests/mpz/Makefile.in mpir-2.7.0-patched/tests/mpz/Makefile.in +--- mpir-2.7.0/tests/mpz/Makefile.in 2015-06-16 12:40:00.000000000 +0200 ++++ mpir-2.7.0-patched/tests/mpz/Makefile.in 2015-09-24 18:42:37.485967609 +0200 +@@ -121,7 +121,7 @@ + t-set_si$(EXEEXT) t-set_str$(EXEEXT) t-set_sx$(EXEEXT) \ + t-set_ux$(EXEEXT) t-sizeinbase$(EXEEXT) t-sqrtrem$(EXEEXT) \ + t-tdiv$(EXEEXT) t-tdiv_ui$(EXEEXT) t-trial_division$(EXEEXT) \ +- $(am__EXEEXT_1) ++ t-19280$(EXEEXT) $(am__EXEEXT_1) + @ENABLE_SHARED_TRUE@@ENABLE_STATIC_TRUE@am__append_1 = st_hamdist st_popcount + subdir = tests/mpz + DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ +@@ -197,6 +197,11 @@ + st_popcount_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(st_popcount_LDFLAGS) $(LDFLAGS) -o $@ ++t_19280_SOURCES = t-19280.c ++t_19280_OBJECTS = t-19280.$(OBJEXT) ++t_19280_LDADD = $(LDADD) ++t_19280_DEPENDENCIES = $(top_builddir)/tests/libtests.la \ ++ $(top_builddir)/libmpir.la + t_addsub_SOURCES = t-addsub.c + t_addsub_OBJECTS = t-addsub.$(OBJEXT) + t_addsub_LDADD = $(LDADD) +@@ -526,21 +531,7 @@ + am__v_CCLD_0 = @echo " CCLD " $@; + am__v_CCLD_1 = + SOURCES = bit.c convert.c dive.c dive_ui.c io.c logic.c reuse.c \ +- $(st_hamdist_SOURCES) $(st_popcount_SOURCES) t-addsub.c \ +- t-aorsmul.c t-bin.c t-cdiv_ui.c t-cmp.c t-cmp_d.c t-cmp_si.c \ +- t-cong.c t-cong_2exp.c t-div_2exp.c t-divis.c t-divis_2exp.c \ +- t-export.c t-fac_ui.c t-fdiv.c t-fdiv_ui.c t-fib_ui.c t-fits.c \ +- t-gcd.c t-gcd_ui.c t-get_d.c t-get_d_2exp.c t-get_si.c \ +- t-get_sx.c t-get_ux.c t-hamdist.c t-import.c t-inp_str.c \ +- t-io_raw.c t-jac.c t-lcm.c t-likely_prime_p.c t-lucnum_ui.c \ +- t-mfac_uiui.c t-mul.c t-mul_i.c t-next_prime_candidate.c \ +- t-oddeven.c t-perfpow.c t-perfsqr.c t-popcount.c t-pow.c \ +- t-powm.c t-powm_ui.c t-pprime_p.c t-primorial_ui.c t-root.c \ +- t-scan.c t-set_d.c t-set_f.c t-set_si.c t-set_str.c t-set_sx.c \ +- t-set_ux.c t-sizeinbase.c t-sqrtrem.c t-tdiv.c t-tdiv_ui.c \ +- t-trial_division.c +-DIST_SOURCES = bit.c convert.c dive.c dive_ui.c io.c logic.c reuse.c \ +- $(am__st_hamdist_SOURCES_DIST) $(am__st_popcount_SOURCES_DIST) \ ++ $(st_hamdist_SOURCES) $(st_popcount_SOURCES) t-19280.c \ + t-addsub.c t-aorsmul.c t-bin.c t-cdiv_ui.c t-cmp.c t-cmp_d.c \ + t-cmp_si.c t-cong.c t-cong_2exp.c t-div_2exp.c t-divis.c \ + t-divis_2exp.c t-export.c t-fac_ui.c t-fdiv.c t-fdiv_ui.c \ +@@ -554,6 +545,21 @@ + t-set_f.c t-set_si.c t-set_str.c t-set_sx.c t-set_ux.c \ + t-sizeinbase.c t-sqrtrem.c t-tdiv.c t-tdiv_ui.c \ + t-trial_division.c ++DIST_SOURCES = bit.c convert.c dive.c dive_ui.c io.c logic.c reuse.c \ ++ $(am__st_hamdist_SOURCES_DIST) $(am__st_popcount_SOURCES_DIST) \ ++ t-19280.c t-addsub.c t-aorsmul.c t-bin.c t-cdiv_ui.c t-cmp.c \ ++ t-cmp_d.c t-cmp_si.c t-cong.c t-cong_2exp.c t-div_2exp.c \ ++ t-divis.c t-divis_2exp.c t-export.c t-fac_ui.c t-fdiv.c \ ++ t-fdiv_ui.c t-fib_ui.c t-fits.c t-gcd.c t-gcd_ui.c t-get_d.c \ ++ t-get_d_2exp.c t-get_si.c t-get_sx.c t-get_ux.c t-hamdist.c \ ++ t-import.c t-inp_str.c t-io_raw.c t-jac.c t-lcm.c \ ++ t-likely_prime_p.c t-lucnum_ui.c t-mfac_uiui.c t-mul.c \ ++ t-mul_i.c t-next_prime_candidate.c t-oddeven.c t-perfpow.c \ ++ t-perfsqr.c t-popcount.c t-pow.c t-powm.c t-powm_ui.c \ ++ t-pprime_p.c t-primorial_ui.c t-root.c t-scan.c t-set_d.c \ ++ t-set_f.c t-set_si.c t-set_str.c t-set_sx.c t-set_ux.c \ ++ t-sizeinbase.c t-sqrtrem.c t-tdiv.c t-tdiv_ui.c \ ++ t-trial_division.c + am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ +@@ -1020,6 +1026,10 @@ + @rm -f st_popcount$(EXEEXT) + $(AM_V_CCLD)$(st_popcount_LINK) $(st_popcount_OBJECTS) $(st_popcount_LDADD) $(LIBS) + ++t-19280$(EXEEXT): $(t_19280_OBJECTS) $(t_19280_DEPENDENCIES) $(EXTRA_t_19280_DEPENDENCIES) ++ @rm -f t-19280$(EXEEXT) ++ $(AM_V_CCLD)$(LINK) $(t_19280_OBJECTS) $(t_19280_LDADD) $(LIBS) ++ + t-addsub$(EXEEXT): $(t_addsub_OBJECTS) $(t_addsub_DEPENDENCIES) $(EXTRA_t_addsub_DEPENDENCIES) + @rm -f t-addsub$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(t_addsub_OBJECTS) $(t_addsub_LDADD) $(LIBS) +@@ -1931,6 +1941,13 @@ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ ++ "$$tst" $(AM_TESTS_FD_REDIRECT) ++t-19280.log: t-19280$(EXEEXT) ++ @p='t-19280$(EXEEXT)'; \ ++ b='t-19280'; \ ++ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ ++ --log-file $$b.log --trs-file $$b.trs \ ++ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) + st_hamdist.log: st_hamdist$(EXEEXT) + @p='st_hamdist$(EXEEXT)'; \ +diff -ruN mpir-2.7.0/tests/mpz/t-19280.c mpir-2.7.0-patched/tests/mpz/t-19280.c +--- mpir-2.7.0/tests/mpz/t-19280.c 1970-01-01 01:00:00.000000000 +0100 ++++ mpir-2.7.0-patched/tests/mpz/t-19280.c 2015-09-24 18:57:03.005937190 +0200 +@@ -0,0 +1,50 @@ ++/* Test t-19280. ++ ++*/ ++ ++#include ++#include ++ ++#include "mpir.h" ++#include "gmp-impl.h" ++#include "tests.h" ++ ++#define printf gmp_printf ++ ++/* Check mpz_tdif_q gives a correct answer on 32-bit, ++ see http://trac.sagemath.org/ticket/19280. ++ This was wrong in sage 6.9.beta7. */ ++static void ++check_19280 (void) ++{ ++ ++ mpz_t one, x, w, correct; ++ mpz_init(one); ++ mpz_init(x); ++ mpz_init(w); ++ mpz_init(correct); ++ mpz_set_str(one, "62165404551223330269422781018352605012557018849668464680057997111644937126566671941632", 10); ++ mpz_set_str(x, "39623752663112484341451587580", 10); ++ mpz_set_str(correct, "1568892403497558507879962225846103176600476845510570267609", 10); ++ ++ mpz_tdiv_q(w, one, x); ++ if (mpz_cmp(w, correct)!=0) { ++ printf("mpz_tdiv_q returned %Zd instead of %Zd.\n", w, correct); ++ abort(); ++ } ++ mpz_clear(one); ++ mpz_clear(x); ++ mpz_clear(w); ++ mpz_clear(correct); ++} ++ ++int ++main (void) ++{ ++ tests_start (); ++ ++ check_19280 (); ++ ++ tests_end (); ++ exit (0); ++} diff --git a/build/pkgs/notebook/package-version.txt b/build/pkgs/notebook/package-version.txt index 61d8a2af785..f0cd2d909bb 100644 --- a/build/pkgs/notebook/package-version.txt +++ b/build/pkgs/notebook/package-version.txt @@ -1 +1 @@ -4.0.4.p1 +4.0.4.p2 diff --git a/build/pkgs/notebook/patches/jupyter_notebook_config.py b/build/pkgs/notebook/patches/jupyter_notebook_config.py index 3bf63a1649a..99c7801b76c 100644 --- a/build/pkgs/notebook/patches/jupyter_notebook_config.py +++ b/build/pkgs/notebook/patches/jupyter_notebook_config.py @@ -4,4 +4,4 @@ # needs to have the mathjax_url set to wherever your distribution # installs mathjax. -c.NotebookApp.mathjax_url = '../nbextensions/mathjax/MathJax.js' +c.NotebookApp.mathjax_url = 'nbextensions/mathjax/MathJax.js' diff --git a/build/pkgs/notebook/patches/mathjax.patch b/build/pkgs/notebook/patches/mathjax.patch new file mode 100644 index 00000000000..1237afef049 --- /dev/null +++ b/build/pkgs/notebook/patches/mathjax.patch @@ -0,0 +1,73 @@ +commit 17b364389182e667d64db93db2dcecc84dada8f9 +Author: Jeroen Demeyer +Date: Mon Oct 26 18:29:42 2015 +0100 + + Interpret mathjax_url relative to base_url + +diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py +index 30b1223..127a6ba 100644 +--- a/notebook/base/handlers.py ++++ b/notebook/base/handlers.py +@@ -32,7 +32,7 @@ from ipython_genutils.path import filefind + from ipython_genutils.py3compat import string_types + + import notebook +-from notebook.utils import is_hidden, url_path_join, url_escape ++from notebook.utils import is_hidden, url_path_join, url_is_absolute, url_escape + from notebook.services.security import csp_report_uri + + #----------------------------------------------------------------------------- +@@ -155,7 +155,10 @@ class IPythonHandler(AuthenticatedHandler): + + @property + def mathjax_url(self): +- return self.settings.get('mathjax_url', '') ++ url = self.settings.get('mathjax_url', '') ++ if not url or url_is_absolute(url): ++ return url ++ return url_path_join(self.base_url, url) + + @property + def base_url(self): +diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py +index bfbe467..c74e4eb 100644 +--- a/notebook/notebookapp.py ++++ b/notebook/notebookapp.py +@@ -658,9 +658,7 @@ class NotebookApp(JupyterApp): + def _mathjax_url_default(self): + if not self.enable_mathjax: + return u'' +- static_url_prefix = self.tornado_settings.get("static_url_prefix", +- url_path_join(self.base_url, "static") +- ) ++ static_url_prefix = self.tornado_settings.get("static_url_prefix", "static") + return url_path_join(static_url_prefix, 'components', 'MathJax', 'MathJax.js') + + def _mathjax_url_changed(self, name, old, new): +diff --git a/notebook/utils.py b/notebook/utils.py +index 9ba424c..ee9c6ac 100644 +--- a/notebook/utils.py ++++ b/notebook/utils.py +@@ -13,9 +13,10 @@ import sys + from distutils.version import LooseVersion + + try: +- from urllib.parse import quote, unquote ++ from urllib.parse import quote, unquote, urlparse + except ImportError: + from urllib import quote, unquote ++ from urlparse import urlparse + + from ipython_genutils import py3compat + +@@ -39,6 +40,10 @@ def url_path_join(*pieces): + if result == '//': result = '/' + return result + ++def url_is_absolute(url): ++ """Determine whether a given URL is absolute""" ++ return urlparse(url).path.startswith("/") ++ + def path2url(path): + """Convert a local file path to a URL""" + pieces = [ quote(p) for p in path.split(os.sep) ] diff --git a/build/pkgs/notebook/spkg-install b/build/pkgs/notebook/spkg-install index 10759a4d973..a42c3ed659d 100755 --- a/build/pkgs/notebook/spkg-install +++ b/build/pkgs/notebook/spkg-install @@ -1,6 +1,18 @@ #!/usr/bin/env bash -cd src && python setup.py install +cd src + +for patch in ../patches/*.patch; do + [ -r "$patch" ] || continue # Skip non-existing or non-readable patches + patch -p1 <"$patch" + if [ $? -ne 0 ]; then + echo >&2 "Error applying '$patch'" + exit 1 + fi +done + + +python setup.py install # Install the Jupyter notebook configuration ETC_JUPYTER="$SAGE_ETC"/jupyter diff --git a/build/pkgs/pari_jupyter/SPKG.txt b/build/pkgs/pari_jupyter/SPKG.txt new file mode 100644 index 00000000000..681cdc57f23 --- /dev/null +++ b/build/pkgs/pari_jupyter/SPKG.txt @@ -0,0 +1,22 @@ += pari_jupyter = + +== Description == + +A Jupyter kernel for PARI/GP + +== License == + +GPL version 3 or later + +== Upstream Contact == + +* https://github.com/jdemeyer/pari_jupyter +* Jeroen Demeyer + +== Dependencies == + +* Jupyter 4 +* Python (tested with 2.7.9) +* Cython (git master) +* PARI (git master) +* GMP or MPIR (any version which works with PARI) diff --git a/build/pkgs/pari_jupyter/checksums.ini b/build/pkgs/pari_jupyter/checksums.ini new file mode 100644 index 00000000000..d3b36f6aebb --- /dev/null +++ b/build/pkgs/pari_jupyter/checksums.ini @@ -0,0 +1,4 @@ +tarball=pari_jupyter-VERSION.tar.gz +sha1=404df06171e68056d9efe8a29804204138488885 +md5=902b290a997128e6be949c0bec44ca6e +cksum=3922118226 diff --git a/build/pkgs/pari_jupyter/dependencies b/build/pkgs/pari_jupyter/dependencies new file mode 100644 index 00000000000..5cf512f3f56 --- /dev/null +++ b/build/pkgs/pari_jupyter/dependencies @@ -0,0 +1 @@ +$(INST)/$(PARI) $(INST)/$(JUPYTER_CORE) | $(INST)/$(CYTHON) diff --git a/build/pkgs/pari_jupyter/package-version.txt b/build/pkgs/pari_jupyter/package-version.txt new file mode 100644 index 00000000000..3eefcb9dd5b --- /dev/null +++ b/build/pkgs/pari_jupyter/package-version.txt @@ -0,0 +1 @@ +1.0.0 diff --git a/build/pkgs/pari_jupyter/spkg-install b/build/pkgs/pari_jupyter/spkg-install new file mode 100755 index 00000000000..94ac1fed30e --- /dev/null +++ b/build/pkgs/pari_jupyter/spkg-install @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd src && ./setup.py install diff --git a/build/pkgs/pari_jupyter/type b/build/pkgs/pari_jupyter/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/pari_jupyter/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/qepcad/spkg-install b/build/pkgs/qepcad/spkg-install index 1f7566b703c..6da4a409be9 100755 --- a/build/pkgs/qepcad/spkg-install +++ b/build/pkgs/qepcad/spkg-install @@ -33,7 +33,7 @@ saclib="$SAGE_LOCAL/lib/saclib" export saclib qe=$(pwd -P) export qe -$MAKE opt +$MAKE -j1 opt if [ $? -ne 0 ]; then echo >&2 "Error building qepcad." diff --git a/build/pkgs/r/spkg-install b/build/pkgs/r/spkg-install index 0e5f4f2c2a6..679d39e1b82 100755 --- a/build/pkgs/r/spkg-install +++ b/build/pkgs/r/spkg-install @@ -82,15 +82,14 @@ fi if [ "$UNAME" = "Darwin" ]; then # We don't want to install R as a library framework on OSX R_CONFIGURE="--enable-R-framework=no $R_CONFIGURE" + # OS X 10.10 and/or Xcode 6.3 and over broke the R installation. See + # http://trac.sagemath.org/ticket/18254. + if [ $MACOSX_VERSION -ge 14 ]; then + echo "OS X 10.$[$MACOSX_VERSION-4] Configuring R without aqua support." + R_CONFIGURE="--with-aqua=no $R_CONFIGURE" + fi fi -# OS X 10.10 and/or Xcode 6.3 broke the R installation. See -# http://trac.sagemath.org/ticket/18254. -if { uname -sr | grep 'Darwin 14\.' ;} &>/dev/null; then - echo "OS X 10.10: Configuring R without aqua support." - R_CONFIGURE="--with-aqua=no $R_CONFIGURE" -fi - if [ "$UNAME" = "CYGWIN" ]; then # Cygwin libm does not provide "long double" functions # and we do not install Cephes on Cygwin at the moment @@ -126,6 +125,12 @@ for patch in ../patches/*.patch; do fi done +if [ "$UNAME" = "Darwin" ]; then + # Fixing install_name(s) + sed -i -e 's:\"-install_name :\"-install_name ${libdir}/R/lib/:' configure + sed -i -e "/SHLIB_EXT/s/\.so/.dylib/" configure +fi + # Don't override R_HOME_DIR in local/bin/R while building R. # See patches/R.sh.patch export SAGE_BUILDING_R=yes diff --git a/build/pkgs/singular/spkg-install b/build/pkgs/singular/spkg-install index d8ff399a465..4d7f0c318be 100755 --- a/build/pkgs/singular/spkg-install +++ b/build/pkgs/singular/spkg-install @@ -218,6 +218,11 @@ build_libsingular() return 1 fi + if [ "$UNAME" = "Darwin" ]; then + # on darwin we need to adjust the install name of the library + install_name_tool -id "${SAGE_LOCAL}"/lib/libsingular.dylib "${SAGE_LOCAL}"/lib/libsingular.dylib + fi + # singular does not install this header cp Singular/sing_dbm.h $SAGE_LOCAL/include/singular/ diff --git a/build/pkgs/zn_poly/spkg-install b/build/pkgs/zn_poly/spkg-install index 00067eab437..25161e48a1c 100755 --- a/build/pkgs/zn_poly/spkg-install +++ b/build/pkgs/zn_poly/spkg-install @@ -192,6 +192,7 @@ else echo >&2 "Error copying 'libzn_poly.dylib'." exit 1 fi + install_name_tool -id ${SAGE_LOCAL}/lib/libzn_poly.dylib "${SAGE_LOCAL}"/lib/libzn_poly.dylib fi ############################################################################### diff --git a/configure.ac b/configure.ac index 5e320f74a41..2950ad9245a 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,14 @@ -dnl Very loosely based on configure.ac in prereq-0.3 written by William Stein. -dnl Version 0.7 written by David Kirkby, released under the GPL version 2. -dnl in January 2010 +#***************************************************************************** +# Copyright (C) 2005-2007 William Stein +# Copyright (C) 2009-2011 David Kirkby +# Copyright (C) 2012-2015 Jeroen Demeyer +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** dnl If you are going to update this, please stick the recommended layout dnl in the autoconf manual - i.e. @@ -14,9 +22,8 @@ dnl Next check compiler characteristics dnl Next check for library functions dnl Next check for system services -dnl Older versions give "undefined macro: _m4_divert_diversion", see -dnl http://trac.sagemath.org/ticket/15606#comment:19 -AC_PREREQ([2.64]) +dnl Older versions do not support $GFC +AC_PREREQ([2.69]) AC_DEFUN([SAGE_VERSION], m4_esyscmd_s([. src/bin/sage-version.sh && echo $SAGE_VERSION])) AC_INIT([Sage], SAGE_VERSION, [sage-devel@googlegroups.com]) @@ -80,8 +87,6 @@ SAGE_SHARE="$SAGE_LOCAL/share" SAGE_EXTCODE="$SAGE_SHARE/sage/ext" SAGE_SPKG_INST="$SAGE_LOCAL/var/lib/sage/installed" -export PATH="$SAGE_ROOT/build/bin:$SAGE_SRC/bin:$SAGE_LOCAL/bin:$PATH" - ############################################################################### # Determine whether to use MPIR (default standard pkg) or GMP (optional pkg). ############################################################################### @@ -115,171 +120,560 @@ else ;; esac fi +] + + +#--------------------------------------------------------- +AX_CHECK_ROOT([AC_MSG_ERROR([You cannot build Sage as root, switch to an unpriviledged user])], []) + +# Check whether we are on a supported platform +AC_CANONICAL_BUILD() +AC_CANONICAL_HOST() + +case $host in +*-*-sunos*|*-*-solaris2.[1-9]) +AC_MSG_ERROR([[ +Sage is not supported on any version of Solaris earlier than 10. +Sage has been tested on the first release of Solaris 10 +(03/2005) and works on that. Sage may or may not work with +your version of Solaris. + +More information can be found about Sage on Solaris +on the Wiki at http://wiki.sagemath.org/solaris]]);; + +*-*-darwin[1-7].*) +AC_MSG_ERROR([[ +Sage has never been built on OS X 10.3 (Panther) +or earlier. The oldest version of OS X successfully used +is OS X version 10.4 (Tiger). You might consider updating +your version of OS X if your hardware permits this, but +Apple charges for upgrades of OS X]]);; + +*-*-hpux*) +AC_MSG_ERROR([[ +You are attempting to build Sage on HP's HP-UX operating system, +which is not a supported platform for Sage yet though +some work has been done on HP-UX. A port does not look to +be particularly difficult. Some information can be +found on the Sage Wiki at http://wiki.sagemath.org/HP-UX + +If you would like to help port Sage to HP-UX, +please join the sage-devel discussion list - see +http://groups.google.com/group/sage-devel +The Sage community would also appreciate any patches you submit]]);; + +*-*-aix*) +AC_MSG_ERROR([[ +You are attempting to build Sage on IBM's AIX operating system, +which is not a supported platform for Sage yet. Things may or +may not work. If you would like to help port Sage to AIX, +please join the sage-devel discussion list - see +http://groups.google.com/group/sage-devel +The Sage community would also appreciate any patches you submit]]);; + +*-*-irix*) +AC_MSG_ERROR([[ +You are attempting to build Sage on SGI's IRIX operating system, +which is not a supported platform for Sage yet. Things may or +may not work. If you would like to help port Sage to IRIX, +please join the sage-devel discussion list - see +http://groups.google.com/group/sage-devel +The Sage community would also appreciate any patches you submit]]);; + +*-*-osf*) +AC_MSG_ERROR([[ +You are attempting to build Sage on HP's Tru64 operating system, +which is not a supported platform for Sage yet. Things may or +may not work. If you would like to help port Sage to Tru64, +please join the sage-devel discussion list - see +http://groups.google.com/group/sage-devel +The Sage community would also appreciate any patches you submit]]);; + +*-*-freebsd*) +AC_MSG_ERROR([[ +You are attempting to build Sage on the FreeBSD operating system, +which is not a supported platform for Sage yet, though +developers are working on adding FreeBSD support. Things may or +may not work. If you would like to help port Sage to FreeBSD, +please join the sage-devel discussion list - see +http://groups.google.com/group/sage-devel +The Sage community would also appreciate any patches you submit]]);; + +# The following are all supported platforms. +*-*-linux*);; +*-*-darwin*);; +*-*-solaris*);; +*-*-cygwin*);; + +# Wildcard for other unsupported platforms +*) +AC_MSG_ERROR([[ +You are attempting to build Sage on $host, +which is not a supported platform for Sage yet. Things may or +may not work. If you would like to help port Sage to $host, +please join the sage-devel discussion list - see +http://groups.google.com/group/sage-devel +The Sage community would also appreciate any patches you submit]]);; +esac + ############################################################################### -# Determine whether to install GCC (gcc, g++, gfortran). +# Check general programs ############################################################################### -# Determine various compilers. These variables should not be exported, -# they are only used in this build/make/install script to determine whether to -# install GCC. The "real" $CC, $CXX,... variables for building Sage are -# set in sage-env. +AC_CHECK_PROG(found_ar, ar, yes, no) +if test x$found_ar != xyes +then + AC_MSG_NOTICE([Sorry, the 'ar' command must be in the path to build AC_PACKAGE_NAME]) + AC_MSG_NOTICE([On some systems it can be found in /usr/ccs/bin ]) + AC_MSG_NOTICE(['ar' is also part of the GNU 'binutils' package.]) + AC_MSG_ERROR([Exiting, as the archiver 'ar' can not be found.]) +fi + +AC_CHECK_PROG(found_m4, m4, yes, no) +if test x$found_m4 != xyes +then + AC_MSG_NOTICE([Sorry, the 'm4' command must be in the path to build AC_PACKAGE_NAME]) + AC_MSG_NOTICE([On some systems it can be found in /usr/ccs/bin]) + AC_MSG_NOTICE([See also http://www.gnu.org/software/m4/]) + AC_MSG_ERROR([Exiting, as the macro processor 'm4' can not be found.]) +fi + +AC_CHECK_PROG(found_ranlib, ranlib, yes, no) +if test x$found_ranlib != xyes +then + AC_MSG_NOTICE([Sorry, the 'ranlib' command must be in the path to build AC_PACKAGE_NAME]) + AC_MSG_NOTICE([On some systems it can be found in /usr/ccs/bin ]) + AC_MSG_NOTICE(['ranlib' is also part of the GNU 'binutils' package.]) + AC_MSG_ERROR([Exiting, as 'ranlib' can not be found.]) +fi -if [ -z "$CXX" ]; then - CXX=g++ +AC_CHECK_PROG(found_strip, strip, yes, no) +if test x$found_strip != xyes +then + AC_MSG_NOTICE([Sorry, the 'strip' command must be in the path to build AC_PACKAGE_NAME]) + AC_MSG_NOTICE([On some systems 'strip' can be found in /usr/ccs/bin ]) + AC_MSG_NOTICE(['strip' is also part of the GNU 'binutils' package.]) + AC_MSG_ERROR([Exiting, as 'strip' can not be found.]) fi -if [ -z "$CC" ]; then - if command -v gcc >/dev/null 2>/dev/null; then - CC=gcc +# Check tar +AC_CACHE_CHECK([for GNU or BSD tar], [ac_cv_path_TAR], [ +AC_PATH_PROGS_FEATURE_CHECK(TAR, [tar gtar], [[ +ac_version_TAR=`$ac_path_TAR --version 2>&1` +if echo "$ac_version_TAR" | grep >/dev/null GNU; then + ac_cv_path_TAR=$ac_path_TAR + if test $ac_prog = tar; then + ac_path_TAR_found=: + fi +fi +if echo "$ac_version_TAR" | grep >/dev/null bsdtar; then + ac_cv_path_TAR=$ac_path_TAR + if test $ac_prog = tar; then + ac_path_TAR_found=: fi fi +]], +[AC_MSG_ERROR([could not find either a GNU or BSD version of tar])], +[$PATH:/usr/sfw/bin]) +]) + +command_TAR=`command -v tar 2>/dev/null` +AS_IF([test x$command_TAR != x$ac_cv_path_TAR], + [AC_MSG_ERROR([[found a good version of tar in $ac_cv_path_TAR, but it's not the first "tar" program in your PATH]])] +) -if [ -z "$FC" ]; then - if command -v gfortran >/dev/null 2>/dev/null; then - FC=gfortran - elif command -v g95 >/dev/null 2>/dev/null; then - FC=g95 - elif command -v g77 >/dev/null 2>/dev/null; then - FC=g77 +# Check make (unless MAKE is set) +if test -z "$MAKE"; then + AC_CACHE_CHECK([for GNU make], [ac_cv_path_MAKE], [ + AC_PATH_PROGS_FEATURE_CHECK(MAKE, [make gmake], [[ + ac_version_MAKE=`$ac_path_MAKE --version 2>&1` + if echo "$ac_version_MAKE" | grep >/dev/null GNU; then + ac_cv_path_MAKE=$ac_path_MAKE + if test $ac_prog = make; then + ac_path_MAKE_found=: + fi fi + ]], + [AC_MSG_ERROR([could not find a GNU version of make])], + [$PATH:/usr/sfw/bin]) + ]) + + command_MAKE=`command -v make 2>/dev/null` + AS_IF([test x$command_MAKE != x$ac_cv_path_MAKE], + [AC_MSG_ERROR([[found GNU make in $ac_cv_path_MAKE, but it's not the first "make" program in your PATH]])]) +fi + +# Check for Latex, the use of which is less important in Sage than +# it used to be, as it was at one time required to build any documentation +# but this is no longer so. +AC_CHECK_PROG(found_latex, latex, yes, no) +if test x$found_latex != xyes +then + AC_MSG_WARN([You do not have 'latex', which is recommended, but not]) + AC_MSG_WARN([required. Latex is only really used for building pdf]) + AC_MSG_WARN([documents and for %latex mode in the AC_PACKAGE_NAME notebook.]) fi -if [ -f "$SAGE_LOCAL/bin/gcc" ]; then - # Ignore SAGE_INSTALL_GCC if GCC is already installed - SAGE_INSTALL_GCC="" +# Check that perl is available, with version 5.8.0 or later. +# Some packages need perl, however it is not clear whether Sage really +# requires version >= 5.8.0. The R package *used* to require it, but +# not anymore. -- Jeroen Demeyer +AC_PATH_PROG([PERL],[perl]) +AX_PROG_PERL_VERSION([5.8.0],[],[ + AC_MSG_ERROR([Exiting, since AC_PACKAGE_NAME requires perl-5.8.0 or later]) +]) + +# To build Python on multi-arch Debian-based systems, we need +# dpkg-architecture. Since we need dpkg-architecture to determine +# whether we're on a multi-arch system and require dpkg-architecture, +# we simply require it always on Debian-based systems. +AC_CHECK_PROG(found_dpkg, dpkg, yes, no) +AC_CHECK_PROG(found_dpkg_arch, dpkg-architecture, yes, no) +if test x$found_dpkg = xyes && test x$found_dpkg_arch = xno +then + AC_MSG_NOTICE([You do not have 'dpkg-architecture', which is required to build]) + AC_MSG_NOTICE([Python on multi-arch Debian-based systems. This includes all recent]) + AC_MSG_NOTICE([Debian and Ubuntu systems. You can install this with:]) + AC_MSG_NOTICE([ sudo apt-get install dpkg-dev]) + AC_MSG_ERROR([Exiting, since AC_PACKAGE_NAME requires dpkg-architecture on Debian]) fi -if [ -n "$SAGE_INSTALL_GCC" ]; then + +############################################################################### +# Check C/C++/Fortran compilers +############################################################################### + +dnl Usage: SAGE_SHOULD_INSTALL_GCC(reason) +dnl +dnl Use this macro to indicate that we SHOULD install GCC. +dnl In this case, GCC will be installed unless SAGE_INSTALL_GCC=no. +dnl In the latter case, a warning is given. +AC_DEFUN([SAGE_SHOULD_INSTALL_GCC], [ + if test x$SAGE_INSTALL_GCC = xexists; then + true # Do nothing if already installed + elif test x$SAGE_INSTALL_GCC = xno; then + AC_MSG_WARN([$1]) + else + AC_MSG_NOTICE([Installing GCC because $1]) + need_to_install_gcc=yes + fi +]) + +dnl Usage: SAGE_MUST_INSTALL_GCC(reason) +dnl +dnl Use this macro to indicate that we MUST install GCC. +dnl In this case, it is an error if SAGE_INSTALL_GCC=no. +AC_DEFUN([SAGE_MUST_INSTALL_GCC], [ + if test x$SAGE_INSTALL_GCC = xexists; then + true # Do nothing if already installed + elif test x$SAGE_INSTALL_GCC = xno; then + AC_MSG_ERROR([SAGE_INSTALL_GCC is set to 'no', but $1]) + else + AC_MSG_NOTICE([Installing GCC because $1]) + need_to_install_gcc=yes + fi +]) + + +# By default, do not install GCC +need_to_install_gcc=no + +if test -f "$SAGE_LOCAL/bin/gcc"; then + # Special value for SAGE_INSTALL_GCC if GCC is already installed + SAGE_INSTALL_GCC=exists +elif test -n "$SAGE_INSTALL_GCC"; then # Check the value of the environment variable SAGE_INSTALL_GCC case "$SAGE_INSTALL_GCC" in yes) - echo >&2 "Installing GCC because SAGE_INSTALL_GCC is set to 'yes'." - need_to_install_gcc=yes;; + SAGE_MUST_INSTALL_GCC([SAGE_INSTALL_GCC is set to 'yes']);; no) - need_to_install_gcc=no;; + true;; *) - echo >&2 "Error: SAGE_INSTALL_GCC should be set to 'yes' or 'no'." - echo >&2 "You can also leave it unset to install GCC when needed." - exit 2;; + AC_MSG_ERROR([SAGE_INSTALL_GCC should be set to 'yes' or 'no'. You can also leave it unset to install GCC when needed]);; esac -else - # SAGE_INSTALL_GCC is not set, install GCC when needed. - need_to_install_gcc=no - - # Check whether $CXX is some version of GCC. If it's a different - # compiler, install GCC. - CXXtype=`source sage-env; testcxx.sh $CXX` - if [ "$CXXtype" != GCC ]; then - echo >&2 "Installing GCC because your '$CXX' isn't GCC (GNU C++)." - need_to_install_gcc=yes - else - # $CXX points to some version of GCC, find out which version. - GCCVERSION=`$CXX -dumpversion` - # Add the .0 because Debian/Ubuntu gives version numbers like - # 4.6 instead of 4.6.4 (Trac #18885) - case "$GCCVERSION.0" in - [0-3].*|4.[0-3].*) - # Install our own GCC if the system-provided one is older than gcc-4.4. - # * gcc-4.2.4 compiles a slow IML: - # https://groups.google.com/forum/?fromgroups#!topic/sage-devel/Ux3t0dW2FSI - # * gcc-4.3 might have trouble building ATLAS: - # https://groups.google.com/forum/?fromgroups#!topic/sage-devel/KCeFqQ_w2FE - echo >&2 "Installing GCC because you have $CXX version $GCCVERSION, which is quite old." - need_to_install_gcc=yes;; - 4.4.*|4.5.*) - # GCC 4.4.x and GCC 4.5.x fail to compile PARI/GP on ia64: - # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46044 - if [ x`uname -m` = xia64 ]; then - echo >&2 "Installing GCC because you have $CXX version $GCCVERSION on ia64." - echo >&2 "gcc <= 4.5 fails to compile PARI/GP on ia64." - need_to_install_gcc=yes - fi;; - 4.6.*) - # Also install GCC if we have version 4.6.* which is - # known to give trouble within Sage: - # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48702 - # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48774 - # * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52061 - # * https://groups.google.com/d/msg/sage-release/xgmJ3nAcUOY/jH8OZjftYRsJ - echo >&2 "Installing GCC because you have $CXX version $GCCVERSION." - echo >&2 "gcc-4.6.* has known bugs affecting Sage." - need_to_install_gcc=yes;; - 4.7.0) - # GCC 4.7.0 is very broken on ia64, see - # http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48496 - # This is fixed in GCC 4.7.1. - if [ x`uname -m` = xia64 ]; then - echo >&2 "Installing GCC because you have $CXX version $GCCVERSION on ia64." - echo >&2 "gcc-4.7.0 has a serious bug on ia64." - need_to_install_gcc=yes - fi;; - esac - fi +fi - # Check C, C++ and Fortran compilers. - if [ -z "$CC" ]; then - echo >&2 "Installing GCC because a C compiler is missing." - need_to_install_gcc=yes - fi - if [ -z "$FC" ]; then - echo >&2 "Installing GCC because a Fortran compiler is missing." - need_to_install_gcc=yes - fi -fi +AC_LANG(C) +AC_PROG_CC() +AC_PROG_CPP() -# If we are not installing GCC: check that the assembler and linker -# used by $CXX match $AS and $LD. -# See http://trac.sagemath.org/sage_trac/ticket/14296 -if [ $need_to_install_gcc != yes ]; then - if [ "$AS" != "" ]; then - CXX_as=`$CXX -print-file-name=as 2>/dev/null` - CXX_as=`command -v $CXX_as 2>/dev/null` - cmd_AS=`command -v $AS` - - if [ "$CXX_as" != "" -a "$CXX_as" != "$cmd_AS" ]; then - echo >&2 "Error: Mismatch of assemblers between" - echo >&2 " * $CXX using $CXX_as" - echo >&2 " * \$AS equal to $AS" - if [ "$SAGE_PORT" = "" ]; then - echo >&2 "Aborting, either change or unset AS or set SAGE_PORT=yes or set" - echo >&2 "SAGE_INSTALL_GCC=yes (this GCC would use assembler $AS)" - exit 1 - else - echo >&2 "Continuing since SAGE_PORT is set." - fi - fi - fi - if [ "$LD" != "" ]; then - CXX_ld=`$CXX -print-file-name=ld 2>/dev/null` - CXX_ld=`command -v $CXX_ld 2>/dev/null` - cmd_LD=`command -v $LD` - - if [ "$CXX_ld" != "" -a "$CXX_ld" != "$cmd_LD" ]; then - echo >&2 "Error: Mismatch of linkers between" - echo >&2 " * $CXX using $CXX_ld" - echo >&2 " * \$LD equal to $LD" - if [ "$SAGE_PORT" = "" ]; then - echo >&2 "Aborting, either change or unset LD or set SAGE_PORT=yes or set" - echo >&2 "SAGE_INSTALL_GCC=yes (this GCC would use linker $LD)" - exit 1 - else - echo >&2 "Continuing since SAGE_PORT is set." - fi - fi - fi +AC_LANG(C++) +AC_PROG_CXX() + +AC_LANG(Fortran) +AC_PROG_FC() + +if test "x$CXX" = x +then + AC_MSG_ERROR([a C++ compiler is missing]) fi ############################################################################### -# Create $SAGE_ROOT/build/make/Makefile starting from build/make/deps +# Check header files ############################################################################### -# Use file descriptor 7 since make uses 3 and 4 and configure uses 5 and 6 -exec 7>build/make/Makefile +# complex.h is one that might not exist on older systems. +AC_LANG(C++) +AC_CHECK_HEADER([complex.h],[],[ + AC_MSG_ERROR([Exiting, since you do not have the 'complex.h' header file.]) +]) -cat >&7 </dev/null` + CXX_as=`command -v $CXX_as 2>/dev/null` + cmd_AS=`command -v $AS` + + if test "$CXX_as" != "" -a "$CXX_as" != "$cmd_AS"; then + SAGE_SHOULD_INSTALL_GCC([there is a mismatch of assemblers]) + AC_MSG_NOTICE([ $CXX uses $CXX_as]) + AC_MSG_NOTICE([ \$AS equal to $AS]) + fi +fi +if test -n "$LD"; then + CXX_ld=`$CXX -print-file-name=ld 2>/dev/null` + CXX_ld=`command -v $CXX_ld 2>/dev/null` + cmd_LD=`command -v $LD` + + if test "$CXX_ld" != "" -a "$CXX_ld" != "$cmd_LD"; then + SAGE_SHOULD_INSTALL_GCC([there is a mismatch of linkers]) + AC_MSG_NOTICE([ $CXX uses $CXX_ld]) + AC_MSG_NOTICE([ \$LD equal to $LD]) + fi +fi + + +############################################################################### +# Check libraries +############################################################################### + +# First check for something that should be in any maths library (sqrt). +AC_LANG(C++) +AC_CHECK_LIB(m,sqrt,[],[ + AC_MSG_NOTICE([This system has no maths library installed.]) + # On AIX this is not installed by default - strange as that might seem. + # but is in a fileset bos.adt.libm. However, the fileset bos.adt + # includes other things that are probably useful. + if test "x`uname`" = 'xAIX' + then + AC_MSG_NOTICE([On AIX, libm is contained in the bos.adt.libm fileset.]) + AC_MSG_NOTICE([Actually, we recommend to install the complete bos.adt fileset.]) + AC_MSG_NOTICE([This needs to be performed by a system administrator.]) + fi + AC_MSG_ERROR([Exiting, since a maths library was not found.]) + ]) + +# Check for system services + +# Check that we are not building in a directory containing spaces +AS_IF([echo "$ac_pwd" |grep " " >/dev/null], + AC_MSG_ERROR([the path to the Sage root directory ($ac_pwd) contains a space. Sage will not build correctly in this case]) +) + + +if test x`uname` = xDarwin; then +[ + # Warning: xcodebuild does not seem to be maintained in Xcode 4.3 + # or later, so do not rely on the variable XCODE_VERS with OS X + # 10.7 or later. + XCODE_VERS=`xcodebuild -version 2> /dev/null | grep Xcode | sed -e 's/[A-Za-z ]//g'` + if [ -z $XCODE_VERS ]; then + XCODE_VERS="2" + fi + XCODE_VERS_MAJOR=`echo $XCODE_VERS | cut '-d.' -f1` + DARWIN_VERSION=`uname -r | cut '-d.' -f1` + echo "***************************************************" + echo "***************************************************" + if [ $DARWIN_VERSION -gt 10 ]; then + echo "You are using OS X Lion (or later)." + echo "You are strongly advised to install Apple's latest Xcode" + echo "unless you already have it. You can install this using" + echo "the App Store. Also, make sure you install Xcode's" + echo "Command Line Tools -- see Sage's README.txt." + elif [ $XCODE_VERS_MAJOR -gt 2 ]; then + echo "You are using Xcode version $XCODE_VERS." + echo "You are strongly advised to install Apple's latest Xcode" + echo "unless you already have it. You can download this from" + echo "http://developer.apple.com/downloads/." + echo "If using Xcode 4.3 or later, make sure you install Xcode's" + echo "Command Line Tools -- see Sage's README.txt." + else + echo "You are using Xcode version 1 or 2" + echo "WARNING: You are strongly advised to install the" + echo "latest version of Apple's Xcode for your platform," + echo "unless you already have it." + if [ $DARWIN_VERSION -eq 10 ]; then + echo "Probably you need Xcode 3.2.6" + elif [ $DARWIN_VERSION -eq 9 ]; then + echo "Probably you need Xcode 3.1.4" + elif [ $DARWIN_VERSION -lt 9 ]; then + echo "Probably you need Xcode 2.5" + fi + fi +] + +########################################################################### +# (OS X only) +# Sage will probably not build at all if either Fink or MacPorts can be +# found, and the error messages can be extremely confusing. Even if it does +# build, the product will probably be wrong. This runs a basic check to +# find them. Once the Sage build process is perfected, this won't be necessary. +# dphilp 15/9/2008 +########################################################################### + PORTS_PATH=`which port` + if test -f "$PORTS_PATH"; then +AC_MSG_ERROR([["found MacPorts in $PORTS_PATH. Either: +(1) rename /opt/local and /sw, or +(2) change PATH and DYLD_LIBRARY_PATH +(Once Sage is built, you can restore them.)]]) + fi + + FINK_PATH=`which fink` + if test -f "$FINK_PATH"; then +AC_MSG_ERROR([["found Fink in $FINK_PATH. Either: +(1) rename /opt/local and /sw, or +(2) change PATH and DYLD_LIBRARY_PATH +(Once Sage is built, you can restore them.)]]) + fi +fi + + +############################################################################### +# Create $SAGE_ROOT/build/make/Makefile starting from build/make/deps +############################################################################### + +# Use file descriptor 7 since make uses 3 and 4 and configure uses 5 and 6 +exec 7>build/make/Makefile + +cat >&7 <&7 "SHELL = `command -v bash`" echo >&7 -] AC_MSG_CHECKING([package versions]) AC_MSG_RESULT([]) @@ -511,573 +904,6 @@ done exec 7>&- ] -#--------------------------------------------------------- -AX_CHECK_ROOT([AC_MSG_ERROR([You cannot build Sage as root, switch to an unpriviledged user])], []) - -# Check whether we are on a supported platform -AC_CANONICAL_BUILD() -AC_CANONICAL_HOST() - -case $host in -*-*-sunos*|*-*-solaris2.[1-9]) -AC_MSG_ERROR([[ -Sage is not supported on any version of Solaris earlier than 10. -Sage has been tested on the first release of Solaris 10 -(03/2005) and works on that. Sage may or may not work with -your version of Solaris. - -More information can be found about Sage on Solaris -on the Wiki at http://wiki.sagemath.org/solaris]]);; - -*-*-darwin[1-7].*) -AC_MSG_ERROR([[ -Sage has never been built on OS X 10.3 (Panther) -or earlier. The oldest version of OS X successfully used -is OS X version 10.4 (Tiger). You might consider updating -your version of OS X if your hardware permits this, but -Apple charges for upgrades of OS X]]);; - -*-*-hpux*) -AC_MSG_ERROR([[ -You are attempting to build Sage on HP's HP-UX operating system, -which is not a supported platform for Sage yet though -some work has been done on HP-UX. A port does not look to -be particularly difficult. Some information can be -found on the Sage Wiki at http://wiki.sagemath.org/HP-UX - -If you would like to help port Sage to HP-UX, -please join the sage-devel discussion list - see -http://groups.google.com/group/sage-devel -The Sage community would also appreciate any patches you submit]]);; - -*-*-aix*) -AC_MSG_ERROR([[ -You are attempting to build Sage on IBM's AIX operating system, -which is not a supported platform for Sage yet. Things may or -may not work. If you would like to help port Sage to AIX, -please join the sage-devel discussion list - see -http://groups.google.com/group/sage-devel -The Sage community would also appreciate any patches you submit]]);; - -*-*-irix*) -AC_MSG_ERROR([[ -You are attempting to build Sage on SGI's IRIX operating system, -which is not a supported platform for Sage yet. Things may or -may not work. If you would like to help port Sage to IRIX, -please join the sage-devel discussion list - see -http://groups.google.com/group/sage-devel -The Sage community would also appreciate any patches you submit]]);; - -*-*-osf*) -AC_MSG_ERROR([[ -You are attempting to build Sage on HP's Tru64 operating system, -which is not a supported platform for Sage yet. Things may or -may not work. If you would like to help port Sage to Tru64, -please join the sage-devel discussion list - see -http://groups.google.com/group/sage-devel -The Sage community would also appreciate any patches you submit]]);; - -*-*-freebsd*) -AC_MSG_ERROR([[ -You are attempting to build Sage on the FreeBSD operating system, -which is not a supported platform for Sage yet, though -developers are working on adding FreeBSD support. Things may or -may not work. If you would like to help port Sage to FreeBSD, -please join the sage-devel discussion list - see -http://groups.google.com/group/sage-devel -The Sage community would also appreciate any patches you submit]]);; - -# The following are all supported platforms. -*-*-linux*);; -*-*-darwin*);; -*-*-solaris*);; -*-*-cygwin*);; - -# Wildcard for other unsupported platforms -*) -AC_MSG_ERROR([[ -You are attempting to build Sage on $host, -which is not a supported platform for Sage yet. Things may or -may not work. If you would like to help port Sage to $host, -please join the sage-devel discussion list - see -http://groups.google.com/group/sage-devel -The Sage community would also appreciate any patches you submit]]);; -esac - - -dnl Check compiler versions -buggy_gcc_version1="4.0.0" -minimum_gcc_version_for_no_hassle="4.0.1" -minimum_gcc_version_for_debugging_purposes="3.4.0" - -#--------------------------------------------------------- -# Process options and environment variables - -# Check --enable-compiler-checks (default depends on whether we are -# installing GCC) -AC_ARG_ENABLE([compiler-checks], [Check versions and presence of C, C++ and Fortran compilers (default: yes unless installing GCC)], - [enable_compiler_checks=$enableval], - [# Default - if test "$need_to_install_gcc" = yes; then - enable_compiler_checks=no; - else - enable_compiler_checks=yes; - fi - ] - ) - -# Import environment variables. -source src/bin/sage-env || AC_MSG_ERROR([failed to source sage-env]) - -#--------------------------------------------------------- -# Check some programs needed actually exist. -AC_CHECK_PROG(found_ar, ar, yes, no) -if test x$found_ar != xyes -then - AC_MSG_NOTICE([Sorry, the 'ar' command must be in the path to build AC_PACKAGE_NAME]) - AC_MSG_NOTICE([On some systems it can be found in /usr/ccs/bin ]) - AC_MSG_NOTICE(['ar' is also part of the GNU 'binutils' package.]) - AC_MSG_ERROR([Exiting, as the archiver 'ar' can not be found.]) -fi - -AC_CHECK_PROG(found_m4, m4, yes, no) -if test x$found_m4 != xyes -then - AC_MSG_NOTICE([Sorry, the 'm4' command must be in the path to build AC_PACKAGE_NAME]) - AC_MSG_NOTICE([On some systems it can be found in /usr/ccs/bin]) - AC_MSG_NOTICE([See also http://www.gnu.org/software/m4/]) - AC_MSG_ERROR([Exiting, as the macro processor 'm4' can not be found.]) -fi - -AC_CHECK_PROG(found_ranlib, ranlib, yes, no) -if test x$found_ranlib != xyes -then - AC_MSG_NOTICE([Sorry, the 'ranlib' command must be in the path to build AC_PACKAGE_NAME]) - AC_MSG_NOTICE([On some systems it can be found in /usr/ccs/bin ]) - AC_MSG_NOTICE(['ranlib' is also part of the GNU 'binutils' package.]) - AC_MSG_ERROR([Exiting, as 'ranlib' can not be found.]) -fi - -AC_CHECK_PROG(found_strip, strip, yes, no) -if test x$found_strip != xyes -then - AC_MSG_NOTICE([Sorry, the 'strip' command must be in the path to build AC_PACKAGE_NAME]) - AC_MSG_NOTICE([On some systems 'strip' can be found in /usr/ccs/bin ]) - AC_MSG_NOTICE(['strip' is also part of the GNU 'binutils' package.]) - AC_MSG_ERROR([Exiting, as 'strip' can not be found.]) -fi - -# Check tar -AC_CACHE_CHECK([for GNU or BSD tar], [ac_cv_path_TAR], [ -AC_PATH_PROGS_FEATURE_CHECK(TAR, [tar gtar], [[ -ac_version_TAR=`$ac_path_TAR --version 2>&1` -if echo "$ac_version_TAR" | grep >/dev/null GNU; then - ac_cv_path_TAR=$ac_path_TAR - if test $ac_prog = tar; then - ac_path_TAR_found=: - fi -fi -if echo "$ac_version_TAR" | grep >/dev/null bsdtar; then - ac_cv_path_TAR=$ac_path_TAR - if test $ac_prog = tar; then - ac_path_TAR_found=: - fi -fi -]], -[AC_MSG_ERROR([could not find either a GNU or BSD version of tar])], -[$PATH:/usr/sfw/bin]) -]) - -command_TAR=`command -v tar 2>/dev/null` -AS_IF([test x$command_TAR != x$ac_cv_path_TAR], - [AC_MSG_ERROR([[found a good version of tar in $ac_cv_path_TAR, but it's not the first "tar" program in your PATH]])] -) - -# Check make (unless MAKE is set) -if test -z "$MAKE"; then - AC_CACHE_CHECK([for GNU make], [ac_cv_path_MAKE], [ - AC_PATH_PROGS_FEATURE_CHECK(MAKE, [make gmake], [[ - ac_version_MAKE=`$ac_path_MAKE --version 2>&1` - if echo "$ac_version_MAKE" | grep >/dev/null GNU; then - ac_cv_path_MAKE=$ac_path_MAKE - if test $ac_prog = make; then - ac_path_MAKE_found=: - fi - fi - ]], - [AC_MSG_ERROR([could not find a GNU version of make])], - [$PATH:/usr/sfw/bin]) - ]) - - command_MAKE=`command -v make 2>/dev/null` - AS_IF([test x$command_MAKE != x$ac_cv_path_MAKE], - [AC_MSG_ERROR([[found GNU make in $ac_cv_path_MAKE, but it's not the first "make" program in your PATH]])]) -fi - -# Check for Latex, the use of which is less important in Sage than -# it used to be, as it was at one time required to build any documentation -# but this is no longer so. -AC_CHECK_PROG(found_latex, latex, yes, no) -if test x$found_latex != xyes -then - AC_MSG_WARN([You do not have 'latex', which is recommended, but not]) - AC_MSG_WARN([required. Latex is only really used for building pdf]) - AC_MSG_WARN([documents and for %latex mode in the AC_PACKAGE_NAME notebook.]) -fi - -# Check that perl is available, with version 5.8.0 or later. -# Some packages need perl, however it is not clear whether Sage really -# requires version >= 5.8.0. The R package *used* to require it, but -# not anymore. -- Jeroen Demeyer -AC_PATH_PROG([PERL],[perl]) -AX_PROG_PERL_VERSION([5.8.0],[],[ - AC_MSG_ERROR([Exiting, since AC_PACKAGE_NAME requires perl-5.8.0 or later]) -]) - -# To build Python on multi-arch Debian-based systems, we need -# dpkg-architecture. Since we need dpkg-architecture to determine -# whether we're on a multi-arch system and require dpkg-architecture, -# we simply require it always on Debian-based systems. -AC_CHECK_PROG(found_dpkg, dpkg, yes, no) -AC_CHECK_PROG(found_dpkg_arch, dpkg-architecture, yes, no) -if test x$found_dpkg = xyes && test x$found_dpkg_arch = xno -then - AC_MSG_NOTICE([You do not have 'dpkg-architecture', which is required to build]) - AC_MSG_NOTICE([Python on multi-arch Debian-based systems. This includes all recent]) - AC_MSG_NOTICE([Debian and Ubuntu systems. You can install this with:]) - AC_MSG_NOTICE([ sudo apt-get install dpkg-dev]) - AC_MSG_ERROR([Exiting, since AC_PACKAGE_NAME requires dpkg-architecture on Debian]) -fi - -#--------------------------------------------------------- -# C/C++/Fortran compilers - -# First check for programs we need. -AC_LANG(C) -AC_PROG_CC() -AC_PROG_CPP() - -AC_LANG(C++) -AC_PROG_CXX() - -AC_LANG(Fortran) -AC_PROG_FC() - -if test "x$CXX" = x -then - AC_MSG_ERROR([Exiting, since a C++ compiler was not found.]) -fi - -if test $enable_compiler_checks = yes -then - if test "x$CC" = x - then - AC_MSG_ERROR([Exiting, since a C compiler was not found.]) - fi - if test "x$FC" = x - then - AC_MSG_ERROR([Exiting, since a Fortran compiler was not found.]) - fi -fi - -# Next one should check for header files. -# complex.h is one that might not exist on older systems. -AC_LANG(C++) -AC_CHECK_HEADER([complex.h],[],[ - AC_MSG_ERROR([Exiting, since you do not have the 'complex.h' header file.]) -]) - -# Next one should check for types. -# None needed - -# Next one should check for structures. -# None needed - -# Next one should check for compiler characterists. - -# Check that we can compile C99 code -AC_LANG(C) -AC_PROG_CC_C99() -if test $enable_compiler_checks = yes -then - if test "x$ac_cv_prog_cc_c99" = xno - then - AC_MSG_ERROR([Exiting, as your C compiler cannot compile C99 code]) - fi -fi - -# Check that the Fortran compiler accepts free-format source code -# (as opposed to the older fixed-format style from Fortran 77). -# This helps verify the compiler works too, so if some idiot -# sets FC to /usr/bin/ls, we will at least know it's -# not a working Fortran compiler. -AC_LANG(Fortran) -if test $enable_compiler_checks = yes -then - # see http://www.gnu.org/software/hello/manual/autoconf/Fortran-Compiler.html - AC_FC_FREEFORM([], - [ - AC_MSG_NOTICE([Your Fortran compiler does not accept free-format source code]) - AC_MSG_NOTICE([which means the compiler is either seriously broken, or]) - AC_MSG_NOTICE([is too old to build Sage.]) - AC_MSG_ERROR([Exiting, as the Fortran compiler is not suitable]) - ]) -fi - -if test $enable_compiler_checks = yes -then - # Check that all compilers (C, C++, Fortan) are either all GNU - # compiler or all non-GNU compilers. If not, there is a problem, as - # mixing GNU and non-GNU compilers is likely to cause problems. - if test x$GCC = xyes && test x$GXX != xyes - then - AC_MSG_NOTICE([You are trying to use gcc but not g++]) - AC_MSG_ERROR([The mixing of GNU and non-GNU compilers is not permitted]) - fi - if test x$GXX = xyes && test x$ac_cv_fc_compiler_gnu != xyes - then - AC_MSG_NOTICE([You are trying to use g++ but not gfortran]) - AC_MSG_ERROR([The mixing of GNU and non-GNU compilers is not permitted]) - fi - if test x$ac_cv_fc_compiler_gnu = xyes && test x$GCC != xyes - then - AC_MSG_NOTICE([You are trying to use gfortran but not gcc]) - AC_MSG_ERROR([The mixing of GNU and non-GNU compilers is not permitted]) - fi -fi - -# The following tests check the version of the compilers (if GNU) -# are all the same. If non-GNU compilers are used, then no such -# checks are performed. -if test $enable_compiler_checks = yes -then -if test x$GCC = xyes -then - # Thank you to Andrew W. Nosenko andrew.w.nosenko@gmail.com - # who answered my query about testing of gcc versions on - # the autoconf@gnu.org mailing list. - # AS_VERSION_COMPARE(ver-1, ver-2, [action-if-less], [action-if-eq], [action-if-greater]) - AX_GCC_VERSION - AX_GXX_VERSION - AS_VERSION_COMPARE([$GCC_VERSION], [$GXX_VERSION], - [ - AC_MSG_NOTICE([gcc ($GCC_VERSION) and g++ ($GXX_VERSION) are not the same version]) - AC_MSG_NOTICE([which they must be. Check your setting of CC and CXX]) - AC_MSG_ERROR([Exiting, since the C and C++ compilers have different versions]) - ],[],[ - AC_MSG_NOTICE([gcc ($GCC_VERSION) and g++ ($GXX_VERSION) are not the same version]) - AC_MSG_NOTICE([which they must be. Check your setting of CC and CXX]) - AC_MSG_ERROR([Exiting, since the C and C++ compilers have different versions])]) - - # In the paragraph below, 'gfortran' is used to indicate the GNU Fortran - # compiler, though it might be called something else. - - # It's not easily possible to determine the Fortran version, as - # gfortran -dumpversion did not until GCC 4.5 return just the - # the version number, but the same as gfortran --version - # for example: - - # drkirkby@hawk:~$ gcc -dumpversion - # 4.3.4 - - # drkirkby@hawk:~$ g++ -dumpversion - # 4.3.4 - - # drkirkby@hawk:~$ gfortran -dumpversion - # GNU Fortran (GCC) 4.3.4 - # Copyright (C) 2008 Free Software Foundation, Inc. - # GNU Fortran comes with NO WARRANTY, to the extent permitted by law. - # You may redistribute copies of GNU Fortran - # under the terms of the GNU General Public License. - # For more information about these matters, see the file named COPYING - - # This behaviour is fixed in the gcc 4.5 branch. Since we need to - # support older versions of the compiler, we can't change this. - - # But I would expect that the version will be on the output - # of the compiler followed by -dumpversion (e.g. fortran -dumpversion) - - # So we grep for the known gcc version on the output of gfortran -dumpversion. - # and hope we find the same string. If so, they are almost certainly - # the same version. - fortran_version_string="`$FC -dumpversion | grep $GCC_VERSION 2>&1`" - - if test "x$fortran_version_string" = x - then - AC_MSG_NOTICE([Although gcc and g++ are both version $GCC_VERSION]) - AC_MSG_NOTICE([the Fortran compiler $FC is some other version.]) - AC_MSG_NOTICE([The output from $FC --version is below.]) - echo "" - $FC --version 2>&1 - echo "" - AC_MSG_ERROR([Exiting, since the Fortran compiler is not the same version as the C and C++ compilers]) - else - AC_MSG_NOTICE([Excellent, the C, C++ and Fortran compilers are all GCC $GCC_VERSION]) - fi - - # Exit if the version of GCC is known to be too old, and so old - # we have no intension whatsoever of trying to make Sage work with it. - AS_VERSION_COMPARE([$GCC_VERSION], [$minimum_gcc_version_for_debugging_purposes],[ - AC_MSG_NOTICE([GCC $GCC_VERSION is too old and can not build AC_PACKAGE_NAME. ]) - AC_MSG_NOTICE([Please use a GCC of at least $minimum_gcc_version_for_no_hassle ]) - AC_MSG_NOTICE([There are no plans whatsoever to support GCC $GCC_VERSION]) - AC_MSG_ERROR([Exiting, due to the use of a version of GCC that is too old]) - ], [],[]) - - # Exit if Sage is *precisely* version $buggy_gcc_version1, as that is very buggy. - # If any later versions of GCC are found to be buggy (which would never surprise me) - # We can easily add a buggy_gcc_version2 and repeat the next 5 lines. - # At the time of writing (28th September 2009) that is version 4.0.0, - # but rather than hard-code that, it is set as a variable. - AS_VERSION_COMPARE([$GCC_VERSION], [$buggy_gcc_version1],[],[ - AC_MSG_NOTICE([GCC $buggy_gcc_version1 is very buggy and can not build AC_PACKAGE_NAME.]) - AC_MSG_NOTICE([Please use a gcc of at least $minimum_gcc_version_for_no_hassle.]) - AC_MSG_ERROR([Exiting, due to the use of a version of GCC that is too buggy]) - ],[]) - - # Issue a warning if gcc is too old to work with all packages, but - # someone wants to try getting one or more packages work with - # an earlier gcc. At the time of writing, (28th Sept 2009), ratpoints - # has such an issue, requiring version 4.0.1, but we would like to - # get it to work with version 3.4.x - AS_VERSION_COMPARE([$GCC_VERSION], [$minimum_gcc_version_for_no_hassle],[ - AC_MSG_NOTICE([******************************************************]) - AC_MSG_NOTICE([******************************************************]) - AC_MSG_NOTICE([******************************************************]) - AC_MSG_NOTICE([GCC $GCC_VERSION is too old and can not build AC_PACKAGE_NAME.]) - AC_MSG_NOTICE([Please use a gcc of at least $minimum_gcc_version_for_no_hassle]) - AC_MSG_NOTICE([if you just want AC_PACKAGE_NAME to build without problems.]) - AC_MSG_NOTICE([]) - if test "${SAGE_USE_OLD_GCC+set}" = set; then - AC_MSG_NOTICE([Since the variable SAGE_USE_OLD_GCC was set, the]) - AC_MSG_NOTICE([build will continue, but it will fail without changes]) - AC_MSG_NOTICE([to the Sage source code. You can be 100% sure of that.]) - else - AC_MSG_NOTICE([If you want to try building Sage with a GCC 3.4.x ]) - AC_MSG_NOTICE([with a view to debugging the problems which stop it ]) - AC_MSG_NOTICE([working on a gcc 3.4 series compiler, set the ]) - AC_MSG_NOTICE([environment variable SAGE_USE_OLD_GCC to something non]) - AC_MSG_NOTICE([empty. But if you just want Sage to work, upgrade GCC ]) - AC_MSG_NOTICE([******************************************************]) - AC_MSG_NOTICE([******************************************************]) - AC_MSG_ERROR([Exiting, due to the use of a version of GCC that is too old]) - fi - ], -[ -AC_MSG_NOTICE([Good, gcc and g++ are are just new enough as GCC $minimum_gcc_version_for_no_hassle]) -AC_MSG_NOTICE([is the minimum version needed needed to build AC_PACKAGE_NAME]) -] -, -[ -AC_MSG_NOTICE([Excellent, GCC $GCC_VERSION is later than the minimum]) -AC_MSG_NOTICE([needed to build AC_PACKAGE_NAME, which is GCC version $minimum_gcc_version_for_no_hassle]) -]) - - # AS_VERSION_COMPARE(ver-1, ver-2, [action-if-less], [action-if-eq], [action-if-greater]) -else - AC_MSG_WARN([You have a non-GNU compiler, but AC_PACKAGE_NAME has never been built]) - AC_MSG_WARN([successfully with any compiler other than GCC, despite]) - AC_MSG_WARN([some attempts made on Solaris to use Sun's compiler, which]) - AC_MSG_WARN([produces faster code than GCC. However, the AC_PACKAGE_NAME developers]) - AC_MSG_WARN([want AC_PACKAGE_NAME to work with other compilers, so please try.]) - AC_MSG_WARN([The AC_PACKAGE_NAME developers would welcome any feedback you can give.]) - AC_MSG_WARN([Please visit http://groups.google.com/group/sage-devel]) - AC_MSG_WARN([If you just want to use AC_PACKAGE_NAME, we suggest the use of]) - AC_MSG_WARN([GCC of at least version $minimum_gcc_version_for_no_hassle]) -fi -fi # test $enable_compiler_checks = yes - -# Testing for library functions -# First check for something that should be in any maths library (sqrt). -AC_LANG(C++) -AC_CHECK_LIB(m,sqrt,[],[ - AC_MSG_NOTICE([This system has no maths library installed.]) - # On AIX this is not installed by default - strange as that might seem. - # but is in a fileset bos.adt.libm. However, the fileset bos.adt - # includes other things that are probably useful. - if test "x`uname`" = 'xAIX' - then - AC_MSG_NOTICE([On AIX, libm is contained in the bos.adt.libm fileset.]) - AC_MSG_NOTICE([Actually, we recommend to install the complete bos.adt fileset.]) - AC_MSG_NOTICE([This needs to be performed by a system administrator.]) - fi - AC_MSG_ERROR([Exiting, since a maths library was not found.]) - ]) - -# Check for system services - -# Check that we are not building in a directory containing spaces -AS_IF([echo "$ac_pwd" |grep " " >/dev/null], - AC_MSG_ERROR([the path to the Sage root directory ($ac_pwd) contains a space. Sage will not build correctly in this case]) -) - - -if test x`uname` = xDarwin; then -[ - # Warning: xcodebuild does not seem to be maintained in Xcode 4.3 - # or later, so do not rely on the variable XCODE_VERS with OS X - # 10.7 or later. - XCODE_VERS=`xcodebuild -version 2> /dev/null | grep Xcode | sed -e 's/[A-Za-z ]//g'` - if [ -z $XCODE_VERS ]; then - XCODE_VERS="2" - fi - XCODE_VERS_MAJOR=`echo $XCODE_VERS | cut '-d.' -f1` - DARWIN_VERSION=`uname -r | cut '-d.' -f1` - echo "***************************************************" - echo "***************************************************" - if [ $DARWIN_VERSION -gt 10 ]; then - echo "You are using OS X Lion (or later)." - echo "You are strongly advised to install Apple's latest Xcode" - echo "unless you already have it. You can install this using" - echo "the App Store. Also, make sure you install Xcode's" - echo "Command Line Tools -- see Sage's README.txt." - elif [ $XCODE_VERS_MAJOR -gt 2 ]; then - echo "You are using Xcode version $XCODE_VERS." - echo "You are strongly advised to install Apple's latest Xcode" - echo "unless you already have it. You can download this from" - echo "http://developer.apple.com/downloads/." - echo "If using Xcode 4.3 or later, make sure you install Xcode's" - echo "Command Line Tools -- see Sage's README.txt." - else - echo "You are using Xcode version 1 or 2" - echo "WARNING: You are strongly advised to install the" - echo "latest version of Apple's Xcode for your platform," - echo "unless you already have it." - if [ $DARWIN_VERSION -eq 10 ]; then - echo "Probably you need Xcode 3.2.6" - elif [ $DARWIN_VERSION -eq 9 ]; then - echo "Probably you need Xcode 3.1.4" - elif [ $DARWIN_VERSION -lt 9 ]; then - echo "Probably you need Xcode 2.5" - fi - fi -] - -########################################################################### -# (OS X only) -# Sage will probably not build at all if either Fink or MacPorts can be -# found, and the error messages can be extremely confusing. Even if it does -# build, the product will probably be wrong. This runs a basic check to -# find them. Once the Sage build process is perfected, this won't be necessary. -# dphilp 15/9/2008 -########################################################################### - PORTS_PATH=`which port` - if test -f "$PORTS_PATH"; then -AC_MSG_ERROR([["found MacPorts in $PORTS_PATH. Either: -(1) rename /opt/local and /sw, or -(2) change PATH and DYLD_LIBRARY_PATH -(Once Sage is built, you can restore them.)]]) - fi - - FINK_PATH=`which fink` - if test -f "$FINK_PATH"; then -AC_MSG_ERROR([["found Fink in $FINK_PATH. Either: -(1) rename /opt/local and /sw, or -(2) change PATH and DYLD_LIBRARY_PATH -(Once Sage is built, you can restore them.)]]) - fi -fi - dnl AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([build/make/Makefile-auto]) diff --git a/m4/ax_cxx_compile_stdcxx_11.m4 b/m4/ax_cxx_compile_stdcxx_11.m4 new file mode 100644 index 00000000000..516da37e822 --- /dev/null +++ b/m4/ax_cxx_compile_stdcxx_11.m4 @@ -0,0 +1,172 @@ +# ============================================================================ +# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html +# ============================================================================ +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the C++11 +# standard; if necessary, add switches to CXXFLAGS to enable support. +# +# The first argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for an extended mode. +# +# The second argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline C++11 support is required and that the macro +# should error out if no mode with that support is found. If specified +# 'optional', then configuration proceeds regardless, after defining +# HAVE_CXX11 if and only if a supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# Copyright (c) 2015 Paul Norman +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 13 + +m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [[ + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + struct Base { + virtual void f() {} + }; + struct Child : public Base { + virtual void f() override {} + }; + + typedef check> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check check_type; + check_type c; + check_type&& cr = static_cast(c); + + auto d = a; + auto l = [](){}; + // Prevent Clang error: unused variable 'l' [-Werror,-Wunused-variable] + struct use_l { use_l() { l(); } }; + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function because of this + namespace test_template_alias_sfinae { + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { + func(0); + } + } + + // Check for C++11 attribute support + void noret [[noreturn]] () { throw 0; } +]]) + +AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl + m4_if([$1], [], [], + [$1], [ext], [], + [$1], [noext], [], + [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl + m4_if([$2], [], [ax_cxx_compile_cxx11_required=true], + [$2], [mandatory], [ax_cxx_compile_cxx11_required=true], + [$2], [optional], [ax_cxx_compile_cxx11_required=false], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + AC_CACHE_CHECK(whether $CXX supports C++11 features by default, + ax_cv_cxx_compile_cxx11, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [ax_cv_cxx_compile_cxx11=yes], + [ax_cv_cxx_compile_cxx11=no])]) + if test x$ax_cv_cxx_compile_cxx11 = xyes; then + ac_success=yes + fi + + m4_if([$1], [noext], [], [dnl + if test x$ac_success = xno; then + for switch in -std=gnu++11 -std=gnu++0x; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, + $cachevar, + [ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXXFLAGS="$ac_save_CXXFLAGS"]) + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi]) + + m4_if([$1], [ext], [], [dnl + if test x$ac_success = xno; then + dnl HP's aCC needs +std=c++11 according to: + dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf + dnl Cray's crayCC needs "-h std=c++11" + for switch in -std=c++11 -std=c++0x +std=c++11 "-h std=c++11"; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, + $cachevar, + [ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXXFLAGS="$ac_save_CXXFLAGS"]) + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx11_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.]) + fi + else + if test x$ac_success = xno; then + HAVE_CXX11=0 + AC_MSG_NOTICE([No compiler with C++11 support was found]) + else + HAVE_CXX11=1 + AC_DEFINE(HAVE_CXX11,1, + [define if the compiler supports basic C++11 syntax]) + fi + + AC_SUBST(HAVE_CXX11) + fi +]) diff --git a/src/bin/sage-banner b/src/bin/sage-banner index 4a95ae46233..23ca8c82c47 100644 --- a/src/bin/sage-banner +++ b/src/bin/sage-banner @@ -1,5 +1,5 @@ ┌────────────────────────────────────────────────────────────────────┐ -│ SageMath Version 6.10.beta0, Release Date: 2015-10-15 │ +│ SageMath Version 6.10.beta3, Release Date: 2015-11-05 │ │ Type "notebook()" for the browser-based notebook interface. │ │ Type "help()" for help. │ └────────────────────────────────────────────────────────────────────┘ diff --git a/src/bin/sage-env b/src/bin/sage-env index 53246b12513..cac4860c415 100644 --- a/src/bin/sage-env +++ b/src/bin/sage-env @@ -279,13 +279,14 @@ if [ "$UNAME" = "Darwin" ]; then # cause lots of random "Abort trap" issues on OSX. see trac # #7095 for an example. MACOSX_VERSION=`uname -r | awk -F. '{print $1}'` - MACOSX_DEPLOYMENT_TARGET=10.$[$MACOSX_VERSION-4] - if [ $MACOSX_DEPLOYMENT_TARGET = '10.10' ]; then - # Workaround for the libtool version detection bug - # See http://trac.sagemath.org/ticket/17204 + if [ $MACOSX_VERSION -ge 14 ]; then + # various packages have still have issues with + # two digit OS X versions MACOSX_DEPLOYMENT_TARGET=10.9 + else + MACOSX_DEPLOYMENT_TARGET=10.$[$MACOSX_VERSION-4] fi - export MACOSX_DEPLOYMENT_TARGET + export MACOSX_DEPLOYMENT_TARGET MACOSX_VERSION fi # Compile-time path for libraries. This is the equivalent of diff --git a/src/bin/sage-notebook b/src/bin/sage-notebook index 3766a840d53..cbe3e56c6f0 100755 --- a/src/bin/sage-notebook +++ b/src/bin/sage-notebook @@ -65,18 +65,16 @@ class NotebookSageNB(object): notebook(*self.args, **self.kwds) -class NotebookIPython(object): +class NotebookJupyter(object): PREREQUISITE_ERROR = textwrap.dedent(""" - The IPython notebook requires ssl, even if you do not use + The Jupyter notebook requires ssl, even if you do not use https. Install the openssl development packages in your system and then rebuild Python (sage -f python2). """) def __init__(self, argv): - from sage.repl.ipython_kernel.install import \ - SageKernelSpec, have_prerequisites - SageKernelSpec.update() + from sage.repl.ipython_kernel.install import have_prerequisites if not have_prerequisites(): print(self.PREREQUISITE_ERROR) raise SystemExit(1) @@ -118,8 +116,8 @@ EXAMPLES: notebook_launcher = { 'default': NotebookSageNB, # change this to change the default 'sagenb': NotebookSageNB, - 'ipython': NotebookIPython, - 'jupyter': NotebookIPython, + 'ipython': NotebookJupyter, + 'jupyter': NotebookJupyter, } notebook_names = ', '.join(notebook_launcher.keys()) @@ -182,8 +180,8 @@ if __name__ == '__main__': parser.print_help() elif launcher == NotebookSageNB: NotebookSageNB([], help=True) - elif launcher == NotebookIPython: - NotebookIPython(['help']) + elif launcher == NotebookJupyter: + NotebookJupyter(['help']) else: parser.print_help() sys.exit(0) diff --git a/src/bin/sage-unzip b/src/bin/sage-unzip new file mode 100755 index 00000000000..ca0fdf834a8 --- /dev/null +++ b/src/bin/sage-unzip @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# +# This file is a replacement for the 'unzip' command. +# +# We obtain its main feature (extraction) through the 'zipfile' python module. + +import argparse + +parser = argparse.ArgumentParser(description='Extract the contents of a .zip archive') +parser.add_argument('filename', help="the .zip archive", type=argparse.FileType('r')) +parser.add_argument('-d',help='An optional directory to which to extract files. Set to "." by default.',action='store',default='.') + +args = parser.parse_args() +from zipfile import ZipFile +ZipFile(args.filename).extractall(args.d) diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index b8eaf7940b3..c50d4aaa35a 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -1,4 +1,4 @@ # Sage version information for shell scripts # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='6.10.beta0' -SAGE_RELEASE_DATE='2015-10-15' +SAGE_VERSION='6.10.beta3' +SAGE_RELEASE_DATE='2015-11-05' diff --git a/src/doc/common/builder.py b/src/doc/common/builder.py index 851b7ee83ad..6787e22e9cc 100755 --- a/src/doc/common/builder.py +++ b/src/doc/common/builder.py @@ -214,7 +214,8 @@ def pdf(self): """ Builds the PDF files for this document. This is done by first (re)-building the LaTeX output, going into that LaTeX - directory, and running 'make all-pdf' there. + directory, and running 'make all-pdf' (or for the special case of + the ja docs, 'all-pdf-ja(ex,to run platex)' there. EXAMPLES:: @@ -225,9 +226,18 @@ def pdf(self): self.latex() tex_dir = self._output_dir('latex') pdf_dir = self._output_dir('pdf') - if subprocess.call("cd '%s' && $MAKE all-pdf && mv -f *.pdf '%s'"%(tex_dir, pdf_dir), shell=True): - raise RuntimeError("failed to run $MAKE all-pdf in %s"%tex_dir) + make_target = "cd '%s' && $MAKE %s && mv -f *.pdf '%s'" + error_message = "failed to run $MAKE %s in %s" + MB_LANG = {'ja': 'all-pdf-ja'} # language name : the modified target + # Replace the command for languages that require special processing + if self.lang in MB_LANG: + command = MB_LANG[self.lang] + else: + command = 'all-pdf' + + if subprocess.call(make_target%(tex_dir, command, pdf_dir), shell=True): + raise RuntimeError(error_message%(command, tex_dir)) logger.warning("Build finished. The built documents can be found in %s", pdf_dir) def clean(self, *args): diff --git a/src/doc/en/developer/manual_git.rst b/src/doc/en/developer/manual_git.rst index ae31a1dd327..a0849457d54 100644 --- a/src/doc/en/developer/manual_git.rst +++ b/src/doc/en/developer/manual_git.rst @@ -12,6 +12,8 @@ If you want to contribute using git only, you are at the right place. This chapter will tell you how to do so, assuming some basic familiarity with git. In particular, you should have read :ref:`chapter-walkthrough` first. +Randall Munroe has provided a `basic overview `_. + We assume that you have a copy of the Sage git repository, for example by running:: diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index 1dfa4eea6a2..ac8fc766b58 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -397,16 +397,6 @@ Sage (``FoO-1.3.tar.gz`` in the example of section in the ``SAGE_ROOT/upstream/`` directory and run ``sage --fix-pkg-checksums`` if you have not done that yet. -In order to update ``build/make/Makefile``, which contains the rules -to build all packages, you need to run the following command from -``SAGE_ROOT``:: - - [user@localhost]$ ./configure - -You need to re-run ``./configure`` whenever you change any package -metadata: if you add or remove a package or if you change the version, -type or dependencies of a package. - Now you can install the package using:: [user@localhost]$ sage -i package_name diff --git a/src/doc/en/reference/categories/index.rst b/src/doc/en/reference/categories/index.rst index 518d9e6582f..34cf4165a73 100644 --- a/src/doc/en/reference/categories/index.rst +++ b/src/doc/en/reference/categories/index.rst @@ -56,6 +56,7 @@ Individual Categories sage/categories/coxeter_group_algebras sage/categories/coxeter_groups sage/categories/crystals + sage/categories/cw_complexes sage/categories/discrete_valuation sage/categories/distributive_magmas_and_additive_magmas sage/categories/division_rings @@ -98,6 +99,7 @@ Individual Categories sage/categories/graded_hopf_algebras_with_basis sage/categories/graded_modules sage/categories/graded_modules_with_basis + sage/categories/graphs sage/categories/group_algebras sage/categories/groupoid sage/categories/groups @@ -109,10 +111,13 @@ Individual Categories sage/categories/integral_domains sage/categories/lattice_posets sage/categories/left_modules + sage/categories/lie_groups sage/categories/magmas sage/categories/magmas_and_additive_magmas sage/categories/magmatic_algebras + sage/categories/manifolds sage/categories/matrix_algebras + sage/categories/metric_spaces sage/categories/modular_abelian_varieties sage/categories/modules sage/categories/modules_with_basis @@ -139,6 +144,8 @@ Individual Categories sage/categories/sets_cat sage/categories/sets_with_grading sage/categories/sets_with_partial_maps + sage/categories/simplicial_complexes + sage/categories/topological_spaces sage/categories/unique_factorization_domains sage/categories/unital_algebras sage/categories/vector_spaces @@ -187,6 +194,7 @@ Examples of parents using categories sage/categories/examples/commutative_additive_semigroups sage/categories/examples/coxeter_groups sage/categories/examples/crystals + sage/categories/examples/cw_complexes sage/categories/examples/facade_sets sage/categories/examples/finite_coxeter_groups sage/categories/examples/finite_dimensional_algebras_with_basis @@ -196,8 +204,10 @@ Examples of parents using categories sage/categories/examples/finite_weyl_groups sage/categories/examples/graded_connected_hopf_algebras_with_basis sage/categories/examples/graded_modules_with_basis + sage/categories/examples/graphs sage/categories/examples/hopf_algebras_with_basis sage/categories/examples/infinite_enumerated_sets + sage/categories/examples/manifolds sage/categories/examples/monoids sage/categories/examples/posets sage/categories/examples/semigroups_cython diff --git a/src/doc/en/reference/coding/index.rst b/src/doc/en/reference/coding/index.rst index 6311e17f696..a96991cd80d 100644 --- a/src/doc/en/reference/coding/index.rst +++ b/src/doc/en/reference/coding/index.rst @@ -15,6 +15,7 @@ Coding Theory sage/coding/code_constructions sage/coding/guava sage/coding/sd_codes + sage/coding/bounds_catalog sage/coding/code_bounds sage/coding/codecan/codecan sage/coding/codecan/autgroup_can_label diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index b2f82fdae21..c29a76d835e 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -27,7 +27,6 @@ Comprehensive Module list sage/combinat/binary_tree sage/combinat/cartesian_product sage/combinat/catalog_partitions - sage/combinat/choose_nk sage/combinat/cluster_algebra_quiver/__init__ sage/combinat/cluster_algebra_quiver/all sage/combinat/cluster_algebra_quiver/cluster_seed @@ -118,7 +117,9 @@ Comprehensive Module list sage/combinat/graph_path sage/combinat/gray_codes sage/combinat/hall_polynomial - sage/combinat/integer_list + sage/combinat/integer_lists/base + sage/combinat/integer_lists/lists + sage/combinat/integer_lists/invlex sage/combinat/integer_matrices sage/combinat/integer_vector sage/combinat/integer_vector_weighted @@ -169,6 +170,7 @@ Comprehensive Module list sage/combinat/posets/incidence_algebras sage/combinat/posets/lattices sage/combinat/posets/linear_extensions + sage/combinat/posets/moebius_algebra sage/combinat/posets/poset_examples sage/combinat/posets/posets sage/combinat/q_analogues @@ -191,6 +193,7 @@ Comprehensive Module list sage/combinat/rigged_configurations/bij_type_C sage/combinat/rigged_configurations/bij_type_D sage/combinat/rigged_configurations/bij_type_D_twisted + sage/combinat/rigged_configurations/bij_type_D_tri sage/combinat/rigged_configurations/bijection sage/combinat/rigged_configurations/kleber_tree sage/combinat/rigged_configurations/kr_tableaux @@ -210,6 +213,7 @@ Comprehensive Module list sage/combinat/root_system/cartan_type sage/combinat/root_system/coxeter_group sage/combinat/root_system/coxeter_matrix + sage/combinat/root_system/coxeter_type sage/combinat/root_system/dynkin_diagram sage/combinat/root_system/hecke_algebra_representation sage/combinat/root_system/integrable_representations @@ -256,6 +260,7 @@ Comprehensive Module list sage/combinat/set_partition_ordered sage/combinat/sf/__init__ sage/combinat/sf/all + sage/combinat/sf/character sage/combinat/sf/classical sage/combinat/sf/dual sage/combinat/sf/elementary @@ -270,9 +275,11 @@ Comprehensive Module list sage/combinat/sf/multiplicative sage/combinat/sf/new_kschur sage/combinat/sf/ns_macdonald + sage/combinat/sf/orthogonal sage/combinat/sf/orthotriang sage/combinat/sf/powersum sage/combinat/sf/schur + sage/combinat/sf/symplectic sage/combinat/sf/sf sage/combinat/sf/sfa sage/combinat/sf/witt @@ -308,7 +315,6 @@ Comprehensive Module list sage/combinat/species/structure sage/combinat/species/subset_species sage/combinat/species/sum_species - sage/combinat/split_nk sage/combinat/subset sage/combinat/subsets_hereditary sage/combinat/subsets_pairwise diff --git a/src/doc/en/reference/homology/index.rst b/src/doc/en/reference/homology/index.rst index 7834bd7d79d..0524030d31c 100644 --- a/src/doc/en/reference/homology/index.rst +++ b/src/doc/en/reference/homology/index.rst @@ -13,6 +13,7 @@ cell complexes. sage/homology/chain_complex sage/homology/chain_complex_morphism + sage/homology/chain_homotopy sage/homology/chain_complex_homspace sage/homology/simplicial_complex sage/homology/simplicial_complex_morphism @@ -23,6 +24,9 @@ cell complexes. sage/homology/cell_complex sage/homology/koszul_complex sage/homology/homology_group + sage/homology/homology_vector_space_with_basis + sage/homology/algebraic_topological_model + sage/homology/homology_morphism sage/homology/matrix_utils sage/interfaces/chomp diff --git a/src/doc/en/reference/misc/index.rst b/src/doc/en/reference/misc/index.rst index 9d389c091aa..ab999774fd0 100644 --- a/src/doc/en/reference/misc/index.rst +++ b/src/doc/en/reference/misc/index.rst @@ -48,6 +48,7 @@ Lists and Iteration, etc. :maxdepth: 1 sage/misc/callable_dict + sage/misc/converting_dict sage/misc/flatten sage/misc/search sage/misc/sage_itertools diff --git a/src/doc/en/reference/rings/asymptotic_expansions_index.rst b/src/doc/en/reference/rings/asymptotic_expansions_index.rst index 127108976d2..3a15f21a3d1 100644 --- a/src/doc/en/reference/rings/asymptotic_expansions_index.rst +++ b/src/doc/en/reference/rings/asymptotic_expansions_index.rst @@ -1,10 +1,59 @@ Asymptotic Expansions ===================== + +The Asymptotic Ring +------------------- + +The asymptotic ring, as well as its main documentation is contained in +the module + +- :doc:`sage/rings/asymptotic/asymptotic_ring`. + + +Supplements +----------- + +Behind the scenes of working with asymptotic expressions a couple of +additional classes and tools turn up. For instance the growth of each +summand is managed in growth groups, see below. + + +Growth Groups +^^^^^^^^^^^^^ + +The growth of a summand of an asymptotic expression is managed in + +- :doc:`sage/rings/asymptotic/growth_group` and + +- :doc:`sage/rings/asymptotic/growth_group_cartesian`. + + +Term Monoids +^^^^^^^^^^^^ + +A summand of an asymptotic expression is basically a term out of the following monoid: + +- :doc:`sage/rings/asymptotic/term_monoid`. + + +Miscellaneous +^^^^^^^^^^^^^ + +Various useful functions and tools are collected in + +- :doc:`sage/rings/asymptotic/misc`. + + +Asymptotic Expansions --- Table of Contents +------------------------------------------- + .. toctree:: + sage/rings/asymptotic/asymptotic_ring sage/rings/asymptotic/growth_group + sage/rings/asymptotic/growth_group_cartesian sage/rings/asymptotic/term_monoid - sage/rings/asymptotic/asymptotic_ring + sage/rings/asymptotic/misc .. include:: ../footer.txt diff --git a/src/doc/en/reference/rings_numerical/index.rst b/src/doc/en/reference/rings_numerical/index.rst index f7971505669..a60a2210743 100644 --- a/src/doc/en/reference/rings_numerical/index.rst +++ b/src/doc/en/reference/rings_numerical/index.rst @@ -41,8 +41,8 @@ ComplexBallField). sage/rings/complex_interval_field sage/rings/complex_interval -.. Modules depending on optional packages: -.. sage/rings/real_arb + sage/rings/real_arb + sage/rings/complex_ball_acb Exact Real Arithmetic --------------------- diff --git a/src/doc/en/thematic_tutorials/coding_theory.rst b/src/doc/en/thematic_tutorials/coding_theory.rst index 427b7c226cc..93fadbc90a1 100644 --- a/src/doc/en/thematic_tutorials/coding_theory.rst +++ b/src/doc/en/thematic_tutorials/coding_theory.rst @@ -787,7 +787,7 @@ Regarding bounds on coding theory parameters, this module implements: :: - sage: dimension_upper_bound(10, 3, 2) + sage: codes.bounds.dimension_upper_bound(10, 3, 2) 6 This was established in the example above. @@ -937,17 +937,17 @@ Here are all the bounds together: :: - sage: f1 = lambda x: gv_bound_asymp(x,2) + sage: f1 = lambda x: codes.bounds.gv_bound_asymp(x,2) sage: P1 = plot(f1,0,1/2,linestyle=":") - sage: f2 = lambda x: plotkin_bound_asymp(x,2) + sage: f2 = lambda x: codes.bounds.plotkin_bound_asymp(x,2) sage: P2 = plot(f2,0,1/2,linestyle="--") - sage: f3 = lambda x: elias_bound_asymp(x,2) + sage: f3 = lambda x: codes.bounds.elias_bound_asymp(x,2) sage: P3 = plot(f3,0,1/2,rgbcolor=(1,0,0)) - sage: f4 = lambda x: singleton_bound_asymp(x,2) + sage: f4 = lambda x: codes.bounds.singleton_bound_asymp(x,2) sage: P4 = plot(f4,0,1/2,linestyle="-.") - sage: f5 = lambda x: mrrw1_bound_asymp(x,2) + sage: f5 = lambda x: codes.bounds.mrrw1_bound_asymp(x,2) sage: P5 = plot(f5,0,1/2,linestyle="steps") - sage: f6 = lambda x: hamming_bound_asymp(x,2) + sage: f6 = lambda x: codes.bounds.hamming_bound_asymp(x,2) sage: P6 = plot(f6,0,1/2,rgbcolor=(0,1,0)) sage: show(P1+P2+P3+P4+P5+P6) diff --git a/src/doc/en/thematic_tutorials/coercion_and_categories.rst b/src/doc/en/thematic_tutorials/coercion_and_categories.rst index 058b9f41f6b..b6f8e8bb015 100644 --- a/src/doc/en/thematic_tutorials/coercion_and_categories.rst +++ b/src/doc/en/thematic_tutorials/coercion_and_categories.rst @@ -878,7 +878,7 @@ The four axioms requested for coercions rational field is a homomorphism of euclidean domains:: sage: QQ.coerce_map_from(ZZ).category_for() - Category of euclidean domains + Join of Category of euclidean domains and Category of metric spaces .. end of output diff --git a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst index e4de374dda9..4756963f818 100644 --- a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst +++ b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst @@ -89,8 +89,7 @@ groups in the GAP transitive groups database. :: sage: K. = NumberField(x^3 - 2) - sage: K.galois_group(type="gap", algorithm='magma') # optional - magma - verbose... + sage: K.galois_group(type="gap", algorithm='magma') # optional - magma database_gap Galois group Transitive group number 2 of degree 3 of the Number Field in a with defining polynomial x^3 - 2 diff --git a/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst b/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst index 3caeb368547..af5ed9821dd 100644 --- a/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst +++ b/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst @@ -164,13 +164,13 @@ coefficients) through the usual free module accessors:: [((0, 0, 0), 1), ((1, 0, 0), 1), ((1, 1, 0), 1), ((1, 1, 1), 1)] sage: pprint(dict(chi)) {(0, 0, 0): 1, (1, 0, 0): 1, (1, 1, 0): 1, (1, 1, 1): 1} - sage: chi.monomials() + sage: M = sorted(chi.monomials(), key=lambda x: x.support()); M [B3(0,0,0), B3(1,0,0), B3(1,1,0), B3(1,1,1)] - sage: chi.support() + sage: sorted(chi.support()) [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1)] sage: chi.coefficients() [1, 1, 1, 1] - sage: [r.degree() for r in chi.monomials()] + sage: [r.degree() for r in M] [1, 7, 21, 35] sage: sum(r.degree() for r in chi.monomials()) 64 @@ -484,10 +484,10 @@ itself, that is, the integral of `|tr(g)|^{10}`:: sage: tr^5 5*A2(2,2,1) + 6*A2(3,1,1) + 5*A2(3,2,0) + 4*A2(4,1,0) + A2(5,0,0) - sage: (tr^5).monomials() + sage: sorted((tr^5).monomials(), key=lambda x: x.support()) [A2(2,2,1), A2(3,1,1), A2(3,2,0), A2(4,1,0), A2(5,0,0)] - sage: (tr^5).coefficients() - [5, 6, 5, 4, 1] + sage: sorted((tr^5).coefficients()) + [1, 4, 5, 5, 6] sage: sum(x^2 for x in (tr^5).coefficients()) 103 diff --git a/src/doc/en/thematic_tutorials/tutorial-objects-and-classes.rst b/src/doc/en/thematic_tutorials/tutorial-objects-and-classes.rst index 388d7d1d473..118e03c69c2 100644 --- a/src/doc/en/thematic_tutorials/tutorial-objects-and-classes.rst +++ b/src/doc/en/thematic_tutorials/tutorial-objects-and-classes.rst @@ -246,7 +246,7 @@ As an element of a vector space, ``el`` has a particular behavior:: sage: 2*el 2*B[[1, 2, 3]] + 6*B[[1, 3, 2]] + B[[3, 2, 1]] - sage: el.support() + sage: sorted(el.support()) [[1, 2, 3], [1, 3, 2], [3, 2, 1]] sage: el.coefficient([1, 2, 3]) 1 diff --git a/src/doc/en/tutorial/tour_coercion.rst b/src/doc/en/tutorial/tour_coercion.rst index 2bbb25d352b..7f94a0c84fd 100644 --- a/src/doc/en/tutorial/tour_coercion.rst +++ b/src/doc/en/tutorial/tour_coercion.rst @@ -118,6 +118,7 @@ implemented in Sage as well: sage: ZZ.category() Join of Category of euclidean domains and Category of infinite enumerated sets + and Category of metric spaces sage: ZZ.category().is_subcategory(Rings()) True sage: ZZ in Rings() diff --git a/src/doc/fr/tutorial/tour_coercion.rst b/src/doc/fr/tutorial/tour_coercion.rst index a032be7968c..395493ba3a6 100644 --- a/src/doc/fr/tutorial/tour_coercion.rst +++ b/src/doc/fr/tutorial/tour_coercion.rst @@ -119,6 +119,7 @@ par ailleurs les catégories en tant que telles : sage: ZZ.category() Join of Category of euclidean domains and Category of infinite enumerated sets + and Category of metric spaces sage: ZZ.category().is_subcategory(Rings()) True sage: ZZ in Rings() diff --git a/src/doc/ja/a_tour_of_sage/conf.py b/src/doc/ja/a_tour_of_sage/conf.py new file mode 100644 index 00000000000..a357e185c79 --- /dev/null +++ b/src/doc/ja/a_tour_of_sage/conf.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# +# Sage documentation build configuration file, based on that created by +# sphinx-quickstart on Thu Aug 21 20:15:55 2008. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os +sys.path.append(os.environ['SAGE_DOC']) +from common.conf import * + +# General information about the project. +project = u"Sage ガイドツアー" +name = u'a_tour_of_sage' +language = "ja" + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = project + " v"+release + +# Output file base name for HTML help builder. +htmlhelp_basename = name + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [ + ('index', name+'.tex', project, + u'The Sage Group', 'manual'), +] + +# LaTeX の docclass 設定 +latex_docclass = {'manual': 'jsbook'} + +# Additional LaTeX stuff for the French version +#latex_elements['preamble'] += '\\DeclareUnicodeCharacter{00A0}{\\nobreakspace}\n' + +# the definition of \\at in the standard preamble of the sphinx doc +# conflicts with that in babel/french[b] +latex_elements['preamble'] += '\\let\\at\\undefined' + +html_use_smartypants = False diff --git a/src/doc/ja/a_tour_of_sage/eigen_plot.png b/src/doc/ja/a_tour_of_sage/eigen_plot.png new file mode 100644 index 00000000000..925264764f1 Binary files /dev/null and b/src/doc/ja/a_tour_of_sage/eigen_plot.png differ diff --git a/src/doc/ja/a_tour_of_sage/index.rst b/src/doc/ja/a_tour_of_sage/index.rst new file mode 100644 index 00000000000..eeb8e01da5f --- /dev/null +++ b/src/doc/ja/a_tour_of_sage/index.rst @@ -0,0 +1,139 @@ +================== +Sageガイドツアー +================== + +以下では,『Mathematicaブック』冒頭のMathematica紹介をなぞって,Sageの紹介を試みる. + + +電卓としてのSage +==================== + +Sageのコマンドラインに表示されている ``sage:`` プロンプトを入力する必要はない. +Sageノートブックを使っている場合, ``sage:`` に続く全てを入力セルに入れて ``shift-enter`` と押すと計算出力が得られる. + +:: + + sage: 3 + 5 + 8 + +キャレット記号 ``^`` は「べき乗」を表わす. + +:: + + sage: 57.1 ^ 100 + 4.60904368661396e175 + + +Sageで :math:`2 \times 2` 行列の逆行列を計算してみよう. + +:: + + sage: matrix([[1,2], [3,4]])^(-1) + [ -2 1] + [ 3/2 -1/2] + +初等的な関数を積分する. + +:: + + sage: x = var('x') # 記号変数を定義 + sage: integrate(sqrt(x)*sqrt(1+x), x) + 1/4*((x + 1)^(3/2)/x^(3/2) + sqrt(x + 1)/sqrt(x))/((x + 1)^2/x^2 - 2*(x + 1)/x + 1) - 1/8*log(sqrt(x + 1)/sqrt(x) + 1) + 1/8*log(sqrt(x + 1)/sqrt(x) - 1) + + +以下ではSageに2次方程式を解かせる. +Sageでは等号として記号 ``==`` を使う. + +:: + + sage: a = var('a') + sage: S = solve(x^2 + x == a, x); S + [x == -1/2*sqrt(4*a + 1) - 1/2, x == 1/2*sqrt(4*a + 1) - 1/2] + +結果は等式のリストになっている. + +.. link + +:: + + sage: S[0].rhs() + -1/2*sqrt(4*a + 1) - 1/2 + +もちろん,よく使われる種々の関数をプロットすることもできる. + +:: + + sage: show(plot(sin(x) + sin(1.6*x), 0, 40)) + +.. image:: sin_plot.* + + +Sageで力まかせに計算 +========================= + +まず要素値が乱数で与えられる :math:`500 \times 500` 行列を作っておく. + +:: + + sage: m = random_matrix(RDF,500) + +Sageでこの行列の固有値を計算してプロットするのも二,三秒程度の仕事だ. + +.. link + +:: + + sage: e = m.eigenvalues() # 約2秒 + sage: w = [(i, abs(e[i])) for i in range(len(e))] + sage: show(points(w)) + +.. image:: eigen_plot.* + + +GNU多倍長ライブラリ(GMP)のおかげで,Sageは数百万から数十億桁までの非常に大きな数を扱うことができる. + +:: + + sage: factorial(100) + 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 + sage: n = factorial(1000000) # 2.5秒ほどかかる + +以下では :math:`\pi` を,少なくとも100桁まで計算する. + +:: + + sage: N(pi, digits=100) + 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068 + +Sageに2変数多項式を因数分解させる. + +:: + + sage: R. = QQ[] + sage: F = factor(x^99 + y^99) + sage: F + (x + y) * (x^2 - x*y + y^2) * (x^6 - x^3*y^3 + y^6) * + (x^10 - x^9*y + x^8*y^2 - x^7*y^3 + x^6*y^4 - x^5*y^5 + + x^4*y^6 - x^3*y^7 + x^2*y^8 - x*y^9 + y^10) * + (x^20 + x^19*y - x^17*y^3 - x^16*y^4 + x^14*y^6 + x^13*y^7 - + x^11*y^9 - x^10*y^10 - x^9*y^11 + x^7*y^13 + x^6*y^14 - + x^4*y^16 - x^3*y^17 + x*y^19 + y^20) * (x^60 + x^57*y^3 - + x^51*y^9 - x^48*y^12 + x^42*y^18 + x^39*y^21 - x^33*y^27 - + x^30*y^30 - x^27*y^33 + x^21*y^39 + x^18*y^42 - x^12*y^48 - + x^9*y^51 + x^3*y^57 + y^60) + sage: F.expand() + x^99 + y^99 + +Sageでは,1億を正整数の和として表す仕方を計算するにも5秒以下しかかからない. + +:: + + sage: z = Partitions(10^8).cardinality() # 約4.5秒 + sage: str(z)[:40] + '1760517045946249141360373894679135204009' + + +Sageにおけるアルゴリズム群の利用 +================================== + +Sageを使うということは,オープンソース計算アルゴリズムの世界最大級の集大成を利用できることを意味している. diff --git a/src/doc/ja/a_tour_of_sage/sin_plot.png b/src/doc/ja/a_tour_of_sage/sin_plot.png new file mode 100644 index 00000000000..ef4e87c69c1 Binary files /dev/null and b/src/doc/ja/a_tour_of_sage/sin_plot.png differ diff --git a/src/doc/ja/tutorial/afterword.rst b/src/doc/ja/tutorial/afterword.rst new file mode 100644 index 00000000000..5cee7577c51 --- /dev/null +++ b/src/doc/ja/tutorial/afterword.rst @@ -0,0 +1,150 @@ +========== + あとがき +========== + +なぜPyhtonなのか +================== + +Pythonの強み +------------- + +Sageの実装には主要言語としてPython([Py]_ を参照)が採用されている. +高速性を要する部分のコードはコンパイラ言語で実装されているものの,Pythonには固有の利点がある: + +- **オブジェクト保存機能** が充実している.Pythonは,(ほぼ)いかなるオブジェクトでもディスクファイルあるいはデータベースとして保存するための機能を豊富に備えている. + +- 優秀な **ドキュメント作成支援機能** を備えている. + 関数やパッケージ群のソースコードに対しては,ドキュメントの抽出と全具体例の検証までが自動化されている. + 具体例の自動検証は常時行なわれており,記載通りの動作を保証している. + +- **メモリ管理**: Pythonは,入念に設計された頑健なメモリ管理機能と,循環参照を間違いなく処理するガーベッジコレクタを備えており,異なるファイル内で定義された局所変数群も問題なく扱うことができる. + +- Pythonで利用できる **豊富なパッケージ群** は,Sageユーザにとっても非常に有益であることは間違いない. + 数値解析,線形代数,2次元および3次元グラフィクス,ネットワーキング(例えばtwistedを経由した分散処理とサーバー構築),データベース対応など,幅広い分野のパッケージが用意されている. + +- **高い移植性**: 一般的なプラットフォームでは,Pythonをソースコードからビルドするのは簡単で時間もかからない仕事である. + +- **例外処理**: Pythonは,細部まで考え抜かれ洗練された例外処理システムを備えている. + この例外処理システムを使えば,呼出し先のコードでエラーが発生した場合でもプログラム本体を破綻なく障害から復帰させることが可能だ. + +- **デバッガ**: 何らかの理由でコードがうまく動かない場合,Pythonのデバッガを使って詳細なスタックトレースを実行したり関連変数全ての状態を調べることができるし,スタック内を上下動することも可能だ. + +- **プロファイラ**: Pythonのプロファイラを使えば,実際にコードを動かして関数の呼出し回数と処理時間を調べることができる. + +- **汎用言語**: 数学指向の **独自言語** を開発したMagma, Maple,Mathematica, Matlab,GP/PARI, GAP, Macaulay 2, Simathなどとは違って,Sageは広く普及している汎用言語Pythonを採用している. + オープンソースの代表的成功事例であるPythonは,安定した開発体制下,熟練したソフトウェア技術者多数によって研究開発と最適化が進められている([PyDev]_ を参照). + + +.. _section-mathannoy: + + +前処理パーサ: SageとPython の相違点 +---------------------------------------- + +Pythonの数学機能には混乱を招きがちな面があり,SageにはPythonとは異なる振舞いを示す部分がある. + + +- **べき乗の記法:** ``**`` と ``^`` の意味が異なる. Pythonでは ``^`` は "xor"の意味で,べき乗を意味しない. + したがって,Pythonでは + + :: + + >>> 2^8 + 10 + >>> 3^2 + 1 + >>> 3**2 + 9 + + この ``^`` の使い方は奇妙な感じがするし,「排他的論理和」機能をほとんど使わない純粋な数学研究では無駄でもある. + Sageでは,Pythonに送る前に全コマンド行に対して文字列中にない限り ``^`` を ``**`` に置換する前処理をしている: + + :: + + sage: 2^8 + 256 + sage: 3^2 + 9 + sage: "3^2" + '3^2' + + Sageでは,ビット単位のXOR演算子は ``^^`` である. + 同じ記号は代入演算子 ``^^=`` にも応用されている: + + :: + + sage: 3^^2 + 1 + sage: a = 2 + sage: a ^^= 8 + sage: a + 10 + +- **整数の除算:** Pythonにおける式 ``2/3`` は,数学者が当然と思う値にならない. + Pythonでは, ``m`` と ``n`` がint型であれば, ``m/n`` つまり ``m`` を ``n`` で割った商もint型になる. + ``2/3=0`` となるのはこのためだ. + Pythonコミュニティでは,仕様を変更して ``2/3`` は浮動小数点数 ``0.6666...`` を返し, ``2//3`` は ``0`` を返すよう修正すべきという議論が続いている. + + この問題を解決するために,Sageインタプリタは整数リテラルを ``Integer()`` でラップし,その除算を有理数のコンストラクタとして機能させている. + 具体例を見てみよう: + + :: + + sage: 2/3 + 2/3 + sage: (2/3).parent() + Rational Field + sage: 2//3 + 0 + sage: int(2)/int(3) + 0 + +- **長整数:** Python本体は,C言語由来のint型だけではなく任意精度整数をサポートしている. + Pythonの任意精度整数はGMP提供のものと比べると著しく速度が劣り,通常のintと区別するために末尾に ``L`` を付けて出力される仕様になっている(この仕様はすぐには変更されそうにない). + Sageの任意精度整数はGMP C-ライブラリを使って実装されており,末尾の ``L`` なしで出力される. + + +Sageでは,(一部の人たちが内輪でやっているように)Pythonインタプリタそのものを改造することはせず,Python言語をそのままの形で使っている. +代りにIPythonに対する前処理パーサを開発して,IPythonコマンドラインの振舞いを数学者に馴染めるようにしてある. +これにより既存のPythonプログラムは例外なくSageで使えることになるが,Sageにインポートして使うパッケージを開発する場合には標準的なPythonの書法に従わなければならない. + + +(インターネットで見つけたPythonライブラリをインストールする場合は, ``python`` コマンドではなく ``sage -python`` としてインストール手順に従えばよい. +こうすると, ``sage -python setup.py install`` の意味になる.) + + +Sageプロジェクトを手助けするには? +================================== + +Sageプロジェクトに助力いただけるのなら,たいへん有難い. +実質的なコードの提供からSageドキュメンテーションの追加やバグ報告まで,どんな形であれ大歓迎である. + + +開発者向けの情報については、SageのWebページをご覧いただきたい. +優先順位とカテゴリー順に整理されたSage関連プロジェクトの長いリストが見つかるはずだ. +開発に役立つ情報は `Sage Developer's Guide `_ にも載っているし,Googleグループ ``sage-devel`` も役立つ. + + + +Sageを引用するには +================== + +Sageを使って論文を書く場合は,Sageによる計算が行なわれたことを明記するため,以下の一文を参考文献として引用していただきたい(Version 4.3の部分は実際に使用したバージョン番号に修正してください.): + +:: + + [Sage] William A. Stein et al., Sage Mathematics Software (Version 4.3). + The Sage Development Team, 2009, http://www.sagemath.org. + +さらに,Sageを構成するPARI,GAP,Singular,Maximaなどのシステムの内,どれを計算に利用したのかを特定してそのシステムも引用していただけるようお願いする. +もし計算に使ったソフトウェアがどれなのか確信がもてない場合は, Googleグループ ``sage-devel`` で気軽に尋ねてみよう. +こうした点については, :ref:`section-univariate` 節に詳しい話がある. + + +------------ + +このチュートリアルを最後まで読み終えた方は,どのくらい時間がかかったかGoogleグループ ``sage-devel`` で教えていただければ幸いである. + +どうかSageで楽しんでほしい. + + diff --git a/src/doc/ja/tutorial/appendix.rst b/src/doc/ja/tutorial/appendix.rst new file mode 100644 index 00000000000..b67d0382180 --- /dev/null +++ b/src/doc/ja/tutorial/appendix.rst @@ -0,0 +1,34 @@ +.. Appendix + +******** +付録 +******** + +.. _section-precedence: + +算術二項演算子の優先順位 +======================================= + +式 ``3^2*4 + 2%5`` は,どのようにして評価されるのだろうか? +その値(38)を決定しているのが,『演算子優先順位表』である.以下に示す順位表は、 +G. Rossum と F. Drakeによる *Python Language Reference Manual* の§5.14 にある表を基にしたものだ. +表中,下へ行くほど演算子の優先順位が高くなっている. + +========================== ================= +演算子 説明 +========================== ================= +or 論理和 +and 論理積 +not 論理否定 +in, not in 包含テスト +is, is not 同一性テスト +>, <=, >, >=, ==, != 比較 ++, - 加算,減算 +\*, /, % 乗算,除算,剰余 +\*\*, ^ べき乗 +========================== ================= + +したがって ``3^2*4 + 2%5`` の値を求めるに際して,Sageはこの式を ``((3^2)*4) + (2%5)`` のように括弧で区切ることになる. +次に ``3^2`` の値 ``9`` を計算し,ついで ``(3^2)*4`` と ``2%5`` 両方の値を求めてから,全てを足し合わせて出来上がりだ. + + diff --git a/src/doc/ja/tutorial/bibliography.rst b/src/doc/ja/tutorial/bibliography.rst new file mode 100644 index 00000000000..db092568660 --- /dev/null +++ b/src/doc/ja/tutorial/bibliography.rst @@ -0,0 +1,52 @@ +************ +Bibliography +************ + +.. [Cyt] Cython, http://www.cython.org. + +.. [Dive] Dive into Python, http://www.diveintopython.net/ から無料でダウンロードできる.Python3対応の日本語版は http://diveintopython3-ja.rdy.jp/ で公開されている. + +.. [GAP] The GAP Group, GAP - Groups, Algorithms, and + Programming, Version 4.4; 2005, http://www.gap-system.org + +.. [GAPkg] GAP Packages, + http://www.gap-system.org/Packages/packages.html + +.. [GP] PARI/GP http://pari.math.u-bordeaux.fr/. + +.. [Ip] The IPython shell http://ipython.scipy.org. + +.. [Jmol] Jmol: Jmol: オープンソース 分子構造 JAVA 3Dビューワ + http://www.jmol.org/. + +.. [Mag] Magma http://magma.maths.usyd.edu.au/magma/. + +.. [Max] Maxima http://maxima.sf.net/ + +.. [NagleEtAl2004] Nagle, Saff, and Snider. + *Fundamentals of Differential Equations*. 6th edition, Addison-Wesley, + 2004. + +.. [Py] The Python language http://www.python.org/ + Reference Manual http://docs.python.org/ref/ref.html. + 日本Pythonユーザ会のサイト http://www.python.jp/ で日本語版ドキュメントが公開されている.感謝. + +.. [PyDev] Guido, Some Guys, and a Mailing List: How Python is + Developed, + http://www.python.org/dev/dev_intro.html. + +.. [Pyr] Pyrex, + http://www.cosc.canterbury.ac.nz/~greg/python/Pyrex/. + +.. [PyT] The Python Tutorial http://www.python.org/. + +.. [SA] Sage web site http://www.sagemath.org/. + +.. [Si] G.-M. Greuel, G. Pfister, and H. Schönemann. Singular + 3.0. A Computer Algebra System for Polynomial Computations. Center + for Computer Algebra, University of Kaiserslautern (2005). + http://www.singular.uni-kl.de. + +.. [SJ] William Stein, David Joyner, Sage: System for Algebra and + Geometry Experimentation, Comm. Computer Algebra {39}(2005)61-64. + diff --git a/src/doc/ja/tutorial/conf.py b/src/doc/ja/tutorial/conf.py new file mode 100644 index 00000000000..81ba55bbd30 --- /dev/null +++ b/src/doc/ja/tutorial/conf.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# Sage documentation build configuration file, based on that created by +# sphinx-quickstart on Thu Aug 21 20:15:55 2008. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os +sys.path.append(os.environ['SAGE_DOC']) +from common.conf import * + +# General information about the project. +project = u"Sage チュートリアル" +name = u'tutorial-jp' +language = "ja" + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = project + " v"+release + +# Output file base name for HTML help builder. +htmlhelp_basename = name + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [ + ('index', name+'.tex', project, + u'The Sage Group', 'manual'), +] + +# LaTeX の docclass 設定 +latex_docclass = {'manual': 'jsbook'} + +# Additional LaTeX stuff for the French version +#latex_elements['preamble'] += '\\DeclareUnicodeCharacter{00A0}{\\nobreakspace}\n' + +# the definition of \\at in the standard preamble of the sphinx doc +# conflicts with that in babel/french[b] +latex_elements['preamble'] += '\\let\\at\\undefined' + +# +# html_use_smartypants = False diff --git a/src/doc/ja/tutorial/index.rst b/src/doc/ja/tutorial/index.rst new file mode 100644 index 00000000000..28b0a1eef8e --- /dev/null +++ b/src/doc/ja/tutorial/index.rst @@ -0,0 +1,37 @@ + +Sageチュートリアルへようこそ +================================ + +Sageは,代数学,幾何学,数論,暗号理論,数値解析,および関連諸分野の研究と教育を支援する,フリーなオープンソース数学ソフトウェアである. +Sageの開発モデルとテクノロジーに共通する著しい特徴は,公開,共有,協調と協働の原則の徹底的な遵守である. +我々の目的は言わば実用車の制作であって,車輪を再発明することではない. +総合目標としているのは,Maple,Mathematica,Magma,MATLABに代りうるフリーかつオープンソース化された実用システムの開発である. + +Sageがどんなものか,短時間で知りたければ,まずこのチュートリアルを読んでみていただきたい. +HTML版とPDF版のどちらを読んでもいいし,Sageノートブックを経由することもできる(チュートリアル内容を対話的に実行するには,ノートブックで ``Help`` , 続けて ``Tutorial`` をクリックする). + +この文章の著作権は `Creative Commons Attribution-Share Alike 3.0 License`__ +に準ずる. + +__ http://creativecommons.org/licenses/by-sa/3.0/ + +.. toctree:: + :maxdepth: 2 + + introduction + tour + interactive_shell + interfaces + latex + programming + sagetex + afterword + appendix + bibliography + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/src/doc/ja/tutorial/interactive_shell.rst b/src/doc/ja/tutorial/interactive_shell.rst new file mode 100644 index 00000000000..592a8827871 --- /dev/null +++ b/src/doc/ja/tutorial/interactive_shell.rst @@ -0,0 +1,981 @@ +.. _chapter-interactive_shell: + +********************* +対話型シェル +********************* + +このチュートリアルの大部分は,読者が ``sage`` コマンドによってSageインタプリタを起動しているものと前提している. +コマンド ``sage`` は改造版IPythonシェルを起動し,大量の関数やクラス群をインポートしてコマンドプロンプトから利用可能にする. +``$SAGE_ROOT/ipythonrc`` ファイルを編集すれば,さらなるシェル環境のカスタマイズも可能だ. +Sageを起動すると,すぐに次のような画面が現れる: + +.. skip + +:: + + ---------------------------------------------------------------------- + | SAGE Version 3.1.1, Release Date: 2008-05-24 | + | Type notebook() for the GUI, and license() for information. | + ---------------------------------------------------------------------- + + + sage: + +Sageを終了するには,Ctrl-Dと押すか, コマンド ``quit`` あるいは ``exit`` を入力する. + +.. skip + +:: + + sage: quit + Exiting SAGE (CPU time 0m0.00s, Wall time 0m0.89s) + + +"Wall time"は,CPUタイムではなく外界の実経過時間を示している. +CPUタイムはGAPやSingularなどのサブプロセスの消費時間までは勘定に入れてくれないから,実経過時間も計算時間の見積りに必要だ. + +(ターミナルから ``kill -9`` を入力してSageプロセスを停止するのは止めたほうがいい. +``kill -9`` ではMapleなどの子プロセスが停止しなかったり, ``$HOME/.sage/tmp`` 内の一時ファイルが消去されずに終わるなどの恐れがある.) + + +Sageセッション +================= + +*セッション* とは,Sageの起動から終了までの間に行なわれた一連の入出力の総体のことをいう. +Sageは,Sageに対する入力の全てをIPython経由で記録している. +事実,(ノートブック経由ではなく)対話型シェルを使ってSageを動かしているのならば,好きな時に ``%history`` (または ``%hist``) と入力して,それまでの全入力履歴を見ることができる. +IPythonについてもっと知りたければ、Sageプロンプトで ``?`` と入力すれば, "IPython offers numbered prompts ... with input and output caching. All input is saved and can be retrieved as variables (besides the usual arrow key recall). The following GLOBAL variables always exist (so don't overwrite them!)" などと詳しい情報を表示させることができる: + +:: + + _: 前回の入力を呼び出す (対話型シェルとノートブックの両方で通用する) + __: 前々回の入力を呼び出す(対話型シェルのみで通用) + _oh : 全ての入力をリストする(対話型シェルのみで通用) + +ここで例を見てみよう: + +.. skip + +:: + + sage: factor(100) + _1 = 2^2 * 5^2 + sage: kronecker_symbol(3,5) + _2 = -1 + sage: %hist # これが使えるのは対話型シェル上のみ.ノートブックではだめ. + 1: factor(100) + 2: kronecker_symbol(3,5) + 3: %hist + sage: _oh + _4 = {1: 2^2 * 5^2, 2: -1} + sage: _i1 + _5 = 'factor(ZZ(100))\n' + sage: eval(_i1) + _6 = 2^2 * 5^2 + sage: %hist + 1: factor(100) + 2: kronecker_symbol(3,5) + 3: %hist + 4: _oh + 5: _i1 + 6: eval(_i1) + 7: %hist + +以降,このチュートリアル,それに他のSageドキュメンテーションでも出力番号を省略する. + +セッション中は,一連の入力をマクロとして保存しておいて再利用することもできる. + + +.. skip + +:: + + sage: E = EllipticCurve([1,2,3,4,5]) + sage: M = ModularSymbols(37) + sage: %hist + 1: E = EllipticCurve([1,2,3,4,5]) + 2: M = ModularSymbols(37) + 3: %hist + sage: %macro em 1-2 + Macro `em` created. To execute, type its name (without quotes). + + +.. skip + +:: + + sage: E + Elliptic Curve defined by y^2 + x*y + 3*y = x^3 + 2*x^2 + 4*x + 5 over + Rational Field + sage: E = 5 + sage: M = None + sage: em + Executing Macro... + sage: E + Elliptic Curve defined by y^2 + x*y + 3*y = x^3 + 2*x^2 + 4*x + 5 over + Rational Field + +対話型シェルを使っている間も,感嘆符 ``!`` を前置すれば好きなUNIXシェルコマンドを実行することができる. +例えば + +.. skip + +:: + + sage: !ls + auto example.sage glossary.tex t tmp tut.log tut.tex + +のように,カレントディレクトリの内容を表示することができる. + +シェル変数 ``PATH`` の先頭にはSageのbinディレクトリが配置されているから, ``gp`` , ``gap`` , ``singular`` , ``maxima`` などを実行すると,Sageに付属しているプログラムのバージョンを確認することができる. + +.. skip + + +:: + + sage: !gp + Reading GPRC: /etc/gprc ...Done. + + GP/PARI CALCULATOR Version 2.2.11 (alpha) + i686 running linux (ix86/GMP-4.1.4 kernel) 32-bit version + ... + sage: !singular + SINGULAR / Development + A Computer Algebra System for Polynomial Computations / version 3-0-1 + 0< + by: G.-M. Greuel, G. Pfister, H. Schoenemann \ October 2005 + FB Mathematik der Universitaet, D-67653 Kaiserslautern \ + + + +入出力のログをとる +======================== + +Sageセッションのロギングと,セッションの保存(:ref:`section-save` 節を参照)は同じことではない. +入力のログをとるには, ``logstart`` コマンドを使う(オプションで出力のログも可能だ). +詳細については ``logstart?`` と入力してみてほしい. +``logstart`` を使えば,全ての入力と出力のログを残し,将来のセッション時に(そのログファイルをリロードしてやるだけで)入力を再生することも可能になる. + +.. skip + +:: + + was@form:~$ sage + ---------------------------------------------------------------------- + | SAGE Version 3.0.2, Release Date: 2008-05-24 | + | Type notebook() for the GUI, and license() for information. | + ---------------------------------------------------------------------- + + sage: logstart setup + Activating auto-logging. Current session state plus future input saved. + Filename : setup + Mode : backup + Output logging : False + Timestamping : False + State : active + sage: E = EllipticCurve([1,2,3,4,5]).minimal_model() + sage: F = QQ^3 + sage: x,y = QQ['x,y'].gens() + sage: G = E.gens() + sage: + Exiting SAGE (CPU time 0m0.61s, Wall time 0m50.39s). + was@form:~$ sage + ---------------------------------------------------------------------- + | SAGE Version 3.0.2, Release Date: 2008-05-24 | + | Type notebook() for the GUI, and license() for information. | + ---------------------------------------------------------------------- + + sage: load("setup") + Loading log file one line at a time... + Finished replaying log file + sage: E + Elliptic Curve defined by y^2 + x*y = x^3 - x^2 + 4*x + 3 over Rational + Field + sage: x*y + x*y + sage: G + [(2 : 3 : 1)] + +SageをLinux KDEターミナル ``konsole`` 上で使っているなら,以下の手順でセッションを保存することもできる. +まず ``konsole`` 上でSageを起動したら、 "settings"(日本語環境であれば『設定』)を選択し,次に "history"(『履歴』), "set unlimited"(『無制限にする』)の順に選択しておく. +セッションを保存したくなった時点で, "edit"(『編集』)の中の "save history as..."(『履歴を名前を付けて保存』)を選択してセッションを保存するファイル名を入力してやればよい. +いったんファイルとして保存してしまえば,好きなようにxemacsなどのエディタで読み込んだりプリントアウトしたりすることができる. + + + +プロンプト記号はペースト時に無視される +======================================== + +SageセッションあるいはPythonの演算結果を読み込んで,Sage上にコピーしたい場合がある. +厄介なのは、そうした出力に ``>>>`` や ``sage:`` といったプロンプト記号が紛れ込んでいることだ. +しかし実際には,プロンプト記号を含む実行例をSage上へ好きにコピー・ペーストしてやることができる. +デフォルトでSageパーサーはデータをPythonに送る前に行頭の ``>>>`` や ``sage:`` プロンプト記号を除去してくれるからだ. +例えば + +.. skip + +:: + + sage: 2^10 + 1024 + sage: sage: sage: 2^10 + 1024 + sage: >>> 2^10 + 1024 + + +計時コマンド +=============== + +入力行の先頭に ``%time`` コマンドを入れておくと,出力までに要した時間を表示することができる. +例として,べき乗計算を異なった方法で行なった場合の実行時間を比較してみよう. +以下に示した実行時間の値は,動かしているコンピュータ本体やSageのバージョンによって大きく異なる可能性が高い. +まず、Pythonを直に動かしてみると: + +.. skip + +:: + + sage: %time a = int(1938)^int(99484) + CPU times: user 0.66 s, sys: 0.00 s, total: 0.66 s + Wall time: 0.66 + + +上の出力は,実行に計0.66秒かかり, "Wall time" つまりユーザーの実待ち時間もやはり0.66秒だったことを示している. +コンピュータに他のプログラムから大きな負荷がかかっている場合, "Wall time"がCPUタイムよりかなり長くなることがある. + +次に,同じべき乗計算をSage組み込みのInteger型を使って実行した場合の時間を計ってみよう. +SageのInteger型は,Cython経由でGMPライブラリを使って実装されている: + +.. skip + +:: + + sage: %time a = 1938^99484 + CPU times: user 0.04 s, sys: 0.00 s, total: 0.04 s + Wall time: 0.04 + +PARIのC-ライブラリを経由すると + +.. skip + +:: + + sage: %time a = pari(1938)^pari(99484) + CPU times: user 0.05 s, sys: 0.00 s, total: 0.05 s + Wall time: 0.05 + +GMPの方が速いが,その差はわずかだ(Sage用にビルドされたPARIは整数演算にGMPを使っているのだから,納得できる結果である). + + +次の例のように, ``cputime`` コマンドを使えば,一連のコマンドからなるコードブロックの実行時間を計ることもできる: + +:: + + sage: t = cputime() + sage: a = int(1938)^int(99484) + sage: b = 1938^99484 + sage: c = pari(1938)^pari(99484) + sage: cputime(t) # random 値には若干の幅がある. + 0.64 + + +.. skip + + +:: + + sage: cputime? + ... + Return the time in CPU second since SAGE started, or with optional + argument t, return the time since time t. + INPUT: + t -- (optional) float, time in CPU seconds + OUTPUT: + float -- time in CPU seconds + + +``walltime`` コマンドの動作は,計測するのが実経過時間である点以外は ``cputime`` コマンドと変わらない. + +上で求めたべき乗を,Sageに取り込まれている各コンピュータ代数システムを使って計算することもできる. +計算を実行するには,使いたいシステムの名前をコマンド名としてそのプログラムのサーバを呼び出す. +いちばん肝心な計測値は,実経過時間(wall time)だ. +しかし,実経過時間とCPUタイムの値が大幅に食い違う場合は,解決すべきパフォーマンス上の問題点の存在を示している可能性がある. + +.. skip + +:: + + sage: time 1938^99484; + CPU times: user 0.01 s, sys: 0.00 s, total: 0.01 s + Wall time: 0.01 + sage: gp(0) + 0 + sage: time g = gp('1938^99484') + CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s + Wall time: 0.04 + sage: maxima(0) + 0 + sage: time g = maxima('1938^99484') + CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s + Wall time: 0.30 + sage: kash(0) + 0 + sage: time g = kash('1938^99484') + CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s + Wall time: 0.04 + sage: mathematica(0) + 0 + sage: time g = mathematica('1938^99484') + CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s + Wall time: 0.03 + sage: maple(0) + 0 + sage: time g = maple('1938^99484') + CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s + Wall time: 0.11 + sage: gap(0) + 0 + sage: time g = gap.eval('1938^99484;;') + CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s + Wall time: 1.02 + +以上のテスト計算で最も遅かったのは,GAPとMaximaである(実行結果はホスト ``sage.math.washington.edu`` 上のもの). +各システムとのpexpectインターフェイスにかかる負荷を考えると,上の一連の計測値を最速だったSageの値と比較するのは公平を欠く面があるかもしれない. + + +IPythonトリック +==================== + +すでに述べたように,SageはそのフロントエンドとしてIPythonを援用しており,ユーザはIPythonのコマンドと独自機能を自由に利用することができる. +その全貌については, ご自分で `full IPython documentation +`_ を読んみてほしい. +そのかわり,ここではIPythonの「マジックコマンド」と呼ばれる,お便利なトリックをいくつか紹介させていただこう: + +- ``%bg`` を使えばコマンドをバックグラウンドで実行し, 結果には ``jobs`` でアクセスすることができる.(この機能は ``not tested`` とコメントされている.というのは ``%bg`` 書法がSageの自動テスト機能とは余り相性が良くないからだ.ユーザ各自が入力してやれば,その通り動作するはずである.もちろん,この機能が最も役立つのは実行に時間のかかるコマンドと組み合わせる場合である.) + + 使用例を以下に示す. + + :: + + sage: def quick(m): return 2*m + sage: %bg quick(20) # not tested + Starting job # 0 in a separate thread. + sage: jobs.status() # not tested + Completed jobs: + 0 : quick(20) + sage: jobs[0].result # the actual answer, not tested + 40 + + バックグラウンドに送られるジョブはSageの前処理パーサを経由しないことに注意 -- 詳細は :ref:`section-mathannoy` 節を参照されたい. + パーサを通すための(不器用であるけれども)1つの方法は + + :: + + sage: %bg eval(preparse('quick(20)')) # not tested + + とすることだろう. + + ただし,より安全で簡単なのは前処理パーサを必要としないコマンドで ``%bg`` を使うことだろう. + + +- ``%edit`` (``%ed`` や ``ed`` でもいい)を使ってエディタを起動すれば,複雑なコードの入力が楽になる. + Sageの使用前に,環境変数 :envvar:`EDITOR` に好みのエディタ名を設定しておこう(``export EDITOR=/usr/bin/emacs`` または ``export EDITOR=/usr/bin/vim`` とするか, ``.profile`` ファイルなどで同様の設定をする). + するとSageプロンプトで ``%edit`` を実行すれば設定したエディタが起動する.そのエディタで関数 + + :: + + def some_function(n): + return n**2 + 3*n + 2 + + を定義し,保存したらエディタを終了する. + 以降,このセッション中は ``some_function`` を利用できるようになる. + 内容を編集したければSageプロンプトで ``%edit some_function`` と入力すればよい. + + +- 結果出力を他の用途のために編集したければ, ``%rep`` を実行する. + すると直前に実行したコマンドの出力が編集できるようにSageプロンプト上に配置される.:: + + sage: f(x) = cos(x) + sage: f(x).derivative(x) + -sin(x) + + この段階でSageプロンプトから ``%rep`` を実行すると,新しいSageプロンプトに続いて ``-sin(x)`` が現われる. + カーソルは同じ行末にある. + + +IPythonのクイック レファレンスガイドを見たければ, ``%quickref`` と入力する. +執筆時点(2011年4月)ではSageはIPythonのバージョン0.9.1を採用しており, `documentation for its magic commands +`_ +はオンラインで読むことができる. +マジックコマンドの,ちょっと進んだ機能群についてはIPythonの `ここ +`_ +で文書化されているのが見つかるはずだ. + + +エラーと例外処理 +===================== + +処理中に何かまずいことが起きると,Pythonはふつう『例外』(exception)を発生し,その例外を引き起こした原因を教えてくれることもある. +よくお目にかかることになるのは, ``NameError`` や ``ValueError`` といった名称の例外だ(Pythonレファレンスマニュアル [Py]_ に例外名の包括的なリストがある). +実例を見てみよう: + +:: + + sage: 3_2 + Traceback (most recent call last): + ... + SyntaxError: invalid syntax + + sage: EllipticCurve([0,infinity]) + Traceback (most recent call last): + ... + SignError: cannot multiply infinity by zero + + +何が悪いか調べるには対話型デバッガが役立つこともある. +デバッガを使うには、 ``%pdb`` コマンドによって作動のオン/オフをトグルする(デフォルトはオフ). +作動後は、例外が発生するとデバッガが起動し,プロンプト ``ipdb>`` が表示される. +このデバッガの中から,任意のローカル変数の状態を表示したり,実行スタックを上下して様子を調べることができる. + +.. skip + +:: + + sage: %pdb + Automatic pdb calling has been turned ON + sage: EllipticCurve([1,infinity]) + --------------------------------------------------------------------------- + Traceback (most recent call last) + ... + + ipdb> + + +デバッガから実行できるコマンドの一覧を見るには, ``ipdb>`` プロンプト上で ``?`` を入力する: +:: + + ipdb> ? + + Documented commands (type help ): + ======================================== + EOF break commands debug h l pdef quit tbreak + a bt condition disable help list pdoc r u + alias c cont down ignore n pinfo return unalias + args cl continue enable j next pp s up + b clear d exit jump p q step w + whatis where + + Miscellaneous help topics: + ========================== + exec pdb + + Undocumented commands: + ====================== + retval rv + +Sageに戻るには,Ctrl-Dか ``quit`` を入力する. + + +.. _section-tabcompletion: + +コマンド入力の遡行検索とタブ補完 +================================= + +*遡行検索*: コマンドの冒頭部を打ち込んでから ``Ctrl-p`` (または上向き矢印キー)を押すと,冒頭部が一致する過去の入力行を全て呼び出すことができる. +この機能は,Sageをいったん終了し再起動してからでも有効である. +``Ctrl-r`` を入力すれば,入力ヒストリを逆方向に検索することも可能だ. +この入力行の検索と再利用機能は全て ``readline`` パッケージを経由しており,ほとんどのLinux系システム上で利用できるはずだ. + +タブ補完機能を体験するため,まず3次元ベクトル空間 :math:`V=\QQ^3` を生成しておく: +:: + + sage: V = VectorSpace(QQ,3) + sage: V + Vector space of dimension 3 over Rational Field + +次のような,もっと簡潔な記号法を使ってもよい: + +:: + + sage: V = QQ^3 + + +タブ補完を使えば,簡単に :math:`V` の全メンバ関数を一覧表示することができる. +``V.`` と入力し,ついで ``[tab]`` キーを押すだけだ: + +.. skip + +:: + + sage: V.[tab key] + V._VectorSpace_generic__base_field + ... + V.ambient_space + V.base_field + V.base_ring + V.basis + V.coordinates + ... + V.zero_vector + +関数名の出だし何文字かを打ってから ``[tab キー]`` を押せば,入力した文字で始まる名前の関数だけに候補を絞ることができる. + +.. skip + +:: + + sage: V.i[tab key] + V.is_ambient V.is_dense V.is_full V.is_sparse + +特定の関数について調べたい場合もある. +coordinates関数を例にとると,そのヘルプを表示するには ``V.coordinates?`` と入力すればいいし,ソースコードを見るには ``V.coordinates??`` を入力すればいい. +詳細については次の節で解説する. + + +統合ヘルプシステム +====================== + +Sageの特長の一つは,総合的なヘルプ機能の装備である. +関数名に続けて?を入力すると、その関数のドキュメントを表示することができる. + +.. skip + +:: + + sage: V = QQ^3 + sage: V.coordinates? + Type: instancemethod + Base Class: + String Form: + Namespace: Interactive + File: /home/was/s/local/lib/python2.4/site-packages/sage/modules/f + ree_module.py + Definition: V.coordinates(self, v) + Docstring: + Write v in terms of the basis for self. + + Returns a list c such that if B is the basis for self, then + + sum c_i B_i = v. + + If v is not in self, raises an ArithmeticError exception. + + EXAMPLES: + sage: M = FreeModule(IntegerRing(), 2); M0,M1=M.gens() + sage: W = M.submodule([M0 + M1, M0 - 2*M1]) + sage: W.coordinates(2*M0-M1) + [2, -1] + + +上で見たように,ヘルプ表示には,そのオブジェクトの型,定義されているファイル,現セッションにペーストすることができる使用例付きの解説が含まれる. +使用例のほとんどは常に自動的なテストが行なわれていて,仕様どおりの正確な動作が確認されている. + +もう一つの機能は,Sageのオープンソース精神をよく表すものだ. +``f`` がPythonで書かれた関数であれば ``f??`` と入力すると ``f`` を定義しているソースを表示することができるのだ. +例えば + +.. skip + +:: + + sage: V = QQ^3 + sage: V.coordinates?? + Type: instancemethod + ... + Source: + def coordinates(self, v): + """ + Write $v$ in terms of the basis for self. + ... + """ + return self.coordinate_vector(v).list() + +これを見ると, ``coordinates`` 関数は ``coordinate_vector`` 関数を呼び出して結果をリストに変換しているだけであることが判る. +では ``coordinate_vector`` 関数が何をしているかと言うと: + +.. skip + +:: + + sage: V = QQ^3 + sage: V.coordinate_vector?? + ... + def coordinate_vector(self, v): + ... + return self.ambient_vector_space()(v) + + +``coordinate_vector`` 関数は,入力を生成空間(ambient space)に合わせて型変換するから,これは :math:`v` の係数ベクトルが空間 :math:`V` ではどう変換されるか計算していることと同じである. +:math:`V` は :math:`\QQ^3` そのものだから,すでに同じ構造になっている. +部分空間用に,上とは異なる ``coordinate_vector`` 関数も用意されている. +部分空間を作って,どんな関数か見てみることにしよう: + +.. skip + +:: + + sage: V = QQ^3; W = V.span_of_basis([V.0, V.1]) + sage: W.coordinate_vector?? + ... + def coordinate_vector(self, v): + """ + ... + """ + # First find the coordinates of v wrt echelon basis. + w = self.echelon_coordinate_vector(v) + # Next use transformation matrix from echelon basis to + # user basis. + T = self.echelon_to_user_matrix() + return T.linear_combination_of_rows(w) + +(こうした実装の仕方は無駄が多いと思われる方は,どうか我々に連絡して線形代数周りの最適化に力を貸していただきたい.) + + +``help(コマンド名)`` あるいは ``help(クラス名)`` と入力すれば,知りた いクラスのmanページ型ヘルプファイルを表示することもできる. + + +.. skip + +:: + + sage: help(VectorSpace) + Help on class VectorSpace ... + + class VectorSpace(__builtin__.object) + | Create a Vector Space. + | + | To create an ambient space over a field with given dimension + | using the calling syntax ... + : + : + + +``q`` と入力してヘルプを終えると,中断前のセッション画面がそのまま復帰する. +セッションに干渉することがある ``function_name?`` と違って, ヘルプ表示はセッションの邪魔をしない. +とりわけ便利なのは ``help(モジュール名)`` と入力することだ. +例えばベクトル空間は ``sage.modules.free_module`` で定義されているから,そのモジュール全体に関するドキュメントを見たければ ``help(sage.modules.free_module)`` と実行すればよい. +ヘルプを使ってドキュメントを閲覧している間は, ``/`` と打てば語句検索 ができるし, ``?`` と打てば逆方向に検索することができる. + + + +オブジェクトの保存と読み込み +===================================== + +行列や,あるいはもっと手間のかかる複雑なモジュラーシンボルの空間を扱っていて,後で利用するため結果を保存しておきたくなったとしよう. +そんな場合にはどうすればよいだろうか. +オブジェクトを保存するために各コンピュータ代数システムが提供している方法は,以下の通りである. + + +#. **セッションの保存:** セッション全体の保存と読み込みのみ可能(GAP,Magmaなど). + +#. **統合入出力:** 全オブジェクトの印字が再利用可能な形式で行なわれる(GAPとPARI). + +#. **再実行**: インタープリタによるプログラムの再実行が容易にしてある(Singular,PARI). + + +.. + #. **Save your Game:** Only support saving and loading of complete + sessions (e.g., GAP, Magma). + + #. **Unified Input/Output:** Make every object print in a way that + can be read back in (GP/PARI). + + #. **Eval**: Make it easy to evaluate arbitrary code in the + interpreter (e.g., Singular, PARI). + +Pythonで動くSageでは,全てのオブジェクトのシリアル化(直列化)という,他とは異なる方法が採用されている. +つまりオブジェクトを,その原型を再現可能な形式で文字列に変換するのだ. +これはPARIの統合入出力の考え方に近いが,オブジェクトを複雑な印字形式で画面出力してやる必要がないのが利点だ. +さらに保存と読み込みは(ほとんどの場合)完全に自動化されているから,新たにプログラムを書く必要もない. +そうした機能はPythonに最初から組込まれているものだからである. + + + +ほぼ全てのSageオブジェクト ``x`` は, コマンド ``save(x,ファイル名)`` (あるいは多くの場合 ``x.save(ファイル名)``)を使えば圧縮形式でディスクに保存することができるようになっている. +保存したオブジェクトを読み戻すには, ``load(ファイル名)`` を実行する. + + +.. skip + +:: + + sage: A = MatrixSpace(QQ,3)(range(9))^2 + sage: A + [ 15 18 21] + [ 42 54 66] + [ 69 90 111] + sage: save(A, 'A') + +ここでいったんSageを終了してみよう.再起動後に ``A`` を読み込むには: + + +.. skip + +:: + + sage: A = load('A') + sage: A + [ 15 18 21] + [ 42 54 66] + [ 69 90 111] + +楕円曲線のようなもっと複雑なオブジェクトに対しても,以上と同じやり方が通用する. +メモリ上に配置されていたオブジェクト関連の全データは,そのオブジェクトと共に保存される. +例えば + +.. skip + +:: + + sage: E = EllipticCurve('11a') + sage: v = E.anlist(100000) # ちょっと時間がかかる + sage: save(E, 'E') + sage: quit + +こうして保存された ``E`` は,オブジェクト本体と一緒に :math:`a_n` の冒頭100000個も保存するため,153Kバイトの大きさになる. + + +.. skip + +:: + + ~/tmp$ ls -l E.sobj + -rw-r--r-- 1 was was 153500 2006-01-28 19:23 E.sobj + ~/tmp$ sage [...] + sage: E = load('E') + sage: v = E.anlist(100000) # すぐ終了 + +(Python経由の保存と読み込みには, ``cPickle`` モジュールが使われている. +実際,Sageオブジェクト ``x`` の保存は ``cPickle.dumps(x, 2)`` を実行して行なうことができる.引数 ``2`` に注目.) + + +Sageで保存・読み込みできないのは,GAP, Singular, Maximaなど外部コンピュータ代数システムで作成されたオブジェクトである. +これらは読み込むことができても "invalid"(利用不能)な状態にあると認識される. +GAPでは,相当数のオブジェクトが再構成に使える印字形式を持つ一方,再構成できな場合も多いため印字形式からのオブジェクトの再構成は意図的に禁止されている. + + +.. skip + +:: + + sage: a = gap(2) + sage: a.save('a') + sage: load('a') + Traceback (most recent call last): + ... + ValueError: The session in which this object was defined is no longer + running. + +GP/PARIオブジェクトは,印字形式から十分に再構成可能なため,保存と読み込みも可能になっている. + + +.. skip + +:: + + sage: a = gp(2) + sage: a.save('a') + sage: load('a') + 2 + + +保存したオブジェクトは,異なるアーキテクチャ上の,異なるオペレーティングシステムで動くSageへもロードすることができる. +例えば,32ビット OSX上で保存した大規模行列を64ビット版LinuxのSageへ読み込み,その階段形式を求めてから元のOS X上へ戻すといったことも可能だ. +さらに,オブジェクトの保存までに使ったのとは違うバージョンのSageでオブジェクトを読み込むこともできる場合が多い. +ただし,これは読み書きしたいオブジェクトに関わるコードがバージョン間で大きくは異ならないことが条件となる. +オブジェクトの保存に際しては,その属性の全てがオブジェクトを定義している(ソースコードではなく)クラスと共に保存される. +そのクラスが新バージョンのSageに存在しない場合,配下のオブジェクトを新バージョンでは読み込むことはできない. +しかし古いバージョンで読み込むことはできるはずだから,(``x.__dict__`` で)オブジェクト ``x`` のディクショナリを生成して保存しておけば,それを新しいバージョンで読み込むことができることもある. + + + +テキスト形式で保存する +-------------------------- + +オブジェクトをASCIIテキスト形式で保存しておくこともできる. +手順は,ファイルを書込みモードで開いて,そこに保存すべきオブジェクトの文字列表現を書き込むだけのことだ(このやり方で複数個のオブジェクトを保存することができる). +オブジェクトの書込みを終えたら,ファイルをクローズすればよい. + + +.. skip + +:: + + sage: R. = PolynomialRing(QQ,2) + sage: f = (x+y)^7 + sage: o = open('file.txt','w') + sage: o.write(str(f)) + sage: o.close() + + + +.. _section-save: + +セッション全体の保存と読み込み +==================================== + +Sageは,セッション全体を保存し再ロードするための非常に柔軟な機能を備えている. + + +コマンド ``save_session(セッション名)`` は,現セッション中に定義された全ての変数を、コマンドで指定した ``セッション名`` にディクショナリとして保存する. +(保存を想定していない変数がある場合もまれに見られるが,そうした時はディクショナリに保存されずに終るだけだ.) +保存先は ``.sobj`` ファイルとなり,他の保存済みオブジェクトと全く同じように読み込むことができる. +セッション中に保存したオブジェクトを再びロードすると,変数名をキー,オブジェクトを値とするディクショナリが生成されることになる. + + +実行中のセッションに ``セッション名`` に定義された変数をロードするには, ``load_session(セッション名)`` コマンドを使う. +このコマンドは現セッションとロードされる側のセッション内容を合併するのであって,現セッションで定義した変数が消去されるわけではない. + + +まずSageを起動し,変数をいくつか定義しておく. + +.. skip + +:: + + sage: E = EllipticCurve('11a') + sage: M = ModularSymbols(37) + sage: a = 389 + sage: t = M.T(2003).matrix(); t.charpoly().factor() + _4 = (x - 2004) * (x - 12)^2 * (x + 54)^2 + +次にこのセッションをファイルに保存し,先に定義した変数を残しておく. +``.sobj`` ファイルを確認すると,その大きさは3Kバイトほどとなっている. + + +.. skip + +:: + + sage: save_session('misc') + Saving a + Saving M + Saving t + Saving E + sage: quit + was@form:~/tmp$ ls -l misc.sobj + -rw-r--r-- 1 was was 2979 2006-01-28 19:47 misc.sobj + +仕上げにSageを再起動し,変数をいくつか追加定義してから,先に保存したセッションを読み込んでみよう. + + +.. skip + +:: + + sage: b = 19 + sage: load_session('misc') + Loading a + Loading M + Loading E + Loading t + +保存しておいた変数が再び利用可能になる一方,上で追加した変数 ``b`` は上書きされていないことが分る. + + +.. skip + +:: + + sage: M + Full Modular Symbols space for Gamma_0(37) of weight 2 with sign 0 + and dimension 5 over Rational Field + sage: E + Elliptic Curve defined by y^2 + y = x^3 - x^2 - 10*x - 20 over Rational + Field + sage: b + 19 + sage: a + 389 + + + +.. _section-notebook: + +ノートブックインターフェイス +================================== + +Sageノートブックを起動するには、Sageコマンドライン上で + +.. skip + +:: + + sage: notebook() + +と実行する. +これでSageノートブックが起動すると同時に,閲覧用のデフォルトWebブラウザが開かれる. +ノートブックサーバが使用する状態ファイル群は, ``$HOME/.sage/sage\_notebook`` に保存される. + + +起動時に指定できるオプションとして + +.. skip + + +:: + + sage: notebook("ディレクトリ名") + + +とすると,標準ディレクトリ ``$HOME/.sage/sage_notebook`` ではなく指定した ``ディレクトリ名`` のディレクトリにある状態ファイル群を使って新しくノートブックサーバを起動する. +このオプションは,特定のプロジェクトに多数のワークシート群がぶら下がっていたり,同時に複数のノートブックサーバを動かしたい場合に便利だ. + +ノートブックを起動すると、まず ``$HOME/.sage/sage_notebook`` 内に以下のようなファイル群が生成される: + + +:: + + nb.sobj (ノートブックSAGEオブジェクト ファイル) + objects/ (SAGEオブジェクト群を保管するディレクトリ) + worksheets/ (SAGEワークシートを保管するディレクトリ) + + +上のファイル群の作成後,ノートブックはWebサーバの起動を行なう. + + +「ノートブック」(notebook)とはユーザーアカウントの集合であって,各ノートブックにはワークシートを好きな数だけ保持することができる. +ワークシートを新規に作成すると,そのワークシートを定義するデータが ``worksheets/username/number`` ディレクトリに保存される. +これらのディレクトリに必ず出来ているのがファイル ``worksheet.txt`` である. +このプレーンテキストからなるファイルには,ワークシートやSageその他に何によらず変更が加えられた場合,元のワークシートを復元するために必要な全情報が可読形式で保存されている. + + +Sage上で ``notebook?`` と入力すると,ノートブックサーバを起動する方法に関する詳しい情報が得られる. + + +次の図を見ると,Sageノートブックの構造が分る: + +:: + + ---------------------- + | | + | | + | firefox/safari | + | | + | javascript | + | program | + | | + | | + ---------------------- + | ^ + | AJAX | + V | + ---------------------- + | | + | sage | SAGE process 1 + | web | ------------> SAGE process 2 (Python processes) + | server | pexpect SAGE process 3 + | | . + | | . + ---------------------- . + + + +ノートブックからSageコマンド ``コマンド名`` のヘルプを見たければ,ブラウザ表示画面内入力ボックスで ``コマンド名?`` と入力し, ```` を押せばよい(```` ではない). + + +ノートブック上で通用するキーボードショートカットを確認するには, ``Help`` リンクをクリックする. + diff --git a/src/doc/ja/tutorial/interfaces.rst b/src/doc/ja/tutorial/interfaces.rst new file mode 100644 index 00000000000..99158bb31e8 --- /dev/null +++ b/src/doc/ja/tutorial/interfaces.rst @@ -0,0 +1,309 @@ +.. linkall + +************************* +インターフェイスについて +************************* + +Sageの最大の特長は,多種多様なコンピュータ代数システムを,共通インターフェイスと本格的なプログラミング言語Pythonを用いて一つ屋根の下にまとめ上げている点だ. +これによりSageでは由来の異なるオブジェクト群を組み合わせた演算処理が可能になっている. + +ある1つのインターフェイスに対し, ``console`` メソッドと ``interact`` メソッドは,全く違った機能を果たしている. +GAPを例にとって具体的に見てみよう: + + +#. ``gap.console()``: このメソッドはGAPのコンソールを起動し,命令実行の主体をGAP上へ移す. + Sageの役割は,Linuxのbashシェルのような便利なプログラムランチャとしての役割に限定される. + +#. ``gap.interact()``: このメソッドは,Sageオブジェクト群を使って動作しているGAPインスタンスと情報交換するための便利な径路を提供する. + これを使うと,GAPセッション中に(対話型インタフェイスからでも)Sageオブジェクトをインポートすることができる. + + +.. index: PARI; GP + +GP/PARI +======== + + + +数論関係の演算処理を主目的とするPARIは,コンパクトで非常に練れたプログラムで,高度に最適化されている. +SageからPARIを使うには,大きく異なる2種類のインターフェイスを選ぶことができる: + +- ``gp`` - "**G** o **P** ARI" インタープリタ + +- ``pari`` - PARI Cライブラリ + +以下の例では,同一の計算を二通りのやり方で実行している. +一見同じように見えても実は出力内容は同一ではないし,画面の奥で実際に行なわれている処理過程は二つの方法で全くと言っていいほど異なっているのだ. + +:: + + sage: gp('znprimroot(10007)') + Mod(5, 10007) + sage: pari('znprimroot(10007)') + Mod(5, 10007) + + +第一のやり方では,GPインタープリタが独立したサーバプロセスとして起動され,そのプロセスに文字列 ``'znprimroot(10007)'`` が送られる. +GPは送られて来た文字列を評価し,結果を変数に格納する(変数は子GPプロセス配下の,開放されないメモリ空間内に確保される). +その変数の値が画面表示されて仕上がりになる. +第二のやり方では、独立したプロセスが起動されることはなく,文字列 ``'znprimroot(10007)'`` はPARI Cライブラリ関数群によって処理される. +処理結果はPythonの確保しているヒープ上に配置され,その変数が参照されなくなると使っていたヒープメモリは開放される. +二つのやり方では、生成されるオブジェクトの型からして違っている: + +:: + + sage: type(gp('znprimroot(10007)')) + + sage: type(pari('znprimroot(10007)')) + + +では,どちらの方法を選ぶべきだろうか? +答は目的による、としか言えない. +GPインターフェイスはGP/PARIプログラムをそのまま動作させるのだから,GP/PARIのコマンド入力行から可能なことは全て出来る. +複雑なPARIプログラムを読み込んで走らせたい場合などには向いているだろう. +これと比較すると,(Cライブラリを経由する)PARIインターフェイスはかなり制限がきつい. +まだライブラリのメンバ関数の全てが実装されているわけではないし, +数値積分などを含むコードの多くがPARIインターフェイスからは使えない状態だ. +一方,PARIインターフェイスはGPインターフェイスより大幅に高速で頑強でもある. + +(入力行を評価中にメモリ不足に陥った場合,GPインターフェイスは特に警告することなく自動的にスタックサイズを2倍して評価を再試行する. +必要とされるメモリ量を正しく見積っていなかったとしても処理が頓挫することはまずない. +この有難い仕掛けは,標準のGPインタープリタには備えられていないようだ. +PARI Cライブラリ インターフェイスについて言うと,こちらは生成したオジェクトを直ちにコピーしてPARIスタックから送り出してしまうので,スタックが積み上がることはない. +しかし,どんなオブジェクトも大きさが100MBを越えてはならず,越えて生成された場合はスタックオーバーフローが起きる. +このオブジェクトのコピーが,わずかながら実行効率上の不利を招いているのも確かである.) + +まとめると,SageはPARI Cライブラリを利用してGP/PARIインタープリタと同様の機能を提供しているが,優秀なメモリ管理とプログラミング言語Pythonの援用という利点がある,ということになる. + + +ここで,PythonのリストからPARIのリストを作ってみよう. + +:: + + sage: v = pari([1,2,3,4,5]) + sage: v + [1, 2, 3, 4, 5] + sage: type(v) + + + +PARIのオブジェクトは全て ``py_pari.gen`` 型になる. +各オブジェクトに埋め込まれているPARI由来のデータ型を取得するには,メンバ関数 ``type`` を使う. + +:: + + sage: v.type() + 't_VEC' + +PARIで楕円曲線を生成するには, ``ellinit([1,2,3,4,5])`` と入力する. +Sageの方法も似ているが, ``ellinit`` を先の ``t\_VEC v`` のような任意のPARIオブジェクトに対して呼び出すことができる点が違っている. +:: + + sage: e = v.ellinit() + sage: e.type() + 't_VEC' + sage: pari(e)[:13] + [1, 2, 3, 4, 5, 9, 11, 29, 35, -183, -3429, -10351, 6128487/10351] + +楕円曲線オブジェクトが生成できたので,これを使った計算を試みよう. + +:: + + sage: e.elltors() + [1, [], []] + sage: e.ellglobalred() + [10351, [1, -1, 0, -1], 1, [11, 1; 941, 1], [[1, 5, 0, 1], [1, 5, 0, 1]]] + sage: f = e.ellchangecurve([1,-1,0,-1]) + sage: f[:5] + [1, -1, 0, 4, 3] + +.. index: GAP + +.. _section-gap: + +GAP +=== + +Sageには GAP 4.4.10が付属しており,群論を始めとする計算離散数学に対応している. + +ここでは、例としてGAPの ``IdGroup`` 関数を取り上げることにしよう. +この機能を使うには群論関係の小規模なデータベースが必要だが,標準ではインストールされない. +これは別途インストールしておかなければならないから,後で手順を説明する. + +:: + + sage: G = gap('Group((1,2,3)(4,5), (3,4))') + sage: G + Group( [ (1,2,3)(4,5), (3,4) ] ) + sage: G.Center() + Group( () ) + sage: G.IdGroup() # optional - database_gap オプション - database_gapが必要 + [ 120, 34 ] + sage: G.Order() + 120 + + +上と同じ処理を,GAPインタ=フェイスを明示的には呼び出さずにSageから実行するには: + +:: + + sage: G = PermutationGroup([[(1,2,3),(4,5)],[(3,4)]]) + sage: G.center() + Subgroup of (Permutation Group with generators [(3,4), (1,2,3)(4,5)]) generated by [()] + sage: G.group_id() # optional - database_gap オプションのdatabase_gapパッケージが必要 + [120, 34] + sage: n = G.order(); n + 120 + +(GAPの機能の一部は,二種類の標準外Sageパッケージをインストールしないと使うことができない. +コマンド行から ``sage -optional`` と入力すると,インストール可能なパッケージの一覧が表示される. +その一覧に ``gap\_packages-x.y.z`` といった項目があるから, ``sage -i gap\_packages-x.y.z`` を実行してパッケージをインストールする. +パッケージ ``database\_gap-x.y.z`` のインストールも手順は同じだ. +一部のGPLではないGAPパッケージは,GAPウェブサイト [GAPkg]_ からダウンロードし, ``$SAGE_ROOT/local/lib/gap-4.4.10/pkg`` で解凍してからインストールする必要がある.) + + +Singular +======== + +Singularは,グレブナー基底,多変数多項式のgcd,平面曲線のRieman-Roch空間に対する基底、因数分解などを始めとする各種処理のための,大規模で十分に枯れたライブラリを提供する. +実例として,多変数多項式の因数分解をSageからSingularへのインターフェイスを使って実行してみよう(``....`` は入力しないこと): + +:: + + sage: R1 = singular.ring(0, '(x,y)', 'dp') + sage: R1 + // characteristic : 0 + // number of vars : 2 + // block 1 : ordering dp + // : names x y + // block 2 : ordering C + sage: f = singular('9*y^8 - 9*x^2*y^7 - 18*x^3*y^6 - 18*x^5*y^6 +' + ....: '9*x^6*y^4 + 18*x^7*y^5 + 36*x^8*y^4 + 9*x^10*y^4 - 18*x^11*y^2 -' + ....: '9*x^12*y^3 - 18*x^13*y^2 + 9*x^16') + +:math:`f` を定義できたので,その内容を表示してから因数分解を試みる. + +:: + + sage: f + 9*x^16-18*x^13*y^2-9*x^12*y^3+9*x^10*y^4-18*x^11*y^2+36*x^8*y^4+18*x^7*y^5-18*x^5*y^6+9*x^6*y^4-18*x^3*y^6-9*x^2*y^7+9*y^8 + sage: f.parent() + Singular + sage: F = f.factorize(); F + [1]: + _[1]=9 + _[2]=x^6-2*x^3*y^2-x^2*y^3+y^4 + _[3]=-x^5+y^2 + [2]: + 1,1,2 + sage: F[1][2] + x^6-2*x^3*y^2-x^2*y^3+y^4 + + +:ref:`section-gap` 節におけるGAPの実行例のように,Singularインターフェイスを直には使わず上の因数分解を行なうこともできる(Sageが実際の計算に裏でSingularインターフェイスを使っていることに変わりない). +以下の例でも, ``....`` は入力しないこと: + +:: + + sage: x, y = QQ['x, y'].gens() + sage: f = (9*y^8 - 9*x^2*y^7 - 18*x^3*y^6 - 18*x^5*y^6 + 9*x^6*y^4 + ....: + 18*x^7*y^5 + 36*x^8*y^4 + 9*x^10*y^4 - 18*x^11*y^2 - 9*x^12*y^3 + ....: - 18*x^13*y^2 + 9*x^16) + sage: factor(f) + (9) * (-x^5 + y^2)^2 * (x^6 - 2*x^3*y^2 - x^2*y^3 + y^4) + + + +.. _section-maxima: + +Maxima +====== + +Maximaは,LISP言語の実装の一種と共にSageに同梱されてくる. +(Maximaが標準でプロットに用いる)gnuplotパッケージは,Sageでも非標準パッケージとして公開されている. +Maximaが得意とするのは,記号処理である.Maximaは関数の微分と積分を記号的に実行し,1階の常微分方程式(ODE)と大半の線形2次ODEを解くことができるし,任意次数の線形ODEをラプラス変換することもできる. +さらにMaximaには多様な特殊関数も組込まれており,gnuplotを介したプロット機能も備えている. +(掃き出し法や固有値問題などに始まる)行列操作や行列方程式の解法を実行し,多項式方程式を解くことも可能だ. + +Sage/Maximaインターフェイスの使い方を例示するため,ここでは :math:`i,j` 要素の値が :math:`i/j` で与えられる行列を作成してみよう. +ただし :math:`i,j=1,\ldots,4` とする. + +:: + + sage: f = maxima.eval('ij_entry[i,j] := i/j') + sage: A = maxima('genmatrix(ij_entry,4,4)'); A + matrix([1,1/2,1/3,1/4],[2,1,2/3,1/2],[3,3/2,1,3/4],[4,2,4/3,1]) + sage: A.determinant() + 0 + sage: A.echelon() + matrix([1,1/2,1/3,1/4],[0,0,0,0],[0,0,0,0],[0,0,0,0]) + sage: A.eigenvalues() + [[0,4],[3,1]] + sage: A.eigenvectors() + [[[0,4],[3,1]],[[[1,0,0,-4],[0,1,0,-2],[0,0,1,-4/3]],[[1,2,3,4]]]] + + +使用例をもう一つ示す: + +:: + + sage: A = maxima("matrix ([1, 0, 0], [1, -1, 0], [1, 3, -2])") + sage: eigA = A.eigenvectors() + sage: V = VectorSpace(QQ,3) + sage: eigA + [[[-2,-1,1],[1,1,1]],[[[0,0,1]],[[0,1,3]],[[1,1/2,5/6]]]] + sage: v1 = V(sage_eval(repr(eigA[1][0][0]))); lambda1 = eigA[0][0][0] + sage: v2 = V(sage_eval(repr(eigA[1][1][0]))); lambda2 = eigA[0][0][1] + sage: v3 = V(sage_eval(repr(eigA[1][2][0]))); lambda3 = eigA[0][0][2] + + sage: M = MatrixSpace(QQ,3,3) + sage: AA = M([[1,0,0],[1, - 1,0],[1,3, - 2]]) + sage: b1 = v1.base_ring() + sage: AA*v1 == b1(lambda1)*v1 + True + sage: b2 = v2.base_ring() + sage: AA*v2 == b2(lambda2)*v2 + True + sage: b3 = v3.base_ring() + sage: AA*v3 == b3(lambda3)*v3 + True + +最後に,Sage経由で ``openmath`` を使ってプロットを行なう際の手順を紹介する. +以下の例題の多くは,Maximaのレファレンスマニュアルのものを修正したものだ. + + +関数の2次元プロットをするには( ``...`` は入力しない) + +:: + + sage: maxima.plot2d('[cos(7*x),cos(23*x)^4,sin(13*x)^3]','[x,0,1]', # not tested + ....: '[plot_format,openmath]') + +次の「ライブ」3次元プロットは,マウスで動かすことができる( ``....`` は入力しない): + +:: + + sage: maxima.plot3d ("2^(-u^2 + v^2)", "[u, -3, 3]", "[v, -2, 2]", # not tested + ....: '[plot_format, openmath]') + sage: maxima.plot3d("atan(-x^2 + y^3/4)", "[x, -4, 4]", "[y, -4, 4]", # not tested + ....: "[grid, 50, 50]",'[plot_format, openmath]') + +次に有名なメビウスの帯を3次元プロットしてみよう( ``....`` は入力しない). + +:: + + sage: maxima.plot3d("[cos(x)*(3 + y*cos(x/2)), sin(x)*(3 + y*cos(x/2)), y*sin(x/2)]", # not tested + ....: "[x, -4, 4]", "[y, -4, 4]", '[plot_format, openmath]') + +プロットの最後の例は,あの「クラインの壺」である( ``....`` は入力しない): + +:: + + sage: maxima("expr_1: 5*cos(x)*(cos(x/2)*cos(y) + sin(x/2)*sin(2*y)+ 3.0) - 10.0") + 5*cos(x)*(sin(x/2)*sin(2*y)+cos(x/2)*cos(y)+3.0)-10.0 + sage: maxima("expr_2: -5*sin(x)*(cos(x/2)*cos(y) + sin(x/2)*sin(2*y)+ 3.0)") + -5*sin(x)*(sin(x/2)*sin(2*y)+cos(x/2)*cos(y)+3.0) + sage: maxima("expr_3: 5*(-sin(x/2)*cos(y) + cos(x/2)*sin(2*y))") + 5*(cos(x/2)*sin(2*y)-sin(x/2)*cos(y)) + sage: maxima.plot3d ("[expr_1, expr_2, expr_3]", "[x, -%pi, %pi]", # not tested + ....: "[y, -%pi, %pi]", "['grid, 40, 40]", '[plot_format, openmath]') diff --git a/src/doc/ja/tutorial/introduction.rst b/src/doc/ja/tutorial/introduction.rst new file mode 100644 index 00000000000..79299043eea --- /dev/null +++ b/src/doc/ja/tutorial/introduction.rst @@ -0,0 +1,132 @@ +************ +はじめに +************ + +このチュートリアルは,3〜4時間あればじゅうぶん読み通すことができるはずだ. +HTML版とPDF版のどちらを読んでもいいし、Sageノートブックを経由することもできる(チュートリアル内容をSageから対話的に実行するには,ノートブックで ``Help``, 続けて ``Tutorial`` をクリックする). + +Sageのかなりの部分がPythonを使って実装されているものの,このチュートリアルを読むについてはPythonの予備知識はいらない. +いずれはPythonを勉強したくなるはずだが(とても面白い言語だ),そんな場合のためには [PyT]_ や [Dive]_ などの優れた教材がフリーでたくさん用意されている. +とにかく手っ取り早くSageを試してみたいだけなら、このチュートリアルがよい出発点になる. +例えばこんな具合だ: + +:: + + sage: 2 + 2 + 4 + sage: factor(-2007) + -1 * 3^2 * 223 + + sage: A = matrix(4,4, range(16)); A + [ 0 1 2 3] + [ 4 5 6 7] + [ 8 9 10 11] + [12 13 14 15] + + sage: factor(A.charpoly()) + x^2 * (x^2 - 30*x - 80) + + sage: m = matrix(ZZ,2, range(4)) + sage: m[0,0] = m[0,0] - 3 + sage: m + [-3 1] + [ 2 3] + + sage: E = EllipticCurve([1,2,3,4,5]); + sage: E + Elliptic Curve defined by y^2 + x*y + 3*y = x^3 + 2*x^2 + 4*x + 5 + over Rational Field + sage: E.anlist(10) + [0, 1, 1, 0, -1, -3, 0, -1, -3, -3, -3] + sage: E.rank() + 1 + + sage: k = 1/(sqrt(3)*I + 3/4 + sqrt(73)*5/9); k + 36/(20*sqrt(73) + 36*I*sqrt(3) + 27) + sage: N(k) + 0.165495678130644 - 0.0521492082074256*I + sage: N(k,30) # 精度は30ビット + 0.16549568 - 0.052149208*I + sage: latex(k) + \frac{36}{20 \, \sqrt{73} + 36 i \, \sqrt{3} + 27} + +.. _installation: + +インストール +============== + +まだSageをコンピュータにインストールしていないけれども何かコマンドを実行してはみたいというなら, http://www.sagenb.org 上でオンライン実行してみる手がある. + +Sageを自分のコンピュータへインストールする手順については,本家Sageウェブページ [SA]_ のドキュメンテーション部にある "Sage Installation Guide"を見てほしい. +ここではいくつかコメントしておくだけにしよう. + +#. Sageのダウンロード用ファイルは「バッテリー込み」である. + つまり、SageはPython, IPython, PARI, GAP, Singular, Maxima, NTL, GMPなどを援用して動作するが,これらは全てSageの配布ファイルに含まれているので別途インストールする必要はないということだ. + ただし、MacaulayやKASHなど一部の機能を利用するには関連するオプショナルなSageパッケージをインストールするか,少なくとも使うコンピュータに関連プログラム群がインストール済みでなくてはならない(利用できるオプショナル・パッケージの一覧を見るには, ``sage -optional`` を実行するか,あるいはSageウェブサイトの "Download"ページを見るとよい). + +#. コンパイル済みのバイナリ版Sage(Sageサイトにある)は,ソースコードより簡単かつ速やかにインストールすることができる. + ファイルを入手したら展開して ``sage`` コマンドを実行するだけで出来上がりだ. + +#. SageTeXパッケージを使いたいのならば(SageTeXはSageの処理結果をLaTeX文書に埋め込み可能にしてくれる),使用すべきTeXディストリビューションをSageTeXに教えてやる必要がある. + 設定法については, `Sage installation guide `_ 中の "Make SageTeX known to TeX" を参照してほしい(ローカルシステム上の `ここ <../../en/installation/index.html>`_ にもインストールガイドがある). + 手順はごく簡単で,環境変数を一つ設定するか,あるいはTeX配下のディレクトリにファイルを1個コピーしてやるだけである. + + +SageTeXの利用に関する解説は +``$SAGE_ROOT/local/share/texmf/tex/generic/sagetex/`` にある. +``$SAGE_ROOT`` はSageがインストールされているディレクトリで,例えば ``/opt/sage-4.2.1`` などとなっているはずだ. + + + +Sageの使いかた +================ + +Sageを使うには以下のようなやり方がある. + +- **ノートブック グラフィカル インターフェイス:** レファレンスマニュアルのノートブックに関する節,および以下の :ref:`section-notebook` 節を参照. + +- **対話的コマンドライン:** :ref:`chapter-interactive_shell` 節を参照. + +- **プログラム作成:** Sage上でインタープリタおよびコンパイラを経由してプログラムを書く(:ref:`section-loadattach` 節と :ref:`section-compile` 節を参照).さらに + +- **スクリプト作成:** Sageライブラリを利用するスタンドアロンPythonスクリプトを書く(:ref:`section-standalone` 節を参照). + + + + + +Sageの長期目標 +======================= + +- **有用性**: Sageが想定しているユーザは,数学を学ぶ学生(高校生から大学学部生まで)と教師、そして数学の専門家である. + 代数、幾何、数論、解析学、数値解析などの数学諸分野には,種々の概念や量が現われてくる. + Sageの狙いは、ユーザが数学上の概念や諸量の性質を探ったり,それらの働きを体験する手助けになるようなソフトウェアを提供することである. + Sageを使えば,各種の数学的な実験を容易に対話的に実行することができる. + +- **高速性:** 動作が高速である. + Sageは GMP, PARI, GAP, NTLなど高度に最適化された完成度の高いソフトウェアを援用しており,多くの場合きわめて高速に演算が実行される. + +- **フリーかつオープンソース:** ソースコードは自由に入手可能で,可読性が高くなければならない. + そうすればユーザはSageが行なう処理の詳細を理解することができるし,拡張も容易になる. + 数学者であれば,定理を深く理解するために証明をていねいに読むか,少なくとも証明の流れ程度は追っておくはずである. + 計算システムのユーザも同じことで,演算処理がどのように実行されるのかソースコードを読んで把握できるようであってほしい. + 論文発表する仕事の計算にSageを使っておけば,論文の読者も確実にSageとその全ソースコードを自由に利用できることになる. + Sageでは,仕事に使ったバージョンを保存しておいて再配布することすら許されているのだ. + +- **コンパイルが容易:** Sageは,Linux, OSXあるいはWindowsのユーザがソースコードから容易にコンパイル・ビルドできるようでなくてはならない. + これによりユーザはSageシステムを柔軟に修正することができる. + +- **協調性:** Sageは,PARI, GAP, Singular, Maxima, KASH, Magma, Maple,さらにMathematicaなど多くのコンピュータ代数システムとの頑健なインターフェイスを提供する. + Sageの狙いは、既存の数学ソフトウェアとの統合と拡張である. + +- **豊富な関連文書:** チュートリアル,プログラミングガイド,レファレンスマニュアル,ハウツー類が揃っている. + これには多数の具体例と数学的背景知識の解説も含まれる. + +- **拡張性:** 新しいデータ型をゼロから定義したり,既存のデータ型を利用して作り出すことができる. + さまざまな言語で書いたプログラムをシステムに組み込んで利用することも可能だ. + +- **ユーザーフレンドリー**: ユーザは使用するオブジェクトにどんな属性や機能が組込まれているかを簡単に把握し,さらに関連文書やソースコードなども容易に閲覧できなくてはならない. + 高度のユーザーサポートも提供される. + + + diff --git a/src/doc/ja/tutorial/japanesesupport.py b/src/doc/ja/tutorial/japanesesupport.py new file mode 100644 index 00000000000..6d954e3ddc9 --- /dev/null +++ b/src/doc/ja/tutorial/japanesesupport.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +import re +__RGX = re.compile(r'([^!-~])[\n\r\t]+([^!-~])') + +def trunc_whitespace(app, doctree, docname): + from docutils.nodes import Text, paragraph + if not app.config.japanesesupport_trunc_whitespace: + return + for node in doctree.traverse(Text): + if isinstance(node.parent, paragraph): + newtext = node.astext() + #↓「非ASCII」+「"\n\r\t"たち」+「非ASCII」 + # の場合だけ置換する… + newtext = __RGX.sub(r"\1\2", newtext) + #newtext = newtext.strip() + node.parent.replace(node, Text(newtext)) + +def setup(app): + app.add_config_value('japanesesupport_trunc_whitespace', True, True) + app.connect("doctree-resolved", trunc_whitespace) diff --git a/src/doc/ja/tutorial/latex.rst b/src/doc/ja/tutorial/latex.rst new file mode 100644 index 00000000000..7eb10a5272b --- /dev/null +++ b/src/doc/ja/tutorial/latex.rst @@ -0,0 +1,415 @@ +********************************* +Sage,LaTexと仲間たち +********************************* + +著者: Rob Beezer (2010-05-23) + +SageとTeX派生の組版システムLaTeXは,緊密な相乗的協働関係にある. +ここでは,最も基本的なものから始めて,かなり特殊かつ難解なものに至るまでの両者の多様な相互関係を概観する. +(このチュートリアルを初めて読む場合は,この節は飛ばしてかまわない) + + +概観 +======== + +SageはLaTeXを多種多様な形で利用している. +利用のメカニズムを理解する早道は,まず三種類の代表的な利用法のあらましを把握しておくことだろう. + +#. Sageでは全オブジェクトに対してLaTeX形式の印字表現が可能になっている. + Sageオブジェクト ``foo`` のLaTeX形式による印字表現を見たければ,SageノートブックまたはSageコマンド入力行で ``latex(foo)`` と実行する. + 出力された文字列をTeXの数式モード( 例えば一対の ``$`` で囲まれた領域 )内で処理すると十分に使える ``foo`` の表現が得られるはずだ. + これについては以下で実例を見る. + + 同じ手順によってSageをLaTeX文書の作成に活用することができる. + 目的のオブジェクトをSage上で作成あるいは計算しておいてから,そのオブジェクトを ``latex()`` 経由で表示した出力をLaTeX文書にカット/ペーストすればよい. + +#. ノートブックインターフェイスは,webブラウザ上で数式を明瞭に表示するため `MathJax `_ を使用するよう設定されている. + MathJaxはJavaScriptで書かれたオープンソースの数式表示エンジンで,現行のブラウザ全てで動作し,TeX形式で表現された数式の全てではないにしても,その大部分を表示することができる. + その目的はTeXで表わされた短い数式を正確に表示することであって,複雑な表や文書構造の表現と管理を支援するものではない. + ノートブックにおける見たところ自動的な数式のレンダリング表示は,数式オブジェクトの ``latex()`` 出力をMathJaxが動作可能なHTML形式に変換して得られたものである. + MathJaxはそれ自身のスケーラブルフォントを利用しており,方程式などTeX形式で表現されたテキストを静的なインライン画像に変換する方法より優れているといえる. + +#. Sageコマンドラインあるいはノートブック上でMathJaxでは処理しきれないほどLaTeXコードが複雑化してしまった場合は,システム上で公開運用されているLaTeXによって処理することもできる. + Sageには,Sage自体をビルドして使用するために必要な物ほとんど全てが含まれているが,TeXはその例外となっている. + 場合によっては,TeXシステムおよび関連の変換ユーティリティ群を別途インストールしなければ欲しい機能が完全には使えないこともあるかもしれない. + + + +ここで ``latex()`` 関数の基本的な用法を試してみよう. + +:: + + sage: var('z') + z + sage: latex(z^12) + z^{12} + sage: latex(integrate(z^4, z)) + \frac{1}{5} \, z^{5} + sage: latex('a string') + \text{\texttt{a{ }string}} + sage: latex(QQ) + \Bold{Q} + sage: latex(matrix(QQ, 2, 3, [[2,4,6],[-1,-1,-1]])) + \left(\begin{array}{rrr} + 2 & 4 & 6 \\ + -1 & -1 & -1 + \end{array}\right) + +ノートブック上ではMathJaxの基本機能は自動的に動作するが,ここでは ``MathJax`` クラスを使ってMathJaxの働きを少しばかり観察してみることにしよう. +``MathJax`` クラスの ``eval`` 関数はSageオブジェクトをLaTeX形式に変換し,さらにCSSの "math"クラスを呼び出すHTMLにラップしてMathJaxを起動する. + +:: + + sage: from sage.misc.latex import MathJax + sage: mj = MathJax() + sage: var('z') + z + sage: mj(z^12) + + sage: mj(QQ) + + sage: mj(ZZ[x]) + + sage: mj(integrate(z^4, z)) + + + + +基本的な使い方 +=================== + +上で概観したように,SageのLaTeX支援機能を利用する最も基本的な方法は ``latex()`` 関数を使って数学オブジェクトのLaTeX形式による印字表現を生成することである. +生成されたLaTeX表現は別仕立てのLaTeX文書に貼り付けて利用すればよい. +この方法はノートブックでもSageコマンドラインでも同じように通用する. + + +もう一つの手軽な方法に ``view()`` コマンドの使用がある. +Sageコマンドラインで ``view(foo)`` を実行すると ``foo`` のLaTeX表現が得られるから,これをLaTeX文書に取り込んで使っているシステムにインストールされたTeXで処理する. +TeXの出力を適切なビューワーで表示して出来上がりである. +使用するTeXのバージョンや,それに応じた出力とビューワーは選択することができる( :ref:`sec-custom-processing` 節を参照). + + +ノートブック上では, ``view(foo)`` コマンドはMathJaxがワークシート上でLaTeX表現を正しくレンダリングできるよう適切なHTMLとCSSの組を生成する. +ユーザーの目に映るのはSageデフォルトのASCII文字出力ではなく,きれいにフォーマットされた数式出力ということになる. +ただしSageの数学オブジェクト全てがMathJaxで表示可能なLaTeX表現をもつとは限らない. +表示できない場合にはMathJaxではなくシステム上のTeXを使って処理し,出力をワークシートに表示可能な画像へ変換することができる. +これに必要な一連の処理をどうやってコントロールするかについては :ref:`sec-custom-generation` 節で解説する. + + +ノートブックにはTeXを利用するための機能があと二つある. +一つはワークシートの最初のセルのすぐ上に並ぶ四つのドロップダウンボックスの右にある "Typeset"ボタンである. +これをチェックしておくと,以降はセルの評価結果はMathJaxで処理されて印刷品質で表示される. +ただし,この機能は遡及的に作用するわけではなく,事前に評価済みのセルについては評価し直してやる必要がある. +つまるところ "Typeset"ボタンにチェックを入れるのは,以降のセル評価出力を ``view()`` コマンドでラップして表示することを意味することになる. + + +ノートブックのTeX支援機能の二つ目は,ワークシートの注釈にTeX形式が利用可能な点である. +ワークシート上でセルの間にカーソルが移動すると青色のバーが現れるから,そこで ```` するとTinyMCEというちょっとしたワードプロセッサが起動する. +これを使えばテキスト入力を行ない,かつWSIWYGエディタ経由でHTMLとCSSを作成して書式付けすることができる. +つまりワークシート内で書式付きテキストを作成し注釈として付加することが可能なわけだ. +一方,ダラー記号(``$``)あるいはダラー記号2つ(``$$``)の間に挟まれた文字列はMathJaxによってインラインあるいはディスプレイ数式として解釈される. + + + +.. _sec-custom-generation: + + +LaTeXコード生成のカスタマイズ +================================== + +``latex()`` コマンドによるLaTeXコードの生成をカスタマイズする方法は何通りも用意されている. +ノートブックでもSageコマンドラインでも,すでに ``latex`` という名前のオブジェクトが定義済みで, ``latex.`` (ピリオド ``.`` に 注意)と入力して ``[Tab]`` キーを押せばメソッドの一覧を表示することができる. + +.. .. +.. There are several ways to customize the actual LaTeX code generated by +.. the ``latex()`` command. In the notebook and at the Sage command-line +.. there is a pre-defined object named ``latex`` which has several methods, +.. which you can list by typing ``latex.``, followed by the tab key +.. (note the period). + +ここでは ``latex.matrix_delimiters`` メソッドに注目してみよう. +このメソッドを使えば,行列を囲む記号を大丸かっこ,角かっこ,中かっこ,縦棒などに変更することができる. +何か形式上の制限があるわけではないから,好きなように組み合わせてかまわない. +LaTeXで使われるバックスラッシュには,Pythonの文字列内でエスケープするためもう1個のバックスラッシュを付ける必要があることに注意. + + +:: + + sage: A = matrix(ZZ, 2, 2, range(4)) + sage: latex(A) + \left(\begin{array}{rr} + 0 & 1 \\ + 2 & 3 + \end{array}\right) + sage: latex.matrix_delimiters(left='[', right=']') + sage: latex(A) + \left[\begin{array}{rr} + 0 & 1 \\ + 2 & 3 + \end{array}\right] + sage: latex.matrix_delimiters(left='\\{', right='\\}') + sage: latex(A) + \left\{\begin{array}{rr} + 0 & 1 \\ + 2 & 3 + \end{array}\right\} + +``latex.vector_delimiters`` メソッドも同様の機能をもつ. + + +(整数,有理数,実数など)標準的な環や体をどんな書体で表示するかは ``latex.blackboard_bold`` メソッドによって制御することができる. +デフォルトではボールド体で表示されるが,手書きの場合にやるように黒板ボールド体(重ね打ち体)を使うこともできる. +それにはSageのビルトインマクロ ``\Bold{}`` を再定義してやればよい. + + +:: + + sage: latex(QQ) + \Bold{Q} + sage: from sage.misc.latex import MathJax + sage: mj=MathJax() + sage: mj(QQ) + + sage: latex.blackboard_bold(True) + sage: mj(QQ) + + sage: latex.blackboard_bold(False) + +新しいマクロやパッケージなどを追加して,TeXの高い拡張性を利用することができる. +まず,ノートブックでMathJaxが短いTeXコードを解釈する際に使われる,自分用のマクロを追加してみよう. + + +:: + + sage: latex.extra_macros() + '' + sage: latex.add_macro("\\newcommand{\\foo}{bar}") + sage: latex.extra_macros() + '\\newcommand{\\foo}{bar}' + sage: var('x y') + (x, y) + sage: latex(x+y) + x + y + sage: from sage.misc.latex import MathJax + sage: mj=MathJax() + sage: mj(x+y) + + + +以上のようなやり方で追加したマクロは,MathJaxでは対応しきれない大規模な処理が発生してシステム上のTeXが呼ばれるような場合にも使われる. +自立したLaTeX文書のプリアンブルを定義する ``latex_extra_preamble`` コマンドの使い方は以下で具体例を示す. +これまで通りPython文字列中ではバックスラッシュが二重になっていることに注意. + + +:: + + sage: latex.extra_macros('') + sage: latex.extra_preamble('') + sage: from sage.misc.latex import latex_extra_preamble + sage: print latex_extra_preamble() + \newcommand{\ZZ}{\Bold{Z}} + ... + \newcommand{\Bold}[1]{\mathbf{#1}} + sage: latex.add_macro("\\newcommand{\\foo}{bar}") + sage: print latex_extra_preamble() + \newcommand{\ZZ}{\Bold{Z}} + ... + \newcommand{\Bold}[1]{\mathbf{#1}} + \newcommand{\foo}{bar} + + +長く複雑なLaTeX表現を処理するために,LaTeXファイルのプリアンブルでパッケージ類を付加してやることができる. +``latex.add_to_preamble`` コマンドを使えば好きなものをプリアンブルに取り込めるし, ``latex.add_package_to_preamble_if_available`` コマンドはプリアンブルへの取り込みを実行する前に,指定したパッケージが利用可能かどうかをチェックしてくれる. + + +以下の例ではプリアンブルでgeometryパッケージを取り込み,ページ上でTeXに割り当てる領域(実質的にはマージン)サイズを指定している. +例によってPython文字列のバックスラッシュは二重になっていることに注意. + + +:: + + sage: from sage.misc.latex import latex_extra_preamble + sage: latex.extra_macros('') + sage: latex.extra_preamble('') + sage: latex.add_to_preamble('\\usepackage{geometry}') + sage: latex.add_to_preamble('\\geometry{letterpaper,total={8in,10in}}') + sage: latex.extra_preamble() + '\\usepackage{geometry}\\geometry{letterpaper,total={8in,10in}}' + sage: print latex_extra_preamble() + \usepackage{geometry}\geometry{letterpaper,total={8in,10in}} + \newcommand{\ZZ}{\Bold{Z}} + ... + \newcommand{\Bold}[1]{\mathbf{#1}} + +あるパッケージの存在確認をした上で取り込みを実行することもできる. +例として,ここでは存在しないはずのパッケージのプリアンブルへの取り込みを試みてみよう. + + +:: + + sage: latex.extra_preamble('') + sage: latex.extra_preamble() + '' + sage: latex.add_to_preamble('\\usepackage{foo-bar-unchecked}') + sage: latex.extra_preamble() + '\\usepackage{foo-bar-unchecked}' + sage: latex.add_package_to_preamble_if_available('foo-bar-checked') + sage: latex.extra_preamble() + '\\usepackage{foo-bar-unchecked}' + + +.. _sec-custom-processing: + + +LaTeX処理のカスタマイズ +============================ + +システムで公開運用されているTeXシステムから好みの種類を指定して,出力形式を変更することも可能だ. +さらに,ノートブックがMathJax(簡易TeX表現用)とTeXシステム(複雑なLaTeX表現用)を使い分ける仕方を制御することができる. + + +``latex.engine()`` コマンドを使えば,複雑なLaTeX表現に遭遇した場合,システム上で運用されているTeXの実行形式 ``latex``, ``pdflatex`` または ``xelatex`` の内どれを使って処理するかを指定することができる. +``view()`` がsageコマンドラインから発行されると,実行形式 ``latex`` が選択されるから,出力はdviファイルとなりsageにおける表示にも(xdviのような)dviビューワーが使われる. +これに対し,TeX実行形式として ``pdflatex`` が設定された状態でsageコマンドラインから ``view()`` を呼ぶと,出力はPDFファイルとなってSageが表示に使用するのはシステム上のPDF表示ユーティリティ(acrobat,okular, evinceなど)となる. + + +ノートブックでは,簡単なTeX表現だからMathJaxで処理できるのか,あるいは複雑なLaTeX表現のためシステム上のTeXを援用すべきなのか,判断の手掛りを与えてやる必要がある. +手掛りとするのは文字列リストで,リストに含まれる文字列が処理対象のLaTeX表現に含まれていたらノートブックはMathJaxを飛ばしてlatex(もしくは ``latex.engine()`` コマンドで指定された実行形式)による処理を開始する. +このリストは ``latex.add_to_mathjax_avoid_list`` および ``latex.mathjax_avoid_list`` コマンドによって指定される. + + +:: + + sage: latex.mathjax_avoid_list([]) + sage: latex.mathjax_avoid_list() + [] + sage: latex.mathjax_avoid_list(['foo', 'bar']) + sage: latex.mathjax_avoid_list() + ['foo', 'bar'] + sage: latex.add_to_mathjax_avoid_list('tikzpicture') + sage: latex.mathjax_avoid_list() + ['foo', 'bar', 'tikzpicture'] + sage: latex.mathjax_avoid_list([]) + sage: latex.mathjax_avoid_list() + [] + + +ノートブック上で ``view()`` コマンド,あるいは "Typeset"ボタンがチェックされた状態でLaTeX表式が生成されたが, ``latex.mathjax_avoid_list`` によってシステム上のLaTeXが別途必要とされたとしよう. +すると,そのLaTeX表式は(``latex.engine()`` で設定した)指定のTeX実行形式によって処理される. +しかし,Sageは(コマンドライン上のように)外部ビューワーを起動して表示する代わりに,結果をセル出力としてきっちりトリミングした1個の画像に変換してからワークシートに挿入する. + + +変換が実際にどう実行されるかについては,いくつもの要因が影響している. +しかし大勢は,TeX実行形式として何が指定されているか,そして利用可能な変換ユーティリティは何かによって決まるようだ. +``dvips``, ``ps2pdf``, ``dvipng`` そして ``ImageMagick`` に含まれる ``convert`` の四種の優秀な変換ユーティリティがあれば,あらゆる状況に対処できるだろう. +目標はワークシートに挿入して表示可能なPNGファイルを生成することで,LaTeX表式からdvi形式へのLatexエンジンによる変換が成功していれば,dvipngが変換を仕上げてくれる. +LaTeX表式とLatexエンジンの生成するdvi形式にdvipngが扱えないspecial命令が入っている場合には,dvipsでポストスクリプトファイルへ変換する. +ポストスクリプトあるいは ``pdflatex`` エンジンによって出力されたPDFファイルは ``convert`` ユーティリティによってPNG形式へ変換される. +ここで紹介した二つの変換ユーティリティは ``have_dvipng()`` と ``have_convert()`` ルーチンを使って存在を確認することができる. + + +必要な変換プログラムがインストールされていれば変換は自動的に行われる. +ない場合は,何が不足していて,どこからダウンロードすればよいかを告げるエラーメッセージが表示される. + + +どうすれば複雑なLaTeX表式を処理できるのか,その具体例として次節(:ref:`sec-tkz-graph`)ではLaTeXの ``tkz-graph`` パッケージを使って高品位の連結グラフを作成する方法を解説する. +他にも例が見たければ,パッケージ化済みのテストケースが用意されている. +利用するには, 以下で見るように ``sage.misc.latex.LatexExamples`` クラスのインスタンスである ``sage.misc.latex.latex_examples`` オブジェクトをインポートしなければならない. +現在,このクラスには可換図,組合せ論グラフと結び目理論,およびpstricks関連の例題が含まれており,各々がxy, tkz-graph, xypic, pstricksパッケージの使用例になっている. +インポートを終えたら, ``latex_examples`` をタブ補完してパッケージに含まれる実例を表示してみよう. +例題各々で適正な表示に必要なパッケージや手続きが解説されている. +(プリアンブルやlatexエンジン類が全て上手く設定されていても)実際に例題を表示するには ``view()`` を使わなくてはならない. + + + +:: + + sage: from sage.misc.latex import latex_examples + sage: latex_examples.diagram() + LaTeX example for testing display of a commutative diagram produced + by xypic. + + To use, try to view this object -- it won't work. Now try + 'latex.add_to_preamble("\\usepackage[matrix,arrow,curve,cmtip]{xy}")', + and try viewing again -- it should work in the command line but not + from the notebook. In the notebook, run + 'latex.add_to_mathjax_avoid_list("xymatrix")' and try again -- you + should get a picture (a part of the diagram arising from a filtered + chain complex). + + +.. _sec-tkz-graph: + + +具体例: tkz-graphによる連結グラフの作成 +=============================================== + +``tkz-graph`` パッケージを使って高品位の連結グラフ(以降はたんに「グラフ」と呼ぶ)を作成することができる. +このパッケージは ``pgf`` ライブラリの ``tikz`` フロントエンド上に構築されている. +したがってその全構成要素がシステム運用中のTeXで利用可能でなければならないが,TeXシステムによっては必要なパッケージが全て最新になっているとは限らない. +満足すべき結果を得るには,必要なパッケージの最新版をユーザのtexmf配下にインストールする必要が生ずるかもしれない. +個人または公開利用のためのTeXのインストールや管理運用についてはこのチュートリアルの範囲を越えるが,必要な情報は簡単に見つかるはずだ. +必要なファイル類は :ref:`sec-system-wide-tex` 節に挙げられている. + + +まずは土台とするLaTeX文書のプリアンブルで必要なパッケージが付加されることを確認しておこう. +グラフ画像はdviファイルを経由すると正しく生成されないので,latexエンジンとしては ``pdflatex`` プログラムを指定するのが一番だ. +すると,Sageコマンドライン上で ``view(graphs.CompleteGraph(4))`` の実行が可能になり,完結したグラフ `K_4` の適切なPDF画像が生成されるはずだ. + + +ノートブックでも同様の動作を再現するには,グラフを表わすLaTeXコードのMathJaxによる処理を ``mathjax avoid list`` を使って抑止する必要がある. +グラフは ``tikzpicture`` 環境で取り込まれるから,文字列 ``tikzpicture`` を ``mathjax avoid list`` に入れておくとよい. +そうしてからワークシートで ``view(graphs.CompleteGraph(4))`` を実行するとpdflatexがPDFファイルを生成し,ついで ``convert`` ユーティリティが抽出したPNG画像がワークシートの出力セルに挿入されることになる. +ノートブック上でグラフをLaTeXにグラフを処理させるために必要なコマンド操作を以下に示す. +:: + + sage: from sage.graphs.graph_latex import setup_latex_preamble + sage: setup_latex_preamble() + sage: latex.extra_preamble() # random - システムで運用されているTeXに依存 + '\\usepackage{tikz}\n\\usepackage{tkz-graph}\n\\usepackage{tkz-berge}\n' + sage: latex.engine('pdflatex') + sage: latex.add_to_mathjax_avoid_list('tikzpicture') + sage: latex.mathjax_avoid_list() + ['tikz', 'tikzpicture'] + +ここまで設定してから ``view(graphs.CompleteGraph(4))`` のようなコマンドを実行すると, ``tkz-graph`` で表現されたグラフが ``pdflatex`` で処理されてノートブックに挿入される. +LaTeXに ``tkz-graph`` 経由でグラフを描画させる際には多くのオプションが影響するが,詳細はこの節の範囲を越えている. +興味があればレファレンスマニュアル "LaTeX Options for Graphs"の解説を見てほしい. + + + +.. _sec-system-wide-tex: + +TeXシステムの完全な運用 +================================ + +TeXをSageに統合して運用する際,高度な機能の多くはシステムに独立してインストールされたTeXがないと利用できない. +Linux系システムではTeXliveを基にした基本TeXパッケージを採用しているディストリビューションが多く,OSXではTeXshop,WindowsではMikTeXなどが使われている. +``convert`` ユーティリティは `ImageMagick `_ パッケージ(簡単にダウンロード可能)に含まれているし, ``dvipng``, ``ps2pdf`` と ``dvips`` の三つのプログラムはTeXパッケージに同梱されているはずだ. +また ``dvipng`` は http://sourceforge.net/projects/dvipng/ から, ``ps2pdf`` は `Ghostscript `_ の一部として入手することもできる. + + +連結グラフの作画には,PGFライブラリの新しいバージョンに加えて ``tkz-graph.sty`` と ``tkz-arith.sty`` が必要で,さらに ``tkz-berge.sty`` も必要になるかもしれない. +tkz関係のファイルは全て `Altermundus site `_ から入手することができる. + + + +外部プログラム +================= + +TeXとSageのさらなる統合運用に役立つプログラムが三つある. +その一番目がsagetexで,このTeXマクロ集を使えばLaTeX文書からSage上の多様なオブジェクトに対する演算や組み込みコマンド ``latex()`` によるフォーマットなどを実行することができる. +LaTeX文書のコンパイル処理過程で,Sageの演算やLaTeXによるフォーマット支援などの全ての機能も自動的に実行されるのである. +sagetexを使えば,例えば数学試験作成において,問題の計算そのものをSageに実行させて対応する解答を正確に維持管理することなどが可能になる. +詳細は :ref:`sec-sagetex` 節を参照してほしい. + + +tex2swsはLaTeX文書にSageコードを組込むための環境を定義している. +これをしかるべきツールで処理すると,MathJaxで適切に表示される本文と入力セル経由で動作するSageコードが組込まれたSageワークシートが出来上がる. +tex2sws環境を使えばLaTeXで教科書や記事をSageコードのブロックを含んだ形で執筆することができるが,これを変換すると数式混じりの本文は美しく整形され,かつSageコード部分が機能するSageワークシートになるわけである. +現在も開発進行中で,詳細は `tex2sws @ BitBucket `_ を見てほしい. + + +これとは逆に,sws2texはSageワークシートをLaTeX形式に変換してLaTeX関連ツールによる処理を可能にする. +現在も開発中で,詳細は `sws2tex @ BitBucket `_ を見てほしい. + diff --git a/src/doc/ja/tutorial/programming.rst b/src/doc/ja/tutorial/programming.rst new file mode 100644 index 00000000000..750856b3bd8 --- /dev/null +++ b/src/doc/ja/tutorial/programming.rst @@ -0,0 +1,798 @@ +================= + プログラミング +================= + +.. _section-loadattach: + +Sageファイルの読み込みと結合 +============================== + +ここでは,独立したファイルに保存したプログラムをSageに読み込む方法を解説する. +まず,ファイル ``example.sage`` に以下のプログラムを保存しておこう: + +.. skip + +:: + + print "Hello World" + print 2^3 + +``example.sage`` ファイルを読み込んで実行するには, ``load`` コマンド を使う. + +.. skip + +:: + + sage: load("example.sage") + Hello World + 8 + +実行中のセッションにSageファイルを結合するには, ``attach`` コマンドが使える: + +.. skip + +:: + + sage: attach("example.sage") + Hello World + 8 + +こうして ``attach`` したファイル ``example.sage`` に変更を加えてからSageで空行を入力すると( ``return`` を押す),Sageは自動的に ``example.sage`` の内容を読み込んで実行する. + +``attach`` は結合したファイルに変更が生じると自動的に読込んで実行してくれるので,デバッグの時にはとりわけ便利なコマンドになる. +これに対し ``load`` の方は,コマンド実行時に一度だけファイルを読込んで実行するだけだ. + +``example.sage`` を読込むとSageは内容をPythonプログラムへと変換し,これをPythonインタプリタが実行する. +このPythonへの変換は最小限に留められていて,整数リテラルを ``Integer()`` で,浮動小数点数リテラルを ``RealNumber()`` でラップし, ``^`` を ``**`` で,また ``R.2`` を ``R.gen(2)`` で置換するなどといった程度である. +変換された ``example.sage`` のコードは, ``example.sage`` と同じディレクトリに ``example.sage.py`` という名前のファイルとして保存されている. +この ``example.sage.py`` に入っているコードは: + +:: + + print "Hello World" + print Integer(2)**Integer(3) + +たしかに整数リテラルはラップされ, ``^`` は ``**`` に置換されている.(Pythonでは ``^`` は「排他的論理和」, ``**`` は「べき乗」を意味する.) + +こうした前処理は ``sage/misc/interpreter.py`` として実装されている. + +コードブロックが改行で開始されているなら,インデントされた複数行のコードをSageにペーストすることができる(ファイルの読込みについてはブロック開始時の改行も不要だ). +しかしSage上にコードを取り込むには,そのコードをファイルに保存してから,先に説明したように ``attach`` するのが一番安全だ. + + +.. _section-compile: + +実行形式の作成 +=============== + +数学的演算処理では実行速度がとりわけ重要になる. +Pythonは使い勝手のよい非常に高水準の言語ではあるが,計算の種類によってはスタティックに型付けされたコンパイラ言語で実行した方が処理速度が数桁も速くなる場合がある. +Sageにも,もし完全にPythonのみで実装していたら遅くて使いものにならなかったはずの機能がある. +そうした状況を切り抜けるために,SageはCythonと呼ばれるコンパイラ言語版Pythonを利用している([Cyt]_ と [Pyr]_). +Cythonは,PythonとCの両方に似ていて,リスト内包表記、条件制御、それに ``+=`` などを含むPythonの構文の大半を使うことができるし,Pythonで書いておいたモジュールをインポートすることも可能だ. +加えて,C言語の変数を自由に宣言し,かつ好きなCライブラリをコード内から直接呼び出すことができる. +Cythonで書いたコードはCに変換され,Cコンパイラでコンパイルされることになる. + +Sageコードの実行形式を作成するには,ソースファイルの拡張子を(``.sage`` ではなく) ``.spyx`` とする. +コマンドラインインターフェイスを使っている場合,実行形式をインタプリタ用コードのときと全く同じやり方で ``attach`` あるいは ``load`` することができる(今のところ,ノートブックインターフェイスではCythonで書いたコードの読込みと結合はできない). +実際のコンパイル処理は,ユーザーが特に何も指定しなくとも裏で実行されている. +作成された実行形式の共有ライブラリは ``$HOME/.sage/temp/hostname/pid/spyx`` に格納されるが,Sageの終了時に消去される. + + +Sageはspyxファイルに対しては前処理をしない. +だから例えばspyxファイル中の ``1/3`` は有理数 :math:`1/3` ではなく0と評価される.Sageライブラリに含まれる関数 ``foo`` をspyxファイルで使用したければ, ``sage.all`` をインポートしてから ``sage.all.foo`` として呼び出す必要がある. + +:: + + import sage.all + def foo(n): + return sage.all.factorial(n) + + + +他ファイル中のC関数を使う +------------------------- + +別途 \*.cファイルで定義されたC関数を使うのも簡単だ. +実例を見てみよう. +まず,同じディレクトリにファイル ``test.c`` と ``test.spyx`` を作り,内容はそれぞれ以下の通りであるとする: + + +純粋にCで書かれたプログラムは ``test.c`` : + +:: + + int add_one(int n) { + return n + 1; + } + +Cythonプログラムが入っているのが ``test.spyx``: + +:: + + cdef extern from "test.c": + int add_one(int n) + + def test(n): + return add_one(n) + +すると,次の手順でCプログラムを取り込むことができる: + +.. skip + +:: + + sage: attach("test.spyx") + Compiling (...)/test.spyx... + sage: test(10) + 11 + +Cythonソースファイルから生成されたC言語コードをコンパイルするために,さらにライブラリ ``foo`` が必要な場合は,元のCythonソースに ``clib foo`` という行を加える. +同様に、他にもCソースファイル ``bar`` が必要ならばCythonソースに取り込みを宣言する行 ``cfile bar`` を加えてコンパイルすればよい. + + +.. _section-standalone: + +スタンドアロンPython/Sageスクリプト +==================================== + +以下のスタンドアロン型Sageスクリプトは,整数や多項式などを因数分解する: + +:: + + #!/usr/bin/env sage -python + + import sys + from sage.all import * + + if len(sys.argv) != 2: + print "Usage: %s "%sys.argv[0] + print "Outputs the prime factorization of n." + sys.exit(1) + + print factor(sage_eval(sys.argv[1])) + +このスクリプトを実行するには, ``SAGE_ROOT`` をPATHに含めておかなければならない. +スクリプト名を ``factor`` とすると,実行は以下のような具合になる: + + +:: + + bash $ ./factor 2006 + 2 * 17 * 59 + bash $ ./factor "32*x^5-1" + (2*x - 1) * (16*x^4 + 8*x^3 + 4*x^2 + 2*x + 1) + + + +データ型 +========= + +Sageに現れるオブジェクトには,全て明確に定義されたデータ型が割り当てられている. +Pythonは豊富な組み込み型を備えているが,それをさらに多彩に拡張しているのがSageのライブラリだ. +Pythonの組み込み型としては,string(文字列),list(リスト),タプル(tuple),int(整数),float(浮動小数点数)などがある. +実際に型を表示してみると: + + +:: + + sage: s = "sage"; type(s) + + sage: s = 'sage'; type(s) # シングルあるいはダブル クォーテーションのどちらも使える + + sage: s = [1,2,3,4]; type(s) + + sage: s = (1,2,3,4); type(s) + + sage: s = int(2006); type(s) + + sage: s = float(2006); type(s) + + +Sageでは,さらに多様な型が加わる. +その一例がベクトル空間である: + +:: + + sage: V = VectorSpace(QQ, 1000000); V + Vector space of dimension 1000000 over Rational Field + sage: type(V) + + +この ``V`` に適用できるのは,あらかじめ定められた特定の関数に限られる. +他の数学ソフトウェアでは,そうした関数の呼び出しに「関数型記法」 ``foo(V,...)`` が用いられているようだ. +これに対し,Sageでは ``V`` の型(クラス)に付属する関数群が定められていて,JAVAやC++に見られるようなオブジェクト指向型の構文 ``V.foo(...)`` で関数呼び出しが行なわれる. +オブジェクト指向の世界では,関数が莫大な数になってもグローバルな名前空間を混乱なく運用することが可能になる. +たとえ機能の異なる関数群が同じ ``foo`` という名前を持っていたとしても,型チェックや場合分け抜きで引数の型に応じた適切な関数が自動的に呼び出されるのである. +さらに,ある関数の名前を他の意味で使い回しても関数そのものは使い続けることができる(例えば ``zeta`` を何かの変数名として使った後でも,リーマンのゼータ関数の0.5における値を求めるには ``s=.5; s.zeta()`` と入力すれば足りる). + + +:: + + sage: zeta = -1 + sage: s=.5; s.zeta() + -1.46035450880959 + + +ごく慣用化している場合については,通常の関数型記法を使うこともできる. +これは便利であると同時に,数学表現にはそもそもオブジェクト指向型記法になじまないものもあるためだ. +ここで少し例を見てみよう. + + +:: + + sage: n = 2; n.sqrt() + sqrt(2) + sage: sqrt(2) + sqrt(2) + sage: V = VectorSpace(QQ,2) + sage: V.basis() + [ + (1, 0), + (0, 1) + ] + sage: basis(V) + [ + (1, 0), + (0, 1) + ] + sage: M = MatrixSpace(GF(7), 2); M + Full MatrixSpace of 2 by 2 dense matrices over Finite Field of size 7 + sage: A = M([1,2,3,4]); A + [1 2] + [3 4] + sage: A.charpoly('x') + x^2 + 2*x + 5 + sage: charpoly(A, 'x') + x^2 + 2*x + 5 + +:math:`A` のメンバ関数を全て表示するには,タブ補完入力を利用すればよい. +:ref:`section-tabcompletion` 節で説明したように,これは ``A.`` と入力してから,キーボードの ``[tab]`` キーを押すだけのことだ. + + +リスト,タプル,シーケンス +=========================== + +リスト型には,任意の型の要素を格納することができる. +(大半のコンピュータ代数システムとは違って)CやC++などと同じように,Sageでもリストの要素番号は :math:`0` から始まる: + + +:: + + sage: v = [2, 3, 5, 'x', SymmetricGroup(3)]; v + [2, 3, 5, 'x', Symmetric group of order 3! as a permutation group] + sage: type(v) + + sage: v[0] + 2 + sage: v[2] + 5 + +リストの要素番号は,Pythonのint型でなくとも平気だ. +SageのIntegerクラスが使えるのは言うまでもない(Rationalクラスを含めて、 ``__index__`` メソッドが有効なクラスであれば何でも使える). + +.. .. +.. (When indexing into a list, it is OK if the index is +.. not a Python int!) +.. A Sage Integer (or Rational, or anything with an ``__index__`` method) +.. will work just fine. + +:: + + sage: v = [1,2,3] + sage: v[2] + 3 + sage: n = 2 # SAGEの整数 + sage: v[n] # 全く問題なし + 3 + sage: v[int(n)] # これも大丈夫 + 3 + +``range`` 関数は,Pythonのint型からなるリストを生成する(SageのIntegerではないことに注意): + +:: + + sage: range(1, 15) + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] + +この ``range`` が便利なのは,リスト内包表記を使ってリストを生成する場合だ: + + +:: + + sage: L = [factor(n) for n in range(1, 15)] + sage: print L + [1, 2, 3, 2^2, 5, 2 * 3, 7, 2^3, 3^2, 2 * 5, 11, 2^2 * 3, 13, 2 * 7] + sage: L[12] + 13 + sage: type(L[12]) + + sage: [factor(n) for n in range(1, 15) if is_odd(n)] + [1, 3, 5, 7, 3^2, 11, 13] + +以上のようなリスト内包表記を使ったリスト生成については, [PyT]_ に詳しい. + +.. .. +.. For more about how to create lists using list comprehensions, see +.. [PyT]_. + +とりわけ使い勝手が良いのが,リストのスライシングだ. +リスト ``L`` のスライシング ``L[m:n]`` は, :math:`m` 番目の要素に始まり :math:`n-1` 番目の要素で終わる部分リストを返す. +以下に例を示そう: + +:: + + sage: L = [factor(n) for n in range(1, 20)] + sage: L[4:9] + [5, 2 * 3, 7, 2^3, 3^2] + sage: print L[:4] + [1, 2, 3, 2^2] + sage: L[14:4] + [] + sage: L[14:] + [3 * 5, 2^4, 17, 2 * 3^2, 19] + +タプルはリストに似ているが,これがいったん生成された後は変更できない不変性(immutable)オブジェクトである点で異なる. + + +:: + + sage: v = (1,2,3,4); v + (1, 2, 3, 4) + sage: type(v) + + sage: v[1] = 5 + Traceback (most recent call last): + ... + TypeError: 'tuple' object does not support item assignment + +Sageで使われる第三のリスト類似データ型が,シーケンスである. +リストやタプルと違って,シーケンスはPython本体の組み込み型ではない. +デフォルトではシーケンス型は可変だが,以下の例で見るように ``Sequence`` クラスのメソッド ``set_immutable`` を使って不変性を与えることができる. +あるシーケンスの全要素は,シーケンス・ユニバースと呼ばれる共通のペアレントを持つ. + + +:: + + sage: v = Sequence([1,2,3,4/5]) + sage: v + [1, 2, 3, 4/5] + sage: type(v) + + sage: type(v[1]) + + sage: v.universe() + Rational Field + sage: v.is_immutable() + False + sage: v.set_immutable() + sage: v[0] = 3 + Traceback (most recent call last): + ... + ValueError: object is immutable; please change a copy instead. + + +シーケンスはリストから導き出すことができて,リストが使える文脈では常に利用することができる: + +:: + + sage: v = Sequence([1,2,3,4/5]) + sage: isinstance(v, list) + True + sage: list(v) + [1, 2, 3, 4/5] + sage: type(list(v)) + + + +不変性シーケンスの例としては,ベクトル空間の基底系があげられる. +基底系そのものが変わっては困るから,これは当然のことだ. + + +:: + + sage: V = QQ^3; B = V.basis(); B + [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: type(B) + + sage: B[0] = B[1] + Traceback (most recent call last): + ... + ValueError: object is immutable; please change a copy instead. + sage: B.universe() + Vector space of dimension 3 over Rational Field + + +ディクショナリ +=============== + +ディクショナリ(「連想配列」と呼ばれる場合もある)とは,文字列、数値、タプルなどのハッシュ可能なオブジェクトから任意のオブジェクトへの写像のことである. +(ハッシュ可能オブジェクトについての詳細は http://docs.python.org/tut/node7.html と http://docs.python.org/lib/typesmapping.html を参照.) + +:: + + sage: d = {1:5, 'sage':17, ZZ:GF(7)} + sage: type(d) + + sage: d.keys() + [1, 'sage', Integer Ring] + sage: d['sage'] + 17 + sage: d[ZZ] + Finite Field of size 7 + sage: d[1] + 5 + +三番目の例を見ると分るように,ディクショナリのインデックス(キー)として整数環のような複雑なオブジェクトでも使うことができる. + + +上の例のディクショナリは,同じデータを含むリストに直すことができる: + + +.. link + +:: + + sage: d.items() + [(1, 5), ('sage', 17), (Integer Ring, Finite Field of size 7)] + +ディクショナリに含まれるキーと値の対を反復に利用する場合に,よく使われるイディオムがある: + + +:: + + sage: d = {2:4, 3:9, 4:16} + sage: [a*b for a, b in d.iteritems()] + [8, 27, 64] + +最後の出力を見ると判るように,ディクショナリ内は整列されていない. + + + +集合 +===== + +Pythonには集合(set)型が組込まれている. +集合型の主な利点としては,標準的な集合演算が可能になるだけではなく,ある要素が集合に属するかどうかを極めて高速に判定する機能を備えている点があげられる. + +:: + + sage: X = set([1,19,'a']); Y = set([1,1,1, 2/3]) + sage: X # random sort order + {1, 19, 'a'} + sage: X == set(['a', 1, 1, 19]) + True + sage: Y + {2/3, 1} + sage: 'a' in X + True + sage: 'a' in Y + False + sage: X.intersection(Y) + {1} + +さらに,Sageは(Pythonの組み込み集合型を使って実装されたものも含まれる)独自の集合型を備えており,こちらにはSageに固有の付加機能がいくつか加えられている. +このSage独自の集合型を生成するには, ``Set(...)`` を使う. +例えば + +:: + + sage: X = Set([1,19,'a']); Y = Set([1,1,1, 2/3]) + sage: X # random sort order + {'a', 1, 19} + sage: X == Set(['a', 1, 1, 19]) + True + sage: Y + {1, 2/3} + sage: X.intersection(Y) + {1} + sage: print latex(Y) + \left\{1, \frac{2}{3}\right\} + sage: Set(ZZ) + Set of elements of Integer Ring + + + +イテレータ +=========== + +イテレータ(iterator)は最近になってPythonに加えられた機能で,数学指向のアプリケーション作成にはとりわけ便利なものだ. +以下で実例を見ていくことにするが,使用法の詳細は [PyT]_ を見てほしい. +まず :math:`10000000` までの非負整数の平方に関するイテレータを作ってみよう. + +:: + + sage: v = (n^2 for n in xrange(10000000)) + sage: v.next() + 0 + sage: v.next() + 1 + sage: v.next() + 4 + +今度は,素数 :math:`p` から :math:`4p+1` の形の素数に関するイテレータを作り,最初の数個を見てみることにする. + + +:: + + sage: w = (4*p + 1 for p in Primes() if is_prime(4*p+1)) + sage: w # 次の行の 0xb0853d6c はランダムに生成された16進数 + at ...> + sage: w.next() + 13 + sage: w.next() + 29 + sage: w.next() + 53 + +有限体,整数など,ある種の環にはイテレータが付随している: + +:: + + sage: [x for x in GF(7)] + [0, 1, 2, 3, 4, 5, 6] + sage: W = ((x,y) for x in ZZ for y in ZZ) + sage: W.next() + (0, 0) + sage: W.next() + (0, 1) + sage: W.next() + (0, -1) + + + +ループ,関数,制御文,比較 +============================= + +``for`` ループの一般的な使用法については,これまでに何度も実例を見ている. +Pythonでは、 ``for`` ループ構文はインデントで分節されている. +次のような具合だ: + +:: + + >>> for i in range(5): + ... print(i) + ... + 0 + 1 + 2 + 3 + 4 + + +``for`` 文はコロン ``:`` で終っており,ループの本体すなわち ``print(i)`` がインデントされていることに注意(GAPやMapleに見られる "do"や "od"はない). +Pythonでは,このインデントが重要な役割を果たしている. +以下の例のように,Sageでは ``:`` に続けて ``enter`` キーを押すと自動的にインデントが挿入される. + +:: + + sage: for i in range(5): + ....: print(i) # ここでは[Enter]を2回押す + ....: + 0 + 1 + 2 + 3 + 4 + + +代入には ``=`` 記号,比較には ``==`` 記号を使う: + +:: + + sage: for i in range(15): + ....: if gcd(i,15) == 1: + ....: print(i) + ....: + 1 + 2 + 4 + 7 + 8 + 11 + 13 + 14 + +``if`` , ``for`` および ``while`` 文のブロック構造が,インデントによって決まっているところに注目: + + +:: + + sage: def legendre(a,p): + ....: is_sqr_modp=-1 + ....: for i in range(p): + ....: if a % p == i^2 % p: + ....: is_sqr_modp=1 + ....: return is_sqr_modp + + sage: legendre(2,7) + 1 + sage: legendre(3,7) + -1 + +むろん,上のコードの目的はPython/Sageによるプログラムの特徴を例示することであって,Legendre記号の効率的な実装にはなっていない. +Sageに付属している関数 ``kronecker`` は,PARIのCライブラリを経由してLegendre記号を効率良く計算する. + + +最後に注意したいのは ``==`` , ``!=`` , ``<=`` , ``>=`` , ``>`` , ``<`` などを使った比較演算では,比較対象の量は可能ならば自動的に同じ型に変換されることだ: + + +:: + + sage: 2 < 3.1; 3.1 <= 1 + True + False + sage: 2/3 < 3/2; 3/2 < 3/1 + True + True + +比較演算は,ほとんどいかなる組合せの二つのオブジェクトに対しても行ないうると考えてよい. +対象となるオブジェクトは,全順序付け(total ordering)されなくても構わない. + + +:: + + sage: 2 < CC(3.1,1) + True + sage: 5 < VectorSpace(QQ,3) # random 出力は一定しない。 + False + +記号を含む不等号の判定には ``bool`` 関数を用いる: + +:: + + sage: x < x + 1 + x < x + 1 + sage: bool(x < x + 1) + True + + +Sageにおける異種オブジェクト間の比較演算では,まず対象オブジェクトの共通ペアレント型への正準型強制(変換)が試みられる(「型強制」(coercion)の詳細については :ref:`section-coercion` 節を参照). +比較演算は,この型強制が成功後,変換されたオブジェクトに対して実行される. +変換が不可能であれば,その二つのオブジェクトは等しくないと判定されることになる. +二つの変数が同一のオブジェクトを参照(レファレンス)しているかどうかを調べるには、 ``is`` を使う. +例えばPythonのint型 ``1`` は唯一だが,SageのInteger型 ``1`` は違う: + +:: + + sage: 1 is 2/2 + False + sage: int(1) is int(2)/int(2) + True + sage: 1 is 1 + False + sage: 1 == 2/2 + True + +以下に示す例の,前半の二つの不等式は ``False`` になる. +これは正準写像 :math:`\QQ\to \GF{5}` が存在せず, :math:`\GF{5}` 上の :math:`1` を :math:`1 \in \QQ` と比較する基準がないためである. +一方、後半の不等式は関係する正準写像 :math:`\ZZ \to \GF{5}` が存在するため ``True`` と判定される. +比較する式の左辺右辺を入れ替えても結果は変わらない. + + +:: + + sage: GF(5)(1) == QQ(1); QQ(1) == GF(5)(1) + False + False + sage: GF(5)(1) == ZZ(1); ZZ(1) == GF(5)(1) + True + True + sage: ZZ(1) == QQ(1) + True + +*警告:* Sageにおける比較演算は, :math:`1 \in \GF{5}` は :math:`1 \in \QQ` と等しいとみなすMagmaよりも制限がきつい. + +:: + + sage: magma('GF(5)!1 eq Rationals()!1') # optional - magma オプションでmagmaが必要 + true + + +プロファイリング +================ + +著者: Martin Albrecht (malb@informatik.uni-bremen.de) + + 「早計な最適化は,あらゆる災厄の源である」 -- ドナルド・クヌース + + +コードのボトルネック,つまり処理時間の大半を費している部分を洗い出さなければならない場面がある. +ボトルネックが判らなければどこを最適化すべきかの判定もできないからだ. +コードのボトルネックを特定する作業のことをプロファイリングと呼ぶが,Python/Sageにはプロファイリングの手段が何通りも用意されている. + + +一番簡単な方法は、対話型シェルで ``prun`` コマンドを実行することだ. +``prun`` は,使われた関数と各関数の処理時間の一覧を表示してくれる. +例として,有限体上の行列積演算(バージョン1.0で、まだ低速だ)をプロファイルしてみよう.その手順は: + + +:: + + sage: k,a = GF(2**8, 'a').objgen() + sage: A = Matrix(k,10,10,[k.random_element() for _ in range(10*10)]) + +.. skip + + +:: + + sage: %prun B = A*A + 32893 function calls in 1.100 CPU seconds + + Ordered by: internal time + + ncalls tottime percall cumtime percall filename:lineno(function) + 12127 0.160 0.000 0.160 0.000 :0(isinstance) + 2000 0.150 0.000 0.280 0.000 matrix.py:2235(__getitem__) + 1000 0.120 0.000 0.370 0.000 finite_field_element.py:392(__mul__) + 1903 0.120 0.000 0.200 0.000 finite_field_element.py:47(__init__) + 1900 0.090 0.000 0.220 0.000 finite_field_element.py:376(__compat) + 900 0.080 0.000 0.260 0.000 finite_field_element.py:380(__add__) + 1 0.070 0.070 1.100 1.100 matrix.py:864(__mul__) + 2105 0.070 0.000 0.070 0.000 matrix.py:282(ncols) + ... + +ここで ``ncalls`` は関数の呼出し回数, ``tottime`` はその関数が費した総時間(配下の関数群の呼出しにかかった時間は除く), ``percall`` は ``tottime`` を ``ncalls`` で割って得られる平均総消費時間, ``cumtime`` は関数本体とそれが呼出している配下の全関数双方の処理にかかった(つまり注目している関数の呼出しから開放までの)全時間, ``percall`` は ``cumtime`` を呼出し回数で割って得られる関数の平均処理時間で, ``filename:lineno(function)`` は各関数の所在情報を示している. +結局のところ, ``prun`` の表示一覧の上位にある関数ほど処理に要する負荷も大きいことが判る. +これで,どこを最適化すべきか考えやすくなるはずだ. + + +これまでと同じように, ``prun?`` と入力するとプロファイラの使い方と表示情報の解釈について詳細を見ることができる. + + +プロファイル情報をオブジェクトとして保存しておいて,後で詳しく調べてもよい: + + +.. skip + +:: + + sage: %prun -r A*A + sage: stats = _ + sage: stats? + +*注意*: ``stats = prun -r A\*A`` と実行すると,文法エラーが表示される. +これは ``prun`` がIPythonのシェルコマンドであって,通常の関数ではないためである. + + +プロファイル情報をグラフィカルに表示したければ,hotshotプロファイラ, ``hotshot2cachetree`` スクリプトと ``kcachegrind`` プログラム(Unix系のみ)などを使えばよい. +上の例と同じ作業をhotshotプロファイラで実行すると: + +.. skip + +:: + + sage: k,a = GF(2**8, 'a').objgen() + sage: A = Matrix(k,10,10,[k.random_element() for _ in range(10*10)]) + sage: import hotshot + sage: filename = "pythongrind.prof" + sage: prof = hotshot.Profile(filename, lineevents=1) + +.. skip + +:: + + sage: prof.run("A*A") + + sage: prof.close() + +得られた結果は,現ディレクトリのファイル ``pythongrind.prof`` に保存されている. +これをcachegrindフォーマットに変換すれば,内容をビジュアル化して把握することができる. + + +システムのコマンドシェルに戻り + +.. skip + +:: + + hotshot2calltree -o cachegrind.out.42 pythongrind.prof + +として変換を実行すると,出力ファイル ``cachegrind.out.42`` 内の情報を ``kcachegrind`` コマンドを使って検討することができる. +ファイルの慣例的名称 ``cachegrind.out.XX`` は残念ながら変更できない. + diff --git a/src/doc/ja/tutorial/sagetex.rst b/src/doc/ja/tutorial/sagetex.rst new file mode 100644 index 00000000000..7c36bd3e8b7 --- /dev/null +++ b/src/doc/ja/tutorial/sagetex.rst @@ -0,0 +1,233 @@ +.. _sec-sagetex: + +=============== + SageTeXを使う +=============== + +SageTeXパッケージを使うと,Sageによる処理結果をLaTeX文書に埋め込むことができるようになる. +利用するためには,まずSageTeXを「インストール」しておかなければならない(:ref:`sec-sagetex_install` 節を参照). + + +具体例 +======= + +ここでは,ごく簡単な例題を通してSageTeXの利用手順を紹介する. +完全な解説ドキュメントと例題ファイルは,ディレクトリ ``SAGE_ROOT/local/share/doc/sagetex`` に置いてある. +``SAGE_ROOT/local/share/texmf/tex/generic/sagetex`` にあるPythonスクリプトは何か役に立つ場面があるはずだ. +以上の ``SAGE_ROOT`` は,Sageをインストールしたディレクトリである. + + +SageTeXの動作を体験するために,まずSageTeXのインストール手続き(:ref:`sec-sagetex_install` 節)を実行し, +以下のテキストを ``st_example.tex`` などという名前で保存しておいてほしい: + + +.. warning:: + + このテキストを "live"ヘルプから表示すると命令未定義エラーになる. + 正常に表示するためには "static"ヘルプで表示すること. + + +.. code-block:: latex + + \documentclass{article} + \usepackage{sagetex} + + \begin{document} + + Using Sage\TeX, one can use Sage to compute things and put them into + your \LaTeX{} document. For example, there are + $\sage{number_of_partitions(1269)}$ integer partitions of $1269$. + You don't need to compute the number yourself, or even cut and paste + it from somewhere. + + Here's some Sage code: + + \begin{sageblock} + f(x) = exp(x) * sin(2*x) + \end{sageblock} + + The second derivative of $f$ is + + \[ + \frac{\mathrm{d}^{2}}{\mathrm{d}x^{2}} \sage{f(x)} = + \sage{diff(f, x, 2)(x)}. + \] + + Here's a plot of $f$ from $-1$ to $1$: + + \sageplot{plot(f, -1, 1)} + + \end{document} + +この ``st_example.tex`` をいつも通りにLaTexで処理する. +するとLaTeXは以下のような文句をつけてくるだろう: + + +:: + + Package sagetex Warning: Graphics file + sage-plots-for-st_example.tex/plot-0.eps on page 1 does not exist. Plot + command is on input line 25. + + Package sagetex Warning: There were undefined Sage formulas and/or + plots. Run Sage on st_example.sagetex.sage, and then run LaTeX on + st_example.tex again. + +注目してほしいのは,LaTeXが通常の処理で生成するファイル群に加えて, ``st_example.sage`` というファイルが出来ていることだ. +これは ``st_example.tex`` の処理畤に生成されたSageスクリプトで,上で見たLaTeX処理時のメッセージは,この ``st_example.sage`` をSageで実行せよという内容である. +その通り実行すると ``st_example.tex`` を再びLaTeXで処理せよと告げられるが,その前に新しいファイル ``st_example.sout`` が生成されていることに注意. +このファイルにはSageの演算結果がLaTeXテキストに挿入して利用可能な形式で保存されている. +プロット画像のEPSファイルを含むディレクトリも新規作成されている. +ここでLaTeX処理を実行すると,Sageの演算結果とプロットの全てがLaTeX文書に収められることになる. + + +上の処理に用いられた各マクロの内容はごく簡単に理解できる. +``sageblock`` 環境はSageコードを入力通りに組版し,ユーザーがSageを動かすとそのコードを実行する. +``\sage{foo}`` とすると, Sage上で ``latex(foo)`` を実行したのと同じ結果がLaTeX文書に挿入される. +プロット命令はやや複雑だが,もっとも単純な場合である ``\sageplot{foo}`` は ``foo.save('filename.eps')`` を実行して得られた画像を文書へ挿入する役割を果たす. + + +要するに,必要な作業は以下の三段階になる: + +- LaTeXで .texファイルを処理 +- 生成された .sageファイルをSageで実行 +- LaTeXで .texファイルを再処理 + + +作業中にLaTeX文書内のSageコマンドを変更しない場合,Sageによる処理は省略することができる. + + +SageTeXは到底以上で語り尽せるものでなく,SageとLaTeXは共に複雑で強力なツールだ. +``SAGE_ROOT/local/share/doc/sagetex`` にあるSageTeXのドキュメントを読むことを強くお勧めする. + + +.. _sec-sagetex_install: + + +TeXにSageTeXの存在を教える +=========================== + +Sageはおおむね自己完結的なシステムなのだが,正しく機能するために外部ツールの介入を要する部分があることも確かだ. +SageTeXもそうした部分の一つである. + + +SageTeXパッケージを使えばSageによる演算やプロットをLaTeX文書に埋め込むことが可能になる. +SageTeXはデフォルトでSageにインストールされるが,LaTeX文書で利用する前に,運用しているTeXシステムへSageTeXの存在を教えておかねばならない. + + + +鍵になるのは, TeXが ``sagetex.sty`` を発見できるかどうかである. +この ``sagetex.sty`` は, ``SAGE_ROOT`` をSageがビルトあるいはインストールされたディレクトリとすると, +``SAGE_ROOT/local/share/texmf/tex/generic/sagetex/`` に置かれているはずだ. +TeXが ``sagetex.sty`` を読めるようにしてやらなければ,SageTeXも動作できないのである. +これを実現するには何通りかのやり方がある. + + +- 第一の,かつ一番簡単な方法は, ``sagetex.sty`` を作成すべきLaTeX文書と同じディレクトリ内にコピーしておくことである. + TeXは組版処理の際に現ディレクトリを必ずサーチするから,この方法は常に有効だ. + + ただし,このやり方には二つのちょっとした問題点がある. + 一つ目は,このやり方では使用しているシステムが重複した ``sagetex.sty`` だらけになってしまうこと. + 二つ目の,もっと厄介な問題は,この状態でSageが更新されてSageTeXも新しいバージョンになった場合,SageTeXを構成するPythonコードやLaTeXコードとの食い違いが生じて実行時にエラーが発生しかねない点である. + + + +- 第二の方法は,環境変数 ``TEXINPUTS`` を利用することである. + bashシェルを使っているなら + + :: + + export TEXINPUTS="SAGE_ROOT/local/share/texmf//:" + + と実行すればよい.ただし ``SAGE_ROOT`` はSageのインストール先ディレクトリである. + 上の実行例では,行末にスラッシュ2個とコロンを付け忘れないでいただきたい. + 実行後は,TeXと関連ツールがSageTeXスタイルファイルを見つけられるようになる. + 上のコマンド行を ``.bashrc`` に付加して保存しておけば設定を永続させることができる. + bash以外のシェルを使っている場合, ``TEXINPUTS`` 変数を設定するためのコマンドも異なる可能性がある. + 設定法については,自分の使っているシェルのドキュメントを参照のこと. + + この方法にも瑕はある. + ユーザがTeXShopやKile,あるいはEmacs/AucTeXなどを使っている場合,必ずしも環境変数を認識してくれるとは限らないのである. + これらのアプリケーションが常にシェル環境を通してLaTeXを起動するわけではないからだ. + + インストール済みのSageを移動したり,新バージョンを旧版とは違う場所にインストールした場合, + 先に紹介したコマンドも新しい ``SAGE_ROOT`` を反映させるように変更する必要がある. + + + +- TeXに ``sagetex.sty`` の在処を教える第三の(かつ最善の)方法は,このスタイルファイルを自分のホームディレクトリのどこか都合のよい所にコピーしておくことだ. + TeXディストリビューションの多くは,パッケージを求めてホームディレクトリにある ``texmf`` ディレクトリを自動的に探索するようになっている. + このディレクトリを正確に特定するには,コマンド + + :: + + kpsewhich -var-value=TEXMFHOME + + を実行する.すると ``/home/drake/texmf`` や ``/Users/drake/Library/texmf`` などと表示されるはずだから, ``SAGE_ROOT/local/share/texmf/`` 内の ``tex/`` ディレクトリをホームディレクトリの ``texmf`` にコピーするには + + :: + + cp -R SAGE_ROOT/local/share/texmf/tex TEXMFHOME + + などとする. + もちろん, ``SAGE_ROOT`` を実際にSageをインストールしたディレクトリとするのはこれまでと同じことで, ``TEXMFHOME`` は上で見た ``kpsewhich`` コマンドの結果で置き換える. + + SageをアップグレードしたらSageTeXがうまく動かなくなったという場合は,上記の手順をもう一度繰り返すだけでSageTeXのSageとTeX関連部分が同期する. + + +.. _sagetex_installation_multiuser: + +- 複数ユーザに対応するシステムでは,以上の手続きを変更して ``sagetex.sty`` を公開運用中のTeXディレクトリにコピーすればよい. + おそらく一番賢いコピー先は ``TEXMFHOME`` ディレクトリではなく,コマンド + + :: + + kpsewhich -var-value=TEXMFLOCAL + + + の実行結果に従うことだろう.出力は ``/usr/local/share/texmf`` のようになるはずで, 上と同じように ``tex`` ディレクトリを ``TEXMFLOCAL`` ディレクトリ内にコピーする. + ついでTeXのパッケージデータベースを更新しなければならないが,これは簡単で,ルート権限で + + :: + + texhash TEXMFLOCAL + + + と実行すればよい.ただし ``TEXMFLOCAL`` を現実に合わせて変更するのは先と同じだ. + これでシステムの全ユーザはSageTeXパッケージへアクセス可能になり,Sageが利用できればSageTeXも使えるようになる. + +.. warning:: + + 肝心なのは,LaTeXが組版処理時に使う ``sagetex.sty`` ファイルと,Sageが援用するSageTeXのバージョンが一致していることである. + Sageを更新したら,あちこちに散らばった古いバージョンの ``sagetex.sty`` を面倒でも全て削除してやらなければいけない. + + SageTeX関連ファイルをホームディレクトリの ``texmf`` ディレクトリ内にコピーしてしまうこと(先に紹介した第三の方法)をお勧めするのは,この面倒があるからである. + 第三の方法にしておけば,Sage更新後もSageTeXを正常に動作させるために必要な作業はディレクトリを一つコピーするだけになる. + + + +SageTeXドキュメント +--------------------- + +厳密にはSageのインストール一式には含まれないものの,ここで +SageTeXのドキュメントが ``SAGE_ROOT/local/share/doc/sagetex/sagetex.pdf`` に配置されていることに触れておきたい. +同じディレクトリには例題ファイルと,これをLaTeXとSageTeXによってすでに組版処理した結果も用意されている(``example.tex`` と ``example.pdf`` を参照). +これらのファイルは `SageTeX bitbucket ページ `_ からダンロードすることもできる. + + + +SageTeXとTeXLive +------------------- + +混乱を招きかねない問題点の一つとして,人気あるTeXディストリビューション +`TeXLive 2009 `_ にSageTeXが含まれている現実があげられる. +これは有り難い感じがするかもしれないが,SageTeXに関して重要なのはSageとLaTeXの各要素が同期していることだ. +SageとSageTeXは共に頻繁にアップデートされるがTeXLiveはそうではないから,その「同期」のところで問題が生じる. +この文の執筆時点(2013年3月)では,多くのLinuxディストリビューションが新しいTeXLiveリリースに移行しつつある. +しかし2009リリースもしぶとく生き残っていて,実はこれがSageTeXに関するバグレポートの主要な発生源になっているのだ. + +このため *強く推奨* させていただきたいのは,SageTeXのLaTeX関連部分は以上で説明したやり方で常にSageからインストールすることである. +上記の手順に従えば,SageTeXのSageおよびLaTeX対応部分の互換性が保証されるから,動作も正常に保たれる. +SageTeXのLaTeX対応部分をTeXLiveから援用することはサポート対象外になる. + + diff --git a/src/doc/ja/tutorial/tour.rst b/src/doc/ja/tutorial/tour.rst new file mode 100644 index 00000000000..2516c94af03 --- /dev/null +++ b/src/doc/ja/tutorial/tour.rst @@ -0,0 +1,24 @@ +**************** +Sage観光ツアー +**************** + +この節では,Sageを使えばどんなことできるのか,その一端をご覧にいれる. +「…を作るにはどうやるの?」という質問全般に答える "Sage Constructions"ドキュメントには,さらに豊富な実例が用意されている. +加えて "Sage Reference Manual"では,数千の具体例が見つかるはずだ.Sageノートブックの ``Help`` リンクをクリックすれば、このツアーの内容を対話的に確認することもできる. + +(このチュートリアルをSageノートブックから閲覧している場合,入力セルの内容を実行するには ``shift-enter`` と押せばよい. ``shift-enter`` を押して実行する前に入力セルの中身を変更することもで きる.Macマシンでは, ``shift-enter`` ではなく ``shift-return`` を押すことになるかもしれない.) + +.. toctree:: + + tour_assignment + tour_help + tour_algebra + tour_plotting + tour_functions + tour_rings + tour_linalg + tour_polynomial + tour_coercion + tour_groups + tour_numtheory + tour_advanced diff --git a/src/doc/ja/tutorial/tour_advanced.rst b/src/doc/ja/tutorial/tour_advanced.rst new file mode 100644 index 00000000000..ffa2b4a29e5 --- /dev/null +++ b/src/doc/ja/tutorial/tour_advanced.rst @@ -0,0 +1,527 @@ + +より進んだ数学 +============================== + + +代数幾何 +------------------ + +Sageでは,任意の代数多様体を定義することができるが,その非自明な機能は :math:`\QQ` 上の環あるいは有限体でしか使えない場合がある. +例として,2本のアフィン平面曲線の和を取り,ついで元の曲線を和の既約成分として分離してみよう. + + +:: + + sage: x, y = AffineSpace(2, QQ, 'xy').gens() + sage: C2 = Curve(x^2 + y^2 - 1) + sage: C3 = Curve(x^3 + y^3 - 1) + sage: D = C2 + C3 + sage: D + Affine Curve over Rational Field defined by + x^5 + x^3*y^2 + x^2*y^3 + y^5 - x^3 - y^3 - x^2 - y^2 + 1 + sage: D.irreducible_components() + [ + Closed subscheme of Affine Space of dimension 2 over Rational Field defined by: + x^2 + y^2 - 1, + Closed subscheme of Affine Space of dimension 2 over Rational Field defined by: + x^3 + y^3 - 1 + ] + +以上の2本の曲線の交わりを取れば,全ての交点を求めてその既約成分を計算することもできる. + + +.. link + +:: + + sage: V = C2.intersection(C3) + sage: V.irreducible_components() + [ + Closed subscheme of Affine Space of dimension 2 over Rational Field defined by: + y - 1, + x, + Closed subscheme of Affine Space of dimension 2 over Rational Field defined by: + y, + x - 1, + Closed subscheme of Affine Space of dimension 2 over Rational Field defined by: + x + y + 2, + 2*y^2 + 4*y + 3 + ] + +というわけで,点 :math:`(1,0)` および :math:`(0,1)` が双方の曲線上にあるのはすぐ見てとることができるし, +:math:`y` 成分が :math:`2y^2 + 4y + 3=0` を満足する(2次の)点についても同じことだ. + + +Sageでは,3次元射影空間における捻れ3次曲線のトーリック・イデアルを計算することができる: + + +:: + + sage: R. = PolynomialRing(QQ, 4) + sage: I = ideal(b^2-a*c, c^2-b*d, a*d-b*c) + sage: F = I.groebner_fan(); F + Groebner fan of the ideal: + Ideal (b^2 - a*c, c^2 - b*d, -b*c + a*d) of Multivariate Polynomial Ring + in a, b, c, d over Rational Field + sage: F.reduced_groebner_bases () + [[-c^2 + b*d, -b*c + a*d, -b^2 + a*c], + [-c^2 + b*d, b^2 - a*c, -b*c + a*d], + [-c^2 + b*d, b*c - a*d, b^2 - a*c, -c^3 + a*d^2], + [c^3 - a*d^2, -c^2 + b*d, b*c - a*d, b^2 - a*c], + [c^2 - b*d, -b*c + a*d, -b^2 + a*c], + [c^2 - b*d, b*c - a*d, -b^2 + a*c, -b^3 + a^2*d], + [c^2 - b*d, b*c - a*d, b^3 - a^2*d, -b^2 + a*c], + [c^2 - b*d, b*c - a*d, b^2 - a*c]] + sage: F.polyhedralfan() + Polyhedral fan in 4 dimensions of dimension 4 + + + +楕円曲線 +--------------- + +Sageの楕円曲線部門にはPARIの楕円曲線機能の大部分が取り込まれており,Cremonaの管理するオンラインデータベースに接続することもできる(これにはデータベースパッケージを追加する必要がある). +さらに、Second-descentによって楕円曲線の完全Mordell-Weil群を計算するmwrankの機能が使えるし,SEAアルゴリズムの実行や同種写像全ての計算なども可能だ. +:math:`\QQ` 上の曲線群を扱うためのコードは大幅に更新され,Denis Simonによる代数的降下法ソフトウェアも取り込まれている. + + +楕円曲線を生成するコマンド ``EllipticCurve`` には,さまざまな書法がある: + + +- EllipticCurve([:math:`a_1`, :math:`a_2`, :math:`a_3`, :math:`a_4`, :math:`a_6` ]): + 楕円曲線 + + .. math:: y^2+a_1xy+a_3y=x^3+a_2x^2+a_4x+a_6, + + を生成する. + ただし :math:`a_i` は :math:`a_1` のペアレントクラスに合わせて型強制される. + 全ての :math:`a_i` がペアレント :math:`\ZZ` を持つ場合, :math:`a_i` は :math:`\QQ` に型強制される. + + + +- EllipticCurve([:math:`a_4`, :math:`a_6` ]): :math:`a_1=a_2=a_3=0` となる以外は上と同じ. + + +- EllipticCurve(ラベル): Cremonaの(新しい)分類ラベルを指定して,Cremonaデータベースに登録された楕円曲線を生成する. + ラベルは ``"11a"`` や ``"37b2"`` といった文字列で,(以前のラベルと混同しないように)小文字でなければならない. + + +- EllipticCurve(j): :math:`j` -不変量 :math:`j` を持つ楕円曲線を生成する. + + +- EllipticCurve(R,[:math:`a_1`, :math:`a_2`, :math:`a_3`, :math:`a_4`, :math:`a_6` ]): + 最初と同じように :math:`a_i` を指定して環 :math:`R` 上の楕円曲線を生成する. + + +以上の各コンストラクタを実際に動かしてみよう: + + +:: + + sage: EllipticCurve([0,0,1,-1,0]) + Elliptic Curve defined by y^2 + y = x^3 - x over Rational Field + + sage: EllipticCurve([GF(5)(0),0,1,-1,0]) + Elliptic Curve defined by y^2 + y = x^3 + 4*x over Finite Field of size 5 + + sage: EllipticCurve([1,2]) + Elliptic Curve defined by y^2 = x^3 + x + 2 over Rational Field + + sage: EllipticCurve('37a') + Elliptic Curve defined by y^2 + y = x^3 - x over Rational Field + + sage: EllipticCurve_from_j(1) + Elliptic Curve defined by y^2 + x*y = x^3 + 36*x + 3455 over Rational Field + + sage: EllipticCurve(GF(5), [0,0,1,-1,0]) + Elliptic Curve defined by y^2 + y = x^3 + 4*x over Finite Field of size 5 + +点 :math:`(0,0)` は、 :math:`y^2 + y = x^3 - x` で定義される楕円曲線 :math:`E` 上にある. +Sageを使ってこの点を生成するには, ``E([0,0])`` と入力する. +Sageは,そうした楕円曲線上に点を付け加えていくことができる(楕円曲線は,無限遠点が零元、同一曲線上の3点を加えると0となる加法群としての構造を備えている): + +:: + + sage: E = EllipticCurve([0,0,1,-1,0]) + sage: E + Elliptic Curve defined by y^2 + y = x^3 - x over Rational Field + sage: P = E([0,0]) + sage: P + P + (1 : 0 : 1) + sage: 10*P + (161/16 : -2065/64 : 1) + sage: 20*P + (683916417/264517696 : -18784454671297/4302115807744 : 1) + sage: E.conductor() + 37 + +複素数体上の楕円曲線は, :math:`j` -不変量によって記述される. +Sageでは, :math:`j` -不変量を以下のようにして計算する: + +:: + + sage: E = EllipticCurve([0,0,0,-4,2]); E + Elliptic Curve defined by y^2 = x^3 - 4*x + 2 over Rational Field + sage: E.conductor() + 2368 + sage: E.j_invariant() + 110592/37 + +:math:`E` と同じ :math:`j` -不変量を指定して楕円曲線を作っても,それが :math:`E` と同型になるとは限らない. +次の例でも,2つの曲線は導手(conductor)が異なるため同型にならない. + + +:: + + sage: F = EllipticCurve_from_j(110592/37) + sage: F.conductor() + 37 + +しかし, :math:`F` を2で捻ったツイスト(twist)は同型の曲線になる. + + +.. link + +:: + + sage: G = F.quadratic_twist(2); G + Elliptic Curve defined by y^2 = x^3 - 4*x + 2 over Rational Field + sage: G.conductor() + 2368 + sage: G.j_invariant() + 110592/37 + +楕円曲線に随伴する :math:`L` -級数,あるいはモジュラー形式 :math:`\sum_{n=0}^\infty a_nq^n` の係数 :math:`a_n` を求めることもできる. +計算にはPARIのC-ライブラリを援用している: + +:: + + sage: E = EllipticCurve([0,0,1,-1,0]) + sage: print E.anlist(30) + [0, 1, -2, -3, 2, -2, 6, -1, 0, 6, 4, -5, -6, -2, 2, 6, -4, 0, -12, 0, -4, + 3, 10, 2, 0, -1, 4, -9, -2, 6, -12] + sage: v = E.anlist(10000) + +:math:`a_n` を :math:`n\leq 10^5` の全てについて計算しても1秒ほどしかかからない: + + +.. skip + +:: + + sage: %time v = E.anlist(100000) + CPU times: user 0.98 s, sys: 0.06 s, total: 1.04 s + Wall time: 1.06 + + +楕円曲線を,対応するCremonaの分類ラベルを指定して生成する方法もある. +そうすると,目的の楕円曲線がその階数,玉河数,単数基準(regulator)などの情報と共にプレロードされる: + + +:: + + sage: E = EllipticCurve("37b2") + sage: E + Elliptic Curve defined by y^2 + y = x^3 + x^2 - 1873*x - 31833 over Rational + Field + sage: E = EllipticCurve("389a") + sage: E + Elliptic Curve defined by y^2 + y = x^3 + x^2 - 2*x over Rational Field + sage: E.rank() + 2 + sage: E = EllipticCurve("5077a") + sage: E.rank() + 3 + +Cremonaのデータベースへ直接にアクセスすることも可能だ. + + +:: + + sage: db = sage.databases.cremona.CremonaDatabase() + sage: db.curves(37) + {'a1': [[0, 0, 1, -1, 0], 1, 1], 'b1': [[0, 1, 1, -23, -50], 0, 3]} + sage: db.allcurves(37) + {'a1': [[0, 0, 1, -1, 0], 1, 1], + 'b1': [[0, 1, 1, -23, -50], 0, 3], + 'b2': [[0, 1, 1, -1873, -31833], 0, 1], + 'b3': [[0, 1, 1, -3, 1], 0, 3]} + + +この方法でデータベースから引き出されるデータは,むろん ``EllipticCurve`` 型のオブジェクトにはならない. +複数のフィールドから構成されたデータベースのレコードであるにすぎない. +デフォルトでSageに付属しているのは,導手が :math:`\leq 10000` の楕円曲線の情報要約からなる,Cremonaのデータベースの小型版である. +オプションで大型版のデータベースも用意されていて,こちらは導手が :math:`120000` までの全ての楕円曲線群の詳細情報を含む(2005年10月時点). +さらに、Sage用の大規模版データベースパッケージ(2GB)では,Stein-Watkinsデータベース上の数千万種の楕円曲線を利用することができる. + + + +ディリクレ指標 +-------------------- + +ディリクレ指標とは, +環 :math:`R` に対する準同型写像 :math:`(\ZZ/N\ZZ)^* \to R^*` を, :math:`\gcd(N,x)>1` なる整数 :math:`x` を0と置くことによって写像 +:math:`\ZZ \to R` へ拡張したものである. + + +:: + + sage: G = DirichletGroup(12) + sage: G.list() + [Dirichlet character modulo 12 of conductor 1 mapping 7 |--> 1, 5 |--> 1, + Dirichlet character modulo 12 of conductor 4 mapping 7 |--> -1, 5 |--> 1, + Dirichlet character modulo 12 of conductor 3 mapping 7 |--> 1, 5 |--> -1, + Dirichlet character modulo 12 of conductor 12 mapping 7 |--> -1, 5 |--> -1] + sage: G.gens() + (Dirichlet character modulo 12 of conductor 4 mapping 7 |--> -1, 5 |--> 1, + Dirichlet character modulo 12 of conductor 3 mapping 7 |--> 1, 5 |--> -1) + sage: len(G) + 4 + +ディリクレ群を作成したので、次にその元を一つ取って演算に使ってみよう. + +.. link + +:: + + sage: G = DirichletGroup(21) + sage: chi = G.1; chi + Dirichlet character modulo 21 of conductor 7 mapping 8 |--> 1, 10 |--> zeta6 + sage: chi.values() + [0, 1, zeta6 - 1, 0, -zeta6, -zeta6 + 1, 0, 0, 1, 0, zeta6, -zeta6, 0, -1, + 0, 0, zeta6 - 1, zeta6, 0, -zeta6 + 1, -1] + sage: chi.conductor() + 7 + sage: chi.modulus() + 21 + sage: chi.order() + 6 + sage: chi(19) + -zeta6 + 1 + sage: chi(40) + -zeta6 + 1 + +この指標に対してガロワ群 :math:`\text{Gal}(\QQ(\zeta_N)/\QQ)` がどう振る舞うか計算したり,法(modulus)の因数分解に相当する直積分解を実行することも可能だ. + +.. link + +:: + + sage: chi.galois_orbit() + [Dirichlet character modulo 21 of conductor 7 mapping 8 |--> 1, 10 |--> zeta6, + Dirichlet character modulo 21 of conductor 7 mapping 8 |--> 1, 10 |--> -zeta6 + 1] + + sage: go = G.galois_orbits() + sage: [len(orbit) for orbit in go] + [1, 2, 2, 1, 1, 2, 2, 1] + + sage: G.decomposition() + [ + Group of Dirichlet characters of modulus 3 over Cyclotomic Field of order + 6 and degree 2, + Group of Dirichlet characters of modulus 7 over Cyclotomic Field of order + 6 and degree 2 + ] + +次に,mod 20,ただし値が :math:`\QQ(i)` 上に収まるディリクレ指標の群を作成する: + +:: + + sage: K. = NumberField(x^2+1) + sage: G = DirichletGroup(20,K) + sage: G + Group of Dirichlet characters of modulus 20 over Number Field in i with defining polynomial x^2 + 1 + + +ついで, ``G`` の不変量をいくつか計算してみよう: + +.. link + +:: + + sage: G.gens() + (Dirichlet character modulo 20 of conductor 4 mapping 11 |--> -1, 17 |--> 1, + Dirichlet character modulo 20 of conductor 5 mapping 11 |--> 1, 17 |--> i) + + sage: G.unit_gens() + (11, 17) + sage: G.zeta() + i + sage: G.zeta_order() + 4 + +以下の例では、数体上でディリクレ指標を生成する.1の累乗根については、 ``DirichletGroup`` の3番目の引数として明示的に指定している. + +:: + + sage: x = polygen(QQ, 'x') + sage: K = NumberField(x^4 + 1, 'a'); a = K.0 + sage: b = K.gen(); a == b + True + sage: K + Number Field in a with defining polynomial x^4 + 1 + sage: G = DirichletGroup(5, K, a); G + Group of Dirichlet characters of modulus 5 over Number Field in a with + defining polynomial x^4 + 1 + sage: chi = G.0; chi + Dirichlet character modulo 5 of conductor 5 mapping 2 |--> a^2 + sage: [(chi^i)(2) for i in range(4)] + [1, a^2, -1, -a^2] + + +ここで ``NumberField(x^4 + 1, 'a')`` と指定したのは,Sageに記号 `a` を使って ``K`` の内容(`a` で生成される数体上の多項式 :math:`x^4 + 1`)を表示させるためである. +その時点で記号名 `a` はいったん未定義になるが、 ``a = K.0`` (``a = K.gen()`` としても同じ)が実行されると記号 `a` は多項式 :math:`x^4+1` の根を表すようになる. + + + + +モジュラー形式 +----------------- + +Sageを使ってモジュラー空間の次元,モジュラー・シンポルの空間,Hecke演算子、素因数分解などを含むモジュラー形式に関連した計算を実行することができる. + +モジュラー形式が張る空間の次元を求める関数が数種類用意されている. +例えば + + +:: + + sage: dimension_cusp_forms(Gamma0(11),2) + 1 + sage: dimension_cusp_forms(Gamma0(1),12) + 1 + sage: dimension_cusp_forms(Gamma1(389),2) + 6112 + +次に、レベル :math:`1` ,ウェイト :math:`12` のモジュラー・シンボル空間上でHecke演算子を計算してみよう. + + +:: + + sage: M = ModularSymbols(1,12) + sage: M.basis() + ([X^8*Y^2,(0,0)], [X^9*Y,(0,0)], [X^10,(0,0)]) + sage: t2 = M.T(2) + sage: t2 + Hecke operator T_2 on Modular Symbols space of dimension 3 for Gamma_0(1) + of weight 12 with sign 0 over Rational Field + sage: t2.matrix() + [ -24 0 0] + [ 0 -24 0] + [4860 0 2049] + sage: f = t2.charpoly('x'); f + x^3 - 2001*x^2 - 97776*x - 1180224 + sage: factor(f) + (x - 2049) * (x + 24)^2 + sage: M.T(11).charpoly('x').factor() + (x - 285311670612) * (x - 534612)^2 + +:math:`\Gamma_0(N)` と :math:`\Gamma_1(N)` の空間を生成することもできる. + + +:: + + sage: ModularSymbols(11,2) + Modular Symbols space of dimension 3 for Gamma_0(11) of weight 2 with sign + 0 over Rational Field + sage: ModularSymbols(Gamma1(11),2) + Modular Symbols space of dimension 11 for Gamma_1(11) of weight 2 with + sign 0 and over Rational Field + +特性多項式と :math:`q` -展開を計算してみよう. + + +:: + + sage: M = ModularSymbols(Gamma1(11),2) + sage: M.T(2).charpoly('x') + x^11 - 8*x^10 + 20*x^9 + 10*x^8 - 145*x^7 + 229*x^6 + 58*x^5 - 360*x^4 + + 70*x^3 - 515*x^2 + 1804*x - 1452 + sage: M.T(2).charpoly('x').factor() + (x - 3) * (x + 2)^2 * (x^4 - 7*x^3 + 19*x^2 - 23*x + 11) + * (x^4 - 2*x^3 + 4*x^2 + 2*x + 11) + sage: S = M.cuspidal_submodule() + sage: S.T(2).matrix() + [-2 0] + [ 0 -2] + sage: S.q_expansion_basis(10) + [ + q - 2*q^2 - q^3 + 2*q^4 + q^5 + 2*q^6 - 2*q^7 - 2*q^9 + O(q^10) + ] + +モジュラー・シンボルの空間を,指標を指定して生成することも可能だ. + +:: + + sage: G = DirichletGroup(13) + sage: e = G.0^2 + sage: M = ModularSymbols(e,2); M + Modular Symbols space of dimension 4 and level 13, weight 2, character + [zeta6], sign 0, over Cyclotomic Field of order 6 and degree 2 + sage: M.T(2).charpoly('x').factor() + (x - zeta6 - 2) * (x - 2*zeta6 - 1) * (x + zeta6 + 1)^2 + sage: S = M.cuspidal_submodule(); S + Modular Symbols subspace of dimension 2 of Modular Symbols space of + dimension 4 and level 13, weight 2, character [zeta6], sign 0, over + Cyclotomic Field of order 6 and degree 2 + sage: S.T(2).charpoly('x').factor() + (x + zeta6 + 1)^2 + sage: S.q_expansion_basis(10) + [ + q + (-zeta6 - 1)*q^2 + (2*zeta6 - 2)*q^3 + zeta6*q^4 + (-2*zeta6 + 1)*q^5 + + (-2*zeta6 + 4)*q^6 + (2*zeta6 - 1)*q^8 - zeta6*q^9 + O(q^10) + ] + +以下の例では,モジュラー形式によって張られる空間に対するHecke演算子の作用を,Sageでどうやって計算するかを示す. + + +:: + + sage: T = ModularForms(Gamma0(11),2) + sage: T + Modular Forms space of dimension 2 for Congruence Subgroup Gamma0(11) of + weight 2 over Rational Field + sage: T.degree() + 2 + sage: T.level() + 11 + sage: T.group() + Congruence Subgroup Gamma0(11) + sage: T.dimension() + 2 + sage: T.cuspidal_subspace() + Cuspidal subspace of dimension 1 of Modular Forms space of dimension 2 for + Congruence Subgroup Gamma0(11) of weight 2 over Rational Field + sage: T.eisenstein_subspace() + Eisenstein subspace of dimension 1 of Modular Forms space of dimension 2 + for Congruence Subgroup Gamma0(11) of weight 2 over Rational Field + sage: M = ModularSymbols(11); M + Modular Symbols space of dimension 3 for Gamma_0(11) of weight 2 with sign + 0 over Rational Field + sage: M.weight() + 2 + sage: M.basis() + ((1,0), (1,8), (1,9)) + sage: M.sign() + 0 + +:math:`T_p` は通常のHecke演算子( :math:`p` は素数)を表す. +Hecke演算子 :math:`T_2` , :math:`T_3` , :math:`T_5` はモジュラー・シンボル空間にどんな作用を及ぼすのだろうか? + + +.. link + +:: + + sage: M.T(2).matrix() + [ 3 0 -1] + [ 0 -2 0] + [ 0 0 -2] + sage: M.T(3).matrix() + [ 4 0 -1] + [ 0 -1 0] + [ 0 0 -1] + sage: M.T(5).matrix() + [ 6 0 -1] + [ 0 1 0] + [ 0 0 1] diff --git a/src/doc/ja/tutorial/tour_algebra.rst b/src/doc/ja/tutorial/tour_algebra.rst new file mode 100644 index 00000000000..00518832a09 --- /dev/null +++ b/src/doc/ja/tutorial/tour_algebra.rst @@ -0,0 +1,409 @@ + +代数と微積分の基本 +========================== + +Sageでは,初等的な代数と微積分に関連した多様な演算を実行することができる. +例として,方程式の解を求める,微分や積分を計算する,ラプラス変換の実行などがあげられる. +`Sage Constructions `_ には,さらに多様な具体例が盛られている. + + + +方程式を解く +----------------- + + +方程式を解析的に解く +~~~~~~~~~~~~~~~~~~~~~~~~~ + +``solve`` 関数を使って方程式の解を求めることができる. +これを使うには、まず変数を定義し、ついで対象とする方程式(または方程式系)と解くべき変数を ``solve`` の引数として指定する: + + +:: + + sage: x = var('x') + sage: solve(x^2 + 3*x + 2, x) + [x == -2, x == -1] + +解くべき変数を変更して,解を他の変数で表わすこともできる: + + +:: + + sage: x, b, c = var('x b c') + sage: solve([x^2 + b*x + c == 0],x) + [x == -1/2*b - 1/2*sqrt(b^2 - 4*c), x == -1/2*b + 1/2*sqrt(b^2 - 4*c)] + + +多変数の方程式を解くことも可能だ: + +:: + + sage: x, y = var('x, y') + sage: solve([x+y==6, x-y==4], x, y) + [[x == 5, y == 1]] + + +次のJason Groutによる例題では、Sageを使って連立非線形方程式を解く.まず,この連立方程式の解を記号的に求めてみよう: + + +:: + + sage: var('x y p q') + (x, y, p, q) + sage: eq1 = p+q==9 + sage: eq2 = q*y+p*x==-6 + sage: eq3 = q*y^2+p*x^2==24 + sage: solve([eq1,eq2,eq3,p==1],p,q,x,y) + [[p == 1, q == 8, x == -4/3*sqrt(10) - 2/3, y == 1/6*sqrt(5)*sqrt(2) - 2/3], + [p == 1, q == 8, x == 4/3*sqrt(10) - 2/3, y == -1/6*sqrt(5)*sqrt(2) - 2/3]] + + + +解の数値近似を求めるには,やり方を変えて: + +.. link + +:: + + sage: solns = solve([eq1,eq2,eq3,p==1],p,q,x,y, solution_dict=True) + sage: [[s[p].n(30), s[q].n(30), s[x].n(30), s[y].n(30)] for s in solns] + [[1.0000000, 8.0000000, -4.8830369, -0.13962039], + [1.0000000, 8.0000000, 3.5497035, -1.1937129]] + + +``n`` 関数は解の数値的近似値を表示する. ``n`` の引数は数値精度を表わすビット数を指定している. + + + +方程式を数値的に解く +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +目的の方程式(または方程式系)に対し ``solve`` では厳密解を求めることができないというのは珍しいことではない. +そうした場合には ``find_root`` を使って数値解を求めることができる. +例えば,以下に示す方程式については ``solve`` は何も役に立つことを教えてくれない. + +:: + + sage: theta = var('theta') + sage: solve(cos(theta)==sin(theta), theta) + [sin(theta) == cos(theta)] + + +しかし代りに ``find_root`` を使えば, :math:`0 < \phi < \pi/2` の範囲で上の方程式の数値解を求めることができる. + + +:: + + sage: phi = var('phi') + sage: find_root(cos(phi)==sin(phi),0,pi/2) + 0.785398163397448... + + + +微分,積分,その他 +---------------------------------- + +Sageで多様な関数の微分と積分を計算することができる. +例えば :math:`\sin(u)` を :math:`u` で微分するには,以下のようにする: + +:: + + sage: u = var('u') + sage: diff(sin(u), u) + cos(u) + +:math:`\sin(x^2)` の4次微分を計算するには: + + +:: + + sage: diff(sin(x^2), x, 4) + 16*x^4*sin(x^2) - 48*x^2*cos(x^2) - 12*sin(x^2) + + +:math:`x^2+17y^2` の `x` と `y` それぞれによる偏微分を計算するには: + + +:: + + sage: x, y = var('x,y') + sage: f = x^2 + 17*y^2 + sage: f.diff(x) + 2*x + sage: f.diff(y) + 34*y + + +次は不定積分と定積分だ. :math:`\int x\sin(x^2)\, dx` と :math:`\int_0^1 \frac{x}{x^2+1}\, dx` を計算してみよう. + + +:: + + sage: integral(x*sin(x^2), x) + -1/2*cos(x^2) + sage: integral(x/(x^2+1), x, 0, 1) + 1/2*log(2) + +:math:`\frac{1}{x^2-1}` の部分分数展開を求めるには: + +:: + + sage: f = 1/((1+x)*(x-1)) + sage: f.partial_fraction(x) + -1/2/(x + 1) + 1/2/(x - 1) + + + +.. _section-systems: + +微分方程式を解く +------------------------------ + +Sageを使って常微分方程式を研究することもできる. :math:`x'+x-1=0` を解くには: +:: + + sage: t = var('t') # 変数 t を定義 + sage: x = function('x',t) # x を t の関数とする + sage: DE = diff(x, t) + x - 1 + sage: desolve(DE, [x,t]) + (_C + e^t)*e^(-t) + + +ここでSageはMaxima [Max]_ とインターフェイスしているので,その出力もこれまで見てきたSageの出力とは若干違っている. +上の結果は,上の微分方程式の一般解が :math:`x(t) = e^{-t}(e^{t}+c)` であることを示している. + +ラプラス変換を実行することができる. +:math:`t^2e^t -\sin(t)` のラプラス変換は以下のような手順を踏む: + +:: + + sage: s = var("s") + sage: t = var("t") + sage: f = t^2*exp(t) - sin(t) + sage: f.laplace(t,s) + -1/(s^2 + 1) + 2/(s - 1)^3 + + + +もう少し手間のかかる問題を考えてみよう. +左端が壁に固定された連成バネ各々の、平衡位置からの変位 + + +:: + + |------\/\/\/\/\---|mass1|----\/\/\/\/\/----|mass2| + spring1 spring2 + +は、連立2階微分方程式 + + +.. math:: + + m_1 x_1'' + (k_1+k_2) x_1 - k_2 x_2 = 0 + + m_2 x_2''+ k_2 (x_2-x_1) = 0, + +でモデル化される. +ここで :math:`m_{i}` はおもり *i* の質量, :math:`x_{i}` はそのおもり *i* の平衡位置からの変位,そして :math:`k_{i}` はバネ *i* のバネ定数である. + + +**例題:** 上の問題で各パラメータの値を :math:`m_{1}=2`, :math:`m_{2}=1`, :math:`k_{1}=4`, :math:`k_{2}=2`, :math:`x_{1}(0)=3`, :math:`x_{1}'(0)=0`, :math:`x_{2}(0)=3`, :math:`x_{2}'(0)=0` と置き,Sageを使って解いてみよう. + +**解法:** まず1番目の方程式をラプラス変換する(記号は :math:`x=x_{1}`, :math:`y=x_{2}` に変える): + +:: + + sage: de1 = maxima("2*diff(x(t),t, 2) + 6*x(t) - 2*y(t)") + sage: lde1 = de1.laplace("t","s"); lde1 + 2*(-%at('diff(x(t),t,1),t=0)+s^2*'laplace(x(t),t,s)-x(0)*s)-2*'laplace(y(t),t,s)+6*'laplace(x(t),t,s) + + +この出力は読みにくいけれども,意味しているのは + +.. math:: -2x'(0) + 2s^2 \cdot X(s) - 2sx(0) - 2Y(s) + 6X(s) = 0 + +ということだ(ここでは小文字名の関数 :math:`x(t)` のラプラス変換が大文字名の関数 :math:`X(s)` となっている). +2番目の方程式もラプラス変換してやると: + + +:: + + sage: de2 = maxima("diff(y(t),t, 2) + 2*y(t) - 2*x(t)") + sage: lde2 = de2.laplace("t","s"); lde2 + -%at('diff(y(t),t,1),t=0)+s^2*'laplace(y(t),t,s)+2*'laplace(y(t),t,s)-2*'laplace(x(t),t,s)-y(0)*s + +意味するところは + +.. math:: -Y'(0) + s^2Y(s) + 2Y(s) - 2X(s) - sy(0) = 0. + +初期条件 :math:`x(0)`, :math:`x'(0)`, :math:`y(0)` ,および :math:`y'(0)` を代入して得られる2つの方程式を `X` と `Y` について解く: + +:: + + sage: var('s X Y') + (s, X, Y) + sage: eqns = [(2*s^2+6)*X-2*Y == 6*s, -2*X +(s^2+2)*Y == 3*s] + sage: solve(eqns, X,Y) + [[X == 3*(s^3 + 3*s)/(s^4 + 5*s^2 + 4), + Y == 3*(s^3 + 5*s)/(s^4 + 5*s^2 + 4)]] + +この解の逆ラプラス変換を行なうと: + + +:: + + sage: var('s t') + (s, t) + sage: inverse_laplace((3*s^3 + 9*s)/(s^4 + 5*s^2 + 4),s,t) + cos(2*t) + 2*cos(t) + sage: inverse_laplace((3*s^3 + 15*s)/(s^4 + 5*s^2 + 4),s,t) + -cos(2*t) + 4*cos(t) + + +というわけで,求めていた解は + +.. math:: x_1(t) = \cos(2t) + 2\cos(t), \quad x_2(t) = 4\cos(t) - \cos(2t). + +これを媒介変数プロットするには + +:: + + sage: t = var('t') + sage: P = parametric_plot((cos(2*t) + 2*cos(t), 4*cos(t) - cos(2*t) ), + ....: (t, 0, 2*pi), rgbcolor=hue(0.9)) + sage: show(P) + +各成分ごとにプロットするには + + +:: + + sage: t = var('t') + sage: p1 = plot(cos(2*t) + 2*cos(t), (t,0, 2*pi), rgbcolor=hue(0.3)) + sage: p2 = plot(4*cos(t) - cos(2*t), (t,0, 2*pi), rgbcolor=hue(0.6)) + sage: show(p1 + p2) + + + +プロットについては :ref:`section-plot` 節の,もう少し詳しい説明を見てほしい. +微分方程式については [NagleEtAl2004]_ の5.5節にもっと詳しい解説がある. + + + +オイラーによる連立微分方程式の解法 +---------------------------------------------------- + +次の例では,1階および2階微分方程式に対するオイラーの解法を具体的に解説する. +手始めに1階微分方程式に対する解法の基本的アイデアを復習しておこう.初期値問題が + +.. math:: + + y'=f(x,y), \quad y(a)=c, + +のような形式で与えられており, :math:`b>a` を満足する :math:`x=b` における解の近似値を求めたいものとする. + +微分係数の定義から + +.. math:: y'(x) \approx \frac{y(x+h)-y(x)}{h}, + +ここで :math:`h>0` は与えるべき小さな量である. +この近似式と先の微分方程式を組み合わせると :math:`f(x,y(x))\approx \frac{y(x+h)-y(x)}{h}` が得られる. +これを :math:`y(x+h)` について解くと: + +.. math:: y(x+h) \approx y(x) + h\cdot f(x,y(x)). + + +(他にうまい呼び方も思いつかないので) :math:`h \cdot f(x,y(x))` を "補正項" と呼び, :math:`y(x)` を `y` の "更新前項(old)", :math:`y(x+h)` を `y` の "更新後項(new)"と呼ぶことにすると,上の近似式を + +.. math:: y_{new} \approx y_{old} + h\cdot f(x,y_{old}). + +と表わすことができる. + + +ここで `a` から `b` までの区間を `n` ステップに分割すると :math:`h=\frac{b-a}{n}` と書けるから,ここまでの作業から得られた情報を整理して以下の表のようにまとめることができる. + + +============== ======================= ===================== +:math:`x` :math:`y` :math:`h\cdot f(x,y)` +============== ======================= ===================== +:math:`a` :math:`c` :math:`h\cdot f(a,c)` +:math:`a+h` :math:`c+h\cdot f(a,c)` ... +:math:`a+2h` ... +... +:math:`b=a+nh` ??? ... +============== ======================= ===================== + + +我々の目標は,この表の空欄を上から一行づつ全て埋めていき,最終的に :math:`y(b)` のオイラー法による近似である???に到達することである. + +連立微分方程式に対する解法もアイデアは似ている. + +**例題:** :math:`z''+tz'+z=0`, :math:`z(0)=1`, :math:`z'(0)=0` を満足する :math:`t=1` における :math:`z(t)` を,4ステップのオイラー法を使って数値的に近似してみよう. + +ここでは問題の2階常微分方程式を( :math:`x=z`, :math:`y=z'` として)二つの1階微分方程式に分解してからオイラー法を適用することになる。 + +:: + + sage: t,x,y = PolynomialRing(RealField(10),3,"txy").gens() + sage: f = y; g = -x - y * t + sage: eulers_method_2x2(f,g, 0, 1, 0, 1/4, 1) + t x h*f(t,x,y) y h*g(t,x,y) + 0 1 0.00 0 -0.25 + 1/4 1.0 -0.062 -0.25 -0.23 + 1/2 0.94 -0.12 -0.48 -0.17 + 3/4 0.82 -0.16 -0.66 -0.081 + 1 0.65 -0.18 -0.74 0.022 + +したがって, :math:`z(1)\approx 0.65` が判る. + +点 :math:`(x,y)` をプロットすれば、その曲線としての概形を見ることができる. +それには関数 ``eulers_method_2x2_plot`` を使うが,その前に三つの成分(`t`, `x`, `y`)からなる引数を持つ関数 `f` と `g` を定義しておかなければならない. + +:: + + sage: f = lambda z: z[2] # f(t,x,y) = y + sage: g = lambda z: -sin(z[1]) # g(t,x,y) = -sin(x) + sage: P = eulers_method_2x2_plot(f,g, 0.0, 0.75, 0.0, 0.1, 1.0) + +この時点で, ``P`` は2系列のプロットを保持していることになる. +`x` と `t` のプロットである ``P[0]`` , および `y` と `t` のプロットである ``P[1]`` である. +これら二つをプロットするには、次のようにする: + +.. link + +:: + + sage: show(P[0] + P[1]) + +(プロットの詳細については :ref:`section-plot` 節を参照.) + + +特殊関数 +----------------- + +数種類の直交多項式と特殊関数が,PARI [GAP]_ およびMaxima [Max]_ を援用して実装されている. +詳細についてはSageレファレンスマニュアルの“Orthogonal polynomials"(直交多項式)と“Special functions"(特殊関数)を参照してほしい. + +:: + + sage: x = polygen(QQ, 'x') + sage: chebyshev_U(2,x) + 4*x^2 - 1 + sage: bessel_I(1,1).n(250) + 0.56515910399248502720769602760986330732889962162109200948029448947925564096 + sage: bessel_I(1,1).n() + 0.565159103992485 + sage: bessel_I(2,1.1).n() + 0.167089499251049 + + +ここで注意したいのは,Sageではこれらの関数群が専ら数値計算に便利なようにラップ(wrap)されている点だ. +記号処理をする場合には,以下の例のようにMaximaインターフェイスをじかに呼び出してほしい. + +:: + + sage: maxima.eval("f:bessel_y(v, w)") + 'bessel_y(v,w)' + sage: maxima.eval("diff(f,w)") + '(bessel_y(v-1,w)-bessel_y(v+1,w))/2' diff --git a/src/doc/ja/tutorial/tour_assignment.rst b/src/doc/ja/tutorial/tour_assignment.rst new file mode 100644 index 00000000000..d2c74f685d4 --- /dev/null +++ b/src/doc/ja/tutorial/tour_assignment.rst @@ -0,0 +1,107 @@ + +代入,等式と算術演算 +==================================== + +一部に些細な例外はあるが,Sageはプログラミング言語Pythonに拠っているので,Pythonの入門書があればSageを学ぶ助けになるはずだ. + +Sageでは代入に記号 ``=`` ,比較演算には ``==`` , ``<=`` , ``>=`` , ``<`` と ``>`` を用いる. + +:: + + sage: a = 5 + sage: a + 5 + sage: 2 == 2 + True + sage: 2 == 3 + False + sage: 2 < 3 + True + sage: a == 5 + True + +基本的な演算操作は全て可能だ: + +:: + + sage: 2**3 # ** は「べき乗」の意味 + 8 + sage: 2^3 # ^ は**と同じ「べき乗」の意味 (Pythonでは通用しない) + 8 + sage: 10 % 3 # 整数については % はmod, つまり余りを与える + 1 + sage: 10/4 + 5/2 + sage: 10//4 # 整数に対して // は商を返す + 2 + sage: 4 * (10 // 4) + 10 % 4 == 10 + True + sage: 3^2*4 + 2%5 + 38 + +``3^2*4 + 2%5`` のような式の値は,演算子が適用される順序に依存する. +付録にある :ref:`section-precedence` の表を見ると演算子の適用順序が判る. + +Sageでは,一般によく使われる数学関数も豊富に用意されている. +ここでは,ほんの一部しか例を示すことができないが: + +:: + + sage: sqrt(3.4) + 1.84390889145858 + sage: sin(5.135) + -0.912021158525540 + sage: sin(pi/3) + 1/2*sqrt(3) + +いちばん最後の例のように,数式の中には近似値ではなく「厳密」な値を返してくるものもある. +近似値が欲しいときは関数 ``n`` あるいはメソッド ``n`` を使う(両者とも同じ説明的な名称 ``numerical_approx`` を持つが,関数 ``N`` は ``n`` と同じものだ). +双方の精度ビット数を指定する引数 ``prec`` と有効十進桁数を指定する引数 ``digits`` はオプションで,精度53ビットがデフォルト値になる. +:: + + sage: exp(2) + e^2 + sage: n(exp(2)) + 7.38905609893065 + sage: sqrt(pi).numerical_approx() + 1.77245385090552 + sage: sin(10).n(digits=5) + -0.54402 + sage: N(sin(10),digits=10) + -0.5440211109 + sage: numerical_approx(pi, prec=200) + 3.1415926535897932384626433832795028841971693993751058209749 + +Pythonのデータはダイナミックに型付けされ,変数を通して参照される値にはデータの型情報が付随している. +いずれにせよ、Pythonの変数はそのスコープ内ではいかなる型の変数でも保持することができる: + +:: + + sage: a = 5 # aは整数 + sage: type(a) + + sage: a = 5/3 # aは有理数になった + sage: type(a) + + sage: a = 'hello' # ここでaは文字列 + sage: type(a) + + + +プログラミング言語Cでは変数がスタティックに型付けされるから振舞いはかなり異なっていて,整数(int)型として宣言された変数は同じスコープ内では整数しか保持できない. + + +Pythonで取り違えがちな点の一つは, ``0`` で始まる整数リテラルが8進数、つまり基数8の数と見なされるところである. + +:: + + sage: 011 + 9 + sage: 8 + 1 + 9 + sage: n = 011 + sage: n.str(8) # nを基数8で文字列表現 + '11' + +この点はC言語と一致している. + diff --git a/src/doc/ja/tutorial/tour_coercion.rst b/src/doc/ja/tutorial/tour_coercion.rst new file mode 100644 index 00000000000..81e7625c7f9 --- /dev/null +++ b/src/doc/ja/tutorial/tour_coercion.rst @@ -0,0 +1,362 @@ +.. -*- coding: utf-8 -*- + +.. _section-coercion: + +================================ +ペアレント,型変換および型強制 +================================ + +この節の内容はこれまでと比べるとテクニカルな感じがするかもしれない. +しかし,ペアレントと型強制の意味について理解しておかないと,Sageにおける環その他の代数構造を有効かつ効率的に利用することができないのである. + +以下で試みるのは概念の解説であって,それをどうやって実現するかまでは示すことはできない. +実装法に関するチュートリアルは `Sage thematic tutorial `_ にある. + + +元 +-------- + +ある環をPythonを使って実装する場合,その第一歩は目的の環の元 ``X`` を表すクラスを作り, ``__add__`` , ``__sub__`` , ``__mul__`` のようなダブルアンダースコア メソッド(フックメソッド)によって環の公理を保証する演算を実現することだ. + + +Pythonは(ダイナミックではあっても)強い型付けがなされる言語なので,最初のうちは環それぞれを一つのPythonクラスで実装すべきだろうと思うかもしれない. +なんと言っても,Pythonは整数については ```` ,実数については ```` といった具合に型を一つづつ備えているわけだし. +しかし,このやり方はすぐに行き詰まらないわけにはいかない. +環の種類が無限だからと言って,対応するクラスを無限に実装することはできないからだ. + +代りに,群,環,斜体(加除環),可換環,体,代数系などという順で,普遍的な代数構造の実装を目的とするクラスの階層を作りあげようとする人もいるかもしれない. + +しかし,これは明確に異なる環に属する元が同じ型を持ちうることを意味する. +:: + + sage: P. = GF(3)[] + sage: Q. = GF(4,'z')[] + sage: type(x)==type(a) + True + + +一方,数学的には同等の構造物に対して,(例えば密行列と疎行列に対するように)別々のPythonクラスが異なった形で実装されることもありうる. +:: + + sage: P. = PolynomialRing(ZZ) + sage: Q. = PolynomialRing(ZZ, sparse=True) + sage: R. = PolynomialRing(ZZ, implementation='NTL') + sage: type(a); type(b); type(c) + + + + + +以上から,解決すべき問題は二系統あることが分る. +ある二つの元が同じPythonクラス由来のインスタンスであるとすると,付随する ``__add__`` メソッドによる加算が可能になっているはずだ. +しかし,これら二つが数学的には非常に異なる環に属しているのならば,加算は不能にしておきたい. +一方,数学的に同一の環に属している元に対しては,異なる実装に由来していてもそれらの元の加算は可能であるべきだろう. +異なるPythonクラスに由来する限り,これは簡単に実現できることではない. + + +これらの問題に対する解は「型強制」(coercion)と呼ばれており,以降で解説する. + + +しかし,まず肝心なのは,全ての元が自分の帰属先を知っていることだ. +これを可能にするのが ``parent()`` メソッドである: + +.. link + +:: + + sage: a.parent(); b.parent(); c.parent() + Univariate Polynomial Ring in a over Integer Ring + Sparse Univariate Polynomial Ring in b over Integer Ring + Univariate Polynomial Ring in c over Integer Ring (using NTL) + + +ペアレントとカテゴリー +------------------------- + +Pythonが代数構造の元に対応するクラス階層を備えているように,Sageもそれらの元を含む代数構造に対応するクラス群を提供している. +Sageでは元が属する構造物のことを「ペアレント構造」(parent structure)と呼び,基底となるクラスを持つ. +そうしたクラス群は,おおむね数学的概念に沿った形で,集合,環,体などといった順の階層を形成している. + +:: + + sage: isinstance(QQ,Field) + True + sage: isinstance(QQ, Ring) + True + sage: isinstance(ZZ,Field) + False + sage: isinstance(ZZ, Ring) + True + +代数学では,同じ種類の代数構造を共有する物を,いわゆる「圏」(category)と呼ばれるものに集約して扱う. +Sageのクラス階層と圏の階層構造にはそれなりに類似が見られないでもない. +しかし,Pythonクラスについては圏との類似はあまり強調すべきものでもなさそうだ. +いずれにせよ,数学的な意味における圏はSageでも実装されている: + + +:: + + sage: Rings() + Category of rings + sage: ZZ.category() + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces + sage: ZZ.category().is_subcategory(Rings()) + True + sage: ZZ in Rings() + True + sage: ZZ in Fields() + False + sage: QQ in Fields() + True + +Sageにおけるクラス階層は具体的実装に焦点が当てられている一方,Sageの圏フレームワークではより数学的な構造が重視されている. +圏に対する個々の実装からは独立した,包括的なメソッドとテストを構成することが可能である. + + +Sageにおけるペアレント構造は,Pythonオブジェクトとして唯一のものであると仮定されている. +例えば,いったんある基底環上の多項式環が生成元と共に作成されると,その結果は実記憶上に配置される: + +:: + + sage: RR['x','y'] is RR['x','y'] + True + + + +型とペアレント +-------------------- + +``RingElement`` 型は数学的概念としての「環の元」に完璧に対応しているわけではない. +例えば,正方行列は一つの環に属していると見なしうるにもかかわらず, ``RingElement`` のインスタンスにはならない: + + +:: + + sage: M = Matrix(ZZ,2,2); M + [0 0] + [0 0] + sage: isinstance(M, RingElement) + False + + +*ペアレント* が唯一のものであるとしても,同じSageのペアレントに由来する対等な *元* までが同一になるとは限らない. +この辺りはPythonの(全てではないにしても)整数の振舞いとは違っている. + +:: + + sage: int(1) is int(1) # Pythonのint型 + True + sage: int(-15) is int(-15) + False + sage: 1 is 1 # Sageの整数 + False + + +重要なのは,異なる環に由来する元は,一般にその型ではなくペアレントによって判別されることである: + +:: + + sage: a = GF(2)(1) + sage: b = GF(5)(1) + sage: type(a) is type(b) + True + sage: parent(a) + Finite Field of size 2 + sage: parent(b) + Finite Field of size 5 + +とういうわけで,代数学的な立場からすると **元のペアレントはその型より重要である** ことになる. + + +型変換と型強制 +-------------------------- + +場合によっては,あるペアレント構造に由来する元を,異なるペアレント構造の元へ変換することができる. +そうした変換は明示的に,あるいは暗黙的に行なうことが可能で,後者を *型強制* (coercion)と呼ぶ. + + +読者は,例えばC言語における *型変換* (type conversion)と *型強制* (type coercion)の概念をご存知かもしれない. +Sageにも *型変換* と *型強制* の考えは取り込まれている. +しかし,Sageでは主たる対象が型ではなくペアレントになっているので,Cの型変換とSageにおける変換を混同しないよう注意していただきたい. + +以下の説明はかなり簡略化されているので,詳しい解説と実装情報についてはSageレファレンスマニュアルの型強制に関する節と `thematic tutorial `_ を参照されたい. + +*異なる* 環に属する元同士の演算実行については,両極をなす二つの立場がある: + + +* 異なる環はそれぞれが異なる世界を形作っており,何であれ異なる環由来の元同士で和や積を作ることは意味をなさない. + ``1`` は整数であるのに ``1/2`` が有理数なのだから, ``1 + 1/2`` ですら意味をもちえない. + + +という立場もあるし + +* 環 ``R1`` の元 ``r1`` が何とか他の環 ``R2`` の元と見なしうるなら, ``r1`` と ``R2`` の任意の元に対する全ての算術演算が許される.単位元は全ての体と多くの環に存在し,全て等価と見なしうる. + +と考える立場もありうる. + + + +Sageが宗とするのは歩み寄りだ. +``P1`` と ``P2`` がペアレント構造で ``p1`` が ``P1`` の元であるとき, ``p1`` が ``P2`` に帰属するとする解釈をユーザが明示的に求めることがあるかもしれない. +この解釈があらゆる状況で有意であるとは限らないし, ``P1`` の全ての元に対して適用可能とも言えない. +その解釈が意味を持つかどうかはユーザの判断にかかっているのである. +我々はこうした解釈の要求を, **変換** (conversion) と呼ぶことにする: + + +:: + + sage: a = GF(2)(1) + sage: b = GF(5)(1) + sage: GF(5)(a) == b + True + sage: GF(2)(b) == a + True + + +しかし, *暗黙的* (自動的) 変換については,変換が *全面的* かつ *無矛盾* に行ないうる場合にのみ実行される. +こちらで重視されているのは数学的な厳密さである. + + +そうした暗黙的変換は **型強制** (coercion)と呼ばれる. +型強制が定義できるのならば,結果は型変換と一致しなければならない. +型強制の定義に際して満足されるべき条件は二つある: + + +#. ``P1`` から ``P2`` への型強制は構造保存写像(すなわち環準同形写像)になっていなければならない. + ``P1`` の要素が ``P2`` に写像されるだけでは不十分で,その写像は ``P1`` の代数構造を反映している必要がある. + +#. 型強制は無矛盾に構成されなければならない. + ``P3`` を3つ目のペアレント構造として, ``P1`` から ``P2`` への型強制と + ``P2`` から ``P3`` への型強制を合成すると, ``P1`` から ``P3`` への型強制に一致しなければならない. + 特に ``P1`` から ``P2`` へと ``P2`` から ``P1`` への型強制が存在する場合,この2つの変換を合成すると ``P1`` への恒等写像にならねばならない. + + +したがって, ``GF(2)`` の全ての元は ``GF(5)`` 上へ変換可能であるにも関わらず,型強制は成立しない. +``GF(2)`` と ``GF(5)`` の間には環準同形写像が存在しないからである. + + +二つ目の条件 --- 無矛盾性 --- については,いくぶん説明が難しいところがある. +多変数多項式環を例にとって説明してみたい. +実用上,変数名を維持しない型強制はまず使いものにならないはずだ.であれば: + + +:: + + sage: R1. = ZZ[] + sage: R2 = ZZ['y','x'] + sage: R2.has_coerce_map_from(R1) + True + sage: R2(x) + x + sage: R2(y) + y + + +変数名を維持する環準同形写像が定義できなければ,型強制も成立しない. +しかし,対象とする環の生成元を生成元リスト上の順序に応じて写像してやれば,型変換の方はまだ定義の可能性が残る: + +.. link + +:: + + sage: R3 = ZZ['z','x'] + sage: R3.has_coerce_map_from(R1) + False + sage: R3(x) + z + sage: R3(y) + x + +ところが,そうした順序依存の変換は型強制としては満足すべきものにならない. +``ZZ['x','y']`` から ``ZZ['y','x']`` への変数名維持写像と ``ZZ['y','x']`` から ``ZZ['a','b']`` への順序依存写像を合成すると,結果は変数名も順序も保存しない写像となって無矛盾性が破れてしまうからである. + + +型強制が成立するなら,異なる環に由来する元同士の比較や算術演算の際に利用されるはずである. +これはたしかに便利なのだが,ペアレントの違いを越えた ``==`` 型関係の適用には無理が生じがちなことには注意を要する. +``==`` は *同一の* 環上の元同士の等価関係を表わすが,これは *異なる* 環の元が関わると必ずしも有効なわけではない. +例えば, ``ZZ`` 上の ``1`` と,何か有限体上にあるとした ``1`` は等価であると見なすことができる. +というのは,整数から任意の有限体へは型強制が成り立つからだ. +しかし,一般には二つの異なる有限体環の間に型強制は成立しない. +以下を見ていただきたい: + + +.. link + +:: + + sage: GF(5)(1) == 1 + True + sage: 1 == GF(2)(1) + True + sage: GF(5)(1) == GF(2)(1) + False + sage: GF(5)(1) != GF(2)(1) + True + + +同様にして + + +.. link + +:: + + sage: R3(R1.1) == R3.1 + True + sage: R1.1 == R3.1 + False + sage: R1.1 != R3.1 + True + + +さらに無矛盾性の条件から帰結するのは,厳密な環(例えば有理数 ``QQ``)から厳密ではない環(例えば有限精度の実数 ``RR``)への型強制は成立するが,逆方向は成立しないことである. +``QQ`` から ``RR`` への型強制と ``RR`` から ``QQ`` への変換を合成すると ``QQ`` 上の恒等写像になるはずだが,これは不可能である. +と言うのは,有理数の中には,以下で示すように ``RR`` 上で問題なく扱えるものがあるからだ: + +:: + + sage: RR(1/10^200+1/10^100) == RR(1/10^100) + True + sage: 1/10^200+1/10^100 == 1/10^100 + False + +型強制が成立しない環 ``P1`` と ``P2`` の二つのペアレント由来の元を比較するとき,基準となるペアレント ``P3`` が選択できて ``P1`` と ``P2`` を ``P3`` へ型強制できる場合がある. +そうした状況では型強制がうまく成立するはずだ. +典型的な例は有理数と整数係数の多項式の和の計算で,結果は有理係数の多項式になる. + + +:: + + sage: P1. = ZZ[] + sage: p = 2*x+3 + sage: q = 1/2 + sage: parent(p) + Univariate Polynomial Ring in x over Integer Ring + sage: parent(p+q) + Univariate Polynomial Ring in x over Rational Field + + +この結果は,原則的には ``ZZ['x']`` の有理数体上でも成立する. +しかし,Sageは最も自然に見える *正準* な共通のペアレントを選択しようとする(ここでは ``QQ['x']``). +共通のペアレント候補が複数あってどれも同じく有望そうな場合,Sageは中の一つをランダムに選択するということは *しない* . +これは再現性の高い結果を求めるためで,選択の手段については `thematic tutorial +`_ +に解説がある. + + +以下に示すのは,共通のペアレントへの型強制が成立しない例である: + +:: + + sage: R. = QQ[] + sage: S. = QQ[] + sage: x+y + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '+': 'Univariate Polynomial Ring in x over Rational Field' and 'Univariate Polynomial Ring in y over Rational Field' + +だめな理由は,Sageが有望そうな候補 ``QQ['x']['y']`` , ``QQ['y']['x']`` , ``QQ['x','y']`` あるいは ``QQ['y','x']`` のどれも選択できないことである. +と言うのも,これら4つの相異なる構造はどれも共通なペアレントとして相応しく,基準となるべき選択肢にならないからだ. + diff --git a/src/doc/ja/tutorial/tour_functions.rst b/src/doc/ja/tutorial/tour_functions.rst new file mode 100644 index 00000000000..e2334cae25b --- /dev/null +++ b/src/doc/ja/tutorial/tour_functions.rst @@ -0,0 +1,253 @@ +.. _section-functions-issues: + +関数まわりの注意点 +================================= + +関数の定義については紛らわしい側面があって,微積分やプロットなどを行なう際に問題になることがある. +この節で,関連する諸問題について検討してみたい. + +Sageで「関数」と呼ばれるべきものを定義する方法は何通りもある: + +1. :ref:`section-functions` 節で解説されている方法で,Python関数を定義する. +こうして定義された関数はプロット可能だが,微分積分演算はできない. + +:: + + sage: def f(z): return z^2 + sage: type(f) + + sage: f(3) + 9 + sage: plot(f, 0, 2) + Graphics object consisting of 1 graphics primitive + + +最終行の書法に注目していただきたい. +これを ``plot(f(z), 0, 2)`` としていたら,エラーになっていたはずである. +``z`` は ``f`` 定義におけるダミー変数であって,定義ブロックの外では未定義になるからだ. +むろん ``f(z)`` のみを実行してもエラーになる. +以下のようにすると切り抜けられるが,どんな場合でも通用するとは限らないので要注意だ(下の第4項を参照). + +.. link + +:: + + sage: var('z') # zを変数として定義 + z + sage: f(z) + z^2 + sage: plot(f(z), 0, 2) + Graphics object consisting of 1 graphics primitive + +こうすると ``f(z)`` はシンボリック表現になる.シンボリック表現については,次の項目で解説する. + + + + +2. 「呼び出し可能シンボリック表現」(callable symbolic expression)を定義する. +これはプロットおよび微分積分演算が可能である. + +:: + + sage: g(x) = x^2 + sage: g # gはxをx^2に送る + x |--> x^2 + sage: g(3) + 9 + sage: Dg = g.derivative(); Dg + x |--> 2*x + sage: Dg(3) + 6 + sage: type(g) + + sage: plot(g, 0, 2) + Graphics object consisting of 1 graphics primitive + +``g`` は呼び出し可能シンボリック表現だが, ``g(x)`` の方はこれに関係はあっても異なる種類のオブジェクトである. +やはりプロットと微積分などが可能なのだが,違っている点もあるので注意を要する. +以下の第5項で具体的に説明する. + +.. link + +:: + + sage: g(x) + x^2 + sage: type(g(x)) + + sage: g(x).derivative() + 2*x + sage: plot(g(x), 0, 2) + Graphics object consisting of 1 graphics primitive + + +3. Sageで定義済みの「初等関数」(calculus function)を使う. +これらはプロット可能で,ちょっと工夫すると微分積分もできるようになる. + + +:: + + sage: type(sin) + + sage: plot(sin, 0, 2) + Graphics object consisting of 1 graphics primitive + sage: type(sin(x)) + + sage: plot(sin(x), 0, 2) + Graphics object consisting of 1 graphics primitive + +そのままでは ``sin`` は微分演算を受けつけない. +少なくとも ``cos`` にはならない. + + +:: + + sage: f = sin + sage: f.derivative() + Traceback (most recent call last): + ... + AttributeError: ... + + +``sin`` そのままではなく ``f = sin(x)`` とすると微積分を受けつけるようになるが, もっと手堅いのは ``f(x) = sin(x)`` として呼び出し可能シンボリック表現を定義することである. + + +:: + + sage: S(x) = sin(x) + sage: S.derivative() + x |--> cos(x) + + + +まだ注意を要する点が残っているので,説明しておこう: + +4. 意図しない評価が起きることがある. + +:: + + sage: def h(x): + ....: if x < 2: + ....: return 0 + ....: else: + ....: return x - 2 + + +ここで ``plot(h(x), 0, 4)`` を実行すると,プロットされるのは `y=x-2` で,複数行にわたって定義しておいた ``h`` ではない. +原因を考えてみよう. +コマンド ``plot(h(x), 0, 4)`` が実行されると,まず ``h(x)`` が評価されるが, これは ``x`` が関数 ``h(x)`` に突っ込まれ ``x<2`` が評価されることを意味する. + +.. link + +:: + + sage: type(x<2) + + + +シンボリック式が評価される際, ``h`` の定義の場合と同じように,その式が明らかに真でないかぎり戻り値は偽になる. +したがって ``h(x)`` は ``x-2`` と評価され,プロットされるのも ``x-2`` になるわけである. + + +解決策はというと, ``plot(h(x), 0, 4)`` ではなく + + +.. link + + + +:: + + sage: plot(h, 0, 4) + Graphics object consisting of 1 graphics primitive + +を実行せよ,ということになる. + + + +5. 意図せず関数が定数になってしまう. +:: + + sage: f = x + sage: g = f.derivative() + sage: g + 1 + + +問題は,例えば ``g(3)`` などと実行するとエラーになって, "ValueError: the number of arguments must be less than or equal to 0."と文句をつけてくることだ. + +.. link + +:: + + sage: type(f) + + sage: type(g) + + + +``g`` は関数ではなく定数になっているので,変数を持たないから何も値を受けつけない. + + +解決策は何通りかある. + +- ``f`` を最初にシンボリック表式として定義しておく. + +:: + + sage: f(x) = x # 'f = x'とはしない + sage: g = f.derivative() + sage: g + x |--> 1 + sage: g(3) + 1 + sage: type(g) + + + +- または ``f`` の定義は元のまま ``g`` をシンボリック表式として定義する. + +:: + + sage: f = x + sage: g(x) = f.derivative() # 'g = f.derivative()'とするかわり + sage: g + x |--> 1 + sage: g(3) + 1 + sage: type(g) + + + +- または ``f`` と ``g`` の定義は元のまま,代入すべき変数を特定する. + +:: + + sage: f = x + sage: g = f.derivative() + sage: g + 1 + sage: g(x=3) # たんに'g(3)'とはしない + 1 + + +おしまいになったが, ``f = x`` と ``f(x) = x`` 各々に対する微分の相違点を示す方法がまだあった. + + +:: + + sage: f(x) = x + sage: g = f.derivative() + sage: g.variables() # gに属する変数は? + () + sage: g.arguments() # gに値を送り込むための引数は? + (x,) + sage: f = x + sage: h = f.derivative() + sage: h.variables() + () + sage: h.arguments() + () + + +ここの例から判るように, ``h(3)`` がエラーになるのは,そもそも ``h`` が引数を受けつけないためである. diff --git a/src/doc/ja/tutorial/tour_groups.rst b/src/doc/ja/tutorial/tour_groups.rst new file mode 100644 index 00000000000..be7433e7a28 --- /dev/null +++ b/src/doc/ja/tutorial/tour_groups.rst @@ -0,0 +1,89 @@ + +有限群,アーベル群 +============================= + +Sageでは,置換群,有限古典群(例えば :math:`SU(n,q)`),有限行列群(生成元を指定して生成),そしてアーベル群(無限次も可)などの演算が可能である. +これらの機能の大半は,GAPとのインターフェイスを経由して実現されている. + + +まず,例として置換群を生成してみよう. +それには,以下のようにして生成元のリストを指定してやればよい. + + +:: + + sage: G = PermutationGroup(['(1,2,3)(4,5)', '(3,4)']) + sage: G + Permutation Group with generators [(3,4), (1,2,3)(4,5)] + sage: G.order() + 120 + sage: G.is_abelian() + False + sage: G.derived_series() # 結果は変化しがち + [Subgroup of (Permutation Group with generators [(3,4), (1,2,3)(4,5)]) generated by [(3,4), (1,2,3)(4,5)], + Subgroup of (Permutation Group with generators [(3,4), (1,2,3)(4,5)]) generated by [(1,5,3), (1,5)(3,4), (1,5)(2,4)]] + sage: G.center() + Subgroup of (Permutation Group with generators [(3,4), (1,2,3)(4,5)]) generated by [()] + sage: G.random_element() # random 出力は変化する + (1,5,3)(2,4) + sage: print latex(G) + \langle (3,4), (1,2,3)(4,5) \rangle + +Sageを使えば(LaTeX形式で)指標表を作ることもできる: + + +:: + + sage: G = PermutationGroup([[(1,2),(3,4)], [(1,2,3)]]) + sage: latex(G.character_table()) + \left(\begin{array}{rrrr} + 1 & 1 & 1 & 1 \\ + 1 & -\zeta_{3} - 1 & \zeta_{3} & 1 \\ + 1 & \zeta_{3} & -\zeta_{3} - 1 & 1 \\ + 3 & 0 & 0 & -1 + \end{array}\right) + +Sageは有限体上の古典群と行列群も扱うことができる: + + +:: + + sage: MS = MatrixSpace(GF(7), 2) + sage: gens = [MS([[1,0],[-1,1]]),MS([[1,1],[0,1]])] + sage: G = MatrixGroup(gens) + sage: G.conjugacy_class_representatives() + ( + [1 0] [0 6] [0 4] [6 0] [0 6] [0 4] [0 6] [0 6] [0 6] [4 0] + [0 1], [1 5], [5 5], [0 6], [1 2], [5 2], [1 0], [1 4], [1 3], [0 2], + + [5 0] + [0 3] + ) + sage: G = Sp(4,GF(7)) + sage: G + Symplectic Group of degree 4 over Finite Field of size 7 + sage: G.random_element() # random 元をランダムに出力 + [5 5 5 1] + [0 2 6 3] + [5 0 1 0] + [4 6 3 4] + sage: G.order() + 276595200 + +(無限次および有限次の)アーベル群を使う演算も可能だ: + + +:: + + sage: F = AbelianGroup(5, [5,5,7,8,9], names='abcde') + sage: (a, b, c, d, e) = F.gens() + sage: d * b**2 * c**3 + b^2*c^3*d + sage: F = AbelianGroup(3,[2]*3); F + Multiplicative Abelian group isomorphic to C2 x C2 x C2 + sage: H = AbelianGroup([2,3], names="xy"); H + Multiplicative Abelian group isomorphic to C2 x C3 + sage: AbelianGroup(5) + Multiplicative Abelian group isomorphic to Z x Z x Z x Z x Z + sage: AbelianGroup(5).order() + +Infinity diff --git a/src/doc/ja/tutorial/tour_help.rst b/src/doc/ja/tutorial/tour_help.rst new file mode 100644 index 00000000000..d4382ef1da1 --- /dev/null +++ b/src/doc/ja/tutorial/tour_help.rst @@ -0,0 +1,367 @@ +.. _chapter-help: + +ヘルプの利用 +============ + +Sageには充実したドキュメントが組込まれていて,(例えば)関数や定数の名前に続けて疑問符 ``?`` を入力するだけで解説を呼び出すことができる: + +.. skip + +:: + + sage: tan? + Type: + Definition: tan( [noargspec] ) + Docstring: + + The tangent function + + EXAMPLES: + sage: tan(pi) + 0 + sage: tan(3.1415) + -0.0000926535900581913 + sage: tan(3.1415/4) + 0.999953674278156 + sage: tan(pi/4) + 1 + sage: tan(1/2) + tan(1/2) + sage: RR(tan(1/2)) + 0.546302489843790 + sage: log2? + Type: + Definition: log2( [noargspec] ) + Docstring: + + The natural logarithm of the real number 2. + + EXAMPLES: + sage: log2 + log2 + sage: float(log2) + 0.69314718055994529 + sage: RR(log2) + 0.693147180559945 + sage: R = RealField(200); R + Real Field with 200 bits of precision + sage: R(log2) + 0.69314718055994530941723212145817656807550013436025525412068 + sage: l = (1-log2)/(1+log2); l + (1 - log(2))/(log(2) + 1) + sage: R(l) + 0.18123221829928249948761381864650311423330609774776013488056 + sage: maxima(log2) + log(2) + sage: maxima(log2).float() + .6931471805599453 + sage: gp(log2) + 0.6931471805599453094172321215 # 精度は32ビット + 0.69314718055994530941723212145817656807 # 同じく64ビット + sage: sudoku? + File: sage/local/lib/python2.5/site-packages/sage/games/sudoku.py + Type: + Definition: sudoku(A) + Docstring: + + Solve the 9x9 Sudoku puzzle defined by the matrix A. + + EXAMPLE: + sage: A = matrix(ZZ,9,[5,0,0, 0,8,0, 0,4,9, 0,0,0, 5,0,0, + 0,3,0, 0,6,7, 3,0,0, 0,0,1, 1,5,0, 0,0,0, 0,0,0, 0,0,0, 2,0,8, 0,0,0, + 0,0,0, 0,0,0, 0,1,8, 7,0,0, 0,0,4, 1,5,0, 0,3,0, 0,0,2, + 0,0,0, 4,9,0, 0,5,0, 0,0,3]) + sage: A + [5 0 0 0 8 0 0 4 9] + [0 0 0 5 0 0 0 3 0] + [0 6 7 3 0 0 0 0 1] + [1 5 0 0 0 0 0 0 0] + [0 0 0 2 0 8 0 0 0] + [0 0 0 0 0 0 0 1 8] + [7 0 0 0 0 4 1 5 0] + [0 3 0 0 0 2 0 0 0] + [4 9 0 0 5 0 0 0 3] + sage: sudoku(A) + [5 1 3 6 8 7 2 4 9] + [8 4 9 5 2 1 6 3 7] + [2 6 7 3 4 9 5 8 1] + [1 5 8 4 6 3 9 7 2] + [9 7 4 2 1 8 3 6 5] + [3 2 6 7 9 5 4 1 8] + [7 8 2 9 3 4 1 5 6] + [6 3 5 1 7 2 8 9 4] + [4 9 1 8 5 6 7 2 3] + +さらにSageでは,関数名の最初の何文字かを入力して ``TAB`` キーを打つと候補が表示される,タブ補完機能を使うことができる. +例えば ``ta`` と入力して ``TAB`` キーを押せば、Sageは ``tachyon, tan, tanh,taylor`` を表示してくる. +この機能を使えば関数やオブジェクトなどの名称を容易に探しあてることができるはずだ. + + +.. _section-functions: + +関数, インデントおよび数え上げ +==================================== + +Sageで新しい関数を定義するには, ``def`` 命令を使い、変数名を並べた後にコロン ``:`` を付ける. +以下に例を示そう: + +:: + + sage: def is_even(n): + ....: return n % 2 == 0 + ....: + sage: is_even(2) + True + sage: is_even(3) + False + +*注意* : チュートリアルをどの形式で閲覧しているかにもよるが,上のコード例の2行目には四つのドット ``....`` が見えているはずだ. +この四点ドットは入力しないこと. +四点ドットは,コードがインデントされていることを示しているだけだからだ. +そうした場面では,常に構文ブロックの末尾で一度 ``Return/Enter`` を押して空行を挿入し,関数定義を終了してやらねばならない. + +引数の型を指定していないことに注意.複数個の引数を指定し,その各々にデフォルト値を割り当てることもできる. +例えば、以下の関数では引数 ``divisor`` の値が指定されない場合, ``divisor=2`` がデフォルト値になる: + +:: + + sage: def is_divisible_by(number, divisor=2): + ....: return number % divisor == 0 + sage: is_divisible_by(6,2) + True + sage: is_divisible_by(6) + True + sage: is_divisible_by(6, 5) + False + + +関数を呼び出すときには,特定の引数へ明示的に値を代入することもできる. +引数への明示的な代入を行なう場合,関数に渡す引数の順序は任意になる: + +.. link + +:: + + sage: is_divisible_by(6, divisor=5) + False + sage: is_divisible_by(divisor=2, number=6) + True + + +Pythonの構文ブロックは,他の多くの言語のように中括弧やbegin-endで括ることによって示されるわけではない. +代りに、Pythonでは構文構造に正確に対応したインデンテーション(字下げ)によってブロックを示す. +次の例は, ``return`` ステートメントが関数内の他のコードと同じようにインデントされていないために,文法エラーになっている: + +.. skip + +:: + + sage: def even(n): + ....: v = [] + ....: for i in range(3, n): + ....: if i % 2 == 0: + ....: v.append(i) + ....: return v + Syntax Error: + return v + +しかし正しくインデントし直せば,この関数はきちんと動くようになる: + +:: + + sage: def even(n): + ....: v = [] + ....: for i in range(3,n): + ....: if i % 2 == 0: + ....: v.append(i) + ....: return v + sage: even(10) + [4, 6, 8] + +行末にセミコロンは必要ない. +ほとんどの場合,行末は改行記号によって示される. +しかし,1行に複数のステートメントをセミコロンで区切って書き込むこともできる: + +:: + + sage: a = 5; b = a + 3; c = b^2; c + 64 + +1行の内容を複数行に分けて書きたければ,各行末にバックスラッシュをつければよい: + +:: + + sage: (2 + + ....: 3) + 5 + + +Sageでは,一定範囲の整数の数え上げによって反復を制御する. +例えば,以下のコードの1行目はC++やJavaにおける ``for(i=0; i<3; i++)`` と全く同じ意味になる: + + +:: + + sage: for i in range(3): + ....: print i + 0 + 1 + 2 + + +次の例の最初の行は, ``for(i=2;i<5;i++)`` に対応している. + + +:: + + sage: for i in range(2,5): + ....: print i + 2 + 3 + 4 + + +``range`` の三つ目の引数は増分値を与えるので, +次のコードは ``for(i=1;i<6;i+=2)`` と同じ意味になる. + +:: + + sage: for i in range(1,6,2): + ....: print i + 1 + 3 + 5 + + +Sageで計算した値を見映えよく表形式に並べて表示したくなることもあるだろう. +そんなとき役立つのが文字列フォーマットだ. +以下では,各列幅がきっかり6文字分の表を作り,整数とその2乗、3乗の値を並べてみる. + + +:: + + sage: for i in range(5): + ....: print '%6s %6s %6s' % (i, i^2, i^3) + 0 0 0 + 1 1 1 + 2 4 8 + 3 9 27 + 4 16 64 + + +Sageにおける最も基本的なデータ構造はリストで,名前の示すとおり任意のオブジェクトの並びのことである. +上で使った ``range`` も、整数のリストを生成している: + +:: + + sage: range(2,10) + [2, 3, 4, 5, 6, 7, 8, 9] + +もう少し複雑なリストの例として: + +:: + + sage: v = [1, "hello", 2/3, sin(x^3)] + sage: v + [1, 'hello', 2/3, sin(x^3)] + +多くのプログラミング言語と同じように,リスト添字は0から始まる. + + +.. link + +:: + + sage: v[0] + 1 + sage: v[3] + sin(x^3) + + + +``v`` の長さを取得するには ``len(v)`` を使い, ``v`` の末尾に新しいオブジェクトを追加するには ``v.append(obj)`` ,そして ``v`` の :math:`i` 番目の要素を削除するためには ``del v[i]`` とする: + + +.. link + + +:: + + sage: len(v) + 4 + sage: v.append(1.5) + sage: v + [1, 'hello', 2/3, sin(x^3), 1.50000000000000] + sage: del v[1] + sage: v + [1, 2/3, sin(x^3), 1.50000000000000] + + + +もう一つの重要なデータ構造がディクショナリ(連想配列とも言う)である. +ディクショナリの振舞いはリストに似ているが,異なるのはその添字付けに基本的にいかなるオブジェクトでも使うことができる点だ(ただし添字は不変性オブジェクトでなければならない). + +:: + + sage: d = {'hi':-2, 3/8:pi, e:pi} + sage: d['hi'] + -2 + sage: d[e] + pi + + + +クラスを使えば自分で新しいデータ型を定義することも可能だ. +クラスによる数学オブジェクトのカプセル化は,Sageプログラムを見通しよく構成するための強力な方法である. +以下では, *n* までの正の偶数のリストを表すクラスを定義してみよう. +定義には組み込み型 ``list`` を使っている. +:: + + sage: class Evens(list): + ....: def __init__(self, n): + ....: self.n = n + ....: list.__init__(self, range(2, n+1, 2)) + ....: def __repr__(self): + ....: return "Even positive numbers up to n." + + +オブジェクトの生成時には初期化のために ``__init__`` メソッドが呼ばれ, ``__repr__`` メソッドはオブジェクトを印字する. +``__init__`` メソッドの2行目ではリストコンストラクタを使った. +クラス ``Evens`` のオブジェクトを生成するには以下のようにする: + + +.. link + + +:: + + sage: e = Evens(10) + sage: e + Even positive numbers up to n. + + +``e`` の印字には,我々が定義した ``__repr__`` メソッドが使われている. +オブジェクトに含まれる偶数のリストを表示するには, ``list`` 関数を使う: + + +.. link + + +:: + + sage: list(e) + [2, 4, 6, 8, 10] + + +``n`` 属性にアクセスし、 ``e`` をリストのように扱うこともできる. + + +.. link + + +:: + + sage: e.n + 10 + sage: e[2] + 6 diff --git a/src/doc/ja/tutorial/tour_linalg.rst b/src/doc/ja/tutorial/tour_linalg.rst new file mode 100644 index 00000000000..14e223c6a5d --- /dev/null +++ b/src/doc/ja/tutorial/tour_linalg.rst @@ -0,0 +1,269 @@ +.. _section-linalg: + +線形代数 +============== + +Sageには線形代数で常用されるツールが揃っていて,例えば行列の特性多項式の計算、階段形式、跡(トレース)、各種の分解などの操作が可能である. + +行列の生成と積演算の手順は,簡単かつ自然なものだ: + + +:: + + sage: A = Matrix([[1,2,3],[3,2,1],[1,1,1]]) + sage: w = vector([1,1,-4]) + sage: w*A + (0, 0, 0) + sage: A*w + (-9, 1, -2) + sage: kernel(A) + Free module of degree 3 and rank 1 over Integer Ring + Echelon basis matrix: + [ 1 1 -4] + + +Sageでは,行列 :math:`A` の核空間は「左」核空間,すなわち :math:`wA=0` を満足するベクトル :math:`w` が張る空間をさす. + +.. + Note that in Sage, the kernel of a matrix :math:`A` is the + "left kernel", i.e. the space of vectors :math:`w` such that + :math:`wA=0`. + +行列方程式もメソッド ``solve_right`` を使って簡単に解くことができる. +``A.solve_right(Y)`` と実行すれば, :math:`AX=Y` を満たす行列(またはベクトル) :math:`X` が得られる. + + +.. link + +:: + + sage: Y = vector([0, -4, -1]) + sage: X = A.solve_right(Y) + sage: X + (-2, 1, 0) + sage: A * X # 解のチェック... + (0, -4, -1) + + +``solve_right`` の代わりにバックスラッシュ ``\`` を使うこともできる. +つまり ``A.solve_right(Y)`` ではなく ``A \ Y`` と書くわけである. + +.. link + +:: + + sage: A \ Y + (-2, 1, 0) + + + +解がない場合は,Sageはエラーを返してくる: + +.. skip + +:: + + sage: A.solve_right(w) + Traceback (most recent call last): + ... + ValueError: matrix equation has no solutions + + +同様にして, :math:`XA=Y` を満足する :math:`X` を求めるには ``A.solve_left(Y)`` とすればよい. + + +Sageは固有値と固有ベクトルの計算もしてくれる: + + +:: + + sage: A = matrix([[0, 4], [-1, 0]]) + sage: A.eigenvalues () + [-2*I, 2*I] + sage: B = matrix([[1, 3], [3, 1]]) + sage: B.eigenvectors_left() + [(4, [ + (1, 1) + ], 1), (-2, [ + (1, -1) + ], 1)] + + +( ``eigenvectors_left`` の出力は,三つ組タプル(固有値,固有ベクトル,多重度)のリストになっている.) + +``QQ`` または ``RR`` 上の固有値と固有ベクトルはMaximaを使って計算することもできる( 後半の :ref:`section-maxima` 節を参照). + + + +:ref:`section-rings` 節で述べたように,行列の性質の中には,その行列がどんな環の上で定義されているかに影響を受けるものがある. +以下では, ``matrix`` コマンドの最初の引数を使って,Sageに生成すべき行列が整数の行列( ``ZZ`` の場合) なのか,有理数の行列(``QQ``)なのか,あるいは実数の行列(``RR``)なのかを指定している. + + +:: + + sage: AZ = matrix(ZZ, [[2,0], [0,1]]) + sage: AQ = matrix(QQ, [[2,0], [0,1]]) + sage: AR = matrix(RR, [[2,0], [0,1]]) + sage: AZ.echelon_form() + [2 0] + [0 1] + sage: AQ.echelon_form() + [1 0] + [0 1] + sage: AR.echelon_form() + [ 1.00000000000000 0.000000000000000] + [0.000000000000000 1.00000000000000] + + +浮動小数点型の実数または複素数上で定義された行列の固有値と固有ベクトルを計算するためには,対象とする行列を,それぞれ ``RDF`` (Real Double Field)または ``CDF`` (Complex Double Field)上で定義しておかなければならない. +もし環を指定しないまま行列に浮動小数点型の実数あるいは複素数が使われる場合,その行列はデフォルトでそれぞれ ``RR`` あるいは ``CC`` 体上で定義される. +この場合,以下の演算があらゆる状況で実行可能になるとは限らない. + + +:: + + sage: ARDF = matrix(RDF, [[1.2, 2], [2, 3]]) + sage: ARDF.eigenvalues() # abs tol 1e-10 + [-0.09317121994613098, 4.293171219946131] + sage: ACDF = matrix(CDF, [[1.2, I], [2, 3]]) + sage: ACDF.eigenvectors_right() # abs tol 1e-10 + [(0.881845698329 - 0.820914065343*I, [(0.750560818381, -0.616145932705 + 0.238794153033*I)], 1), + (3.31815430167 + 0.820914065343*I, [(0.145594698293 + 0.37566908585*I, 0.915245825866)], 1)] + + + +行列の空間 +------------- + +有理数型の要素からなる `3 \times 3` 行列の空間 +:math:`\text{Mat}_{3\times 3}(\QQ)` を生成してみよう: + +:: + + sage: M = MatrixSpace(QQ,3) + sage: M + Full MatrixSpace of 3 by 3 dense matrices over Rational Field + + +(3行4列の行列空間を生成したければ ``MatrixSpace(QQ,3,4)`` とする. +列数を省略するとデフォルトで行数に合わせられるから, ``MatrixSpace(QQ,3)`` は ``MatrixSpace(QQ,3,3)`` と同じ意味になる.) +行列の空間は基底系を備えており,Sageはこれをリストとして保存している: + +.. link + +:: + + sage: B = M.basis() + sage: len(B) + 9 + sage: B[1] + [0 1 0] + [0 0 0] + [0 0 0] + +``M`` の元の一つとして行列を生成してみよう. + + +.. link + +:: + + sage: A = M(range(9)); A + [0 1 2] + [3 4 5] + [6 7 8] + + +ついで,その既約階段形式と核を計算する. + +.. link + +:: + + sage: A.echelon_form() + [ 1 0 -1] + [ 0 1 2] + [ 0 0 0] + sage: A.kernel() + Vector space of degree 3 and dimension 1 over Rational Field + Basis matrix: + [ 1 -2 1] + +次に,有限体上で定義された行列による計算を実行してみる. + + +:: + + sage: M = MatrixSpace(GF(2),4,8) + sage: A = M([1,1,0,0, 1,1,1,1, 0,1,0,0, 1,0,1,1, + ....: 0,0,1,0, 1,1,0,1, 0,0,1,1, 1,1,1,0]) + sage: A + [1 1 0 0 1 1 1 1] + [0 1 0 0 1 0 1 1] + [0 0 1 0 1 1 0 1] + [0 0 1 1 1 1 1 0] + sage: rows = A.rows() + sage: A.columns() + [(1, 0, 0, 0), (1, 1, 0, 0), (0, 0, 1, 1), (0, 0, 0, 1), + (1, 1, 1, 1), (1, 0, 1, 1), (1, 1, 0, 1), (1, 1, 1, 0)] + sage: rows + [(1, 1, 0, 0, 1, 1, 1, 1), (0, 1, 0, 0, 1, 0, 1, 1), + (0, 0, 1, 0, 1, 1, 0, 1), (0, 0, 1, 1, 1, 1, 1, 0)] + +上に現れた行ベクトル系(rows)によって張られる `\GF{2}` の部分空間を作成する. + + +.. link + +:: + + sage: V = VectorSpace(GF(2),8) + sage: S = V.subspace(rows) + sage: S + Vector space of degree 8 and dimension 4 over Finite Field of size 2 + Basis matrix: + [1 0 0 0 0 1 0 0] + [0 1 0 0 1 0 1 1] + [0 0 1 0 1 1 0 1] + [0 0 0 1 0 0 1 1] + sage: A.echelon_form() + [1 0 0 0 0 1 0 0] + [0 1 0 0 1 0 1 1] + [0 0 1 0 1 1 0 1] + [0 0 0 1 0 0 1 1] + + +Sageは `S` の基底として, `S` の生成元行列の既約階段形式の非ゼロ行を使用している. + + +疎行列の線形代数 +--------------------- + +SageではPID(単項イデアル整域)上の疎行列に関する線形代数を扱うことができる. + +:: + + sage: M = MatrixSpace(QQ, 100, sparse=True) + sage: A = M.random_element(density = 0.05) + sage: E = A.echelon_form() + + +Sageで使われている多重モジュラーアルゴリズムは,正方行列ではうまく働く(非正方行列ではいまひとつである): + +:: + + sage: M = MatrixSpace(QQ, 50, 100, sparse=True) + sage: A = M.random_element(density = 0.05) + sage: E = A.echelon_form() + sage: M = MatrixSpace(GF(2), 20, 40, sparse=True) + sage: A = M.random_element() + sage: E = A.echelon_form() + +Pythonでは,大文字小文字が区別されることに注意: + +:: + + sage: M = MatrixSpace(QQ, 10,10, Sparse=True) + Traceback (most recent call last): + ... + TypeError: __classcall__() got an unexpected keyword argument 'Sparse' diff --git a/src/doc/ja/tutorial/tour_numtheory.rst b/src/doc/ja/tutorial/tour_numtheory.rst new file mode 100644 index 00000000000..4e091a77f66 --- /dev/null +++ b/src/doc/ja/tutorial/tour_numtheory.rst @@ -0,0 +1,172 @@ +.. Number Theory + +数論 +============= + +Sageは数論関連の多彩な機能を備えている. +例えば,以下のようにして :math:`\ZZ/N\ZZ` 上の演算を実行することができる: + +:: + + sage: R = IntegerModRing(97) + sage: a = R(2) / R(3) + sage: a + 33 + sage: a.rational_reconstruction() + 2/3 + sage: b = R(47) + sage: b^20052005 + 50 + sage: b.modulus() + 97 + sage: b.is_square() + True + +Sageは数論では標準となっている関数群を装備している.例えば + + +:: + + sage: gcd(515,2005) + 5 + sage: factor(2005) + 5 * 401 + sage: c = factorial(25); c + 15511210043330985984000000 + sage: [valuation(c,p) for p in prime_range(2,23)] + [22, 10, 6, 3, 2, 1, 1, 1] + sage: next_prime(2005) + 2011 + sage: previous_prime(2005) + 2003 + sage: divisors(28); sum(divisors(28)); 2*28 + [1, 2, 4, 7, 14, 28] + 56 + 56 + +という具合で,申し分なしだ. + + +Sageの ``sigma(n,k)`` 関数は, ``n`` の商の :math:`k` 乗の和を計算する: + +:: + + sage: sigma(28,0); sigma(28,1); sigma(28,2) + 6 + 56 + 1050 + +以下では,拡張ユークリッド互除法,オイラーの :math:`\phi` -関数,そして中国剰余定理を見てみよう: + + +:: + + sage: d,u,v = xgcd(12,15) + sage: d == u*12 + v*15 + True + sage: n = 2005 + sage: inverse_mod(3,n) + 1337 + sage: 3 * 1337 + 4011 + sage: prime_divisors(n) + [5, 401] + sage: phi = n*prod([1 - 1/p for p in prime_divisors(n)]); phi + 1600 + sage: euler_phi(n) + 1600 + sage: prime_to_m_part(n, 5) + 401 + +次に, :math:`3n+1` 問題をちょっと調べてみる. + +:: + + sage: n = 2005 + sage: for i in range(1000): + ....: n = 3 * odd_part(n) + 1 + ....: if odd_part(n) == 1: + ....: print i + ....: break + 38 + +最後に,中国剰余定理を確かめてみよう. + + +:: + + sage: x = crt(2, 1, 3, 5); x + 11 + sage: x % 3 # x mod 3 = 2 + 2 + sage: x % 5 # x mod 5 = 1 + 1 + sage: [binomial(13,m) for m in range(14)] + [1, 13, 78, 286, 715, 1287, 1716, 1716, 1287, 715, 286, 78, 13, 1] + sage: [binomial(13,m)%2 for m in range(14)] + [1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] + sage: [kronecker(m,13) for m in range(1,13)] + [1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, 1] + sage: n = 10000; sum([moebius(m) for m in range(1,n)]) + -23 + sage: Partitions(4).list() + [[4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]] + + + +:math:`p` \-進数 +------------------------ + +Sageには :math:`p` \-進数体も組込まれている. +ただし,いったん生成された :math:`p` \-進数体については,後でその精度を変更することはできないことを注意しておく. + + +:: + + sage: K = Qp(11); K + 11-adic Field with capped relative precision 20 + sage: a = K(211/17); a + 4 + 4*11 + 11^2 + 7*11^3 + 9*11^5 + 5*11^6 + 4*11^7 + 8*11^8 + 7*11^9 + + 9*11^10 + 3*11^11 + 10*11^12 + 11^13 + 5*11^14 + 6*11^15 + 2*11^16 + + 3*11^17 + 11^18 + 7*11^19 + O(11^20) + sage: b = K(3211/11^2); b + 10*11^-2 + 5*11^-1 + 4 + 2*11 + O(11^18) + +:math:`p` \-進数体あるいは :math:`QQ` 以外の数体上に整数環を実装するために多大の労力が投入されてきている. +興味ある読者はGoogleグループ ``sage-support`` で専門家に詳細を聞いてみてほしい. + + +``NumberField`` クラスには,すでに多くの関連メソッドが実装されている. + + +:: + + sage: R. = PolynomialRing(QQ) + sage: K = NumberField(x^3 + x^2 - 2*x + 8, 'a') + sage: K.integral_basis() + [1, 1/2*a^2 + 1/2*a, a^2] + +.. link + +:: + + sage: K.galois_group(type="pari") + Galois group PARI group [6, -1, 2, "S3"] of degree 3 of the Number Field + in a with defining polynomial x^3 + x^2 - 2*x + 8 + +.. link + +:: + + sage: K.polynomial_quotient_ring() + Univariate Quotient Polynomial Ring in a over Rational Field with modulus + x^3 + x^2 - 2*x + 8 + sage: K.units() + (3*a^2 + 13*a + 13,) + sage: K.discriminant() + -503 + sage: K.class_group() + Class group of order 1 of Number Field in a with + defining polynomial x^3 + x^2 - 2*x + 8 + sage: K.class_number() + 1 diff --git a/src/doc/ja/tutorial/tour_plotting.rst b/src/doc/ja/tutorial/tour_plotting.rst new file mode 100644 index 00000000000..c9e94b61e65 --- /dev/null +++ b/src/doc/ja/tutorial/tour_plotting.rst @@ -0,0 +1,229 @@ +.. _section-plot: + +プロットする +================== + +Sageを使って2次元および3次元のグラフを作成することができる. + + +2次元プロット +--------------------- + +Sageの2次元プロット機能を使うと,円,直線,多辺形の描画はもちろん, +直交座標系における関数のプロット,極座標プロット、等高線プロット,ベクトル場プロットを行うことができる. +以下では、その具体例を見ていくことにしよう.さらにSageによるプロットの具体例を見たければ, :ref:`section-systems` 節と :ref:`section-maxima` 節,および `Sage Constructions `_ を参照してほしい. + +次のコマンドは原点を中心とした半径1の黄色い円を描く: +:: + + sage: circle((0,0), 1, rgbcolor=(1,1,0)) + Graphics object consisting of 1 graphics primitive + +円を塗りつぶすこともできる: + +:: + + sage: circle((0,0), 1, rgbcolor=(1,1,0), fill=True) + Graphics object consisting of 1 graphics primitive + +円を生成して,それを変数に収めておくこともできる. +ただし,それだけでは描画は実行されない. + +:: + + sage: c = circle((0,0), 1, rgbcolor=(1,1,0)) + +描画するには,以下のように ``c.show()`` または ``show(c)`` などとする: + +.. link + +:: + + sage: c.show() + +かわりに ``c.save('filename.png')`` などとすれば,プロット ``c`` は画像としてファイルに保存される. + +ところで「円」がどちらかと言えば「楕円」に見えるのは,軸が縦横で異なってスケールされるからだ. +これを直すには: + +.. link + +:: + + sage: c.show(aspect_ratio=1) + + + +同じことはコマンド ``show(c, aspect_ratio=1)`` としても可能だし,画像ファイルとして保存したければ ``c.save('filename.png', aspect_ratio=1)`` と実行してもよい. + + +初等関数のプロットも簡単だ: + +:: + + sage: plot(cos, (-5,5)) + Graphics object consisting of 1 graphics primitive + +変数を特定しておけば,媒介変数プロットも可能になる: + +:: + + sage: x = var('x') + sage: parametric_plot((cos(x),sin(x)^3),(x,0,2*pi),rgbcolor=hue(0.6)) + Graphics object consisting of 1 graphics primitive + +プロットにおける座標軸の交点は,それがグラフの描画範囲にない限り表示されないことに注意しておいてほしい. +描画範囲として十分大きな値を指定するために,科学記法(指数記法)を用いる必要があるかもしれない. + +:: + + sage: plot(x^2,(x,300,500)) + Graphics object consisting of 1 graphics primitive + +複数のプロットをまとめて描画するには,それらを足し合わせればよい: + +:: + + sage: x = var('x') + sage: p1 = parametric_plot((cos(x),sin(x)),(x,0,2*pi),rgbcolor=hue(0.2)) + sage: p2 = parametric_plot((cos(x),sin(x)^2),(x,0,2*pi),rgbcolor=hue(0.4)) + sage: p3 = parametric_plot((cos(x),sin(x)^3),(x,0,2*pi),rgbcolor=hue(0.6)) + sage: show(p1+p2+p3, axes=false) + + +塗りつぶし図形を生成するには,まず図形の頂点からなるリストを生成し(以下の例の ``L``),次に ``polygon`` コマンドで頂点をつないで閉領域を作るとよい. +ここでは,例として緑色の三角形を描画してみる: + +:: + + sage: L = [[-1+cos(pi*i/100)*(1+cos(pi*i/100)), + ....: 2*sin(pi*i/100)*(1-cos(pi*i/100))] for i in range(200)] + sage: p = polygon(L, rgbcolor=(1/8,3/4,1/2)) + sage: p + Graphics object consisting of 1 graphics primitive + +``show(p, axes=false)`` と入力して座標軸なしの図を表示してみよう. + +図形プロットに文字列を加えることができる: + +:: + + sage: L = [[6*cos(pi*i/100)+5*cos((6/2)*pi*i/100), + ....: 6*sin(pi*i/100)-5*sin((6/2)*pi*i/100)] for i in range(200)] + sage: p = polygon(L, rgbcolor=(1/8,1/4,1/2)) + sage: t = text("hypotrochoid", (5,4), rgbcolor=(1,0,0)) + sage: show(p+t) + + +次に出てくるarcsinのグラフは、微積分の教師が黒板にたびたび描くものだ. +主値だけではなく複数の分岐を含めてプロットするには, :math:`x` が :math:`-2\pi` から :math:`2\pi` までの :math:`y=\sin(x)` のグラフを傾き45度の線について反転させて描いてやればよい. +これをSageで実行するには,以下のコマンドを使う: + +:: + + sage: v = [(sin(x),x) for x in srange(-2*float(pi),2*float(pi),0.1)] + sage: line(v) + Graphics object consisting of 1 graphics primitive + +正接(tan)関数はsin関数よりも値域が広いので,arcsinと同じ作戦で逆正接関数のグラフを描くには *x* -軸の最大値と最小値を調節してやる必要がある: + +:: + + sage: v = [(tan(x),x) for x in srange(-2*float(pi),2*float(pi),0.01)] + sage: show(line(v), xmin=-20, xmax=20) + +Sageでは、(対象となる関数は限られるが)極座標プロット、等高線プロット、ベクトル場プロットも可能だ. +ここでは,例として等高線プロットを見ておこう: + +:: + + sage: f = lambda x,y: cos(x*y) + sage: contour_plot(f, (-4, 4), (-4, 4)) + Graphics object consisting of 1 graphics primitive + + + +3次元プロット +----------------------- + +Sageでは3次元プロットも作成することができる. +ノートブック上でもREPL(コマンドライン)上でも,3次元プロットの表示はデフォルトでオープンソースパッケージ [Jmol]_ によって行なわれる. +Jmolではマウスによる描画の回転と拡大縮小が可能だ. + +``plot3d`` を使って `f(x, y) = z` 形式の関数をプロットしてみよう: + +:: + + sage: x, y = var('x,y') + sage: plot3d(x^2 + y^2, (x,-2,2), (y,-2,2)) + Graphics3d Object + +代りに ``parametric_plot3d`` を使い, `x, y, z` 各々が1あるいは2個のパラメター(いわゆる媒介変数,記号 `u` や `v` などが使われることが多い)で決定されるパラメトリック曲面として描画することもできる. +上の関数を媒介変数表示してプロットするには: + +:: + + sage: u, v = var('u, v') + sage: f_x(u, v) = u + sage: f_y(u, v) = v + sage: f_z(u, v) = u^2 + v^2 + sage: parametric_plot3d([f_x, f_y, f_z], (u, -2, 2), (v, -2, 2)) + Graphics3d Object + +Sageで3次元曲面プロットを行うための第三の方法が ``implicit_plot3d`` の使用で,これは(空間内の点の集合を定義する) `f(x, y, z) = 0` を満足する関数の等高線を描画する. +ここでは古典的な表式を使って球面を作画してみよう: + +:: + + sage: x, y, z = var('x, y, z') + sage: implicit_plot3d(x^2 + y^2 + z^2 - 4, (x,-2, 2), (y,-2, 2), (z,-2, 2)) + Graphics3d Object + +以下で,さらにいくつかの3次元プロットを示しておこう: + +.. `Yellow Whitney's umbrella `__: + +`ホィットニーの傘 `__: + +:: + + sage: u, v = var('u,v') + sage: fx = u*v + sage: fy = u + sage: fz = v^2 + sage: parametric_plot3d([fx, fy, fz], (u, -1, 1), (v, -1, 1), + ....: frame=False, color="yellow") + Graphics3d Object + +`クロスキャップ(十字帽) `__: + +:: + + sage: u, v = var('u,v') + sage: fx = (1+cos(v))*cos(u) + sage: fy = (1+cos(v))*sin(u) + sage: fz = -tanh((2/3)*(u-pi))*sin(v) + sage: parametric_plot3d([fx, fy, fz], (u, 0, 2*pi), (v, 0, 2*pi), + ....: frame=False, color="red") + Graphics3d Object + +ねじれトーラス(twisted torus): + +:: + + sage: u, v = var('u,v') + sage: fx = (3+sin(v)+cos(u))*cos(2*v) + sage: fy = (3+sin(v)+cos(u))*sin(2*v) + sage: fz = sin(u)+2*cos(v) + sage: parametric_plot3d([fx, fy, fz], (u, 0, 2*pi), (v, 0, 2*pi), + ....: frame=False, color="red") + Graphics3d Object + +レムニスケート(連珠形, lemniscate): + +:: + + sage: x, y, z = var('x,y,z') + sage: f(x, y, z) = 4*x^2 * (x^2 + y^2 + z^2 + z) + y^2 * (y^2 + z^2 - 1) + sage: implicit_plot3d(f, (x, -0.5, 0.5), (y, -1, 1), (z, -1, 1)) + Graphics3d Object diff --git a/src/doc/ja/tutorial/tour_polynomial.rst b/src/doc/ja/tutorial/tour_polynomial.rst new file mode 100644 index 00000000000..47f3ab37cfb --- /dev/null +++ b/src/doc/ja/tutorial/tour_polynomial.rst @@ -0,0 +1,323 @@ +.. _section-poly: + +多項式 +=========== + +この節では,Sage上で多項式を生成し利用する方法について解説する. + + +.. _section-univariate: + + +1変数多項式 +---------------------- + +多項式環を生成するには,三通りのやり方がある. + +:: + + sage: R = PolynomialRing(QQ, 't') + sage: R + Univariate Polynomial Ring in t over Rational Field + +このやり方では,多項式環を生成し,画面表示時の変数名として(文字列) ``t`` を割り当てている. +ただし,これはSage内で使うために記号 ``t`` を定義しているわけではないから,( :math:`t^2+1` のようにして) ``R`` 上の多項式を入力するときには使えないことを注意しておく. + +これに代わるやり方として + +.. link + +:: + + sage: S = QQ['t'] + sage: S == R + True + +があるが, 記号 ``t`` については最初の方法と同じ問題が残る. + +第三の,とても便利な方法が + +:: + + sage: R. = PolynomialRing(QQ) + +とするか +:: + + sage: R. = QQ['t'] + +あるいはまた + +:: + + sage: R. = QQ[] + +とすることである. + +最後の方法には,多項式の変数として変数 ``t`` が定義される余禄がついてくるので,以下のようにして簡単に ``R`` の元を構成することができる. +(第三の方法が,Magmaにおけるコンストラクタ記法によく似ていることに注意.Magmaと同じように,Sageでも多様なオブジェクトでコンストラクタ記法を使うことができる.) + +.. link + +:: + + sage: poly = (t+1) * (t+2); poly + t^2 + 3*t + 2 + sage: poly in R + True + +どの方法で多項式環を定義していても,その変数を :math:`0` 番目の生成元として取り出すことができる: + +:: + + sage: R = PolynomialRing(QQ, 't') + sage: t = R.0 + sage: t in R + True + + +Sageでは,複素数も多項式環と似た構成法で生成されている. +すなわち,実数体上に記号 ``i`` を生成元として構成されているのが複素数であると見なすことができるのだ. +それを示すのが: + +:: + + sage: CC + Complex Field with 53 bits of precision + sage: CC.0 # CCの0番目の生成元 + 1.00000000000000*I + + +多項式環を生成するときは,以下のように環と生成元の両方を同時に作るか,あるいは生成元のみを作るか選ぶことができる: + +:: + + sage: R, t = QQ['t'].objgen() + sage: t = QQ['t'].gen() + sage: R, t = objgen(QQ['t']) + sage: t = gen(QQ['t']) + +最後に, :math:`\QQ[t]` 上の演算を試してみよう. + +:: + + sage: R, t = QQ['t'].objgen() + sage: f = 2*t^7 + 3*t^2 - 15/19 + sage: f^2 + 4*t^14 + 12*t^9 - 60/19*t^7 + 9*t^4 - 90/19*t^2 + 225/361 + sage: cyclo = R.cyclotomic_polynomial(7); cyclo + t^6 + t^5 + t^4 + t^3 + t^2 + t + 1 + sage: g = 7 * cyclo * t^5 * (t^5 + 10*t + 2) + sage: g + 7*t^16 + 7*t^15 + 7*t^14 + 7*t^13 + 77*t^12 + 91*t^11 + 91*t^10 + 84*t^9 + + 84*t^8 + 84*t^7 + 84*t^6 + 14*t^5 + sage: F = factor(g); F + (7) * t^5 * (t^5 + 10*t + 2) * (t^6 + t^5 + t^4 + t^3 + t^2 + t + 1) + sage: F.unit() + 7 + sage: list(F) + [(t, 5), (t^5 + 10*t + 2, 1), (t^6 + t^5 + t^4 + t^3 + t^2 + t + 1, 1)] + + +因数分解の際には,定数(倍)部がきちんと分離して記録されていることに注目. + + +仕事中に,例えば ``R.cyclotomic_polynomial`` 関数を頻繁に使う必要があったとしよう. +研究発表の際には,Sage本体だけではなく,円周等分多項式の実質的な計算に使われたコンポーネントを突きとめて引用するのが筋だ. +この場合、 ``R.cyclotomic_polynomial??`` と入力してソースコードを表示すると,すぐに ``f = pari.polcyclo(n)`` という行が見つかるはずだ. +これで円周等分多項式の計算にPARIが使われていることが判ったのだから,発表の際にはPARIも引用することができる. + +多項式同士で割算すると,結果は(Sageが自動的に生成する)有理数体の元になる. + + +:: + + sage: x = QQ['x'].0 + sage: f = x^3 + 1; g = x^2 - 17 + sage: h = f/g; h + (x^3 + 1)/(x^2 - 17) + sage: h.parent() + Fraction Field of Univariate Polynomial Ring in x over Rational Field + +``QQ[x]`` の有理数体上でローラン級数による級数展開を計算することができる: + +:: + + sage: R. = LaurentSeriesRing(QQ); R + Laurent Series Ring in x over Rational Field + sage: 1/(1-x) + O(x^10) + 1 + x + x^2 + x^3 + x^4 + x^5 + x^6 + x^7 + x^8 + x^9 + O(x^10) + + +異なる変数名を割り当てて生成した1変数多項式環は,ぞれぞれ異なる環と見なされる. + + +:: + + sage: R. = PolynomialRing(QQ) + sage: S. = PolynomialRing(QQ) + sage: x == y + False + sage: R == S + False + sage: R(y) + x + sage: R(y^2 - 17) + x^2 - 17 + + +環は変数名によって識別される. +同じ変数 ``x`` を使うと,異なる環をもう1つ作ったつもりでいても,そうはならないことに注意してほしい. + + +:: + + sage: R = PolynomialRing(QQ, "x") + sage: T = PolynomialRing(QQ, "x") + sage: R == T + True + sage: R is T + True + sage: R.0 == T.0 + True + +Sageでは,任意の基底環上で巾級数環およびローラン級数環を扱うことができる. +以下の例では, :math:`\GF{7}[[T]]` の元を生成し,ついでその逆数をとって :math:`\GF{7}((T))` の元を作っている. + +:: + + sage: R. = PowerSeriesRing(GF(7)); R + Power Series Ring in T over Finite Field of size 7 + sage: f = T + 3*T^2 + T^3 + O(T^4) + sage: f^3 + T^3 + 2*T^4 + 2*T^5 + O(T^6) + sage: 1/f + T^-1 + 4 + T + O(T^2) + sage: parent(1/f) + Laurent Series Ring in T over Finite Field of size 7 + +巾級数環を生成するには,二重括弧を使う省略記法を用いることもできる: + + +:: + + sage: GF(7)[['T']] + Power Series Ring in T over Finite Field of size 7 + + + +多変数多項式 +------------------------ + +複数個の変数を含む多項式を扱うには,まず多項式環と変数を宣言する. +:: + + sage: R = PolynomialRing(GF(5),3,"z") # 3 = 変数の数 + sage: R + Multivariate Polynomial Ring in z0, z1, z2 over Finite Field of size 5 + + +1変数の多項式を定義したときと同じように,他の方法もある: + +:: + + sage: GF(5)['z0, z1, z2'] + Multivariate Polynomial Ring in z0, z1, z2 over Finite Field of size 5 + sage: R. = GF(5)[]; R + Multivariate Polynomial Ring in z0, z1, z2 over Finite Field of size 5 + + +さらに,変数名を1文字にしたければ,以下のような略記法を使えばよい: + +:: + + sage: PolynomialRing(GF(5), 3, 'xyz') + Multivariate Polynomial Ring in x, y, z over Finite Field of size 5 + + +ここで,ちょっと計算してみよう. + +:: + + sage: z = GF(5)['z0, z1, z2'].gens() + sage: z + (z0, z1, z2) + sage: (z[0]+z[1]+z[2])^2 + z0^2 + 2*z0*z1 + z1^2 + 2*z0*z2 + 2*z1*z2 + z2^2 + + +多項式環を生成するには,もっと数学寄りの記号法を使うこともできる. + + +:: + + sage: R = GF(5)['x,y,z'] + sage: x,y,z = R.gens() + sage: QQ['x'] + Univariate Polynomial Ring in x over Rational Field + sage: QQ['x,y'].gens() + (x, y) + sage: QQ['x'].objgens() + (Univariate Polynomial Ring in x over Rational Field, (x,)) + + +Sageの多変数多項式は,多項式に対する分配表現(distributive representation)とPyhonのディクショナリを使って実装されている. +gcdやイデアルのグレブナー基底の計算にはSingular [Si]_ を経由している部分がある. + +:: + + sage: R, (x, y) = PolynomialRing(RationalField(), 2, 'xy').objgens() + sage: f = (x^3 + 2*y^2*x)^2 + sage: g = x^2*y^2 + sage: f.gcd(g) + x^2 + + +次に, :math:`f` と :math:`g` から生成されるイデアル :math:`(f,g)` を求めてみる. +これには ``(f,g)`` に ``R`` を掛けてやるだけでよい(``ideal([f,g])`` あるいは ``ideal(f,g)`` としても同じだ). + +.. link + + +:: + + sage: I = (f, g)*R; I + Ideal (x^6 + 4*x^4*y^2 + 4*x^2*y^4, x^2*y^2) of Multivariate Polynomial + Ring in x, y over Rational Field + sage: B = I.groebner_basis(); B + [x^6, x^2*y^2] + sage: x^2 in I + False + + +ちなみに,上のグレブナー基底はリストではなく不変性シーケンスとして与えられている. +これは,基底がユニバースまたは親クラスとなっているため変更できないことを意味している(グレブナー基底が変えられてしまうとその基底系に依存するルーチン群も働かなくなるから当然のことだ). + +.. link + + +:: + + sage: B.parent() + + sage: B.universe() + Multivariate Polynomial Ring in x, y over Rational Field + sage: B[1] = x + Traceback (most recent call last): + ... + ValueError: object is immutable; please change a copy instead. + +(種類はまだ十分ではないものの)可換代数の中にはSigular経由でSage上に実装されているものがある. +例えば, :math:`I`: の準素分解および随伴素イデアルを求めることができる: + + +.. link + +:: + + sage: I.primary_decomposition() + [Ideal (x^2) of Multivariate Polynomial Ring in x, y over Rational Field, + Ideal (y^2, x^6) of Multivariate Polynomial Ring in x, y over Rational Field] + sage: I.associated_primes() + [Ideal (x) of Multivariate Polynomial Ring in x, y over Rational Field, + Ideal (y, x) of Multivariate Polynomial Ring in x, y over Rational Field] diff --git a/src/doc/ja/tutorial/tour_rings.rst b/src/doc/ja/tutorial/tour_rings.rst new file mode 100644 index 00000000000..c62410b7812 --- /dev/null +++ b/src/doc/ja/tutorial/tour_rings.rst @@ -0,0 +1,149 @@ +.. _section-rings: + +基本的な環 +============= + +行列やベクトル,あるいは多項式を定義する際,定義の土台となる「環」を指定することが有利に働くだけではなく,不可欠ですらある場合がある. +*環* (ring)とは,和と積が質よくふるまうことが保証されている数学的構造物のことだ. +これまで聞いたことがなかったとしても,以下にあげる四つの広く使われている環についてだけは知っておくべきだろう: + +* 整数 `\{..., -1, 0, 1, 2, ...\}`, Sageでは ``ZZ`` で呼ぶ. +* 有理数 -- つまり整数による分数あるいは比 -- ``QQ`` で呼ぶ. +* 実数, ``RR`` で呼ぶ. +* 複素数, ``CC`` で呼ぶ. + +これらの環の違いについて意識しておきたいのは,例えば同じ多項式であってもそれがどの環の上で定義されるかによって異なった扱いがなされることがあるからだ. +具体例をあげると,多項式 `x^2-2` は二つの根 `\pm \sqrt{2}` を持つ. +両方とも有理数ではないから,もし有理係数の多項式として扱うのならばこの多項式は因数分解できない. +しかし,実係数の多項式として扱うのなら分解可能だ. +とすれば,求める情報を確実に得られる環を指定しておきたくなるのも当然だろう. +以下の二つのコマンドは,有理係数と実係数の多項式の集合を定義する. +集合はそれぞれ ``ratpoly`` と ``realpoly`` と命名されているが,大切なのはその名前ではない. +むしろ注意すべきは,各々の集合で使われる *変数名* を定義しているのが文字列 ``.`` と ``.`` であることである. + + +:: + + sage: ratpoly. = PolynomialRing(QQ) + sage: realpoly. = PolynomialRing(RR) + + +ここで `x^2-2` の因数分解の様子を見てみよう: + +.. link + +:: + + sage: factor(t^2-2) + t^2 - 2 + sage: factor(z^2-2) + (z - 1.41421356237310) * (z + 1.41421356237310) + + +以上と同様の注意点は,行列に対しても通用する. +既約行階段形式は元の行列がどんな環の上で定義されているかに依存するし,固有値と固有ベクトルも同様である. +多項式の構成法については :ref:`section-poly` 節に,行列に関しては :ref:`section-linalg` 節にもっと詳しい解説がある. + + +記号 ``I`` は :math:`-1` の平方根を表わし, ``i`` は ``I`` と同義である. +言うまでもなく,これは有理数ではない. + + +:: + + sage: i # -1の平方根 + I + sage: i in QQ + False + + +上のコードは,変数 ``i`` が反復制御に使われるなどして,違った値が割り当てられていたら予期した結果にならないことがある. +そんな場合には,次のように入力すると元の虚数単位 ``i`` として復活する: + +:: + + sage: reset('i') + + + +複素数の定義には一つ微妙な点がある. +すでに述べたように記号 ``i`` は `-1` の平方根を表わすが,これはあくまでも `-1` の *形式的* あるいは *シンボリック* な平方根である. +一方, ``CC(i)`` または ``CC.0`` を実行すると戻ってくるのは `-1` の *複素* 平方根である. +異なる種類の数同士の演算は,いわゆる「型強制」(coercion)によって可能になる. +これについては :ref:`section-coercion` 節を参照. + + +:: + + sage: i = CC(i) # 複素浮動小数点数 + sage: i == CC.0 + True + sage: a, b = 4/3, 2/3 + sage: z = a + b*i + sage: z + 1.33333333333333 + 0.666666666666667*I + sage: z.imag() # 虚部 + 0.666666666666667 + sage: z.real() == a # 比較の前に自動的に型変換される + True + sage: a + b + 2 + sage: 2*b == a + True + sage: parent(2/3) + Rational Field + sage: parent(4/2) + Rational Field + sage: 2/3 + 0.1 # 加算の前に自動型変換 + 0.766666666666667 + sage: 0.1 + 2/3 # SAGEの型変換規則は対称的である + 0.766666666666667 + + +もう少しSageで基本となる環の実例をあげておこう. +先に示したように,有理数の環は ``QQ`` あるいは ``RationalField()`` で参照することができる(*体(field)* とは積演算が可換で,非零元が常に逆元をもつ環のことである. +したがって有理数は体を構成するが整数は体にならない). + +:: + + sage: RationalField() + Rational Field + sage: QQ + Rational Field + sage: 1/2 in QQ + True + + +有理数でもある小数は有理数に「型強制」することができるから, +例えば小数 ``1.2`` は ``QQ`` に属すると見なされる( :ref:`section-coercion` 節を参照). +しかし, `\pi` と `\sqrt{2}` は有理数にはならない: + + +:: + + sage: 1.2 in QQ + True + sage: pi in QQ + False + sage: pi in RR + True + sage: sqrt(2) in QQ + False + sage: sqrt(2) in CC + True + + +より高度な数学で利用するため,Sageには有限体,p-進整数,代数環,多項式環,そして行列環などが用意されている. +以下ではその中のいくつかを構成してみよう. + + +:: + + sage: GF(3) + Finite Field of size 3 + sage: GF(27, 'a') # 素数体でなければ生成元の命名が必要 + Finite Field in a of size 3^3 + sage: Zp(5) + 5-adic Ring with capped relative precision 20 + sage: sqrt(3) in QQbar # QQの代数的閉包(拡大) + True diff --git a/src/doc/pt/tutorial/tour_coercion.rst b/src/doc/pt/tutorial/tour_coercion.rst index 58ca4301876..569caee10fa 100644 --- a/src/doc/pt/tutorial/tour_coercion.rst +++ b/src/doc/pt/tutorial/tour_coercion.rst @@ -121,7 +121,9 @@ categorias matemáticas também são implementadas no Sage: sage: Rings() Category of rings sage: ZZ.category() - Join of Category of euclidean domains and Category of infinite enumerated sets + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces sage: ZZ.category().is_subcategory(Rings()) True diff --git a/src/module_list.py b/src/module_list.py index d0a48218689..045dd2923d9 100644 --- a/src/module_list.py +++ b/src/module_list.py @@ -1221,11 +1221,10 @@ def uname_specific(name, value, alternative): libraries=['ntl'], language = 'c++'), - OptionalExtension("sage.rings.complex_ball_acb", - ["sage/rings/complex_ball_acb.pyx"], - libraries=['arb', 'mpfi', 'mpfr'], - include_dirs=[SAGE_INC + '/flint'], - package='arb'), + Extension("sage.rings.complex_ball_acb", + ["sage/rings/complex_ball_acb.pyx"], + libraries=['arb', 'mpfi', 'mpfr'], + include_dirs=[SAGE_INC + '/flint']), Extension('sage.rings.complex_double', sources = ['sage/rings/complex_double.pyx'], @@ -1294,11 +1293,10 @@ def uname_specific(name, value, alternative): Extension('sage.rings.real_interval_absolute', sources = ['sage/rings/real_interval_absolute.pyx']), - OptionalExtension("sage.rings.real_arb", - ["sage/rings/real_arb.pyx"], - libraries = ['arb', 'mpfi', 'mpfr'], - include_dirs = [SAGE_INC + '/flint'], - package = 'arb'), + Extension("sage.rings.real_arb", + ["sage/rings/real_arb.pyx"], + libraries = ['arb', 'mpfi', 'mpfr'], + include_dirs = [SAGE_INC + '/flint']), Extension('sage.rings.real_lazy', sources = ['sage/rings/real_lazy.pyx']), diff --git a/src/sage/algebras/catalog.py b/src/sage/algebras/catalog.py index 262e8113f44..06910534633 100644 --- a/src/sage/algebras/catalog.py +++ b/src/sage/algebras/catalog.py @@ -25,6 +25,7 @@ - :class:`algebras.Incidence ` - :class:`algebras.IwahoriHecke ` +- :class:`algebras.Mobius ` - :class:`algebras.Jordan ` - :class:`algebras.NilCoxeter @@ -56,6 +57,7 @@ lazy_import('sage.algebras.schur_algebra', 'SchurAlgebra', 'Schur') lazy_import('sage.algebras.commutative_dga', 'GradedCommutativeAlgebra', 'GradedCommutative') lazy_import('sage.combinat.posets.incidence_algebras', 'IncidenceAlgebra', 'Incidence') +lazy_import('sage.combinat.posets.mobius_algebra', 'MobiusAlgebra', 'Mobius') lazy_import('sage.combinat.free_prelie_algebra', 'FreePreLieAlgebra', 'FreePreLie') del lazy_import # We remove the object from here so it doesn't appear under tab completion diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index bb88f5e3bb4..0e570434890 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -524,8 +524,8 @@ def __init__(self, Q, names, category=None): sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: Cl = CliffordAlgebra(Q) sage: Cl.category() - Category of finite dimensional super algebras with basis - over (euclidean domains and infinite enumerated sets) + Category of finite dimensional super algebras with basis over + (euclidean domains and infinite enumerated sets and metric spaces) sage: TestSuite(Cl).run() TESTS: @@ -1838,9 +1838,9 @@ def lifted_bilinear_form(self, M): sage: M = Matrix(QQ, [[1, 2, 3], [2, 3, 4], [3, 4, 5]]) sage: Eform = E.lifted_bilinear_form(M) sage: Eform - Bilinear Form from Cartesian product of The exterior algebra of rank 3 over - Rational Field, The exterior algebra of rank 3 over Rational Field to - Rational Field + Bilinear Form from The exterior algebra of rank 3 over Rational + Field (+) The exterior algebra of rank 3 over Rational Field to + Rational Field sage: Eform(x*y, y*z) -1 sage: Eform(x*y, y) @@ -1911,8 +1911,8 @@ def lifted_form(x, y): # typing (:trac:`17124`). result += cx * cy * matr.determinant() return result - from sage.combinat.cartesian_product import CartesianProduct - return PoorManMap(lifted_form, domain=CartesianProduct(self, self), + from sage.categories.cartesian_product import cartesian_product + return PoorManMap(lifted_form, domain=cartesian_product([self, self]), codomain=self.base_ring(), name="Bilinear Form") diff --git a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra.py b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra.py index 4c842d11d4d..964ca1931be 100644 --- a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra.py +++ b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra.py @@ -18,17 +18,18 @@ from sage.rings.integer_ring import ZZ -from sage.categories.all import FiniteDimensionalAlgebrasWithBasis +from sage.categories.magmatic_algebras import MagmaticAlgebras from sage.matrix.constructor import Matrix from sage.matrix.matrix import is_Matrix from sage.modules.free_module_element import vector from sage.rings.ring import Algebra +from sage.structure.unique_representation import UniqueRepresentation from sage.misc.cachefunc import cached_method from functools import reduce -class FiniteDimensionalAlgebra(Algebra): +class FiniteDimensionalAlgebra(UniqueRepresentation, Algebra): """ Create a finite-dimensional `k`-algebra from a multiplication table. @@ -42,10 +43,11 @@ class FiniteDimensionalAlgebra(Algebra): elements - ``assume_associative`` -- (default: ``False``) boolean; if - ``True``, then methods requiring associativity assume this - without checking + ``True``, then the category is set to ``category.Associative()`` + and methods requiring associativity assume this - - ``category`` -- (default: ``FiniteDimensionalAlgebrasWithBasis(k)``) + - ``category`` -- (default: + ``MagmaticAlgebras(k).FiniteDimensional().WithBasis()``) the category to which this algebra belongs The list ``table`` must have the following form: there exists a @@ -59,13 +61,103 @@ class FiniteDimensionalAlgebra(Algebra): sage: A = FiniteDimensionalAlgebra(GF(3), [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [0, 0]])]) sage: A Finite-dimensional algebra of degree 2 over Finite Field of size 3 + sage: TestSuite(A).run() sage: B = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0,0], [0,1,0], [0,0,0]]), Matrix([[0,1,0], [0,0,0], [0,0,0]]), Matrix([[0,0,0], [0,0,0], [0,0,1]])]) sage: B Finite-dimensional algebra of degree 3 over Rational Field + + TESTS:: + + sage: A.category() + Category of finite dimensional magmatic algebras with basis over Finite Field of size 3 + sage: A = FiniteDimensionalAlgebra(GF(3), [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [0, 0]])], assume_associative=True) + sage: A.category() + Category of finite dimensional associative algebras with basis over Finite Field of size 3 """ + @staticmethod + def __classcall_private__(cls, k, table, names='e', assume_associative=False, + category=None): + """ + Normalize input. + + TESTS:: + + sage: table = [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [0, 0]])] + sage: A1 = FiniteDimensionalAlgebra(GF(3), table) + sage: A2 = FiniteDimensionalAlgebra(GF(3), table, names='e') + sage: A3 = FiniteDimensionalAlgebra(GF(3), table, names=['e0', 'e1']) + sage: A1 is A2 and A2 is A3 + True + + The ``assume_associative`` keyword is built into the category:: - def __init__(self, k, table, names='e', assume_associative=False, category=None): + sage: from sage.categories.magmatic_algebras import MagmaticAlgebras + sage: cat = MagmaticAlgebras(GF(3)).FiniteDimensional().WithBasis() + sage: A1 = FiniteDimensionalAlgebra(GF(3), table, category=cat.Associative()) + sage: A2 = FiniteDimensionalAlgebra(GF(3), table, assume_associative=True) + sage: A1 is A2 + True + + Uniqueness depends on the category:: + + sage: cat = Algebras(GF(3)).FiniteDimensional().WithBasis() + sage: A1 = FiniteDimensionalAlgebra(GF(3), table) + sage: A2 = FiniteDimensionalAlgebra(GF(3), table, category=cat) + sage: A1 == A2 + False + sage: A1 is A2 + False + + Checking that equality is still as expected:: + + sage: A = FiniteDimensionalAlgebra(GF(3), table) + sage: B = FiniteDimensionalAlgebra(GF(5), [Matrix([0])]) + sage: A == A + True + sage: B == B + True + sage: A == B + False + sage: A != A + False + sage: B != B + False + sage: A != B + True + """ + n = len(table) + table = [b.base_extend(k) for b in table] + for b in table: + b.set_immutable() + if not (is_Matrix(b) and b.dimensions() == (n, n)): + raise ValueError("input is not a multiplication table") + table = tuple(table) + + cat = MagmaticAlgebras(k).FiniteDimensional().WithBasis() + cat = cat.or_subcategory(category) + if assume_associative: + cat = cat.Associative() + + # TODO: Change once normalize_names is a static method or a function + #names = CategoryObject.normalize_names(n, names) + # TODO: ...and remove this + if isinstance(names, str): + if ',' in names: + names = names.split(',') + elif n != 1: + names = [names + str(i) for i in range(n)] + else: + names = [names] + names = tuple(names) + if len(names) != n: + # This is the same type of error that normalize_names throws - TCS + raise IndexError("the number of names must equal the number of generators") + + return super(FiniteDimensionalAlgebra, cls).__classcall__(cls, k, table, + names, category=cat) + + def __init__(self, k, table, names='e', category=None): """ TESTS:: @@ -99,14 +191,9 @@ def __init__(self, k, table, names='e', assume_associative=False, category=None) sage: E.gens() (e,) """ - n = len(table) - self._table = [b.base_extend(k) for b in table] - if not all([is_Matrix(b) and b.dimensions() == (n, n) for b in table]): - raise ValueError("input is not a multiplication table") - self._assume_associative = assume_associative + self._table = table + self._assume_associative = "Associative" in category.axioms() # No further validity checks necessary! - if category is None: - category = FiniteDimensionalAlgebrasWithBasis(k) Algebra.__init__(self, base_ring=k, names=names, category=category) def _repr_(self): @@ -172,7 +259,8 @@ def _Hom_(self, B, category): sage: A._Hom_(B, A.category()) Set of Homomorphisms from Finite-dimensional algebra of degree 1 over Rational Field to Finite-dimensional algebra of degree 2 over Rational Field """ - if category.is_subcategory(FiniteDimensionalAlgebrasWithBasis(self.base_ring())): + cat = MagmaticAlgebras(self.base_ring()).FiniteDimensional().WithBasis() + if category.is_subcategory(cat): from sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra_morphism import FiniteDimensionalAlgebraHomset return FiniteDimensionalAlgebraHomset(self, B, category=category) return super(FiniteDimensionalAlgebra, self)._Hom_(B, category) @@ -238,7 +326,8 @@ def __iter__(self): def _ideal_class_(self, n=0): """ - Return the ideal class of ``self``. + Return the ideal class of ``self`` (that is, the class that + all ideals of ``self`` inherit from). EXAMPLES:: @@ -257,10 +346,10 @@ def table(self): sage: A = FiniteDimensionalAlgebra(GF(3), [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [0, 0]])]) sage: A.table() - [ + ( [1 0] [0 1] [0 1], [0 0] - ] + ) """ return self._table @@ -273,41 +362,34 @@ def left_table(self): EXAMPLES:: sage: B = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0], [0,1]]), Matrix([[0,1],[-1,0]])]) - sage: B.left_table() - [ + sage: T = B.left_table(); T + ( [1 0] [ 0 1] [0 1], [-1 0] - ] - """ - B = self.table() - n = self.degree() - return [Matrix([B[j][i] for j in xrange(n)]) for i in xrange(n)] + ) - def __cmp__(self, other): - """ - Compare ``self`` to ``other``. - - EXAMPLES:: + We check immutability:: - sage: A = FiniteDimensionalAlgebra(GF(3), [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [0, 0]])]) - sage: B = FiniteDimensionalAlgebra(GF(5), [Matrix([0])]) - sage: cmp(A, A) - 0 - sage: cmp(B, B) - 0 - sage: cmp(A, B) - 1 + sage: T[0] = "vandalized by h4xx0r" + Traceback (most recent call last): + ... + TypeError: 'tuple' object does not support item assignment + sage: T[1][0] = [13, 37] + Traceback (most recent call last): + ... + ValueError: matrix is immutable; please change a copy instead + (i.e., use copy(M) to change a copy of M). """ - if not isinstance(other, FiniteDimensionalAlgebra): - return cmp(type(self), type(other)) - if self.base_ring() == other.base_ring(): - return cmp(self.table(), other.table()) - else: - return 1 + B = self.table() + n = self.degree() + table = [Matrix([B[j][i] for j in xrange(n)]) for i in xrange(n)] + for b in table: + b.set_immutable() + return tuple(table) def base_extend(self, F): """ - Return ``self`` base changed to ``F``. + Return ``self`` base changed to the field ``F``. EXAMPLES:: @@ -316,7 +398,7 @@ def base_extend(self, F): sage: C.base_extend(k) Finite-dimensional algebra of degree 1 over Finite Field in y of size 2^2 """ - # Base extension of the multiplication table is done by __init__. + # Base extension of the multiplication table is done by __classcall_private__. return FiniteDimensionalAlgebra(F, self.table()) def cardinality(self): @@ -350,9 +432,9 @@ def ideal(self, gens=None, given_by_matrix=False): - ``gens`` -- (default: None) - either an element of ``A`` or a list of elements of ``A``, given as vectors, matrices, or - FiniteDimensionalAlgebraElements. If ``given_by_matrix`` is ``True``, then - ``gens`` should instead be a matrix whose rows form a basis - of an ideal of ``A``. + FiniteDimensionalAlgebraElements. If ``given_by_matrix`` is + ``True``, then ``gens`` should instead be a matrix whose rows + form a basis of an ideal of ``A``. - ``given_by_matrix`` -- boolean (default: ``False``) - if ``True``, no checking is done @@ -443,6 +525,11 @@ def is_unitary(self): Return ``True`` if ``self`` has a two-sided multiplicative identity element. + .. WARNING:: + + This uses linear algebra; thus expect wrong results when + the base ring is not a field. + EXAMPLES:: sage: A = FiniteDimensionalAlgebra(QQ, []) @@ -461,25 +548,40 @@ def is_unitary(self): sage: D.is_unitary() False - .. NOTE:: + sage: E = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0],[1,0]]), Matrix([[0,1],[0,1]])]) + sage: E.is_unitary() + False - If a finite-dimensional algebra over a field admits a left identity, - then this is the unique left identity, and it is also a - right identity. + sage: F = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0,0], [0,1,0], [0,0,1]]), Matrix([[0,1,0], [0,0,0], [0,0,0]]), Matrix([[0,0,1], [0,0,0], [1,0,0]])]) + sage: F.is_unitary() + True + + sage: G = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0,0], [0,1,0], [0,0,1]]), Matrix([[0,1,0], [0,0,0], [0,0,0]]), Matrix([[0,1,0], [0,0,0], [1,0,0]])]) + sage: G.is_unitary() # Unique right identity, but no left identity. + False """ - k = self.base_ring() n = self.degree() - # B is obtained by concatenating the elements of - # self.table(), and v by concatenating the rows of - # the n times n identity matrix. - B = reduce(lambda x, y: x.augment(y), - self.table(), Matrix(k, n, 0)) - v = vector(Matrix.identity(k, n).list()) - try: - self._one = B.solve_left(v) + k = self.base_ring() + if n == 0: + self._one = vector(k, []) return True + B1 = reduce(lambda x, y: x.augment(y), + self._table, Matrix(k, n, 0)) + B2 = reduce(lambda x, y: x.augment(y), + self.left_table(), Matrix(k, n, 0)) + # This is the vector obtained by concatenating the rows of the + # n times n identity matrix: + kone = k.one() + kzero = k.zero() + v = vector(k, (n - 1) * ([kone] + n * [kzero]) + [kone]) + try: + sol1 = B1.solve_left(v) + sol2 = B2.solve_left(v) except ValueError: return False + assert sol1 == sol2 + self._one = sol1 + return True def is_zero(self): """ @@ -517,6 +619,16 @@ def one(self): Traceback (most recent call last): ... TypeError: algebra is not unitary + + sage: D = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0,0], [0,1,0], [0,0,1]]), Matrix([[0,1,0], [0,0,0], [0,0,0]]), Matrix([[0,0,1], [0,0,0], [1,0,0]])]) + sage: D.one() + e0 + + sage: E = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0,0], [0,1,0], [0,0,1]]), Matrix([[0,1,0], [0,0,0], [0,0,0]]), Matrix([[0,1,0], [0,0,0], [1,0,0]])]) + sage: E.one() + Traceback (most recent call last): + ... + TypeError: algebra is not unitary """ if not self.is_unitary(): raise TypeError("algebra is not unitary") diff --git a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.py b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.py index 898ceabe337..2b633114115 100644 --- a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.py +++ b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.py @@ -134,6 +134,28 @@ def matrix(self): """ return self._matrix + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements in + the support of ``self`` and whose values are the corresponding + coefficients. + + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + + EXAMPLES:: + + sage: B = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0], [0,1]]), Matrix([[0,1], [-1,0]])]) + sage: elt = B(Matrix([[1,1], [-1,1]])) + sage: elt.monomial_coefficients() + {0: 1, 1: 1} + """ + return self._vector.dict(copy) + def left_matrix(self): """ Return the matrix for multiplication by ``self`` from the left. @@ -344,6 +366,9 @@ def is_invertible(self): Return ``True`` if ``self`` has a two-sided multiplicative inverse. + This assumes that the algebra to which ``self`` belongs is + associative. + .. NOTE:: If an element of a unitary finite-dimensional algebra over a field @@ -366,6 +391,9 @@ def _inverse(self): The two-sided inverse of ``self``, if it exists; otherwise this is ``None``. + This assumes that the algebra to which ``self`` belongs is + associative. + EXAMPLES:: sage: C = FiniteDimensionalAlgebra(QQ, [Matrix([[1,0], [0,1]]), Matrix([[0,1], [-1,0]])]) @@ -391,11 +419,14 @@ def inverse(self): Return the two-sided multiplicative inverse of ``self``, if it exists. + This assumes that the algebra to which ``self`` belongs is + associative. + .. NOTE:: - If an element of a unitary finite-dimensional algebra over a field - admits a left inverse, then this is the unique left - inverse, and it is also a right inverse. + If an element of a finite-dimensional unitary associative + algebra over a field admits a left inverse, then this is the + unique left inverse, and it is also a right inverse. EXAMPLES:: diff --git a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_ideal.py b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_ideal.py index 4361661657c..07fe4bd9e04 100644 --- a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_ideal.py +++ b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_ideal.py @@ -84,7 +84,7 @@ def __eq__(self, other): sage: A2 = FiniteDimensionalAlgebra(GF(3), [Matrix([[1, 0], [0, 1]]), Matrix([[0, 1], [0, 0]])]) sage: A is A2 - False + True sage: A == A2 True sage: I2 = A.ideal(A([1,1])) diff --git a/src/sage/algebras/jordan_algebra.py b/src/sage/algebras/jordan_algebra.py index d434996437b..0f024e61113 100644 --- a/src/sage/algebras/jordan_algebra.py +++ b/src/sage/algebras/jordan_algebra.py @@ -556,6 +556,30 @@ def _rmul_(self, other): """ return self.__class__(self.parent(), other * self._x) + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements in + the support of ``self`` and whose values are the corresponding + coefficients. + + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + + EXAMPLES:: + + sage: F. = FreeAlgebra(QQ) + sage: J = JordanAlgebra(F) + sage: a,b,c = map(J, F.gens()) + sage: elt = a + 2*b - c + sage: elt.monomial_coefficients() + {x: 1, y: 2, z: -1} + """ + return self._x.monomial_coefficients(copy) + class JordanAlgebraSymmetricBilinear(JordanAlgebra): r""" A Jordan algebra given by a symmetric bilinear form `m`. @@ -935,6 +959,29 @@ def _rmul_(self, other): """ return self.__class__(self.parent(), other * self._s, other * self._v) + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements in + the support of ``self`` and whose values are the corresponding + coefficients. + + INPUT: + + - ``copy`` -- ignored + + EXAMPLES:: + + sage: m = matrix([[0,1],[1,1]]) + sage: J. = JordanAlgebra(m) + sage: elt = a + 2*b - c + sage: elt.monomial_coefficients() + {0: 1, 1: 2, 2: -1} + """ + d = {0: self._s} + for i,c in enumerate(self._v): + d[i+1] = c + return d + def trace(self): r""" Return the trace of ``self``. diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index e42bee8ae05..b591d5a18ee 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1735,7 +1735,17 @@ def ternary_quadratic_form(self, include_basis=False): return Q class QuaternionFractionalIdeal(Ideal_fractional): - pass + def __hash__(self): + r""" + Stupid constant hash function! + + TESTS:: + + sage: R = QuaternionAlgebra(-11,-1).maximal_order() + sage: hash(R.right_ideal(R.basis())) + 0 + """ + return 0 class QuaternionFractionalIdeal_rational(QuaternionFractionalIdeal): """ diff --git a/src/sage/algebras/schur_algebra.py b/src/sage/algebras/schur_algebra.py index 7ef9cfabbde..f3092e9cf3f 100644 --- a/src/sage/algebras/schur_algebra.py +++ b/src/sage/algebras/schur_algebra.py @@ -21,20 +21,24 @@ .. [GreenPoly] J. Green, Polynomial representations of `GL_n`, Springer Verlag. """ + #***************************************************************************** -# Copyright (C) 2010 Eric Webster -# Copyright (C) 2011 Hugh Thomas (hugh.ross.thomas@gmail.com) +# Copyright (C) 2010 Eric Webster +# Copyright (C) 2011 Hugh Thomas # -# Distributed under the terms of the GNU General Public License (GPL) +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools from sage.categories.all import AlgebrasWithBasis from sage.categories.rings import Rings from sage.combinat.free_module import CombinatorialFreeModule, CombinatorialFreeModule_Tensor -from sage.combinat.cartesian_product import CartesianProduct -from sage.combinat.integer_list import IntegerListsLex +from sage.combinat.integer_lists import IntegerListsLex from sage.combinat.partition import Partitions, Partition from sage.combinat.permutation import Permutations from sage.combinat.sf.sf import SymmetricFunctions @@ -465,7 +469,7 @@ def _monomial_product(self, xi, v): B[1] # B[1] # B[2] + B[1] # B[2] # B[1] + B[2] # B[1] # B[1] """ ret = [] - for i in CartesianProduct(*[range(1, self._n + 1)] * self._r): + for i in itertools.product(range(1, self._n + 1), repeat=self._r): if schur_representative_from_index(i, v) == xi: ret.append(tuple(i)) return self.sum_of_monomials(ret) diff --git a/src/sage/algebras/steenrod/steenrod_algebra.py b/src/sage/algebras/steenrod/steenrod_algebra.py index de25ebb1564..e98c6d2474e 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra.py +++ b/src/sage/algebras/steenrod/steenrod_algebra.py @@ -404,12 +404,13 @@ 1 (2, 1) sage: c.monomial_coefficients() {(2, 1): 1, (5,): 1} - sage: c.monomials() + sage: sorted(c.monomials(), key=lambda x: x.support()) [Sq(2,1), Sq(5)] - sage: c.support() + sage: sorted(c.support()) [(2, 1), (5,)] sage: Adem = SteenrodAlgebra(basis='adem') - sage: (Adem.Sq(10) + Adem.Sq(9) * Adem.Sq(1)).monomials() + sage: elt = Adem.Sq(10) + Adem.Sq(9) * Adem.Sq(1) + sage: sorted(elt.monomials(), key=lambda x: x.support()) [Sq^9 Sq^1, Sq^10] sage: A7 = SteenrodAlgebra(p=7) @@ -1567,13 +1568,13 @@ def counit_on_basis(self, t): return self.base_ring().one() def _milnor_on_basis(self, t): - """ - Convert the tuple t in the current basis to an element in the + r""" + Convert the tuple ``t`` in the current basis to an element in the Milnor basis. INPUT: - - t - tuple, representing basis element in the current basis. + - ``t`` - tuple, representing basis element in the current basis. OUTPUT: element of the Steenrod algebra with the Milnor basis @@ -3115,9 +3116,9 @@ class Element(CombinatorialFreeModuleElement): 1 (2, 1) sage: c.monomial_coefficients() {(2, 1): 1, (5,): 1} - sage: c.monomials() + sage: sorted(c.monomials(), key=lambda x: x.support()) [Sq(2,1), Sq(5)] - sage: c.support() + sage: sorted(c.support()) [(2, 1), (5,)] See the documentation for this module (type @@ -3451,8 +3452,8 @@ def excess(self): OUTPUT: ``excess`` - non-negative integer The excess of a Milnor basis element `\text{Sq}(a,b,c,...)` is - `a + b + c + ...`. When `p` is odd, the excess of `Q_{0}^{e_0} - Q_{1}^{e_1} ... P(r_1, r_2, ...)` is `\sum e_i + 2 \sum r_i`. + `a + b + c + \cdots`. When `p` is odd, the excess of `Q_{0}^{e_0} + Q_{1}^{e_1} \cdots P(r_1, r_2, ...)` is `\sum e_i + 2 \sum r_i`. The excess of a linear combination of Milnor basis elements is the minimum of the excesses of those basis elements. @@ -3470,9 +3471,11 @@ def excess(self): 6 sage: (Sq(0,0,1) + Sq(4,1) + Sq(7)).excess() 1 - sage: [m.excess() for m in (Sq(0,0,1) + Sq(4,1) + Sq(7)).monomials()] + sage: elt = Sq(0,0,1) + Sq(4,1) + Sq(7) + sage: M = sorted(elt.monomials(), key=lambda x: x.support()) + sage: [m.excess() for m in M] [1, 5, 7] - sage: [m for m in (Sq(0,0,1) + Sq(4,1) + Sq(7)).monomials()] + sage: [m for m in M] [Sq(0,0,1), Sq(4,1), Sq(7)] sage: B = SteenrodAlgebra(7) sage: a = B.Q(1,2,5) @@ -3495,7 +3498,7 @@ def excess_odd(mono): of factors, plus twice the sum of the terms in the second component. """ - if len(mono) == 0: + if not mono: return 0 else: return len(mono[0]) + 2 * sum(mono[1]) diff --git a/src/sage/algebras/weyl_algebra.py b/src/sage/algebras/weyl_algebra.py index d94fbd37f9d..af3f004c2aa 100644 --- a/src/sage/algebras/weyl_algebra.py +++ b/src/sage/algebras/weyl_algebra.py @@ -402,10 +402,39 @@ def _lmul_(self, other): M = self.__monomials return self.__class__(self.parent(), {t: M[t]*other for t in M}) + def monomial_coefficients(self, copy=True): + """ + Return a dictionary which has the basis keys in the support + of ``self`` as keys and their corresponding coefficients + as values. + + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + + EXAMPLES:: + + sage: W. = DifferentialWeylAlgebra(QQ) + sage: dx,dy,dz = W.differentials() + sage: elt = (dy - (3*x - z)*dx) + sage: sorted(elt.monomial_coefficients().items()) + [(((0, 0, 0), (0, 1, 0)), 1), + (((0, 0, 1), (1, 0, 0)), 1), + (((1, 0, 0), (1, 0, 0)), -3)] + """ + if copy: + return dict(self.__monomials) + return self.__monomials + def __iter__(self): """ Return an iterator of ``self``. + This is the iterator of ``self.list()``. + EXAMPLES:: sage: W. = DifferentialWeylAlgebra(QQ) @@ -421,6 +450,11 @@ def list(self): """ Return ``self`` as a list. + This list consists of pairs `(m, c)`, where `m` is a pair of + tuples indexing a basis element of ``self``, and `c` is the + coordinate of ``self`` corresponding to this basis element. + (Only nonzero coordinates are shown.) + EXAMPLES:: sage: W. = DifferentialWeylAlgebra(QQ) @@ -738,13 +772,21 @@ def basis(self): sage: [next(it) for i in range(20)] [1, x, y, dx, dy, x^2, x*y, x*dx, x*dy, y^2, y*dx, y*dy, dx^2, dx*dy, dy^2, x^3, x^2*y, x^2*dx, x^2*dy, x*y^2] + sage: dx, dy = W.differentials() + sage: (dx*x).monomials() + [1, x*dx] + sage: B[(x*y).support()[0]] + x*y + sage: sorted((dx*x).monomial_coefficients().items()) + [(((0, 0), (0, 0)), 1), (((1, 0), (1, 0)), 1)] """ n = self._n - # TODO in #17927: use IntegerVectors(length=2*n) - from sage.combinat.integer_list import IntegerListsNN - I = IntegerListsNN(length=n*2) + from sage.combinat.integer_lists.nn import IntegerListsNN + from sage.categories.cartesian_product import cartesian_product + elt_map = lambda u : (tuple(u[:n]), tuple(u[n:])) + I = IntegerListsNN(length=2*n, element_constructor=elt_map) one = self.base_ring().one() - f = lambda x: self.element_class(self, {(tuple(x[:n]),tuple(x[n:])): one}) + f = lambda x: self.element_class(self, {(x[0], x[1]): one}) return Family(I, f, name="basis map") @cached_method diff --git a/src/sage/calculus/calculus.py b/src/sage/calculus/calculus.py index c895e9cb135..5480a5f4632 100644 --- a/src/sage/calculus/calculus.py +++ b/src/sage/calculus/calculus.py @@ -13,7 +13,7 @@ - Golam Mortuza Hossain (2009-06-22): _laplace_latex(), _inverse_laplace_latex() -- Tom Coates (2010-06-11): fixed Trac #9217 +- Tom Coates (2010-06-11): fixed :trac:`9217` The Sage calculus module is loosely based on the Sage Enhancement Proposal found at: http://www.sagemath.org:9001/CalculusSEP. @@ -1768,7 +1768,7 @@ def symbolic_expression_from_maxima_string(x, equals_sub=False, maxima=maxima): sage: solve([2*x==3, x != 5], x) [[x == (3/2), (-7/2) != 0]] - Make sure that we don't accidentally pick up variables in the maxima namespace (trac #8734):: + Make sure that we don't accidentally pick up variables in the maxima namespace (:trac:`8734`):: sage: sage.calculus.calculus.maxima('my_new_var : 2') 2 diff --git a/src/sage/calculus/functions.py b/src/sage/calculus/functions.py index 29482d915fd..30483478dd2 100644 --- a/src/sage/calculus/functions.py +++ b/src/sage/calculus/functions.py @@ -94,7 +94,7 @@ def wronskian(*args): row = lambda n: [diff(f, n) for f in fs] # NOTE: I rewrote the below as two lines to avoid a possible subtle # memory management problem on some platforms (only VMware as far - # as we know?). See trac #2990. + # as we know?). See :trac:`2990`. # There may still be a real problem that this is just hiding for now. A = matrix([row(_) for _ in range(len(fs))]) return A.determinant() diff --git a/src/sage/categories/additive_magmas.py b/src/sage/categories/additive_magmas.py index e84ede20193..3f990e84a0a 100644 --- a/src/sage/categories/additive_magmas.py +++ b/src/sage/categories/additive_magmas.py @@ -711,9 +711,11 @@ def _test_zero(self, **options): tester.assert_(self.is_parent_of(zero)) for x in tester.some_elements(): tester.assert_(x + zero == x) - # Check that zero is immutable by asking its hash: - tester.assertEqual(type(zero.__hash__()), int) - tester.assertEqual(zero.__hash__(), zero.__hash__()) + # Check that zero is immutable if it looks like we can: + if hasattr(zero,"is_immutable"): + tester.assertEqual(zero.is_immutable(),True) + if hasattr(zero,"is_mutable"): + tester.assertEqual(zero.is_mutable(),False) # Check that bool behave consistently on zero tester.assertFalse(bool(self.zero())) diff --git a/src/sage/categories/additive_semigroups.py b/src/sage/categories/additive_semigroups.py index 7f9c2de9b5c..bb45bd19e8b 100644 --- a/src/sage/categories/additive_semigroups.py +++ b/src/sage/categories/additive_semigroups.py @@ -80,8 +80,8 @@ def _test_additive_associativity(self, **options): """ tester = self._tester(**options) S = tester.some_elements() - from sage.combinat.cartesian_product import CartesianProduct - for x,y,z in tester.some_elements(CartesianProduct(S,S,S)): + from sage.misc.misc import some_tuples + for x,y,z in some_tuples(S, 3, tester._max_runs): tester.assert_((x + y) + z == x + (y + z)) class Homsets(HomsetsCategory): diff --git a/src/sage/categories/algebras_with_basis.py b/src/sage/categories/algebras_with_basis.py index a490faf7c31..ee0ad3865ae 100644 --- a/src/sage/categories/algebras_with_basis.py +++ b/src/sage/categories/algebras_with_basis.py @@ -200,7 +200,7 @@ class ElementMethods: def __invert__(self): """ - Returns the inverse of self if self is a multiple of one, + Return the inverse of ``self`` if ``self`` is a multiple of one, and one is in the basis of this algebra. Otherwise throws an error. @@ -209,6 +209,14 @@ def __invert__(self): inversed this way. It is correct though for graded connected algebras with basis. + .. WARNING:: + + This might produce a result which does not belong to + the parent of ``self``, yet believes to do so. For + instance, inverting 2 times the unity will produce 1/2 + times the unity, even if 1/2 is not in the base ring. + Handle with care. + EXAMPLES:: sage: C = AlgebrasWithBasis(QQ).example() @@ -224,17 +232,18 @@ def __invert__(self): ValueError: cannot invert self (= B[word: a]) """ # FIXME: make this generic - mcs = self._monomial_coefficients + mcs = self.monomial_coefficients(copy=False) one = self.parent().one_basis() if len(mcs) == 1 and one in mcs: - return self.parent()( ~mcs[ one ] ) + return self.parent().term(one, ~mcs[one]) else: raise ValueError("cannot invert self (= %s)"%self) class CartesianProducts(CartesianProductsCategory): """ - The category of algebras with basis, constructed as cartesian products of algebras with basis + The category of algebras with basis, constructed as cartesian + products of algebras with basis. Note: this construction give the direct products of algebras with basis. See comment in :class:`Algebras.CartesianProducts diff --git a/src/sage/categories/all.py b/src/sage/categories/all.py index cae2f9dec34..279174d751a 100644 --- a/src/sage/categories/all.py +++ b/src/sage/categories/all.py @@ -2,10 +2,11 @@ from category_types import( Elements, - SimplicialComplexes, ChainComplexes, ) +from sage.categories.simplicial_complexes import SimplicialComplexes + from tensor import tensor from cartesian_product import cartesian_product diff --git a/src/sage/categories/bialgebras.py b/src/sage/categories/bialgebras.py index e779d8acb08..fd7ff083689 100644 --- a/src/sage/categories/bialgebras.py +++ b/src/sage/categories/bialgebras.py @@ -12,6 +12,7 @@ from sage.categories.category_types import Category_over_base_ring from sage.categories.all import Algebras, Coalgebras from sage.categories.super_modules import SuperModulesCategory +from sage.misc.lazy_import import LazyImport class Bialgebras(Category_over_base_ring): """ @@ -60,3 +61,5 @@ def additional_structure(self): class Super(SuperModulesCategory): pass + WithBasis = LazyImport('sage.categories.bialgebras_with_basis', 'BialgebrasWithBasis') + diff --git a/src/sage/categories/bialgebras_with_basis.py b/src/sage/categories/bialgebras_with_basis.py index 0487c0db3a3..d0fd23e870f 100644 --- a/src/sage/categories/bialgebras_with_basis.py +++ b/src/sage/categories/bialgebras_with_basis.py @@ -9,8 +9,12 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -def BialgebrasWithBasis(base_ring): - """ +from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring +from sage.categories.modules_with_basis import ModulesWithBasis +from sage.categories.tensor import tensor + +class BialgebrasWithBasis(CategoryWithAxiom_over_base_ring): + r""" The category of bialgebras with a distinguished basis. EXAMPLES:: @@ -27,6 +31,378 @@ def BialgebrasWithBasis(base_ring): sage: TestSuite(BialgebrasWithBasis(ZZ)).run() """ - from sage.categories.all import Bialgebras - return Bialgebras(base_ring).WithBasis() + class ParentMethods: + + def convolution_product(self, *maps): + r""" + Return the convolution product (a map) of the given maps. + + Let `A` and `B` be bialgebras over a commutative ring `R`. + Given maps `f_i : A \to B` for `1 \leq i < n`, define the + convolution product + + .. MATH:: + + (f_1 * f_2 * \cdots * f_n) := \mu^{(n-1)} \circ (f_1 \otimes + f_2 \otimes \cdots \otimes f_n) \circ \Delta^{(n-1)}, + + where `\Delta^{(k)} := \bigl(\Delta \otimes + \mathrm{Id}^{\otimes(k-1)}\bigr) \circ \Delta^{(k-1)}`, + with `\Delta^{(1)} = \Delta` (the ordinary coproduct in `A`) and + `\Delta^{(0)} = \mathrm{Id}`; and with `\mu^{(k)} := \mu \circ + \bigl(\mu^{(k-1)} \otimes \mathrm{Id})` and `\mu^{(1)} = \mu` + (the ordinary product in `B`). See [Sw1969]_. + + (In the literature, one finds, e.g., `\Delta^{(2)}` for what we + denote above as `\Delta^{(1)}`. See [KMN2012]_.) + + INPUT: + + - ``maps`` -- any number `n \geq 0` of linear maps `f_1, f_2, + \ldots, f_n` on ``self``; or a single ``list`` or ``tuple`` + of such maps + + OUTPUT: + + - the new map `f_1 * f_2 * \cdots * f_2` representing their + convolution product + + .. SEEALSO:: + + :meth:`sage.categories.bialgebras.ElementMethods.convolution_product` + + AUTHORS: + + - Aaron Lauve - 12 June 2015 - Sage Days 65 + + .. TODO:: + + Remove dependency on ``modules_with_basis`` methods. + + EXAMPLES: + + We construct some maps: the identity, the antipode and + projection onto the homogeneous componente of degree 2:: + + sage: Id = lambda x: x + sage: Antipode = lambda x: x.antipode() + sage: Proj2 = lambda x: x.parent().sum_of_terms([(m, c) for (m, c) in x if m.size() == 2]) + + Compute the convolution product of the identity with itself and + with the projection ``Proj2`` on the Hopf algebra of + non-commutative symmetric functions:: + + sage: R = NonCommutativeSymmetricFunctions(QQ).ribbon() + sage: T = R.convolution_product([Id, Id]) + sage: [T(R(comp)) for comp in Compositions(3)] + [4*R[1, 1, 1] + R[1, 2] + R[2, 1], + 2*R[1, 1, 1] + 4*R[1, 2] + 2*R[2, 1] + 2*R[3], + 2*R[1, 1, 1] + 2*R[1, 2] + 4*R[2, 1] + 2*R[3], + R[1, 2] + R[2, 1] + 4*R[3]] + sage: T = R.convolution_product(Proj2, Id) + sage: [T(R([i])) for i in range(1, 5)] + [0, R[2], R[2, 1] + R[3], R[2, 2] + R[4]] + + Compute the convolution product of no maps on the Hopf algebra of + symmetric functions in non-commuting variables. This is the + composition of the counit with the unit:: + + sage: m = SymmetricFunctionsNonCommutingVariables(QQ).m() + sage: T = m.convolution_product() + sage: [T(m(lam)) for lam in SetPartitions(0).list() + SetPartitions(2).list()] + [m{}, 0, 0] + + Compute the convolution product of the projection ``Proj2`` with + the identity on the Hopf algebra of symmetric functions in + non-commuting variables:: + + sage: T = m.convolution_product(Proj2, Id) + sage: [T(m(lam)) for lam in SetPartitions(3)] + [0, + m{{1, 2}, {3}} + m{{1, 2, 3}}, + m{{1, 2}, {3}} + m{{1, 2, 3}}, + m{{1, 2}, {3}} + m{{1, 2, 3}}, + 3*m{{1}, {2}, {3}} + 3*m{{1}, {2, 3}} + 3*m{{1, 3}, {2}}] + + Compute the convolution product of the antipode with itself and the + identity map on group algebra of the symmetric group:: + + sage: G = SymmetricGroup(3) + sage: QG = GroupAlgebra(G, QQ) + sage: x = QG.sum_of_terms([(p,p.number_of_peaks() + p.number_of_inversions()) for p in Permutations(3)]); x + 2*[1, 3, 2] + [2, 1, 3] + 3*[2, 3, 1] + 2*[3, 1, 2] + 3*[3, 2, 1] + sage: T = QG.convolution_product(Antipode, Antipode, Id) + sage: T(x) + 2*[1, 3, 2] + [2, 1, 3] + 2*[2, 3, 1] + 3*[3, 1, 2] + 3*[3, 2, 1] + """ + onbasis = lambda x: self.term(x).convolution_product(*maps) + return self.module_morphism(on_basis=onbasis, codomain=self) + + class ElementMethods: + + def adams_operator(self, n): + r""" + Compute the `n`-th convolution power of the identity morphism + `\mathrm{Id}` on ``self``. + + INPUT: + + - ``n`` -- a nonnegative integer + + OUTPUT: + + - the image of ``self`` under the convolution power `\mathrm{Id}^{*n}` + + .. NOTE:: + + In the literature, this is also called a Hopf power or + Sweedler power, cf. [AL2015]_. + + .. SEEALSO:: + + :meth:`sage.categories.bialgebras.ElementMethods.convolution_product` + + REFERENCES: + + .. [AL2015] *The characteristic polynomial of the Adams operators + on graded connected Hopf algebras*. + Marcelo Aguiar and Aaron Lauve. + Algebra Number Theory, v.9, 2015, n.3, 2015. + + .. TODO:: + + Remove dependency on ``modules_with_basis`` methods. + + EXAMPLES:: + + sage: h = SymmetricFunctions(QQ).h() + sage: h[5].adams_operator(2) + 2*h[3, 2] + 2*h[4, 1] + 2*h[5] + sage: h[5].plethysm(2*h[1]) + 2*h[3, 2] + 2*h[4, 1] + 2*h[5] + sage: h([]).adams_operator(0) + h[] + sage: h([]).adams_operator(1) + h[] + sage: h[3,2].adams_operator(0) + 0 + sage: h[3,2].adams_operator(1) + h[3, 2] + + :: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: S[4].adams_operator(5) + 5*S[1, 1, 1, 1] + 10*S[1, 1, 2] + 10*S[1, 2, 1] + 10*S[1, 3] + 10*S[2, 1, 1] + 10*S[2, 2] + 10*S[3, 1] + 5*S[4] + + + :: + + sage: m = SymmetricFunctionsNonCommutingVariables(QQ).m() + sage: m[[1,3],[2]].adams_operator(-2) + 3*m{{1}, {2, 3}} + 3*m{{1, 2}, {3}} + 6*m{{1, 2, 3}} - 2*m{{1, 3}, {2}} + """ + if n < 0: + if hasattr(self, 'antipode'): + T = lambda x: x.antipode() + n = abs(n) + else: + raise ValueError("antipode not defined; cannot take negative convolution powers: {} < 0".format(n)) + else: + T = lambda x: x + return self.convolution_product([T] * n) + + def convolution_product(self, *maps): + r""" + Return the image of ``self`` under the convolution product (map) of + the maps. + + Let `A` and `B` be bialgebras over a commutative ring `R`. + Given maps `f_i : A \to B` for `1 \leq i < n`, define the + convolution product + + .. MATH:: + + (f_1 * f_2 * \cdots * f_n) := \mu^{(n-1)} \circ (f_1 \otimes + f_2 \otimes \cdots \otimes f_n) \circ \Delta^{(n-1)}, + + where `\Delta^{(k)} := \bigl(\Delta \otimes + \mathrm{Id}^{\otimes(k-1)}\bigr) \circ \Delta^{(k-1)}`, + with `\Delta^{(1)} = \Delta` (the ordinary coproduct in `A`) and + `\Delta^{(0)} = \mathrm{Id}`; and with `\mu^{(k)} := \mu \circ + \bigl(\mu^{(k-1)} \otimes \mathrm{Id})` and `\mu^{(1)} = \mu` + (the ordinary product in `B`). See [Sw1969]_. + + (In the literature, one finds, e.g., `\Delta^{(2)}` for what we + denote above as `\Delta^{(1)}`. See [KMN2012]_.) + + INPUT: + + - ``maps`` -- any number `n \geq 0` of linear maps `f_1, f_2, + \ldots, f_n` on ``self.parent()``; or a single ``list`` or + ``tuple`` of such maps + + OUTPUT: + + - the convolution product of ``maps`` applied to ``self`` + + REFERENCES: + + .. [KMN2012] On the trace of the antipode and higher indicators. + Yevgenia Kashina and Susan Montgomery and Richard Ng. + Israel J. Math., v.188, 2012. + + .. [Sw1969] Hopf algebras. + Moss Sweedler. + W.A. Benjamin, Math Lec Note Ser., 1969. + + AUTHORS: + + - Amy Pang - 12 June 2015 - Sage Days 65 + + .. TODO:: + + Remove dependency on ``modules_with_basis`` methods. + + EXAMPLES: + + We compute convolution products of the identity and antipode maps + on Schur functions:: + + sage: Id = lambda x: x + sage: Antipode = lambda x: x.antipode() + sage: s = SymmetricFunctions(QQ).schur() + sage: s[3].convolution_product(Id, Id) + 2*s[2, 1] + 4*s[3] + sage: s[3,2].convolution_product(Id) == s[3,2] + True + + The method accepts multiple arguments, or a single argument + consisting of a list of maps:: + + sage: s[3,2].convolution_product(Id, Id) + 2*s[2, 1, 1, 1] + 6*s[2, 2, 1] + 6*s[3, 1, 1] + 12*s[3, 2] + 6*s[4, 1] + 2*s[5] + sage: s[3,2].convolution_product([Id, Id]) + 2*s[2, 1, 1, 1] + 6*s[2, 2, 1] + 6*s[3, 1, 1] + 12*s[3, 2] + 6*s[4, 1] + 2*s[5] + + We test the defining property of the antipode morphism; namely, + that the antipode is the inverse of the identity map in the + convolution algebra whose identity element is the composition of + the counit and unit:: + + sage: s[3,2].convolution_product() == s[3,2].convolution_product(Antipode, Id) == s[3,2].convolution_product(Id, Antipode) + True + + :: + + sage: Psi = NonCommutativeSymmetricFunctions(QQ).Psi() + sage: Psi[2,1].convolution_product(Id, Id, Id) + 3*Psi[1, 2] + 6*Psi[2, 1] + sage: (Psi[5,1] - Psi[1,5]).convolution_product(Id, Id, Id) + -3*Psi[1, 5] + 3*Psi[5, 1] + + :: + + sage: G = SymmetricGroup(3) + sage: QG = GroupAlgebra(G,QQ) + sage: x = QG.sum_of_terms([(p,p.length()) for p in Permutations(3)]); x + [1, 3, 2] + [2, 1, 3] + 2*[2, 3, 1] + 2*[3, 1, 2] + 3*[3, 2, 1] + sage: x.convolution_product(Id, Id) + 5*[1, 2, 3] + 2*[2, 3, 1] + 2*[3, 1, 2] + sage: x.convolution_product(Id, Id, Id) + 4*[1, 2, 3] + [1, 3, 2] + [2, 1, 3] + 3*[3, 2, 1] + sage: x.convolution_product([Id]*6) + 9*[1, 2, 3] + + TESTS:: + + sage: Id = lambda x: x + sage: Antipode = lambda x: x.antipode() + + :: + + sage: h = SymmetricFunctions(QQ).h() + sage: h[5].convolution_product([Id, Id]) + 2*h[3, 2] + 2*h[4, 1] + 2*h[5] + sage: h.one().convolution_product([Id, Antipode]) + h[] + sage: h[3,2].convolution_product([Id, Antipode]) + 0 + sage: h.one().convolution_product([Id, Antipode]) == h.one().convolution_product() + True + + :: + + sage: S = NonCommutativeSymmetricFunctions(QQ).S() + sage: S[4].convolution_product([Id]*5) + 5*S[1, 1, 1, 1] + 10*S[1, 1, 2] + 10*S[1, 2, 1] + 10*S[1, 3] + + 10*S[2, 1, 1] + 10*S[2, 2] + 10*S[3, 1] + 5*S[4] + + :: + + sage: m = SymmetricFunctionsNonCommutingVariables(QQ).m() + sage: m[[1,3],[2]].convolution_product([Antipode, Antipode]) + 3*m{{1}, {2, 3}} + 3*m{{1, 2}, {3}} + 6*m{{1, 2, 3}} - 2*m{{1, 3}, {2}} + sage: m[[]].convolution_product([]) + m{} + sage: m[[1,3],[2]].convolution_product([]) + 0 + + :: + + sage: QS = SymmetricGroupAlgebra(QQ, 5) + sage: x = QS.sum_of_terms(zip(Permutations(5)[3:6],[1,2,3])); x + [1, 2, 4, 5, 3] + 2*[1, 2, 5, 3, 4] + 3*[1, 2, 5, 4, 3] + sage: x.convolution_product([Antipode, Id]) + 6*[1, 2, 3, 4, 5] + sage: x.convolution_product(Id, Antipode, Antipode, Antipode) + 3*[1, 2, 3, 4, 5] + [1, 2, 4, 5, 3] + 2*[1, 2, 5, 3, 4] + + :: + + sage: G = SymmetricGroup(3) + sage: QG = GroupAlgebra(G,QQ) + sage: x = QG.sum_of_terms([(p,p.length()) for p in Permutations(3)]); x + [1, 3, 2] + [2, 1, 3] + 2*[2, 3, 1] + 2*[3, 1, 2] + 3*[3, 2, 1] + sage: x.convolution_product(Antipode, Id) + 9*[1, 2, 3] + sage: x.convolution_product([Id, Antipode, Antipode, Antipode]) + 5*[1, 2, 3] + 2*[2, 3, 1] + 2*[3, 1, 2] + + :: + + sage: s[3,2].counit().parent() == s[3,2].convolution_product().parent() + False + """ + # Be flexible on how the maps are entered: accept a list/tuple of + # maps as well as multiple arguments + if len(maps) == 1 and isinstance(maps[0], (list, tuple)): + T = tuple(maps[0]) + else: + T = maps + + H = self.parent() + + n = len(T) + if n == 0: + return H.one() * self.counit() + if n == 1: + return T[0](self) + + # We apply the maps T_i and products concurrently with coproducts, as this + # seems to be faster than applying a composition of maps, e.g., (H.nfold_product) * tensor(T) * (H.nfold_coproduct). + + out = tensor((H.one(),self)) + HH = tensor((H,H)) + + for mor in T[:-1]: + #ALGORITHM: + #`split_convolve` moves terms of the form x # y to x*Ti(y1) # y2 in Sweedler notation. + split_convolve = lambda (x,y): ( ((xy1,y2),c*d) + for ((y1,y2),d) in H.term(y).coproduct() + for (xy1,c) in H.term(x)*mor(H.term(y1)) ) + out = HH.module_morphism(on_basis=lambda t: HH.sum_of_terms(split_convolve(t)), codomain=HH)(out) + + #Apply final map `T_n` to last term, `y`, and multiply. + return HH.module_morphism(on_basis=lambda (x,y): H.term(x)*T[-1](H.term(y)), codomain=H)(out) diff --git a/src/sage/categories/bimodules.py b/src/sage/categories/bimodules.py index 7971135a4f3..f778479bbc8 100644 --- a/src/sage/categories/bimodules.py +++ b/src/sage/categories/bimodules.py @@ -70,15 +70,21 @@ def _make_named_class_key(self, name): EXAMPLES:: sage: Bimodules(QQ,ZZ)._make_named_class_key('parent_class') - (Category of quotient fields, - Join of Category of euclidean domains and Category of infinite enumerated sets) + (Join of Category of quotient fields and Category of metric spaces, + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces) + sage: Bimodules(Fields(), ZZ)._make_named_class_key('element_class') (Category of fields, - Join of Category of euclidean domains and Category of infinite enumerated sets) + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces) sage: Bimodules(QQ, Rings())._make_named_class_key('element_class') - (Category of quotient fields, Category of rings) + (Join of Category of quotient fields and Category of metric spaces, + Category of rings) sage: Bimodules(Fields(), Rings())._make_named_class_key('element_class') (Category of fields, Category of rings) diff --git a/src/sage/categories/cartesian_product.py b/src/sage/categories/cartesian_product.py index 70cbbf3bab5..6cf5370c87c 100644 --- a/src/sage/categories/cartesian_product.py +++ b/src/sage/categories/cartesian_product.py @@ -16,6 +16,8 @@ from sage.categories.covariant_functorial_construction import CovariantFunctorialConstruction, CovariantConstructionCategory from sage.categories.pushout import MultivariateConstructionFunctor +native_python_containers = set([tuple, list, set, frozenset]) + class CartesianProductFunctor(CovariantFunctorialConstruction, MultivariateConstructionFunctor): """ A singleton class for the Cartesian product functor. @@ -121,6 +123,56 @@ def __init__(self): from sage.categories.sets_cat import Sets MultivariateConstructionFunctor.__init__(self, Sets(), Sets()) + def __call__(self, args, **kwds): + r""" + Functorial construction application. + + This specializes the generic ``__call__`` from + :class:`CovariantFunctorialConstruction` to: + + - handle the following plain Python containers as input: + :class:`frozenset`, :class:`list`, :class:`set` and + :class:`tuple`. + + - handle the empty list of factors. + + See the examples below. + + EXAMPLES:: + + sage: cartesian_product([[0,1], ('a','b','c')]) + The cartesian product of ({0, 1}, {'a', 'b', 'c'}) + sage: _.category() + Category of Cartesian products of finite enumerated sets + + sage: cartesian_product([set([0,1,2]), [0,1]]) + The cartesian product of ({0, 1, 2}, {0, 1}) + sage: _.category() + Category of Cartesian products of sets + + Check that the empty product is handled correctly: + + sage: C = cartesian_product([]) + sage: C + The cartesian product of () + sage: C.cardinality() + 1 + sage: C.an_element() + () + sage: C.category() + Category of Cartesian products of sets + """ + if any(type(arg) in native_python_containers for arg in args): + from sage.categories.sets_cat import Sets + S = Sets() + args = [S(a, enumerated_set=True) for a in args] + elif not args: + from sage.categories.sets_cat import Sets + from sage.sets.cartesian_product import CartesianProduct + return CartesianProduct((), Sets().CartesianProducts()) + + return super(CartesianProductFunctor, self).__call__(args, **kwds) + class CartesianProductsCategory(CovariantConstructionCategory): """ An abstract base class for all ``CartesianProducts`` categories. diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index a43973e47f9..2d50eac827c 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -35,7 +35,7 @@ sage: V = VectorSpace(RationalField(), 3) sage: V.category() - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis over (quotient fields and metric spaces) sage: G = SymmetricGroup(9) sage: G.category() Join of Category of finite permutation groups and Category of finite weyl groups @@ -323,7 +323,8 @@ class inheritance from ``C.parent_class``. sage: Algebras(GF(5)).parent_class is Algebras(GF(7)).parent_class True - sage: Coalgebras(QQ).parent_class is Coalgebras(FractionField(QQ['x'])).parent_class + sage: F = FractionField(ZZ['t']) + sage: Coalgebras(F).parent_class is Coalgebras(FractionField(F['x'])).parent_class True We now construct a parent in the usual way:: @@ -2755,9 +2756,9 @@ def _make_named_class(self, name, method_provider, cache = False, **options): Similarly for ``QQ`` and ``RR``:: sage: QQ.category() - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: RR.category() - Category of fields + Join of Category of fields and Category of complete metric spaces sage: Modules(QQ).parent_class is Modules(RR).parent_class False @@ -2818,14 +2819,17 @@ def _make_named_class_key(self, name): sage: Algebras(ZZ)._make_named_class_key("parent_class") Join of Category of euclidean domains - and Category of infinite enumerated sets + and Category of infinite enumerated sets + and Category of metric spaces The morphism class of a bimodule depends only on the category of the left and right base rings:: sage: Bimodules(QQ, ZZ)._make_named_class_key("morphism_class") - (Category of quotient fields, - Join of Category of euclidean domains and Category of infinite enumerated sets) + (Join of Category of quotient fields and Category of metric spaces, + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces) The element class of a join category depends only on the element class of its super categories:: @@ -2953,13 +2957,14 @@ def _make_named_class_key(self, name): sage: Modules(ZZ)._make_named_class_key('element_class') Join of Category of euclidean domains - and Category of infinite enumerated sets + and Category of infinite enumerated sets + and Category of metric spaces sage: Modules(QQ)._make_named_class_key('parent_class') - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: Schemes(Spec(ZZ))._make_named_class_key('parent_class') Category of schemes sage: ModularAbelianVarieties(QQ)._make_named_class_key('parent_class') - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces """ return tuple(getattr(cat, name) for cat in self._super_categories) @@ -3005,7 +3010,8 @@ def _subcategory_hook_(self, category): EXAMPLE:: - sage: QQ['x'].category().is_subcategory(Category.join([Rings(), VectorSpaces(QuotientFields())])) # indirect doctest + sage: cat = Category.join([Rings(), VectorSpaces(QuotientFields().Metric())]) + sage: QQ['x'].category().is_subcategory(cat) # indirect doctest True """ return all(category.is_subcategory(X) for X in self._super_categories) diff --git a/src/sage/categories/category_cy_helper.pyx b/src/sage/categories/category_cy_helper.pyx index 2156bf3169b..213034df6fc 100644 --- a/src/sage/categories/category_cy_helper.pyx +++ b/src/sage/categories/category_cy_helper.pyx @@ -135,17 +135,16 @@ cpdef tuple join_as_tuple(tuple categories, tuple axioms, tuple ignore_axioms): (Category of algebras over Integer Ring, Category of finite monoids, Category of coalgebras over Rational Field, - Category of simplicial complexes) + Category of finite simplicial complexes) sage: join_as_tuple(T,('WithBasis',),()) (Category of algebras with basis over Integer Ring, Category of finite monoids, Category of coalgebras with basis over Rational Field, - Category of simplicial complexes) + Category of finite simplicial complexes) sage: join_as_tuple(T,(),((Monoids(),'Finite'),)) (Category of algebras over Integer Ring, Category of coalgebras over Rational Field, - Category of finite sets, - Category of simplicial complexes) + Category of finite simplicial complexes) """ cdef set axiomsS = set(axioms) for category in categories: diff --git a/src/sage/categories/category_types.py b/src/sage/categories/category_types.py index 435e6a5f980..3241fab5b10 100644 --- a/src/sage/categories/category_types.py +++ b/src/sage/categories/category_types.py @@ -193,13 +193,15 @@ def _make_named_class_key(self, name): EXAMPLES:: sage: Modules(ZZ)._make_named_class_key('element_class') - Join of Category of euclidean domains and Category of infinite enumerated sets + Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces sage: Modules(QQ)._make_named_class_key('parent_class') - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: Schemes(Spec(ZZ))._make_named_class_key('parent_class') Category of schemes sage: ModularAbelianVarieties(QQ)._make_named_class_key('parent_class') - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: Algebras(Fields())._make_named_class_key('morphism_class') Category of fields """ @@ -513,34 +515,7 @@ def __call__(self, v): return v return self.ring().ideal(v) -############################################################# -# TODO: make those two into real categories (with super_category, ...) - -# SimplicialComplex -############################################################# -class SimplicialComplexes(Category): - """ - The category of simplicial complexes. - - EXAMPLES:: - - sage: SimplicialComplexes() - Category of simplicial complexes - - TESTS:: - - sage: TestSuite(SimplicialComplexes()).run() - """ - - def super_categories(self): - """ - EXAMPLES:: - - sage: SimplicialComplexes().super_categories() - [Category of objects] - """ - return [Objects()] # anything better? - +# TODO: make this into a better category ############################################################# # ChainComplex ############################################################# @@ -566,11 +541,11 @@ def super_categories(self): EXAMPLES:: sage: ChainComplexes(Integers(9)).super_categories() - [Category of modules with basis over Ring of integers modulo 9] + [Category of modules over Ring of integers modulo 9] """ - from sage.categories.all import Fields, FreeModules, VectorSpaces + from sage.categories.all import Fields, Modules, VectorSpaces base_ring = self.base_ring() if base_ring in Fields(): return [VectorSpaces(base_ring)] - return [FreeModules(base_ring)] + return [Modules(base_ring)] diff --git a/src/sage/categories/category_with_axiom.py b/src/sage/categories/category_with_axiom.py index 9a80ddff627..6fe7f5c42cf 100644 --- a/src/sage/categories/category_with_axiom.py +++ b/src/sage/categories/category_with_axiom.py @@ -1673,8 +1673,11 @@ class ``Sets.Finite``), or in a separate file (typically in a class all_axioms = AxiomContainer() all_axioms += ("Flying", "Blue", + "Compact", + "Differentiable", "Smooth", "Analytic", "AlmostComplex", "FinitelyGeneratedAsMagma", "Facade", "Finite", "Infinite", + "Complete", "FiniteDimensional", "Connected", "WithBasis", "Irreducible", "Commutative", "Associative", "Inverse", "Unital", "Division", "NoZeroDivisors", diff --git a/src/sage/categories/coalgebras.py b/src/sage/categories/coalgebras.py index a5fdc492111..c700ea8f7ab 100644 --- a/src/sage/categories/coalgebras.py +++ b/src/sage/categories/coalgebras.py @@ -226,7 +226,7 @@ def coproduct(self, x): def counit(self, x): r""" - Returns the counit of ``x``. + Return the counit of ``x``. EXAMPLES:: @@ -258,7 +258,8 @@ class ParentMethods: def coproduct_by_coercion(self, x): r""" - Returns the coproduct by coercion if coproduct_by_basis is not implemented. + Return the coproduct by coercion if ``coproduct_by_basis`` + is not implemented. EXAMPLES:: @@ -289,3 +290,26 @@ def coproduct_by_coercion(self, x): """ R = self.realization_of().a_realization() return self.tensor_square()(R(x).coproduct()) + + def counit_by_coercion(self, x): + r""" + Return the counit of ``x`` if ``counit_by_basis`` is + not implemented. + + EXAMPLES:: + + sage: sp = SymmetricFunctions(QQ).sp() + sage: sp.an_element() + 2*sp[] + 2*sp[1] + 3*sp[2] + sage: sp.counit(sp.an_element()) + 2 + + sage: o = SymmetricFunctions(QQ).o() + sage: o.an_element() + 2*o[] + 2*o[1] + 3*o[2] + sage: o.counit(o.an_element()) + -1 + """ + R = self.realization_of().a_realization() + return R(x).counit() + diff --git a/src/sage/categories/coalgebras_with_basis.py b/src/sage/categories/coalgebras_with_basis.py index ddcaad16d26..b691f363935 100644 --- a/src/sage/categories/coalgebras_with_basis.py +++ b/src/sage/categories/coalgebras_with_basis.py @@ -126,9 +126,74 @@ def counit(self): """ if self.counit_on_basis is not NotImplemented: return self.module_morphism(self.counit_on_basis,codomain=self.base_ring()) + elif hasattr(self, "counit_by_coercion"): + return self.counit_by_coercion class ElementMethods: - pass + def coproduct_iterated(self, n=1): + r""" + Apply ``n`` coproducts to ``self``. + + .. TODO:: + + Remove dependency on ``modules_with_basis`` methods. + + EXAMPLES:: + + sage: Psi = NonCommutativeSymmetricFunctions(QQ).Psi() + sage: Psi[2,2].coproduct_iterated(0) + Psi[2, 2] + sage: Psi[2,2].coproduct_iterated(2) + Psi[] # Psi[] # Psi[2, 2] + 2*Psi[] # Psi[2] # Psi[2] + + Psi[] # Psi[2, 2] # Psi[] + 2*Psi[2] # Psi[] # Psi[2] + + 2*Psi[2] # Psi[2] # Psi[] + Psi[2, 2] # Psi[] # Psi[] + + TESTS:: + + sage: p = SymmetricFunctions(QQ).p() + sage: p[5,2,2].coproduct_iterated() + p[] # p[5, 2, 2] + 2*p[2] # p[5, 2] + p[2, 2] # p[5] + + p[5] # p[2, 2] + 2*p[5, 2] # p[2] + p[5, 2, 2] # p[] + sage: p([]).coproduct_iterated(3) + p[] # p[] # p[] # p[] + + :: + + sage: Psi = NonCommutativeSymmetricFunctions(QQ).Psi() + sage: Psi[2,2].coproduct_iterated(0) + Psi[2, 2] + sage: Psi[2,2].coproduct_iterated(3) + Psi[] # Psi[] # Psi[] # Psi[2, 2] + 2*Psi[] # Psi[] # Psi[2] # Psi[2] + + Psi[] # Psi[] # Psi[2, 2] # Psi[] + 2*Psi[] # Psi[2] # Psi[] # Psi[2] + + 2*Psi[] # Psi[2] # Psi[2] # Psi[] + Psi[] # Psi[2, 2] # Psi[] # Psi[] + + 2*Psi[2] # Psi[] # Psi[] # Psi[2] + 2*Psi[2] # Psi[] # Psi[2] # Psi[] + + 2*Psi[2] # Psi[2] # Psi[] # Psi[] + Psi[2, 2] # Psi[] # Psi[] # Psi[] + + :: + + sage: m = SymmetricFunctionsNonCommutingVariables(QQ).m() + sage: m[[1,3],[2]].coproduct_iterated(2) + m{} # m{} # m{{1, 3}, {2}} + m{} # m{{1}} # m{{1, 2}} + + m{} # m{{1, 2}} # m{{1}} + m{} # m{{1, 3}, {2}} # m{} + + m{{1}} # m{} # m{{1, 2}} + m{{1}} # m{{1, 2}} # m{} + + m{{1, 2}} # m{} # m{{1}} + m{{1, 2}} # m{{1}} # m{} + + m{{1, 3}, {2}} # m{} # m{} + sage: m[[]].coproduct_iterated(3), m[[1,3],[2]].coproduct_iterated(0) + (m{} # m{} # m{} # m{}, m{{1, 3}, {2}}) + """ + if n < 0: + raise ValueError("cannot take fewer than 0 coproduct iterations: %s < 0" % str(n)) + if n == 0: + return self + if n == 1: + return self.coproduct() + from sage.functions.all import floor, ceil + from sage.rings.all import Integer + + # Use coassociativity of `\Delta` to perform many coproducts simultaneously. + fn = floor(Integer(n-1)/2); cn = ceil(Integer(n-1)/2) + split = lambda a,b: tensor([a.coproduct_iterated(fn), b.coproduct_iterated(cn)]) + return self.coproduct().apply_multilinear_morphism(split) class Super(SuperModulesCategory): pass diff --git a/src/sage/categories/coxeter_groups.py b/src/sage/categories/coxeter_groups.py index d57a1710fb4..a457d0aff48 100644 --- a/src/sage/categories/coxeter_groups.py +++ b/src/sage/categories/coxeter_groups.py @@ -1163,8 +1163,7 @@ def reduced_word_graph(self): if i == len(x): continue a, b = x[i], y[i] - I = P.index_set() - m = P.coxeter_matrix()[I.index(a),I.index(b)] + m = P.coxeter_matrix()[a,b] subword = [a,b] * (m // 2) subword2 = [b,a] * (m // 2) if m % 2 != 0: diff --git a/src/sage/categories/cw_complexes.py b/src/sage/categories/cw_complexes.py new file mode 100644 index 00000000000..f7f4641d5cc --- /dev/null +++ b/src/sage/categories/cw_complexes.py @@ -0,0 +1,215 @@ +r""" +CW Complexes +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_singleton import Category_singleton +from sage.categories.category_with_axiom import CategoryWithAxiom +from sage.categories.sets_cat import Sets + +class CWComplexes(Category_singleton): + r""" + The category of CW complexes. + + A CW complex is a Closure-finite cell complex in the Weak toplogy. + + REFERENCES: + + - :wikipedia:`CW_complex` + + .. NOTE:: + + The notion of "finite" is that the number of cells is finite. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: C = CWComplexes(); C + Category of CW complexes + + TESTS:: + + sage: TestSuite(C).run() + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes().super_categories() + [Category of topological spaces] + """ + return [Sets().Topological()] + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes() # indirect doctest + Category of CW complexes + """ + return "CW complexes" + + class SubcategoryMethods: + @cached_method + def Connected(self): + """ + Return the full subcategory of the connected objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes().Connected() + Category of connected CW complexes + + TESTS:: + + sage: TestSuite(CWComplexes().Connected()).run() + sage: CWComplexes().Connected.__module__ + 'sage.categories.cw_complexes' + """ + return self._with_axiom('Connected') + + @cached_method + def FiniteDimensional(self): + """ + Return the full subcategory of the finite dimensional + objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: C = CWComplexes().FiniteDimensional(); C + Category of finite dimensional CW complexes + + TESTS:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: C = CWComplexes().FiniteDimensional() + sage: TestSuite(C).run() + sage: CWComplexes().Connected().FiniteDimensional.__module__ + 'sage.categories.cw_complexes' + """ + return self._with_axiom('FiniteDimensional') + + class Connected(CategoryWithAxiom): + """ + The category of connected CW complexes. + """ + + class FiniteDimensional(CategoryWithAxiom): + """ + Category of finite dimensional CW complexes. + """ + + class Finite(CategoryWithAxiom): + """ + Category of finite CW complexes. + + A finite CW complex is a CW complex with a finite number of cells. + """ + def extra_super_categories(self): + """ + Return the extra super categories of ``self``. + + A finite CW complex is a compact finite-dimensional CW complex. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: C = CWComplexes().Finite() + sage: C.extra_super_categories() + [Category of finite dimensional CW complexes, + Category of compact topological spaces] + """ + return [CWComplexes().FiniteDimensional(), Sets().Topological().Compact()] + + class ParentMethods: + @cached_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.dimension() + 2 + """ + C = self.cells() + return max(c.dimension() for d in C.keys() for c in C[d]) + + def Compact_extra_super_categories(self): + """ + Return extraneous super categories for ``CWComplexes().Compact()``. + + A compact CW complex is finite, see Proposition A.1 in [Hat]_. + + .. TODO:: + + Fix the name of finite CW complexes. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes().Compact() # indirect doctest + Category of finite finite dimensional CW complexes + sage: CWComplexes().Compact() is CWComplexes().Finite() + True + """ + return (Sets().Finite(),) + + class ElementMethods: + @abstract_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.an_element().dimension() + 2 + """ + + class ParentMethods: + @abstract_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.dimension() + 2 + """ + + @abstract_method(optional=True) + def cells(self): + """ + Return the cells of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: C = X.cells() + sage: sorted((d, C[d]) for d in C.keys()) + [(0, (0-cell v,)), + (1, (0-cell e1, 0-cell e2)), + (2, (2-cell f,))] + """ + diff --git a/src/sage/categories/distributive_magmas_and_additive_magmas.py b/src/sage/categories/distributive_magmas_and_additive_magmas.py index ebcb996bdaf..c0c3a53bb80 100644 --- a/src/sage/categories/distributive_magmas_and_additive_magmas.py +++ b/src/sage/categories/distributive_magmas_and_additive_magmas.py @@ -73,8 +73,8 @@ def _test_distributivity(self, **options): """ tester = self._tester(**options) S = tester.some_elements() - from sage.combinat.cartesian_product import CartesianProduct - for x,y,z in tester.some_elements(CartesianProduct(S,S,S)): + from sage.misc.misc import some_tuples + for x,y,z in some_tuples(tester.some_elements(), 3, tester._max_runs): # left distributivity tester.assert_(x * (y + z) == (x * y) + (x * z)) # right distributivity diff --git a/src/sage/categories/domains.py b/src/sage/categories/domains.py index 2e9b9ec2db1..2cdbb4cd2ce 100644 --- a/src/sage/categories/domains.py +++ b/src/sage/categories/domains.py @@ -84,8 +84,8 @@ def _test_zero_divisors(self, **options): # Filter out zero S = [s for s in tester.some_elements() if not s.is_zero()] - from sage.combinat.cartesian_product import CartesianProduct - for a,b in tester.some_elements(CartesianProduct(S,S)): + from sage.misc.misc import some_tuples + for a,b in some_tuples(S, 2, tester._max_runs): p = a * b tester.assertFalse(p.is_zero()) diff --git a/src/sage/categories/enumerated_sets.py b/src/sage/categories/enumerated_sets.py index 1cbe3103953..7b01e89d48a 100644 --- a/src/sage/categories/enumerated_sets.py +++ b/src/sage/categories/enumerated_sets.py @@ -23,7 +23,7 @@ class EnumeratedSets(Category_singleton): together with a canonical enumeration of its elements; conceptually, this is very similar to an immutable list. The main difference lies in the names and the return type of the methods, - and of course the fact that the list of element is not supposed to + and of course the fact that the list of elements is not supposed to be expanded in memory. Whenever possible one should use one of the two sub-categories :class:`FiniteEnumeratedSets` or :class:`InfiniteEnumeratedSets`. @@ -39,7 +39,7 @@ class EnumeratedSets(Category_singleton): - ``S.cardinality()``: the number of elements of the set. This is the equivalent for ``len`` on a list except that the return value is specified to be a Sage :class:`Integer` or - ``infinity``, instead of a Python ``int``; + ``infinity``, instead of a Python ``int``. - ``iter(S)``: an iterator for the elements of the set; @@ -48,15 +48,15 @@ class EnumeratedSets(Category_singleton): predictably too large to be expanded in memory. - ``S.unrank(n)``: the ``n-th`` element of the set when ``n`` is a sage - ``Integer``. This is the equivanlent for ``l[n]`` on a list. + ``Integer``. This is the equivalent for ``l[n]`` on a list. - ``S.rank(e)``: the position of the element ``e`` in the set; This is equivalent to ``l.index(e)`` for a list except that the return value is specified to be a Sage :class:`Integer`, - instead of a Python ``int``; + instead of a Python ``int``. - ``S.first()``: the first object of the set; it is equivalent to - ``S.unrank(0)``; + ``S.unrank(0)``. - ``S.next(e)``: the object of the set which follows ``e``; It is equivalent to ``S.unrank(S.rank(e)+1)``. @@ -148,12 +148,12 @@ def __iter__(self): An iterator for the enumerated set. ``iter(self)`` allows the combinatorial class to be treated as an - iterable. This if the default implementation from the category - ``EnumeratedSets()`` it just goes through the iterator of the set + iterable. This is the default implementation from the category + ``EnumeratedSets()``; it just goes through the iterator of the set to count the number of objects. By decreasing order of priority, the second column of the - following array shows which methods is used to define + following array shows which method is used to define ``__iter__``, when the methods of the first column are overloaded: +------------------------+---------------------------------+ @@ -166,53 +166,53 @@ def __iter__(self): | ``list` | ``_iterator_from_next`` | +------------------------+---------------------------------+ - If non of these are provided raise a ``NotImplementedError`` + If none of these are provided, raise a ``NotImplementedError``. EXAMPLES:: We start with an example where nothing is implemented:: sage: class broken(UniqueRepresentation, Parent): - ... def __init__(self): - ... Parent.__init__(self, category = EnumeratedSets()) - ... + ....: def __init__(self): + ....: Parent.__init__(self, category = EnumeratedSets()) + ....: sage: it = iter(broken()); [next(it), next(it), next(it)] Traceback (most recent call last): ... NotImplementedError: iterator called but not implemented - Here is what happends when ``first`` and ``next`` are implemeted:: + Here is what happens when ``first`` and ``next`` are implemented:: sage: class set_first_next(UniqueRepresentation, Parent): - ... def __init__(self): - ... Parent.__init__(self, category = EnumeratedSets()) - ... def first(self): - ... return 0 - ... def next(self, elt): - ... return elt+1 - ... + ....: def __init__(self): + ....: Parent.__init__(self, category = EnumeratedSets()) + ....: def first(self): + ....: return 0 + ....: def next(self, elt): + ....: return elt+1 + ....: sage: it = iter(set_first_next()); [next(it), next(it), next(it)] [0, 1, 2] Let us try with ``unrank``:: sage: class set_unrank(UniqueRepresentation, Parent): - ... def __init__(self): - ... Parent.__init__(self, category = EnumeratedSets()) - ... def unrank(self, i): - ... return i + 5 - ... + ....: def __init__(self): + ....: Parent.__init__(self, category = EnumeratedSets()) + ....: def unrank(self, i): + ....: return i + 5 + ....: sage: it = iter(set_unrank()); [next(it), next(it), next(it)] [5, 6, 7] Let us finally try with ``list``:: sage: class set_list(UniqueRepresentation, Parent): - ... def __init__(self): - ... Parent.__init__(self, category = EnumeratedSets()) - ... def list(self): - ... return [5, 6, 7] - ... + ....: def __init__(self): + ....: Parent.__init__(self, category = EnumeratedSets()) + ....: def list(self): + ....: return [5, 6, 7] + ....: sage: it = iter(set_list()); [next(it), next(it), next(it)] [5, 6, 7] @@ -257,7 +257,7 @@ def is_empty(self): def list(self): """ - Return an error since the cardinality of self is not known. + Return an error since the cardinality of ``self`` is not known. EXAMPLES:: @@ -715,98 +715,6 @@ def rank(self): class CartesianProducts(CartesianProductsCategory): class ParentMethods: - def __iter__(self): - r""" - Return a lexicographic iterator for the elements of this cartesian product. - - EXAMPLES:: - - sage: A = FiniteEnumeratedSets()(["a", "b"]) - sage: B = FiniteEnumeratedSets().example(); B - An example of a finite enumerated set: {1,2,3} - sage: C = cartesian_product([A, B, A]); C - The cartesian product of ({'a', 'b'}, An example of a finite enumerated set: {1,2,3}, {'a', 'b'}) - sage: C in FiniteEnumeratedSets() - True - sage: list(C) - [('a', 1, 'a'), ('a', 1, 'b'), ('a', 2, 'a'), ('a', 2, 'b'), ('a', 3, 'a'), ('a', 3, 'b'), - ('b', 1, 'a'), ('b', 1, 'b'), ('b', 2, 'a'), ('b', 2, 'b'), ('b', 3, 'a'), ('b', 3, 'b')] - sage: C.__iter__.__module__ - 'sage.categories.enumerated_sets' - - sage: F22 = GF(2).cartesian_product(GF(2)) - sage: list(F22) - [(0, 0), (0, 1), (1, 0), (1, 1)] - - sage: C = cartesian_product([Permutations(10)]*4) - sage: it = iter(C) - sage: next(it) - ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - sage: next(it) - ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [1, 2, 3, 4, 5, 6, 7, 8, 10, 9]) - - .. WARNING:: - - The elements are returned in lexicographic order, - which gives a valid enumeration only if all - factors, but possibly the first one, are - finite. So the following one is fine:: - - sage: it = iter(cartesian_product([ZZ, GF(2)])) - sage: [next(it) for _ in range(10)] - [(0, 0), (0, 1), (1, 0), (1, 1), - (-1, 0), (-1, 1), (2, 0), (2, 1), - (-2, 0), (-2, 1)] - - But this one is not:: - - sage: it = iter(cartesian_product([GF(2), ZZ])) - sage: [next(it) for _ in range(10)] - doctest:...: UserWarning: Sage is not able to determine - whether the factors of this cartesian product are - finite. The lexicographic ordering might not go through - all elements. - [(0, 0), (0, 1), (0, -1), (0, 2), (0, -2), - (0, 3), (0, -3), (0, 4), (0, -4), (0, 5)] - - .. NOTE:: - - Here it would be faster to use :func:`itertools.product` for sets - of small size. But the latter expands all factor in memory! - So we can not reasonably use it in general. - - ALGORITHM: - - Recipe 19.9 in the Python Cookbook by Alex Martelli - and David Ascher. - """ - if any(f not in Sets().Finite() for f in self.cartesian_factors()[1:]): - from warnings import warn - warn("Sage is not able to determine whether the factors of " - "this cartesian product are finite. The lexicographic " - "ordering might not go through all elements.") - - # visualize an odometer, with "wheels" displaying "digits"...: - factors = list(self.cartesian_factors()) - wheels = map(iter, factors) - digits = [next(it) for it in wheels] - while True: - yield self._cartesian_product_of_elements(digits) - for i in range(len(digits)-1, -1, -1): - try: - digits[i] = next(wheels[i]) - break - except StopIteration: - wheels[i] = iter(factors[i]) - digits[i] = next(wheels[i]) - else: - break def first(self): r""" diff --git a/src/sage/categories/euclidean_domains.py b/src/sage/categories/euclidean_domains.py index dc7364599ad..b9d29a79a29 100644 --- a/src/sage/categories/euclidean_domains.py +++ b/src/sage/categories/euclidean_domains.py @@ -87,8 +87,8 @@ def _test_euclidean_degree(self, **options): tester.assertGreaterEqual(a.euclidean_degree(), min_degree) tester.assertEqual(a.euclidean_degree() == min_degree, a.is_unit()) - from sage.combinat.cartesian_product import CartesianProduct - for a,b in tester.some_elements(CartesianProduct(S,S)): + from sage.misc.misc import some_tuples + for a,b in some_tuples(S, 2, tester._max_runs): p = a * b # For rings which are not exact, we might get something that # acts like a zero divisor. @@ -114,9 +114,8 @@ def _test_quo_rem(self, **options): """ tester = self._tester(**options) S = tester.some_elements() - - from sage.combinat.cartesian_product import CartesianProduct - for a,b in tester.some_elements(CartesianProduct(S,S)): + from sage.misc.misc import some_tuples + for a,b in some_tuples(S, 2, tester._max_runs): if b.is_zero(): tester.assertRaises(ZeroDivisionError, lambda: a.quo_rem(b)) else: diff --git a/src/sage/categories/examples/cw_complexes.py b/src/sage/categories/examples/cw_complexes.py new file mode 100644 index 00000000000..8c33b303e61 --- /dev/null +++ b/src/sage/categories/examples/cw_complexes.py @@ -0,0 +1,164 @@ +""" +Examples of CW complexes +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.element import Element +from sage.categories.cw_complexes import CWComplexes +from sage.rings.integer import Integer +from sage.rings.all import QQ +from sage.sets.family import Family + +class Surface(UniqueRepresentation, Parent): + r""" + An example of a CW complex: a (2-dimensional) surface. + + This class illustrates a minimal implementation of a CW complex. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example(); X + An example of a CW complex: the surface given by the boundary map (1, 2, 1, 2) + + sage: X.category() + Category of finite finite dimensional CW complexes + + We conclude by running systematic tests on this manifold:: + + sage: TestSuite(X).run() + """ + def __init__(self, bdy=(1, 2, 1, 2)): + r""" + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example((1, 2)); X + An example of a CW complex: the surface given by the boundary map (1, 2) + + TESTS:: + + sage: TestSuite(X).run() + """ + self._bdy = bdy + self._edges = frozenset(bdy) + Parent.__init__(self, category=CWComplexes().Finite()) + + def _repr_(self): + r""" + TESTS:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: CWComplexes().example() + An example of a CW complex: the surface given by the boundary map (1, 2, 1, 2) + """ + return "An example of a CW complex: the surface given by the boundary map {}".format(self._bdy) + + def cells(self): + """ + Return the cells of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: C = X.cells() + sage: sorted((d, C[d]) for d in C.keys()) + [(0, (0-cell v,)), + (1, (0-cell e1, 0-cell e2)), + (2, (2-cell f,))] + """ + d = {0: (self.element_class(self, 0, 'v'),)} + d[1] = tuple([self.element_class(self, 0, 'e'+str(e)) for e in self._edges]) + d[2] = (self.an_element(),) + return Family(d) + + def an_element(self): + r""" + Return an element of the CW complex, as per + :meth:`Sets.ParentMethods.an_element`. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.an_element() + 2-cell f + """ + return self.element_class(self, 2, 'f') + + class Element(Element): + """ + A cell in a CW complex. + """ + def __init__(self, parent, dim, name): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: f = X.an_element() + sage: TestSuite(f).run() + """ + Element.__init__(self, parent) + self._dim = dim + self._name = name + + def _repr_(self): + """ + Return a string represention of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: X.an_element() + 2-cell f + """ + return "{}-cell {}".format(self._dim, self._name) + + def __eq__(self, other): + """ + Check equality. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: f = X.an_element() + sage: f == X(2, 'f') + True + sage: e1 = X(1, 'e1') + sage: e1 == f + False + """ + return (isinstance(other, Surface.Element) + and self.parent() is other.parent() + and self._dim == other._dim + and self._name == other._name) + + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.cw_complexes import CWComplexes + sage: X = CWComplexes().example() + sage: f = X.an_element() + sage: f.dimension() + 2 + """ + return self._dim + +Example = Surface + diff --git a/src/sage/categories/examples/graphs.py b/src/sage/categories/examples/graphs.py new file mode 100644 index 00000000000..9c1e1eddb7e --- /dev/null +++ b/src/sage/categories/examples/graphs.py @@ -0,0 +1,122 @@ +""" +Examples of graphs +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.element_wrapper import ElementWrapper +from sage.categories.graphs import Graphs +from sage.rings.all import QQ + +class Cycle(UniqueRepresentation, Parent): + r""" + An example of a graph: the cycle of length `n`. + + This class illustrates a minimal implementation of a graph. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example(); C + An example of a graph: the 5-cycle + + sage: C.category() + Category of graphs + + We conclude by running systematic tests on this graph:: + + sage: TestSuite(C).run() + """ + def __init__(self, n=5): + r""" + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example(6); C + An example of a graph: the 6-cycle + + TESTS:: + + sage: TestSuite(C).run() + """ + self._n = n + Parent.__init__(self, category=Graphs()) + + def _repr_(self): + r""" + TESTS:: + + sage: from sage.categories.graphs import Graphs + sage: Graphs().example() + An example of a graph: the 5-cycle + """ + return "An example of a graph: the {}-cycle".format(self._n) + + def an_element(self): + r""" + Return an element of the graph, as per + :meth:`Sets.ParentMethods.an_element`. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.an_element() + 0 + """ + return self(0) + + def vertices(self): + """ + Return the vertices of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.vertices() + [0, 1, 2, 3, 4] + """ + return [self(i) for i in range(self._n)] + + def edges(self): + """ + Return the edges of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.edges() + [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] + """ + return [self( (i, (i+1) % self._n) ) for i in range(self._n)] + + class Element(ElementWrapper): + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: e = C.edges()[0] + sage: e.dimension() + 2 + sage: v = C.vertices()[0] + sage: v.dimension() + 1 + """ + if isinstance(self.value, tuple): + return 2 + return 1 + +Example = Cycle + diff --git a/src/sage/categories/examples/manifolds.py b/src/sage/categories/examples/manifolds.py new file mode 100644 index 00000000000..a71b7cf30e3 --- /dev/null +++ b/src/sage/categories/examples/manifolds.py @@ -0,0 +1,94 @@ +""" +Examples of manifolds +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.element_wrapper import ElementWrapper +from sage.categories.manifolds import Manifolds +from sage.rings.all import QQ + +class Plane(UniqueRepresentation, Parent): + r""" + An example of a manifold: the `n`-dimensional plane. + + This class illustrates a minimal implementation of a manifold. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(QQ).example(); M + An example of a Rational Field manifold: the 3-dimensional plane + + sage: M.category() + Category of manifolds over Rational Field + + We conclude by running systematic tests on this manifold:: + + sage: TestSuite(M).run() + """ + + def __init__(self, n=3, base_ring=None): + r""" + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(QQ).example(6); M + An example of a Rational Field manifold: the 6-dimensional plane + + TESTS:: + + sage: TestSuite(M).run() + """ + self._n = n + Parent.__init__(self, base=base_ring, category=Manifolds(base_ring)) + + def _repr_(self): + r""" + TESTS:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(QQ).example() + An example of a Rational Field manifold: the 3-dimensional plane + """ + return "An example of a {} manifold: the {}-dimensional plane".format( + self.base_ring(), self._n) + + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(QQ).example() + sage: M.dimension() + 3 + """ + return self._n + + def an_element(self): + r""" + Return an element of the manifold, as per + :meth:`Sets.ParentMethods.an_element`. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(QQ).example() + sage: M.an_element() + (0, 0, 0) + """ + zero = self.base_ring().zero() + return self(tuple([zero]*self._n)) + + Element = ElementWrapper + +Example = Plane + diff --git a/src/sage/categories/finite_dimensional_algebras_with_basis.py b/src/sage/categories/finite_dimensional_algebras_with_basis.py index 862540e3bc3..b4378371526 100644 --- a/src/sage/categories/finite_dimensional_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_algebras_with_basis.py @@ -948,6 +948,27 @@ def is_identity_decomposition_into_orthogonal_idempotents(self, l): and all(e*e == e for e in l) and all(e*f == 0 for e in l for f in l if f != e)) + @cached_method + def is_commutative(self): + """ + Return whether ``self`` is a commutative algebra. + + EXAMPLES:: + + sage: S4 = SymmetricGroupAlgebra(QQ, 4) + sage: S4.is_commutative() + False + sage: S2 = SymmetricGroupAlgebra(QQ, 2) + sage: S2.is_commutative() + True + """ + B = list(self.basis()) + try: # See if 1 is a basis element, if so, remove it + B.remove(self.one()) + except ValueError: + pass + return all(b*bp == bp*b for i,b in enumerate(B) for bp in B[i+1:]) + class ElementMethods: def to_matrix(self, base_ring=None, action=operator.mul, side='left'): diff --git a/src/sage/categories/finite_dimensional_bialgebras_with_basis.py b/src/sage/categories/finite_dimensional_bialgebras_with_basis.py index f276371d25f..086892237e6 100644 --- a/src/sage/categories/finite_dimensional_bialgebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_bialgebras_with_basis.py @@ -18,8 +18,7 @@ def FiniteDimensionalBialgebrasWithBasis(base_ring): sage: C = FiniteDimensionalBialgebrasWithBasis(QQ); C Category of finite dimensional bialgebras with basis over Rational Field sage: sorted(C.super_categories(), key=str) - [Category of bialgebras over Rational Field, - Category of coalgebras with basis over Rational Field, + [Category of bialgebras with basis over Rational Field, Category of finite dimensional algebras with basis over Rational Field] sage: C is Bialgebras(QQ).WithBasis().FiniteDimensional() True diff --git a/src/sage/categories/finite_dimensional_modules_with_basis.py b/src/sage/categories/finite_dimensional_modules_with_basis.py index 9a3264a4bc6..5dccfac10ee 100644 --- a/src/sage/categories/finite_dimensional_modules_with_basis.py +++ b/src/sage/categories/finite_dimensional_modules_with_basis.py @@ -265,7 +265,33 @@ def quotient_module(self, submodule, check=True, already_echelonized=False, cate return QuotientModuleWithBasis(submodule, category=category) class ElementMethods: - pass + def dense_coefficient_list(self, order=None): + """ + Return a list of *all* coefficients of ``self``. + + By default, this list is ordered in the same way as the + indexing set of the basis of the parent of ``self``. + + INPUT: + + - ``order`` -- (optional) an ordering of the basis indexing set + + EXAMPLES:: + + sage: v = vector([0, -1, -3]) + sage: v.dense_coefficient_list() + [0, -1, -3] + sage: v.dense_coefficient_list([2,1,0]) + [-3, -1, 0] + sage: sorted(v.coefficients()) + [-3, -1] + """ + if order is None: + try: + order = sorted(self.parent().basis().keys()) + except AttributeError: # Not a family, assume it is list-like + order = range(self.parent().dimension()) + return [self[i] for i in order] class MorphismMethods: def matrix(self, base_ring=None, side="left"): diff --git a/src/sage/categories/finite_enumerated_sets.py b/src/sage/categories/finite_enumerated_sets.py index 1cce141f25e..1c795543d62 100644 --- a/src/sage/categories/finite_enumerated_sets.py +++ b/src/sage/categories/finite_enumerated_sets.py @@ -496,14 +496,14 @@ class ParentMethods: 'sage.categories.sets_cat' sage: C.__iter__.__module__ - 'sage.categories.enumerated_sets' + 'sage.categories.sets_cat' """ # Ambiguity resolution between methods inherited from # Sets.CartesianProducts and from EnumeratedSets.Finite. random_element = Sets.CartesianProducts.ParentMethods.random_element.__func__ cardinality = Sets.CartesianProducts.ParentMethods.cardinality.__func__ - __iter__ = EnumeratedSets.CartesianProducts.ParentMethods.__iter__.__func__ + __iter__ = Sets.CartesianProducts.ParentMethods.__iter__.__func__ def last(self): r""" diff --git a/src/sage/categories/graded_bialgebras_with_basis.py b/src/sage/categories/graded_bialgebras_with_basis.py index 171de5b99e7..8b9ff21acdf 100644 --- a/src/sage/categories/graded_bialgebras_with_basis.py +++ b/src/sage/categories/graded_bialgebras_with_basis.py @@ -18,8 +18,7 @@ def GradedBialgebrasWithBasis(base_ring): sage: C = GradedBialgebrasWithBasis(QQ); C Join of Category of ... sage: sorted(C.super_categories(), key=str) - [Category of bialgebras over Rational Field, - Category of coalgebras with basis over Rational Field, + [Category of bialgebras with basis over Rational Field, Category of graded algebras with basis over Rational Field] TESTS:: diff --git a/src/sage/categories/graphs.py b/src/sage/categories/graphs.py new file mode 100644 index 00000000000..1be42bb94a5 --- /dev/null +++ b/src/sage/categories/graphs.py @@ -0,0 +1,108 @@ +""" +Graphs +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_singleton import Category_singleton +from sage.categories.simplicial_complexes import SimplicialComplexes + +class Graphs(Category_singleton): + r""" + The category of graphs. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs(); C + Category of graphs + + TESTS:: + + sage: TestSuite(C).run() + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: Graphs().super_categories() + [Category of simplicial complexes] + """ + return [SimplicialComplexes()] + + class ParentMethods: + @abstract_method + def vertices(self): + """ + Return the vertices of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.vertices() + [0, 1, 2, 3, 4] + """ + + @abstract_method + def edges(self): + """ + Return the edges of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.edges() + [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] + """ + + def dimension(self): + """ + Return the dimension of ``self`` as a CW complex. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.dimension() + 1 + """ + if self.edges(): + return 1 + return 0 + + def facets(self): + """ + Return the facets of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: C.facets() + [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] + """ + return self.edges() + + def faces(self): + """ + Return the faces of ``self``. + + EXAMPLES:: + + sage: from sage.categories.graphs import Graphs + sage: C = Graphs().example() + sage: sorted(C.faces(), key=lambda x: (x.dimension(), x.value)) + [0, 1, 2, 3, 4, (0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] + """ + return set(self.edges()).union(self.vertices()) + diff --git a/src/sage/categories/groups.py b/src/sage/categories/groups.py index 0970c056ccd..59d32d80c03 100644 --- a/src/sage/categories/groups.py +++ b/src/sage/categories/groups.py @@ -19,6 +19,7 @@ from sage.categories.algebra_functor import AlgebrasCategory from sage.categories.cartesian_product import CartesianProductsCategory, cartesian_product from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.categories.topological_spaces import TopologicalSpacesCategory class Groups(CategoryWithAxiom): """ @@ -480,6 +481,7 @@ def conjugacy_class(self): return self.parent().conjugacy_class(self) Finite = LazyImport('sage.categories.finite_groups', 'FiniteGroups') + Lie = LazyImport('sage.categories.lie_groups', 'LieGroups', 'Lie') #Algebras = LazyImport('sage.categories.group_algebras', 'GroupAlgebras') class Commutative(CategoryWithAxiom): @@ -943,3 +945,15 @@ def order(self): """ from sage.misc.misc_c import prod return prod(c.cardinality() for c in self.cartesian_factors()) + + class Topological(TopologicalSpacesCategory): + """ + Category of topological groups. + + A topological group `G` is a group which has a topology such that + multiplication and taking inverses are continuous functions. + + REFERENCES: + + - :wikipedia:`Topological_group` + """ diff --git a/src/sage/categories/highest_weight_crystals.py b/src/sage/categories/highest_weight_crystals.py index 25b64b2e993..d2b99a7cbbc 100644 --- a/src/sage/categories/highest_weight_crystals.py +++ b/src/sage/categories/highest_weight_crystals.py @@ -428,11 +428,22 @@ def _Hom_(self, Y, category=None, **options): to The crystal of tableaux of type ['A', 2] and shape(s) [[2, 1]] sage: type(H) + + TESTS: + + Check that we fallback first to trying a crystal homset + (:trac:`19458`):: + + sage: Binf = crystals.infinity.Tableaux(['A',2]) + sage: Bi = crystals.elementary.Elementary(Binf.cartan_type(), 1) + sage: tens = Bi.tensor(Binf) + sage: Hom(Binf, tens) + Set of Crystal Morphisms from ... """ if category is None: category = self.category() - elif not category.is_subcategory(HighestWeightCrystals()): - raise TypeError("{} is not a subcategory of HighestWeightCrystals()".format(category)) + elif not category.is_subcategory(Crystals()): + raise TypeError("{} is not a subcategory of Crystals()".format(category)) if Y not in Crystals(): raise TypeError("{} is not a crystal".format(Y)) return HighestWeightCrystalHomset(self, Y, category=category, **options) diff --git a/src/sage/categories/homset.py b/src/sage/categories/homset.py index 51a3f327d18..208668e7687 100644 --- a/src/sage/categories/homset.py +++ b/src/sage/categories/homset.py @@ -283,17 +283,13 @@ def Hom(X, Y, category=None, check=True): sage: S = SimplicialComplex([[1,2], [1,4]]); S.rename("S") sage: Hom(S, S, SimplicialComplexes()) - Set of Morphisms from S to S in Category of simplicial complexes + Set of Morphisms from S to S in Category of finite simplicial complexes - sage: H = Hom(Set(), S, Sets()) - Traceback (most recent call last): - ... - ValueError: S is not in Category of sets + sage: Hom(Set(), S, Sets()) + Set of Morphisms from {} to S in Category of sets - sage: H = Hom(S, Set(), Sets()) - Traceback (most recent call last): - ... - ValueError: S is not in Category of sets + sage: Hom(S, Set(), Sets()) + Set of Morphisms from S to {} in Category of sets sage: H = Hom(S, S, ChainComplexes(QQ)) Traceback (most recent call last): @@ -479,7 +475,7 @@ def End(X, category=None): Category of finite groups sage: H = Hom(G,G) sage: H.homset_category() - Category of groups + Category of finite groups sage: H.category() Category of endsets of unital magmas @@ -651,7 +647,7 @@ def __reduce__(self): (, (Vector space of dimension 2 over Rational Field, Vector space of dimension 3 over Rational Field, - Category of vector spaces with basis over quotient fields, + Category of finite dimensional vector spaces with basis over (quotient fields and metric spaces), False)) TESTS:: @@ -859,7 +855,7 @@ def __call__(self, x=None, y=None, check=True, **options): sage: H = Hom(Set([1,2,3]), Set([1,2,3])) sage: f = H( lambda x: 4-x ) sage: f.parent() - Set of Morphisms from {1, 2, 3} to {1, 2, 3} in Category of sets + Set of Morphisms from {1, 2, 3} to {1, 2, 3} in Category of finite sets sage: f(1), f(2), f(3) # todo: not implemented sage: H = Hom(ZZ, QQ, Sets()) @@ -1168,18 +1164,18 @@ def reversed(self): sage: H = Hom(ZZ^2, ZZ^3); H Set of Morphisms from Ambient free module of rank 2 over - the principal ideal domain Integer Ring to Ambient free - module of rank 3 over the principal ideal domain Integer - Ring in Category of modules with basis over (euclidean - domains and infinite enumerated sets) + the principal ideal domain Integer Ring to Ambient free module + of rank 3 over the principal ideal domain Integer Ring in + Category of finite dimensional modules with basis over (euclidean + domains and infinite enumerated sets and metric spaces) sage: type(H) sage: H.reversed() Set of Morphisms from Ambient free module of rank 3 over - the principal ideal domain Integer Ring to Ambient free - module of rank 2 over the principal ideal domain Integer - Ring in Category of modules with basis over (euclidean - domains and infinite enumerated sets) + the principal ideal domain Integer Ring to Ambient free module + of rank 2 over the principal ideal domain Integer Ring in + Category of finite dimensional modules with basis over (euclidean + domains and infinite enumerated sets and metric spaces) sage: type(H.reversed()) """ diff --git a/src/sage/categories/hopf_algebras.py b/src/sage/categories/hopf_algebras.py index a28911b4448..46c5dd8fccc 100644 --- a/src/sage/categories/hopf_algebras.py +++ b/src/sage/categories/hopf_algebras.py @@ -8,7 +8,6 @@ # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ #****************************************************************************** - from sage.misc.lazy_import import LazyImport from category import Category from category_types import Category_over_base_ring @@ -47,7 +46,7 @@ def super_categories(self): def dual(self): """ - Returns the dual category + Return the dual category EXAMPLES: @@ -62,9 +61,10 @@ def dual(self): WithBasis = LazyImport('sage.categories.hopf_algebras_with_basis', 'HopfAlgebrasWithBasis') class ElementMethods: + def antipode(self): """ - Returns the antipode of self. + Return the antipode of self EXAMPLES:: diff --git a/src/sage/categories/hopf_algebras_with_basis.py b/src/sage/categories/hopf_algebras_with_basis.py index 26be399ee97..fb01c14d369 100644 --- a/src/sage/categories/hopf_algebras_with_basis.py +++ b/src/sage/categories/hopf_algebras_with_basis.py @@ -27,8 +27,7 @@ class HopfAlgebrasWithBasis(CategoryWithAxiom_over_base_ring): Category of hopf algebras with basis over Rational Field sage: C.super_categories() [Category of hopf algebras over Rational Field, - Category of algebras with basis over Rational Field, - Category of coalgebras with basis over Rational Field] + Category of bialgebras with basis over Rational Field] We now show how to use a simple Hopf algebra, namely the group algebra of the dihedral group (see also AlgebrasWithBasis):: diff --git a/src/sage/categories/lie_groups.py b/src/sage/categories/lie_groups.py new file mode 100644 index 00000000000..3883822f105 --- /dev/null +++ b/src/sage/categories/lie_groups.py @@ -0,0 +1,72 @@ +r""" +Lie Groups +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +#from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_types import Category_over_base_ring +from sage.categories.groups import Groups +from sage.categories.manifolds import Manifolds + +class LieGroups(Category_over_base_ring): + r""" + The category of Lie groups. + + A Lie group is a topological group with a smooth manifold structure. + + EXAMPLES:: + + sage: from sage.categories.lie_groups import LieGroups + sage: C = LieGroups(QQ); C + Category of Lie groups over Rational Field + + TESTS:: + + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.lie_groups import LieGroups + sage: LieGroups(QQ).super_categories() + [Category of topological groups, + Category of smooth manifolds over Rational Field] + """ + return [Groups().Topological(), Manifolds(self.base()).Smooth()] + + def additional_structure(self): + r""" + Return ``None``. + + Indeed, the category of Lie groups defines no new + structure: a morphism of topological spaces and of smooth + manifolds is a morphism as Lie groups. + + .. SEEALSO:: :meth:`Category.additional_structure` + + EXAMPLES:: + + sage: from sage.categories.lie_groups import LieGroups + sage: LieGroups(QQ).additional_structure() + """ + return None + + # Because Lie is a name that deserves to be capitalized + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: from sage.categories.lie_groups import LieGroups + sage: LieGroups(QQ) # indirect doctest + Category of Lie groups over Rational Field + """ + return "Lie groups over {}".format(self.base_ring()) + diff --git a/src/sage/categories/magmas.py b/src/sage/categories/magmas.py index c3f8f9ab458..42f03fc918a 100644 --- a/src/sage/categories/magmas.py +++ b/src/sage/categories/magmas.py @@ -468,9 +468,11 @@ def _test_one(self, **options): for x in tester.some_elements(): tester.assert_(x * one == x) tester.assert_(one * x == x) - # Check that one is immutable by asking its hash; - tester.assertEqual(type(one.__hash__()), int) - tester.assertEqual(one.__hash__(), one.__hash__()) + # Check that one is immutable if it looks like we can test this + if hasattr(one,"is_immutable"): + tester.assertEqual(one.is_immutable(),True) + if hasattr(one,"is_mutable"): + tester.assertEqual(one.is_mutable(),False) def is_empty(self): r""" diff --git a/src/sage/categories/manifolds.py b/src/sage/categories/manifolds.py new file mode 100644 index 00000000000..4a6433b95ec --- /dev/null +++ b/src/sage/categories/manifolds.py @@ -0,0 +1,349 @@ +r""" +Manifolds +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_types import Category_over_base_ring +from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring +from sage.categories.sets_cat import Sets +from sage.categories.fields import Fields + +class Manifolds(Category_over_base_ring): + r""" + The category of manifolds over any topological field. + + Let `k` be a topological field. A `d`-dimensional `k`-*manifold* `M` + is a second countable Hausdorff space such that the neighborhood of + any point `x \in M` is homeomorphic to `k^d`. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR); C + Category of manifolds over Real Field with 53 bits of precision + sage: C.super_categories() + [Category of topological spaces] + + TESTS:: + + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + def __init__(self, base, name=None): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR) + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + if base not in Fields().Topological(): + raise ValueError("base must be a topological field") + Category_over_base_ring.__init__(self, base, name) + + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).super_categories() + [Category of topological spaces] + """ + return [Sets().Topological()] + + def additional_structure(self): + r""" + Return ``None``. + + Indeed, the category of manifolds defines no new + structure: a morphism of topological spaces between + manifolds is a manifold morphism. + + .. SEEALSO:: :meth:`Category.additional_structure` + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).additional_structure() + """ + return None + + class ParentMethods: + @abstract_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: M = Manifolds(RR).example() + sage: M.dimension() + 3 + """ + + class SubcategoryMethods: + @cached_method + def Connected(self): + """ + Return the full subcategory of the connected objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Connected() + Category of connected manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: Manifolds(RR).Connected.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('Connected') + + @cached_method + def FiniteDimensional(self): + """ + Return the full subcategory of the finite dimensional + objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR).Connected().FiniteDimensional(); C + Category of finite dimensional connected manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Connected().FiniteDimensional.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('FiniteDimensional') + + @cached_method + def Differentiable(self): + """ + Return the subcategory of the differentiable objects + of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Differentiable() + Category of differentiable manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(RR).Differentiable()).run() + sage: Manifolds(RR).Differentiable.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('Differentiable') + + @cached_method + def Smooth(self): + """ + Return the subcategory of the smooth objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Smooth() + Category of smooth manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(RR).Smooth()).run() + sage: Manifolds(RR).Smooth.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('Smooth') + + @cached_method + def Analytic(self): + """ + Return the subcategory of the analytic objects of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Analytic() + Category of analytic manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(RR).Analytic()).run() + sage: Manifolds(RR).Analytic.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('Analytic') + + @cached_method + def AlmostComplex(self): + """ + Return the subcategory of the almost complex objects + of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).AlmostComplex() + Category of almost complex manifolds + over Real Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(RR).AlmostComplex()).run() + sage: Manifolds(RR).AlmostComplex.__module__ + 'sage.categories.manifolds' + """ + return self._with_axiom('AlmostComplex') + + @cached_method + def Complex(self): + """ + Return the subcategory of manifolds over `\CC` of ``self``. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(CC).Complex() + Category of complex manifolds over + Complex Field with 53 bits of precision + + TESTS:: + + sage: TestSuite(Manifolds(CC).Complex()).run() + sage: Manifolds(CC).Complex.__module__ + 'sage.categories.manifolds' + """ + return ComplexManifolds(self.base())._with_axioms(self.axioms()) + + class Differentiable(CategoryWithAxiom_over_base_ring): + """ + The category of differentiable manifolds. + + A differentiable manifold is a manifold with a differentiable atlas. + """ + + class Smooth(CategoryWithAxiom_over_base_ring): + """ + The category of smooth manifolds. + + A smooth manifold is a manifold with a smooth atlas. + """ + def extra_super_categories(self): + """ + Return the extra super categories of ``self``. + + A smooth manifold is differentiable. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Smooth().super_categories() # indirect doctest + [Category of differentiable manifolds + over Real Field with 53 bits of precision] + """ + return [Manifolds(self.base()).Differentiable()] + + class Analytic(CategoryWithAxiom_over_base_ring): + r""" + The category of complex manifolds. + + An analytic manifold is a manifold with an analytic atlas. + """ + def extra_super_categories(self): + """ + Return the extra super categories of ``self``. + + An analytic manifold is smooth. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).Analytic().super_categories() # indirect doctest + [Category of smooth manifolds + over Real Field with 53 bits of precision] + """ + return [Manifolds(self.base()).Smooth()] + + class AlmostComplex(CategoryWithAxiom_over_base_ring): + r""" + The category of almost complex manifolds. + + An *almost complex manifold* `M` is a manifold with a smooth tensor + field `J` of rank `(1, 1)` such that `J^2 = -1` when regarded as a + vector bundle isomorphism `J : TM \to TM` on the tangent bundle. + The tensor field `J` is called the *almost complex structure* of `M`. + """ + def extra_super_categories(self): + """ + Return the extra super categories of ``self``. + + An almost complex manifold is smooth. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).AlmostComplex().super_categories() # indirect doctest + [Category of smooth manifolds + over Real Field with 53 bits of precision] + """ + return [Manifolds(self.base()).Smooth()] + + class FiniteDimensional(CategoryWithAxiom_over_base_ring): + """ + Category of finite dimensional manifolds. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR).FiniteDimensional() + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + + class Connected(CategoryWithAxiom_over_base_ring): + """ + The category of connected manifolds. + + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: C = Manifolds(RR).Connected() + sage: TestSuite(C).run(skip="_test_category_over_bases") + """ + +class ComplexManifolds(Category_over_base_ring): + r""" + The category of complex manifolds. + + A `d`-dimensional complex manifold is a manifold whose underlying + vector space is `\CC^d` and has a holomorphic atlas. + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.manifolds import Manifolds + sage: Manifolds(RR).super_categories() + [Category of topological spaces] + """ + return [Manifolds(self.base()).Analytic()] + diff --git a/src/sage/categories/map.pyx b/src/sage/categories/map.pyx index 499b1b22486..00fc6ec3d61 100644 --- a/src/sage/categories/map.pyx +++ b/src/sage/categories/map.pyx @@ -628,9 +628,13 @@ cdef class Map(Element): sage: R. = QQ[] sage: f = R.hom([x+y, x-y], R) sage: f.category_for() - Join of Category of unique factorization domains and Category of commutative algebras over quotient fields + Join of Category of unique factorization domains + and Category of commutative algebras over (quotient fields and metric spaces) sage: f.category() - Category of endsets of unital magmas and right modules over quotient fields and left modules over quotient fields + Category of endsets of unital magmas + and right modules over (quotient fields and metric spaces) + and left modules over (quotient fields and metric spaces) + FIXME: find a better name for this method """ diff --git a/src/sage/categories/metric_spaces.py b/src/sage/categories/metric_spaces.py new file mode 100644 index 00000000000..c22ef8008db --- /dev/null +++ b/src/sage/categories/metric_spaces.py @@ -0,0 +1,258 @@ +r""" +Metric Spaces +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.categories.category import Category +from sage.categories.category_with_axiom import CategoryWithAxiom +from sage.categories.covariant_functorial_construction import RegressiveCovariantConstructionCategory +from sage.categories.with_realizations import WithRealizationsCategory + +class MetricSpacesCategory(RegressiveCovariantConstructionCategory): + + _functor_category = "Metric" + + @classmethod + def default_super_categories(cls, category): + """ + Return the default super categories of ``category.Metric()``. + + Mathematical meaning: if `A` is a metric space in the + category `C`, then `A` is also a topological space. + + INPUT: + + - ``cls`` -- the class ``MetricSpaces`` + - ``category`` -- a category `Cat` + + OUTPUT: + + A (join) category + + In practice, this returns ``category.Metric()``, joined + together with the result of the method + :meth:`RegressiveCovariantConstructionCategory.default_super_categories() + ` + (that is the join of ``category`` and ``cat.Metric()`` for + each ``cat`` in the super categories of ``category``). + + EXAMPLES: + + Consider ``category=Groups()``. Then, a group `G` with a metric + is simultaneously a topological group by itself, and a + metric space:: + + sage: Groups().Metric().super_categories() + [Category of topological groups, Category of metric spaces] + + This resulted from the following call:: + + sage: sage.categories.metric_spaces.MetricSpacesCategory.default_super_categories(Groups()) + Join of Category of topological groups and Category of metric spaces + """ + return Category.join([category.Topological(), + super(MetricSpacesCategory, cls).default_super_categories(category)]) + + # We currently don't have a use for this, but we probably will + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: Groups().Metric() # indirect doctest + Join of Category of topological groups and Category of metric spaces + """ + return "metric {}".format(self.base_category()._repr_object_names()) + +class MetricSpaces(MetricSpacesCategory): + r""" + The category of metric spaces. + + A *metric* on a set `S` is a function `d : S \times S \to \RR` + such that: + + - `d(a, b) \geq 0`, + - `d(a, b) = 0` if and only if `a = b`. + + A metric space is a set `S` with a distinguished metric. + + .. RUBRIC:: Implementation + + Objects in this category must implement either a ``dist`` on the parent + or the elements or ``metric`` on the parent; otherwise this will cause + an infinite recursion. + + .. TODO:: + + - Implement a general geodesics class. + - Implement a category for metric additive groups + and move the generic distance `d(a, b) = |a - b|` there. + - Incorperate the length of a geodesic as part of the default + distance cycle. + + EXAMPLES:: + + sage: from sage.categories.metric_spaces import MetricSpaces + sage: C = MetricSpaces() + sage: C + Category of metric spaces + sage: TestSuite(C).run() + """ + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: Sets().Metric() # indirect doctest + Category of metric spaces + """ + return "metric spaces" + + class ParentMethods: + def _test_metric(self, **options): + r""" + Test that this metric space has a properly implemented metric. + + INPUT: + + - ``options`` -- any keyword arguments accepted + by :meth:`_tester` + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: UHP._test_metric() + sage: elts = [UHP.random_element() for i in range(5)] + sage: UHP._test_metric(some_elements=elts) + """ + tester = self._tester(**options) + S = tester.some_elements() + dist = self.metric() + for a in S: + for b in S: + d = dist(a, b) + if a != b: + tester.assertGreater(d, 0) + else: + tester.assertEqual(d, 0) + + def metric(self): + """ + Return the metric of ``self``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: m = UHP.metric() + sage: p1 = UHP.get_point(5 + 7*I) + sage: p2 = UHP.get_point(1.0 + I) + sage: m(p1, p2) + 2.23230104635820 + """ + return lambda a,b: a.dist(b) + + def dist(self, a, b): + """ + Return the distance between ``a`` and ``b`` in ``self``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: p1 = UHP.get_point(5 + 7*I) + sage: p2 = UHP.get_point(1.0 + I) + sage: UHP.dist(p1, p2) + 2.23230104635820 + + sage: PD = HyperbolicPlane().PD() + sage: PD.dist(PD.get_point(0), PD.get_point(I/2)) + arccosh(5/3) + + TESTS:: + + sage: RR.dist(-1, pi) + 4.14159265358979 + sage: RDF.dist(1, -1/2) + 1.5 + sage: CC.dist(3, 2) + 1.00000000000000 + sage: CC.dist(-1, I) + 1.41421356237310 + sage: CDF.dist(-1, I) + 1.4142135623730951 + """ + return (self(a) - self(b)).abs() + + class ElementMethods: + def abs(self): + """ + Return the absolute value of ``self``. + + EXAMPLES:: + + sage: CC(I).abs() + 1.00000000000000 + """ + P = self.parent() + return P.metric()(self, P.zero()) + + def dist(self, b): + """ + Return the distance between ``self`` and ``other``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: p1 = UHP.get_point(5 + 7*I) + sage: p2 = UHP.get_point(1 + I) + sage: p1.dist(p2) + arccosh(33/7) + """ + return self.parent().dist(self, b) + + class WithRealizations(WithRealizationsCategory): + class ParentMethods: + def dist(self, a, b): + """ + Return the distance between ``a`` and ``b`` by converting them + to a realization of ``self`` and doing the computation. + + EXAMPLES:: + + sage: H = HyperbolicPlane() + sage: PD = H.PD() + sage: p1 = PD.get_point(0) + sage: p2 = PD.get_point(I/2) + sage: H.dist(p1, p2) + arccosh(5/3) + """ + R = self.a_realization() + return R.dist(R(a), R(b)) + + class SubcategoryMethods: + @cached_method + def Complete(self): + """ + Return the full subcategory of the complete objects of ``self``. + + EXAMPLES:: + + sage: Sets().Metric().Complete() + Category of complete metric spaces + + TESTS:: + + sage: TestSuite(Sets().Metric().Complete()).run() + sage: Sets().Metric().Complete.__module__ + 'sage.categories.metric_spaces' + """ + return self._with_axiom('Complete') + + class Complete(CategoryWithAxiom): + """ + The category of complete metric spaces. + """ + diff --git a/src/sage/categories/modules_with_basis.py b/src/sage/categories/modules_with_basis.py index 282b6fc7fa9..aa4dc15e85c 100644 --- a/src/sage/categories/modules_with_basis.py +++ b/src/sage/categories/modules_with_basis.py @@ -16,8 +16,10 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -from sage.misc.lazy_import import LazyImport +from sage.misc.lazy_import import LazyImport, lazy_import +from sage.misc.lazy_attribute import lazy_attribute from sage.misc.cachefunc import cached_method +from sage.misc.abstract_method import abstract_method from sage.misc.sage_itertools import max_cmp, min_cmp from sage.categories.homsets import HomsetsCategory from sage.categories.cartesian_product import CartesianProductsCategory @@ -25,8 +27,9 @@ from sage.categories.dual import DualObjectsCategory from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring from sage.categories.modules import Modules +from sage.categories.poor_man_map import PoorManMap +from sage.rings.infinity import Infinity from sage.structure.element import Element, parent -from sage.misc.lazy_import import lazy_import lazy_import('sage.modules.with_basis.morphism', ['ModuleMorphismByLinearity', 'ModuleMorphismFromMatrix', @@ -158,7 +161,7 @@ def _call_(self, x): Vector space of dimension 3 over Rational Field If ``x`` itself is not a module with basis, but there is a - canonical one associated to it, the later is returned:: + canonical one associated to it, the latter is returned:: sage: CQ(AbelianVariety(Gamma0(37))) # indirect doctest Vector space of dimension 4 over Rational Field @@ -191,6 +194,12 @@ def is_abelian(self): Graded = LazyImport('sage.categories.graded_modules_with_basis', 'GradedModulesWithBasis') Super = LazyImport('sage.categories.super_modules_with_basis', 'SuperModulesWithBasis') + # To implement a module_with_basis you need to implement the + # following methods: + # - On the parent class, either basis() or an _indices attribute and + # monomial(). + # - On the element class, monomial_coefficients(). + class ParentMethods: @cached_method def basis(self): @@ -235,7 +244,9 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, - ``on_basis`` -- a function `f` from `I` to `Y` - ``diagonal`` -- a function `d` from `I` to `R` - ``function`` -- a function `f` from `X` to `Y` - - ``matrix`` -- a matrix of size `\dim X \times \dim Y` or `\dim Y \times \dim X` + - ``matrix`` -- a matrix of size `\dim Y \times \dim X` + (if the keyword ``side`` is set to ``'left'``) or + `\dim Y \times \dim X` (if this keyword is ``'right'``) Further options include: @@ -329,7 +340,8 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, sage: phi.category_for() # todo: not implemented (ZZ is currently not in Modules(ZZ)) Category of modules over Integer Ring - Or more generaly any ring admitting a coercion map from the base ring:: + Or more generaly any ring admitting a coercion map from + the base ring:: sage: phi = X.module_morphism(on_basis=lambda i: i, codomain=RR ) sage: phi( 2 * X.monomial(1) + 3 * X.monomial(-1) ) @@ -386,13 +398,17 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, sage: phi = X.module_morphism( on_basis=on_basis, codomain=Y ) Traceback (most recent call last): ... - ValueError: codomain(=Free module generated by {'z'} over Rational Field) should be a module over the base ring of the domain(=Free module generated by {'x', 'y'} over Real Field with 53 bits of precision) + ValueError: codomain(=Free module generated by {'z'} over Rational Field) + should be a module over the base ring of the + domain(=Free module generated by {'x', 'y'} over Real Field with 53 bits of precision) sage: Y = CombinatorialFreeModule(RR['q'],['z']) sage: phi = Y.module_morphism( on_basis=on_basis, codomain=X ) Traceback (most recent call last): ... - ValueError: codomain(=Free module generated by {'x', 'y'} over Real Field with 53 bits of precision) should be a module over the base ring of the domain(=Free module generated by {'z'} over Univariate Polynomial Ring in q over Real Field with 53 bits of precision) + ValueError: codomain(=Free module generated by {'x', 'y'} over Real Field with 53 bits of precision) + should be a module over the base ring of the + domain(=Free module generated by {'z'} over Univariate Polynomial Ring in q over Real Field with 53 bits of precision) With the ``diagonal=d`` argument, this constructs the @@ -400,7 +416,7 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, .. MATH:: - `g(x_i) = d(i) y_i` + `g(x_i) = d(i) y_i`. This assumes that the respective bases `x` and `y` of `X` and `Y` have the same index set `I`:: @@ -520,7 +536,7 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, ValueError: diagonal (=3) should be a function """ - if not len([x for x in [matrix, on_basis, function, diagonal] if x is not None]) == 1: + if len([x for x in [matrix, on_basis, function, diagonal] if x is not None]) != 1: raise ValueError("module_morphism() takes exactly one option out of `matrix`, `on_basis`, `function`, `diagonal`") if matrix is not None: return ModuleMorphismFromMatrix(domain=self, matrix=matrix, **keywords) @@ -772,6 +788,277 @@ def tensor(*parents): """ return parents[0].__class__.Tensor(parents, category = tensor.category_from_parents(parents)) + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: S = SymmetricGroupAlgebra(QQ, 4) + sage: S.cardinality() + +Infinity + sage: S = SymmetricGroupAlgebra(GF(2), 4) # not tested -- MRO bug :trac:`15475` + sage: S.cardinality() # not tested -- MRO bug :trac:`15475` + 16777216 + sage: S.cardinality().factor() # not tested -- MRO bug :trac:`15475` + 2^24 + + sage: E. = ExteriorAlgebra(QQ) + sage: E.cardinality() + +Infinity + sage: E. = ExteriorAlgebra(GF(3)) + sage: E.cardinality() + 81 + + sage: s = SymmetricFunctions(GF(2)).s() + sage: s.cardinality() + +Infinity + """ + if self.dimension() == Infinity: + return Infinity + return self.base_ring().cardinality() ** self.dimension() + + def monomial(self, i): + """ + Return the basis element indexed by ``i``. + + INPUT: + + - ``i`` -- an element of the index set + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: F.monomial('a') + B['a'] + + ``F.monomial`` is in fact (almost) a map:: + + sage: F.monomial + Term map from {'a', 'b', 'c'} to Free module generated by {'a', 'b', 'c'} over Rational Field + """ + return self.basis()[i] + + def _sum_of_monomials(self, indices): + """ + TESTS:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: F._sum_of_monomials(['a', 'b']) + B['a'] + B['b'] + """ + # This is the generic implementation. When implementing a + # concrete instance of a module with basis, you probably want + # to override it with something faster. + return self.sum(self.monomial(index) for index in indices) + + @lazy_attribute + def sum_of_monomials(self): + """ + Return the sum of the basis elements with indices in + ``indices``. + + INPUT: + + - ``indices`` -- an list (or iterable) of indices of basis + elements + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: F.sum_of_monomials(['a', 'b']) + B['a'] + B['b'] + + sage: F.sum_of_monomials(['a', 'b', 'a']) + 2*B['a'] + B['b'] + + ``F.sum_of_monomials`` is in fact (almost) a map:: + + sage: F.sum_of_monomials + A map to Free module generated by {'a', 'b', 'c'} over Rational Field + """ + # domain = iterables of basis indices of self. + return PoorManMap(self._sum_of_monomials, codomain = self) + + def monomial_or_zero_if_none(self, i): + """ + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: F.monomial_or_zero_if_none('a') + B['a'] + sage: F.monomial_or_zero_if_none(None) + 0 + """ + if i is None: + return self.zero() + return self.monomial(i) + + def term(self, index, coeff=None): + """ + Construct a term in ``self``. + + INPUT: + + - ``index`` -- the index of a basis element + - ``coeff`` -- an element of the coefficient ring (default: one) + + OUTPUT: + + ``coeff * B[index]``, where ``B`` is the basis of ``self``. + + EXAMPLES:: + + sage: m = matrix([[0,1],[1,1]]) + sage: J. = JordanAlgebra(m) + sage: J.term(1, -2) + 0 + (-2, 0) + + Design: should this do coercion on the coefficient ring? + """ + if coeff is None: + coeff = self.base_ring().one() + return coeff * self.monomial(index) + + def sum_of_terms(self, terms): + """ + Construct a sum of terms of ``self``. + + INPUT: + + - ``terms`` -- a list (or iterable) of pairs ``(index, coeff)`` + + OUTPUT: + + Sum of ``coeff * B[index]`` over all ``(index, coeff)`` in + ``terms``, where ``B`` is the basis of ``self``. + + EXAMPLES:: + + sage: m = matrix([[0,1],[1,1]]) + sage: J. = JordanAlgebra(m) + sage: J.sum_of_terms([(0, 2), (2, -3)]) + 2 + (0, -3) + """ + return self.sum(self.term(index, coeff) for (index, coeff) in terms) + + def linear_combination(self, iter_of_elements_coeff, factor_on_left=True): + """ + Return the linear combination `\lambda_1 v_1 + \cdots + + \lambda_k v_k` (resp. the linear combination `v_1 \lambda_1 + + \cdots + v_k \lambda_k`) where ``iter_of_elements_coeff`` iterates + through the sequence `((\lambda_1, v_1), ..., (\lambda_k, v_k))`. + + INPUT: + + - ``iter_of_elements_coeff`` -- iterator of pairs + ``(element, coeff)`` with ``element`` in ``self`` and + ``coeff`` in ``self.base_ring()`` + + - ``factor_on_left`` -- (optional) if ``True``, the coefficients + are multiplied from the left; if ``False``, the coefficients + are multiplied from the right + + EXAMPLES:: + + sage: m = matrix([[0,1],[1,1]]) + sage: J. = JordanAlgebra(m) + sage: J.linear_combination(((a+b, 1), (-2*b + c, -1))) + 1 + (3, -1) + """ + if factor_on_left: + return self.sum(coeff * element + for element, coeff in iter_of_elements_coeff) + else: + return self.sum(element * coeff + for element, coeff in iter_of_elements_coeff) + + def _apply_module_morphism(self, x, on_basis, codomain=False): + """ + Return the image of ``x`` under the module morphism defined by + extending :func:`on_basis` by linearity. + + INPUT: + + - ``x`` -- a element of ``self`` + + - ``on_basis`` -- a function that takes in an object indexing + a basis element and returns an element of the codomain + + - ``codomain`` -- (optional) the codomain of the morphism (by + default, it is computed using :func:`on_basis`) + + If ``codomain`` is not specified, then the function tries to + compute the codomain of the module morphism by finding the image + of one of the elements in the support; hence :func:`on_basis` + should return an element whose parent is the codomain. + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).schur() + sage: a = s([3]) + s([2,1]) + s([1,1,1]) + sage: b = 2*a + sage: f = lambda part: Integer( len(part) ) + sage: s._apply_module_morphism(a, f) #1+2+3 + 6 + sage: s._apply_module_morphism(b, f) #2*(1+2+3) + 12 + sage: s._apply_module_morphism(s(0), f) + 0 + sage: s._apply_module_morphism(s(1), f) + 0 + sage: s._apply_module_morphism(s(1), lambda part: len(part), ZZ) + 0 + sage: s._apply_module_morphism(s(1), lambda part: len(part)) + Traceback (most recent call last): + ... + ValueError: codomain could not be determined + """ + if x == self.zero(): + if not codomain: + from sage.combinat.family import Family + B = Family(self.basis()) + try: + z = B.first() + except StopIteration: + raise ValueError('codomain could not be determined') + codomain = on_basis(z).parent() + return codomain.zero() + + if not codomain: + keys = x.support() + key = keys[0] + try: + codomain = on_basis(key).parent() + except Exception: + raise ValueError('codomain could not be determined') + + if hasattr( codomain, 'linear_combination' ): + mc = x.monomial_coefficients(copy=False) + return codomain.linear_combination( (on_basis(key), coeff) + for key, coeff in mc.iteritems() ) + else: + return_sum = codomain.zero() + mc = x.monomial_coefficients(copy=False) + for key, coeff in mc.iteritems(): + return_sum += coeff * on_basis(key) + return return_sum + + def _apply_module_endomorphism(self, x, on_basis): + """ + This takes in a function ``on_basis`` from the basis indices + to the elements of ``self``, and applies it linearly to ``x``. + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).schur() + sage: f = lambda part: 2*s(part.conjugate()) + sage: s._apply_module_endomorphism( s([2,1]) + s([1,1,1]), f) + 2*s[2, 1] + 2*s[3] + """ + mc = x.monomial_coefficients(copy=False) + return self.linear_combination( (on_basis(key), coeff) + for key, coeff in mc.iteritems() ) class ElementMethods: # TODO: Define the appropriate element methods here (instead of in @@ -785,6 +1072,311 @@ class ElementMethods: # """ # return self._lmul_(-self.parent().base_ring().one(), self) + @abstract_method + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements + in the support of ``self`` and whose values are the + corresponding coefficients. + + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] + 3*B['c'] + sage: d = f.monomial_coefficients() + sage: d['a'] + 1 + sage: d['c'] + 3 + + TESTS: + + We check that we make a copy of the coefficient dictonary:: + + sage: F = CombinatorialFreeModule(ZZ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] + 3*B['c'] + sage: d = f.monomial_coefficients() + sage: d['a'] = 5 + sage: f + B['a'] + 3*B['c'] + """ + + def __getitem__(self, m): + """ + Return the coefficient of ``m`` in ``self``. + + EXAMPLES:: + + sage: p = Partition([2,1]) + sage: q = Partition([1,1,1]) + sage: s = SymmetricFunctions(QQ).schur() + sage: a = s(p) + sage: a._coefficient_fast([2,1]) + Traceback (most recent call last): + ... + TypeError: unhashable type: 'list' + + :: + + sage: a._coefficient_fast(p) + 1 + sage: a._coefficient_fast(q) + 0 + sage: a[p] + 1 + sage: a[q] + 0 + """ + return self.monomial_coefficients(copy=False).get(m, self.base_ring().zero()) + + def coefficient(self, m): + """ + Return the coefficient of ``m`` in ``self`` and raise an error + if ``m`` is not in the basis indexing set. + + INPUT: + + - ``m`` -- a basis index of the parent of ``self`` + + OUTPUT: + + The ``B[m]``-coordinate of ``self`` with respect to the basis + ``B``. Here, ``B`` denotes the given basis of the parent of + ``self``. + + EXAMPLES:: + + sage: s = CombinatorialFreeModule(QQ, Partitions()) + sage: z = s([4]) - 2*s([2,1]) + s([1,1,1]) + s([1]) + sage: z.coefficient([4]) + 1 + sage: z.coefficient([2,1]) + -2 + sage: z.coefficient(Partition([2,1])) + -2 + sage: z.coefficient([1,2]) + Traceback (most recent call last): + ... + AssertionError: [1, 2] should be an element of Partitions + sage: z.coefficient(Composition([2,1])) + Traceback (most recent call last): + ... + AssertionError: [2, 1] should be an element of Partitions + + Test that ``coefficient`` also works for those parents that do + not yet have an element_class:: + + sage: G = DihedralGroup(3) + sage: F = CombinatorialFreeModule(QQ, G) + sage: hasattr(G, "element_class") + False + sage: g = G.an_element() + sage: (2*F.monomial(g)).coefficient(g) + 2 + """ + # NT: coefficient_fast should be the default, just with appropriate assertions + # that can be turned on or off + C = self.parent().basis().keys() + # TODO: This should raise a ValueError - TS + assert m in C, "%s should be an element of %s"%(m, C) + if hasattr(C, "element_class") and not isinstance(m, C.element_class): + m = C(m) + return self[m] + + def is_zero(self): + """ + Return ``True`` if and only if ``self == 0``. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: f.is_zero() + False + sage: F.zero().is_zero() + True + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: s([2,1]).is_zero() + False + sage: s(0).is_zero() + True + sage: (s([2,1]) - s([2,1])).is_zero() + True + """ + zero = self.parent().base_ring().zero() + return all(v == zero for v in self.monomial_coefficients(copy=False).values()) + + def __len__(self): + """ + Return the number of basis elements whose coefficients in + ``self`` are nonzero. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: len(f) + 2 + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) + sage: len(z) + 4 + """ + zero = self.parent().base_ring().zero() + return len([key for key, coeff in self.monomial_coefficients(copy=False).iteritems() + if coeff != zero]) + + def length(self): + """ + Return the number of basis elements whose coefficients in + ``self`` are nonzero. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: f.length() + 2 + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) + sage: z.length() + 4 + """ + return len(self) + + def support(self): + """ + Return a list of the objects indexing the basis of + ``self.parent()`` whose corresponding coefficients of + ``self`` are non-zero. + + This method returns these objects in an arbitrary order. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: sorted(f.support()) + ['a', 'c'] + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) + sage: sorted(z.support()) + [[1], [1, 1, 1], [2, 1], [4]] + """ + zero = self.parent().base_ring().zero() + return [key for key, coeff in self.monomial_coefficients(copy=False).iteritems() + if coeff != zero] + + def monomials(self): + """ + Return a list of the monomials of ``self`` (in an arbitrary + order). + + The monomials of an element `a` are defined to be the basis + elements whose corresponding coefficients of `a` are + non-zero. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] + 2*B['c'] + sage: f.monomials() + [B['a'], B['c']] + + sage: (F.zero()).monomials() + [] + """ + P = self.parent() + return [P.monomial(key) for key in self.support()] + + def terms(self): + """ + Return a list of the (non-zero) terms of ``self`` (in an + arbitrary order). + + .. SEEALSO:: :meth:`monomials` + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] + 2*B['c'] + sage: f.terms() + [B['a'], 2*B['c']] + """ + P = self.parent() + zero = P.base_ring().zero() + return [P.term(key, value) + for key, value in self.monomial_coefficients(copy=False).iteritems() + if value != zero] + + def coefficients(self, sort=True): + """ + Return a list of the (non-zero) coefficients appearing on + the basis elements in ``self`` (in an arbitrary order). + + INPUT: + + - ``sort`` -- (default: ``True``) to sort the coefficients + based upon the default ordering of the indexing set + + .. SEEALSO:: + + :meth:`~sage.categories.finite_dimensional_modules_with_basis.FiniteDimensionalModulesWithBasis.ElementMethods.dense_coefficient_list` + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] - 3*B['c'] + sage: f.coefficients() + [1, -3] + sage: f = B['c'] - 3*B['a'] + sage: f.coefficients() + [-3, 1] + + :: + + sage: s = SymmetricFunctions(QQ).schur() + sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) + sage: z.coefficients() + [1, 1, 1, 1] + """ + zero = self.parent().base_ring().zero() + mc = self.monomial_coefficients(copy=False) + if not sort: + return [value for key, value in mc.iteritems() if value != zero] + + v = sorted([(key, value) for key, value in mc.iteritems() + if value != zero]) + return [value for key, value in v] + def support_of_term(self): """ Return the support of ``self``, where ``self`` is a monomial @@ -808,7 +1400,7 @@ def support_of_term(self): if len(self) == 1: return self.support()[0] else: - raise ValueError("%s is not a single term"%(self)) + raise ValueError("{} is not a single term".format(self)) def leading_support(self, cmp=None): r""" @@ -838,7 +1430,6 @@ def leading_support(self, cmp=None): """ return max_cmp(self.support(), cmp) - def leading_item(self, cmp=None): r""" Return the pair ``(k, c)`` where diff --git a/src/sage/categories/morphism.pyx b/src/sage/categories/morphism.pyx index a6c357407ba..a76d6ab4aa3 100644 --- a/src/sage/categories/morphism.pyx +++ b/src/sage/categories/morphism.pyx @@ -162,7 +162,10 @@ cdef class Morphism(Map): sage: R. = ZZ[] sage: f = R.hom([t**2]) sage: f.category() - Category of endsets of unital magmas and right modules over (euclidean domains and infinite enumerated sets) and left modules over (euclidean domains and infinite enumerated sets) + Category of endsets of unital magmas and right modules over + (euclidean domains and infinite enumerated sets and metric spaces) + and left modules over (euclidean domains + and infinite enumerated sets and metric spaces) sage: K = CyclotomicField(12) sage: L = CyclotomicField(132) diff --git a/src/sage/categories/primer.py b/src/sage/categories/primer.py index b8c70db515d..5e32c54e985 100644 --- a/src/sage/categories/primer.py +++ b/src/sage/categories/primer.py @@ -348,9 +348,12 @@ sage: ZZ.category() Join of Category of euclidean domains and Category of infinite enumerated sets + and Category of metric spaces sage: ZZ.categories() - [Join of Category of euclidean domains and Category of infinite enumerated sets, + [Join of Category of euclidean domains + and Category of infinite enumerated sets + and Category of metric spaces, Category of euclidean domains, Category of principal ideal domains, Category of unique factorization domains, Category of gcd domains, Category of integral domains, Category of domains, @@ -360,7 +363,8 @@ Category of commutative magmas, Category of unital magmas, Category of magmas, Category of commutative additive groups, ..., Category of additive magmas, Category of infinite enumerated sets, Category of enumerated sets, - Category of infinite sets, Category of sets, + Category of infinite sets, Category of metric spaces, + Category of topological spaces, Category of sets, Category of sets with partial maps, Category of objects] diff --git a/src/sage/categories/regular_crystals.py b/src/sage/categories/regular_crystals.py index 9fa8085633b..85deb76afeb 100644 --- a/src/sage/categories/regular_crystals.py +++ b/src/sage/categories/regular_crystals.py @@ -453,8 +453,7 @@ def wt_zero(x): if checker(y): edges.append([x, y, i]) from sage.graphs.all import DiGraph - G = DiGraph(edges) - G.add_vertices(X) + G = DiGraph([X, edges], format="vertices_and_edges", immutable=True) if have_dot2tex(): G.set_latex_options(format="dot2tex", edge_labels=True, color_by_label=self.cartan_type()._index_set_coloring) @@ -560,7 +559,7 @@ def demazure_operator_simple(self, i, ring = None): sage: K = crystals.KirillovReshetikhin(['A',2,1],2,1) sage: t = K(rows=[[3],[2]]) sage: t.demazure_operator_simple(0) - B[[[2, 3]]] + B[[[1, 2]]] + B[[[1, 2]]] + B[[[2, 3]]] TESTS:: @@ -874,8 +873,8 @@ def dual_equivalence_class(self, index_set=None): if y not in visited: todo.add(y) from sage.graphs.graph import Graph - G = Graph(edges, multiedges=True) - G.add_vertices(visited) + G = Graph([visited, edges], format="vertices_and_edges", + immutable=True, multiedges=True) if have_dot2tex(): G.set_latex_options(format="dot2tex", edge_labels=True, color_by_label=self.cartan_type()._index_set_coloring) diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index dd4f4e3912c..41e312055f3 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -580,16 +580,17 @@ def quotient(self, I, names=None): sage: F. = FreeAlgebra(QQ) sage: from sage.rings.noncommutative_ideals import Ideal_nc + sage: from itertools import product sage: class PowerIdeal(Ideal_nc): - ... def __init__(self, R, n): - ... self._power = n - ... Ideal_nc.__init__(self,R,[R.prod(m) for m in CartesianProduct(*[R.gens()]*n)]) - ... def reduce(self,x): - ... R = self.ring() - ... return add([c*R(m) for m,c in x if len(m) n: - from random import sample - S = sample(S, n) - - for x, y in S: + from sage.misc.misc import some_tuples + for x,y in some_tuples(S, 2, tester._max_runs): tester.assertEqual(x==y, y==x, LazyFormat("non symmetric equality: %s but %s")%( print_compare(x, y), print_compare(y, x))) @@ -1279,14 +1297,9 @@ def _test_elements_neq(self, **options): """ tester = self._tester(**options) S = list(tester.some_elements()) + [None, 0] - n = tester._max_runs - from sage.combinat.cartesian_product import CartesianProduct - S = CartesianProduct(S,S) - if len(S) > n: - from random import sample - S = sample(S, n) - for x,y in S: + from sage.misc.misc import some_tuples + for x,y in some_tuples(S, 2, tester._max_runs): tester.assertNotEqual(x == y, x != y, LazyFormat("__eq__ and __ne__ inconsistency:\n" " %s == %s returns %s but %s != %s returns %s")%( @@ -1697,6 +1710,10 @@ def __invert__(self): Facade = LazyImport('sage.categories.facade_sets', 'FacadeSets') Finite = LazyImport('sage.categories.finite_sets', 'FiniteSets', at_startup=True) + Topological = LazyImport('sage.categories.topological_spaces', + 'TopologicalSpaces', 'Topological', at_startup=True) + Metric = LazyImport('sage.categories.metric_spaces', 'MetricSpaces', + 'Mertic', at_startup=True) class Infinite(CategoryWithAxiom): @@ -2028,6 +2045,105 @@ def example(self): class ParentMethods: + def __iter__(self): + r""" + Return a lexicographic iterator for the elements of this cartesian product. + + EXAMPLES:: + + sage: for x,y in cartesian_product([Set([1,2]), Set(['a','b'])]): + ....: print x,y + 1 a + 1 b + 2 a + 2 b + + sage: A = FiniteEnumeratedSets()(["a", "b"]) + sage: B = FiniteEnumeratedSets().example(); B + An example of a finite enumerated set: {1,2,3} + sage: C = cartesian_product([A, B, A]); C + The cartesian product of ({'a', 'b'}, An example of a finite enumerated set: {1,2,3}, {'a', 'b'}) + sage: C in FiniteEnumeratedSets() + True + sage: list(C) + [('a', 1, 'a'), ('a', 1, 'b'), ('a', 2, 'a'), ('a', 2, 'b'), ('a', 3, 'a'), ('a', 3, 'b'), + ('b', 1, 'a'), ('b', 1, 'b'), ('b', 2, 'a'), ('b', 2, 'b'), ('b', 3, 'a'), ('b', 3, 'b')] + sage: C.__iter__.__module__ + 'sage.categories.enumerated_sets' + + sage: F22 = GF(2).cartesian_product(GF(2)) + sage: list(F22) + [(0, 0), (0, 1), (1, 0), (1, 1)] + + sage: C = cartesian_product([Permutations(10)]*4) + sage: it = iter(C) + sage: next(it) + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + sage: next(it) + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 10, 9]) + + .. WARNING:: + + The elements are returned in lexicographic order, + which gives a valid enumeration only if all + factors, but possibly the first one, are + finite. So the following one is fine:: + + sage: it = iter(cartesian_product([ZZ, GF(2)])) + sage: [next(it) for _ in range(10)] + [(0, 0), (0, 1), (1, 0), (1, 1), + (-1, 0), (-1, 1), (2, 0), (2, 1), + (-2, 0), (-2, 1)] + + But this one is not:: + + sage: it = iter(cartesian_product([GF(2), ZZ])) + sage: [next(it) for _ in range(10)] + doctest:...: UserWarning: Sage is not able to determine + whether the factors of this cartesian product are + finite. The lexicographic ordering might not go through + all elements. + [(0, 0), (0, 1), (0, -1), (0, 2), (0, -2), + (0, 3), (0, -3), (0, 4), (0, -4), (0, 5)] + + .. NOTE:: + + Here it would be faster to use :func:`itertools.product` for sets + of small size. But the latter expands all factor in memory! + So we can not reasonably use it in general. + + ALGORITHM: + + Recipe 19.9 in the Python Cookbook by Alex Martelli + and David Ascher. + """ + if any(f not in Sets().Finite() for f in self.cartesian_factors()[1:]): + from warnings import warn + warn("Sage is not able to determine whether the factors of " + "this cartesian product are finite. The lexicographic " + "ordering might not go through all elements.") + + # visualize an odometer, with "wheels" displaying "digits"...: + factors = list(self.cartesian_factors()) + wheels = map(iter, factors) + digits = [next(it) for it in wheels] + while True: + yield self._cartesian_product_of_elements(digits) + for i in range(len(digits)-1, -1, -1): + try: + digits[i] = next(wheels[i]) + break + except StopIteration: + wheels[i] = iter(factors[i]) + digits[i] = next(wheels[i]) + else: + break @cached_method def an_element(self): @@ -2076,7 +2192,15 @@ def is_finite(self): True """ f = self.cartesian_factors() - return any(c.is_empty() for c in f) or all(c.is_finite() for c in f) + try: + # Note: some parent might not implement "is_empty". So we + # carefully isolate this test. + test = any(c.is_empty() for c in f) + except (AttributeError, NotImplementedError): + pass + else: + if test: return test + return all(c.is_finite() for c in f) def cardinality(self): r""" @@ -2109,7 +2233,7 @@ def cardinality(self): # Note: some parent might not implement "is_empty". So we # carefully isolate this test. is_empty = any(c.is_empty() for c in f) - except Exception: + except (AttributeError,NotImplementedError): pass else: if is_empty: diff --git a/src/sage/categories/simplicial_complexes.py b/src/sage/categories/simplicial_complexes.py new file mode 100644 index 00000000000..aa19a8a277b --- /dev/null +++ b/src/sage/categories/simplicial_complexes.py @@ -0,0 +1,101 @@ +""" +Simplicial Complexes +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.categories.category_singleton import Category_singleton +from sage.categories.category_with_axiom import CategoryWithAxiom +#from sage.categories.cw_complexes import CWComplexes +from sage.categories.sets_cat import Sets + +class SimplicialComplexes(Category_singleton): + r""" + The category of abstract simplicial complexes. + + An abstract simplicial complex `A` is a collection of sets `X` + such that: + + - `\emptyset \in A`, + - if `X \subset Y \in A`, then `X \in A`. + + .. TODO:: + + Implement the category of simplicial complexes considered + as :class:`CW complexes ` + and rename this to the category of ``AbstractSimplicialComplexes`` + with appropriate functors. + + EXAMPLES:: + + sage: from sage.categories.simplicial_complexes import SimplicialComplexes + sage: C = SimplicialComplexes(); C + Category of simplicial complexes + + TESTS:: + + sage: TestSuite(C).run() + """ + @cached_method + def super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.simplicial_complexes import SimplicialComplexes + sage: SimplicialComplexes().super_categories() + [Category of sets] + """ + return [Sets()] + + class Finite(CategoryWithAxiom): + """ + Category of finite simplicial complexes. + """ + class ParentMethods: + @cached_method + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: S = SimplicialComplex([[1,3,4], [1,2],[2,5],[4,5]]) + sage: S.dimension() + 2 + """ + return max(c.dimension() for c in self.facets()) + + class ParentMethods: + @abstract_method + def facets(self): + """ + Return the facets of ``self``. + + EXAMPLES:: + + sage: S = SimplicialComplex([[1,3,4], [1,2],[2,5],[4,5]]) + sage: S.facets() + {(1, 2), (1, 3, 4), (2, 5), (4, 5)} + """ + + @abstract_method + def faces(self): + """ + Return the faces of ``self``. + + EXAMPLES:: + + sage: S = SimplicialComplex([[1,3,4], [1,2],[2,5],[4,5]]) + sage: S.faces() + {-1: {()}, + 0: {(1,), (2,), (3,), (4,), (5,)}, + 1: {(1, 2), (1, 3), (1, 4), (2, 5), (3, 4), (4, 5)}, + 2: {(1, 3, 4)}} + """ + diff --git a/src/sage/categories/topological_spaces.py b/src/sage/categories/topological_spaces.py new file mode 100644 index 00000000000..41f98e0decc --- /dev/null +++ b/src/sage/categories/topological_spaces.py @@ -0,0 +1,108 @@ +r""" +Topological Spaces +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.categories.category_with_axiom import CategoryWithAxiom +from sage.categories.covariant_functorial_construction import RegressiveCovariantConstructionCategory +from sage.categories.sets_cat import Sets + +class TopologicalSpacesCategory(RegressiveCovariantConstructionCategory): + + _functor_category = "Topological" + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: Groups().Topological() # indirect doctest + Category of topological groups + """ + return "topological {}".format(self.base_category()._repr_object_names()) + +class TopologicalSpaces(TopologicalSpacesCategory): + """ + The category of topological spaces. + + EXAMPLES:: + + sage: Sets().Topological() + Category of topological spaces + sage: Sets().Topological().super_categories() + [Category of sets] + + The category of topological spaces defines the topological structure, + which shall be preserved by morphisms:: + + sage: Sets().Topological().additional_structure() + Category of topological spaces + + TESTS:: + + sage: TestSuite(Sets().Topological()).run() + """ + # We must override the general object because the names don't match + _base_category_class = (Sets,) + + def _repr_object_names(self): + """ + EXAMPLES:: + + sage: Sets().Topological() # indirect doctest + Category of topological spaces + """ + return "topological spaces" + + class SubcategoryMethods: + @cached_method + def Connected(self): + """ + Return the full subcategory of the connected objects of ``self``. + + EXAMPLES:: + + sage: Sets().Topological().Connected() + Category of connected topological spaces + + TESTS:: + + sage: TestSuite(Sets().Topological().Connected()).run() + sage: Sets().Topological().Connected.__module__ + 'sage.categories.topological_spaces' + """ + return self._with_axiom('Connected') + + @cached_method + def Compact(self): + """ + Return the subcategory of the compact objects of ``self``. + + EXAMPLES:: + + sage: Sets().Topological().Compact() + Category of compact topological spaces + + TESTS:: + + sage: TestSuite(Sets().Topological().Compact()).run() + sage: Sets().Topological().Compact.__module__ + 'sage.categories.topological_spaces' + """ + return self._with_axiom('Compact') + + class Connected(CategoryWithAxiom): + """ + The category of connected topological spaces. + """ + + class Compact(CategoryWithAxiom): + """ + The category of compact topological spaces. + """ + diff --git a/src/sage/coding/all.py b/src/sage/coding/all.py index 67bf3516e87..5912596f906 100644 --- a/src/sage/coding/all.py +++ b/src/sage/coding/all.py @@ -1,77 +1,45 @@ from sage.misc.lazy_import import lazy_import -from code_constructions import (permutation_action, walsh_matrix,cyclotomic_cosets) +lazy_import("sage.coding.code_constructions", ["permutation_action", + "walsh_matrix"]) from sage.misc.superseded import deprecated_callable_import -deprecated_callable_import(15445, - 'sage.coding.code_constructions', - globals(), - locals(), - ["BinaryGolayCode", - "BCHCode", - "CyclicCode", - "CyclicCodeFromGeneratingPolynomial", - "CyclicCodeFromCheckPolynomial", - "DuadicCodeEvenPair", - "DuadicCodeOddPair", - "ExtendedBinaryGolayCode", - "ExtendedQuadraticResidueCode", - "ExtendedTernaryGolayCode", - "HammingCode", - "LinearCodeFromCheckMatrix", - "QuadraticResidueCode", - "QuadraticResidueCodeEvenPair", - "QuadraticResidueCodeOddPair", - "RandomLinearCode", - "TernaryGolayCode", - "ToricCode", - "TrivialCode", - "WalshCode"], - ("This method soon will not be available in that " - "way anymore. To use it, you can now call it by " - "typing codes.%(name)s")) -deprecated_callable_import(15445, - 'sage.coding.guava', - globals(), - locals(), - ["BinaryReedMullerCode", - "QuasiQuadraticResidueCode", - "RandomLinearCodeGuava"], - ("This method soon will not be available in that " - "way anymore. To use it, you can now call it by " - "typing codes.%(name)s")) +deprecated_callable_import(19315, + "sage.coding.code_bounds", + globals(), + locals(), + ["codesize_upper_bound", + "dimension_upper_bound", + "volume_hamming", + "gilbert_lower_bound", + "plotkin_upper_bound", + "griesmer_upper_bound", + "elias_upper_bound", + "hamming_upper_bound", + "singleton_upper_bound", + "gv_info_rate", + "entropy", + "gv_bound_asymp", + "hamming_bound_asymp", + "singleton_bound_asymp", + "plotkin_bound_asymp", + "elias_bound_asymp", + "mrrw1_bound_asymp"], + ("This method soon will not be available in that way." + "Please call codes.bounds.%(name)s instead")) -from code_bounds import (codesize_upper_bound, - dimension_upper_bound, - volume_hamming, - gilbert_lower_bound, - plotkin_upper_bound, - griesmer_upper_bound, - elias_upper_bound, - hamming_upper_bound, - singleton_upper_bound, - gv_info_rate, - entropy, - gv_bound_asymp, - hamming_bound_asymp, - singleton_bound_asymp, - plotkin_bound_asymp, - elias_bound_asymp, - mrrw1_bound_asymp) - -lazy_import("sage.coding.linear_code", ["LinearCode",\ - "LinearCodeFromVectorSpace",\ - "best_known_linear_code",\ - "best_known_linear_code_www",\ +lazy_import("sage.coding.linear_code", ["LinearCode", + "LinearCodeFromVectorSpace", + "best_known_linear_code", + "best_known_linear_code_www", "bounds_minimum_distance", "self_orthogonal_binary_codes"]) +lazy_import("sage.coding.delsarte_bounds", ["Krawtchouk", + "delsarte_bound_hamming_space", + "delsarte_bound_additive_hamming_space"]) + from sd_codes import self_dual_codes_binary -from encoder import Encoder -lazy_import("sage.coding.delsarte_bounds", - ["Krawtchouk", "delsarte_bound_hamming_space", "delsarte_bound_additive_hamming_space"]) lazy_import('sage.coding', 'codes_catalog', 'codes') lazy_import('sage.coding', 'channels_catalog', 'channels') - -import sage.coding.channel_constructions diff --git a/src/sage/coding/bounds_catalog.py b/src/sage/coding/bounds_catalog.py new file mode 100644 index 00000000000..aca2e2448f3 --- /dev/null +++ b/src/sage/coding/bounds_catalog.py @@ -0,0 +1,35 @@ +r""" +Index of bounds + +The ``codes.bounds`` object may be used to access the bounds that Sage can compute. + +{INDEX_OF_FUNCTIONS} + +.. NOTE:: + + To import these names into the global namespace, use: + + sage: from sage.coding.bounds_catalog import * +""" +from sage.misc.lazy_import import lazy_import as _lazy_import +_lazy_import("sage.coding.code_bounds", ["codesize_upper_bound", + "dimension_upper_bound", + "volume_hamming", + "gilbert_lower_bound", + "plotkin_upper_bound", + "griesmer_upper_bound", + "elias_upper_bound", + "hamming_upper_bound", + "singleton_upper_bound", + "gv_info_rate", + "entropy", + "gv_bound_asymp", + "hamming_bound_asymp", + "singleton_bound_asymp", + "plotkin_bound_asymp", + "elias_bound_asymp", + "mrrw1_bound_asymp"]) + +from sage.misc.rest_index_of_methods import gen_rest_table_index as _gen_rest_table_index +import sys as _sys +__doc__ = __doc__.format(INDEX_OF_FUNCTIONS=_gen_rest_table_index(_sys.modules[__name__], only_local_functions=False)) diff --git a/src/sage/coding/channel_constructions.py b/src/sage/coding/channel_constructions.py index 2a6a14f1120..a49147640ef 100644 --- a/src/sage/coding/channel_constructions.py +++ b/src/sage/coding/channel_constructions.py @@ -62,7 +62,8 @@ def random_error_vector(n, F, error_positions): EXAMPLES:: - sage: sage.coding.channel_constructions.random_error_vector(5, GF(2), [1,3]) + sage: from sage.coding.channel_constructions import random_error_vector + sage: random_error_vector(5, GF(2), [1,3]) (0, 1, 0, 1, 0) """ vect = [F.zero()]*n @@ -90,12 +91,13 @@ def format_interval(t): TESTS:: + sage: from sage.coding.channel_constructions import format_interval sage: t = (5, 5) - sage: sage.coding.channel_constructions.format_interval(t) + sage: format_interval(t) '5' sage: t = (2, 10) - sage: sage.coding.channel_constructions.format_interval(t) + sage: format_interval(t) 'between 2 and 10' """ @@ -142,7 +144,8 @@ def __init__(self, input_space, output_space): We first create a new Channel subclass:: - sage: class ChannelExample(sage.coding.channel_constructions.Channel): + sage: from sage.coding.channel_constructions import Channel + sage: class ChannelExample(Channel): ....: def __init__(self, input_space, output_space): ....: super(ChannelExample, self).__init__(input_space, output_space) diff --git a/src/sage/coding/code_bounds.py b/src/sage/coding/code_bounds.py index 3ecc60e23a4..0a9a16e3335 100644 --- a/src/sage/coding/code_bounds.py +++ b/src/sage/coding/code_bounds.py @@ -217,17 +217,17 @@ def codesize_upper_bound(n,d,q,algorithm=None): EXAMPLES:: - sage: codesize_upper_bound(10,3,2) + sage: codes.bounds.codesize_upper_bound(10,3,2) 93 - sage: codesize_upper_bound(24,8,2,algorithm="LP") + sage: codes.bounds.codesize_upper_bound(24,8,2,algorithm="LP") 4096 - sage: codesize_upper_bound(10,3,2,algorithm="gap") # optional - gap_packages (Guava package) + sage: codes.bounds.codesize_upper_bound(10,3,2,algorithm="gap") # optional - gap_packages (Guava package) 85 - sage: codesize_upper_bound(11,3,4,algorithm=None) + sage: codes.bounds.codesize_upper_bound(11,3,4,algorithm=None) 123361 - sage: codesize_upper_bound(11,3,4,algorithm="gap") # optional - gap_packages (Guava package) + sage: codes.bounds.codesize_upper_bound(11,3,4,algorithm="gap") # optional - gap_packages (Guava package) 123361 - sage: codesize_upper_bound(11,3,4,algorithm="LP") + sage: codes.bounds.codesize_upper_bound(11,3,4,algorithm="LP") 109226 """ @@ -254,11 +254,11 @@ def dimension_upper_bound(n,d,q,algorithm=None): EXAMPLES:: - sage: dimension_upper_bound(10,3,2) + sage: codes.bounds.dimension_upper_bound(10,3,2) 6 - sage: dimension_upper_bound(30,15,4) + sage: codes.bounds.dimension_upper_bound(30,15,4) 13 - sage: dimension_upper_bound(30,15,4,algorithm="LP") + sage: codes.bounds.dimension_upper_bound(30,15,4,algorithm="LP") 12 """ @@ -277,7 +277,7 @@ def volume_hamming(n,q,r): EXAMPLES:: - sage: volume_hamming(10,2,3) + sage: codes.bounds.volume_hamming(10,2,3) 176 """ ans=sum([factorial(n)/(factorial(i)*factorial(n-i))*(q-1)**i for i in range(r+1)]) @@ -290,7 +290,7 @@ def gilbert_lower_bound(n,q,d): EXAMPLES:: - sage: gilbert_lower_bound(10,2,3) + sage: codes.bounds.gilbert_lower_bound(10,2,3) 128/7 """ ans=q**n/volume_hamming(n,q,d-1) @@ -306,9 +306,9 @@ def plotkin_upper_bound(n,q,d, algorithm=None): EXAMPLES:: - sage: plotkin_upper_bound(10,2,3) + sage: codes.bounds.plotkin_upper_bound(10,2,3) 192 - sage: plotkin_upper_bound(10,2,3,algorithm="gap") # optional - gap_packages (Guava package) + sage: codes.bounds.plotkin_upper_bound(10,2,3,algorithm="gap") # optional - gap_packages (Guava package) 192 """ if algorithm=="gap": @@ -338,9 +338,9 @@ def griesmer_upper_bound(n,q,d,algorithm=None): EXAMPLES:: - sage: griesmer_upper_bound(10,2,3) + sage: codes.bounds.griesmer_upper_bound(10,2,3) 128 - sage: griesmer_upper_bound(10,2,3,algorithm="gap") # optional - gap_packages (Guava package) + sage: codes.bounds.griesmer_upper_bound(10,2,3,algorithm="gap") # optional - gap_packages (Guava package) 128 """ if algorithm=="gap": @@ -373,9 +373,9 @@ def elias_upper_bound(n,q,d,algorithm=None): EXAMPLES:: - sage: elias_upper_bound(10,2,3) + sage: codes.bounds.elias_upper_bound(10,2,3) 232 - sage: elias_upper_bound(10,2,3,algorithm="gap") # optional - gap_packages (Guava package) + sage: codes.bounds.elias_upper_bound(10,2,3,algorithm="gap") # optional - gap_packages (Guava package) 232 """ @@ -425,7 +425,7 @@ def hamming_upper_bound(n,q,d): EXAMPLES:: - sage: hamming_upper_bound(10,2,3) + sage: codes.bounds.hamming_upper_bound(10,2,3) 93 """ return int((q**n)/(volume_hamming(n, q, int((d-1)/2)))) @@ -452,7 +452,7 @@ def singleton_upper_bound(n,q,d): EXAMPLES:: - sage: singleton_upper_bound(10,2,3) + sage: codes.bounds.singleton_upper_bound(10,2,3) 256 """ return q**(n - d + 1) @@ -464,7 +464,7 @@ def gv_info_rate(n,delta,q): EXAMPLES:: - sage: RDF(gv_info_rate(100,1/4,3)) # abs tol 1e-15 + sage: RDF(codes.bounds.gv_info_rate(100,1/4,3)) # abs tol 1e-15 0.36704992608261894 """ q = ZZ(q) @@ -484,20 +484,20 @@ def entropy(x, q=2): EXAMPLES:: - sage: entropy(0, 2) + sage: codes.bounds.entropy(0, 2) 0 - sage: entropy(1/5,4) + sage: codes.bounds.entropy(1/5,4) 1/5*log(3)/log(4) - 4/5*log(4/5)/log(4) - 1/5*log(1/5)/log(4) - sage: entropy(1, 3) + sage: codes.bounds.entropy(1, 3) log(2)/log(3) Check that values not within the limits are properly handled:: - sage: entropy(1.1, 2) + sage: codes.bounds.entropy(1.1, 2) Traceback (most recent call last): ... ValueError: The entropy function is defined only for x in the interval [0, 1] - sage: entropy(1, 1) + sage: codes.bounds.entropy(1, 1) Traceback (most recent call last): ... ValueError: The value q must be an integer greater than 1 @@ -571,9 +571,9 @@ def gv_bound_asymp(delta,q): EXAMPLES:: - sage: RDF(gv_bound_asymp(1/4,2)) + sage: RDF(codes.bounds.gv_bound_asymp(1/4,2)) 0.18872187554086... - sage: f = lambda x: gv_bound_asymp(x,2) + sage: f = lambda x: codes.bounds.gv_bound_asymp(x,2) sage: plot(f,0,1) Graphics object consisting of 1 graphics primitive """ @@ -586,9 +586,9 @@ def hamming_bound_asymp(delta,q): EXAMPLES:: - sage: RDF(hamming_bound_asymp(1/4,2)) + sage: RDF(codes.bounds.hamming_bound_asymp(1/4,2)) 0.456435556800... - sage: f = lambda x: hamming_bound_asymp(x,2) + sage: f = lambda x: codes.bounds.hamming_bound_asymp(x,2) sage: plot(f,0,1) Graphics object consisting of 1 graphics primitive """ @@ -600,9 +600,9 @@ def singleton_bound_asymp(delta,q): EXAMPLES:: - sage: singleton_bound_asymp(1/4,2) + sage: codes.bounds.singleton_bound_asymp(1/4,2) 3/4 - sage: f = lambda x: singleton_bound_asymp(x,2) + sage: f = lambda x: codes.bounds.singleton_bound_asymp(x,2) sage: plot(f,0,1) Graphics object consisting of 1 graphics primitive """ @@ -615,7 +615,7 @@ def plotkin_bound_asymp(delta,q): EXAMPLES:: - sage: plotkin_bound_asymp(1/4,2) + sage: codes.bounds.plotkin_bound_asymp(1/4,2) 1/2 """ r = 1-1/q @@ -628,7 +628,7 @@ def elias_bound_asymp(delta,q): EXAMPLES:: - sage: elias_bound_asymp(1/4,2) + sage: codes.bounds.elias_bound_asymp(1/4,2) 0.39912396330... """ r = 1-1/q @@ -641,7 +641,7 @@ def mrrw1_bound_asymp(delta,q): EXAMPLES:: - sage: mrrw1_bound_asymp(1/4,2) # abs tol 4e-16 + sage: codes.bounds.mrrw1_bound_asymp(1/4,2) # abs tol 4e-16 0.3545789026652697 """ return RDF(entropy((q-1-delta*(q-2)-2*sqrt((q-1)*delta*(1-delta)))/q,q)) diff --git a/src/sage/coding/code_constructions.py b/src/sage/coding/code_constructions.py index 0a142a0cb8c..46704110993 100644 --- a/src/sage/coding/code_constructions.py +++ b/src/sage/coding/code_constructions.py @@ -164,48 +164,6 @@ ############### utility functions ################ -def cyclotomic_cosets(q, n, t = None): - r""" - This method is deprecated. - - See the documentation in - :func:`~sage.categories.rings.Rings.Finite.ParentMethods.cyclotomic_cosets`. - - INPUT: q,n,t positive integers (or t=None) Some type-checking of - inputs is performed. - - OUTPUT: q-cyclotomic cosets mod n (or, if t is not None, the q-cyclotomic - coset mod n containing t) - - EXAMPLES:: - - sage: cyclotomic_cosets(2,11) - doctest:...: DeprecationWarning: cyclotomic_cosets(q,n,t) is deprecated. - Use Zmod(n).cyclotomic_cosets(q) or Zmod(n).cyclotomic_cosets(q,[t]) - instead. Be careful that this method returns elements of Zmod(n). - See http://trac.sagemath.org/16464 for details. - [[0], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]] - - sage: Zmod(11).cyclotomic_cosets(2) - [[0], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]] - - sage: cyclotomic_cosets(5,11) - [[0], [1, 3, 4, 5, 9], [2, 6, 7, 8, 10]] - sage: cyclotomic_cosets(5,11,3) - [1, 3, 4, 5, 9] - """ - from sage.misc.superseded import deprecation - deprecation(16464, """cyclotomic_cosets(q,n,t) is deprecated. Use - Zmod(n).cyclotomic_cosets(q) or - Zmod(n).cyclotomic_cosets(q,[t]) instead. Be careful - that this method returns elements of Zmod(n).""") - - from sage.rings.finite_rings.integer_mod_ring import Zmod - if t is None: - return [[x.lift() for x in cos] for cos in Zmod(n).cyclotomic_cosets(q)] - else: - return [x.lift() for x in Zmod(n).cyclotomic_cosets(q,[t])[0]] - def is_a_splitting(S1, S2, n, return_automorphism=False): """ Check wether ``(S1,S2)`` is a splitting of `\ZZ/n\ZZ`. @@ -978,15 +936,6 @@ def HammingCode(r,F): 3 sage: C = codes.HammingCode(3,GF(4,'a')); C Linear code of length 21, dimension 18 over Finite Field in a of size 2^2 - - While the ``codes`` object now gathers all code constructors, - ``HammingCode`` is still available in the global namespace:: - - sage: HammingCode(3,GF(2)) - doctest:...: DeprecationWarning: This method soon will not be available in that way anymore. To use it, you can now call it by typing codes.HammingCode - See http://trac.sagemath.org/15445 for details. - Linear code of length 7, dimension 4 over Finite Field of size 2 - """ q = F.order() n = (q**r-1)/(q-1) diff --git a/src/sage/coding/codes_catalog.py b/src/sage/coding/codes_catalog.py index bde49dbd759..ac464e8f95e 100644 --- a/src/sage/coding/codes_catalog.py +++ b/src/sage/coding/codes_catalog.py @@ -33,6 +33,7 @@ from guava import BinaryReedMullerCode, QuasiQuadraticResidueCode, RandomLinearCodeGuava import encoders_catalog as encoders +import bounds_catalog as bounds from sage.misc.rest_index_of_methods import gen_rest_table_index as _gen_rest_table_index import sys as _sys __doc__ = __doc__.format(INDEX_OF_FUNCTIONS=_gen_rest_table_index(_sys.modules[__name__], only_local_functions=False)) diff --git a/src/sage/coding/encoder.py b/src/sage/coding/encoder.py index d5051e7ccfe..d9833623243 100644 --- a/src/sage/coding/encoder.py +++ b/src/sage/coding/encoder.py @@ -70,7 +70,8 @@ def __init__(self, code): We first create a new :class:`Encoder` subclass:: - sage: class EncoderExample(sage.coding.encoder.Encoder): + sage: from sage.coding.encoder import Encoder + sage: class EncoderExample(Encoder): ....: def __init__(self, code): ....: super(EncoderExample, self).__init__(code) diff --git a/src/sage/coding/grs.py b/src/sage/coding/grs.py index a1a17b76ad8..26bc69dd6ad 100644 --- a/src/sage/coding/grs.py +++ b/src/sage/coding/grs.py @@ -51,6 +51,10 @@ class GeneralizedReedSolomonCode(AbstractLinearCode): - ``column_multipliers`` -- (default: ``None``) List of column multipliers in F for this code. All column multipliers are set to 1 if default value is kept. + REFERENCES: + + .. [Nielsen] Johan S. R. Nielsen, (https://bitbucket.org/jsrn/codinglib/) + EXAMPLES: We construct a GRS code with a manually built support, without specifying column multipliers:: @@ -280,8 +284,7 @@ def multipliers_product(self): AUTHORS: - This function is taken from codinglib (https://bitbucket.org/jsrn/codinglib/) - and was written by Johan Nielsen. + This function is taken from codinglib [Nielsen]_ EXAMPLES:: @@ -302,8 +305,7 @@ def parity_column_multipliers(self): AUTHORS: - This function is taken from codinglib (https://bitbucket.org/jsrn/codinglib/) - and was written by Johan Nielsen. + This function is taken from codinglib [Nielsen]_ EXAMPLES:: @@ -316,7 +318,7 @@ def parity_column_multipliers(self): n = self.length() col_mults = self.column_multipliers() etas = self.multipliers_product() - return [ etas[i]/col_mults[i] for i in range(0,n) ] + return [ etas[i]/col_mults[i] for i in range(n) ] @cached_method def parity_check_matrix(self): @@ -363,11 +365,15 @@ def covering_radius(self): r""" Returns the covering radius of ``self``. - The covering radius of a linear code `C` is the smallest number `r` s.t. any element of + The covering radius of a linear code `C` is the smallest + number `r` s.t. any element of the ambient space of `C` is at most at distance `r` to `C`. - As Reed-Solomon codes are MDS codes, their covering radius is always `d-1`, where `d` is - the minimum distance. + As Reed-Solomon codes are MDS codes, their covering radius + is always `d-1`, where `d` is the minimum distance. + + This is a custom method for GRS codes which is faster than + generic implementation. EXAMPLES:: @@ -384,8 +390,11 @@ def weight_distribution(self): r""" Returns the weight distribution of ``self``. - The weight distribution is returned as a list, where the `i-th` entry corresponds to - the number of words of weight `i` in the code. + The weight distribution is returned as a list, where the + `i-th` entry corresponds to the number of words of weight `i` in the code. + + This is a custom method for GRS codes which is faster than + generic implementation. EXAMPLES:: @@ -409,6 +418,9 @@ def weight_enumerator(self): r""" Returns the weight enumerator of ``self``. + This is a custom method for GRS codes which is faster than + generic implementation. + EXAMPLES:: sage: F = GF(11) diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index edf2499c475..69c1db51820 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -216,6 +216,7 @@ import sage.modules.free_module as fm import sage.modules.module as module from sage.categories.modules import Modules +from 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 @@ -833,9 +834,18 @@ def __init__(self, base_field, length, default_encoder_name): Traceback (most recent call last): ... ValueError: You must set a valid encoder as default encoder for this code, by completing __init__.py + + A ring instead of a field:: + + sage: codes.LinearCode(IntegerModRing(4),matrix.ones(4)) + Traceback (most recent call last): + ... + ValueError: 'generator_matrix' must be defined on a field (not a ring) """ if not isinstance(length, (int, Integer)): raise ValueError("length must be a Python int or a Sage Integer") + if not base_field.is_field(): + raise ValueError("'base_field' must be a field (and {} is not one)".format(base_field)) self._length = Integer(length) if not default_encoder_name in self._registered_encoders: raise ValueError("You must set a valid encoder as default encoder for this code, by completing __init__.py") @@ -845,7 +855,6 @@ def __init__(self, base_field, length, default_encoder_name): self.Element = type(facade_for.an_element()) #for when we made this a non-facade parent Parent.__init__(self, base=base_field, facade=facade_for, category=cat) - def _latex_(self): """ Return a latex representation of ``self``. @@ -1608,6 +1617,57 @@ def divisor(self): if len(S)>1: return GCD(S0) return 1 + def is_projective(self): + r""" + Test whether the code is projective. + + A linear code `C` over a field is called *projective* when its dual `Cd` + has minimum weight `\geq 3`, i.e. when no two coordinate positions of + `C` are linearly independent (cf. definition 3 from [BS11] or 9.8.1 from + [BH12]). + + EXAMPLE:: + + sage: C = codes.BinaryGolayCode() + sage: C.is_projective() + True + sage: C.dual_code().minimum_distance() + 8 + + A non-projective code:: + + sage: C = codes.LinearCode(matrix(GF(2),[[1,0,1],[1,1,1]])) + sage: C.is_projective() + False + + REFERENCE: + + .. [BS11] E. Byrne and A. Sneyd, + On the Parameters of Codes with Two Homogeneous Weights. + WCC 2011-Workshop on coding and cryptography, pp. 81-90. 2011. + https://hal.inria.fr/inria-00607341/document + """ + M = self.generator_matrix().transpose() + R = self.base_field() + + def projectivize(row): + if not row.is_zero(): + for i in range(len(row)): + if row[i]: + break + row = ~(row[i]) * row + row.set_immutable() + return row + + rows = set() + for row in M.rows(): + row = projectivize(row) + if row in rows: + return False + rows.add(row) + + return True + def dual_code(self): r""" This computes the dual code `Cd` of the code `C`, @@ -3617,6 +3677,9 @@ def __init__(self, generator_matrix, d=None): ValueError: this linear code contains no non-zero vector """ base_ring = generator_matrix.base_ring() + if not base_ring.is_field(): + raise ValueError("'generator_matrix' must be defined on a field (not a ring)") + # if the matrix does not have full rank we replace it if generator_matrix.rank() != generator_matrix.nrows(): from sage.matrix.constructor import matrix diff --git a/src/sage/combinat/affine_permutation.py b/src/sage/combinat/affine_permutation.py index 42910e4469d..b9ad2739432 100644 --- a/src/sage/combinat/affine_permutation.py +++ b/src/sage/combinat/affine_permutation.py @@ -778,16 +778,18 @@ def to_lehmer_code(self, typ='decreasing', side='right'): EXAMPLES:: + sage: import itertools sage: A=AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) - sage: CP=CartesianProduct( ('increasing','decreasing'),('left','right') ) - sage: for a in CP: - ....: p.to_lehmer_code(a[0],a[1]) + sage: orders = ('increasing','decreasing') + sage: sides = ('left','right') + sage: for o,s in itertools.product(orders, sides): + ....: p.to_lehmer_code(o,s) [2, 3, 2, 0, 1, 2, 0, 0] [2, 2, 0, 0, 2, 1, 0, 3] [3, 1, 0, 0, 2, 1, 0, 3] [0, 3, 3, 0, 1, 2, 0, 1] - sage: for a in CP: + sage: for a in itertools.product(orders, sides): ....: A.from_lehmer_code(p.to_lehmer_code(a[0],a[1]), a[0],a[1])==p True True @@ -2198,15 +2200,17 @@ def from_lehmer_code(self, C, typ='decreasing', side='right'): EXAMPLES:: + sage: import itertools sage: A=AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.to_lehmer_code() [0, 3, 3, 0, 1, 2, 0, 1] sage: A.from_lehmer_code(p.to_lehmer_code())==p True - sage: CP=CartesianProduct( ('increasing','decreasing'),('left','right') ) - sage: for a in CP: - ....: A.from_lehmer_code(p.to_lehmer_code(a[0],a[1]),a[0],a[1])==p + sage: orders = ('increasing','decreasing') + sage: sides = ('left','right') + sage: for o,s in itertools.product(orders,sides): + ....: A.from_lehmer_code(p.to_lehmer_code(o,s),o,s)==p True True True diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index 7a0c223e36f..bbfce97acdf 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -48,9 +48,8 @@ #PerfectMatchings from perfect_matching import PerfectMatching, PerfectMatchings -# Integer lists lex - -from integer_list import IntegerListsLex as IntegerListsLex +# Integer lists +from integer_lists import IntegerListsLex #Compositions from composition import Composition, Compositions diff --git a/src/sage/combinat/alternating_sign_matrix.py b/src/sage/combinat/alternating_sign_matrix.py index 8558936ef7a..b199b91f966 100644 --- a/src/sage/combinat/alternating_sign_matrix.py +++ b/src/sage/combinat/alternating_sign_matrix.py @@ -532,11 +532,30 @@ def to_fully_packed_loop(self): from sage.combinat.fully_packed_loop import FullyPackedLoop return FullyPackedLoop(self) + def link_pattern(self): + """ + Return the link pattern corresponding to the fully packed loop + corresponding to self. + + EXAMPLES: + + We can extract the underlying link pattern (a non-crossing + partition) from a fully packed loop:: + + sage: A = AlternatingSignMatrix([[0, 1, 0], [1, -1, 1], [0, 1, 0]]) + sage: A.link_pattern() + [(1, 2), (3, 6), (4, 5)] + + sage: B = AlternatingSignMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + sage: B.link_pattern() + [(1, 6), (2, 5), (3, 4)] + """ + return self.to_fully_packed_loop().link_pattern() @combinatorial_map(name='gyration') def gyration(self): r""" - Return the alternating sign matrix obtained by applying the gyration + Return the alternating sign matrix obtained by applying gyration to the height function in bijection with ``self``. Gyration acts on height functions as follows. Go through the entries of @@ -546,11 +565,6 @@ def gyration(self): still a height function. Gyration was first defined in [Wieland00]_ as an action on fully-packed loops. - REFERENCES: - - .. [Wieland00] B. Wieland. *A large dihedral symmetry of the set of - alternating sign matrices*. Electron. J. Combin. 7 (2000). - EXAMPLES:: sage: A = AlternatingSignMatrices(3) @@ -568,7 +582,6 @@ def gyration(self): [0 1 0] [0 0 1] [1 0 0] - sage: A = AlternatingSignMatrices(3) sage: A([[1, 0, 0],[0, 1, 0],[0, 0, 1]]).gyration().gyration() [ 0 1 0] @@ -800,25 +813,70 @@ def ASM_compatible_smaller(self): return(output) @combinatorial_map(name='to Dyck word') - def to_dyck_word(self): + def to_dyck_word(self, method): r""" - Return the Dyck word determined by the last diagonal of - the monotone triangle corresponding to ``self``. + Return a Dyck word determined by the specified method. + + The method 'last_diagonal' uses the last diagonal of the monotone + triangle corresponding to ``self``. The method 'link_pattern' returns + the Dyck word in bijection with the link pattern of the fully packed + loop. + + Note that these two methods in general yield different Dyck words for a + given alternating sign matrix. + + INPUT: + + - ``method`` - + + - ``'last_diagonal'`` + - ``'link_pattern'`` EXAMPLES:: sage: A = AlternatingSignMatrices(3) - sage: A([[0,1,0],[1,0,0],[0,0,1]]).to_dyck_word() + sage: A([[0,1,0],[1,0,0],[0,0,1]]).to_dyck_word(method = 'last_diagonal') [1, 1, 0, 0, 1, 0] - sage: d = A([[0,1,0],[1,-1,1],[0,1,0]]).to_dyck_word(); d + sage: d = A([[0,1,0],[1,-1,1],[0,1,0]]).to_dyck_word(method = 'last_diagonal'); d [1, 1, 0, 1, 0, 0] sage: parent(d) Complete Dyck words - """ - MT = self.to_monotone_triangle() - nplus = self._matrix.nrows() + 1 - parkfn = [nplus - row[0] for row in list(MT) if len(row) > 0] - return NonDecreasingParkingFunction(parkfn).to_dyck_word().reverse() + sage: A = AlternatingSignMatrices(3) + sage: asm = A([[0,1,0],[1,0,0],[0,0,1]]) + sage: asm.to_dyck_word(method = 'link_pattern') + [1, 0, 1, 0, 1, 0] + sage: asm = A([[0,1,0],[1,-1,1],[0,1,0]]) + sage: asm.to_dyck_word(method = 'link_pattern') + [1, 0, 1, 1, 0, 0] + sage: A = AlternatingSignMatrices(4) + sage: asm = A([[0,0,1,0],[1,0,0,0],[0,1,-1,1],[0,0,1,0]]) + sage: asm.to_dyck_word(method = 'link_pattern') + [1, 1, 1, 0, 1, 0, 0, 0] + sage: asm.to_dyck_word() + Traceback (most recent call last): + ... + TypeError: to_dyck_word() takes exactly 2 arguments (1 given) + sage: asm.to_dyck_word(method = 'notamethod') + Traceback (most recent call last): + ... + ValueError: unknown method 'notamethod' + """ + if method == 'last_diagonal': + MT = self.to_monotone_triangle() + nplus = self._matrix.nrows() + 1 + parkfn = [nplus - row[0] for row in list(MT) if len(row) > 0] + return NonDecreasingParkingFunction(parkfn).to_dyck_word().reverse() + + elif method == 'link_pattern': + from sage.combinat.perfect_matching import PerfectMatching + from sage.combinat.dyck_word import DyckWords + p = PerfectMatching(self.link_pattern()).to_non_crossing_set_partition() + asm = self.to_matrix() + n = asm.nrows() + d = DyckWords(n) + return d.from_noncrossing_partition(p) + + raise ValueError("unknown method '%s'" % method) def number_negative_ones(self): """ @@ -902,6 +960,8 @@ def to_semistandard_tableau(self): ssyt[i][j] = mt[j][-(i+1)] return SemistandardTableau(ssyt) + + def left_key(self): r""" Return the left key of the alternating sign matrix ``self``. diff --git a/src/sage/combinat/cartesian_product.py b/src/sage/combinat/cartesian_product.py index 125eabd2928..e0f292fc1a4 100644 --- a/src/sage/combinat/cartesian_product.py +++ b/src/sage/combinat/cartesian_product.py @@ -25,15 +25,17 @@ def CartesianProduct(*iters): """ - Returns the combinatorial class of the Cartesian product of - \*iters. + This is deprecated. Use :obj:`cartesian_product` instead. EXAMPLES:: sage: cp = CartesianProduct([1,2], [3,4]); cp - Cartesian product of [1, 2], [3, 4] + doctest:...: DeprecationWarning: CartesianProduct is deprecated. Use + cartesian_product instead + See http://trac.sagemath.org/18411 for details. + The cartesian product of ({1, 2}, {3, 4}) sage: cp.list() - [[1, 3], [1, 4], [2, 3], [2, 4]] + [(1, 3), (1, 4), (2, 3), (2, 4)] Note that you must not use a generator-type object that is returned by a function (using "yield"). They cannot be copied or @@ -48,24 +50,106 @@ def CartesianProduct(*iters): ValueError: generators are not allowed, see the documentation (type "CartesianProduct?") for a workaround - You either create a list of all values or you use - :class:`sage.combinat.misc.IterableFunctionCall` to make a - (copy-able) iterator:: + The usage of iterable is also deprecated, so the following will no longer be + supported:: sage: from sage.combinat.misc import IterableFunctionCall - sage: CartesianProduct(IterableFunctionCall(a, 3), IterableFunctionCall(b)).list() - [[3, 'a'], [3, 'b'], [6, 'a'], [6, 'b']] - - See the documentation for - :class:`~sage.combinat.misc.IterableFunctionCall` for more - information. + sage: C = CartesianProduct(IterableFunctionCall(a, 3), IterableFunctionCall(b)) + doctest:...: DeprecationWarning: Usage of IterableFunctionCall in + CartesianProduct is deprecated. You can use EnumeratedSetFromIterator + (in sage.sets.set_from_iterator) instead. + See http://trac.sagemath.org/18411 for details. + sage: list(C) + doctest:...: UserWarning: Sage is not able to determine whether the + factors of this cartesian product are finite. The lexicographic ordering + might not go through all elements. + [(3, 'a'), (3, 'b'), (6, 'a'), (6, 'b')] + + You might use + :class:`~sage.sets.set_from_iterator.EnumeratedSetFromIterator` for that + purpose.:: + + sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator + sage: A = EnumeratedSetFromIterator(a, (3,), category=FiniteEnumeratedSets()) + sage: B = EnumeratedSetFromIterator(b, category=FiniteEnumeratedSets()) + sage: C = cartesian_product([A, B]) + sage: C.list() + [(3, 'a'), (3, 'b'), (6, 'a'), (6, 'b')] """ if any(isgenerator(i) for i in iters): raise ValueError('generators are not allowed, see the documentation '+ '(type "CartesianProduct?") for a workaround') - return CartesianProduct_iters(*iters) + + from sage.misc.superseded import deprecation + deprecation(18411, "CartesianProduct is deprecated. Use cartesian_product instead") + + from sage.combinat.misc import IterableFunctionCall + from sage.sets.set_from_iterator import EnumeratedSetFromIterator + deprecate_ifc = False + iiters = [] + for a in iters: + if isinstance(a, IterableFunctionCall): + deprecate_ifc = True + iiters.append(EnumeratedSetFromIterator(a.f, a.args, a.kwargs)) + else: + iiters.append(a) + iters = tuple(iiters) + + if deprecate_ifc: + deprecation(18411, """Usage of IterableFunctionCall in CartesianProduct is deprecated. You can use EnumeratedSetFromIterator (in sage.sets.set_from_iterator) instead.""") + + from sage.categories.cartesian_product import cartesian_product + return cartesian_product(iters) class CartesianProduct_iters(CombinatorialClass): + r""" + Cartesian product of finite sets. + + This class will soon be deprecated (see :trac:`18411` and :trac:`19195`). + One should instead use the functorial construction + :class:`cartesian_product `. + The main differences in behavior are: + + - construction: ``CartesianProduct`` takes as many argument as + there are factors whereas ``cartesian_product`` takes a single + list (or iterable) of factors; + + - representation of elements: elements are represented by plain + Python list for ``CartesianProduct`` versus a custom element + class for ``cartesian_product``; + + - membership testing: because of the above, plain Python lists are + not considered as elements of a ``cartesian_product``. + + All of these is illustrated in the examples below. + + EXAMPLES:: + + sage: F1 = ['a', 'b'] + sage: F2 = [1, 2, 3, 4] + sage: F3 = Permutations(3) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: C = CartesianProduct_iters(F1, F2, F3) + sage: c = cartesian_product([F1, F2, F3]) + + sage: type(C.an_element()) + + sage: type(c.an_element()) + + + sage: l = ['a', 1, Permutation([3,2,1])] + sage: l in C + True + sage: l in c + False + sage: elt = c(l) + sage: elt + ('a', 1, [3, 2, 1]) + sage: elt in c + True + sage: elt.parent() is c + True + """ def __init__(self, *iters): """ TESTS:: @@ -84,13 +168,20 @@ def __contains__(self, x): """ EXAMPLES:: - sage: cp = CartesianProduct([1,2],[3,4]) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: cp = CartesianProduct_iters([1,2],[3,4]) sage: [1,3] in cp True sage: [1,2] in cp False sage: [1, 3, 1] in cp False + + Note that it differs with the behavior of cartesian products:: + + sage: cp = cartesian_product([[1,2], [3,4]]) + sage: [1,3] in cp + False """ try: return len(x) == len(self.iters) and all(x[i] in self.iters[i] for i in range(len(self.iters))) @@ -101,7 +192,8 @@ def __repr__(self): """ EXAMPLES:: - sage: CartesianProduct(range(2), range(3)) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters(range(2), range(3)) Cartesian product of [0, 1], [0, 1, 2] """ return "Cartesian product of " + ", ".join(map(str, self.iters)) @@ -113,18 +205,19 @@ def cardinality(self): EXAMPLES:: - sage: CartesianProduct(range(2), range(3)).cardinality() + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters(range(2), range(3)).cardinality() 6 - sage: CartesianProduct(range(2), xrange(3)).cardinality() + sage: CartesianProduct_iters(range(2), xrange(3)).cardinality() 6 - sage: CartesianProduct(range(2), xrange(3), xrange(4)).cardinality() + sage: CartesianProduct_iters(range(2), xrange(3), xrange(4)).cardinality() 24 This works correctly for infinite objects:: - sage: CartesianProduct(ZZ, QQ).cardinality() + sage: CartesianProduct_iters(ZZ, QQ).cardinality() +Infinity - sage: CartesianProduct(ZZ, []).cardinality() + sage: CartesianProduct_iters(ZZ, []).cardinality() 0 """ return self._mrange.cardinality() @@ -145,15 +238,16 @@ def __len__(self): EXAMPLES:: - sage: C = CartesianProduct(xrange(3), xrange(4)) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: C = CartesianProduct_iters(xrange(3), xrange(4)) sage: len(C) 12 - sage: C = CartesianProduct(ZZ, QQ) + sage: C = CartesianProduct_iters(ZZ, QQ) sage: len(C) Traceback (most recent call last): ... TypeError: cardinality does not fit into a Python int. - sage: C = CartesianProduct(ZZ, []) + sage: C = CartesianProduct_iters(ZZ, []) sage: len(C) 0 """ @@ -165,9 +259,10 @@ def list(self): EXAMPLES:: - sage: CartesianProduct(range(3), range(3)).list() + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters(range(3), range(3)).list() [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]] - sage: CartesianProduct('dog', 'cat').list() + sage: CartesianProduct_iters('dog', 'cat').list() [['d', 'c'], ['d', 'a'], ['d', 't'], @@ -191,9 +286,10 @@ def __iter__(self): EXAMPLES:: - sage: [e for e in CartesianProduct(range(3), range(3))] + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: [e for e in CartesianProduct_iters(range(3), range(3))] [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]] - sage: [e for e in CartesianProduct('dog', 'cat')] + sage: [e for e in CartesianProduct_iters('dog', 'cat')] [['d', 'c'], ['d', 'a'], ['d', 't'], @@ -213,9 +309,10 @@ def is_finite(self): EXAMPLES:: - sage: CartesianProduct(ZZ, []).is_finite() + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters(ZZ, []).is_finite() True - sage: CartesianProduct(4,4).is_finite() + sage: CartesianProduct_iters(4,4).is_finite() Traceback (most recent call last): ... ValueError: Unable to determine whether this product is finite @@ -237,14 +334,15 @@ def unrank(self, x): EXAMPLES:: - sage: C = CartesianProduct(xrange(1000), xrange(1000), xrange(1000)) + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: C = CartesianProduct_iters(xrange(1000), xrange(1000), xrange(1000)) sage: C[238792368] [238, 792, 368] Check for :trac:`15919`:: sage: FF = IntegerModRing(29) - sage: C = CartesianProduct(FF, FF, FF) + sage: C = CartesianProduct_iters(FF, FF, FF) sage: C.unrank(0) [0, 0, 0] """ @@ -271,7 +369,8 @@ def random_element(self): EXAMPLES:: - sage: CartesianProduct('dog', 'cat').random_element() + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: CartesianProduct_iters('dog', 'cat').random_element() ['d', 'a'] """ return [rnd.choice(_) for _ in self.iters] diff --git a/src/sage/combinat/choose_nk.py b/src/sage/combinat/choose_nk.py index faa48df8beb..285c954b4f9 100644 --- a/src/sage/combinat/choose_nk.py +++ b/src/sage/combinat/choose_nk.py @@ -1,57 +1,6 @@ -""" -Deprecated combinations +# the following functions have been moved to sage.combinat.combination +import sage.combinat.combination as combination -AUTHORS: - -- Mike Hansen (2007): initial implementation - -- Vincent Delecroix (2014): deprecation -""" -#***************************************************************************** -# Copyright (C) 2007 Mike Hansen , -# -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# The full text of the GPL is available at: -# -# http://www.gnu.org/licenses/ -#***************************************************************************** -from sage.rings.arith import binomial - -def ChooseNK(n, k): - """ - All possible choices of k elements out of range(n) without repetitions. - - The elements of the output are tuples of Python int (and not Sage Integer). - - This was deprecated in :trac:`10534` for :func:`Combinations` - (or ``itertools.combinations`` for doing iteration). - - EXAMPLES:: - - sage: from sage.combinat.choose_nk import ChooseNK - sage: c = ChooseNK(4,2) - doctest:...: DeprecationWarning: ChooseNk is deprecated and will be - removed. Use Combinations instead (or combinations from the itertools - module for iteration) - See http://trac.sagemath.org/10534 for details. - sage: c.first() - [0, 1] - sage: c.list() - [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]] - """ - from sage.misc.superseded import deprecation - deprecation(10534, "ChooseNk is deprecated and will be removed. Use Combinations instead (or combinations from the itertools module for iteration)") - from sage.combinat.combination import Combinations - return Combinations(n,k) - -#TODO: the following functions are used sage.combinat.combination and -# sage.combinat.subset. It might be good to move them somewhere else. def rank(comb, n, check=True): """ Return the rank of ``comb`` in the subsets of ``range(n)`` of size ``k`` @@ -64,6 +13,8 @@ def rank(comb, n, check=True): sage: import sage.combinat.choose_nk as choose_nk sage: choose_nk.rank((), 3) + doctest:...: DeprecationWarning: choose_nk.rank is deprecated and will be removed. Use combination.rank instead + See http://trac.sagemath.org/18674 for details. 0 sage: choose_nk.rank((0,), 3) 0 @@ -94,55 +45,14 @@ def rank(comb, n, check=True): sage: choose_nk.rank([0,1,2], 3) 0 """ - k = len(comb) - if check: - if k > n: - raise ValueError("len(comb) must be <= n") - comb = [int(_) for _ in comb] - for i in xrange(k - 1): - if comb[i + 1] <= comb[i]: - raise ValueError("comb must be a subword of (0,1,...,n)") - - #Generate the combinadic from the - #combination - - #w = [n-1-comb[i] for i in xrange(k)] - - #Calculate the integer that is the dual of - #the lexicographic index of the combination - r = k - t = 0 - for i in range(k): - t += binomial(n - 1 - comb[i], r) - r -= 1 - - return binomial(n,k)-t-1 - - - -def _comb_largest(a,b,x): - """ - Returns the largest w < a such that binomial(w,b) <= x. - - EXAMPLES:: - - sage: from sage.combinat.choose_nk import _comb_largest - sage: _comb_largest(6,3,10) - 5 - sage: _comb_largest(6,3,5) - 4 - """ - w = a - 1 - - while binomial(w,b) > x: - w -= 1 - - return w + from sage.misc.superseded import deprecation + deprecation(18674, "choose_nk.rank is deprecated and will be removed. Use combination.rank instead") + return combination.rank(comb, n, check) def from_rank(r, n, k): - """ - Returns the combination of rank r in the subsets of range(n) of - size k when listed in lexicographic order. + r""" + Returns the combination of rank ``r`` in the subsets of ``range(n)`` of + size ``k`` when listed in lexicographic order. The algorithm used is based on combinadics and James McCaffrey's MSDN article. See: http://en.wikipedia.org/wiki/Combinadic @@ -151,6 +61,8 @@ def from_rank(r, n, k): sage: import sage.combinat.choose_nk as choose_nk sage: choose_nk.from_rank(0,3,0) + doctest:...: DeprecationWarning: choose_nk.from_rank is deprecated and will be removed. Use combination.from_rank instead + See http://trac.sagemath.org/18674 for details. () sage: choose_nk.from_rank(0,3,1) (0,) @@ -167,23 +79,7 @@ def from_rank(r, n, k): sage: choose_nk.from_rank(0,3,3) (0, 1, 2) """ - if k < 0: - raise ValueError("k must be > 0") - if k > n: - raise ValueError("k must be <= n") - - a = n - b = k - x = binomial(n, k) - 1 - r # x is the 'dual' of m - comb = [None] * k - - for i in xrange(k): - comb[i] = _comb_largest(a, b, x) - x = x - binomial(comb[i], b) - a = comb[i] - b = b - 1 - - for i in xrange(k): - comb[i] = (n - 1) - comb[i] + from sage.misc.superseded import deprecation + deprecation(18674, "choose_nk.from_rank is deprecated and will be removed. Use combination.from_rank instead") + return combination.from_rank(r, n, k) - return tuple(comb) diff --git a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py index ad41db77a43..fff2fcb9fcf 100644 --- a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py +++ b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py @@ -213,6 +213,7 @@ def __init__(self, data, frozen=None, is_principal=False, user_labels=None, user # Copy the following attributes from data self._M = copy( data._M ) + self._M.set_immutable() self._B = copy( data._B ) self._n = data._n self._m = data._m @@ -269,6 +270,7 @@ def __init__(self, data, frozen=None, is_principal=False, user_labels=None, user quiver = ClusterQuiver( data ) self._M = copy(quiver._M) # B-tilde exchange matrix + self._M.set_immutable() self._n = quiver._n self._m = quiver._m self._B = copy(self._M[:self._n,:self._n]) # Square Part of the B_matrix @@ -397,6 +399,7 @@ def use_c_vectors(self, use=True, bot_is_c=False, force=False): self._C = copy(self._M[self._m:(self._n+self._m),:self._n]) self._BC = copy(self._M) self._M = self._M[:self._m:self._n] + self._M.set_immutable() self._bot_is_c = False def use_g_vectors(self, use=True, force=False): @@ -858,6 +861,27 @@ def __eq__(self, other): d_vec = self.d_matrix() == other.d_matrix() return g_vec and c_vec and d_vec and clusters and ExMat + def __hash__(self): + """ + Return a hash of ``self``. + + EXAMPLES:: + + sage: Q = ClusterSeed(['A',5]) + sage: hash(Q) # indirect doctest + -5649412990944896369 # 64-bit + 222337679 # 32-bit + """ + # mat_hash = self._M.__hash__() + if self._use_fpolys: + return tuple(self.cluster()).__hash__() + elif self._use_g_vec: + return self.g_matrix().__hash__() + elif self._use_c_vec: + return self.c_matrix().__hash__() + elif self._use_d_vec: + return self.d_matrix().__hash__() + def _repr_(self): r""" Returns the description of ``self``. @@ -1077,7 +1101,7 @@ def b_matrix(self): [ 0 0 0 1] [ 0 0 -2 0] """ - return copy( self._M ) + return copy(self._M) def ground_field(self): r""" @@ -2359,6 +2383,7 @@ def mutate(self, sequence, inplace=True): seed._BC.mutate(k) seed._M = copy(seed._BC[:n+m,:n]) + self._M.set_immutable() if seed._use_c_vec: seed._C = seed._BC[n+m:2*n+m,:n+m] @@ -2804,7 +2829,8 @@ def universal_extension(self): A = 2 - self.b_matrix().apply_map(abs).transpose() - rs = CartanMatrix(A).root_space() + # We give the indexing set of the Cartan matrix to be [1, 2, ..., n] + rs = CartanMatrix(A, index_set=range(1,A.ncols()+1)).root_space() almost_positive_coroots = rs.almost_positive_roots() sign = [-1 if all(x <= 0 for x in self.b_matrix()[i]) else 1 @@ -3050,15 +3076,19 @@ def reset_coefficients( self ): [ 0 1 0] [ 0 0 1] """ - n,m = self._n, self._m + n, m = self._n, self._m if not n == m: - raise ValueError("The numbers of cluster variables and of frozen variables do not coincide.") + raise ValueError("The numbers of cluster variables " + "and of frozen variables do not coincide.") + newM = copy(self._M) for i in xrange(m): for j in xrange(n): if i == j: - self._M[i+n,j] = 1 + newM[i + n, j] = 1 else: - self._M[i+n,j] = 0 + newM[i + n, j] = 0 + self._M = newM + self._M.set_immutable() self._quiver = None self._is_principal = None diff --git a/src/sage/combinat/cluster_algebra_quiver/quiver.py b/src/sage/combinat/cluster_algebra_quiver/quiver.py index be2cbbba0d6..3df226d2e61 100644 --- a/src/sage/combinat/cluster_algebra_quiver/quiver.py +++ b/src/sage/combinat/cluster_algebra_quiver/quiver.py @@ -1,12 +1,27 @@ r""" Quiver -A *quiver* is an oriented graphs without loops, two-cycles, or multiple edges. The edges are labelled by pairs `(i,-j)` -such that the matrix `M = (m_{ab})` with `m_{ab} = i, m_{ba} = -j` for an edge `(i,-j)` between vertices `a` and `b` is skew-symmetrizable. +A *quiver* is an oriented graph without loops, two-cycles, or multiple +edges. The edges are labelled by pairs `(i,-j)` (with `i` and `j` being +positive integers) such that the matrix `M = (m_{ab})` with +`m_{ab} = i, m_{ba} = -j` for an edge `(i,-j)` between vertices +`a` and `b` is skew-symmetrizable. -For the compendium on the cluster algebra and quiver package see +.. WARNING: - http://arxiv.org/abs/1102.4844. + This is not the standard definition of a quiver. Normally, in + cluster algebra theory, a quiver is defined as an oriented graph + without loops and two-cycles but with multiple edges allowed; the + edges are unlabelled. This notion of quivers, however, can be seen + as a particular case of our notion of quivers. Namely, if we have + a quiver (in the regular sense of this word) with (precisely) + `i` edges from `a` to `b`, then we represent it by a quiver + (in our sense of this word) with an edge from `a` to `b` labelled + by the pair `(i,-i)`. + +For the compendium on the cluster algebra and quiver package see :: + + http://arxiv.org/abs/1102.4844. AUTHORS: @@ -31,7 +46,6 @@ from sage.combinat.cluster_algebra_quiver.mutation_class import _principal_part, _digraph_mutate, _matrix_to_digraph, _dg_canonical_form, _mutation_class_iter, _digraph_to_dig6, _dig6_to_matrix from sage.combinat.cluster_algebra_quiver.mutation_type import _connected_mutation_type, _mutation_type_from_data, is_mutation_finite -from sage.groups.perm_gps.permgroup import PermutationGroup class ClusterQuiver(SageObject): """ @@ -251,6 +265,7 @@ def __init__( self, data, frozen=None ): print 'The input data is a quiver, therefore the additional parameter frozen is ignored.' self._M = copy(data._M) + self._M.set_immutable() self._n = data._n self._m = data._m self._digraph = copy( data._digraph ) @@ -266,6 +281,7 @@ def __init__( self, data, frozen=None ): print 'The input data is a matrix, therefore the additional parameter frozen is ignored.' self._M = copy(data).sparse_matrix() + self._M.set_immutable() self._n = n = self._M.ncols() self._m = m = self._M.nrows() - self._n self._digraph = _matrix_to_digraph( self._M ) @@ -330,6 +346,7 @@ def __init__( self, data, frozen=None ): self._digraph = dg self._vertex_dictionary = {} self._M = M + self._M.set_immutable() if n+m == 0: self._description = 'Quiver without vertices' elif n+m == 1: @@ -364,6 +381,18 @@ def __eq__(self, other): """ return isinstance(other, ClusterQuiver) and self._M == other._M + def __hash__(self): + """ + Return a hash of ``self``. + + EXAMPLES:: + + sage: Q = ClusterQuiver(['A',5]) + sage: hash(Q) # indirect doctest + 16 + """ + return self._M.__hash__() + def _repr_(self): """ Returns the description of ``self``. @@ -701,7 +730,7 @@ def b_matrix(self): [ 0 0 0 1] [ 0 0 -2 0] """ - return copy( self._M ) + return copy(self._M) def digraph(self): """ @@ -942,7 +971,8 @@ def m(self): def canonical_label( self, certify=False ): """ - Returns the canonical labelling of ``self``, see sage.graphs.graph.GenericGraph.canonical_label. + Returns the canonical labelling of ``self``, see + :meth:`sage.graphs.graph.GenericGraph.canonical_label`. INPUT: @@ -1277,6 +1307,7 @@ def mutate(self, data, inplace=True): M = _edge_list_to_matrix( dg.edge_iterator(), n, m ) if inplace: self._M = M + self._M.set_immutable() self._digraph = dg else: Q = ClusterQuiver( M ) @@ -1387,6 +1418,7 @@ def reorient( self, data ): dg_new.add_edge( edge[1],edge[0],edge[2] ) self._digraph = dg_new self._M = _edge_list_to_matrix( dg_new.edges(), self._n, self._m ) + self._M.set_immutable() self._mutation_type = None elif all( type(edge) in [list,tuple] and len(edge)==2 for edge in data ): edges = self._digraph.edges(labels=False) @@ -1396,6 +1428,7 @@ def reorient( self, data ): self._digraph.delete_edge(edge[1],edge[0]) self._digraph.add_edge(edge[0],edge[1],label) self._M = _edge_list_to_matrix( self._digraph.edges(), self._n, self._m ) + self._M.set_immutable() self._mutation_type = None else: raise ValueError('The order is no total order on the vertices of the quiver or a list of edges to be oriented.') @@ -1777,3 +1810,100 @@ def relabel(self, relabelling, inplace=True): quiver._vertex_dictionary = relabelling return quiver + def d_vector_fan(self): + r""" + Return the d-vector fan associated with the quiver. + + It is the fan whose maximal cones are generated by the + d-matrices of the clusters. + + This is a complete simplicial fan (and even smooth when the + initial quiver is acyclic). It only makes sense for quivers of + finite type. + + EXAMPLES:: + + sage: Fd = ClusterQuiver([[1,2]]).d_vector_fan(); Fd + Rational polyhedral fan in 2-d lattice N + sage: Fd.ngenerating_cones() + 5 + + sage: Fd = ClusterQuiver([[1,2],[2,3]]).d_vector_fan(); Fd + Rational polyhedral fan in 3-d lattice N + sage: Fd.ngenerating_cones() + 14 + sage: Fd.is_smooth() + True + + sage: Fd = ClusterQuiver([[1,2],[2,3],[3,1]]).d_vector_fan(); Fd + Rational polyhedral fan in 3-d lattice N + sage: Fd.ngenerating_cones() + 14 + sage: Fd.is_smooth() + False + + TESTS:: + + sage: ClusterQuiver(['A',[2,2],1]).d_vector_fan() + Traceback (most recent call last): + ... + ValueError: only makes sense for quivers of finite type + """ + from cluster_seed import ClusterSeed + from sage.geometry.fan import Fan + from sage.geometry.cone import Cone + + if not(self.is_finite()): + raise ValueError('only makes sense for quivers of finite type') + seed = ClusterSeed(self) + return Fan([Cone(s.d_matrix().columns()) + for s in seed.mutation_class()]) + + def g_vector_fan(self): + r""" + Return the g-vector fan associated with the quiver. + + It is the fan whose maximal cones are generated by the + g-matrices of the clusters. + + This is a complete simplicial fan. It is only supported for + quivers of finite type. + + EXAMPLES:: + + sage: Fg = ClusterQuiver([[1,2]]).g_vector_fan(); Fg + Rational polyhedral fan in 2-d lattice N + sage: Fg.ngenerating_cones() + 5 + + sage: Fg = ClusterQuiver([[1,2],[2,3]]).g_vector_fan(); Fg + Rational polyhedral fan in 3-d lattice N + sage: Fg.ngenerating_cones() + 14 + sage: Fg.is_smooth() + True + + sage: Fg = ClusterQuiver([[1,2],[2,3],[3,1]]).g_vector_fan(); Fg + Rational polyhedral fan in 3-d lattice N + sage: Fg.ngenerating_cones() + 14 + sage: Fg.is_smooth() + True + + TESTS:: + + sage: ClusterQuiver(['A',[2,2],1]).g_vector_fan() + Traceback (most recent call last): + ... + ValueError: only supported for quivers of finite type + """ + from cluster_seed import ClusterSeed + from sage.geometry.fan import Fan + from sage.geometry.cone import Cone + + if not(self.is_finite()): + raise ValueError('only supported for quivers of finite type') + seed = ClusterSeed(self).principal_extension() + return Fan([Cone(s.g_matrix().columns()) + for s in seed.mutation_class()]) + diff --git a/src/sage/combinat/colored_permutations.py b/src/sage/combinat/colored_permutations.py index 40e14878ce9..399d26b67ba 100644 --- a/src/sage/combinat/colored_permutations.py +++ b/src/sage/combinat/colored_permutations.py @@ -6,6 +6,8 @@ Much of the colored permutations (and element) class can be generalized to `G \wr S_n` """ +import itertools + from sage.categories.groups import Groups from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.categories.finite_coxeter_groups import FiniteCoxeterGroups @@ -16,7 +18,6 @@ from sage.misc.misc_c import prod from sage.combinat.permutation import Permutations -from sage.combinat.cartesian_product import CartesianProduct from sage.matrix.constructor import diagonal_matrix from sage.rings.finite_rings.integer_mod_ring import IntegerModRing from sage.rings.number_field.number_field import CyclotomicField @@ -42,6 +43,18 @@ def __init__(self, parent, colors, perm): self._perm = perm MultiplicativeGroupElement.__init__(self, parent=parent) + def __hash__(self): + r""" + TESTS:: + + sage: C = ColoredPermutations(4, 3) + sage: s1,s2,t = C.gens() + sage: hash(s1), hash(s2), hash(t) + (2666658751600856334, 3639282354432100950, 3639281107336048003) # 64-bit + (-1973744370, 88459862, -1467077245) # 32-bit + """ + return hash(self._perm) ^ hash(self._colors) + def _repr_(self): """ Return a string representation of ``self``. @@ -460,9 +473,8 @@ def __iter__(self): [[1, 0], [2, 1]], [[1, 1], [2, 1]]] """ - C = CartesianProduct(*[self._C] * self._n) for p in self._P: - for c in C: + for c in itertools.product(self._C, repeat=self._n): yield self.element_class(self, c, p) def cardinality(self): @@ -983,10 +995,9 @@ def __iter__(self): [[1, 2], [1, -2], [-1, 2], [-1, -2], [2, 1], [2, -1], [-2, 1], [-2, -1]] """ - one = ZZ.one() - C = CartesianProduct(*[[one, -one]] * self._n) + pmone = [ZZ.one(), -ZZ.one()] for p in self._P: - for c in C: + for c in itertools.product(pmone, repeat=self._n): yield self.element_class(self, c, p) def _coerce_map_from_(self, C): diff --git a/src/sage/combinat/combination.py b/src/sage/combinat/combination.py index e72bce63ae7..9b1680f2302 100644 --- a/src/sage/combinat/combination.py +++ b/src/sage/combinat/combination.py @@ -27,7 +27,6 @@ from sage.rings.all import ZZ, Integer from sage.rings.arith import binomial from combinat import CombinatorialClass -from choose_nk import rank, from_rank from integer_vector import IntegerVectors from sage.misc.misc import uniq @@ -466,6 +465,140 @@ def rank(self, x): return rank(x, len(self.mset)) +def rank(comb, n, check=True): + """ + Return the rank of ``comb`` in the subsets of ``range(n)`` of size ``k`` + where ``k`` is the length of ``comb``. + + The algorithm used is based on combinadics and James McCaffrey's + MSDN article. See: :wikipedia:`Combinadic`. + + EXAMPLES:: + + sage: import sage.combinat.combination as combination + sage: combination.rank((), 3) + 0 + sage: combination.rank((0,), 3) + 0 + sage: combination.rank((1,), 3) + 1 + sage: combination.rank((2,), 3) + 2 + sage: combination.rank((0,1), 3) + 0 + sage: combination.rank((0,2), 3) + 1 + sage: combination.rank((1,2), 3) + 2 + sage: combination.rank((0,1,2), 3) + 0 + + sage: combination.rank((0,1,2,3), 3) + Traceback (most recent call last): + ... + ValueError: len(comb) must be <= n + sage: combination.rank((0,0), 2) + Traceback (most recent call last): + ... + ValueError: comb must be a subword of (0,1,...,n) + + sage: combination.rank([1,2], 3) + 2 + sage: combination.rank([0,1,2], 3) + 0 + """ + k = len(comb) + if check: + if k > n: + raise ValueError("len(comb) must be <= n") + comb = [int(_) for _ in comb] + for i in xrange(k - 1): + if comb[i + 1] <= comb[i]: + raise ValueError("comb must be a subword of (0,1,...,n)") + + #Generate the combinadic from the + #combination + + #w = [n-1-comb[i] for i in xrange(k)] + + #Calculate the integer that is the dual of + #the lexicographic index of the combination + r = k + t = 0 + for i in range(k): + t += binomial(n - 1 - comb[i], r) + r -= 1 + + return binomial(n,k)-t-1 + +def _comb_largest(a,b,x): + r""" + Returns the largest `w < a` such that `binomial(w,b) <= x`. + + EXAMPLES:: + + sage: from sage.combinat.combination import _comb_largest + sage: _comb_largest(6,3,10) + 5 + sage: _comb_largest(6,3,5) + 4 + """ + w = a - 1 + + while binomial(w,b) > x: + w -= 1 + + return w + +def from_rank(r, n, k): + r""" + Returns the combination of rank ``r`` in the subsets of + ``range(n)`` of size ``k`` when listed in lexicographic order. + + The algorithm used is based on combinadics and James McCaffrey's + MSDN article. See: :wikipedia:`Combinadic` + + EXAMPLES:: + + sage: import sage.combinat.combination as combination + sage: combination.from_rank(0,3,0) + () + sage: combination.from_rank(0,3,1) + (0,) + sage: combination.from_rank(1,3,1) + (1,) + sage: combination.from_rank(2,3,1) + (2,) + sage: combination.from_rank(0,3,2) + (0, 1) + sage: combination.from_rank(1,3,2) + (0, 2) + sage: combination.from_rank(2,3,2) + (1, 2) + sage: combination.from_rank(0,3,3) + (0, 1, 2) + """ + if k < 0: + raise ValueError("k must be > 0") + if k > n: + raise ValueError("k must be <= n") + + a = n + b = k + x = binomial(n, k) - 1 - r # x is the 'dual' of m + comb = [None] * k + + for i in xrange(k): + comb[i] = _comb_largest(a, b, x) + x = x - binomial(comb[i], b) + a = comb[i] + b = b - 1 + + for i in xrange(k): + comb[i] = (n - 1) - comb[i] + + return tuple(comb) + ########################################################## # Deprecations diff --git a/src/sage/combinat/composition.py b/src/sage/combinat/composition.py index 028a1d5d980..a49a587798d 100644 --- a/src/sage/combinat/composition.py +++ b/src/sage/combinat/composition.py @@ -33,10 +33,12 @@ from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent +from sage.sets.finite_enumerated_set import FiniteEnumeratedSet from sage.rings.all import ZZ from combinat import CombinatorialElement -from cartesian_product import CartesianProduct -from integer_list import IntegerListsLex +from sage.categories.cartesian_product import cartesian_product + +from integer_lists import IntegerListsLex import __builtin__ from sage.rings.integer import Integer from sage.combinat.combinatorial_map import combinatorial_map @@ -748,10 +750,16 @@ def finer(self): sage: C = Composition([3,2]).finer() sage: C.cardinality() 8 - sage: list(C) + sage: C.list() [[1, 1, 1, 1, 1], [1, 1, 1, 2], [1, 2, 1, 1], [1, 2, 2], [2, 1, 1, 1], [2, 1, 2], [3, 1, 1], [3, 2]] + + sage: Composition([]).finer() + {[]} """ - return CartesianProduct(*[Compositions(i) for i in self]).map(Composition.sum) + if not self: + return FiniteEnumeratedSet([self]) + else: + return cartesian_product([Compositions(i) for i in self]).map(Composition.sum) def is_finer(self, co2): """ diff --git a/src/sage/combinat/composition_signed.py b/src/sage/combinat/composition_signed.py index 918fcf2241f..eff46c83d51 100644 --- a/src/sage/combinat/composition_signed.py +++ b/src/sage/combinat/composition_signed.py @@ -16,8 +16,9 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools + from composition import Compositions_n, Composition -import cartesian_product from sage.rings.all import Integer from sage.rings.arith import binomial import __builtin__ @@ -130,8 +131,7 @@ def __iter__(self): """ for comp in Compositions_n.__iter__(self): l = len(comp) - a = [[1,-1] for i in range(l)] - for sign in cartesian_product.CartesianProduct(*a): + for sign in itertools.product([1,-1], repeat=l): yield [ sign[i]*comp[i] for i in range(l)] from sage.structure.sage_object import register_unpickle_override diff --git a/src/sage/combinat/crystals/affine.py b/src/sage/combinat/crystals/affine.py index 136a1873c32..bc74fad55f0 100644 --- a/src/sage/combinat/crystals/affine.py +++ b/src/sage/combinat/crystals/affine.py @@ -15,6 +15,7 @@ from sage.misc.abstract_method import abstract_method from sage.categories.regular_crystals import RegularCrystals from sage.categories.finite_crystals import FiniteCrystals +from sage.structure.element import parent from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element_wrapper import ElementWrapper @@ -349,7 +350,7 @@ def e(self, i): [[3]] sage: b.e(1) """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.e0() else: x = self.lift().e(i) @@ -374,7 +375,7 @@ def f(self, i): [[1]] sage: b.f(2) """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.f0() else: x = self.lift().f(i) @@ -417,7 +418,7 @@ def epsilon(self, i): sage: [x.epsilon(1) for x in A.list()] [0, 1, 0] """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.epsilon0() else: return self.lift().epsilon(i) @@ -455,18 +456,50 @@ def phi(self, i): sage: [x.phi(1) for x in A.list()] [1, 0, 0] """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.phi0() else: return self.lift().phi(i) - def __lt__(self, other): + def __eq__(self, other): """ Non elements of the crystal are incomparable with elements of the crystal (or should it return ``NotImplemented``?). Elements of this crystal are compared using the comparison in the underlying classical crystal. + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: b==c + False + sage: b==b + True + sage: b==1 + False + """ + return parent(self) is parent(other) and self.value == other.value + + def __ne__(self, other): + """" + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: b!=c + True + sage: b!=b + False + sage: b!=1 + True + """ + return not self == other + + def __lt__(self, other): + """" EXAMPLES:: sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) @@ -479,9 +512,74 @@ def __lt__(self, other): sage: bc + False + sage: b>b + False + sage: c>b + True + """ + return parent(self) is parent(other) and self.value > other.value + + def __le__(self, other): + """" + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: b<=c + True + sage: b<=b + True + sage: c<=b + False + """ + return parent(self) is parent(other) and self.value <= other.value + + def __ge__(self, other): + """" + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: c>=b + True + sage: b>=b + True + sage: b>=c + False + """ + return parent(self) is parent(other) and self.value >= other.value + + def __cmp__(self, other): + """" + EXAMPLES:: + + sage: K = crystals.KirillovReshetikhin(['A',2,1],1,1) + sage: b = K(rows=[[1]]) + sage: c = K(rows=[[2]]) + sage: cmp(b,c) + -1 + sage: cmp(b,b) + 0 + + If the parent are different, it uses comparison of the parents:: + + sage: cmp(b,1) == cmp(b.parent(), ZZ) + True + """ + return cmp(parent(self), parent(other)) or cmp(self.value, other.value) AffineCrystalFromClassical.Element = AffineCrystalFromClassicalElement diff --git a/src/sage/combinat/crystals/affinization.py b/src/sage/combinat/crystals/affinization.py index 3e0c1bb2713..62b1fb5c213 100644 --- a/src/sage/combinat/crystals/affinization.py +++ b/src/sage/combinat/crystals/affinization.py @@ -17,6 +17,7 @@ # http://www.gnu.org/licenses/ #**************************************************************************** +from sage.structure.element import parent from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element import Element @@ -187,11 +188,23 @@ def _latex_(self): from sage.misc.latex import latex return latex(self._b) + "({})".format(self._m) - def __eq__(self, other): + def __hash__(self): + r""" + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['A',2,1], 2, 2).affinization() + sage: mg = A.module_generators[0] + sage: hash(mg) + -6948036233304877976 # 64-bit + -1420700568 # 32-bit """ - Check equality. + return hash(self._b) ^ hash(self._m) - EXAMPLES:: + def __cmp__(self, other): + """ + Comparison. + + TESTS:: sage: A = crystals.KirillovReshetikhin(['A',2,1], 2, 2).affinization() sage: mg = A.module_generators[0] @@ -203,17 +216,6 @@ def __eq__(self, other): sage: A = crystals.AffinizationOf(KT) sage: A(KT.module_generators[3], 1).f(0) == A.module_generators[0] True - """ - if not isinstance(other, AffinizationOfCrystal.Element): - return False - return self.parent() == other.parent() \ - and self._b == other._b and self._m == other._m - - def __ne__(self, other): - """ - Check inequality. - - EXAMPLES:: sage: A = crystals.KirillovReshetikhin(['A',2,1], 2, 2).affinization() sage: mg = A.module_generators[0] @@ -221,14 +223,7 @@ def __ne__(self, other): True sage: mg != mg.f(2).e(2) False - """ - return not self.__eq__(other) - def __lt__(self, other): - """ - Check less than. - - EXAMPLES:: sage: A = crystals.KirillovReshetikhin(['A',2,1], 2, 2).affinization() sage: S = A.subcrystal(max_depth=2) @@ -241,8 +236,12 @@ def __lt__(self, other): [[1, 2], [2, 3]](1), [[1, 2], [3, 3]](1), [[2, 2], [3, 3]](2)] + """ - return self._m < other._m or (self._m == other._m and self._b < other._b) + if parent(self) is parent(other): + return cmp(self._m, other._m) or cmp(self._b, other._b) + else: + return cmp(parent(self), parent(other)) def e(self, i): """ diff --git a/src/sage/combinat/crystals/alcove_path.py b/src/sage/combinat/crystals/alcove_path.py index b8cacdf58c2..1e73f182842 100644 --- a/src/sage/combinat/crystals/alcove_path.py +++ b/src/sage/combinat/crystals/alcove_path.py @@ -714,23 +714,33 @@ def epsilon(self, i): def weight(self): """ - Returns the weight of self. + Return the weight of ``self``. EXAMPLES:: sage: C = crystals.AlcovePaths(['A',2],[2,0]) sage: for i in C: i.weight() - 2*Lambda[1] - Lambda[2] - Lambda[1] - Lambda[2] - -2*Lambda[1] + 2*Lambda[2] - -Lambda[1] - -2*Lambda[2] + (0, 2, 0) + (0, 0, 1) + (0, 1, -1) + (0, -2, 2) + (0, -1, 0) + (0, 0, -2) sage: B = crystals.AlcovePaths(['A',2,1],[1,0,0]) sage: p = B.module_generators[0].f_string([0,1,2]) sage: p.weight() Lambda[0] - delta + + TESTS: + + Check that crystal morphisms work (:trac:`19481`):: + + sage: C1 = crystals.AlcovePaths(['A',2],[1,0]) + sage: C2 = crystals.AlcovePaths(['A',2],[2,0]) + sage: phi = C1.crystal_morphism(C2.module_generators, scaling_factors={1:2, 2:2}) + sage: [phi(x) for x in C1] + [(), ((alpha[1], 0),), ((alpha[1], 0), (alpha[1] + alpha[2], 0))] """ root_space = self.parent().R.root_space() weight = -self.parent().weight @@ -738,7 +748,10 @@ def weight(self): root = root_space(i.root) weight = -i.height*root + weight.reflection(root) - return -weight + WLR = self.parent().weight_lattice_realization() + B = WLR.basis() + return WLR._from_dict({i: Integer(c) for i,c in -weight}, + remove_zeros=False) #def __repr__(self): #return str(self.integer_sequence()) diff --git a/src/sage/combinat/crystals/elementary_crystals.py b/src/sage/combinat/crystals/elementary_crystals.py index 925c11ba1f8..3745064c7b5 100644 --- a/src/sage/combinat/crystals/elementary_crystals.py +++ b/src/sage/combinat/crystals/elementary_crystals.py @@ -83,6 +83,7 @@ from sage.combinat.root_system.cartan_type import CartanType from sage.combinat.root_system.root_system import RootSystem from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ class AbstractSingleCrystalElement(Element): r""" @@ -102,6 +103,17 @@ def __lt__(self,other): """ return False + def __hash__(self): + r""" + TESTS:: + + sage: C = crystals.elementary.Component("D7") + sage: c = C.highest_weight_vector() + sage: hash(c) # random + 879 + """ + return hash(self.parent()) + def __eq__(self,other): r""" EXAMPLES:: @@ -787,7 +799,7 @@ def _element_constructor_(self, m): sage: B(721) 721 """ - return self.element_class(self, m) + return self.element_class(self, ZZ(m)) def weight_lattice_realization(self): """ @@ -817,6 +829,16 @@ def __init__(self, parent, m): self._m = m Element.__init__(self, parent) + def __hash__(self): + r""" + TESTS:: + + sage: B = crystals.elementary.Elementary(['B',7],7) + sage: hash(B(17)) + 17 + """ + return hash(self._m) + def _repr_(self): r""" EXAMPLES:: diff --git a/src/sage/combinat/crystals/fast_crystals.py b/src/sage/combinat/crystals/fast_crystals.py index 9bf2a3ec596..7372dbd1f9d 100644 --- a/src/sage/combinat/crystals/fast_crystals.py +++ b/src/sage/combinat/crystals/fast_crystals.py @@ -364,7 +364,17 @@ def _repr_(self): else: raise NotImplementedError - def __eq__(self, other): + def __hash__(self): + r""" + TESTS:: + + sage: C = crystals.FastRankTwo(['A',2],shape=[2,1]) + sage: hash(C(0)) + 0 + """ + return hash(self.value) + + def __cmp__(self, other): """ EXAMPLES:: @@ -376,14 +386,6 @@ def __eq__(self, other): False sage: C(0) == D(0) False - """ - return self.__class__ is other.__class__ and \ - self.parent() == other.parent() and \ - self.value == other.value - - def __ne__(self, other): - """ - EXAMPLES:: sage: C = crystals.FastRankTwo(['A',2],shape=[2,1]) sage: D = crystals.FastRankTwo(['B',2],shape=[2,1]) @@ -393,13 +395,6 @@ def __ne__(self, other): True sage: C(0) != D(0) True - """ - return not self == other - - - def __cmp__(self, other): - """ - EXAMPLES:: sage: C = crystals.FastRankTwo(['A',2],shape=[2,1]) sage: C(1) < C(2) @@ -411,11 +406,10 @@ def __cmp__(self, other): sage: C(1) <= C(1) True """ - if type(self) is not type(other): - return cmp(type(self), type(other)) - if self.parent() != other.parent(): - return cmp(self.parent(), other.parent()) - return self.parent().cmp_elements(self, other) + if parent(self) is parent(other): + return cmp(self.value, other.value) + else: + return cmp(parent(self), parent(other)) def e(self, i): """ @@ -436,7 +430,6 @@ def e(self, i): r = self.parent()._rootoperators[self.value][2] return self.parent()(r) if r is not None else None - def f(self, i): """ Returns the action of `f_i` on self. diff --git a/src/sage/combinat/crystals/kirillov_reshetikhin.py b/src/sage/combinat/crystals/kirillov_reshetikhin.py index c945e4cc32b..6e5d34913ca 100644 --- a/src/sage/combinat/crystals/kirillov_reshetikhin.py +++ b/src/sage/combinat/crystals/kirillov_reshetikhin.py @@ -3040,13 +3040,21 @@ def classical_decomposition(self): sage: K = crystals.KirillovReshetikhin(['D',4,1],3,2) sage: K.classical_decomposition() The crystal of tableaux of type ['D', 4] and shape(s) [[1, 1, 1, -1]] + + TESTS: + + Check that this is robust against python ints:: + + sage: K = crystals.KirillovReshetikhin(['D',4,1], 4, int(1)) + sage: K.classical_crystal + The crystal of tableaux of type ['D', 4] and shape(s) [[1/2, 1/2, 1/2, 1/2]] """ C = self.cartan_type().classical() - s = self.s() + s = QQ(self.s()) if self.r() == C.n: - c = [s/2]*C.n + c = [s/QQ(2)]*C.n else: - c = [s/2]*(C.n-1)+[-s/2] + c = [s/QQ(2)]*(C.n-1)+[-s/QQ(2)] return CrystalOfTableaux(C, shape = c) def dynkin_diagram_automorphism(self, i): @@ -3260,6 +3268,44 @@ def from_coordinates(self, coords): + [l(-2)]*coords[4] + [l(-1)]*coords[5]) return self.element_class(self, C(*lst)) + def _element_constructor_(self, *args, **options): + """ + Construct an element of ``self``. + + TESTS:: + + sage: KRC = crystals.KirillovReshetikhin(['D',4,3], 1, 3) + sage: KRT = crystals.KirillovReshetikhin(['D',4,3], 1, 3, model='KR') + sage: elt = KRC.module_generators[2].f_string([1,1,2,1,2]); elt + [[3, 0]] + sage: ret = KRT(elt); ret + [[3, 0, E]] + sage: test = KRC(ret); test + [[3, 0]] + sage: test == elt + True + """ + from sage.combinat.rigged_configurations.kr_tableaux import KirillovReshetikhinTableauxElement + if isinstance(args[0], KirillovReshetikhinTableauxElement): + elt = args[0] + # Check to make sure it can be converted + if elt.cartan_type() != self.cartan_type() \ + or elt.parent().r() != self._r or elt.parent().s() != self._s: + raise ValueError("the Kirillov-Reshetikhin tableau must have the same Cartan type and shape") + + to_hw = elt.to_classical_highest_weight() + # The classically HW element consists of 1, -1, and 'E' + wt = sum(x.value for x in to_hw[0] if x.value != 'E') + letters = elt.parent().letters + if wt: + rows = [[letters(1)]*int(wt)] + else: + rows = [] + hw_elt = self(rows=rows) + f_str = reversed(to_hw[1]) + return hw_elt.f_string(f_str) + return KirillovReshetikhinGenericCrystal._element_constructor_(self, *args, **options) + class Element(KirillovReshetikhinGenericCrystalElement): @cached_method def coordinates(self): diff --git a/src/sage/combinat/crystals/monomial_crystals.py b/src/sage/combinat/crystals/monomial_crystals.py index 147c9b5aecc..876a40600b7 100644 --- a/src/sage/combinat/crystals/monomial_crystals.py +++ b/src/sage/combinat/crystals/monomial_crystals.py @@ -162,6 +162,18 @@ def _repr_(self): return_str += "Y(%s,%s) "%(L[x][0][0],L[x][0][1]) return return_str + def __hash__(self): + r""" + TESTS:: + + sage: M = crystals.infinity.NakajimaMonomials(['C',5]) + sage: m1 = M.module_generators[0].f(1) + sage: hash(m1) + 4715601665014767730 # 64-bit + -512614286 # 32-bit + """ + return hash(frozenset(tuple(self._dict.iteritems()))) + def __eq__(self,other): r""" EXAMPLES:: diff --git a/src/sage/combinat/crystals/subcrystal.py b/src/sage/combinat/crystals/subcrystal.py index 341c3540231..031b1d6c1d2 100644 --- a/src/sage/combinat/crystals/subcrystal.py +++ b/src/sage/combinat/crystals/subcrystal.py @@ -25,6 +25,7 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.element import parent from sage.structure.parent import Parent from sage.structure.element_wrapper import ElementWrapper from sage.categories.crystals import Crystals @@ -127,6 +128,8 @@ def __classcall_private__(cls, ambient, contained=None, generators=None, generators = ambient.module_generators category = Crystals().or_subcategory(category) + if ambient in FiniteCrystals() or isinstance(contained, frozenset): + category = category.Finite() if virtualization is not None: if scaling_factors is None: @@ -250,6 +253,18 @@ def cardinality(self): sage: S = B.subcrystal(max_depth=4) sage: S.cardinality() 22 + + TESTS: + + Check that :trac:`19481` is fixed:: + + sage: from sage.combinat.crystals.virtual_crystal import VirtualCrystal + sage: A = crystals.infinity.Tableaux(['A',3]) + sage: V = VirtualCrystal(A, {1:(1,3), 2:(2,)}, {1:1, 2:2}, cartan_type=['C',2]) + sage: V.cardinality() + Traceback (most recent call last): + ... + NotImplementedError: unknown cardinality """ if self._cardinality is not None: return self._cardinality @@ -261,7 +276,10 @@ def cardinality(self): except AttributeError: if self in FiniteCrystals(): return Integer(len(self.list())) - card = super(Subcrystal, self).cardinality() + try: + card = super(Subcrystal, self).cardinality() + except AttributeError: + raise NotImplementedError("unknown cardinality") if card == infinity: self._cardinality = card return card @@ -285,9 +303,9 @@ class Element(ElementWrapper): """ An element of a subcrystal. Wraps an element in the ambient crystal. """ - def __lt__(self, other): + def __eq__(self, other): """ - Check less than. + Check sorting EXAMPLES:: @@ -307,11 +325,87 @@ def __lt__(self, other): [[-1, -1]](1), [[-1, -1]](2)] """ - if not isinstance(other, Subcrystal.Element): - return False - if other.parent() is not self.parent(): - return False - return self.value < other.value + return parent(self) is parent(other) and self.value == other.value + + def __ne__(self, other): + """ + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['C',2,1], 1,2).affinization() + sage: S = A.subcrystal(max_depth=2) + sage: ([(i,j) for i in range(len(S)) for j in range(len(S)) if S[i]!=S[j]] + ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if + ....: S[i].value!=S[j].value]) + True + """ + return parent(self) is not parent(other) or self.value != other.value + + def __lt__(self, other): + """ + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['C',2,1], 1,2).affinization() + sage: S = A.subcrystal(max_depth=2) + sage: ([(i,j) for i in range(len(S)) for j in range(len(S)) if S[i]S[j]] + ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if + ....: S[i].value>S[j].value]) + True + """ + return parent(self) is parent(other) and self.value > other.value + + def __ge__(self, other): + """ + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['C',2,1], 1,2).affinization() + sage: S = A.subcrystal(max_depth=2) + sage: ([(i,j) for i in range(len(S)) for j in range(len(S)) if S[i]>=S[j]] + ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if + ....: S[i].value>=S[j].value]) + True + """ + return parent(self) is parent(other) and self.value >= other.value + + def __cmp__(self, other): + """ + TESTS:: + + sage: A = crystals.KirillovReshetikhin(['C',2,1], 1,2).affinization() + sage: S = A.subcrystal(max_depth=2) + sage: ([(i,j,cmp(S[i],S[j])) for i in range(len(S)) for j in range(len(S))] + ....: == [(i,j,cmp(S[i].value,S[j].value)) for i in range(len(S)) for j in range(len(S))]) + True + """ + if parent(self) is parent(other): + return cmp(self.value, other.value) + else: + return cmp(parent(self), parent(other)) def e(self, i): """ diff --git a/src/sage/combinat/crystals/tensor_product.py b/src/sage/combinat/crystals/tensor_product.py index 3e78cf09a71..95968325cad 100644 --- a/src/sage/combinat/crystals/tensor_product.py +++ b/src/sage/combinat/crystals/tensor_product.py @@ -36,11 +36,11 @@ from sage.structure.element import parent from sage.structure.global_options import GlobalOptions from sage.categories.category import Category +from sage.categories.cartesian_product import cartesian_product from sage.categories.classical_crystals import ClassicalCrystals from sage.categories.regular_crystals import RegularCrystals from sage.categories.sets_cat import Sets from sage.combinat.root_system.cartan_type import CartanType -from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.combinat import CombinatorialElement from sage.combinat.partition import Partition from sage.combinat.tableau import Tableau @@ -808,7 +808,7 @@ def __init__(self, crystals, **options): raise ValueError("you need to specify the Cartan type if the tensor product list is empty") else: self._cartan_type = crystals[0].cartan_type() - self.cartesian_product = CartesianProduct(*self.crystals) + self.cartesian_product = cartesian_product(self.crystals) self.module_generators = self def _repr_(self): diff --git a/src/sage/combinat/crystals/virtual_crystal.py b/src/sage/combinat/crystals/virtual_crystal.py index eef6051c311..9f72c2f4291 100644 --- a/src/sage/combinat/crystals/virtual_crystal.py +++ b/src/sage/combinat/crystals/virtual_crystal.py @@ -168,6 +168,16 @@ def __classcall_private__(cls, ambient, virtualization, scaling_factors, sage: V2 = psi2.image() sage: V1 is V2 True + + TESTS: + + Check that :trac:`19481` is fixed:: + + sage: from sage.combinat.crystals.virtual_crystal import VirtualCrystal + sage: A = crystals.Tableaux(['A',3], shape=[2,1,1]) + sage: V = VirtualCrystal(A, {1:(1,3), 2:(2,)}, {1:1, 2:2}, cartan_type=['C',2]) + sage: V.category() + Category of finite crystals """ if cartan_type is None: cartan_type = ambient.cartan_type() @@ -181,6 +191,8 @@ def __classcall_private__(cls, ambient, virtualization, scaling_factors, scaling_factors = Family(scaling_factors) category = Crystals().or_subcategory(category) + if ambient in FiniteCrystals() or isinstance(contained, frozenset): + category = category.Finite() return super(Subcrystal, cls).__classcall__(cls, ambient, virtualization, scaling_factors, contained, tuple(generators), cartan_type, diff --git a/src/sage/combinat/diagram_algebras.py b/src/sage/combinat/diagram_algebras.py index 7752a564c42..b4f977d86c0 100644 --- a/src/sage/combinat/diagram_algebras.py +++ b/src/sage/combinat/diagram_algebras.py @@ -1967,6 +1967,45 @@ def _element_constructor_(self, set_partition): set_partition = to_Brauer_partition(set_partition, k = self.order()) return DiagramAlgebra._element_constructor_(self, set_partition) + def jucys_murphy(self, j): + r""" + Return the ``j``-th generalized Jucys-Murphy element of ``self``. + + The `j`-th Jucys-Murphy element of a Brauer algebra is simply + the `j`-th Jucys-Murphy element of the symmetric group algebra + with an extra `(z-1)/2` term, where ``z`` is the parameter + of the Brauer algebra. + + REFERENCES: + + .. [Naz96] Maxim Nazarov, Young's Orthogonal Form for Brauer's + Centralizer Algebra. Journal of Algebra 182 (1996), 664--693. + + EXAMPLES:: + + sage: z = var('z') + sage: B = BrauerAlgebra(3,z) + sage: B.jucys_murphy(1) + (1/2*z-1/2)*B{{-3, 3}, {-2, 2}, {-1, 1}} + sage: B.jucys_murphy(3) + -B{{-3, -2}, {-1, 1}, {2, 3}} - B{{-3, -1}, {-2, 2}, {1, 3}} + + B{{-3, 1}, {-2, 2}, {-1, 3}} + B{{-3, 2}, {-2, 3}, {-1, 1}} + + (1/2*z-1/2)*B{{-3, 3}, {-2, 2}, {-1, 1}} + """ + if j < 1: + raise ValueError("Jucys-Murphy index must be positive") + k = self.order() + if j > k: + raise ValueError("Jucys-Murphy index cannot be greater than the order of the algebra") + I = lambda x: self._indices(to_Brauer_partition(x, k=k)) + R = self.base_ring() + one = R.one() + d = {self.one_basis(): R( (self._q-1) / 2 )} + for i in range(1,j): + d[I([[i,-j],[j,-i]])] = one + d[I([[i,j],[-i,-j]])] = -one + return self._from_dict(d, remove_zeros=True) + class TemperleyLiebAlgebra(SubPartitionAlgebra): r""" A Temperley--Lieb algebra. diff --git a/src/sage/combinat/enumerated_sets.py b/src/sage/combinat/enumerated_sets.py index a39220a900e..7aeb4054a15 100644 --- a/src/sage/combinat/enumerated_sets.py +++ b/src/sage/combinat/enumerated_sets.py @@ -15,7 +15,7 @@ - :class:`~sage.combinat.subset.Subsets`, :class:`~sage.combinat.combination.Combinations` - :class:`~sage.combinat.permutation.Arrangements`, :class:`~sage.combinat.tuple.Tuples` - :class:`~sage.sets.finite_enumerated_set.FiniteEnumeratedSet` -- :class:`~DisjointUnionEnumeratedSets`, :class:`~CartesianProduct` +- :class:`~DisjointUnionEnumeratedSets` Integer lists ------------- @@ -123,15 +123,13 @@ - :ref:`sage.combinat.dlx` - :ref:`sage.combinat.matrices.dlxcpp` - :ref:`sage.combinat.species` -- :class:`~sage.combinat.integer_list.IntegerListsLex` +- :class:`~sage.combinat.integer_lists.IntegerListsLex` - :class:`~sage.combinat.integer_vectors_mod_permgroup.IntegerVectorsModPermutationGroup` Low level enumerated sets ------------------------- - :ref:`sage.combinat.permutation_nk` -- :ref:`sage.combinat.split_nk` -- :ref:`sage.combinat.choose_nk` - :ref:`sage.combinat.multichoose_nk` - :ref:`sage.combinat.gray_codes` diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index d79c83937d0..19358d78d8d 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -49,6 +49,7 @@ :meth:`~FiniteStateMachine.predecessors` | List of predecessors of a state :meth:`~FiniteStateMachine.induced_sub_finite_state_machine` | Induced sub-machine :meth:`~FiniteStateMachine.accessible_components` | Accessible components + :meth:`~FiniteStateMachine.coaccessible_components` | Coaccessible components :meth:`~FiniteStateMachine.final_components` | Final components (connected components which cannot be left again) @@ -86,7 +87,9 @@ :meth:`~FiniteStateMachine.delete_transition` | Delete a transition :meth:`~FiniteStateMachine.remove_epsilon_transitions` | Remove epsilon transitions (not implemented) :meth:`~FiniteStateMachine.split_transitions` | Split transitions with input words of length ``> 1`` - :meth:`~FiniteStateMachine.determine_alphabets` | Determines input and output alphabets + :meth:`~FiniteStateMachine.determine_alphabets` | Determine input and output alphabets + :meth:`~FiniteStateMachine.determine_input_alphabet` | Determine input alphabet + :meth:`~FiniteStateMachine.determine_output_alphabet` | Determine output alphabet :meth:`~FiniteStateMachine.construct_final_word_out` | Construct final output by implicitly reading trailing letters; cf. :meth:`~FiniteStateMachine.with_final_word_out` @@ -110,6 +113,7 @@ :meth:`Automaton.is_equivalent` | Checks for equivalent automata :meth:`~FiniteStateMachine.is_Markov_chain` | Checks for a Markov chain :meth:`~FiniteStateMachine.is_monochromatic` | Checks whether the colors of all states are equal + :meth:`~FiniteStateMachine.number_of_words` | Determine the number of successful paths :meth:`~FiniteStateMachine.asymptotic_moments` | Main terms of expectation and variance of sums of labels :meth:`~FiniteStateMachine.moments_waiting_time` | Moments of the waiting time for first true output :meth:`~FiniteStateMachine.epsilon_successors` | Epsilon successors of a state @@ -2971,6 +2975,38 @@ class FiniteStateMachine(sage.structure.sage_object.SageObject): ValueError: with_final_word_out cannot be specified when copying another finite state machine. + :trac:`19454` rewrote automatic detection of the alphabets:: + + sage: def transition_function(state, letter): + ....: return (0, 3 + letter) + sage: T1 = Transducer(transition_function, + ....: input_alphabet=[0, 1], + ....: initial_states=[0], + ....: final_states=[0]) + sage: T1.output_alphabet + [3, 4] + sage: T2 = Transducer([(0, 0, 0, 3), (0, 0, 0, 4)], + ....: initial_states=[0], + ....: final_states=[0]) + sage: T2.output_alphabet + [3, 4] + sage: T = Transducer([(0, 0, 1, 2)]) + sage: (T.input_alphabet, T.output_alphabet) + ([1], [2]) + sage: T = Transducer([(0, 0, 1, 2)], determine_alphabets=False) + sage: (T.input_alphabet, T.output_alphabet) + (None, None) + sage: T = Transducer([(0, 0, 1, 2)], input_alphabet=[0, 1]) + sage: (T.input_alphabet, T.output_alphabet) + ([0, 1], [2]) + sage: T = Transducer([(0, 0, 1, 2)], output_alphabet=[2, 3]) + sage: (T.input_alphabet, T.output_alphabet) + ([1], [2, 3]) + sage: T = Transducer([(0, 0, 1, 2)], input_alphabet=[0, 1], + ....: output_alphabet=[2, 3]) + sage: (T.input_alphabet, T.output_alphabet) + ([0, 1], [2, 3]) + .. automethod:: __call__ """ @@ -3140,9 +3176,6 @@ def __init__(self, self.add_transition(L) else: raise TypeError('Wrong input data for transition.') - if determine_alphabets is None and input_alphabet is None \ - and output_alphabet is None: - determine_alphabets = True elif hasattr(data, '__iter__'): # data is a something that is iterable, # items are transitions @@ -3155,15 +3188,17 @@ def __init__(self, self.add_transition(transition) else: raise TypeError('Wrong input data for transition.') - if determine_alphabets is None and input_alphabet is None \ - and output_alphabet is None: - determine_alphabets = True elif hasattr(data, '__call__'): self.add_from_transition_function(data) else: raise TypeError('Cannot decide what to do with data.') - if determine_alphabets: + if determine_alphabets is None and data is not None: + if input_alphabet is None: + self.determine_input_alphabet() + if output_alphabet is None: + self.determine_output_alphabet() + elif determine_alphabets: self.determine_alphabets() if with_final_word_out is not None: @@ -5126,27 +5161,27 @@ def default_function(transitions): len(relabeledFSM.states()), dictionary) - def determine_alphabets(self, reset=True): + def determine_input_alphabet(self, reset=True): """ - Determines the input and output alphabet according to the - transitions in self. + Determine the input alphabet according to the transitions + of this finite state machine. INPUT: - - ``reset`` -- If reset is ``True``, then the existing input - and output alphabets are erased, otherwise new letters are - appended to the existing alphabets. + - ``reset`` -- a boolean (default: ``True``). If ``True``, then + the existing input alphabet is erased, otherwise new letters are + appended to the existing alphabet. OUTPUT: Nothing. - After this operation the input alphabet and the output - alphabet of self are a list of letters. + After this operation the input alphabet of this finite state machine + is a list of letters. .. TODO:: - At the moment, the letters of the alphabets need to be hashable. + At the moment, the letters of the alphabet need to be hashable. EXAMPLES:: @@ -5154,32 +5189,126 @@ def determine_alphabets(self, reset=True): ....: (2, 2, 1, 1), (2, 2, 0, 0)], ....: final_states=[1], ....: determine_alphabets=False) - sage: T.state(1).final_word_out = [1, 4] sage: (T.input_alphabet, T.output_alphabet) (None, None) - sage: T.determine_alphabets() + sage: T.determine_input_alphabet() sage: (T.input_alphabet, T.output_alphabet) - ([0, 1, 2], [0, 1, 4]) - """ + ([0, 1, 2], None) + + .. SEEALSO:: + + :meth:`determine_output_alphabet`, + :meth:`determine_alphabets`. + """ if reset: ain = set() - aout = set() else: ain = set(self.input_alphabet) - aout = set(self.output_alphabet) for t in self.iter_transitions(): for letter in t.word_in: ain.add(letter) + self.input_alphabet = list(ain) + + + def determine_output_alphabet(self, reset=True): + """ + Determine the output alphabet according to the transitions + of this finite state machine. + + INPUT: + + - ``reset`` -- a boolean (default: ``True``). If ``True``, then + the existing output alphabet is erased, otherwise new letters are + appended to the existing alphabet. + + OUTPUT: + + Nothing. + + After this operation the output alphabet of this finite state machine + is a list of letters. + + .. TODO:: + + At the moment, the letters of the alphabet need to be hashable. + + EXAMPLES:: + + sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1), + ....: (2, 2, 1, 1), (2, 2, 0, 0)], + ....: final_states=[1], + ....: determine_alphabets=False) + sage: T.state(1).final_word_out = [1, 4] + sage: (T.input_alphabet, T.output_alphabet) + (None, None) + sage: T.determine_output_alphabet() + sage: (T.input_alphabet, T.output_alphabet) + (None, [0, 1, 4]) + + .. SEEALSO:: + + :meth:`determine_input_alphabet`, + :meth:`determine_alphabets`. + """ + if reset: + aout = set() + else: + aout = set(self.output_alphabet) + + for t in self.iter_transitions(): for letter in t.word_out: aout.add(letter) for s in self.iter_final_states(): for letter in s.final_word_out: aout.add(letter) - self.input_alphabet = list(ain) self.output_alphabet = list(aout) + def determine_alphabets(self, reset=True): + """ + Determine the input and output alphabet according to the + transitions in this finite state machine. + + INPUT: + + - ``reset`` -- If reset is ``True``, then the existing input + and output alphabets are erased, otherwise new letters are + appended to the existing alphabets. + + OUTPUT: + + Nothing. + + After this operation the input alphabet and the output + alphabet of this finite state machine are a list of letters. + + .. TODO:: + + At the moment, the letters of the alphabets need to be hashable. + + EXAMPLES:: + + sage: T = Transducer([(1, 1, 1, 0), (1, 2, 2, 1), + ....: (2, 2, 1, 1), (2, 2, 0, 0)], + ....: final_states=[1], + ....: determine_alphabets=False) + sage: T.state(1).final_word_out = [1, 4] + sage: (T.input_alphabet, T.output_alphabet) + (None, None) + sage: T.determine_alphabets() + sage: (T.input_alphabet, T.output_alphabet) + ([0, 1, 2], [0, 1, 4]) + + .. SEEALSO:: + + :meth:`determine_input_alphabet`, + :meth:`determine_output_alphabet`. + """ + self.determine_input_alphabet(reset) + self.determine_output_alphabet(reset) + + #************************************************************************* # get states and transitions #************************************************************************* @@ -6988,7 +7117,7 @@ def epsilon_successors(self, state): def accessible_components(self): """ - Returns a new finite state machine with the accessible states + Return a new finite state machine with the accessible states of self and all transitions between those states. INPUT: @@ -7018,6 +7147,9 @@ def accessible_components(self): sage: F.accessible_components() Automaton with 1 state + .. SEEALSO:: + :meth:`coaccessible_components` + TESTS: Check whether input of length > 1 works:: @@ -7050,6 +7182,47 @@ def accessible(from_state, read): return result + def coaccessible_components(self): + r""" + Return the sub-machine induced by the coaccessible states of this + finite state machine. + + OUTPUT: + + A finite state machine of the same type as this finite state + machine. + + EXAMPLES:: + + sage: A = automata.ContainsWord([1, 1], + ....: input_alphabet=[0, 1]).complement().minimization().relabeled() + sage: A.transitions() + [Transition from 0 to 0: 0|-, + Transition from 0 to 0: 1|-, + Transition from 1 to 1: 0|-, + Transition from 1 to 2: 1|-, + Transition from 2 to 1: 0|-, + Transition from 2 to 0: 1|-] + sage: A.initial_states() + [1] + sage: A.final_states() + [1, 2] + sage: C = A.coaccessible_components() + sage: C.transitions() + [Transition from 1 to 1: 0|-, + Transition from 1 to 2: 1|-, + Transition from 2 to 1: 0|-] + + .. SEEALSO:: + :meth:`accessible_components`, + :meth:`induced_sub_finite_state_machine` + """ + DG = self.digraph().reverse() + coaccessible_states = DG.breadth_first_search( + [_.label() for _ in self.iter_final_states()]) + return self.induced_sub_finite_state_machine( + [self.state(_) for _ in coaccessible_states]) + # ************************************************************************* # creating new finite state machines # ************************************************************************* @@ -8402,14 +8575,15 @@ def projection(self, what='input'): return new - def transposition(self): + def transposition(self, reverse_output_labels=True): """ Returns a new finite state machine, where all transitions of the input finite state machine are reversed. INPUT: - Nothing. + - ``reverse_output_labels`` -- a boolean (default: ``True``): whether to reverse + output labels. OUTPUT: @@ -8429,6 +8603,28 @@ def transposition(self): sage: aut.transposition().initial_states() ['1', '2'] + :: + + sage: A = Automaton([(0, 1, [1, 0])], + ....: initial_states=[0], + ....: final_states=[1]) + sage: A([1, 0]) + True + sage: A.transposition()([0, 1]) + True + + :: + + sage: T = Transducer([(0, 1, [1, 0], [1, 0])], + ....: initial_states=[0], + ....: final_states=[1]) + sage: T([1, 0]) + [1, 0] + sage: T.transposition()([0, 1]) + [0, 1] + sage: T.transposition(reverse_output_labels=False)([0, 1]) + [1, 0] + TESTS: @@ -8448,6 +8644,11 @@ def transposition(self): """ from copy import deepcopy + if reverse_output_labels: + rewrite_output = lambda word: list(reversed(word)) + else: + rewrite_output = lambda word: word + transposition = self.empty_copy() for state in self.iter_states(): @@ -8456,7 +8657,8 @@ def transposition(self): for transition in self.iter_transitions(): transposition.add_transition( transition.to_state.label(), transition.from_state.label(), - transition.word_in, transition.word_out) + list(reversed(transition.word_in)), + rewrite_output(transition.word_out)) for initial in self.iter_initial_states(): state = transposition.state(initial.label()) @@ -9731,6 +9933,146 @@ def predecessors(self, state, valid_input=None): return(done) + def number_of_words(self, variable=sage.symbolic.ring.SR.var('n'), + base_ring=sage.rings.qqbar.QQbar): + r""" + Return the number of successful input words of given length. + + INPUT: + + - ``variable`` -- a symbol denoting the length of the words, + by default `n`. + + - ``base_ring`` -- Ring (default: ``QQbar``) in which to + compute the eigenvalues. + + OUTPUT: + + A symbolic expression. + + EXAMPLES:: + + sage: NAFpm = Automaton([(0, 0, 0), (0, 1, 1), + ....: (0, 1, -1), (1, 0, 0)], + ....: initial_states=[0], + ....: final_states=[0, 1]) + sage: N = NAFpm.number_of_words(); N + 4/3*2^n - 1/3*(-1)^n + sage: all(len(list(NAFpm.language(s))) + ....: - len(list(NAFpm.language(s-1))) == N.subs(n=s) + ....: for s in srange(1, 6)) + True + + An example with non-rational eigenvalues. By default, + eigenvalues are elements of the + :mod:`field of algebraic numbers `. :: + + sage: NAFp = Automaton([(0, 0, 0), (0, 1, 1), (1, 0, 0)], + ....: initial_states=[0], + ....: final_states=[0, 1]) + sage: N = NAFp.number_of_words(); N + 1.170820393249937?*1.618033988749895?^n + - 0.1708203932499369?*(-0.618033988749895?)^n + sage: all(len(list(NAFp.language(s))) + ....: - len(list(NAFp.language(s-1))) == N.subs(n=s) + ....: for s in srange(1, 6)) + True + + We specify a suitable ``base_ring`` to obtain a radical + expression. To do so, we first compute the characteristic + polynomial and then construct a number field generated by its + roots. :: + + sage: M = NAFp.adjacency_matrix(entry=lambda t: 1) + sage: M.characteristic_polynomial() + x^2 - x - 1 + sage: R. = NumberField(x^2-x-1, embedding=1.6) + sage: N = NAFp.number_of_words(base_ring=R); N + 1/10*(1/2*sqrt(5) + 1/2)^n*(3*sqrt(5) + 5) + - 1/10*(-1/2*sqrt(5) + 1/2)^n*(3*sqrt(5) - 5) + sage: all(len(list(NAFp.language(s))) + ....: - len(list(NAFp.language(s-1))) == N.subs(n=s) + ....: for s in srange(1, 6)) + True + + In this special case, we might also use the constant + :class:`golden_ratio `:: + + sage: R. = NumberField(x^2-x-1, embedding=golden_ratio) + sage: N = NAFp.number_of_words(base_ring=R); N + 1/5*(3*golden_ratio + 1)*golden_ratio^n + - 1/5*(3*golden_ratio - 4)*(-golden_ratio + 1)^n + sage: all(len(list(NAFp.language(s))) + ....: - len(list(NAFp.language(s-1))) == N.subs(n=s) + ....: for s in srange(1, 6)) + True + + The adjacency matrix of the following example is a Jordan + matrix of size 3 to the eigenvalue 4:: + + sage: J3 = Automaton([(0, 1, -1), (1, 2, -1)], + ....: initial_states=[0], + ....: final_states=[0, 1, 2]) + sage: for i in range(3): + ....: for j in range(4): + ....: new_transition = J3.add_transition(i, i, j) + sage: J3.adjacency_matrix(entry=lambda t: 1) + [4 1 0] + [0 4 1] + [0 0 4] + sage: N = J3.number_of_words(); N + 1/2*4^(n - 2)*(n - 1)*n + 4^(n - 1)*n + 4^n + sage: all(len(list(J3.language(s))) + ....: - len(list(J3.language(s-1))) == N.subs(n=s) + ....: for s in range(1, 6)) + True + + Here is an automaton without cycles, so with eigenvalue `0`. :: + + sage: A = Automaton([(j, j+1, 0) for j in range(3)], + ....: initial_states=[0], + ....: final_states=range(3)) + sage: A.number_of_words() + 1/2*0^(n - 2)*(n - 1)*n + 0^(n - 1)*n + 0^n + + TESTS:: + + sage: A = Automaton([(0, 0, 0), (0, 1, 0)], + ....: initial_states=[0]) + sage: A.number_of_words() + Traceback (most recent call last): + ... + NotImplementedError: Finite State Machine must be deterministic. + """ + from sage.matrix.constructor import matrix + from sage.modules.free_module_element import vector + from sage.rings.arith import falling_factorial + from sage.rings.integer_ring import ZZ + from sage.symbolic.ring import SR + + def jordan_block_power(block, exponent): + eigenvalue = SR(block[0, 0]) + return matrix(block.nrows(), + block.nrows(), + lambda i, j: eigenvalue**(exponent-(j-i)) * + falling_factorial(exponent, j-i) / ZZ(j-i).factorial() + if j >= i else 0) + + if not self.is_deterministic(): + raise NotImplementedError("Finite State Machine must be deterministic.") + + left = vector(ZZ(s.is_initial) for s in self.iter_states()) + right = vector(ZZ(s.is_final) for s in self.iter_states()) + A = self.adjacency_matrix(entry=lambda t: 1) + J, T = A.jordan_form(base_ring, transformation=True) + Jpower = matrix.block_diagonal( + [jordan_block_power(J.subdivision(j, j), variable) + for j in range(len(J.subdivisions()[0]) + 1)]) + T_inv_right = T.solve_right(right).change_ring(SR) + left_T = (left * T).change_ring(SR) + return left_T * Jpower * T_inv_right + + def asymptotic_moments(self, variable=sage.symbolic.ring.SR.var('n')): r""" Returns the main terms of expectation and variance of the sum @@ -11025,13 +11367,7 @@ def determinisation(self): sage: A = Automaton([(0, 1, 1), (0, 2, [1, 1]), (0, 3, [1, 1, 1]), ....: (1, 0, -1), (2, 0, -2), (3, 0, -3)], ....: initial_states=[0], final_states=[0, 1, 2, 3]) - sage: B = A.determinisation().relabeled() - sage: all(t.to_state.label() == 2 for t in - ....: B.state(2).transitions) - True - sage: B.state(2).is_final - False - sage: B.delete_state(2) # this is a sink + sage: B = A.determinisation().relabeled().coaccessible_components() sage: sorted(B.transitions()) [Transition from 0 to 1: 1|-, Transition from 1 to 0: -1|-, diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index f75f58e56e9..3a9c4679b2a 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -12,23 +12,19 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.element import Element, have_same_parent from sage.structure.parent import Parent -from sage.structure.element import have_same_parent from sage.structure.indexed_generators import IndexedGenerators -from sage.modules.free_module_element import vector from sage.misc.misc import repr_lincomb from sage.modules.module import Module from sage.rings.all import Integer import sage.structure.element from sage.combinat.family import Family from sage.sets.finite_enumerated_set import FiniteEnumeratedSet -from sage.combinat.cartesian_product import CartesianProduct +from sage.combinat.cartesian_product import CartesianProduct_iters from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets from sage.misc.cachefunc import cached_method -from sage.misc.all import lazy_attribute -from sage.categories.poor_man_map import PoorManMap +from sage.misc.lazy_attribute import lazy_attribute from sage.categories.all import Category, Sets, ModulesWithBasis from sage.combinat.dict_addition import dict_addition, dict_linear_combination -from sage.sets.family import Family from sage.typeset.ascii_art import AsciiArt, empty_ascii_art # TODO: move the content of this class to CombinatorialFreeModule.Element and ModulesWithBasis.Element @@ -131,12 +127,19 @@ def __hash__(self): """ return hash(frozenset(self._monomial_coefficients.items())) - def monomial_coefficients(self): + def monomial_coefficients(self, copy=True): """ Return the internal dictionary which has the combinatorial objects indexing the basis as keys and their corresponding coefficients as values. + INPUT: + + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` + EXAMPLES:: sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) @@ -168,6 +171,8 @@ def monomial_coefficients(self): sage: d[ Partition([3,2]) ] 2 """ + if copy: + return dict(self._monomial_coefficients) return self._monomial_coefficients def _sorted_items_for_printing(self): @@ -257,12 +262,10 @@ def _ascii_art_(self): s = empty_ascii_art # "" first = True - i = 0 if scalar_mult is None: scalar_mult = "*" - all_atomic = True for (monomial,c) in terms: b = repr_monomial(monomial) # PCR if c != 0: @@ -516,10 +519,10 @@ def _sub_(self, other): F = self.parent() return F._from_dict( dict_linear_combination( [ ( self._monomial_coefficients, 1 ), (other._monomial_coefficients, -1 ) ] ), remove_zeros=False ) - def _coefficient_fast(self, m, default=None): + def _coefficient_fast(self, m): """ - Returns the coefficient of m in self, where m is key in - self._monomial_coefficients. + Return the coefficient of ``m`` in ``self``, where ``m`` is key in + ``self._monomial_coefficients``. EXAMPLES:: @@ -536,238 +539,24 @@ def _coefficient_fast(self, m, default=None): sage: a._coefficient_fast(p) 1 - sage: a._coefficient_fast(p, 2) - 1 sage: a._coefficient_fast(q) 0 - sage: a._coefficient_fast(q, 2) - 2 sage: a[p] 1 sage: a[q] 0 """ - if default is None: - default = self.base_ring()(0) - return self._monomial_coefficients.get(m, default) + return self._monomial_coefficients.get(m, self.base_ring().zero()) __getitem__ = _coefficient_fast - def coefficient(self, m): - """ - EXAMPLES:: - - sage: s = CombinatorialFreeModule(QQ, Partitions()) - sage: z = s([4]) - 2*s([2,1]) + s([1,1,1]) + s([1]) - sage: z.coefficient([4]) - 1 - sage: z.coefficient([2,1]) - -2 - sage: z.coefficient(Partition([2,1])) - -2 - sage: z.coefficient([1,2]) - Traceback (most recent call last): - ... - AssertionError: [1, 2] should be an element of Partitions - sage: z.coefficient(Composition([2,1])) - Traceback (most recent call last): - ... - AssertionError: [2, 1] should be an element of Partitions - - Test that coefficient also works for those parents that do not yet have an element_class:: - - sage: G = DihedralGroup(3) - sage: F = CombinatorialFreeModule(QQ, G) - sage: hasattr(G, "element_class") - False - sage: g = G.an_element() - sage: (2*F.monomial(g)).coefficient(g) - 2 - """ - # NT: coefficient_fast should be the default, just with appropriate assertions - # that can be turned on or off - C = self.parent()._indices - assert m in C, "%s should be an element of %s"%(m, C) - if hasattr(C, "element_class") and not isinstance(m, C.element_class): - m = C(m) - return self._coefficient_fast(m) - - - def is_zero(self): - """ - Returns True if and only self == 0. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: f.is_zero() - False - sage: F.zero().is_zero() - True - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: s([2,1]).is_zero() - False - sage: s(0).is_zero() - True - sage: (s([2,1]) - s([2,1])).is_zero() - True - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - return all( v == zero for v in self._monomial_coefficients.values() ) - - def __len__(self): - """ - Returns the number of basis elements of self with nonzero - coefficients. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: len(f) - 2 - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) - sage: len(z) - 4 - """ - return self.length() - - def length(self): - """ - Returns the number of basis elements of self with nonzero - coefficients. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: f.length() - 2 - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) - sage: z.length() - 4 - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - return len( [ key for key, coeff in self._monomial_coefficients.iteritems() if coeff != zero ] ) - - def support(self): - """ - Returns a list of the combinatorial objects indexing the basis - elements of self which non-zero coefficients. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: f.support() - ['a', 'c'] - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) - sage: z.support() - [[1], [1, 1, 1], [2, 1], [4]] - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - - supp = sorted([ key for key, coeff in self._monomial_coefficients.iteritems() if coeff != zero ]) - - return supp - - def monomials(self): - """ - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] + 2*B['c'] - sage: f.monomials() - [B['a'], B['c']] - - sage: (F.zero()).monomials() - [] - """ - P = self.parent() - BR = P.base_ring() - zero = BR( 0 ) - one = BR( 1 ) - - supp = sorted([ key for key, coeff in self._monomial_coefficients.iteritems() if coeff != zero ]) - - return [ P._from_dict( { key : one }, remove_zeros=False ) for key in supp ] - - def terms(self): - """ - Returns a list of the terms of ``self`` - - .. seealso:: :meth:`monomials` - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] + 2*B['c'] - sage: f.terms() - [B['a'], 2*B['c']] - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - v = sorted([ ( key, value ) for key, value in self._monomial_coefficients.iteritems() if value != zero ]) - from_dict = self.parent()._from_dict - return [ from_dict( { key : value } ) for key,value in v ] - - def coefficients(self): - """ - Returns a list of the coefficients appearing on the basis elements in - self. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) - sage: B = F.basis() - sage: f = B['a'] - 3*B['c'] - sage: f.coefficients() - [1, -3] - - :: - - sage: s = SymmetricFunctions(QQ).schur() - sage: z = s([4]) + s([2,1]) + s([1,1,1]) + s([1]) - sage: z.coefficients() - [1, 1, 1, 1] - """ - BR = self.parent().base_ring() - zero = BR( 0 ) - v = sorted([ ( key, value ) for key, value in self._monomial_coefficients.iteritems() if value != zero ]) - return [ value for key,value in v ] - def _vector_(self, new_base_ring=None): """ Returns ``self`` as a dense vector INPUT: - - ``new_base_ring`` -- a ring (default: None) + - ``new_base_ring`` -- a ring (default: ``None``) OUTPUT: a dense :func:`FreeModule` vector @@ -808,8 +597,8 @@ def _vector_(self, new_base_ring=None): sage: a == QS3.from_vector(a.to_vector()) True - If ''new_base_ring'' is specified, then a vector over - ''new_base_ring'' is returned:: + If ``new_base_ring`` is specified, then a vector over + ``new_base_ring`` is returned:: sage: a._vector_(RDF) (2.0, 0.0, 0.0, 0.0, 0.0, 4.0) @@ -838,9 +627,10 @@ def _vector_(self, new_base_ring=None): to_vector = _vector_ - def _acted_upon_(self, scalar, self_on_left = False): + def _acted_upon_(self, scalar, self_on_left=False): """ - Returns the action of a scalar on self + Return the action of ``scalar`` (an element of the base ring) on + ``self``. EXAMPLES:: @@ -920,7 +710,7 @@ def _acted_upon_(self, scalar, self_on_left = False): def __div__(self, x, self_on_left=False ): """ - Division by coefficients + Division by coefficients. EXAMPLES:: @@ -937,19 +727,19 @@ def __div__(self, x, self_on_left=False ): sage: f/2 B[2] + 2*B[3] """ - if self.base_ring().is_field(): - F = self.parent() - x = self.base_ring()( x ) - x_inv = x**-1 - D = self._monomial_coefficients - if self_on_left: - D = dict_linear_combination( [ ( D, x_inv ) ], factor_on_left=False ) - else: - D = dict_linear_combination( [ ( D, x_inv ) ] ) + if not self.base_ring().is_field(): + return self.map_coefficients(lambda c: _divide_if_possible(c, x)) - return F._from_dict( D, remove_zeros=False ) + F = self.parent() + x = self.base_ring()( x ) + x_inv = x**-1 + D = self._monomial_coefficients + if self_on_left: + D = dict_linear_combination( [ ( D, x_inv ) ], factor_on_left=False ) else: - return self.map_coefficients(lambda c: _divide_if_possible(c, x)) + D = dict_linear_combination( [ ( D, x_inv ) ] ) + + return F._from_dict( D, remove_zeros=False ) def _divide_if_possible(x, y): """ @@ -1118,7 +908,8 @@ class CombinatorialFreeModule(UniqueRepresentation, Module, IndexedGenerators): [('bracket', None), ('generator_cmp', ), ('latex_bracket', False), ('latex_prefix', None), ('latex_scalar_mult', None), ('prefix', 'x'), - ('scalar_mult', '*'), ('tensor_symbol', None)] + ('scalar_mult', '*'), ('string_quotes', True), + ('tensor_symbol', None)] sage: e = F.basis() sage: e['a'] - 3 * e['b'] @@ -1288,10 +1079,11 @@ def __init__(self, R, basis_keys, element_class = None, category = None, prefix= if element_class is not None: self.Element = element_class - # The following is needed by e.g. root systems that don't call - # the classcall and passes lists as basis_keys - if isinstance(basis_keys, (list, tuple)): - basis_keys = FiniteEnumeratedSet(basis_keys) + # The following is to ensure that basis keys is indeed a parent. + # tuple/list are converted to FiniteEnumeratedSet and set/frozenset to + # Set + # (e.g. root systems passes lists) + basis_keys = Sets()(basis_keys, enumerated_set=True) # ignore the optional 'key' since it only affects CachedRepresentation kwds.pop('key', None) @@ -1406,7 +1198,7 @@ def __contains__(self, x): def _element_constructor_(self, x): """ - Coerce x into self. + Convert ``x`` into ``self``. EXAMPLES:: @@ -1505,7 +1297,6 @@ def _element_constructor_(self, x): 2*[1, 2, 3] """ R = self.base_ring() - eclass = self.element_class #Coerce ints to Integers if isinstance(x, int): @@ -1535,7 +1326,7 @@ def _element_constructor_(self, x): def _an_element_impl(self): """ - Returns an element of self, namely the zero element. + Return an element of ``self``, namely the zero element. EXAMPLES:: @@ -1549,7 +1340,7 @@ def _an_element_impl(self): def dimension(self): """ - Returns the dimension of the combinatorial algebra (which is given + Return the dimension of the free module (which is given by the number of elements in the basis). EXAMPLES:: @@ -1584,17 +1375,19 @@ def gens(self): def set_order(self, order): """ - Sets the order of the elements of the basis. + Set the order of the elements of the basis. If :meth:`set_order` has not been called, then the ordering is the one used in the generation of the elements of self's associated enumerated set. - .. WARNING:: Many cached methods depend on this order, in - particular for constructing subspaces and quotients. - Changing the order after some computations have been - cached does not invalidate the cache, and is likely to - introduce inconsistencies. + .. WARNING:: + + Many cached methods depend on this order, in + particular for constructing subspaces and quotients. + Changing the order after some computations have been + cached does not invalidate the cache, and is likely to + introduce inconsistencies. EXAMPLES:: @@ -1612,7 +1405,7 @@ def set_order(self, order): @cached_method def get_order(self): """ - Returns the order of the elements in the basis. + Return the order of the elements in the basis. EXAMPLES:: @@ -1679,7 +1472,7 @@ def _dense_free_module(self, base_ring=None): - ``base_ring`` -- a ring or ``None`` - If ``base_ring`` is None, then the base ring of ``self`` is used. + If ``base_ring`` is ``None``, then the base ring of ``self`` is used. This method is mostly used by :meth:`CombinatorialFreeModule.Element._vector_` @@ -1702,7 +1495,7 @@ def _dense_free_module(self, base_ring=None): def from_vector(self, vector): """ - Build an element of ``self`` from a (sparse) vector + Build an element of ``self`` from a (sparse) vector. .. SEEALSO:: :meth:`get_order`, :meth:`CombinatorialFreeModule.Element._vector_` @@ -1735,101 +1528,16 @@ def __cmp__(self, other): if c: return c return 0 - def _apply_module_morphism( self, x, on_basis, codomain=False ): - """ - Returns the image of ``x`` under the module morphism defined by - extending :func:`on_basis` by linearity. - - INPUT: - - - - ``x`` : a element of self - - - ``on_basis`` - a function that takes in a combinatorial - object indexing a basis element and returns an element of the - codomain - - - ``codomain`` (optional) - the codomain of the morphism, otherwise it is computed - using :func:`on_basis` - - If ``codomain`` is not specified, then the function tries to compute the codomain - of the module morphism by finding the image of one of the elements in the - support, hence :func:`on_basis` should return an element whose parent is the - codomain. - - EXAMPLES:: - - sage: s = SymmetricFunctions(QQ).schur() - sage: a = s([3]) + s([2,1]) + s([1,1,1]) - sage: b = 2*a - sage: f = lambda part: Integer( len(part) ) - sage: s._apply_module_morphism(a, f) #1+2+3 - 6 - sage: s._apply_module_morphism(b, f) #2*(1+2+3) - 12 - sage: s._apply_module_morphism(s(0), f) - 0 - sage: s._apply_module_morphism(s(1), f) - 0 - sage: s._apply_module_morphism(s(1), lambda part: len(part), ZZ) - 0 - sage: s._apply_module_morphism(s(1), lambda part: len(part)) - Traceback (most recent call last): - ... - ValueError: Codomain could not be determined - """ - - if x == self.zero(): - if not codomain: - B = Family(self.basis()) - try: - z = B.first() - except StopIteration: - raise ValueError('Codomain could not be determined') - codomain = on_basis(z).parent() - return codomain.zero() - else: - if not codomain: - keys = x.support() - key = keys[0] - try: - codomain = on_basis(key).parent() - except Exception: - raise ValueError('Codomain could not be determined') - - if hasattr( codomain, 'linear_combination' ): - return codomain.linear_combination( ( on_basis( key ), coeff ) for key, coeff in x._monomial_coefficients.iteritems() ) - else: - return_sum = codomain.zero() - for key, coeff in x._monomial_coefficients.iteritems(): - return_sum += coeff * on_basis( key ) - return return_sum - - def _apply_module_endomorphism(self, x, on_basis): - """ - This takes in a function from the basis elements to the elements of - self and applies it linearly to a. Note that - _apply_module_endomorphism does not require multiplication on - self to be defined. - - EXAMPLES:: - - sage: s = SymmetricFunctions(QQ).schur() - sage: f = lambda part: 2*s(part.conjugate()) - sage: s._apply_module_endomorphism( s([2,1]) + s([1,1,1]), f) - 2*s[2, 1] + 2*s[3] - """ - return self.linear_combination( ( on_basis( key ), coeff ) for key, coeff in x._monomial_coefficients.iteritems() ) - def sum(self, iter_of_elements): """ - overrides method inherited from commutative additive monoid as it is much faster on dicts directly + Return the sum of all elements in ``iter_of_elements``. - INPUT: + Overrides method inherited from commutative additive monoid as it + is much faster on dicts directly. - - ``iter_of_elements``: iterator of elements of ``self`` + INPUT: - Returns the sum of all elements in ``iter_of_elements`` + - ``iter_of_elements`` -- iterator of elements of ``self`` EXAMPLES:: @@ -1839,26 +1547,25 @@ def sum(self, iter_of_elements): sage: F.sum( f for _ in range(5) ) 10*B[1] + 10*B[2] """ - D = dict_addition( element._monomial_coefficients for element in iter_of_elements ) return self._from_dict( D, remove_zeros=False ) - def linear_combination( self, iter_of_elements_coeff, factor_on_left=True ): + def linear_combination(self, iter_of_elements_coeff, factor_on_left=True): """ + Return the linear combination `\lambda_1 v_1 + \cdots + + \lambda_k v_k` (resp. the linear combination `v_1 \lambda_1 + + \cdots + v_k \lambda_k`) where ``iter_of_elements_coeff`` iterates + through the sequence `((\lambda_1, v_1), ..., (\lambda_k, v_k))`. + INPUT: - ``iter_of_elements_coeff`` -- iterator of pairs ``(element, coeff)`` with ``element`` in ``self`` and ``coeff`` in ``self.base_ring()`` - - ``factor_on_left`` (optional) -- if ``True``, the coefficients are + - ``factor_on_left`` -- (optional) if ``True``, the coefficients are multiplied from the left if ``False``, the coefficients are multiplied from the right - Returns the linear combination `\lambda_1 v_1 + ... + \lambda_k v_k` - (resp. the linear combination `v_1 \lambda_1 + ... + v_k \lambda_k`) - where ``iter_of_elements_coeff`` iterates through the sequence - `(\lambda_1, v_1) ... (\lambda_k, v_k)`. - EXAMPLES:: sage: F = CombinatorialFreeModule(QQ,[1,2]) @@ -1871,7 +1578,7 @@ def linear_combination( self, iter_of_elements_coeff, factor_on_left=True ): def term(self, index, coeff=None): """ - Constructs a term in ``self`` + Construct a term in ``self``. INPUT: @@ -1902,15 +1609,14 @@ def _monomial(self, index): """ return self._from_dict( {index: self.base_ring().one()}, remove_zeros = False ) - # This is generic, and should be lifted into modules_with_basis @lazy_attribute def monomial(self): """ - INPUT: + Return the basis element indexed by ``i``. - - ''i'' + INPUT: - Returns the basis element indexed by i + - ``i`` -- an element of the index set EXAMPLES:: @@ -1918,61 +1624,24 @@ def monomial(self): sage: F.monomial('a') B['a'] - F.monomial is in fact (almost) a map:: + ``F.monomial`` is in fact (almost) a map:: sage: F.monomial Term map from {'a', 'b', 'c'} to Free module generated by {'a', 'b', 'c'} over Rational Field """ # Should use a real Map, as soon as combinatorial_classes are enumerated sets, and therefore parents + from sage.categories.poor_man_map import PoorManMap return PoorManMap(self._monomial, domain=self._indices, codomain=self, name="Term map") - def _sum_of_monomials(self, indices): - """ - TESTS:: - - sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) - sage: F._sum_of_monomials(['a', 'b']) - B['a'] + B['b'] - """ - # TODO: optimize by calling directly _from_dict if we - # know that all indices are distinct as sum_of_terms; - # otherwise, maybe call dict_addition directly - return self.sum(self.monomial(index) for index in indices) - - @lazy_attribute - def sum_of_monomials(self): - """ - INPUT: - - - ''indices'' -- an list (or iterable) of indices of basis elements - - Returns the sum of the corresponding basis elements - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) - sage: F.sum_of_monomials(['a', 'b']) - B['a'] + B['b'] - - sage: F.sum_of_monomials(['a', 'b', 'a']) - 2*B['a'] + B['b'] - - F.sum_of_monomials is in fact (almost) a map:: - - sage: F.sum_of_monomials - A map to Free module generated by {'a', 'b', 'c'} over Rational Field - """ - # domain = sets of self.combinatorial_class(), - return PoorManMap(self._sum_of_monomials, codomain = self) - def sum_of_terms(self, terms, distinct=False): """ - Constructs a sum of terms of ``self`` + Construct a sum of terms of ``self``. INPUT: - - ``terms`` -- a list (or iterable) of pairs (index, coeff) - - ``distinct`` -- whether the indices are guaranteed to be distinct (default: ``False``) + - ``terms`` -- a list (or iterable) of pairs ``(index, coeff)`` + - ``distinct`` -- (default: ``False``) whether the indices are + guaranteed to be distinct EXAMPLES:: @@ -1985,7 +1654,7 @@ def sum_of_terms(self, terms, distinct=False): sage: F.sum_of_terms([('a',2), ('c',3)], distinct = True) 2*B['a'] + 3*B['c'] - .. warning:: + .. WARNING:: Use ``distinct=True`` only if you are sure that the indices are indeed distinct:: @@ -2002,20 +1671,6 @@ def sum_of_terms(self, terms, distinct=False): return self._from_dict(dict(terms)) return self.sum(self.term(index, coeff) for (index, coeff) in terms) - def monomial_or_zero_if_none(self, i): - """ - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) - sage: F.monomial_or_zero_if_none('a') - B['a'] - sage: F.monomial_or_zero_if_none(None) - 0 - """ - if i is None: - return self.zero() - return self.monomial(i) - @cached_method def zero(self): """ @@ -2027,18 +1682,19 @@ def zero(self): """ return self._from_dict({}, remove_zeros=False) - def _from_dict( self, d, coerce=False, remove_zeros=True ): - """ - Construct an element of ``self`` from an `{index: coefficient}` dictionary. + def _from_dict(self, d, coerce=False, remove_zeros=True): + r""" + Construct an element of ``self`` from an ``{index: coefficient}`` + dictionary. INPUT: - - ``d`` -- a dictionary ``{index: coeff}`` where each ``index`` is the - index of a basis element and each ``coeff`` belongs to the + - ``d`` -- a dictionary ``{index: coeff}`` where each ``index`` is + the index of a basis element and each ``coeff`` belongs to the coefficient ring ``self.base_ring()`` - - ``coerce`` -- a boolean (default: ``False``), whether to coerce the - coefficients ``coeff`` to the coefficient ring + - ``coerce`` -- a boolean (default: ``False``), whether to coerce + the coefficients ``coeff`` to the coefficient ring - ``remove_zeros`` -- a boolean (default: ``True``), if some coefficients ``coeff`` may be zero and should therefore be removed @@ -2067,7 +1723,7 @@ def _from_dict( self, d, coerce=False, remove_zeros=True ): sage: s._from_dict({part:0}) 0 - .. warning:: + .. WARNING:: With ``remove_zeros=True``, it is assumed that no coefficient of the dictionary is zero. Otherwise, this may @@ -2079,9 +1735,9 @@ def _from_dict( self, d, coerce=False, remove_zeros=True ): assert isinstance(d, dict) if coerce: R = self.base_ring() - d = dict( (key, R(coeff)) for key,coeff in d.iteritems()) + d = {key: R(coeff) for key,coeff in d.iteritems()} if remove_zeros: - d = dict( (key, coeff) for key, coeff in d.iteritems() if coeff) + d = {key: coeff for key, coeff in d.iteritems() if coeff} return self.element_class( self, d ) class CombinatorialFreeModule_Tensor(CombinatorialFreeModule): @@ -2194,7 +1850,7 @@ def __init__(self, modules, **options): """ from sage.categories.tensor import tensor self._sets = modules - CombinatorialFreeModule.__init__(self, modules[0].base_ring(), CartesianProduct(*[module.basis().keys() for module in modules]).map(tuple), **options) + CombinatorialFreeModule.__init__(self, modules[0].base_ring(), CartesianProduct_iters(*[module.basis().keys() for module in modules]).map(tuple), **options) # the following is not the best option, but it's better than nothing. self._print_options['tensor_symbol'] = options.get('tensor_symbol', tensor.symbol) @@ -2686,3 +2342,4 @@ class Element(CombinatorialFreeModule.Element): # TODO: get rid of this inherita pass CombinatorialFreeModule.CartesianProduct = CombinatorialFreeModule_CartesianProduct + diff --git a/src/sage/combinat/fully_packed_loop.py b/src/sage/combinat/fully_packed_loop.py index 0b053c3809c..a2c3ad3d1da 100644 --- a/src/sage/combinat/fully_packed_loop.py +++ b/src/sage/combinat/fully_packed_loop.py @@ -875,6 +875,37 @@ def plot(self): G.axes(False) return G + def gyration(self): + r""" + Return the fully packed loop obtained by applying gyration + to the alternating sign matrix in bijection with ``self``. + + Gyration was first defined in [Wieland00]_ as an action on + fully-packed loops. + + REFERENCES: + + .. [Wieland00] B. Wieland. *A large dihedral symmetry of the set of + alternating sign matrices*. Electron. J. Combin. 7 (2000). + + EXAMPLES:: + + sage: A = AlternatingSignMatrix([[1, 0, 0],[0, 1, 0],[0, 0, 1]]) + sage: fpl = FullyPackedLoop(A) + sage: fpl.gyration().to_alternating_sign_matrix() + [0 0 1] + [0 1 0] + [1 0 0] + sage: asm = AlternatingSignMatrix([[0, 0, 1],[1, 0, 0],[0, 1, 0]]) + sage: f = FullyPackedLoop(asm) + sage: f.gyration().to_alternating_sign_matrix() + [0 1 0] + [0 0 1] + [1 0 0] + """ + return FullyPackedLoop(self.to_alternating_sign_matrix().gyration()) + + def link_pattern(self): """ Return a link pattern corresponding to a fully packed loop. @@ -1416,4 +1447,4 @@ def _an_element_(self): #ASM = AlternatingSignMatrix(matrix.identity(self._n)) #SVM = ASM.to_six_vertex_model() SVM = SixVertexModel(self._n,boundary_conditions='ice').an_element() - return self.element_class(self, SVM) \ No newline at end of file + return self.element_class(self, SVM) diff --git a/src/sage/combinat/integer_list.py b/src/sage/combinat/integer_list.py index 774ea865bad..136d7baa95f 100644 --- a/src/sage/combinat/integer_list.py +++ b/src/sage/combinat/integer_list.py @@ -1,2408 +1,15 @@ -r""" -Enumerated set of lists of integers with constraints, in inverse lexicographic order - -- :class:`IntegerListsLex`: the enumerated set of lists of nonnegative - integers with specified constraints, in inverse lexicographic order. - -- :class:`Envelope`: a utility class for upper (lower) envelope of a - function under constraints. - -HISTORY: - -This generic tool was originally written by Hivert and Thiery in -MuPAD-Combinat in 2000 and ported over to Sage by Mike Hansen in -2007. It was then completely rewritten in 2015 by Gillespie, -Schilling, and Thiery, with the help of many, to deal with -limitations and lack of robustness w.r.t. input. """ -#***************************************************************************** -# Copyright (C) 2015 Bryan Gillespie -# Nicolas M. Thiery -# Anne Schilling -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** - -from inspect import ismethod -from sage.misc.classcall_metaclass import ClasscallMetaclass, typecall -from sage.misc.constant_function import ConstantFunction -from sage.misc.cachefunc import cached_method -from sage.categories.enumerated_sets import EnumeratedSets -from sage.structure.list_clone import ClonableArray -from sage.structure.parent import Parent -from sage.sets.family import Family -from sage.rings.integer_ring import ZZ - -Infinity = float('+inf') - -class IntegerListsLex(Parent): - r""" - Lists of nonnegative integers with constraints, in inverse lexicographic order. - - An *integer list* is a list `l` of nonnegative integers, its *parts*. The - slope (at position `i`) is the difference ``l[i+1]-l[i]`` between two - consecutive parts. - - This class allows to construct the set `S` of all integer lists - `l` satisfying specified bounds on the sum, the length, the slope, - and the individual parts, enumerated in *inverse* lexicographic - order, that is from largest to smallest in lexicographic - order. Note that, to admit such an enumeration, `S` is almost - necessarily finite (see :ref:`IntegerListsLex_finiteness`). - - The main purpose is to provide a generic iteration engine for all the - enumerated sets like :class:`Partitions`, :class:`Compositions`, - :class:`IntegerVectors`. It can also be used to generate many other - combinatorial objects like Dyck paths, Motzkin paths, etc. Mathematically - speaking, this is a special case of set of integral points of a polytope (or - union thereof, when the length is not fixed). - - INPUT: - - - ``min_sum`` -- a nonnegative integer (default: 0): - a lower bound on ``sum(l)``. - - - ``max_sum`` -- a nonnegative integer or `\infty` (default: `\infty`): - an upper bound on ``sum(l)``. - - - ``n`` -- a nonnegative integer (optional): if specified, this - overrides ``min_sum`` and ``max_sum``. - - - ``min_length`` -- a nonnegative integer (default: `0`): a lower - bound on ``len(l)``. - - - ``max_length`` -- a nonnegative integer or `\infty` (default: - `\infty`): an upper bound on ``len(l)``. - - - ``length`` -- an integer (optional); overrides ``min_length`` - and ``max_length`` if specified; - - - ``min_part`` -- a nonnegative integer: a lower bounds on all - parts: ``min_part <= l[i]`` for ``0 <= i < len(l)``. - - - ``floor`` -- a list of nonnegative integers or a function: lower - bounds on the individual parts `l[i]`. - - If ``floor`` is a list of integers, then ``floor<=l[i]`` for ``0 - <= i < min(len(l), len(floor)``. Similarly, if ``floor`` is a - function, then ``floor(i) <= l[i]`` for ``0 <= i < len(l)``. - - - ``max_part`` -- a nonnegative integer or `\infty`: an upper - bound on all parts: ``l[i] <= max_part`` for ``0 <= i < len(l)``. - - - ``ceiling`` -- upper bounds on the individual parts ``l[i]``; - this takes the same type of input as ``floor``, except that - `\infty` is allowed in addition to integers, and the default - value is `\infty`. - - - ``min_slope`` -- an integer or `-\infty` (default: `-\infty`): - an lower bound on the slope between consecutive parts: - ``min_slope <= l[i+1]-l[i]`` for ``0 <= i < len(l)-1`` - - - ``max_slope`` -- an integer or `+\infty` (defaults: `+\infty`) - an upper bound on the slope between consecutive parts: - ``l[i+1]-l[i] <= max_slope`` for ``0 <= i < len(l)-1`` - - - ``category`` -- a category (default: :class:`FiniteEnumeratedSets`) - - - ``check`` -- boolean (default: ``True``): whether to display the - warnings raised when functions are given as input to ``floor`` - or ``ceiling`` and the errors raised when there is no proper - enumeration. - - - ``name`` -- a string or ``None`` (default: ``None``) if set, - this will be passed down to :meth:`Parent.rename` to specify the - name of ``self``. It is recommented to use directly the rename - method as this feature may become deprecated. - - - ``element_constructor`` -- a function (or callable) that creates - elements of ``self`` from a list. See also :class:`Parent`. - - - ``element_class`` -- a class for the elements of ``self`` - (default: `ClonableArray`). This merely sets the attribute - ``self.Element``. See the examples for details. - - - ``global_options`` -- (deprecated) a - :class:`~sage.structure.global_options.GlobalOptions` - object that will be assigned to the attribute - ``_global_options``; for internal use only (subclasses, ...). - - - .. NOTE:: - - When several lists satisfying the constraints differ only by - trailing zeroes, only the shortest one is enumerated (and - therefore counted). The others are still considered valid. - See the examples below. - - This feature is questionable. It is recommended not to rely on - it, as it may eventually be discontinued. - - EXAMPLES: - - We create the enumerated set of all lists of nonnegative integers - of length `3` and sum `2`:: - - sage: C = IntegerListsLex(2, length=3) - sage: C - Integer lists of sum 2 satisfying certain constraints - sage: C.cardinality() - 6 - sage: [p for p in C] - [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] - - sage: [2, 0, 0] in C - True - sage: [2, 0, 1] in C - False - sage: "a" in C - False - sage: ["a"] in C - False - sage: C.first() - [2, 0, 0] - - One can specify lower and upper bounds on each part:: - - sage: list(IntegerListsLex(5, length=3, floor=[1,2,0], ceiling=[3,2,3])) - [[3, 2, 0], [2, 2, 1], [1, 2, 2]] - - When the length is fixed as above, one can also use - :class:`IntegerVectors`:: - - sage: IntegerVectors(2,3).list() - [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] - - Using the slope condition, one can generate integer partitions - (but see :class:`Partitions`):: - - sage: list(IntegerListsLex(4, max_slope=0)) - [[4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]] - - The following is the list of all partitions of `7` with parts at least `2`:: - - sage: list(IntegerListsLex(7, max_slope=0, min_part=2)) - [[7], [5, 2], [4, 3], [3, 2, 2]] - - - .. RUBRIC:: floor and ceiling conditions - - Next we list all partitions of `5` of length at most `3` which are - bounded below by ``[2,1,1]``:: - - sage: list(IntegerListsLex(5, max_slope=0, max_length=3, floor=[2,1,1])) - [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] - - Note that ``[5]`` is considered valid, because the floor - constraints only apply to existing positions in the list. To - obtain instead the partitions containing ``[2,1,1]``, one needs to - use ``min_length`` or ``length``:: - - sage: list(IntegerListsLex(5, max_slope=0, length=3, floor=[2,1,1])) - [[3, 1, 1], [2, 2, 1]] - - Here is the list of all partitions of `5` which are contained in - ``[3,2,2]``:: - - sage: list(IntegerListsLex(5, max_slope=0, max_length=3, ceiling=[3,2,2])) - [[3, 2], [3, 1, 1], [2, 2, 1]] - - This is the list of all compositions of `4` (but see :class:`Compositions`):: - - sage: list(IntegerListsLex(4, min_part=1)) - [[4], [3, 1], [2, 2], [2, 1, 1], [1, 3], [1, 2, 1], [1, 1, 2], [1, 1, 1, 1]] - - This is the list of all integer vectors of sum `4` and length `3`:: - - sage: list(IntegerListsLex(4, length=3)) - [[4, 0, 0], [3, 1, 0], [3, 0, 1], [2, 2, 0], [2, 1, 1], - [2, 0, 2], [1, 3, 0], [1, 2, 1], [1, 1, 2], [1, 0, 3], - [0, 4, 0], [0, 3, 1], [0, 2, 2], [0, 1, 3], [0, 0, 4]] - - For whatever it is worth, the ``floor`` and ``min_part`` - constraints can be combined:: - - sage: L = IntegerListsLex(5, floor=[2,0,2], min_part=1) - sage: L.list() - [[5], [4, 1], [3, 2], [2, 3], [2, 1, 2]] - - This is achieved by updating the floor upon constructing ``L``:: - - sage: [L._floor(i) for i in range(5)] - [2, 1, 2, 1, 1] - - Similarly, the ``ceiling`` and ``max_part`` constraints can be - combined:: - - sage: L = IntegerListsLex(4, ceiling=[2,3,1], max_part=2, length=3) - sage: L.list() - [[2, 2, 0], [2, 1, 1], [1, 2, 1]] - sage: [L._ceiling(i) for i in range(5)] - [2, 2, 1, 2, 2] - - - This can be used to generate Motzkin words (see - :wikipedia:`Motzkin_number`):: - - sage: def motzkin_words(n): - ....: return IntegerListsLex(length=n+1, min_slope=-1, max_slope=1, - ....: ceiling=[0]+[+oo for i in range(n-1)]+[0]) - sage: motzkin_words(4).list() - [[0, 1, 2, 1, 0], - [0, 1, 1, 1, 0], - [0, 1, 1, 0, 0], - [0, 1, 0, 1, 0], - [0, 1, 0, 0, 0], - [0, 0, 1, 1, 0], - [0, 0, 1, 0, 0], - [0, 0, 0, 1, 0], - [0, 0, 0, 0, 0]] - sage: [motzkin_words(n).cardinality() for n in range(8)] - [1, 1, 2, 4, 9, 21, 51, 127] - sage: oeis(_) # optional -- internet - 0: A001006: Motzkin numbers: number of ways of drawing any number - of nonintersecting chords joining n (labeled) points on a circle. - - or Dyck words (see also :class:`DyckWords`), through the bijection - with paths from `(0,0)` to `(n,n)` with left and up steps that remain - below the diagonal:: - - sage: def dyck_words(n): - ....: return IntegerListsLex(length=n, ceiling=range(n+1), min_slope=0) - sage: [dyck_words(n).cardinality() for n in range(8)] - [1, 1, 2, 5, 14, 42, 132, 429] - sage: dyck_words(3).list() - [[0, 1, 2], [0, 1, 1], [0, 0, 2], [0, 0, 1], [0, 0, 0]] - - - .. _IntegerListsLex_finiteness: - - .. RUBRIC:: On finiteness and inverse lexicographic enumeration - - The set of all lists of integers cannot be enumerated in inverse - lexicographic order, since there is no largest list (take `[n]` - for `n` as large as desired):: - - sage: IntegerListsLex().first() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - Here is a variant which could be enumerated in lexicographic order - but not in inverse lexicographic order:: - - sage: L = IntegerListsLex(length=2, ceiling=[Infinity, 0], floor=[0,1]) - sage: for l in L: print l - Traceback (most recent call last): - ... - ValueError: infinite upper bound for values of m - - Even when the sum is specified, it is not necessarily possible to - enumerate *all* elements in inverse lexicographic order. In the - following example, the list ``[1, 1, 1]`` will never appear in the - enumeration:: - - sage: IntegerListsLex(3).first() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - If one wants to proceed anyway, one can sign a waiver by setting - ``check=False`` (again, be warned that some valid lists may never appear):: - - sage: L = IntegerListsLex(3, check=False) - sage: it = iter(L) - sage: [next(it) for i in range(6)] - [[3], [2, 1], [2, 0, 1], [2, 0, 0, 1], [2, 0, 0, 0, 1], [2, 0, 0, 0, 0, 1]] - - In fact, being inverse lexicographically enumerable is almost - equivalent to being finite. The only infinity that can occur would - be from a tail of numbers `0,1` as in the previous example, where - the `1` moves further and further to the right. If there is any - list that is inverse lexicographically smaller than such a - configuration, the iterator would not reach it and hence would not - be considered iterable. Given that the infinite cases are very - specific, at this point only the finite cases are supported - (without signing the waiver). - - The finiteness detection is not complete yet, so some finite cases - may not be supported either, at least not without disabling the - checks. Practical examples of such are welcome. - - .. RUBRIC:: On trailing zeroes, and their caveats - - As mentioned above, when several lists satisfying the constraints - differ only by trailing zeroes, only the shortest one is listed:: - - sage: L = IntegerListsLex(max_length=4, max_part=1) - sage: L.list() - [[1, 1, 1, 1], - [1, 1, 1], - [1, 1, 0, 1], - [1, 1], - [1, 0, 1, 1], - [1, 0, 1], - [1, 0, 0, 1], - [1], - [0, 1, 1, 1], - [0, 1, 1], - [0, 1, 0, 1], - [0, 1], - [0, 0, 1, 1], - [0, 0, 1], - [0, 0, 0, 1], - []] - - and counted:: - - sage: L.cardinality() - 16 - - Still, the others are considered as elements of `L`:: - - sage: L = IntegerListsLex(4,min_length=3,max_length=4) - sage: L.list() - [..., [2, 2, 0], ...] - - sage: [2, 2, 0] in L # in L.list() - True - sage: [2, 2, 0, 0] in L # not in L.list() ! - True - sage: [2, 2, 0, 0, 0] in L - False - - .. RUBRIC:: Specifying functions as input for the floor or ceiling - - We construct all lists of sum `4` and length `4` such that ``l[i] <= i``:: - - sage: list(IntegerListsLex(4, length=4, ceiling=lambda i: i, check=False)) - [[0, 1, 2, 1], [0, 1, 1, 2], [0, 1, 0, 3], [0, 0, 2, 2], [0, 0, 1, 3]] - - .. WARNING:: - - When passing a function as ``floor`` or ``ceiling``, it may - become undecidable to detect improper inverse lexicographic - enumeration. For example, the following example has a finite - enumeration:: - - sage: L = IntegerListsLex(3, floor=lambda i: 1 if i>=2 else 0, check=False) - sage: L.list() - [[3], - [2, 1], - [2, 0, 1], - [1, 2], - [1, 1, 1], - [1, 0, 2], - [1, 0, 1, 1], - [0, 3], - [0, 2, 1], - [0, 1, 2], - [0, 1, 1, 1], - [0, 0, 3], - [0, 0, 2, 1], - [0, 0, 1, 2], - [0, 0, 1, 1, 1]] - - but one cannot decide whether the following has an improper - inverse lexicographic enumeration without computing the floor - all the way to ``Infinity``:: - - sage: L = IntegerListsLex(3, floor=lambda i: 0, check=False) - sage: it = iter(L) - sage: [next(it) for i in range(6)] - [[3], [2, 1], [2, 0, 1], [2, 0, 0, 1], [2, 0, 0, 0, 1], [2, 0, 0, 0, 0, 1]] - - Hence a warning is raised when a function is specified as - input, unless the waiver is signed by setting ``check=False``:: - - sage: L = IntegerListsLex(3, floor=lambda i: 1 if i>=2 else 0) - doctest:... - A function has been given as input of the floor=[...] or ceiling=[...] - arguments of IntegerListsLex. Please see the documentation for the caveats. - If you know what you are doing, you can set check=False to skip this warning. - - Similarly, the algorithm may need to search forever for a - solution when the ceiling is ultimately zero:: - - sage: L = IntegerListsLex(2,ceiling=lambda i:0, check=False) - sage: L.first() # not tested: will hang forever - sage: L = IntegerListsLex(2,ceiling=lambda i:0 if i<20 else 1, check=False) - sage: it = iter(L) - sage: next(it) - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] - sage: next(it) - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1] - sage: next(it) - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1] - - - .. RUBRIC:: Tip: using disjoint union enumerated sets for additional flexibility - - Sometimes, specifying a range for the sum or the length may be too - restrictive. One would want instead to specify a list, or - iterable `L`, of acceptable values. This is easy to achieve using - a :class:`disjoint union of enumerated sets `. - Here we want to accept the values `n=0,2,3`:: - - sage: C = DisjointUnionEnumeratedSets(Family([0,2,3], - ....: lambda n: IntegerListsLex(n, length=2))) - sage: C - Disjoint union of Finite family - {0: Integer lists of sum 0 satisfying certain constraints, - 2: Integer lists of sum 2 satisfying certain constraints, - 3: Integer lists of sum 3 satisfying certain constraints} - sage: C.list() - [[0, 0], - [2, 0], [1, 1], [0, 2], - [3, 0], [2, 1], [1, 2], [0, 3]] - - The price to pay is that the enumeration order is now *graded - lexicographic* instead of lexicographic: first choose the value - according to the order specified by `L`, and use lexicographic - order within each value. Here is we reverse `L`:: - - sage: DisjointUnionEnumeratedSets(Family([3,2,0], - ....: lambda n: IntegerListsLex(n, length=2))).list() - [[3, 0], [2, 1], [1, 2], [0, 3], - [2, 0], [1, 1], [0, 2], - [0, 0]] - - Note that if a given value appears several times, the - corresponding elements will be enumerated several times, which - may, or not, be what one wants:: - - sage: DisjointUnionEnumeratedSets(Family([2,2], - ....: lambda n: IntegerListsLex(n, length=2))).list() - [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] - - Here is a variant where we specify acceptable values for the - length:: - - sage: DisjointUnionEnumeratedSets(Family([0,1,3], - ....: lambda l: IntegerListsLex(2, length=l))).list() - [[2], - [2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] - - - This technique can also be useful to obtain a proper enumeration - on infinite sets by using a graded lexicographic enumeration:: - - sage: C = DisjointUnionEnumeratedSets(Family(NN, - ....: lambda n: IntegerListsLex(n, length=2))) - sage: C - Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} - sage: it = iter(C) - sage: [next(it) for i in range(10)] - [[0, 0], - [1, 0], [0, 1], - [2, 0], [1, 1], [0, 2], - [3, 0], [2, 1], [1, 2], [0, 3]] - - - .. RUBRIC:: Specifying how to construct elements - - This is the list of all monomials of degree `4` which divide the - monomial `x^3y^1z^2` (a monomial being identified with its - exponent vector):: - - sage: R. = QQ[] - sage: m = [3,1,2] - sage: def term(exponents): - ....: return x^exponents[0] * y^exponents[1] * z^exponents[2] - sage: list( IntegerListsLex(4, length=len(m), ceiling=m, element_constructor=term) ) - [x^3*y, x^3*z, x^2*y*z, x^2*z^2, x*y*z^2] - - Note the use of the ``element_constructor`` option to specify how - to construct elements from a plain list. - - A variant is to specify a class for the elements. With the default - element constructor, this class should take as input the parent - ``self`` and a list. Here we want the elements to be constructed - in the class :class:`Partition`:: - - sage: IntegerListsLex(3, max_slope=0, element_class=Partition, global_options=Partitions.global_options).list() - doctest:...: DeprecationWarning: the global_options argument is - deprecated since, in general, pickling is broken; - create your own class instead - See http://trac.sagemath.org/15525 for details. - [[3], [2, 1], [1, 1, 1]] - - Note that the :class:`Partition` further assumes the existence of - an attribute ``_global_options`` in the parent, hence the use of the - ``global_options`` parameter. - - .. WARNING:: - - The protocol for specifying the element class and constructor - is subject to changes. - - ALGORITHM: - - The iteration algorithm uses a depth first search through the - prefix tree of the list of integers (see also - :ref:`section-generic-integerlistlex`). While doing so, it does - some lookahead heuristics to attempt to cut dead branches. - - In most practical use cases, most dead branches are cut. Then, - roughly speaking, the time needed to iterate through all the - elements of `S` is proportional to the number of elements, where - the proportion factor is controlled by the length `l` of the - longest element of `S`. In addition, the memory usage is also - controlled by `l`, which is to say negligible in practice. - - Still, there remains much room for efficiency improvements; see - :trac:`18055`, :trac:`18056`. - - .. NOTE:: - - The generation algorithm could in principle be extended to - deal with non-constant slope constraints and with negative - parts. - - TESTS: - - This example from the combinatorics tutorial used to fail before - :trac:`17979` because the floor conditions did not satisfy the - slope conditions:: - - sage: I = IntegerListsLex(16, min_length=2, max_slope=-1, floor=[5,3,3]) - sage: I.list() - [[13, 3], [12, 4], [11, 5], [10, 6], [9, 7], [9, 4, 3], [8, 5, 3], [8, 4, 3, 1], - [7, 6, 3], [7, 5, 4], [7, 5, 3, 1], [7, 4, 3, 2], [6, 5, 4, 1], [6, 5, 3, 2], - [6, 4, 3, 2, 1]] - - :: - - sage: Partitions(2, max_slope=-1, length=2).list() - [] - sage: list(IntegerListsLex(0, floor=ConstantFunction(1), min_slope=0)) - [[]] - sage: list(IntegerListsLex(0, floor=ConstantFunction(1), min_slope=0, max_slope=0)) - [[]] - sage: list(IntegerListsLex(0, max_length=0, floor=ConstantFunction(1), min_slope=0, max_slope=0)) - [[]] - sage: list(IntegerListsLex(0, max_length=0, floor=ConstantFunction(0), min_slope=0, max_slope=0)) - [[]] - sage: list(IntegerListsLex(0, min_part=1, min_slope=0)) - [[]] - sage: list(IntegerListsLex(1, min_part=1, min_slope=0)) - [[1]] - sage: list(IntegerListsLex(0, min_length=1, min_part=1, min_slope=0)) - [] - sage: list(IntegerListsLex(0, min_length=1, min_slope=0)) - [[0]] - sage: list(IntegerListsLex(3, max_length=2)) - [[3], [2, 1], [1, 2], [0, 3]] - sage: partitions = {"min_part": 1, "max_slope": 0} - sage: partitions_min_2 = {"floor": ConstantFunction(2), "max_slope": 0} - sage: compositions = {"min_part": 1} - sage: integer_vectors = lambda l: {"length": l} - sage: lower_monomials = lambda c: {"length": c, "floor": lambda i: c[i]} - sage: upper_monomials = lambda c: {"length": c, "ceiling": lambda i: c[i]} - sage: constraints = { "min_part":1, "min_slope": -1, "max_slope": 0} - sage: list(IntegerListsLex(6, **partitions)) - [[6], - [5, 1], - [4, 2], - [4, 1, 1], - [3, 3], - [3, 2, 1], - [3, 1, 1, 1], - [2, 2, 2], - [2, 2, 1, 1], - [2, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1]] - sage: list(IntegerListsLex(6, **constraints)) - [[6], - [3, 3], - [3, 2, 1], - [2, 2, 2], - [2, 2, 1, 1], - [2, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1]] - sage: list(IntegerListsLex(1, **partitions_min_2)) - [] - sage: list(IntegerListsLex(2, **partitions_min_2)) - [[2]] - sage: list(IntegerListsLex(3, **partitions_min_2)) - [[3]] - sage: list(IntegerListsLex(4, **partitions_min_2)) - [[4], [2, 2]] - sage: list(IntegerListsLex(5, **partitions_min_2)) - [[5], [3, 2]] - sage: list(IntegerListsLex(6, **partitions_min_2)) - [[6], [4, 2], [3, 3], [2, 2, 2]] - sage: list(IntegerListsLex(7, **partitions_min_2)) - [[7], [5, 2], [4, 3], [3, 2, 2]] - sage: list(IntegerListsLex(9, **partitions_min_2)) - [[9], [7, 2], [6, 3], [5, 4], [5, 2, 2], [4, 3, 2], [3, 3, 3], [3, 2, 2, 2]] - sage: list(IntegerListsLex(10, **partitions_min_2)) - [[10], - [8, 2], - [7, 3], - [6, 4], - [6, 2, 2], - [5, 5], - [5, 3, 2], - [4, 4, 2], - [4, 3, 3], - [4, 2, 2, 2], - [3, 3, 2, 2], - [2, 2, 2, 2, 2]] - sage: list(IntegerListsLex(4, **compositions)) - [[4], [3, 1], [2, 2], [2, 1, 1], [1, 3], [1, 2, 1], [1, 1, 2], [1, 1, 1, 1]] - sage: list(IntegerListsLex(6, min_length=1, floor=[7])) - [] - sage: L = IntegerListsLex(10**100,length=1) - sage: L.list() - [[10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]] - - Noted on :trac:`17898`:: - - sage: list(IntegerListsLex(4, min_part=1, length=3, min_slope=1)) - [] - sage: IntegerListsLex(6, ceiling=[4,2], floor=[3,3]).list() - [] - sage: IntegerListsLex(6, min_part=1, max_part=3, max_slope=-4).list() - [] - - Noted in :trac:`17548`, which are now fixed:: - - sage: IntegerListsLex(10, min_part=2, max_slope=-1).list() - [[10], [8, 2], [7, 3], [6, 4], [5, 3, 2]] - sage: IntegerListsLex(5, min_slope=1, floor=[2,1,1], max_part=2).list() - [] - sage: IntegerListsLex(4, min_slope=0, max_slope=0).list() - [[4], [2, 2], [1, 1, 1, 1]] - sage: IntegerListsLex(6, min_slope=-1, max_slope=-1).list() - [[6], [3, 2, 1]] - sage: IntegerListsLex(6, min_length=3, max_length=2, min_part=1).list() - [] - sage: I = IntegerListsLex(3, max_length=2, min_part=1) - sage: I.list() - [[3], [2, 1], [1, 2]] - sage: [1,1,1] in I - False - sage: I=IntegerListsLex(10, ceiling=[4], max_length=1, min_part=1) - sage: I.list() - [] - sage: [4,6] in I - False - sage: I = IntegerListsLex(4, min_slope=1, min_part=1, max_part=2) - sage: I.list() - [] - sage: I = IntegerListsLex(7, min_slope=1, min_part=1, max_part=4) - sage: I.list() - [[3, 4], [1, 2, 4]] - sage: I = IntegerListsLex(4, floor=[2,1], ceiling=[2,2], max_length=2, min_slope=0) - sage: I.list() - [[2, 2]] - sage: I = IntegerListsLex(10, min_part=1, max_slope=-1) - sage: I.list() - [[10], [9, 1], [8, 2], [7, 3], [7, 2, 1], [6, 4], [6, 3, 1], [5, 4, 1], - [5, 3, 2], [4, 3, 2, 1]] - - - .. RUBRIC:: TESTS from comments on :trac:`17979` - - Comment 191:: - - sage: list(IntegerListsLex(1, min_length=2, min_slope=0, max_slope=0)) - [] - - Comment 240:: - - sage: L = IntegerListsLex(min_length=2, max_part=0) - sage: L.list() - [[0, 0]] - - .. RUBRIC:: Tests on the element constructor feature and mutability - - Internally, the iterator works on a single list that is mutated - along the way. The following test makes sure that we actually make a copy of - this list before passing it to ``element_constructor`` in order to - avoid reference effects:: - - sage: from sage.misc.c3_controlled import identity - sage: P = IntegerListsLex(n=3, max_slope=0, min_part=1, element_constructor=identity) - sage: list(P) - [[3], [2, 1], [1, 1, 1]] - - Same, step by step:: - - sage: it = iter(P) - sage: a = next(it); a - [3] - sage: b = next(it); b - [2, 1] - sage: a - [3] - sage: a is b - False - - Tests from `MuPAD-Combinat `_:: - - sage: IntegerListsLex(7, min_length=2, max_length=6, floor=[0,0,2,0,0,1], ceiling=[3,2,3,2,1,2]).cardinality() - 83 - sage: IntegerListsLex(7, min_length=2, max_length=6, floor=[0,0,2,0,1,1], ceiling=[3,2,3,2,1,2]).cardinality() - 53 - sage: IntegerListsLex(5, min_length=2, max_length=6, floor=[0,0,2,0,0,0], ceiling=[2,2,2,2,2,2]).cardinality() - 30 - sage: IntegerListsLex(5, min_length=2, max_length=6, floor=[0,0,1,1,0,0], ceiling=[2,2,2,2,2,2]).cardinality() - 43 - - sage: IntegerListsLex(0, min_length=0, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).first() - [] - - sage: IntegerListsLex(0, min_length=1, max_length=7, floor=[0,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).first() - [0] - sage: IntegerListsLex(0, min_length=1, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).cardinality() - 0 - - sage: IntegerListsLex(2, min_length=0, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() # Was [1,1], due to slightly different specs - [2] - sage: IntegerListsLex(1, min_length=1, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() - [1] - sage: IntegerListsLex(1, min_length=2, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).cardinality() - 0 - sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() - [1, 1, 0, 0, 0] - sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,0,1], ceiling=[4,3,2,3,2,2,1]).first() - [1, 1, 0, 0, 0] - sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).cardinality() - 0 - - sage: IntegerListsLex(4, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).cardinality() - 0 - sage: IntegerListsLex(5, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() - [2, 1, 2] - sage: IntegerListsLex(6, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() - [3, 1, 2] - sage: IntegerListsLex(12, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() - [3, 1, 2, 3, 2, 1] - sage: IntegerListsLex(13, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() - [3, 1, 2, 3, 2, 2] - sage: IntegerListsLex(14, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).cardinality() - 0 - - This used to hang (see comment 389 and fix in :meth:`Envelope.__init__`):: - - sage: IntegerListsLex(7, max_part=0, ceiling=lambda i:i, check=False).list() - [] - """ - __metaclass__ = ClasscallMetaclass - - @staticmethod - def __classcall_private__(cls, n=None, **kwargs): - r""" - Return a disjoint union if ``n`` is a list or iterable. - - TESTS: - - Specifying a list or iterable as argument is deprecated:: - - sage: IntegerListsLex([2,2], length=2).list() - doctest:...: DeprecationWarning: Calling IntegerListsLex with n an iterable is deprecated. Please use DisjointUnionEnumeratedSets or the min_sum and max_sum arguments instead - See http://trac.sagemath.org/17979 for details. - [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] - sage: IntegerListsLex(NN, max_length=3) - Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} - """ - import collections - if isinstance(n, collections.Iterable): - from sage.misc.superseded import deprecation - deprecation(17979, 'Calling IntegerListsLex with n an iterable is deprecated. Please use DisjointUnionEnumeratedSets or the min_sum and max_sum arguments instead') - from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets - return DisjointUnionEnumeratedSets(Family(n, lambda i: IntegerListsLex(i, **kwargs))) - else: - return typecall(cls, n=n, **kwargs) - - def __init__(self, - n=None, - length=None, min_length=0, max_length=Infinity, - floor=None, ceiling=None, - min_part=0, max_part=Infinity, - min_slope=-Infinity, max_slope=Infinity, - min_sum=0, max_sum=Infinity, - name=None, - category=None, - element_constructor=None, element_class=None, - global_options=None, - check=True): - """ - Initialize ``self``. - - TESTS:: - - sage: C = IntegerListsLex(2, length=3) - sage: C == loads(dumps(C)) - True - sage: C == loads(dumps(C)) # this did fail at some point, really! - True - sage: C is loads(dumps(C)) # todo: not implemented - True - sage: C.cardinality().parent() is ZZ - True - sage: TestSuite(C).run() - - sage: IntegerListsLex(min_sum=Infinity).list() - Traceback (most recent call last): - ... - TypeError: unable to coerce to an integer - sage: IntegerListsLex(min_sum=1.4).list() - Traceback (most recent call last): - ... - TypeError: Attempt to coerce non-integral RealNumber to Integer - """ - if category is None: - category = EnumeratedSets().Finite() - - self._check = check - - if n is not None: - min_sum = n - max_sum = n - self._min_sum = ZZ(min_sum) - self._max_sum = ZZ(max_sum) if max_sum != Infinity else Infinity - - if length is not None: - min_length = length - max_length = length - self._min_length = max(ZZ(min_length), 0) - self._max_length = ZZ(max_length) if max_length != Infinity else Infinity - - self._min_slope = ZZ(min_slope) if min_slope != -Infinity else -Infinity - self._max_slope = ZZ(max_slope) if max_slope != Infinity else Infinity - - self._min_part = ZZ(min_part) - if self._min_part < 0: - raise NotImplementedError("strictly negative min_part") - self._max_part = ZZ(max_part) if max_part != Infinity else Infinity - - # self._floor_or_ceiling_is_function will be set to ``True`` - # if a function is given as input for floor or ceiling; in - # this case a warning will be emitted, unless the user sets - # check=False. See the documentation. - self._floor_or_ceiling_is_function = False - if floor is None: - floor = 0 - elif isinstance(floor, (list, tuple)): - floor = tuple(ZZ(i) for i in floor) - if not all(i >= 0 for i in floor): - raise NotImplementedError("negative parts in floor={}".format(floor)) - elif callable(floor): - self._floor_or_ceiling_is_function = True - else: - raise TypeError("floor should be a list, tuple, or function") - self._floor = Envelope(floor, sign=-1, - min_part= self._min_part, max_part= self._max_part, - min_slope= self._min_slope, max_slope=self._max_slope, - min_length=self._min_length) - - if ceiling is None: - ceiling = Infinity - elif isinstance(ceiling, (list, tuple)): - ceiling = tuple(ZZ(i) if i != Infinity else Infinity - for i in ceiling) - if not all(i >= 0 for i in ceiling): - raise NotImplementedError("negative parts in ceiling={}".format(ceiling)) - elif callable(ceiling): - self._floor_or_ceiling_is_function = True - else: - raise ValueError("Unable to parse value of parameter ceiling") - self._ceiling = Envelope(ceiling, sign=1, - min_part= self._min_part, max_part= self._max_part, - min_slope= self._min_slope, max_slope=self._max_slope, - min_length=self._min_length) - - if name is not None: - self.rename(name) - - if self._floor_or_ceiling_is_function and self._check: - from warnings import warn - warn(""" -A function has been given as input of the floor=[...] or ceiling=[...] -arguments of IntegerListsLex. Please see the documentation for the caveats. -If you know what you are doing, you can set check=False to skip this warning.""") - - # Customization of the class and constructor for the elements - - # We set the following attribute to True if the element - # constructor is known to be safe and does not claim ownership - # on the input list. In this case, we can save a copy in Iter.next. - self._element_constructor_is_copy_safe = False - if element_class is not None: - self.Element = element_class - if element_constructor is not None: - if element_constructor is list or element_constructor is tuple: - self._element_constructor_is_copy_safe = True - elif issubclass(self.Element, ClonableArray): - # Not all element class support check=False - element_constructor = self._element_constructor_nocheck - self._element_constructor_is_copy_safe = True - if global_options is not None: - from sage.misc.superseded import deprecation - deprecation(15525, 'the global_options argument is deprecated since, in general,' - ' pickling is broken; create your own class instead') - self.global_options = global_options - - Parent.__init__(self, element_constructor=element_constructor, - category=category) - - @cached_method - def _check_finiteness(self): - """ - Check that the constraints define a finite set. - - As mentioned in the description of this class, being finite is - almost equivalent to being inverse lexicographic iterable, - which is what we really care about. - - This set is finite if and only if: - - #. For each `i` such that there exists a list of length at - least `i+1` satisfying the constraints, there exists a - direct or indirect upper bound on the `i`-th part, that - is ``self._ceiling(i)`` is finite. - - #. There exists a global upper bound on the length. - - Failures for 1. are detected and reported later, during the - iteration, namely the first time a prefix including the `i`-th - part is explored. - - This method therefore focuses on 2., namely trying to prove - the existence of an upper bound on the length. It may fail - to do so even when the set is actually finite. - - OUTPUT: - - ``None`` if this method finds a proof that there - exists an upper bound on the length. Otherwise a - ``ValueError`` is raised. - - EXAMPLES:: - - sage: L = IntegerListsLex(4, max_length=4) - sage: L._check_finiteness() - - The following example is infinite:: - - sage: L = IntegerListsLex(4) - sage: L._check_finiteness() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - Indeed:: - - sage: it = iter(IntegerListsLex(4, check=False)) - sage: for _ in range(10): print next(it) - [4] - [3, 1] - [3, 0, 1] - [3, 0, 0, 1] - [3, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 0, 0, 0, 1] - [3, 0, 0, 0, 0, 0, 0, 0, 0, 1] - - Unless ``check=False``, :meth:`_check_finiteness` is called as - soon as an iteration is attempted:: - - sage: iter(L) - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - Some other infinite examples:: - - sage: L = IntegerListsLex(ceiling=[0], min_slope=1, max_slope=2) - sage: L.list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - sage: L = IntegerListsLex(ceiling=[0], min_slope=1, max_slope=1) - sage: L.list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - sage: IntegerListsLex(ceiling=[0], min_slope=1, max_slope=1).list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - The following example is actually finite, but not detected as such:: - - sage: IntegerListsLex(7, floor=[4], max_part=4, min_slope=-1).list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - This is sad because the following equivalent example works just fine:: - - sage: IntegerListsLex(7, floor=[4,3], max_part=4, min_slope=-1).list() - [[4, 3]] - - Detecting this properly would require some deeper lookahead, - and the difficulty is to decide how far this lookahead should - search. Until this is fixed, one can disable the checks:: - - sage: IntegerListsLex(7, floor=[4], max_part=4, min_slope=-1, check=False).list() - [[4, 3]] - - If the ceiling or floor is a function, it is much more likely - that a finite set will not be detected as such:: - - sage: IntegerListsLex(ceiling=lambda i: max(3-i,0))._check_finiteness() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - sage: IntegerListsLex(7, ceiling=lambda i:0).list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - The next example shows a case that is finite because we remove - trailing zeroes:: - - sage: list(IntegerListsLex(ceiling=[0], max_slope=0)) - [[]] - sage: L = IntegerListsLex(ceiling=[1], min_slope=1, max_slope=1) - sage: L.list() - Traceback (most recent call last): - ... - ValueError: Could not prove that the specified constraints yield a finite set - - In the next examples, there is either no solution, or the region - is bounded:: - - sage: IntegerListsLex(min_sum=10, max_sum=5).list() - [] - sage: IntegerListsLex(max_part=1, min_slope=10).list() - [[1], []] - sage: IntegerListsLex(max_part=100, min_slope=10).first() - [100] - sage: IntegerListsLex(ceiling=[1,Infinity], max_part=2, min_slope=1).list() - [[1, 2], [1], [0, 2], [0, 1, 2], [0, 1], []] - sage: IntegerListsLex(min_sum=1, floor=[1,2], max_part=1).list() - [[1]] - - sage: IntegerListsLex(min_length=2, max_length=1).list() - [] - sage: IntegerListsLex(min_length=-2, max_length=-1).list() - [] - sage: IntegerListsLex(min_length=-1, max_length=-2).list() - [] - sage: IntegerListsLex(min_length=2, max_slope=0, min_slope=1).list() - [] - sage: IntegerListsLex(min_part=2, max_part=1).list() - [[]] - - sage: IntegerListsLex(floor=[0,2], ceiling=[3,1]).list() - [[3], [2], [1], []] - sage: IntegerListsLex(7, ceiling=[2], floor=[4]).list() - [] - sage: IntegerListsLex(7, max_part=0).list() - [] - sage: IntegerListsLex(5, max_part=0, min_slope=0).list() - [] - sage: IntegerListsLex(max_part=0).list() - [[]] - sage: IntegerListsLex(max_sum=1, min_sum=4, min_slope=0).list() - [] - """ - # Trivial cases - if self._max_length < Infinity: - return - if self._max_sum < self._min_sum: - return - if self._min_slope > self._max_slope: - return - if self._max_slope < 0: - return - if self._ceiling.limit() < self._floor.limit(): - return - if self._ceiling.limit() == 0: - # This assumes no trailing zeroes - return - if self._min_slope > 0 and self._ceiling.limit() < Infinity: - return - - # Compute a lower bound on the sum of floor(i) for i=1 to infinity - if self._floor.limit() > 0 or self._min_slope > 0: - floor_sum_lower_bound = Infinity - elif self._floor.limit_start() < Infinity: - floor_sum_lower_bound = sum(self._floor(i) for i in range(self._floor.limit_start())) - else: - floor_sum_lower_bound = 0 - if floor_sum_lower_bound > 0 and self._min_slope >= 0: - floor_sum_lower_bound = Infinity - - if self._max_sum < floor_sum_lower_bound: - return - if self._max_sum == floor_sum_lower_bound and self._max_sum < Infinity: - # This assumes no trailing zeroes - return - - # Variant on ceiling.limit() ==0 where we actually discover that the ceiling limit is 0 - if self._max_slope == 0 and \ - (self._max_sum < Infinity or - (self._ceiling.limit_start() < Infinity and - any(self._ceiling(i) == 0 for i in range(self._ceiling.limit_start()+1)))): - return - - limit_start = max(self._ceiling.limit_start(), self._floor.limit_start()) - if limit_start < Infinity: - for i in range(limit_start+1): - if self._ceiling(i) < self._floor(i): - return - - raise ValueError("Could not prove that the specified constraints yield a finite set") - - - @staticmethod - def _list_function(l, default): - r""" - Generate a function on the nonnegative integers from input. - - This method generates a function on the nonnegative integers - whose values are taken from ``l`` when the input is a valid index - in the list ``l``, and has a default value ``default`` otherwise. - - INPUT: - - - ``l`` -- a list to use as a source of values - - - ``default`` -- a default value to use for indices outside of the list +Deprecated integer list module - OUTPUT: +TESTS:: - A function on the nonnegative integers. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: f = C._list_function([1,2], Infinity) - sage: f(1) - 2 - sage: f(3) - +Infinity - """ - return lambda i: l[i] if i < len(l) else default - - def __eq__(self, other): - r""" - Return whether ``self == other``. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: D = IntegerListsLex(2, length=3); L = D.list(); - sage: E = IntegerListsLex(2, min_length=3) - sage: F = IntegerListsLex(2, length=3, element_constructor=list) - sage: G = IntegerListsLex(4, length=3) - sage: C == C - True - sage: C == D - True - sage: C == E - False - sage: C == F - False - sage: C == None - False - sage: C == G - False - - This is a minimal implementation enabling pickling tests. It - is safe, but one would want the two following objects to be - detected as equal:: - - sage: C = IntegerListsLex(2, ceiling=[1,1,1]) - sage: D = IntegerListsLex(2, ceiling=[1,1,1]) - sage: C == D - False - - TESTS: - - This used to fail due to poor equality testing. See - :trac:`17979`, comment 433:: - - sage: DisjointUnionEnumeratedSets(Family([2,2], - ....: lambda n: IntegerListsLex(n, length=2))).list() - [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] - sage: DisjointUnionEnumeratedSets(Family([2,2], - ....: lambda n: IntegerListsLex(n, length=1))).list() - [[2], [2]] - """ - if self.__class__ != other.__class__: - return False - for key in ["_min_length", "_max_length", "_floor", "_ceiling", "_min_part", "_max_part", "_min_sum", "_max_sum", "Element"]: - if getattr(self, key) != getattr(other, key): - return False - a = self._element_constructor - b = other._element_constructor - if ismethod(a): - a = a.__func__ - if ismethod(b): - b = b.__func__ - return a == b - - def __ne__(self, other): - r""" - Return whether ``self != other``. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: D = IntegerListsLex(2, length=3); L = D.list(); - sage: E = IntegerListsLex(2, max_length=3) - sage: C != D - False - sage: C != E - True - """ - return not self == other - - def _repr_(self): - """ - Return the name of this enumerated set. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: C # indirect doctest - Integer lists of sum 2 satisfying certain constraints - - sage: C = IntegerListsLex(2, length=3, name="A given name") - sage: C - A given name - """ - if self._min_sum == self._max_sum: - return "Integer lists of sum {} satisfying certain constraints".format(self._min_sum) - elif self._max_sum == Infinity: - if self._min_sum == 0: - return "Integer lists with arbitrary sum satisfying certain constraints" - else: - return "Integer lists of sum at least {} satisfying certain constraints".format(self._min_sum) - else: - return "Integer lists of sum between {} and {} satisfying certain constraints".format(self._min_sum,self._max_sum) - - def __contains__(self, comp): - """ - Return ``True`` if ``comp`` meets the constraints imposed by the arguments. - - EXAMPLES:: - - sage: C = IntegerListsLex(n=2, max_length=3, min_slope=0) - sage: all([l in C for l in C]) - True - """ - if len(comp) < self._min_length or len(comp) > self._max_length: - return False - n = sum(comp) - if n < self._min_sum or n > self._max_sum: - return False - for i in range(len(comp)): - if comp[i] < self._floor(i): - return False - if comp[i] > self._ceiling(i): - return False - for i in range(len(comp)-1): - slope = comp[i+1] - comp[i] - if slope < self._min_slope or slope > self._max_slope: - return False - return True - - - def __iter__(self): - """ - Return an iterator for the elements of ``self``. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: list(C) # indirect doctest - [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] - """ - if self._check: - self._check_finiteness() - return IntegerListsLexIter(self) - - def _element_constructor_nocheck(self, l): - r""" - A variant of the standard element constructor that passes - ``check=False`` to the element class. - - EXAMPLES:: - - sage: L = IntegerListsLex(4, max_slope=0) - sage: L._element_constructor_nocheck([1,2,3]) - [1, 2, 3] - - When relevant, this is assigned to - ``self._element_constructor`` by :meth:`__init__`, to avoid - overhead when constructing elements from trusted data in the - iterator:: - - sage: L._element_constructor - - sage: L._element_constructor([1,2,3]) - [1, 2, 3] - """ - return self.element_class(self, l, check=False) - - class Element(ClonableArray): - """ - Element class for :class:`IntegerListsLex`. - """ - def check(self): - """ - Check to make sure this is a valid element in its - :class:`IntegerListsLex` parent. - - EXAMPLES:: - - sage: C = IntegerListsLex(4) - sage: C([4]).check() - True - sage: C([5]).check() # not implemented - False - """ - return self.parent().__contains__(self) - - -# Constants for IntegerListsLexIter._next_state -LOOKAHEAD = 5 -PUSH = 4 -ME = 3 -DECREASE = 2 -POP = 1 -STOP = 0 - -class IntegerListsLexIter: - r""" - Iterator class for IntegerListsLex. - - Let ``T`` be the prefix tree of all lists of nonnegative - integers that satisfy all constraints except possibly for - ``min_length`` and ``min_sum``; let the children of a list - be sorted decreasingly according to their last part. - - The iterator is based on a depth-first search exploration of a - subtree of this tree, trying to cut branches that do not - contain a valid list. Each call of ``next`` iterates through - the nodes of this tree until it finds a valid list to return. - - Here are the attributes describing the current state of the - iterator, and their invariants: - - - ``_parent`` -- the :class:`IntegerListsLex` object this is - iterating on; - - - ``_current_list`` -- the list corresponding to the current - node of the tree; - - - ``_j`` -- the index of the last element of ``_current_list``: - ``self._j == len(self._current_list) - 1``; - - - ``_current_sum`` -- the sum of the parts of ``_current_list``; - - - ``_search_ranges`` -- a list of same length as - ``_current_list``: the range for each part. - - Furthermore, we assume that there is no obvious contradiction - in the contraints: - - - ``self._parent._min_length <= self._parent._max_length``; - - ``self._parent._min_slope <= self._parent._max_slope`` - unless ``self._parent._min_length <= 1``. - - Along this iteration, ``next`` switches between the following - states: - - - LOOKAHEAD: determine whether the current list could be a - prefix of a valid list; - - PUSH: go deeper into the prefix tree by appending the - largest possible part to the current list; - - ME: check whether the current list is valid and if yes return it - - DECREASE: decrease the last part; - - POP: pop the last part of the current list; - - STOP: the iteration is finished. - - The attribute ``_next_state`` contains the next state ``next`` - should enter in. - """ - def __init__(self, parent): - """ - TESTS:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: I = IntegerListsLexIter(C) - sage: I._search_ranges - [] - sage: I._current_list - [] - sage: I._j - -1 - sage: I._current_sum - 0 - """ - self._parent = parent - - self._search_ranges = [] - self._current_list = [] - self._j = -1 # index of last element of _current_list - self._current_sum = 0 # sum of parts in _current_list - - # Make sure that some invariants are respected in the iterator - if parent._min_length <= parent._max_length and \ - (parent._min_slope <= parent._max_slope or parent._min_length <= 1): - self._next_state = PUSH - else: - self._next_state = STOP - - def __iter__(self): - """ - Return ``self`` as per the iterator protocol. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: it = IntegerListsLexIter(C) - sage: it.__iter__() is it - True - """ - return self - - def _push_search(self): - """ - Push search forward, resetting attributes. - - The push may fail if it is discovered that - ``self._current_list`` cannot be extended in a valid way. - - OUTPUT: a boolean: whether the push succeeded - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: I = C.__iter__() - sage: I._j - -1 - sage: I._search_ranges - [] - sage: I._current_list - [] - sage: I._current_sum - 0 - sage: I._push_search() - True - sage: I._j - 0 - sage: I._search_ranges - [(0, 2)] - sage: I._current_list - [2] - sage: I._current_sum - 2 - sage: I._push_search() - True - sage: I._j - 1 - sage: I._search_ranges - [(0, 2), (0, 0)] - sage: I._current_list - [2, 0] - sage: I._current_sum - 2 - """ - p = self._parent - max_sum = p._max_sum - min_length = p._min_length - max_length = p._max_length - if self._j+1 >= max_length: - return False - if self._j+1 >= min_length and self._current_sum == max_sum: - # Cannot add trailing zeroes - return False - - if self._j >= 0: - prev = self._current_list[self._j] - else: - prev = None - interval = self._m_interval(self._j+1, self._parent._max_sum - self._current_sum, prev) - if interval[0] > interval[1]: - return False - - self._j += 1 - m = interval[1] - self._search_ranges.append(interval) - self._current_list.append(m) - self._current_sum += m - return True - - def _pop_search(self): - """ - Go back in search tree. Resetting attributes. - - EXAMPLES:: - - sage: C = IntegerListsLex(2, length=3) - sage: I = C.__iter__() - sage: I._push_search() - True - sage: I._j - 0 - sage: I._search_ranges - [(0, 2)] - sage: I._current_sum - 2 - sage: I._current_list - [2] - sage: I._pop_search() - sage: I._j - -1 - sage: I._search_ranges - [] - sage: I._current_sum - 0 - sage: I._current_list - [] - """ - if self._j >= 0: # TODO: get rid of this condition - self._j -= 1 - self._search_ranges.pop() - self._current_sum -= self._current_list[-1] - self._current_list.pop() - - def next(self): - r""" - Return the next element in the iteration. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: I = IntegerListsLexIter(C) - sage: next(I) - [2, 0, 0] - sage: next(I) - [1, 1, 0] - """ - p = self._parent - min_sum = p._min_sum - max_length = p._max_length - search_ranges = self._search_ranges - - while True: - assert self._j == len(self._current_list) - 1 - assert self._j == len(self._search_ranges) - 1 - - # LOOK AHEAD - if self._next_state == LOOKAHEAD: - if self._lookahead(): - self._next_state = PUSH - else: - # We should reuse information about the - # reasons for this failure, to avoid when - # possible retrying with smaller values. - # We just do a special case for now: - if self._j + 1 == max_length and self._current_sum < min_sum: - self._next_state = POP - else: - self._next_state = DECREASE - - # PUSH - if self._next_state == PUSH: - if self._push_search(): - self._next_state = LOOKAHEAD - continue - self._next_state = ME - - # ME - if self._next_state == ME: - if self._j == -1: - self._next_state = STOP - else: - self._next_state = DECREASE - if self._internal_list_valid(): - return p._element_constructor( - self._current_list - if p._element_constructor_is_copy_safe - else self._current_list[:]) - - # DECREASE - if self._next_state == DECREASE: - self._current_list[-1] -= 1 - self._current_sum -= 1 - if self._current_list[-1] >= search_ranges[self._j][0]: - self._next_state = LOOKAHEAD - continue - self._next_state = POP - - # POP - if self._next_state == POP: - self._pop_search() - self._next_state = ME - continue - - # STOP - if self._next_state == STOP: - raise StopIteration() - - assert False - - def _internal_list_valid(self): - """ - Return whether the current list in the iteration variable ``self._current_list`` is a valid list. - - This method checks whether the sum of the parts in ``self._current_list`` - is in the right range, whether its length is in the - required range, and whether there are trailing zeroes. It does not check all of the - necessary conditions to verify that an arbitrary list satisfies the - constraints from the corresponding ``IntegerListsLex`` object, and should - not be used except internally in the iterator class. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: I = IntegerListsLexIter(C) - sage: I._current_list - [] - sage: I._internal_list_valid() - False - sage: next(I) - [2, 0, 0] - sage: I._current_list - [2, 0, 0] - sage: I._internal_list_valid() - True - """ - p = self._parent - mu = self._current_list - nu = self._current_sum - l = self._j + 1 - good_sum = (nu >= p._min_sum and nu <= p._max_sum) - good_length = (l >= p._min_length and l <= p._max_length) - no_trailing_zeros = (l <= max(p._min_length,0) or mu[-1] != 0) - return good_sum and good_length and no_trailing_zeros - - def _m_interval(self, i, max_sum, prev=None): - r""" - Return coarse lower and upper bounds for the part ``m`` at position ``i``. - - INPUT: - - - ``i`` -- a nonnegative integer (position) - - - ``max_sum`` -- a nonnegative integer or ``+oo`` - - - ``prev`` -- a nonnegative integer or ``None`` - - Return coarse lower and upper bounds for the value ``m`` - of the part at position ``i`` so that there could exists - some list suffix `v_i,\ldots,v_k` of sum bounded by - ``max_sum`` and satisfying the floor and upper bound - constraints. If ``prev`` is specified, then the slope - conditions between ``v[i-1]=prev`` and ``v[i]=m`` should - also be satisfied. - - Additionally, this raises an error if it can be detected - that some part is neither directly nor indirectly bounded - above, which implies that the constraints possibly do not allow for - an inverse lexicographic iterator. - - OUTPUT: - - A tuple of two integers ``(lower_bound, upper_bound)``. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsLexIter - sage: C = IntegerListsLex(2, length=3) - sage: I = IntegerListsLexIter(C) - sage: I._m_interval(1,2) - (0, 2) - - The second part is not bounded above, hence we can not - iterate lexicographically through all the elements:: - - sage: IntegerListsLex(ceiling=[2,infinity,3], max_length=3).first() - Traceback (most recent call last): - ... - ValueError: infinite upper bound for values of m - - Same here:: - - sage: IntegerListsLex(ceiling=[2,infinity,2], max_length=3, min_slope=-1).cardinality() - Traceback (most recent call last): - ... - ValueError: infinite upper bound for values of m - - In the following examples however, all parts are - indirectly bounded above:: - - sage: IntegerListsLex(ceiling=[2,infinity,2], length=3, min_slope=-1).cardinality() - 24 - sage: IntegerListsLex(ceiling=[2,infinity,2], max_length=3, max_slope=1).cardinality() - 24 - - sage: IntegerListsLex(max_part=2, max_length=3).cardinality() - 27 - sage: IntegerListsLex(3, max_length=3).cardinality() # parts bounded by n - 10 - sage: IntegerListsLex(max_length=0, min_length=1).list() # no part! - [] - sage: IntegerListsLex(length=0).list() # no part! - [[]] - """ - p = self._parent - - lower_bound = max(0, p._floor(i)) - upper_bound = min(max_sum, p._ceiling(i)) - if prev != None: - lower_bound = max(lower_bound, prev + p._min_slope) - upper_bound = min(upper_bound, prev + p._max_slope) - - ## check for infinite upper bound, in case max_sum is infinite - if p._check and upper_bound == Infinity: - # This assumes that there exists a valid list (which - # is not yet always guaranteed). Then we just - # discovered that part 'i' of this list can be made as - # large as desired, which implies that `self._parent` - # cannot be iterated in inverse lexicographic order - raise ValueError("infinite upper bound for values of m") - - return (lower_bound, upper_bound) - - def _lookahead(self): - r""" - Return whether the current list can possibly be a prefix of a valid list. - - OUTPUT: ``False`` if it is guaranteed that the current - list cannot be a prefix of a valid list and ``True`` - otherwise. - - EXAMPLES:: - - sage: it = iter(IntegerListsLex(length=3, min_sum=2, max_sum=2)) - sage: it._current_list = [0,1] # don't do this at home, kids - sage: it._current_sum = 1 - sage: it._j = 1 - sage: it._lookahead() - True - - sage: it = iter(IntegerListsLex(length=3, min_sum=3, max_sum=2)) - sage: it._current_list = [0,1] - sage: it._current_sum = 1 - sage: it._j = 1 - sage: it._lookahead() - False - - sage: it = iter(IntegerListsLex(min_length=2, max_part=0)) - sage: it._current_list = [0] - sage: it._current_sum = 0 - sage: it._j = 0 - sage: it._lookahead() - True - sage: it._current_list = [0, 0] - sage: it._j = 1 - sage: it._lookahead() - True - sage: it._current_list = [0, 0, 0] - sage: it._j = 2 - sage: it._lookahead() - False - - sage: n = 10**100 - sage: it = iter(IntegerListsLex(n, length=1)) - sage: it._current_list = [n-1] - sage: it._current_sum = n-1 - sage: it._j = 0 - sage: it._lookahead() - False - - sage: it = iter(IntegerListsLex(n=3, min_part=2, min_sum=3, max_sum=3)) - sage: it._current_list = [2] - sage: it._current_sum = 2 - sage: it._j = 0 - sage: it._lookahead() - False - - ALGORITHM: - - Let ``j=self._j`` be the position of the last part `m` of - ``self._current_list``. The current algorithm computes, - for `k=j,j+1,\ldots`, a lower bound `l_k` and an upper - bound `u_k` for `v_0+\dots+v_k`, and stops if none of the - invervals `[l_k, u_k]` intersect ``[min_sum, max_sum]``. - - The lower bound `l_k` is given by the area below - `v_0,\dots,v_{j-1}` prolongated by the lower envelope - between `j` and `k` and starting at `m`. The upper bound - `u_k` is given similarly using the upper envelope. - - The complexity of this algorithm is bounded above by - ``O(max_length)``. When ``max_length=oo``, the algorithm - is guaranteed to terminate, unless ``floor`` is a function - which is eventually constant with value `0`, or which - reaches the value `0` while ``max_slope=0``. - - Indeed, the lower bound `l_k` is increasing with `k`; in - fact it is strictly increasing, unless the local lower bound - at `k` is `0`. Furthermore as soon as ``l_k >= min_sum``, - we can conclude; we can also conclude if we know that the - floor is eventually constant with value `0`, or there is a - local lower bound at `k` is `0` and ``max_slope=0``. - - .. RUBRIC:: Room for improvement - - Improved prediction: the lower bound `l_k` does not take - the slope conditions into account, except for those imposed - by the value `m` at `j`. Similarly for `u_k`. - - Improved speed: given that `l_k` is increasing with `k`, - possibly some dichotomy could be used to search for `k`, - with appropriate caching / fast calculation of the partial - sums. Also, some of the information gained at depth `j` - could be reused at depth `j+1`. - - TESTS:: - - sage: it = iter(IntegerListsLex(1, min_length=2, min_slope=0, max_slope=0, min_sum=1, max_sum=1)) - sage: it._current_list = [0] - sage: it._current_sum = 0 - sage: it._j = 0 - sage: it._lookahead() - False - """ - # Check code for various termination conditions. Possible cases: - # 0. interval [lower, upper] intersects interval [min_sum, max_sum] -- terminate True - # 1. lower sum surpasses max_sum -- terminate False - # 2. iteration surpasses max_length -- terminate False - # 3. upper envelope is smaller than lower envelope -- terminate False - # 4. max_slope <= 0 -- terminate False after upper passes 0 - # 5. ceiling_limit == 0 -- terminate False after reaching larger limit point - # 6. (uncomputable) ceiling function == 0 for all but finitely many input values -- terminate False after reaching (unknown) limit point -- currently hangs - - m = self._current_list[-1] - j = self._j - min_sum = self._parent._min_sum - (self._current_sum-m) - max_sum = self._parent._max_sum - (self._current_sum-m) - - if min_sum > max_sum: - return False - - p = self._parent - - # Beware that without slope conditions, the functions below - # currently forget about the value m at k! - lower_envelope = self._parent._floor.adapt(m,j) - upper_envelope = self._parent._ceiling.adapt(m,j) - lower = 0 # The lower bound `l_k` - upper = 0 # The upper bound `u_k` - - assert j >= 0 - # get to smallest valid number of parts - for k in range(j, p._min_length-1): - # We are looking at lists `v_j,...,v_k` - lo = m if k == j else lower_envelope(k) - up = m if k == j else upper_envelope(k) - if lo > up: - return False - lower += lo - upper += up - - if j < p._min_length and min_sum <= upper and lower <= max_sum: - # There could exist a valid list `v_j,\dots,v_{min_length-1}` - return True - - k = max(p._min_length-1,j) - # Check if any of the intervals intersect the target interval - while k < p._max_length: - lo = m if k == j else lower_envelope(k) - up = m if k == j else upper_envelope(k) - if lo > up: - # There exists no valid list of length >= k - return False - lower += lo - upper += up - assert lower <= upper - - if lower > max_sum: - # There cannot exist a valid list `v_j,\dots,v_l` with l>=k - return False - - if (p._max_slope <= 0 and up <= 0) or \ - (p._ceiling.limit() == 0 and k > p._ceiling.limit_start()): - # This implies v_l=0 for l>=k: that is we would be generating - # a list with trailing zeroes - return False - - if min_sum <= upper and lower <= max_sum: - # There could exist a valid list `v_j,\dots,v_k` - return True - - k += 1 - - return False - - -class Envelope(object): - """ - The (currently approximated) upper (lower) envelope of a function - under the specified constraints. - - INPUT: - - - ``f`` -- a function, list, or tuple; if ``f`` is a list, it is - considered as the function ``f(i)=f[i]``, completed for larger - `i` with ``f(i)=max_part``. - - - ``min_part``, ``max_part``, ``min_slope``, ``max_slope``, ... - as for :class:`IntegerListsLex` (please consult for details). - - - ``sign`` -- (+1 or -1) multiply the input values with ``sign`` - and multiply the output with ``sign``. Setting this to `-1` can - be used to implement a lower envelope. - - The *upper envelope* `U(f)` of `f` is the (pointwise) largest - function which is bounded above by `f` and satisfies the - ``max_part`` and ``max_slope`` conditions. Furthermore, for - ``i,i+1 inf, - '_f_limit_start': 0, - '_max_part': -3, - '_max_slope': inf, - '_min_slope': 1, - '_precomputed': [-6, -5, -4, -3], - '_sign': -1} - sage: TestSuite(f).run(skip="_test_pickling") - sage: Envelope(3, sign=1/3, max_slope=-1, min_length=4) - Traceback (most recent call last): - ... - TypeError: no conversion of this rational to integer - sage: Envelope(3, sign=-2, max_slope=-1, min_length=4) - Traceback (most recent call last): - ... - ValueError: sign should be +1 or -1 - """ - # self._sign = sign for the output values (the sign change for - # f is handled here in __init__) - self._sign = ZZ(sign) - if self._sign == 1: - self._max_part = max_part - self._min_slope = min_slope - self._max_slope = max_slope - if max_part == 0: - # This uses that all entries are nonnegative. - # This is not for speed optimization but for - # setting the limit start and avoid hangs. - # See #17979: comment 389 - f = 0 - elif self._sign == -1: - self._max_part = -min_part - self._min_slope = -max_slope - self._max_slope = -min_slope - else: - raise ValueError("sign should be +1 or -1") - - # Handle different types of f and multiply f with sign - if f == Infinity or f == -Infinity or f in ZZ: - limit_start = 0 - self._max_part = min(self._sign * f, self._max_part) - f = ConstantFunction(Infinity) - elif isinstance(f, (list, tuple)): - limit_start = len(f) - f_tab = [self._sign * i for i in f] - f = lambda k: f_tab[k] if k < len(f_tab) else Infinity - else: - g = f - f = lambda k: self._sign * g(k) - # At this point, this is not really used - limit_start = Infinity - - self._f = f - # For i >= limit_start, f is constant - # This does not necessarily means that self is constant! - self._f_limit_start = limit_start - self._precomputed = [] - - if min_length > 0: - self(min_length-1) - for i in range(min_length-1,0,-1): - self._precomputed[i-1] = min(self._precomputed[i-1], self._precomputed[i] - self._min_slope) - - def __eq__(self, other): - r""" - Return whether ``self == other``. - - This is a minimal implementation enabling pickling tests. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: f = Envelope([3,2,2]) - sage: g = Envelope([3,2,2]) - sage: h = Envelope([3,2,2], min_part=2) - sage: f == f, f == h, f == None - (True, False, False) - - This would be desirable:: - - sage: f == g # todo: not implemented - True - """ - return self.__class__ == other.__class__ and self.__dict__ == other.__dict__ - - def __ne__(self, other): - r""" - Return whether ``self != other``. - - This is a minimal implementation enabling pickling tests. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: f = Envelope([3,2,2]) - sage: g = Envelope([3,2,2]) - sage: h = Envelope([3,2,2], min_part=2) - sage: f != f, f != h, f != None - (False, True, True) - - This would be desirable:: - - sage: f != g # todo: not implemented - False - """ - return not self == other - - def limit_start(self): - """ - Return from which `i` on the bound returned by ``limit`` holds. - - .. SEEALSO:: :meth:`limit` for the precise specifications. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: Envelope([4,1,5]).limit_start() - 3 - sage: Envelope([4,1,5], sign=-1).limit_start() - 3 - - sage: Envelope([4,1,5], max_part=2).limit_start() - 3 - - sage: Envelope(4).limit_start() - 0 - sage: Envelope(4, sign=-1).limit_start() - 0 - - sage: Envelope(lambda x: 3).limit_start() == Infinity - True - sage: Envelope(lambda x: 3, max_part=2).limit_start() == Infinity - True - - sage: Envelope(lambda x: 3, sign=-1, min_part=2).limit_start() == Infinity - True - - """ - return self._f_limit_start - - def limit(self): - """ - Return a bound on the limit of ``self``. - - OUTPUT: a nonnegative integer or `\infty` - - This returns some upper bound for the accumulation points of - this upper envelope. For a lower envelope, a lower bound is - returned instead. - - In particular this gives a bound for the value of ``self`` at - `i` for `i` large enough. Special case: for a lower envelop, - and when the limit is `\infty`, the envelope is guaranteed to - tend to `\infty` instead. - - When ``s=self.limit_start()`` is finite, this bound is - guaranteed to be valid for `i>=s`. - - Sometimes it's better to have a loose bound that starts early; - sometimes the converse holds. At this point which specific - bound and starting point is returned is not set in stone, in - order to leave room for later optimizations. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: Envelope([4,1,5]).limit() - inf - sage: Envelope([4,1,5], max_part=2).limit() - 2 - sage: Envelope([4,1,5], max_slope=0).limit() - 1 - sage: Envelope(lambda x: 3, max_part=2).limit() - 2 - - Lower envelopes:: - - sage: Envelope(lambda x: 3, min_part=2, sign=-1).limit() - 2 - sage: Envelope([4,1,5], min_slope=0, sign=-1).limit() - 5 - sage: Envelope([4,1,5], sign=-1).limit() - 0 - - .. SEEALSO:: :meth:`limit_start` - """ - if self.limit_start() < Infinity and self._max_slope <= 0: - return self(self.limit_start()) - else: - return self._max_part * self._sign - - def __call__(self, k): - """ - Return the value of this envelope at `k`. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: f = Envelope([4,1,5,3,5]) - sage: f.__call__(2) - 5 - sage: [f(i) for i in range(10)] - [4, 1, 5, 3, 5, inf, inf, inf, inf, inf] - - .. NOTE:: - - See the documentation of :class:`Envelope` for tests and - examples. - """ - if k >= len(self._precomputed): - for i in range(len(self._precomputed), k+1): - value = min(self._f(i), self._max_part) - if i>0: - value = min(value, self._precomputed[i-1] + self._max_slope) - self._precomputed.append(value) - return self._precomputed[k] * self._sign - - def adapt(self, m, j): - """ - Return this envelope adapted to an additional local constraint. - - INPUT: - - - ``m`` -- a nonnegative integer (starting value) - - - ``j`` -- a nonnegative integer (position) - - This method adapts this envelope to the additional local - constraint imposed by having a part `m` at position `j`. - Namely, this returns a function which computes, for any `i>j`, - the minimum of the ceiling function and the value restriction - given by the slope conditions. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import Envelope - sage: f = Envelope(3) - sage: g = f.adapt(1,1) - sage: g is f - True - sage: [g(i) for i in range(10)] - [3, 3, 3, 3, 3, 3, 3, 3, 3, 3] - - sage: f = Envelope(3, max_slope=1) - sage: g = f.adapt(1,1) - sage: [g(i) for i in range(10)] - [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] - - Note that, in both cases above, the adapted envelope is only - guaranteed to be valid for `i>j`! This is to leave potential - room in the future for sharing similar adapted envelopes:: - - sage: g = f.adapt(0,0) - sage: [g(i) for i in range(10)] - [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] - - sage: g = f.adapt(2,2) - sage: [g(i) for i in range(10)] - [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] - - sage: g = f.adapt(3,3) - sage: [g(i) for i in range(10)] - [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] - - Now with a lower envelope:: - - sage: f = Envelope(1, sign=-1, min_slope=-1) - sage: g = f.adapt(2,2) - sage: [g(i) for i in range(10)] - [4, 3, 2, 1, 1, 1, 1, 1, 1, 1] - sage: g = f.adapt(1,3) - sage: [g(i) for i in range(10)] - [4, 3, 2, 1, 1, 1, 1, 1, 1, 1] - """ - if self._max_slope == Infinity: - return self - m *= self._sign - m = m - j * self._max_slope - return lambda i: self._sign * min(m + i*self._max_slope, self._sign*self(i) ) - - -def IntegerListsNN(**kwds): - """ - Lists of nonnegative integers with constraints. - - This function returns the union of ``IntegerListsLex(n, **kwds)`` - where `n` ranges over all nonnegative integers. - - .. WARNING:: this function is likely to disappear in :trac:`17927`. - - EXAMPLES:: - - sage: from sage.combinat.integer_list import IntegerListsNN - sage: L = IntegerListsNN(max_length=3, max_slope=-1) - sage: L - Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} - sage: it = iter(L) - sage: for _ in range(20): - ....: print next(it) - [] - [1] - [2] - [3] - [2, 1] - [4] - [3, 1] - [5] - [4, 1] - [3, 2] - [6] - [5, 1] - [4, 2] - [3, 2, 1] - [7] - [6, 1] - [5, 2] - [4, 3] - [4, 2, 1] - [8] - """ - from sage.rings.semirings.non_negative_integer_semiring import NN - from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets - return DisjointUnionEnumeratedSets(Family(NN, lambda i: IntegerListsLex(i, **kwds))) + sage: from sage.combinat.integer_list import IntegerListsLex + sage: IntegerListsLex(3) + doctest:...: DeprecationWarning: + Importing IntegerListsLex from here is deprecated. If you need to use it, please import it directly from sage.combinat.integer_lists + See http://trac.sagemath.org/18109 for details. + Integer lists of sum 3 satisfying certain constraints +""" +from sage.misc.lazy_import import lazy_import +lazy_import('sage.combinat.integer_list_old', '*', deprecation=18109) +lazy_import('sage.combinat.integer_lists', '*', deprecation=18109) diff --git a/src/sage/combinat/integer_lists/__init__.py b/src/sage/combinat/integer_lists/__init__.py new file mode 100644 index 00000000000..e69886d8799 --- /dev/null +++ b/src/sage/combinat/integer_lists/__init__.py @@ -0,0 +1,6 @@ +from base import IntegerListsBackend, Envelope +from lists import IntegerLists +from invlex import IntegerListsLex + +from sage.structure.sage_object import register_unpickle_override +register_unpickle_override('sage.combinat.integer_list', 'IntegerListsLex', IntegerListsLex) diff --git a/src/sage/combinat/integer_lists/base.pxd b/src/sage/combinat/integer_lists/base.pxd new file mode 100644 index 00000000000..d7850331ffe --- /dev/null +++ b/src/sage/combinat/integer_lists/base.pxd @@ -0,0 +1,15 @@ +cdef class Envelope(object): + cdef readonly sign + cdef f + cdef f_limit_start + cdef list precomputed + cdef readonly max_part + cdef readonly min_slope, max_slope + +cdef class IntegerListsBackend(object): + cdef readonly min_sum, max_sum + cdef readonly min_length, max_length + cdef readonly min_part, max_part + cdef readonly min_slope, max_slope + cdef readonly Envelope floor, ceiling + cdef public dict __cached_methods # Support cached_method diff --git a/src/sage/combinat/integer_lists/base.pyx b/src/sage/combinat/integer_lists/base.pyx new file mode 100644 index 00000000000..de53d35ddd3 --- /dev/null +++ b/src/sage/combinat/integer_lists/base.pyx @@ -0,0 +1,702 @@ +r""" +Enumerated set of lists of integers with constraints: base classes + +- :class:`IntegerListsBackend`: base class for the Cython back-end of + an enumerated set of lists of integers with specified constraints. + +- :class:`Envelope`: a utility class for upper (lower) envelope of a + function under constraints. +""" + +#***************************************************************************** +# Copyright (C) 2015 Bryan Gillespie +# Nicolas M. Thiery +# Anne Schilling +# Jeroen Demeyer +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + + +from cpython.object cimport Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE +from sage.misc.constant_function import ConstantFunction +from sage.structure.element cimport RingElement +from sage.rings.integer cimport Integer + +Infinity = float('+inf') +MInfinity = float('-inf') + + +cdef class IntegerListsBackend(object): + """ + Base class for the Cython back-end of an enumerated set of lists of + integers with specified constraints. + + This base implements the basic operations, including checking for + containment using :meth:`_contains`, but not iteration. For + iteration, subclass this class and implement an ``_iter()`` method. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: L = IntegerListsBackend(6, max_slope=-1) + sage: L._contains([3,2,1]) + True + """ + def __init__(self, + n=None, length=None, *, + min_length=0, max_length=Infinity, + floor=None, ceiling=None, + min_part=0, max_part=Infinity, + min_slope=MInfinity, max_slope=Infinity, + min_sum=0, max_sum=Infinity): + """ + Initialize ``self``. + + TESTS:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: C = IntegerListsBackend(2, length=3) + sage: C = IntegerListsBackend(min_sum=1.4) + Traceback (most recent call last): + ... + TypeError: Attempt to coerce non-integral RealNumber to Integer + sage: C = IntegerListsBackend(min_sum=Infinity) + Traceback (most recent call last): + ... + TypeError: unable to coerce to an integer + """ + if n is not None: + min_sum = n + max_sum = n + self.min_sum = Integer(min_sum) if min_sum != -Infinity else -Infinity + self.max_sum = Integer(max_sum) if max_sum != Infinity else Infinity + + if length is not None: + min_length = length + max_length = length + self.min_length = Integer(max(min_length, 0)) + self.max_length = Integer(max_length) if max_length != Infinity else Infinity + + self.min_slope = Integer(min_slope) if min_slope != -Infinity else -Infinity + self.max_slope = Integer(max_slope) if max_slope != Infinity else Infinity + + self.min_part = Integer(min_part) if min_part != -Infinity else -Infinity + self.max_part = Integer(max_part) if max_part != Infinity else Infinity + + if isinstance(floor, Envelope): + self.floor = floor + else: + if floor is None: + floor = -Infinity + elif isinstance(floor, (list, tuple)): + floor = tuple(Integer(i) for i in floor) + elif callable(floor): + pass + else: + raise TypeError("floor should be a list, tuple, or function") + self.floor = Envelope(floor, sign=-1, + min_part=self.min_part, max_part=self.max_part, + min_slope=self.min_slope, max_slope=self.max_slope, + min_length=self.min_length) + + if isinstance(ceiling, Envelope): + self.ceiling = ceiling + else: + if ceiling is None: + ceiling = Infinity + elif isinstance(ceiling, (list, tuple)): + ceiling = tuple(Integer(i) if i != Infinity else Infinity + for i in ceiling) + elif callable(ceiling): + pass + else: + raise ValueError("Unable to parse value of parameter ceiling") + self.ceiling = Envelope(ceiling, sign=1, + min_part=self.min_part, max_part=self.max_part, + min_slope=self.min_slope, max_slope=self.max_slope, + min_length=self.min_length) + + def __richcmp__(self, other, int op): + r""" + Basic comparison function, supporting only checking for + equality. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3).backend + sage: D = IntegerListsLex(2, length=3).backend; L = list(D._iter()) + sage: E = IntegerListsLex(2, min_length=3).backend + sage: G = IntegerListsLex(4, length=3).backend + sage: C >= C + True + sage: C == D + True + sage: C != D + False + sage: C == E + False + sage: C != E + True + sage: C == None + False + sage: C == G + False + sage: C <= G + Traceback (most recent call last): + ... + TypeError: IntegerListsBackend can only be compared for equality + """ + cdef IntegerListsBackend left = self + cdef IntegerListsBackend right = other + equal = (type(left) is type(other) and + left.min_length == right.min_length and + left.max_length == right.max_length and + left.min_sum == right.min_sum and + left.max_sum == right.max_sum and + left.min_slope == right.min_slope and + left.max_slope == right.max_slope and + left.floor == right.floor and + left.ceiling == right.ceiling) + if equal: + return (op == Py_EQ or op == Py_LE or op == Py_GE) + if op == Py_EQ: + return False + if op == Py_NE: + return True + else: + raise TypeError("IntegerListsBackend can only be compared for equality") + + def _repr_(self): + """ + Return the name of this enumerated set. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: C = IntegerListsBackend(2, length=3) + sage: C._repr_() + 'Integer lists of sum 2 satisfying certain constraints' + """ + if self.min_sum == self.max_sum: + return "Integer lists of sum {} satisfying certain constraints".format(self.min_sum) + elif self.max_sum == Infinity: + if self.min_sum == 0: + return "Integer lists with arbitrary sum satisfying certain constraints" + else: + return "Integer lists of sum at least {} satisfying certain constraints".format(self.min_sum) + else: + return "Integer lists of sum between {} and {} satisfying certain constraints".format(self.min_sum, self.max_sum) + + def _contains(self, comp): + """ + Return ``True`` if ``comp`` meets the constraints imposed + by the arguments. + + EXAMPLES:: + + sage: C = IntegerListsLex(n=2, max_length=3, min_slope=0) + sage: all([l in C for l in C]) # indirect doctest + True + """ + if len(comp) < self.min_length or len(comp) > self.max_length: + return False + n = sum(comp) + if n < self.min_sum or n > self.max_sum: + return False + for i in range(len(comp)): + if comp[i] < self.floor(i): + return False + if comp[i] > self.ceiling(i): + return False + for i in range(len(comp)-1): + slope = comp[i+1] - comp[i] + if slope < self.min_slope or slope > self.max_slope: + return False + return True + + def __getstate__(self): + """ + Pickle ``self``. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: C = IntegerListsBackend(2, length=3) + sage: C.__getstate__() + {'ceiling': , + 'floor': , + 'max_length': 3, + 'max_part': inf, + 'max_slope': inf, + 'max_sum': 2, + 'min_length': 3, + 'min_part': 0, + 'min_slope': -inf, + 'min_sum': 2} + """ + return {"min_sum": self.min_sum, + "max_sum": self.max_sum, + "min_length": self.min_length, + "max_length": self.max_length, + "min_part": self.min_part, + "max_part": self.max_part, + "min_slope": self.min_slope, + "max_slope": self.max_slope, + "floor": self.floor, + "ceiling": self.ceiling} + + def __setstate__(self, state): + """ + Unpickle ``self`` from the state ``state``. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import IntegerListsBackend + sage: C = IntegerListsBackend(2, length=3) + sage: C == loads(dumps(C)) + True + sage: C == loads(dumps(C)) # this did fail at some point, really! + True + sage: C is loads(dumps(C)) # todo: not implemented + True + """ + self.__init__(**state) + + +cdef class Envelope(object): + """ + The (currently approximated) upper (lower) envelope of a function + under the specified constraints. + + INPUT: + + - ``f`` -- a function, list, or tuple; if ``f`` is a list, it is + considered as the function ``f(i)=f[i]``, completed for larger + `i` with ``f(i)=max_part``. + + - ``min_part``, ``max_part``, ``min_slope``, ``max_slope``, ... + as for :class:`IntegerListsLex` (please consult for details). + + - ``sign`` -- (+1 or -1) multiply the input values with ``sign`` + and multiply the output with ``sign``. Setting this to `-1` can + be used to implement a lower envelope. + + The *upper envelope* `U(f)` of `f` is the (pointwise) largest + function which is bounded above by `f` and satisfies the + ``max_part`` and ``max_slope`` conditions. Furthermore, for + ``i,i+1= limit_start, f is constant + # This does not necessarily means that self is constant! + self.f_limit_start = limit_start + self.precomputed = [] + + if min_length > 0: + self(min_length-1) + for i in range(min_length-1,0,-1): + self.precomputed[i-1] = min(self.precomputed[i-1], self.precomputed[i] - self.min_slope) + + def __richcmp__(self, other, int op): + r""" + Basic comparison function, supporting only checking for + equality. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: f = Envelope([3,2,2]) + sage: g = Envelope([3,2,2]) + sage: h = Envelope([3,2,2], min_part=2) + sage: f == f, f == h, f == None + (True, False, False) + sage: f < f, f != h, f != None + (False, True, True) + + This would be desirable:: + + sage: f == g # todo: not implemented + True + """ + cdef Envelope left = self + cdef Envelope right = other + equal = (type(left) is type(other) and + left.sign == right.sign and + left.f == right.f and + left.f_limit_start == right.f_limit_start and + left.max_part == right.max_part and + left.min_slope == right.min_slope and + left.max_slope == right.max_slope) + if equal: + return (op == Py_EQ or op == Py_LE or op == Py_GE) + if op == Py_EQ: + return False + if op == Py_NE: + return True + else: + raise TypeError("Envelopes can only be compared for equality") + + def limit_start(self): + """ + Return from which `i` on the bound returned by ``limit`` holds. + + .. SEEALSO:: :meth:`limit` for the precise specifications. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: Envelope([4,1,5]).limit_start() + 3 + sage: Envelope([4,1,5], sign=-1).limit_start() + 3 + + sage: Envelope([4,1,5], max_part=2).limit_start() + 3 + + sage: Envelope(4).limit_start() + 0 + sage: Envelope(4, sign=-1).limit_start() + 0 + + sage: Envelope(lambda x: 3).limit_start() == Infinity + True + sage: Envelope(lambda x: 3, max_part=2).limit_start() == Infinity + True + + sage: Envelope(lambda x: 3, sign=-1, min_part=2).limit_start() == Infinity + True + + """ + return self.f_limit_start + + def limit(self): + """ + Return a bound on the limit of ``self``. + + OUTPUT: a nonnegative integer or `\infty` + + This returns some upper bound for the accumulation points of + this upper envelope. For a lower envelope, a lower bound is + returned instead. + + In particular this gives a bound for the value of ``self`` at + `i` for `i` large enough. Special case: for a lower envelop, + and when the limit is `\infty`, the envelope is guaranteed to + tend to `\infty` instead. + + When ``s=self.limit_start()`` is finite, this bound is + guaranteed to be valid for `i>=s`. + + Sometimes it's better to have a loose bound that starts early; + sometimes the converse holds. At this point which specific + bound and starting point is returned is not set in stone, in + order to leave room for later optimizations. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: Envelope([4,1,5]).limit() + inf + sage: Envelope([4,1,5], max_part=2).limit() + 2 + sage: Envelope([4,1,5], max_slope=0).limit() + 1 + sage: Envelope(lambda x: 3, max_part=2).limit() + 2 + + Lower envelopes:: + + sage: Envelope(lambda x: 3, min_part=2, sign=-1).limit() + 2 + sage: Envelope([4,1,5], min_slope=0, sign=-1).limit() + 5 + sage: Envelope([4,1,5], sign=-1).limit() + 0 + + .. SEEALSO:: :meth:`limit_start` + """ + if self.limit_start() < Infinity and self.max_slope <= 0: + return self(self.limit_start()) + else: + return self.max_part * self.sign + + def __call__(self, Py_ssize_t k): + """ + Return the value of this envelope at `k`. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: f = Envelope([4,1,5,3,5]) + sage: f.__call__(2) + 5 + sage: [f(i) for i in range(10)] + [4, 1, 5, 3, 5, inf, inf, inf, inf, inf] + + .. NOTE:: + + See the documentation of :class:`Envelope` for tests and + examples. + """ + if k >= len(self.precomputed): + for i in range(len(self.precomputed), k+1): + value = min(self.f(i), self.max_part) + if i > 0: + value = min(value, self.precomputed[i-1] + self.max_slope) + self.precomputed.append(value) + return self.precomputed[k] * self.sign + + def adapt(self, m, j): + """ + Return this envelope adapted to an additional local constraint. + + INPUT: + + - ``m`` -- a nonnegative integer (starting value) + + - ``j`` -- a nonnegative integer (position) + + This method adapts this envelope to the additional local + constraint imposed by having a part `m` at position `j`. + Namely, this returns a function which computes, for any `i>j`, + the minimum of the ceiling function and the value restriction + given by the slope conditions. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: f = Envelope(3) + sage: g = f.adapt(1,1) + sage: g is f + True + sage: [g(i) for i in range(10)] + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3] + + sage: f = Envelope(3, max_slope=1) + sage: g = f.adapt(1,1) + sage: [g(i) for i in range(10)] + [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] + + Note that, in both cases above, the adapted envelope is only + guaranteed to be valid for `i>j`! This is to leave potential + room in the future for sharing similar adapted envelopes:: + + sage: g = f.adapt(0,0) + sage: [g(i) for i in range(10)] + [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] + + sage: g = f.adapt(2,2) + sage: [g(i) for i in range(10)] + [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] + + sage: g = f.adapt(3,3) + sage: [g(i) for i in range(10)] + [0, 1, 2, 3, 3, 3, 3, 3, 3, 3] + + Now with a lower envelope:: + + sage: f = Envelope(1, sign=-1, min_slope=-1) + sage: g = f.adapt(2,2) + sage: [g(i) for i in range(10)] + [4, 3, 2, 1, 1, 1, 1, 1, 1, 1] + sage: g = f.adapt(1,3) + sage: [g(i) for i in range(10)] + [4, 3, 2, 1, 1, 1, 1, 1, 1, 1] + """ + if self.max_slope == Infinity: + return self + m *= self.sign + m = m - j * self.max_slope + return lambda i: self.sign * min(m + i*self.max_slope, self.sign*self(i) ) + + def __reduce__(self): + """ + Pickle ``self``. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import Envelope + sage: h = Envelope(3, min_part=2) + sage: loads(dumps(h)) == h + True + """ + args = (type(self), + self.sign, self.f, self.f_limit_start, self.precomputed, + self.max_part, self.min_slope, self.max_slope) + return _unpickle_Envelope, args + + +def _unpickle_Envelope(type t, _sign, _f, _f_limit_start, _precomputed, + _max_part, _min_slope, _max_slope): + """ + Internal function to support pickling for :class:`Envelope`. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.base import Envelope, _unpickle_Envelope + sage: _unpickle_Envelope(Envelope, + ....: 1, lambda i:i, Infinity, [], 4, -1, 3) + + """ + cdef Envelope self = t.__new__(t) + self.sign = _sign + self.f = _f + self.f_limit_start = _f_limit_start + self.precomputed = _precomputed + self.max_part = _max_part + self.min_slope = _min_slope + self.max_slope = _max_slope + return self diff --git a/src/sage/combinat/integer_lists/invlex.pxd b/src/sage/combinat/integer_lists/invlex.pxd new file mode 100644 index 00000000000..143306b4448 --- /dev/null +++ b/src/sage/combinat/integer_lists/invlex.pxd @@ -0,0 +1,3 @@ +from sage.combinat.integer_lists.base cimport IntegerListsBackend +cdef class IntegerListsBackend_invlex(IntegerListsBackend): + cdef public bint check diff --git a/src/sage/combinat/integer_lists/invlex.pyx b/src/sage/combinat/integer_lists/invlex.pyx new file mode 100644 index 00000000000..c1bbc7163ad --- /dev/null +++ b/src/sage/combinat/integer_lists/invlex.pyx @@ -0,0 +1,1678 @@ +r""" +Enumerated set of lists of integers with constraints, in inverse lexicographic order + +- :class:`IntegerListsLex`: the enumerated set of lists of nonnegative + integers with specified constraints, in inverse lexicographic order. + +- :class:`IntegerListsBackend_invlex`: Cython back-end for + :class:`IntegerListsLex`. + +HISTORY: + +This generic tool was originally written by Hivert and Thiery in +MuPAD-Combinat in 2000 and ported over to Sage by Mike Hansen in +2007. It was then completely rewritten in 2015 by Gillespie, +Schilling, and Thiery, with the help of many, to deal with +limitations and lack of robustness w.r.t. input. +""" + +#***************************************************************************** +# Copyright (C) 2015 Bryan Gillespie +# Nicolas M. Thiery +# Anne Schilling +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + + +from sage.misc.classcall_metaclass import ClasscallMetaclass, typecall +from sage.misc.cachefunc import cached_method +from sage.combinat.integer_lists.base cimport IntegerListsBackend +from sage.combinat.integer_lists.lists import IntegerLists +from sage.combinat.integer_lists.base import Infinity + + +class IntegerListsLex(IntegerLists): + r""" + Lists of nonnegative integers with constraints, in inverse + lexicographic order. + + An *integer list* is a list `l` of nonnegative integers, its *parts*. The + slope (at position `i`) is the difference ``l[i+1]-l[i]`` between two + consecutive parts. + + This class allows to construct the set `S` of all integer lists + `l` satisfying specified bounds on the sum, the length, the slope, + and the individual parts, enumerated in *inverse* lexicographic + order, that is from largest to smallest in lexicographic + order. Note that, to admit such an enumeration, `S` is almost + necessarily finite (see :ref:`IntegerListsLex_finiteness`). + + The main purpose is to provide a generic iteration engine for all the + enumerated sets like :class:`Partitions`, :class:`Compositions`, + :class:`IntegerVectors`. It can also be used to generate many other + combinatorial objects like Dyck paths, Motzkin paths, etc. Mathematically + speaking, this is a special case of set of integral points of a polytope (or + union thereof, when the length is not fixed). + + INPUT: + + - ``min_sum`` -- a nonnegative integer (default: 0): + a lower bound on ``sum(l)``. + + - ``max_sum`` -- a nonnegative integer or `\infty` (default: `\infty`): + an upper bound on ``sum(l)``. + + - ``n`` -- a nonnegative integer (optional): if specified, this + overrides ``min_sum`` and ``max_sum``. + + - ``min_length`` -- a nonnegative integer (default: `0`): a lower + bound on ``len(l)``. + + - ``max_length`` -- a nonnegative integer or `\infty` (default: + `\infty`): an upper bound on ``len(l)``. + + - ``length`` -- an integer (optional); overrides ``min_length`` + and ``max_length`` if specified; + + - ``min_part`` -- a nonnegative integer: a lower bounds on all + parts: ``min_part <= l[i]`` for ``0 <= i < len(l)``. + + - ``floor`` -- a list of nonnegative integers or a function: lower + bounds on the individual parts `l[i]`. + + If ``floor`` is a list of integers, then ``floor<=l[i]`` for ``0 + <= i < min(len(l), len(floor)``. Similarly, if ``floor`` is a + function, then ``floor(i) <= l[i]`` for ``0 <= i < len(l)``. + + - ``max_part`` -- a nonnegative integer or `\infty`: an upper + bound on all parts: ``l[i] <= max_part`` for ``0 <= i < len(l)``. + + - ``ceiling`` -- upper bounds on the individual parts ``l[i]``; + this takes the same type of input as ``floor``, except that + `\infty` is allowed in addition to integers, and the default + value is `\infty`. + + - ``min_slope`` -- an integer or `-\infty` (default: `-\infty`): + an lower bound on the slope between consecutive parts: + ``min_slope <= l[i+1]-l[i]`` for ``0 <= i < len(l)-1`` + + - ``max_slope`` -- an integer or `+\infty` (defaults: `+\infty`) + an upper bound on the slope between consecutive parts: + ``l[i+1]-l[i] <= max_slope`` for ``0 <= i < len(l)-1`` + + - ``category`` -- a category (default: :class:`FiniteEnumeratedSets`) + + - ``check`` -- boolean (default: ``True``): whether to display the + warnings raised when functions are given as input to ``floor`` + or ``ceiling`` and the errors raised when there is no proper + enumeration. + + - ``name`` -- a string or ``None`` (default: ``None``) if set, + this will be passed down to :meth:`Parent.rename` to specify the + name of ``self``. It is recommented to use directly the rename + method as this feature may become deprecated. + + - ``element_constructor`` -- a function (or callable) that creates + elements of ``self`` from a list. See also :class:`Parent`. + + - ``element_class`` -- a class for the elements of ``self`` + (default: `ClonableArray`). This merely sets the attribute + ``self.Element``. See the examples for details. + + - ``global_options`` -- a :class:`~sage.structure.global_options.GlobalOptions` + object that will be assigned to the attribute + ``_global_options``; for internal use only (subclasses, ...). + + + .. NOTE:: + + When several lists satisfying the constraints differ only by + trailing zeroes, only the shortest one is enumerated (and + therefore counted). The others are still considered valid. + See the examples below. + + This feature is questionable. It is recommended not to rely on + it, as it may eventually be discontinued. + + EXAMPLES: + + We create the enumerated set of all lists of nonnegative integers + of length `3` and sum `2`:: + + sage: C = IntegerListsLex(2, length=3) + sage: C + Integer lists of sum 2 satisfying certain constraints + sage: C.cardinality() + 6 + sage: [p for p in C] + [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + + sage: [2, 0, 0] in C + True + sage: [2, 0, 1] in C + False + sage: "a" in C + False + sage: ["a"] in C + False + sage: C.first() + [2, 0, 0] + + One can specify lower and upper bounds on each part:: + + sage: list(IntegerListsLex(5, length=3, floor=[1,2,0], ceiling=[3,2,3])) + [[3, 2, 0], [2, 2, 1], [1, 2, 2]] + + When the length is fixed as above, one can also use + :class:`IntegerVectors`:: + + sage: IntegerVectors(2,3).list() + [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + + Using the slope condition, one can generate integer partitions + (but see :class:`Partitions`):: + + sage: list(IntegerListsLex(4, max_slope=0)) + [[4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]] + + The following is the list of all partitions of `7` with parts at least `2`:: + + sage: list(IntegerListsLex(7, max_slope=0, min_part=2)) + [[7], [5, 2], [4, 3], [3, 2, 2]] + + + .. RUBRIC:: floor and ceiling conditions + + Next we list all partitions of `5` of length at most `3` which are + bounded below by ``[2,1,1]``:: + + sage: list(IntegerListsLex(5, max_slope=0, max_length=3, floor=[2,1,1])) + [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1]] + + Note that ``[5]`` is considered valid, because the floor + constraints only apply to existing positions in the list. To + obtain instead the partitions containing ``[2,1,1]``, one needs to + use ``min_length`` or ``length``:: + + sage: list(IntegerListsLex(5, max_slope=0, length=3, floor=[2,1,1])) + [[3, 1, 1], [2, 2, 1]] + + Here is the list of all partitions of `5` which are contained in + ``[3,2,2]``:: + + sage: list(IntegerListsLex(5, max_slope=0, max_length=3, ceiling=[3,2,2])) + [[3, 2], [3, 1, 1], [2, 2, 1]] + + This is the list of all compositions of `4` (but see :class:`Compositions`):: + + sage: list(IntegerListsLex(4, min_part=1)) + [[4], [3, 1], [2, 2], [2, 1, 1], [1, 3], [1, 2, 1], [1, 1, 2], [1, 1, 1, 1]] + + This is the list of all integer vectors of sum `4` and length `3`:: + + sage: list(IntegerListsLex(4, length=3)) + [[4, 0, 0], [3, 1, 0], [3, 0, 1], [2, 2, 0], [2, 1, 1], + [2, 0, 2], [1, 3, 0], [1, 2, 1], [1, 1, 2], [1, 0, 3], + [0, 4, 0], [0, 3, 1], [0, 2, 2], [0, 1, 3], [0, 0, 4]] + + For whatever it is worth, the ``floor`` and ``min_part`` + constraints can be combined:: + + sage: L = IntegerListsLex(5, floor=[2,0,2], min_part=1) + sage: L.list() + [[5], [4, 1], [3, 2], [2, 3], [2, 1, 2]] + + This is achieved by updating the floor upon constructing ``L``:: + + sage: [L.floor(i) for i in range(5)] + [2, 1, 2, 1, 1] + + Similarly, the ``ceiling`` and ``max_part`` constraints can be + combined:: + + sage: L = IntegerListsLex(4, ceiling=[2,3,1], max_part=2, length=3) + sage: L.list() + [[2, 2, 0], [2, 1, 1], [1, 2, 1]] + sage: [L.ceiling(i) for i in range(5)] + [2, 2, 1, 2, 2] + + + This can be used to generate Motzkin words (see + :wikipedia:`Motzkin_number`):: + + sage: def motzkin_words(n): + ....: return IntegerListsLex(length=n+1, min_slope=-1, max_slope=1, + ....: ceiling=[0]+[+oo for i in range(n-1)]+[0]) + sage: motzkin_words(4).list() + [[0, 1, 2, 1, 0], + [0, 1, 1, 1, 0], + [0, 1, 1, 0, 0], + [0, 1, 0, 1, 0], + [0, 1, 0, 0, 0], + [0, 0, 1, 1, 0], + [0, 0, 1, 0, 0], + [0, 0, 0, 1, 0], + [0, 0, 0, 0, 0]] + sage: [motzkin_words(n).cardinality() for n in range(8)] + [1, 1, 2, 4, 9, 21, 51, 127] + sage: oeis(_) # optional -- internet + 0: A001006: Motzkin numbers: number of ways of drawing any number + of nonintersecting chords joining n (labeled) points on a circle. + + or Dyck words (see also :class:`DyckWords`), through the bijection + with paths from `(0,0)` to `(n,n)` with left and up steps that remain + below the diagonal:: + + sage: def dyck_words(n): + ....: return IntegerListsLex(length=n, ceiling=range(n+1), min_slope=0) + sage: [dyck_words(n).cardinality() for n in range(8)] + [1, 1, 2, 5, 14, 42, 132, 429] + sage: dyck_words(3).list() + [[0, 1, 2], [0, 1, 1], [0, 0, 2], [0, 0, 1], [0, 0, 0]] + + + .. _IntegerListsLex_finiteness: + + .. RUBRIC:: On finiteness and inverse lexicographic enumeration + + The set of all lists of integers cannot be enumerated in inverse + lexicographic order, since there is no largest list (take `[n]` + for `n` as large as desired):: + + sage: IntegerListsLex().first() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + Here is a variant which could be enumerated in lexicographic order + but not in inverse lexicographic order:: + + sage: L = IntegerListsLex(length=2, ceiling=[Infinity, 0], floor=[0,1]) + sage: for l in L: print l + Traceback (most recent call last): + ... + ValueError: infinite upper bound for values of m + + Even when the sum is specified, it is not necessarily possible to + enumerate *all* elements in inverse lexicographic order. In the + following example, the list ``[1, 1, 1]`` will never appear in the + enumeration:: + + sage: IntegerListsLex(3).first() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + If one wants to proceed anyway, one can sign a waiver by setting + ``check=False`` (again, be warned that some valid lists may never appear):: + + sage: L = IntegerListsLex(3, check=False) + sage: it = iter(L) + sage: [next(it) for i in range(6)] + [[3], [2, 1], [2, 0, 1], [2, 0, 0, 1], [2, 0, 0, 0, 1], [2, 0, 0, 0, 0, 1]] + + In fact, being inverse lexicographically enumerable is almost + equivalent to being finite. The only infinity that can occur would + be from a tail of numbers `0,1` as in the previous example, where + the `1` moves further and further to the right. If there is any + list that is inverse lexicographically smaller than such a + configuration, the iterator would not reach it and hence would not + be considered iterable. Given that the infinite cases are very + specific, at this point only the finite cases are supported + (without signing the waiver). + + The finiteness detection is not complete yet, so some finite cases + may not be supported either, at least not without disabling the + checks. Practical examples of such are welcome. + + .. RUBRIC:: On trailing zeroes, and their caveats + + As mentioned above, when several lists satisfying the constraints + differ only by trailing zeroes, only the shortest one is listed:: + + sage: L = IntegerListsLex(max_length=4, max_part=1) + sage: L.list() + [[1, 1, 1, 1], + [1, 1, 1], + [1, 1, 0, 1], + [1, 1], + [1, 0, 1, 1], + [1, 0, 1], + [1, 0, 0, 1], + [1], + [0, 1, 1, 1], + [0, 1, 1], + [0, 1, 0, 1], + [0, 1], + [0, 0, 1, 1], + [0, 0, 1], + [0, 0, 0, 1], + []] + + and counted:: + + sage: L.cardinality() + 16 + + Still, the others are considered as elements of `L`:: + + sage: L = IntegerListsLex(4,min_length=3,max_length=4) + sage: L.list() + [..., [2, 2, 0], ...] + + sage: [2, 2, 0] in L # in L.list() + True + sage: [2, 2, 0, 0] in L # not in L.list() ! + True + sage: [2, 2, 0, 0, 0] in L + False + + .. RUBRIC:: Specifying functions as input for the floor or ceiling + + We construct all lists of sum `4` and length `4` such that ``l[i] <= i``:: + + sage: list(IntegerListsLex(4, length=4, ceiling=lambda i: i, check=False)) + [[0, 1, 2, 1], [0, 1, 1, 2], [0, 1, 0, 3], [0, 0, 2, 2], [0, 0, 1, 3]] + + .. WARNING:: + + When passing a function as ``floor`` or ``ceiling``, it may + become undecidable to detect improper inverse lexicographic + enumeration. For example, the following example has a finite + enumeration:: + + sage: L = IntegerListsLex(3, floor=lambda i: 1 if i>=2 else 0, check=False) + sage: L.list() + [[3], + [2, 1], + [2, 0, 1], + [1, 2], + [1, 1, 1], + [1, 0, 2], + [1, 0, 1, 1], + [0, 3], + [0, 2, 1], + [0, 1, 2], + [0, 1, 1, 1], + [0, 0, 3], + [0, 0, 2, 1], + [0, 0, 1, 2], + [0, 0, 1, 1, 1]] + + but one cannot decide whether the following has an improper + inverse lexicographic enumeration without computing the floor + all the way to ``Infinity``:: + + sage: L = IntegerListsLex(3, floor=lambda i: 0, check=False) + sage: it = iter(L) + sage: [next(it) for i in range(6)] + [[3], [2, 1], [2, 0, 1], [2, 0, 0, 1], [2, 0, 0, 0, 1], [2, 0, 0, 0, 0, 1]] + + Hence a warning is raised when a function is specified as + input, unless the waiver is signed by setting ``check=False``:: + + sage: L = IntegerListsLex(3, floor=lambda i: 1 if i>=2 else 0) + doctest:... + A function has been given as input of the floor=[...] or ceiling=[...] + arguments of IntegerListsLex. Please see the documentation for the caveats. + If you know what you are doing, you can set check=False to skip this warning. + + Similarly, the algorithm may need to search forever for a + solution when the ceiling is ultimately zero:: + + sage: L = IntegerListsLex(2,ceiling=lambda i:0, check=False) + sage: L.first() # not tested: will hang forever + sage: L = IntegerListsLex(2,ceiling=lambda i:0 if i<20 else 1, check=False) + sage: it = iter(L) + sage: next(it) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1] + sage: next(it) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1] + sage: next(it) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1] + + + .. RUBRIC:: Tip: using disjoint union enumerated sets for additional flexibility + + Sometimes, specifying a range for the sum or the length may be too + restrictive. One would want instead to specify a list, or + iterable `L`, of acceptable values. This is easy to achieve using + a :class:`disjoint union of enumerated sets `. + Here we want to accept the values `n=0,2,3`:: + + sage: C = DisjointUnionEnumeratedSets(Family([0,2,3], + ....: lambda n: IntegerListsLex(n, length=2))) + sage: C + Disjoint union of Finite family + {0: Integer lists of sum 0 satisfying certain constraints, + 2: Integer lists of sum 2 satisfying certain constraints, + 3: Integer lists of sum 3 satisfying certain constraints} + sage: C.list() + [[0, 0], + [2, 0], [1, 1], [0, 2], + [3, 0], [2, 1], [1, 2], [0, 3]] + + The price to pay is that the enumeration order is now *graded + lexicographic* instead of lexicographic: first choose the value + according to the order specified by `L`, and use lexicographic + order within each value. Here is we reverse `L`:: + + sage: DisjointUnionEnumeratedSets(Family([3,2,0], + ....: lambda n: IntegerListsLex(n, length=2))).list() + [[3, 0], [2, 1], [1, 2], [0, 3], + [2, 0], [1, 1], [0, 2], + [0, 0]] + + Note that if a given value appears several times, the + corresponding elements will be enumerated several times, which + may, or not, be what one wants:: + + sage: DisjointUnionEnumeratedSets(Family([2,2], + ....: lambda n: IntegerListsLex(n, length=2))).list() + [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] + + Here is a variant where we specify acceptable values for the + length:: + + sage: DisjointUnionEnumeratedSets(Family([0,1,3], + ....: lambda l: IntegerListsLex(2, length=l))).list() + [[2], + [2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + + + This technique can also be useful to obtain a proper enumeration + on infinite sets by using a graded lexicographic enumeration:: + + sage: C = DisjointUnionEnumeratedSets(Family(NN, + ....: lambda n: IntegerListsLex(n, length=2))) + sage: C + Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} + sage: it = iter(C) + sage: [next(it) for i in range(10)] + [[0, 0], + [1, 0], [0, 1], + [2, 0], [1, 1], [0, 2], + [3, 0], [2, 1], [1, 2], [0, 3]] + + + .. RUBRIC:: Specifying how to construct elements + + This is the list of all monomials of degree `4` which divide the + monomial `x^3y^1z^2` (a monomial being identified with its + exponent vector):: + + sage: R. = QQ[] + sage: m = [3,1,2] + sage: def term(exponents): + ....: return x^exponents[0] * y^exponents[1] * z^exponents[2] + sage: list( IntegerListsLex(4, length=len(m), ceiling=m, element_constructor=term) ) + [x^3*y, x^3*z, x^2*y*z, x^2*z^2, x*y*z^2] + + Note the use of the ``element_constructor`` option to specify how + to construct elements from a plain list. + + A variant is to specify a class for the elements. With the default + element constructor, this class should take as input the parent + ``self`` and a list. Here we want the elements to be constructed + in the class :class:`Partition`:: + + sage: IntegerListsLex(3, max_slope=0, element_class=Partition, global_options=Partitions.global_options).list() + doctest:...: DeprecationWarning: the global_options argument is + deprecated since, in general, pickling is broken; + create your own class instead + See http://trac.sagemath.org/15525 for details. + [[3], [2, 1], [1, 1, 1]] + + Note that the :class:`Partition` further assumes the existence of + an attribute ``_global_options`` in the parent, hence the use of the + ``global_options`` parameter. + + .. WARNING:: + + The protocol for specifying the element class and constructor + is subject to changes. + + ALGORITHM: + + The iteration algorithm uses a depth first search through the + prefix tree of the list of integers (see also + :ref:`section-generic-integerlistlex`). While doing so, it does + some lookahead heuristics to attempt to cut dead branches. + + In most practical use cases, most dead branches are cut. Then, + roughly speaking, the time needed to iterate through all the + elements of `S` is proportional to the number of elements, where + the proportion factor is controlled by the length `l` of the + longest element of `S`. In addition, the memory usage is also + controlled by `l`, which is to say negligible in practice. + + Still, there remains much room for efficiency improvements; see + :trac:`18055`, :trac:`18056`. + + .. NOTE:: + + The generation algorithm could in principle be extended to + deal with non-constant slope constraints and with negative + parts. + + TESTS: + + This example from the combinatorics tutorial used to fail before + :trac:`17979` because the floor conditions did not satisfy the + slope conditions:: + + sage: I = IntegerListsLex(16, min_length=2, max_slope=-1, floor=[5,3,3]) + sage: I.list() + [[13, 3], [12, 4], [11, 5], [10, 6], [9, 7], [9, 4, 3], [8, 5, 3], [8, 4, 3, 1], + [7, 6, 3], [7, 5, 4], [7, 5, 3, 1], [7, 4, 3, 2], [6, 5, 4, 1], [6, 5, 3, 2], + [6, 4, 3, 2, 1]] + + :: + + sage: Partitions(2, max_slope=-1, length=2).list() + [] + sage: list(IntegerListsLex(0, floor=ConstantFunction(1), min_slope=0)) + [[]] + sage: list(IntegerListsLex(0, floor=ConstantFunction(1), min_slope=0, max_slope=0)) + [[]] + sage: list(IntegerListsLex(0, max_length=0, floor=ConstantFunction(1), min_slope=0, max_slope=0)) + [[]] + sage: list(IntegerListsLex(0, max_length=0, floor=ConstantFunction(0), min_slope=0, max_slope=0)) + [[]] + sage: list(IntegerListsLex(0, min_part=1, min_slope=0)) + [[]] + sage: list(IntegerListsLex(1, min_part=1, min_slope=0)) + [[1]] + sage: list(IntegerListsLex(0, min_length=1, min_part=1, min_slope=0)) + [] + sage: list(IntegerListsLex(0, min_length=1, min_slope=0)) + [[0]] + sage: list(IntegerListsLex(3, max_length=2)) + [[3], [2, 1], [1, 2], [0, 3]] + sage: partitions = {"min_part": 1, "max_slope": 0} + sage: partitions_min_2 = {"floor": ConstantFunction(2), "max_slope": 0} + sage: compositions = {"min_part": 1} + sage: integer_vectors = lambda l: {"length": l} + sage: lower_monomials = lambda c: {"length": c, "floor": lambda i: c[i]} + sage: upper_monomials = lambda c: {"length": c, "ceiling": lambda i: c[i]} + sage: constraints = { "min_part":1, "min_slope": -1, "max_slope": 0} + sage: list(IntegerListsLex(6, **partitions)) + [[6], + [5, 1], + [4, 2], + [4, 1, 1], + [3, 3], + [3, 2, 1], + [3, 1, 1, 1], + [2, 2, 2], + [2, 2, 1, 1], + [2, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1]] + sage: list(IntegerListsLex(6, **constraints)) + [[6], + [3, 3], + [3, 2, 1], + [2, 2, 2], + [2, 2, 1, 1], + [2, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1]] + sage: list(IntegerListsLex(1, **partitions_min_2)) + [] + sage: list(IntegerListsLex(2, **partitions_min_2)) + [[2]] + sage: list(IntegerListsLex(3, **partitions_min_2)) + [[3]] + sage: list(IntegerListsLex(4, **partitions_min_2)) + [[4], [2, 2]] + sage: list(IntegerListsLex(5, **partitions_min_2)) + [[5], [3, 2]] + sage: list(IntegerListsLex(6, **partitions_min_2)) + [[6], [4, 2], [3, 3], [2, 2, 2]] + sage: list(IntegerListsLex(7, **partitions_min_2)) + [[7], [5, 2], [4, 3], [3, 2, 2]] + sage: list(IntegerListsLex(9, **partitions_min_2)) + [[9], [7, 2], [6, 3], [5, 4], [5, 2, 2], [4, 3, 2], [3, 3, 3], [3, 2, 2, 2]] + sage: list(IntegerListsLex(10, **partitions_min_2)) + [[10], + [8, 2], + [7, 3], + [6, 4], + [6, 2, 2], + [5, 5], + [5, 3, 2], + [4, 4, 2], + [4, 3, 3], + [4, 2, 2, 2], + [3, 3, 2, 2], + [2, 2, 2, 2, 2]] + sage: list(IntegerListsLex(4, **compositions)) + [[4], [3, 1], [2, 2], [2, 1, 1], [1, 3], [1, 2, 1], [1, 1, 2], [1, 1, 1, 1]] + sage: list(IntegerListsLex(6, min_length=1, floor=[7])) + [] + sage: L = IntegerListsLex(10**100,length=1) + sage: L.list() + [[10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]] + + Noted on :trac:`17898`:: + + sage: list(IntegerListsLex(4, min_part=1, length=3, min_slope=1)) + [] + sage: IntegerListsLex(6, ceiling=[4,2], floor=[3,3]).list() + [] + sage: IntegerListsLex(6, min_part=1, max_part=3, max_slope=-4).list() + [] + + Noted in :trac:`17548`, which are now fixed:: + + sage: IntegerListsLex(10, min_part=2, max_slope=-1).list() + [[10], [8, 2], [7, 3], [6, 4], [5, 3, 2]] + sage: IntegerListsLex(5, min_slope=1, floor=[2,1,1], max_part=2).list() + [] + sage: IntegerListsLex(4, min_slope=0, max_slope=0).list() + [[4], [2, 2], [1, 1, 1, 1]] + sage: IntegerListsLex(6, min_slope=-1, max_slope=-1).list() + [[6], [3, 2, 1]] + sage: IntegerListsLex(6, min_length=3, max_length=2, min_part=1).list() + [] + sage: I = IntegerListsLex(3, max_length=2, min_part=1) + sage: I.list() + [[3], [2, 1], [1, 2]] + sage: [1,1,1] in I + False + sage: I=IntegerListsLex(10, ceiling=[4], max_length=1, min_part=1) + sage: I.list() + [] + sage: [4,6] in I + False + sage: I = IntegerListsLex(4, min_slope=1, min_part=1, max_part=2) + sage: I.list() + [] + sage: I = IntegerListsLex(7, min_slope=1, min_part=1, max_part=4) + sage: I.list() + [[3, 4], [1, 2, 4]] + sage: I = IntegerListsLex(4, floor=[2,1], ceiling=[2,2], max_length=2, min_slope=0) + sage: I.list() + [[2, 2]] + sage: I = IntegerListsLex(10, min_part=1, max_slope=-1) + sage: I.list() + [[10], [9, 1], [8, 2], [7, 3], [7, 2, 1], [6, 4], [6, 3, 1], [5, 4, 1], + [5, 3, 2], [4, 3, 2, 1]] + + + .. RUBRIC:: TESTS from comments on :trac:`17979` + + Comment 191:: + + sage: list(IntegerListsLex(1, min_length=2, min_slope=0, max_slope=0)) + [] + + Comment 240:: + + sage: L = IntegerListsLex(min_length=2, max_part=0) + sage: L.list() + [[0, 0]] + + .. RUBRIC:: Tests on the element constructor feature and mutability + + Internally, the iterator works on a single list that is mutated + along the way. Therefore, you need to make sure that the + ``element_constructor`` actually **copies** its input. This example + shows what can go wrong:: + + sage: P = IntegerListsLex(n=3, max_slope=0, min_part=1, element_constructor=lambda x: x) + sage: list(P) + [[], [], []] + + However, specifying ``list()`` as constructor solves this problem:: + + sage: P = IntegerListsLex(n=3, max_slope=0, min_part=1, element_constructor=list) + sage: list(P) + [[3], [2, 1], [1, 1, 1]] + + Same, step by step:: + + sage: it = iter(P) + sage: a = next(it); a + [3] + sage: b = next(it); b + [2, 1] + sage: a + [3] + sage: a is b + False + + Tests from `MuPAD-Combinat `_:: + + sage: IntegerListsLex(7, min_length=2, max_length=6, floor=[0,0,2,0,0,1], ceiling=[3,2,3,2,1,2]).cardinality() + 83 + sage: IntegerListsLex(7, min_length=2, max_length=6, floor=[0,0,2,0,1,1], ceiling=[3,2,3,2,1,2]).cardinality() + 53 + sage: IntegerListsLex(5, min_length=2, max_length=6, floor=[0,0,2,0,0,0], ceiling=[2,2,2,2,2,2]).cardinality() + 30 + sage: IntegerListsLex(5, min_length=2, max_length=6, floor=[0,0,1,1,0,0], ceiling=[2,2,2,2,2,2]).cardinality() + 43 + + sage: IntegerListsLex(0, min_length=0, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).first() + [] + + sage: IntegerListsLex(0, min_length=1, max_length=7, floor=[0,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).first() + [0] + sage: IntegerListsLex(0, min_length=1, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).cardinality() + 0 + + sage: IntegerListsLex(2, min_length=0, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() # Was [1,1], due to slightly different specs + [2] + sage: IntegerListsLex(1, min_length=1, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() + [1] + sage: IntegerListsLex(1, min_length=2, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).cardinality() + 0 + sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,0,0], ceiling=[4,3,2,3,2,2,1]).first() + [1, 1, 0, 0, 0] + sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,0,1], ceiling=[4,3,2,3,2,2,1]).first() + [1, 1, 0, 0, 0] + sage: IntegerListsLex(2, min_length=5, max_length=7, floor=[1,1,0,0,1,0], ceiling=[4,3,2,3,2,2,1]).cardinality() + 0 + + sage: IntegerListsLex(4, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).cardinality() + 0 + sage: IntegerListsLex(5, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() + [2, 1, 2] + sage: IntegerListsLex(6, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() + [3, 1, 2] + sage: IntegerListsLex(12, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() + [3, 1, 2, 3, 2, 1] + sage: IntegerListsLex(13, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).first() + [3, 1, 2, 3, 2, 2] + sage: IntegerListsLex(14, min_length=3, max_length=6, floor=[2, 1, 2, 1, 1, 1], ceiling=[3, 1, 2, 3, 2, 2]).cardinality() + 0 + + This used to hang (see comment 389 and fix in :meth:`Envelope.__init__`):: + + sage: IntegerListsLex(7, max_part=0, ceiling=lambda i:i, check=False).list() + [] + """ + backend_class = IntegerListsBackend_invlex + + __metaclass__ = ClasscallMetaclass + + @staticmethod + def __classcall_private__(cls, n=None, **kwargs): + r""" + Return a disjoint union if ``n`` is a list or iterable. + + TESTS: + + Specifying a list or iterable as argument is deprecated:: + + sage: IntegerListsLex([2,2], length=2).list() + doctest:...: DeprecationWarning: Calling IntegerListsLex with n an iterable is deprecated. Please use DisjointUnionEnumeratedSets or the min_sum and max_sum arguments instead + See http://trac.sagemath.org/17979 for details. + [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] + sage: IntegerListsLex(NN, max_length=3) + Disjoint union of Lazy family (<...>(i))_{i in Non negative integer semiring} + """ + import collections + if isinstance(n, collections.Iterable): + from sage.misc.superseded import deprecation + deprecation(17979, 'Calling IntegerListsLex with n an iterable is deprecated. Please use DisjointUnionEnumeratedSets or the min_sum and max_sum arguments instead') + from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets + from sage.sets.family import Family + return DisjointUnionEnumeratedSets(Family(n, lambda i: IntegerListsLex(i, **kwargs))) + else: + return typecall(cls, n=n, **kwargs) + + +cdef class IntegerListsBackend_invlex(IntegerListsBackend): + """ + Cython back-end of an set of lists of integers with specified + constraints enumerated in inverse lexicographic order. + """ + def __init__(self, *args, check=True, **kwds): + """ + Initialize ``self``. + + TESTS:: + + sage: C = IntegerListsLex(2, length=3) + sage: C == loads(dumps(C)) + True + sage: C.cardinality().parent() is ZZ + True + sage: TestSuite(C).run() + sage: IntegerListsLex(min_part=-1) + Traceback (most recent call last): + ... + NotImplementedError: strictly negative min_part + """ + IntegerListsBackend.__init__(self, *args, **kwds) + + self.check = check + + if self.min_part < 0: + raise NotImplementedError("strictly negative min_part") + + if self.check and ( + self.floor.limit_start() == Infinity or + self.ceiling.limit_start() == Infinity): + from warnings import warn + warn(""" +A function has been given as input of the floor=[...] or ceiling=[...] +arguments of IntegerListsLex. Please see the documentation for the caveats. +If you know what you are doing, you can set check=False to skip this warning.""") + + @cached_method + def _check_finiteness(self): + """ + Check that the constraints define a finite set. + + As mentioned in the description of this class, being finite is + almost equivalent to being inverse lexicographic iterable, + which is what we really care about. + + This set is finite if and only if: + + #. For each `i` such that there exists a list of length at + least `i+1` satisfying the constraints, there exists a + direct or indirect upper bound on the `i`-th part, that + is ``self.ceiling(i)`` is finite. + + #. There exists a global upper bound on the length. + + Failures for 1. are detected and reported later, during the + iteration, namely the first time a prefix including the `i`-th + part is explored. + + This method therefore focuses on 2., namely trying to prove + the existence of an upper bound on the length. It may fail + to do so even when the set is actually finite. + + OUTPUT: + + ``None`` if this method finds a proof that there + exists an upper bound on the length. Otherwise a + ``ValueError`` is raised. + + EXAMPLES:: + + sage: L = IntegerListsLex(4, max_length=4) + sage: L._check_finiteness() + + The following example is infinite:: + + sage: L = IntegerListsLex(4) + sage: L._check_finiteness() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + Indeed:: + + sage: it = iter(IntegerListsLex(4, check=False)) + sage: for _ in range(10): print next(it) + [4] + [3, 1] + [3, 0, 1] + [3, 0, 0, 1] + [3, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 0, 0, 0, 1] + [3, 0, 0, 0, 0, 0, 0, 0, 0, 1] + + Unless ``check=False``, :meth:`_check_finiteness` is called as + soon as an iteration is attempted:: + + sage: iter(L) + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + Some other infinite examples:: + + sage: L = IntegerListsLex(ceiling=[0], min_slope=1, max_slope=2) + sage: L.list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + sage: L = IntegerListsLex(ceiling=[0], min_slope=1, max_slope=1) + sage: L.list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + sage: IntegerListsLex(ceiling=[0], min_slope=1, max_slope=1).list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + The following example is actually finite, but not detected as such:: + + sage: IntegerListsLex(7, floor=[4], max_part=4, min_slope=-1).list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + This is sad because the following equivalent example works just fine:: + + sage: IntegerListsLex(7, floor=[4,3], max_part=4, min_slope=-1).list() + [[4, 3]] + + Detecting this properly would require some deeper lookahead, + and the difficulty is to decide how far this lookahead should + search. Until this is fixed, one can disable the checks:: + + sage: IntegerListsLex(7, floor=[4], max_part=4, min_slope=-1, check=False).list() + [[4, 3]] + + If the ceiling or floor is a function, it is much more likely + that a finite set will not be detected as such:: + + sage: IntegerListsLex(ceiling=lambda i: max(3-i,0))._check_finiteness() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + sage: IntegerListsLex(7, ceiling=lambda i:0).list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + The next example shows a case that is finite because we remove + trailing zeroes:: + + sage: list(IntegerListsLex(ceiling=[0], max_slope=0)) + [[]] + sage: L = IntegerListsLex(ceiling=[1], min_slope=1, max_slope=1) + sage: L.list() + Traceback (most recent call last): + ... + ValueError: could not prove that the specified constraints yield a finite set + + In the next examples, there is either no solution, or the region + is bounded:: + + sage: IntegerListsLex(min_sum=10, max_sum=5).list() + [] + sage: IntegerListsLex(max_part=1, min_slope=10).list() + [[1], []] + sage: IntegerListsLex(max_part=100, min_slope=10).first() + [100] + sage: IntegerListsLex(ceiling=[1,Infinity], max_part=2, min_slope=1).list() + [[1, 2], [1], [0, 2], [0, 1, 2], [0, 1], []] + sage: IntegerListsLex(min_sum=1, floor=[1,2], max_part=1).list() + [[1]] + + sage: IntegerListsLex(min_length=2, max_length=1).list() + [] + sage: IntegerListsLex(min_length=-2, max_length=-1).list() + [] + sage: IntegerListsLex(min_length=-1, max_length=-2).list() + [] + sage: IntegerListsLex(min_length=2, max_slope=0, min_slope=1).list() + [] + sage: IntegerListsLex(min_part=2, max_part=1).list() + [[]] + + sage: IntegerListsLex(floor=[0,2], ceiling=[3,1]).list() + [[3], [2], [1], []] + sage: IntegerListsLex(7, ceiling=[2], floor=[4]).list() + [] + sage: IntegerListsLex(7, max_part=0).list() + [] + sage: IntegerListsLex(5, max_part=0, min_slope=0).list() + [] + sage: IntegerListsLex(max_part=0).list() + [[]] + sage: IntegerListsLex(max_sum=1, min_sum=4, min_slope=0).list() + [] + """ + # Trivial cases + if self.max_length < Infinity: + return + if self.max_sum < self.min_sum: + return + if self.min_slope > self.max_slope: + return + if self.max_slope < 0: + return + if self.ceiling.limit() < self.floor.limit(): + return + if self.ceiling.limit() == 0: + # This assumes no trailing zeroes + return + if self.min_slope > 0 and self.ceiling.limit() < Infinity: + return + + # Compute a lower bound on the sum of floor(i) for i=1 to infinity + if self.floor.limit() > 0 or self.min_slope > 0: + floor_sum_lower_bound = Infinity + elif self.floor.limit_start() < Infinity: + floor_sum_lower_bound = sum(self.floor(i) for i in range(self.floor.limit_start())) + else: + floor_sum_lower_bound = 0 + if floor_sum_lower_bound > 0 and self.min_slope >= 0: + floor_sum_lower_bound = Infinity + + if self.max_sum < floor_sum_lower_bound: + return + if self.max_sum == floor_sum_lower_bound and self.max_sum < Infinity: + # This assumes no trailing zeroes + return + + # Variant on ceiling.limit() ==0 where we actually discover that the ceiling limit is 0 + if ( self.max_slope == 0 and + (self.max_sum < Infinity or + (self.ceiling.limit_start() < Infinity and + any(self.ceiling(i) == 0 for i in range(self.ceiling.limit_start()+1))) + ) ): + return + + limit_start = max(self.ceiling.limit_start(), self.floor.limit_start()) + if limit_start < Infinity: + for i in range(limit_start+1): + if self.ceiling(i) < self.floor(i): + return + + raise ValueError("could not prove that the specified constraints yield a finite set") + + def _iter(self): + """ + Return an iterator for the elements of ``self``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: list(C) # indirect doctest + [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + """ + if self.check: + self._check_finiteness() + return IntegerListsLexIter(self) + + +# Constants for IntegerListsLexIter._next_state +LOOKAHEAD = 5 +PUSH = 4 +ME = 3 +DECREASE = 2 +POP = 1 +STOP = 0 + +class IntegerListsLexIter(object): + r""" + Iterator class for IntegerListsLex. + + Let ``T`` be the prefix tree of all lists of nonnegative + integers that satisfy all constraints except possibly for + ``min_length`` and ``min_sum``; let the children of a list + be sorted decreasingly according to their last part. + + The iterator is based on a depth-first search exploration of a + subtree of this tree, trying to cut branches that do not + contain a valid list. Each call of ``next`` iterates through + the nodes of this tree until it finds a valid list to return. + + Here are the attributes describing the current state of the + iterator, and their invariants: + + - ``backend`` -- the :class:`IntegerListsBackend` object this is + iterating on; + + - ``_current_list`` -- the list corresponding to the current + node of the tree; + + - ``_j`` -- the index of the last element of ``_current_list``: + ``self._j == len(self._current_list) - 1``; + + - ``_current_sum`` -- the sum of the parts of ``_current_list``; + + - ``_search_ranges`` -- a list of same length as + ``_current_list``: the range for each part. + + Furthermore, we assume that there is no obvious contradiction + in the contraints: + + - ``self.backend.min_length <= self.backend.max_length``; + - ``self.backend.min_slope <= self.backend.max_slope`` + unless ``self.backend.min_length <= 1``. + + Along this iteration, ``next`` switches between the following + states: + + - LOOKAHEAD: determine whether the current list could be a + prefix of a valid list; + - PUSH: go deeper into the prefix tree by appending the + largest possible part to the current list; + - ME: check whether the current list is valid and if yes return it + - DECREASE: decrease the last part; + - POP: pop the last part of the current list; + - STOP: the iteration is finished. + + The attribute ``_next_state`` contains the next state ``next`` + should enter in. + """ + def __init__(self, backend): + """ + TESTS:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: I = IntegerListsLexIter(C) + sage: I._search_ranges + [] + sage: I._current_list + [] + sage: I._j + -1 + sage: I._current_sum + 0 + """ + self.backend = backend + + self._search_ranges = [] + self._current_list = [] + self._j = -1 # index of last element of _current_list + self._current_sum = 0 # sum of parts in _current_list + + # Make sure that some invariants are respected in the iterator + if (backend.min_length <= backend.max_length and + (backend.min_slope <= backend.max_slope or backend.min_length <= 1)): + self._next_state = PUSH + else: + self._next_state = STOP + + def __iter__(self): + """ + Return ``self`` as per the iterator protocol. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: it = IntegerListsLexIter(C) + sage: it.__iter__() is it + True + """ + return self + + def _push_search(self): + """ + Push search forward, resetting attributes. + + The push may fail if it is discovered that + ``self._current_list`` cannot be extended in a valid way. + + OUTPUT: a boolean: whether the push succeeded + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: I = C._iter() + sage: I._j + -1 + sage: I._search_ranges + [] + sage: I._current_list + [] + sage: I._current_sum + 0 + sage: I._push_search() + True + sage: I._j + 0 + sage: I._search_ranges + [(0, 2)] + sage: I._current_list + [2] + sage: I._current_sum + 2 + sage: I._push_search() + True + sage: I._j + 1 + sage: I._search_ranges + [(0, 2), (0, 0)] + sage: I._current_list + [2, 0] + sage: I._current_sum + 2 + """ + max_sum = self.backend.max_sum + min_length = self.backend.min_length + max_length = self.backend.max_length + if self._j+1 >= max_length: + return False + if self._j+1 >= min_length and self._current_sum == max_sum: + # Cannot add trailing zeroes + return False + + if self._j >= 0: + prev = self._current_list[self._j] + else: + prev = None + interval = self._m_interval(self._j+1, self.backend.max_sum - self._current_sum, prev) + if interval[0] > interval[1]: + return False + + self._j += 1 + m = interval[1] + self._search_ranges.append(interval) + self._current_list.append(m) + self._current_sum += m + return True + + def _pop_search(self): + """ + Go back in search tree. Resetting attributes. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: I = C._iter() + sage: I._push_search() + True + sage: I._j + 0 + sage: I._search_ranges + [(0, 2)] + sage: I._current_sum + 2 + sage: I._current_list + [2] + sage: I._pop_search() + sage: I._j + -1 + sage: I._search_ranges + [] + sage: I._current_sum + 0 + sage: I._current_list + [] + """ + if self._j >= 0: # TODO: get rid of this condition + self._j -= 1 + self._search_ranges.pop() + self._current_sum -= self._current_list[-1] + self._current_list.pop() + + def next(self): + r""" + Return the next element in the iteration. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: I = IntegerListsLexIter(C) + sage: next(I) + [2, 0, 0] + sage: next(I) + [1, 1, 0] + """ + p = self.backend + min_sum = p.min_sum + max_length = p.max_length + search_ranges = self._search_ranges + + while True: + assert self._j == len(self._current_list) - 1 + assert self._j == len(self._search_ranges) - 1 + + # LOOK AHEAD + if self._next_state == LOOKAHEAD: + if self._lookahead(): + self._next_state = PUSH + else: + # We should reuse information about the + # reasons for this failure, to avoid when + # possible retrying with smaller values. + # We just do a special case for now: + if self._j + 1 == max_length and self._current_sum < min_sum: + self._next_state = POP + else: + self._next_state = DECREASE + + # PUSH + if self._next_state == PUSH: + if self._push_search(): + self._next_state = LOOKAHEAD + continue + self._next_state = ME + + # ME + if self._next_state == ME: + if self._j == -1: + self._next_state = STOP + else: + self._next_state = DECREASE + if self._internal_list_valid(): + return self._current_list + + # DECREASE + if self._next_state == DECREASE: + self._current_list[-1] -= 1 + self._current_sum -= 1 + if self._current_list[-1] >= search_ranges[self._j][0]: + self._next_state = LOOKAHEAD + continue + self._next_state = POP + + # POP + if self._next_state == POP: + self._pop_search() + self._next_state = ME + continue + + # STOP + if self._next_state == STOP: + raise StopIteration() + + assert False + + def _internal_list_valid(self): + """ + Return whether the current list in the iteration variable + ``self._current_list`` is a valid list. + + This method checks whether the sum of the parts in + ``self._current_list`` is in the right range, whether its + length is in the required range, and whether there are trailing + zeroes. It does not check all of the necessary conditions to + verify that an arbitrary list satisfies the constraints from + the corresponding ``IntegerListsLex`` object, and should + not be used except internally in the iterator class. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: I = IntegerListsLexIter(C) + sage: I._current_list + [] + sage: I._internal_list_valid() + False + sage: next(I) + [2, 0, 0] + sage: I._current_list + [2, 0, 0] + sage: I._internal_list_valid() + True + """ + p = self.backend + mu = self._current_list + nu = self._current_sum + l = self._j + 1 + return (nu >= p.min_sum and nu <= p.max_sum # Good sum + and l >= p.min_length and l <= p.max_length # Good length + and (l <= max(p.min_length,0) or mu[-1] != 0)) # No trailing zeros + + def _m_interval(self, i, max_sum, prev=None): + r""" + Return coarse lower and upper bounds for the part ``m`` + at position ``i``. + + INPUT: + + - ``i`` -- a nonnegative integer (position) + + - ``max_sum`` -- a nonnegative integer or ``+oo`` + + - ``prev`` -- a nonnegative integer or ``None`` + + Return coarse lower and upper bounds for the value ``m`` + of the part at position ``i`` so that there could exists + some list suffix `v_i,\ldots,v_k` of sum bounded by + ``max_sum`` and satisfying the floor and upper bound + constraints. If ``prev`` is specified, then the slope + conditions between ``v[i-1]=prev`` and ``v[i]=m`` should + also be satisfied. + + Additionally, this raises an error if it can be detected + that some part is neither directly nor indirectly bounded + above, which implies that the constraints possibly do not + allow for an inverse lexicographic iterator. + + OUTPUT: + + A tuple of two integers ``(lower_bound, upper_bound)``. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.invlex import IntegerListsLexIter + sage: C = IntegerListsLex(2, length=3) + sage: I = IntegerListsLexIter(C) + sage: I._m_interval(1,2) + (0, 2) + + The second part is not bounded above, hence we can not + iterate lexicographically through all the elements:: + + sage: IntegerListsLex(ceiling=[2,infinity,3], max_length=3).first() + Traceback (most recent call last): + ... + ValueError: infinite upper bound for values of m + + Same here:: + + sage: IntegerListsLex(ceiling=[2,infinity,2], max_length=3, min_slope=-1).cardinality() + Traceback (most recent call last): + ... + ValueError: infinite upper bound for values of m + + In the following examples however, all parts are + indirectly bounded above:: + + sage: IntegerListsLex(ceiling=[2,infinity,2], length=3, min_slope=-1).cardinality() + 24 + sage: IntegerListsLex(ceiling=[2,infinity,2], max_length=3, max_slope=1).cardinality() + 24 + + sage: IntegerListsLex(max_part=2, max_length=3).cardinality() + 27 + sage: IntegerListsLex(3, max_length=3).cardinality() # parts bounded by n + 10 + sage: IntegerListsLex(max_length=0, min_length=1).list() # no part! + [] + sage: IntegerListsLex(length=0).list() # no part! + [[]] + """ + p = self.backend + + lower_bound = max(0, p.floor(i)) + upper_bound = min(max_sum, p.ceiling(i)) + if prev != None: + lower_bound = max(lower_bound, prev + p.min_slope) + upper_bound = min(upper_bound, prev + p.max_slope) + + ## check for infinite upper bound, in case max_sum is infinite + if p.check and upper_bound == Infinity: + # This assumes that there exists a valid list (which + # is not yet always guaranteed). Then we just + # discovered that part 'i' of this list can be made as + # large as desired, which implies that `self.backend` + # cannot be iterated in inverse lexicographic order + raise ValueError("infinite upper bound for values of m") + + return (lower_bound, upper_bound) + + def _lookahead(self): + r""" + Return whether the current list can possibly be a prefix + of a valid list. + + OUTPUT: + + ``False`` if it is guaranteed that the current list + cannot be a prefix of a valid list and ``True`` otherwise. + + EXAMPLES:: + + sage: it = IntegerListsLex(length=3, min_sum=2, max_sum=2)._iter() + sage: it._current_list = [0,1] # don't do this at home, kids + sage: it._current_sum = 1 + sage: it._j = 1 + sage: it._lookahead() + True + + sage: it = IntegerListsLex(length=3, min_sum=3, max_sum=2)._iter() + sage: it._current_list = [0,1] + sage: it._current_sum = 1 + sage: it._j = 1 + sage: it._lookahead() + False + + sage: it = IntegerListsLex(min_length=2, max_part=0)._iter() + sage: it._current_list = [0] + sage: it._current_sum = 0 + sage: it._j = 0 + sage: it._lookahead() + True + sage: it._current_list = [0, 0] + sage: it._j = 1 + sage: it._lookahead() + True + sage: it._current_list = [0, 0, 0] + sage: it._j = 2 + sage: it._lookahead() + False + + sage: n = 10**100 + sage: it = IntegerListsLex(n, length=1)._iter() + sage: it._current_list = [n-1] + sage: it._current_sum = n-1 + sage: it._j = 0 + sage: it._lookahead() + False + + sage: it = IntegerListsLex(n=3, min_part=2, min_sum=3, max_sum=3)._iter() + sage: it._current_list = [2] + sage: it._current_sum = 2 + sage: it._j = 0 + sage: it._lookahead() + False + + ALGORITHM: + + Let ``j = self._j`` be the position of the last part `m` of + ``self._current_list``. The current algorithm computes, + for `k = j, j+1, \ldots`, a lower bound `l_k` and an upper + bound `u_k` for `v_0+\dots+v_k`, and stops if none of the + invervals `[l_k, u_k]` intersect ``[min_sum, max_sum]``. + + The lower bound `l_k` is given by the area below + `v_0,\dots,v_{j-1}` prolongated by the lower envelope + between `j` and `k` and starting at `m`. The upper bound + `u_k` is given similarly using the upper envelope. + + The complexity of this algorithm is bounded above by + ``O(max_length)``. When ``max_length=oo``, the algorithm + is guaranteed to terminate, unless ``floor`` is a function + which is eventually constant with value `0`, or which + reaches the value `0` while ``max_slope=0``. + + Indeed, the lower bound `l_k` is increasing with `k`; in + fact it is strictly increasing, unless the local lower bound + at `k` is `0`. Furthermore as soon as ``l_k >= min_sum``, + we can conclude; we can also conclude if we know that the + floor is eventually constant with value `0`, or there is a + local lower bound at `k` is `0` and ``max_slope=0``. + + .. RUBRIC:: Room for improvement + + Improved prediction: the lower bound `l_k` does not take + the slope conditions into account, except for those imposed + by the value `m` at `j`. Similarly for `u_k`. + + Improved speed: given that `l_k` is increasing with `k`, + possibly some dichotomy could be used to search for `k`, + with appropriate caching / fast calculation of the partial + sums. Also, some of the information gained at depth `j` + could be reused at depth `j+1`. + + TESTS:: + + sage: it = IntegerListsLex(1, min_length=2, min_slope=0, max_slope=0, min_sum=1, max_sum=1)._iter() + sage: it._current_list = [0] + sage: it._current_sum = 0 + sage: it._j = 0 + sage: it._lookahead() + False + """ + # Check code for various termination conditions. Possible cases: + # 0. interval [lower, upper] intersects interval [min_sum, max_sum] -- terminate True + # 1. lower sum surpasses max_sum -- terminate False + # 2. iteration surpasses max_length -- terminate False + # 3. upper envelope is smaller than lower envelope -- terminate False + # 4. max_slope <= 0 -- terminate False after upper passes 0 + # 5. ceiling_limit == 0 -- terminate False after reaching larger limit point + # 6. (uncomputable) ceiling function == 0 for all but finitely many input values -- terminate False after reaching (unknown) limit point -- currently hangs + + m = self._current_list[-1] + j = self._j + min_sum = self.backend.min_sum - (self._current_sum-m) + max_sum = self.backend.max_sum - (self._current_sum-m) + + if min_sum > max_sum: + return False + + p = self.backend + + # Beware that without slope conditions, the functions below + # currently forget about the value m at k! + lower_envelope = self.backend.floor.adapt(m,j) + upper_envelope = self.backend.ceiling.adapt(m,j) + lower = 0 # The lower bound `l_k` + upper = 0 # The upper bound `u_k` + + assert j >= 0 + # get to smallest valid number of parts + for k in range(j, p.min_length-1): + # We are looking at lists `v_j,...,v_k` + lo = m if k == j else lower_envelope(k) + up = m if k == j else upper_envelope(k) + if lo > up: + return False + lower += lo + upper += up + + if j < p.min_length and min_sum <= upper and lower <= max_sum: + # There could exist a valid list `v_j,\dots,v_{min_length-1}` + return True + + k = max(p.min_length-1,j) + # Check if any of the intervals intersect the target interval + while k < p.max_length: + lo = m if k == j else lower_envelope(k) + up = m if k == j else upper_envelope(k) + if lo > up: + # There exists no valid list of length >= k + return False + lower += lo + upper += up + assert lower <= upper + + if lower > max_sum: + # There cannot exist a valid list `v_j,\dots,v_l` with l>=k + return False + + if ( (p.max_slope <= 0 and up <= 0) + or (p.ceiling.limit() == 0 and k > p.ceiling.limit_start()) ): + # This implies v_l=0 for l>=k: that is we would be generating + # a list with trailing zeroes + return False + + if min_sum <= upper and lower <= max_sum: + # There could exist a valid list `v_j,\dots,v_k` + return True + + k += 1 + + return False + diff --git a/src/sage/combinat/integer_lists/lists.py b/src/sage/combinat/integer_lists/lists.py new file mode 100644 index 00000000000..42ff0dd6214 --- /dev/null +++ b/src/sage/combinat/integer_lists/lists.py @@ -0,0 +1,292 @@ +r""" +Enumerated set of lists of integers with constraints: front-end + +- :class:`IntegerLists`: class which models an enumerated set of lists + of integers with certain constraints. This is a Python front-end + where all user-accessible functionality should be implemented. +""" + +#***************************************************************************** +# Copyright (C) 2015 Bryan Gillespie +# Nicolas M. Thiery +# Anne Schilling +# Jeroen Demeyer +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + + +from inspect import ismethod +from sage.categories.enumerated_sets import EnumeratedSets +from sage.structure.list_clone import ClonableArray +from sage.structure.parent import Parent +from sage.combinat.integer_lists.base import IntegerListsBackend + + +class IntegerList(ClonableArray): + """ + Element class for :class:`IntegerLists`. + """ + def check(self): + """ + Check to make sure this is a valid element in its + :class:`IntegerLists` parent. + + EXAMPLES:: + + sage: C = IntegerListsLex(4) + sage: C([4]).check() + True + sage: C([5]).check() + False + """ + return self.parent().__contains__(self) + + +class IntegerLists(Parent): + """ + Enumerated set of lists of integers with constraints. + + Currently, this is simply an abstract base class which should not + be used by itself. See :class:`IntegerListsLex` for a class which + can be used by end users. + + ``IntegerLists`` is just a Python front-end, acting as a + :class:`Parent`, supporting element classes and so on. + The attribute ``.backend`` which is an instance of + :class:`sage.combinat.integer_lists.base.IntegerListsBackend` is the + Cython back-end which implements all operations such as iteration. + + The front-end (i.e. this class) and the back-end are supposed to be + orthogonal: there is no imposed correspondence between front-ends + and back-ends. + + For example, the set of partitions of 5 and the set of weakly + decreasing sequences which sum to 5 might be implemented by the + same back-end, but they will be presented to the user by a + different front-end. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists import IntegerLists + sage: L = IntegerLists(5) + sage: L + Integer lists of sum 5 satisfying certain constraints + + sage: IntegerListsLex(2, length=3, name="A given name") + A given name + """ + backend = None + backend_class = IntegerListsBackend + + Element = IntegerList + + def __init__(self, *args, **kwds): + """ + Initialize ``self``. + + TESTS:: + + sage: from sage.combinat.integer_lists import IntegerLists + sage: C = IntegerLists(2, length=3) + sage: C == loads(dumps(C)) + True + """ + if "name" in kwds: + self.rename(kwds.pop("name")) + + if "global_options" in kwds: + from sage.misc.superseded import deprecation + deprecation(15525, 'the global_options argument is deprecated since, in general,' + ' pickling is broken; create your own class instead') + self.global_options = kwds.pop("global_options") + + if "element_class" in kwds: + self.Element = kwds.pop("element_class") + + if "element_constructor" in kwds: + element_constructor = kwds.pop("element_constructor") + elif issubclass(self.Element, ClonableArray): + # Not all element classes support check=False + element_constructor = self._element_constructor_nocheck + else: + element_constructor = None # Parent's default + + category = kwds.pop("category", None) + if category is None: + category = EnumeratedSets().Finite() + + # Let self.backend be some IntegerListsBackend + self.backend = self.backend_class(*args, **kwds) + + Parent.__init__(self, element_constructor=element_constructor, + category=category) + + def __eq__(self, other): + r""" + Return whether ``self == other``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: D = IntegerListsLex(2, length=3); L = D.list(); + sage: E = IntegerListsLex(2, min_length=3) + sage: F = IntegerListsLex(2, length=3, element_constructor=list) + sage: G = IntegerListsLex(4, length=3) + sage: C == C + True + sage: C == D + True + sage: C == E + False + sage: C == F + False + sage: C == None + False + sage: C == G + False + + This is a minimal implementation enabling pickling tests. It + is safe, but one would want the two following objects to be + detected as equal:: + + sage: C = IntegerListsLex(2, ceiling=[1,1,1]) + sage: D = IntegerListsLex(2, ceiling=[1,1,1]) + sage: C == D + False + + TESTS: + + This used to fail due to poor equality testing. See + :trac:`17979`, comment 433:: + + sage: DisjointUnionEnumeratedSets(Family([2,2], + ....: lambda n: IntegerListsLex(n, length=2))).list() + [[2, 0], [1, 1], [0, 2], [2, 0], [1, 1], [0, 2]] + sage: DisjointUnionEnumeratedSets(Family([2,2], + ....: lambda n: IntegerListsLex(n, length=1))).list() + [[2], [2]] + """ + if self.__class__ != other.__class__: + return False + if self.backend != other.backend: + return False + a = self._element_constructor + b = other._element_constructor + if ismethod(a): + a = a.im_func + if ismethod(b): + b = b.im_func + return a == b + + def __ne__(self, other): + r""" + Return whether ``self != other``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: D = IntegerListsLex(2, length=3); L = D.list(); + sage: E = IntegerListsLex(2, max_length=3) + sage: C != D + False + sage: C != E + True + """ + return not self == other + + def __iter__(self): + """ + Return an iterator for the elements of ``self``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: list(C) # indirect doctest + [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + """ + return self._element_iter(self.backend._iter(), self._element_constructor) + + @staticmethod + def _element_iter(itr, constructor): + """ + Given an iterator ``itr`` and an element constructor + ``constructor``, iterate over ``constructor(v)`` where `v` + are the values of ``itr``. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: list(C._element_iter(C._iter(), tuple)) + [(2, 0, 0), (1, 1, 0), (1, 0, 1), (0, 2, 0), (0, 1, 1), (0, 0, 2)] + """ + for v in itr: + yield constructor(v) + + def __getattr__(self, name): + """ + Get an attribute of the implementation backend. + + Ideally, this would be done using multiple inheritance, but + Python doesn't support that for built-in types. + + EXAMPLES:: + + sage: C = IntegerListsLex(2, length=3) + sage: C.min_length + 3 + + TESTS: + + Check that uninitialized instances do not lead to infinite + recursion because there is no ``backend`` attribute:: + + sage: from sage.combinat.integer_lists import IntegerLists + sage: L = IntegerLists.__new__(IntegerLists) + sage: L.foo + Traceback (most recent call last): + ... + AttributeError: 'NoneType' object has no attribute 'foo' + """ + return getattr(self.backend, name) + + def __contains__(self, item): + """ + Return ``True`` if ``item`` meets the constraints imposed by + the arguments. + + EXAMPLES:: + + sage: C = IntegerListsLex(n=2, max_length=3, min_slope=0) + sage: all([l in C for l in C]) + True + """ + return self.backend._contains(item) + + def _element_constructor_nocheck(self, l): + r""" + A variant of the standard element constructor that passes + ``check=False`` to the element class. + + EXAMPLES:: + + sage: L = IntegerListsLex(4, max_slope=0) + sage: L._element_constructor_nocheck([1,2,3]) + [1, 2, 3] + + When relevant, this is assigned to + ``self._element_constructor`` by :meth:`__init__`, to avoid + overhead when constructing elements from trusted data in the + iterator:: + + sage: L._element_constructor + + sage: L._element_constructor([1,2,3]) + [1, 2, 3] + """ + return self.element_class(self, l, check=False) + diff --git a/src/sage/combinat/integer_lists/nn.py b/src/sage/combinat/integer_lists/nn.py new file mode 100644 index 00000000000..24c7e9458fc --- /dev/null +++ b/src/sage/combinat/integer_lists/nn.py @@ -0,0 +1,43 @@ +from sage.sets.family import Family +from sage.combinat.integer_lists import IntegerListsLex +from sage.rings.semirings.non_negative_integer_semiring import NN +from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets + +def IntegerListsNN(**kwds): + """ + Lists of nonnegative integers with constraints. + + This function returns the union of ``IntegerListsLex(n, **kwds)`` + where `n` ranges over all nonnegative integers. + + EXAMPLES:: + + sage: from sage.combinat.integer_lists.nn import IntegerListsNN + sage: L = IntegerListsNN(max_length=3, max_slope=-1) + sage: L + Disjoint union of Lazy family ((i))_{i in Non negative integer semiring} + sage: it = iter(L) + sage: for _ in range(20): + ....: print next(it) + [] + [1] + [2] + [3] + [2, 1] + [4] + [3, 1] + [5] + [4, 1] + [3, 2] + [6] + [5, 1] + [4, 2] + [3, 2, 1] + [7] + [6, 1] + [5, 2] + [4, 3] + [4, 2, 1] + [8] + """ + return DisjointUnionEnumeratedSets(Family(NN, lambda i: IntegerListsLex(i, **kwds))) diff --git a/src/sage/combinat/integer_matrices.py b/src/sage/combinat/integer_matrices.py index 15bf04d4964..0983acbf1f4 100644 --- a/src/sage/combinat/integer_matrices.py +++ b/src/sage/combinat/integer_matrices.py @@ -18,7 +18,7 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets -from sage.combinat.integer_list import IntegerListsLex +from sage.combinat.integer_lists import IntegerListsLex from sage.matrix.constructor import matrix from sage.rings.integer_ring import ZZ diff --git a/src/sage/combinat/integer_vector.py b/src/sage/combinat/integer_vector.py index b11ce3136da..6ca77961adc 100644 --- a/src/sage/combinat/integer_vector.py +++ b/src/sage/combinat/integer_vector.py @@ -27,6 +27,7 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools import misc from __builtin__ import list as builtinlist from sage.categories.enumerated_sets import EnumeratedSets @@ -34,9 +35,8 @@ from sage.rings.integer import Integer from sage.rings.arith import binomial from sage.rings.infinity import PlusInfinity -import cartesian_product import functools -from sage.combinat.integer_list import IntegerListsLex +from sage.combinat.integer_lists import IntegerListsLex def is_gale_ryser(r,s): @@ -114,86 +114,6 @@ def is_gale_ryser(r,s): # same number of 1s domination return len(rstar) <= len(s2) and sum(r2) == sum(s2) and rstar.dominates(s) -def _slider01(A, t, k, p1, p2, fixedcols=[]): - r""" - Assumes `A` is a `(0,1)`-matrix. For each of the - `t` rows with highest row sums, this function - returns a matrix `B` which is the same as `A` except that it - has slid `t` of the `1` in each of these rows of `A` - over towards the `k`-th column. Care must be taken when the - last leading 1 is in column >=k. It avoids those in columns - listed in fixedcols. - - This is a 'private' function for use in gale_ryser_theorem. - - INPUT: - - - ``A`` -- an `m\times n` (0,1) matrix - - ``t``, ``k`` -- integers satisfying `0 < t < m`, `0 < k < n` - - ``fixedcols`` -- those columns (if any) whose entries - aren't permitted to slide - - OUTPUT: - - An `m\times n` (0,1) matrix, which is the same as `A` except - that it has exactly one `1` in `A` slid over to the `k`-th - column. - - EXAMPLES:: - - sage: from sage.combinat.integer_vector import _slider01 - sage: A = matrix([[1,1,1,0],[1,1,1,0],[1,0,0,0],[1,0,0,0]]) - sage: A - [1 1 1 0] - [1 1 1 0] - [1 0 0 0] - [1 0 0 0] - sage: _slider01(A, 1, 3, [3,3,1,1], [3,3,1,1]) - [1 1 0 1] - [1 1 1 0] - [1 0 0 0] - [1 0 0 0] - sage: _slider01(A, 3, 3, [3,3,1,1], [3,3,1,1]) - [1 1 0 1] - [1 0 1 1] - [0 0 0 1] - [1 0 0 0] - - """ - # we assume that the rows of A are arranged so that - # there row sums are decreasing as you go from the - # top row to the bottom row - import copy - from sage.matrix.constructor import matrix - m = len(A.rows()) - rs = [sum(x) for x in A.rows()] - n = len(A.columns()) - cs = [sum(x) for x in A.columns()] - B = [copy.deepcopy(list(A.row(j))) for j in range(m)] - c = 0 # initializing counter - for ii in range(m): - rw = copy.deepcopy(B[ii]) # to make mutable - # now we want to move the rightmost left 1 to the k-th column - fixedcols = [l for l in range(n) if p2[l]==sum(matrix(B).column(l))] - JJ = range(n) - JJ.reverse() - for jj in JJ: - if t==sum(matrix(B).column(k)): - break - if jj=t: next - j=n-1 - else: - next - if c>=t: - break - return matrix(B) - def gale_ryser_theorem(p1, p2, algorithm="gale"): r""" Returns the binary matrix given by the Gale-Ryser theorem. @@ -234,8 +154,8 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): Ryser's Algorithm: - (Ryser [Ryser63]_): The construction of an `m\times n` matrix `A=A_{r,s}`, - due to Ryser, is described as follows. The + (Ryser [Ryser63]_): The construction of an `m\times n` matrix + `A=A_{r,s}`, due to Ryser, is described as follows. The construction works if and only if have `s\preceq r^*`. * Construct the `m\times n` matrix `B` from `r` by defining @@ -255,6 +175,8 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): with a wrong sum in the step below. * Proceed inductively to construct columns `n-1`, ..., `2`, `1`. + Note: when performing the induction on step `k`, we consider + the row sums of the first `k` columns. * Set `A = B`. Return `A`. @@ -282,10 +204,10 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): sage: p1 = [3,3,1,1] sage: p2 = [3,3,1,1] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") - [1 1 0 1] [1 1 1 0] - [0 1 0 0] + [1 1 0 1] [1 0 0 0] + [0 1 0 0] sage: p1 = [4,2,2] sage: p2 = [3,3,1,1] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") @@ -311,19 +233,19 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): sage: from sage.combinat.integer_vector import gale_ryser_theorem sage: gale_ryser_theorem([3,3,0,1,1,0], [3,1,3,1,0], algorithm = "ryser") - [1 0 1 1 0] [1 1 1 0 0] + [1 0 1 1 0] [0 0 0 0 0] - [0 0 1 0 0] [1 0 0 0 0] + [0 0 1 0 0] [0 0 0 0 0] sage: p1 = [3,1,1,1,1]; p2 = [3,2,2,0] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 1 0] - [0 0 1 0] - [0 1 0 0] [1 0 0 0] [1 0 0 0] + [0 1 0 0] + [0 0 1 0] TESTS: @@ -334,20 +256,20 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): checked for correctness.:: sage: def test_algorithm(algorithm, low = 10, high = 50): - ... n,m = randint(low,high), randint(low,high) - ... g = graphs.RandomBipartite(n, m, .3) - ... s1 = sorted(g.degree([(0,i) for i in range(n)]), reverse = True) - ... s2 = sorted(g.degree([(1,i) for i in range(m)]), reverse = True) - ... m = gale_ryser_theorem(s1, s2, algorithm = algorithm) - ... ss1 = sorted(map(lambda x : sum(x) , m.rows()), reverse = True) - ... ss2 = sorted(map(lambda x : sum(x) , m.columns()), reverse = True) - ... if ((ss1 != s1) or (ss2 != s2)): - ... print "Algorithm %s failed with this input:" % algorithm - ... print s1, s2 - - sage: for algorithm in ["gale", "ryser"]: # long time - ... for i in range(50): # long time - ... test_algorithm(algorithm, 3, 10) # long time + ....: n,m = randint(low,high), randint(low,high) + ....: g = graphs.RandomBipartite(n, m, .3) + ....: s1 = sorted(g.degree([(0,i) for i in range(n)]), reverse = True) + ....: s2 = sorted(g.degree([(1,i) for i in range(m)]), reverse = True) + ....: m = gale_ryser_theorem(s1, s2, algorithm = algorithm) + ....: ss1 = sorted(map(lambda x : sum(x) , m.rows()), reverse = True) + ....: ss2 = sorted(map(lambda x : sum(x) , m.columns()), reverse = True) + ....: if ((ss1 != s1) or (ss2 != s2)): + ....: print "Algorithm %s failed with this input:" % algorithm + ....: print s1, s2 + + sage: for algorithm in ["gale", "ryser"]: # long time + ....: for i in range(50): # long time + ....: test_algorithm(algorithm, 3, 10) # long time Null matrix:: @@ -359,14 +281,28 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): [0 0 0 0] [0 0 0 0] [0 0 0 0] - + + Check that :trac:`16638` is fixed:: + + sage: tests = [([4, 3, 3, 2, 1, 1, 1, 1, 0], [6, 5, 1, 1, 1, 1, 1]), + ....: ([4, 4, 3, 3, 1, 1, 0], [5, 5, 2, 2, 1, 1]), + ....: ([4, 4, 3, 2, 1, 1], [5, 5, 1, 1, 1, 1, 1, 0, 0]), + ....: ([3, 3, 3, 3, 2, 1, 1, 1, 0], [7, 6, 2, 1, 1, 0]), + ....: ([3, 3, 3, 1, 1, 0], [4, 4, 1, 1, 1])] + sage: for s1, s2 in tests: + ....: m = gale_ryser_theorem(s1, s2, algorithm="ryser") + ....: ss1 = sorted(map(lambda x : sum(x) , m.rows()), reverse = True) + ....: ss2 = sorted(map(lambda x : sum(x) , m.columns()), reverse = True) + ....: if ((ss1 != s1) or (ss2 != s2)): + ....: print("Error in Ryser algorithm") + ....: print(s1, s2) REFERENCES: .. [Ryser63] H. J. Ryser, Combinatorial Mathematics, - Carus Monographs, MAA, 1963. + Carus Monographs, MAA, 1963. .. [Gale57] D. Gale, A theorem on flows in networks, Pacific J. Math. - 7(1957)1073-1082. + 7(1957)1073-1082. """ from sage.combinat.partition import Partition from sage.matrix.constructor import matrix @@ -380,28 +316,39 @@ def gale_ryser_theorem(p1, p2, algorithm="gale"): # Sorts the sequences if they are not, and remembers the permutation # applied tmp = sorted(enumerate(p1), reverse=True, key=lambda x:x[1]) - r = [x[1] for x in tmp if x[1]>0] + r = [x[1] for x in tmp] r_permutation = [x-1 for x in Permutation([x[0]+1 for x in tmp]).inverse()] m = len(r) tmp = sorted(enumerate(p2), reverse=True, key=lambda x:x[1]) - s = [x[1] for x in tmp if x[1]>0] + s = [x[1] for x in tmp] s_permutation = [x-1 for x in Permutation([x[0]+1 for x in tmp]).inverse()] n = len(s) - A0 = matrix([[1]*r[j]+[0]*(n-r[j]) for j in range(m)]) - - for k in range(1,n+1): - goodcols = [i for i in range(n) if s[i]==sum(A0.column(i))] - if sum(A0.column(n-k)) != s[n-k]: - A0 = _slider01(A0,s[n-k],n-k, p1, p2, goodcols) - - # If we need to add empty rows/columns - if len(p1)!=m: - A0 = A0.stack(matrix([[0]*n]*(len(p1)-m))) - - if len(p2)!=n: - A0 = A0.transpose().stack(matrix([[0]*len(p1)]*(len(p2)-n))).transpose() + # This is the partition equivalent to the sliding algorithm + cols = [] + for t in reversed(s): + c = [0] * m + i = 0 + while t: + k = i + 1 + while k < m and r[i] == r[k]: + k += 1 + if t >= k - i: # == number rows of the same length + for j in range(i, k): + r[j] -= 1 + c[j] = 1 + t -= k - i + else: # Remove the t last rows of that length + for j in range(k-t, k): + r[j] -= 1 + c[j] = 1 + t = 0 + i = k + cols.append(c) + + # We added columns to the back instead of the front + A0 = matrix(list(reversed(cols))).transpose() # Applying the permutations to get a matrix satisfying the # order given by the input @@ -923,34 +870,30 @@ def __init__(self, n, k, constraints, category=None): sage: type(v) - TESTS: - - All the attributes below are private; don't use them! - - :: + TESTS:: - sage: IV._min_length + sage: IV.min_length 3 - sage: IV._max_length + sage: IV.max_length 3 - sage: floor = IV._floor + sage: floor = IV.floor sage: [floor(i) for i in range(1,10)] [0, 0, 0, 0, 0, 0, 0, 0, 0] - sage: ceiling = IV._ceiling + sage: ceiling = IV.ceiling sage: [ceiling(i) for i in range(1,5)] [inf, inf, inf, inf] - sage: IV._min_slope + sage: IV.min_slope 0 - sage: IV._max_slope + sage: IV.max_slope inf sage: IV = IntegerVectors(3, 10, inner=[4,1,3], min_part=2) - sage: floor = IV._floor + sage: floor = IV.floor sage: floor(0), floor(1), floor(2) (4, 2, 3) sage: IV = IntegerVectors(3, 10, outer=[4,1,3], max_part=3) - sage: ceiling = IV._ceiling + sage: ceiling = IV.ceiling sage: ceiling(0), ceiling(1), ceiling(2) (3, 1, 3) """ @@ -1072,7 +1015,7 @@ def next(self, x): [0, 0, 2] """ from sage.combinat.integer_list_old import next - return next(x, self._min_length, self._max_length, self._floor, self._ceiling, self._min_slope, self._max_slope) + return next(x, self.min_length, self.max_length, self.floor, self.ceiling, self.min_slope, self.max_slope) class IntegerVectors_nconstraints(IntegerVectors_nkconstraints): def __init__(self, n, constraints): @@ -1221,7 +1164,7 @@ def __iter__(self): """ for iv in IntegerVectors(self.n, len(self.comp)): blocks = [ IntegerVectors(iv[i], self.comp[i], max_slope=0).list() for i in range(len(self.comp))] - for parts in cartesian_product.CartesianProduct(*blocks): + for parts in itertools.product(*blocks): res = [] for part in parts: res += part diff --git a/src/sage/combinat/k_tableau.py b/src/sage/combinat/k_tableau.py index 12d7a0da215..80850a88a3d 100644 --- a/src/sage/combinat/k_tableau.py +++ b/src/sage/combinat/k_tableau.py @@ -4211,9 +4211,9 @@ def marked_given_unmarked_and_weight_iterator( cls, unmarkedT, k, weight ): if td == {}: # the tableau is empty yield StrongTableau( unmarkedT, k, [] ) else: - allmarkings = cartesian_product.CartesianProduct(*[td[v] for v in td.keys()]) + import itertools dsc = Composition(weight).descents() - for m in allmarkings: + for m in itertools.product(*td.values()): if all(((m[i][1]-m[i][0] 2): + if existence: + return False raise ValueError("The Hadamard matrix of order %s does not exist" % n) if n == 2: - return matrix([[1, 1], [1, -1]]) - if is_even(n): - N = Integer(n / 2) + if existence: + return True + M = matrix([[1, 1], [1, -1]]) elif n == 1: - return matrix([1]) - if is_prime(N - 1) and (N - 1) % 4 == 1: - return hadamard_matrix_paleyII(n) + if existence: + return True + M = matrix([1]) + elif is_prime_power(n//2 - 1) and (n//2 - 1) % 4 == 1: + if existence: + return True + M = hadamard_matrix_paleyII(n) elif n == 4 or n % 8 == 0: - had = hadamard_matrix(Integer(n / 2)) + if existence: + return hadamard_matrix(n//2,existence=True) + had = hadamard_matrix(n//2,check=False) chad1 = matrix([list(r) + list(r) for r in had.rows()]) mhad = (-1) * had R = len(had.rows()) chad2 = matrix([list(had.rows()[i]) + list(mhad.rows()[i]) for i in range(R)]) - return chad1.stack(chad2) - elif is_prime(N - 1) and (N - 1) % 4 == 3: - return hadamard_matrix_paleyI(n) + M = chad1.stack(chad2) + elif is_prime_power(n - 1) and (n - 1) % 4 == 3: + if existence: + return True + M = hadamard_matrix_paleyI(n) else: + if existence: + return Unknown raise ValueError("The Hadamard matrix of order %s is not yet implemented." % n) + if check: + assert is_hadamard_matrix(M, normalized=True) + + return M + def hadamard_matrix_www(url_file, comments=False): """ Pulls file from Sloane's database and returns the corresponding Hadamard @@ -470,21 +647,25 @@ def true(): return M -def _helper_payley_matrix(n): +def _helper_payley_matrix(n, zero_position=True): r""" Return the marix constructed in Lemma 1.19 page 291 of [SWW72]_. This function return a `n^2` matrix `M` whose rows/columns are indexed by the element of a finite field on `n` elements `x_1,...,x_n`. The value - `M_{i,j}` is equal to `\chi(x_i-x_j)`. Note that `n` must be an odd prime power. + `M_{i,j}` is equal to `\chi(x_i-x_j)`. - The elements `x_1,...,x_n` are ordered in such a way that the matrix is - symmetric with respect to its second diagonal. The matrix is symmetric if - n==4k+1, and skew-symmetric if n=4k-1. + The elements `x_1,...,x_n` are ordered in such a way that the matrix + (respectively, its submatrix obtained by removing first row and first column in the case + ``zero_position=False``) is symmetric with respect to its second diagonal. + The matrix is symmetric if `n=4k+1`, and skew-symmetric otherwise. INPUT: - - ``n`` -- a prime power + - ``n`` -- an odd prime power. + + - ``zero_position`` -- if it is true (default), place 0 of ``F_n`` in the middle, + otherwise place it first. .. SEEALSO:: @@ -499,6 +680,33 @@ def _helper_payley_matrix(n): [-1 1 0 1 -1] [-1 -1 1 0 1] [ 1 -1 -1 1 0] + + TESTS:: + + sage: _helper_payley_matrix(11,zero_position=True) + [ 0 -1 1 -1 -1 -1 1 1 1 -1 1] + [ 1 0 -1 -1 1 -1 1 -1 1 1 -1] + [-1 1 0 1 -1 -1 -1 -1 1 1 1] + [ 1 1 -1 0 1 -1 -1 1 -1 -1 1] + [ 1 -1 1 -1 0 1 -1 -1 -1 1 1] + [ 1 1 1 1 -1 0 1 -1 -1 -1 -1] + [-1 -1 1 1 1 -1 0 1 -1 1 -1] + [-1 1 1 -1 1 1 -1 0 1 -1 -1] + [-1 -1 -1 1 1 1 1 -1 0 -1 1] + [ 1 -1 -1 1 -1 1 -1 1 1 0 -1] + [-1 1 -1 -1 -1 1 1 1 -1 1 0] + sage: _helper_payley_matrix(11,zero_position=False) + [ 0 1 1 1 1 -1 1 -1 -1 -1 -1] + [-1 0 -1 1 -1 -1 1 1 1 -1 1] + [-1 1 0 -1 -1 1 1 -1 1 1 -1] + [-1 -1 1 0 1 -1 -1 -1 1 1 1] + [-1 1 1 -1 0 1 -1 1 -1 -1 1] + [ 1 1 -1 1 -1 0 -1 -1 -1 1 1] + [-1 -1 -1 1 1 1 0 1 -1 1 -1] + [ 1 -1 1 1 -1 1 -1 0 1 -1 -1] + [ 1 -1 -1 -1 1 1 1 -1 0 -1 1] + [ 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 K = GF(n,conway=True,prefix='x') @@ -508,11 +716,16 @@ def _helper_payley_matrix(n): K_pairs = set(frozenset([x,-x]) for x in K) K_pairs.discard(frozenset([0])) K_list = [None]*n + if zero_position: + zero_position=n//2 + shift=0 + else: + shift=1 + for i,(x,y) in enumerate(K_pairs): - K_list[i] = x + K_list[i+shift] = x K_list[-i-1] = y - K_list[n//2] = K(0) - + K_list[zero_position] = K(0) M = matrix(n,[[2*((x-y).is_square())-1 for x in K_list] for y in K_list]) @@ -563,7 +776,7 @@ def rshcd_from_close_prime_powers(n): REFERENCE: - .. [GS14] J.M. Goethals, and J. J. Seidel, + .. [GS14] J. M. Goethals, and J. J. Seidel, Strongly regular graphs derived from combinatorial designs, Canadian Journal of Mathematics 22(1970), 597-614, http://dx.doi.org/10.4153/CJM-1970-067-9 @@ -591,3 +804,264 @@ def rshcd_from_close_prime_powers(n): assert len(set(map(sum,HH))) == 1 assert HH**2 == n**2*I(n**2) return HH + +def williamson_goethals_seidel_skew_hadamard_matrix(a, b, c, d, check=True): + r""" + Williamson-Goethals-Seidel construction of a skew Hadamard matrix + + Given `n\times n` (anti)circulant matrices `A`, `B`, `C`, `D` with 1,-1 entries, + and satisfying `A+A^\top = 2I`, `AA^\top + BB^\top + CC^\top + DD^\top = 4nI`, + one can construct a skew Hadamard matrix of order `4n`, cf. [GS70s]_. + + INPUT: + + - ``a`` -- 1,-1 list specifying the 1st row of `A` + + - ``b`` -- 1,-1 list specifying the 1st row of `B` + + - ``d`` -- 1,-1 list specifying the 1st row of `C` + + - ``c`` -- 1,-1 list specifying the 1st row of `D` + + EXAMPLES:: + + sage: from sage.combinat.matrices.hadamard_matrix import williamson_goethals_seidel_skew_hadamard_matrix as WGS + sage: a=[ 1, 1, 1, -1, 1, -1, 1, -1, -1] + sage: b=[ 1, -1, 1, 1, -1, -1, 1, 1, -1] + sage: c=[-1, -1]+[1]*6+[-1] + sage: d=[ 1, 1, 1, -1, 1, 1, -1, 1, 1] + sage: M=WGS(a,b,c,d,check=True) + + REFERENCES: + + .. [GS70s] J.M. Goethals and J. J. Seidel, + A skew Hadamard matrix of order 36, + 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 + .. [KoSt08] C. Koukouvinos, S. Stylianou + On skew-Hadamard matrices, + Discrete Math. 308(2008) 2723-2731 + + + """ + n = len(a) + R = matrix(ZZ, n, n, lambda i,j: 1 if i+j==n-1 else 0) + A,B,C,D=map(matrix.circulant, [a,b,c,d]) + if check: + assert A*A.T+B*B.T+C*C.T+D*D.T==4*n*I(n) + 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], + [-C*R, D.T*R, A, -B.T*R], + [-D*R, -C.T*R, B.T*R, A]]) + if check: + assert is_hadamard_matrix(M, normalized=False, skew=True) + return M + +def GS_skew_hadamard_smallcases(n, existence=False, check=True): + r""" + Data for Williamson-Goethals-Seidel construction of skew Hadamard matrices + + Here we keep the data for this construction. + Namely, it needs 4 circulant matrices with extra properties, as described in + :func:`sage.combinat.matrices.hadamard_matrix.williamson_goethals_seidel_skew_hadamard_matrix` + Matrices for `n=36` and `52` are given in [GS70s]_. Matrices for `n=92` are given + in [Wall71]_. + + INPUT: + + - ``n`` -- the order of the matrix + + - ``existence`` -- if true (default), only check that we can do the construction + + - ``check`` -- if true (default), check the result. + + TESTS:: + + sage: from sage.combinat.matrices.hadamard_matrix import GS_skew_hadamard_smallcases + sage: GS_skew_hadamard_smallcases(36) + 36 x 36 dense matrix over Integer Ring... + sage: GS_skew_hadamard_smallcases(52) + 52 x 52 dense matrix over Integer Ring... + sage: GS_skew_hadamard_smallcases(92) + 92 x 92 dense matrix over Integer Ring... + sage: GS_skew_hadamard_smallcases(100) + """ + from sage.combinat.matrices.hadamard_matrix import\ + williamson_goethals_seidel_skew_hadamard_matrix as WGS + def pmtoZ(s): + return map(lambda x: 1 if x=='+' else -1, 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] + c=[-1, -1]+[1]*6+[-1] + d=[ 1, 1, 1, -1, 1, 1, -1, 1, 1] + return WGS(a,b,c,d, check=check) + if n==52: + a=pmtoZ('++++-++--+---') + b=pmtoZ('-+-++----++-+') + c=pmtoZ('--+-+++++-+++') + return WGS(a,b,c,c, check=check) + if n==92: + a = [1,-1,-1,-1,-1,-1,-1,-1, 1, 1,-1, 1,-1, 1,-1,-1, 1, 1, 1, 1, 1, 1, 1] + b = [1, 1,-1,-1, 1,-1,-1, 1, 1, 1, 1,-1,-1, 1, 1, 1, 1,-1,-1, 1,-1,-1, 1] + 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 + +_skew_had_cache={} + +def skew_hadamard_matrix(n,existence=False, skew_normalize=True, check=True): + r""" + Tries to construct a skew Hadamard matrix + + A Hadamard matrix `H` is called skew if `H=S-I`, for `I` the identity matrix + and `-S=S^\top`. Currently constructions from Section 14.1 of [Ha83]_ and few + more exotic ones are implemented. + + INPUT: + + - ``n`` (integer) -- dimension of the matrix + + - ``existence`` (boolean) -- whether to build the matrix or merely query if + a construction is available in Sage. When set to ``True``, the function + returns: + + - ``True`` -- meaning that Sage knows how to build the matrix + + - ``Unknown`` -- meaning that Sage does not know how to build the + matrix, but that the design may exist (see :mod:`sage.misc.unknown`). + + - ``False`` -- meaning that the matrix does not exist. + + - ``skew_normalize`` (boolean) -- whether to make the 1st row all-one, and + adjust the 1st column accordingly. Set to ``True`` by default. + + - ``check`` (boolean) -- whether to check that output is correct before + returning it. As this is expected to be useless (but we are cautious + guys), you may want to disable it whenever you want speed. Set to ``True`` + by default. + + EXAMPLES:: + + sage: from sage.combinat.matrices.hadamard_matrix import skew_hadamard_matrix + sage: skew_hadamard_matrix(12).det() + 2985984 + sage: 12^6 + 2985984 + sage: skew_hadamard_matrix(1) + [1] + sage: skew_hadamard_matrix(2) + [ 1 1] + [-1 1] + + TESTS:: + + sage: skew_hadamard_matrix(10,existence=True) + False + sage: skew_hadamard_matrix(12,existence=True) + True + sage: skew_hadamard_matrix(784,existence=True) + True + sage: skew_hadamard_matrix(10) + Traceback (most recent call last): + ... + ValueError: A skew Hadamard matrix of order 10 does not exist + sage: skew_hadamard_matrix(36) + 36 x 36 dense matrix over Integer Ring... + sage: skew_hadamard_matrix(36)==skew_hadamard_matrix(36,skew_normalize=False) + False + sage: skew_hadamard_matrix(52) + 52 x 52 dense matrix over Integer Ring... + sage: skew_hadamard_matrix(92) + 92 x 92 dense matrix over Integer Ring... + sage: skew_hadamard_matrix(816) # long time + 816 x 816 dense matrix over Integer Ring... + sage: skew_hadamard_matrix(100) + Traceback (most recent call last): + ... + ValueError: A skew Hadamard matrix of order 100 is not yet implemented. + sage: skew_hadamard_matrix(100,existence=True) + Unknown + + REFERENCES: + + .. [Ha83] M. Hall, + Combinatorial Theory, + 2nd edition, + Wiley, 1983 + """ + def true(): + _skew_had_cache[n]=True + return True + M = None + if existence and n in _skew_had_cache: + return True + if not(n % 4 == 0) and (n > 2): + if existence: + return False + raise ValueError("A skew Hadamard matrix of order %s does not exist" % n) + if n == 2: + if existence: + return true() + M = matrix([[1, 1], [-1, 1]]) + elif n == 1: + if existence: + return true() + M = matrix([1]) + elif is_prime_power(n - 1) and ((n - 1) % 4 == 3): + if existence: + return true() + M = hadamard_matrix_paleyI(n, normalize=False) + + elif n % 8 == 0: + if skew_hadamard_matrix(n//2,existence=True): # (Lemma 14.1.6 in [Ha83]_) + if existence: + return true() + H = skew_hadamard_matrix(n//2,check=False) + M = block_matrix([[H,H], [-H.T,H.T]]) + + else: # try Williamson construction (Lemma 14.1.5 in [Ha83]_) + for d in divisors(n)[2:-2]: # skip 1, 2, n/2, and n + n1 = n//d + if is_prime_power(d - 1) and (d % 4 == 0) and (n1 % 4 == 0)\ + and skew_hadamard_matrix(n1,existence=True): + if existence: + return true() + H = skew_hadamard_matrix(n1, check=False)-I(n1) + U = matrix(ZZ, d, lambda i, j: -1 if i==j==0 else\ + 1 if i==j==1 or (i>1 and j-1==d-i)\ + else 0) + A = block_matrix([[matrix([0]), matrix(ZZ,1,d-1,[1]*(d-1))], + [ matrix(ZZ,d-1,1,[-1]*(d-1)), + _helper_payley_matrix(d-1,zero_position=0)]])+I(d) + M = A.tensor_product(I(n1))+(U*A).tensor_product(H) + break + if M is None: # try Williamson-Goethals-Seidel construction + if GS_skew_hadamard_smallcases(n, existence=True): + if existence: + return true() + M = GS_skew_hadamard_smallcases(n) + + else: + if existence: + return Unknown + raise ValueError("A skew Hadamard matrix of order %s is not yet implemented." % n) + if skew_normalize: + dd = diagonal_matrix(M[0]) + M = dd*M*dd + if check: + assert is_hadamard_matrix(M, normalized=False, skew=True) + if skew_normalize: + from sage.modules.free_module_element import vector + assert M[0]==vector([1]*n) + _skew_had_cache[n]=True + return M diff --git a/src/sage/combinat/ncsf_qsym/ncsf.py b/src/sage/combinat/ncsf_qsym/ncsf.py index d6ba56f6478..b1eb82a11b8 100644 --- a/src/sage/combinat/ncsf_qsym/ncsf.py +++ b/src/sage/combinat/ncsf_qsym/ncsf.py @@ -32,6 +32,7 @@ from sage.functions.other import factorial from sage.categories.realizations import Category_realization_of_parent from sage.categories.rings import Rings +from sage.categories.fields import Fields from sage.categories.graded_hopf_algebras import GradedHopfAlgebras from sage.combinat.composition import Compositions from sage.combinat.free_module import CombinatorialFreeModule @@ -401,9 +402,12 @@ def __init__(self, R): r""" TESTS:: + sage: NCSF1 = NonCommutativeSymmetricFunctions(FiniteField(23)) + sage: NCSF2 = NonCommutativeSymmetricFunctions(Integers(23)) sage: TestSuite(NonCommutativeSymmetricFunctions(QQ)).run() """ - assert(R in Rings()) + # change the line below to assert(R in Rings()) once MRO issues from #15536, #15475 are resolved + assert(R in Fields() or R in Rings()) # side effect of this statement assures MRO exists for R self._base = R # Won't be needed once CategoryObject won't override base_ring Parent.__init__(self, category = GradedHopfAlgebras(R).WithRealizations()) @@ -951,8 +955,9 @@ def bernstein_creation_operator(self, n): ....: for i in reversed(xs): ....: res = res.bernstein_creation_operator(i) ....: return res + sage: import itertools sage: all( immaculate_by_bernstein(p) == I.immaculate_function(p) - ....: for p in CartesianProduct(range(-1, 3), range(-1, 3), range(-1, 3)) ) + ....: for p in itertools.product(range(-1, 3), repeat=3)) True Some examples:: diff --git a/src/sage/combinat/ncsf_qsym/qsym.py b/src/sage/combinat/ncsf_qsym/qsym.py index 26568000798..2434430c93e 100644 --- a/src/sage/combinat/ncsf_qsym/qsym.py +++ b/src/sage/combinat/ncsf_qsym/qsym.py @@ -65,8 +65,8 @@ from sage.misc.bindable_class import BindableClass from sage.categories.graded_hopf_algebras import GradedHopfAlgebras -from sage.categories.all import CommutativeRings from sage.categories.rings import Rings +from sage.categories.fields import Fields from sage.categories.realizations import Category_realization_of_parent from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation @@ -529,9 +529,12 @@ def __init__(self, R): sage: QuasiSymmetricFunctions(QQ) Quasisymmetric functions over the Rational Field + sage: QSym1 = QuasiSymmetricFunctions(FiniteField(23)) + sage: QSym2 = QuasiSymmetricFunctions(Integers(23)) sage: TestSuite(QuasiSymmetricFunctions(QQ)).run() """ - assert R in Rings() + # change the line below to assert(R in Rings()) once MRO issues from #15536, #15475 are resolved + assert(R in Fields() or R in Rings()) # side effect of this statement assures MRO exists for R self._base = R # Won't be needed once CategoryObject won't override base_ring category = GradedHopfAlgebras(R) # TODO: .Commutative() self._category = category diff --git a/src/sage/combinat/ncsf_qsym/tutorial.py b/src/sage/combinat/ncsf_qsym/tutorial.py index 1c987793d64..5cf429b0d17 100644 --- a/src/sage/combinat/ncsf_qsym/tutorial.py +++ b/src/sage/combinat/ncsf_qsym/tutorial.py @@ -92,8 +92,8 @@ sage: y.expand(4) x0*x1^2*x2 + x0*x1^2*x3 + x0*x2^2*x3 + x1*x2^2*x3 -The usual methods on free modules are available such as coefficients, degrees, -and the support:: +The usual methods on free modules are available such as coefficients, +degrees, and the support:: sage: z=3*M[1,2]+M[3]^2; z 3*M[1, 2] + 2*M[3, 3] + M[6] @@ -104,10 +104,10 @@ sage: z.degree() 6 - sage: z.coefficients() - [3, 2, 1] + sage: sorted(z.coefficients()) + [1, 2, 3] - sage: z.monomials() + sage: sorted(z.monomials(), key=lambda x: x.support()) [M[1, 2], M[3, 3], M[6]] sage: z.monomial_coefficients() diff --git a/src/sage/combinat/ncsym/dual.py b/src/sage/combinat/ncsym/dual.py index 3a01c975a1a..11f6a79be99 100644 --- a/src/sage/combinat/ncsym/dual.py +++ b/src/sage/combinat/ncsym/dual.py @@ -17,6 +17,8 @@ from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.categories.graded_hopf_algebras import GradedHopfAlgebras +from sage.categories.rings import Rings +from sage.categories.fields import Fields from sage.combinat.ncsym.bases import NCSymDualBases, NCSymBasis_abstract from sage.combinat.partition import Partition @@ -39,8 +41,12 @@ def __init__(self, R): EXAMPLES:: + sage: NCSymD1 = SymmetricFunctionsNonCommutingVariablesDual(FiniteField(23)) + sage: NCSymD2 = SymmetricFunctionsNonCommutingVariablesDual(Integers(23)) sage: TestSuite(SymmetricFunctionsNonCommutingVariables(QQ).dual()).run() """ + # change the line below to assert(R in Rings()) once MRO issues from #15536, #15475 are resolved + assert(R in Fields() or R in Rings()) # side effect of this statement assures MRO exists for R self._base = R # Won't be needed once CategoryObject won't override base_ring category = GradedHopfAlgebras(R) # TODO: .Commutative() Parent.__init__(self, category=category.WithRealizations()) diff --git a/src/sage/combinat/ncsym/ncsym.py b/src/sage/combinat/ncsym/ncsym.py index 27a63cc7921..6cdd45766b1 100644 --- a/src/sage/combinat/ncsym/ncsym.py +++ b/src/sage/combinat/ncsym/ncsym.py @@ -18,6 +18,8 @@ from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.categories.graded_hopf_algebras import GradedHopfAlgebras +from sage.categories.rings import Rings +from sage.categories.fields import Fields from sage.functions.other import factorial from sage.combinat.free_module import CombinatorialFreeModule @@ -282,8 +284,12 @@ def __init__(self, R): EXAMPLES:: + sage: NCSym1 = SymmetricFunctionsNonCommutingVariables(FiniteField(23)) + sage: NCSym2 = SymmetricFunctionsNonCommutingVariables(Integers(23)) sage: TestSuite(SymmetricFunctionsNonCommutingVariables(QQ)).run() """ + # change the line below to assert(R in Rings()) once MRO issues from #15536, #15475 are resolved + assert(R in Fields() or R in Rings()) # side effect of this statement assures MRO exists for R self._base = R # Won't be needed once CategoryObject won't override base_ring category = GradedHopfAlgebras(R) # TODO: .Commutative() Parent.__init__(self, category = category.WithRealizations()) diff --git a/src/sage/combinat/ordered_tree.py b/src/sage/combinat/ordered_tree.py index a0e39880a95..b69f00686ab 100644 --- a/src/sage/combinat/ordered_tree.py +++ b/src/sage/combinat/ordered_tree.py @@ -14,6 +14,9 @@ # the License, or (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** + +import itertools + from sage.structure.list_clone import ClonableArray, ClonableList from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation @@ -940,7 +943,6 @@ def _element_constructor_(self, *args, **keywords): from sage.misc.lazy_attribute import lazy_attribute from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.combinat.composition import Compositions -from sage.combinat.cartesian_product import CartesianProduct ################################################################# # Enumerated set of binary trees of a given size ################################################################# @@ -1073,7 +1075,7 @@ def __iter__(self): return else: for c in Compositions(self._size - 1): - for lst in CartesianProduct(*[self.__class__(_) for _ in c]): + for lst in itertools.product(*[self.__class__(_) for _ in c]): yield self._element_constructor_(lst) @lazy_attribute diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index cc4d0db3927..2a9b05a9482 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -311,7 +311,7 @@ from sage.combinat.partitions import number_of_partitions as bober_number_of_partitions from sage.combinat.partitions import ZS1_iterator, ZS1_iterator_nk from sage.combinat.integer_vector import IntegerVectors -from sage.combinat.integer_list import IntegerListsLex +from sage.combinat.integer_lists import IntegerListsLex from sage.combinat.root_system.weyl_group import WeylGroup from sage.combinat.combinatorial_map import combinatorial_map from sage.groups.perm_gps.permgroup import PermutationGroup @@ -4635,14 +4635,12 @@ def coloring(i): if directed: from sage.graphs.digraph import DiGraph - G = DiGraph(edges, multiedges=True) - G.add_vertices(T) # Add isolated vertices - self._DDEG = G.copy(immutable=True) + self._DDEG = DiGraph([T, edges], format="vertices_and_edges", + immutable=True, multiedges=True) else: from sage.graphs.graph import Graph - G = Graph(edges, multiedges=True) - G.add_vertices(T) # Add isolated vertices - self._DEG = G.copy(immutable=True) + self._DEG = Graph([T, edges], format="vertices_and_edges", + immutable=True, multiedges=True) return self.dual_equivalence_graph(directed, coloring) ############## @@ -7548,7 +7546,7 @@ def number_of_partitions(n, algorithm='default'): [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1], [2, 1, 1, 1], [1, 1, 1, 1, 1]] sage: len(v) 7 - sage: number_of_partitions(5, algorithm='bober') + sage: number_of_partitions(5, algorithm='bober') 7 The input must be a nonnegative integer or a ``ValueError`` is raised. diff --git a/src/sage/combinat/partition_tuple.py b/src/sage/combinat/partition_tuple.py index 6b7fa5e0e87..06ec9991a60 100644 --- a/src/sage/combinat/partition_tuple.py +++ b/src/sage/combinat/partition_tuple.py @@ -256,7 +256,8 @@ class of modules for the algebras, which are generalisations of the Specht # http://www.gnu.org/licenses/ #***************************************************************************** -from cartesian_product import CartesianProduct +import itertools + from combinat import CombinatorialElement from integer_vector import IntegerVectors from partition import Partition, Partitions, Partitions_n, _Partitions @@ -1564,7 +1565,7 @@ def _element_constructor_(self, mu): """ # one way or another these two cases need to be treated separately - if mu==[] or mu==[[]]: + if mu == [] or mu == () or mu == [[]]: return Partition([]) # As partitions are 1-tuples of partitions we need to treat them separately @@ -2051,7 +2052,7 @@ def __iter__(self): """ p = [Partitions(i) for i in range(self.size()+1)] for iv in IntegerVectors(self.size(),self.level()): - for cp in CartesianProduct(*[p[i] for i in iv]): + for cp in itertools.product(*[p[i] for i in iv]): yield self._element_constructor_(cp) diff --git a/src/sage/combinat/posets/__init__.py b/src/sage/combinat/posets/__init__.py index 9f5c8c89853..61b51a2423d 100644 --- a/src/sage/combinat/posets/__init__.py +++ b/src/sage/combinat/posets/__init__.py @@ -17,6 +17,8 @@ - :ref:`sage.combinat.posets.cartesian_product` +- :ref:`sage.combinat.posets.moebius_algebra` + - :ref:`sage.combinat.tamari_lattices` - :ref:`sage.combinat.interval_posets` - :ref:`sage.combinat.shard_order` diff --git a/src/sage/combinat/posets/hasse_diagram.py b/src/sage/combinat/posets/hasse_diagram.py index ecd358c4abb..b6baeb31ccc 100644 --- a/src/sage/combinat/posets/hasse_diagram.py +++ b/src/sage/combinat/posets/hasse_diagram.py @@ -376,6 +376,8 @@ def is_chain(self): sage: p.is_chain() False """ + if self.cardinality() == 0: + return True return (self.num_edges()+1 == self.num_verts() and # Hasse Diagram is a tree all(d<=1 for d in self.out_degree()) and # max outdegree is <= 1 all(d<=1 for d in self.in_degree())) # max indegree is <= 1 diff --git a/src/sage/combinat/posets/incidence_algebras.py b/src/sage/combinat/posets/incidence_algebras.py index 51525c96168..c9bde6b2a93 100644 --- a/src/sage/combinat/posets/incidence_algebras.py +++ b/src/sage/combinat/posets/incidence_algebras.py @@ -69,7 +69,7 @@ def __init__(self, R, P, prefix='I'): if P in FiniteEnumeratedSets(): cat = cat.FiniteDimensional() self._poset = P - CombinatorialFreeModule.__init__(self, R, map(tuple, P.intervals()), + CombinatorialFreeModule.__init__(self, R, map(tuple, P.relations()), prefix=prefix, category=cat) def _repr_term(self, A): diff --git a/src/sage/combinat/posets/lattices.py b/src/sage/combinat/posets/lattices.py index 7e316701d31..0b985eeb448 100644 --- a/src/sage/combinat/posets/lattices.py +++ b/src/sage/combinat/posets/lattices.py @@ -55,6 +55,7 @@ # # http://www.gnu.org/licenses/ #***************************************************************************** + from sage.categories.finite_lattice_posets import FiniteLatticePosets from sage.combinat.posets.posets import Poset, FinitePoset from sage.combinat.posets.elements import (LatticePosetElement, @@ -1147,6 +1148,37 @@ def frattini_sublattice(self): return LatticePoset(self.subposet([self[x] for x in self._hasse_diagram.frattini_sublattice()])) + def moebius_algebra(self, R): + """ + Return the Mobius algebra of ``self`` over ``R``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: L.moebius_algebra(QQ) + Moebius algebra of Finite lattice containing 16 elements over Rational Field + """ + from sage.combinat.posets.moebius_algebra import MoebiusAlgebra + return MoebiusAlgebra(R, self) + + def quantum_moebius_algebra(self, q=None): + """ + Return the quantum Mobius algebra of ``self`` with parameter ``q``. + + INPUT: + + - ``q`` -- (optional) the deformation parameter `q` + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: L.quantum_moebius_algebra() + Quantum Moebius algebra of Finite lattice containing 16 elements + with q=q over Univariate Laurent Polynomial Ring in q over Integer Ring + """ + from sage.combinat.posets.moebius_algebra import QuantumMoebiusAlgebra + return QuantumMoebiusAlgebra(self, q) + ############################################################################ FiniteMeetSemilattice._dual_class = FiniteJoinSemilattice diff --git a/src/sage/combinat/posets/moebius_algebra.py b/src/sage/combinat/posets/moebius_algebra.py new file mode 100644 index 00000000000..1d57ca89062 --- /dev/null +++ b/src/sage/combinat/posets/moebius_algebra.py @@ -0,0 +1,732 @@ +# -*- coding: utf-8 -*- +r""" +Möbius Algebras +""" +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw , +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# The full text of the GPL is available at: +# +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.misc.bindable_class import BindableClass +from sage.misc.lazy_attribute import lazy_attribute +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.categories.algebras import Algebras +from sage.categories.realizations import Realizations, Category_realization_of_parent +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.combinat.posets.lattices import LatticePoset +from sage.combinat.free_module import CombinatorialFreeModule +from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing +from sage.rings.all import ZZ + +class BasisAbstract(CombinatorialFreeModule, BindableClass): + """ + Abstract base class for a basis. + """ + def __getitem__(self, x): + """ + Return the basis element indexed by ``x``. + + INPUT: + + - ``x`` -- an element of the lattice + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.moebius_algebra(QQ).E() + sage: E[5] + E[5] + sage: C = L.quantum_moebius_algebra().C() + sage: C[5] + C[5] + """ + L = self.realization_of()._lattice + return self.monomial(L(x)) + +class MoebiusAlgebra(Parent, UniqueRepresentation): + r""" + The möbius algebra of a lattice. + + Let `L` be a lattice. The *Möbius algebra* `M_L` was originally + constructed by Solomon and has a natural basis + `\{ E_x \mid x \in L \}` with multiplication given by + `E_x \cdot E_y = E_{x \vee y}`. Moreover this has a basis given by + orthogonal idempotents `\{ I_x \mid x \in L \}` (so + `I_x I_y = \delta_{xy} I_x` where `\delta` is the Kronecker delta) + related to the natural basis by + + .. MATH:: + + I_x = \sum_{y \leq x} \mu_L(y, x) E_x, + + where `\mu_L` is the Möbius function of `L`. + + REFERENCES: + + .. [Greene73] Curtis Greene. + *On the Möbius algebra of a partially ordered set*. + Advances in Mathematics, **10**, 1973. + :doi:`10.1016/0001-8708(73)90106-0`. + + .. [Etienne98] Gwihen Etienne. + *On the Möbius algebra of geometric lattices*. + European Journal of Combinatorics, **19**, 1998. + :doi:`10.1006/eujc.1998.0227`. + """ + def __init__(self, R, L): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: TestSuite(M).run() + """ + if not L.is_lattice(): + raise ValueError("L must be a lattice") + cat = Algebras(R).Commutative().WithBasis() + if L in FiniteEnumeratedSets(): + cat = cat.FiniteDimensional() + self._lattice = L + self._category = cat + Parent.__init__(self, base=R, category=self._category.WithRealizations()) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: L.moebius_algebra(QQ) + Moebius algebra of Finite lattice containing 16 elements over Rational Field + """ + return "Moebius algebra of {} over {}".format(self._lattice, self.base_ring()) + + def a_realization(self): + r""" + Return a particular realization of ``self`` (the `B`-basis). + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: M.a_realization() + Moebius algebra of Finite lattice containing 16 elements + over Rational Field in the natural basis + """ + return self.E() + + def lattice(self): + """ + Return the defining lattice of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: M.lattice() + Finite lattice containing 16 elements + sage: M.lattice() == L + True + """ + return self._lattice + + class E(BasisAbstract): + r""" + The natural basis of a Möbius algebra. + + Let `E_x` and `E_y` be basis elements of `M_L` for some lattice `L`. + Multiplication is given by `E_x E_y = E_{x \vee y}`. + """ + def __init__(self, M, prefix='E'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: TestSuite(M.E()).run() + """ + self._basis_name = "natural" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + @cached_method + def _to_idempotent_basis(self, x): + """ + Convert the element indexed by ``x`` to the idempotent basis. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: E = M.E() + sage: all(E(E._to_idempotent_basis(x)) == E.monomial(x) + ....: for x in E.basis().keys()) + True + """ + M = self.realization_of() + I = M.idempotent() + return I.sum_of_monomials(M._lattice.order_ideal([x])) + + def product_on_basis(self, x, y): + """ + Return the product of basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.moebius_algebra(QQ).E() + sage: E.product_on_basis(5, 14) + E[15] + sage: E.product_on_basis(2, 8) + E[10] + """ + return self.monomial(self.realization_of()._lattice.join(x, y)) + + @cached_method + def one(self): + """ + Return the element ``1`` of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.moebius_algebra(QQ).E() + sage: E.one() + E[0] + """ + elts = self.realization_of()._lattice.minimal_elements() + return self.sum_of_monomials(elts) + + natural = E + + class I(BasisAbstract): + """ + The (orthogonal) idempotent basis of a Möbius algebra. + + Let `I_x` and `I_y` be basis elements of `M_L` for some lattice `L`. + Multiplication is given by `I_x I_y = \delta_{xy} I_x` where + `\delta_{xy}` is the Kronecker delta. + """ + def __init__(self, M, prefix='I'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.moebius_algebra(QQ) + sage: TestSuite(M.I()).run() + """ + self._basis_name = "idempotent" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + ## Change of basis: + E = M.E() + self.module_morphism(self._to_natural_basis, + codomain=E, category=self.category(), + triangular='upper', unitriangular=True + ).register_as_coercion() + + E.module_morphism(E._to_idempotent_basis, + codomain=self, category=self.category(), + triangular='upper', unitriangular=True + ).register_as_coercion() + + + @cached_method + def _to_natural_basis(self, x): + """ + Convert the element indexed by ``x`` to the natural basis. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: I = M.I() + sage: all(I(I._to_natural_basis(x)) == I.monomial(x) + ....: for x in I.basis().keys()) + True + """ + M = self.realization_of() + N = M.natural() + mobius = M._lattice.mobius_function + return N.sum_of_terms((y, mobius(y,x)) for y in M._lattice.order_ideal([x])) + + def product_on_basis(self, x, y): + """ + Return the product of basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: I = L.moebius_algebra(QQ).I() + sage: I.product_on_basis(5, 14) + 0 + sage: I.product_on_basis(2, 2) + I[2] + """ + if x == y: + return self.monomial(x) + return self.zero() + + @cached_method + def one(self): + """ + Return the element ``1`` of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: I = L.moebius_algebra(QQ).I() + sage: I.one() + I[0] + I[1] + I[2] + I[3] + I[4] + I[5] + I[6] + I[7] + I[8] + + I[9] + I[10] + I[11] + I[12] + I[13] + I[14] + I[15] + """ + return self.sum_of_monomials(self.realization_of()._lattice) + + def __getitem__(self, x): + """ + Return the basis element indexed by ``x``. + + INPUT: + + - ``x`` -- an element of the lattice + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: I = L.moebius_algebra(QQ).I() + sage: I[5] + I[5] + """ + L = self.realization_of()._lattice + return self.monomial(L(x)) + + idempotent = I + +class QuantumMoebiusAlgebra(Parent, UniqueRepresentation): + r""" + The quantum Möbius algebra of a lattice. + + Let `L` be a lattice, and we define the *quantum Möbius algebra* `M_L(q)` + as the algebra with basis `\{ E_x \mid x \in L \}` with + multiplication given by + + .. MATH:: + + E_x E_y = \sum_{z \geq a \geq x \vee y} \mu_L(a, z) + q^{\operatorname{crk} a} E_z, + + where `\mu_L` is the Möbius function of `L` and `\operatorname{crk}` + is the corank function (i.e., `\operatorname{crk} a = + \operatorname{rank} L - \operatorname{rank}` a). At `q = 1`, this + reduces to the multiplication formula originally given by Solomon. + """ + def __init__(self, L, q=None): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: TestSuite(M).run() # long time + """ + if not L.is_lattice(): + raise ValueError("L must be a lattice") + if q is None: + q = LaurentPolynomialRing(ZZ, 'q').gen() + self._q = q + R = q.parent() + cat = Algebras(R).WithBasis() + if L in FiniteEnumeratedSets(): + cat = cat.Commutative().FiniteDimensional() + self._lattice = L + self._category = cat + Parent.__init__(self, base=R, category=self._category.WithRealizations()) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: L.quantum_moebius_algebra() + Quantum Moebius algebra of Finite lattice containing 16 elements + with q=q over Univariate Laurent Polynomial Ring in q over Integer Ring + """ + return "Quantum Moebius algebra of {} with q={} over {}".format( + self._lattice, self._q, self.base_ring()) + + def a_realization(self): + r""" + Return a particular realization of ``self`` (the `B`-basis). + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: M.a_realization() + Quantum Moebius algebra of Finite lattice containing 16 elements + with q=q over Univariate Laurent Polynomial Ring in q + over Integer Ring in the natural basis + """ + return self.E() + + def lattice(self): + """ + Return the defining lattice of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: M.lattice() + Finite lattice containing 16 elements + sage: M.lattice() == L + True + """ + return self._lattice + + class E(BasisAbstract): + r""" + The natural basis of a quantum Möbius algebra. + + Let `E_x` and `E_y` be basis elements of `M_L` for some lattice `L`. + Multiplication is given by + + .. MATH:: + + E_x E_y = \sum_{z \geq a \geq x \vee y} \mu_L(a, z) + q^{\operatorname{crk} a} E_z, + + where `\mu_L` is the Möbius function of `L` and `\operatorname{crk}` + is the corank function (i.e., `\operatorname{crk} a = + \operatorname{rank} L - \operatorname{rank}` a). + """ + def __init__(self, M, prefix='E'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: TestSuite(M.E()).run() # long time + """ + self._basis_name = "natural" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + def product_on_basis(self, x, y): + """ + Return the product of basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.quantum_moebius_algebra().E() + sage: E.product_on_basis(5, 14) + E[15] + sage: E.product_on_basis(2, 8) + q^2*E[10] + (q-q^2)*E[11] + (q-q^2)*E[14] + (1-2*q+q^2)*E[15] + """ + L = self.realization_of()._lattice + q = self.realization_of()._q + mobius = L.mobius_function + rank = L.rank_function() + R = L.rank() + j = L.join(x,y) + return self.sum_of_terms(( z, mobius(a,z) * q**(R - rank(a)) ) + for z in L.order_filter([j]) + for a in L.closed_interval(j, z)) + + @cached_method + def one(self): + """ + Return the element ``1`` of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: E = L.quantum_moebius_algebra().E() + sage: all(E.one() * b == b for b in E.basis()) + True + """ + L = self.realization_of()._lattice + q = self.realization_of()._q + mobius = L.mobius_function + rank = L.rank_function() + R = L.rank() + return self.sum_of_terms((x, mobius(y,x) * q**(rank(y) - R)) + for x in L for y in L.order_ideal([x])) + + natural = E + + class C(BasisAbstract): + r""" + The characteristic basis of a quantum Möbius algebra. + + The characteristic basis `\{ C_x \mid x \in L \}` of `M_L` + for some lattice `L` is defined by + + .. MATH:: + + C_x = \sum_{a \geq x} P(F^x; q) E_a, + + where `F^x = \{ y \in L \mid y \geq x \}` is the principal order + filter of `x` and `P(F^x; q)` is the characteristic polynomial + of the (sub)poset `F^x`. + """ + def __init__(self, M, prefix='C'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(3) + sage: M = L.quantum_moebius_algebra() + sage: TestSuite(M.C()).run() # long time + """ + self._basis_name = "characteristic" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + ## Change of basis: + E = M.E() + phi = self.module_morphism(self._to_natural_basis, + codomain=E, category=self.category(), + triangular='lower', unitriangular=True) + + phi.register_as_coercion() + (~phi).register_as_coercion() + + @cached_method + def _to_natural_basis(self, x): + """ + Convert the element indexed by ``x`` to the natural basis. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).quantum_moebius_algebra() + sage: C = M.C() + sage: all(C(C._to_natural_basis(x)) == C.monomial(x) + ....: for x in C.basis().keys()) + True + """ + M = self.realization_of() + N = M.natural() + q = M._q + R = M.base_ring() + L = M._lattice + poly = lambda x,y: L.subposet(L.closed_interval(x, y)).characteristic_polynomial() + # This is a workaround until #17554 is fixed... + subs = lambda p,q: R.sum( c * q**e for e,c in enumerate(p.list()) ) + # ...at which point, we can do poly(x,y)(q=q) + return N.sum_of_terms((y, subs(poly(x,y), q)) + for y in L.order_filter([x])) + + characteristic_basis = C + + class KL(BasisAbstract): + """ + The Kazhdan-Lusztig basis of a quantum Möbius algebra. + + The Kazhdan-Lusztig basis `\{ B_x \mid x \in L \}` of `M_L` + for some lattice `L` is defined by + + .. MATH:: + + B_x = \sum_{y \geq x} P_{x,y}(q) E_a, + + where `P_{x,y}(q)` is the Kazhdan-Lusztig polynomial of `L`, + following the definition given in [EPW14]_. + + EXAMPLES: + + We construct some examples of Proposition 4.5 of [EPW14]_:: + + sage: M = posets.BooleanLattice(4).quantum_moebius_algebra() + sage: KL = M.KL() + sage: KL[4] * KL[5] + (q^2+q^3)*KL[5] + (q+2*q^2+q^3)*KL[7] + (q+2*q^2+q^3)*KL[13] + + (1+3*q+3*q^2+q^3)*KL[15] + sage: KL[4] * KL[15] + (1+3*q+3*q^2+q^3)*KL[15] + sage: KL[4] * KL[10] + (q+3*q^2+3*q^3+q^4)*KL[14] + (1+4*q+6*q^2+4*q^3+q^4)*KL[15] + """ + def __init__(self, M, prefix='KL'): + """ + Initialize ``self``. + + TESTS:: + + sage: L = posets.BooleanLattice(4) + sage: M = L.quantum_moebius_algebra() + sage: TestSuite(M.KL()).run() # long time + """ + self._basis_name = "Kazhdan-Lusztig" + CombinatorialFreeModule.__init__(self, M.base_ring(), + tuple(M._lattice), + prefix=prefix, + category=MoebiusAlgebraBases(M)) + + ## Change of basis: + E = M.E() + phi = self.module_morphism(self._to_natural_basis, + codomain=E, category=self.category(), + triangular='lower', unitriangular=True) + + phi.register_as_coercion() + (~phi).register_as_coercion() + + @cached_method + def _to_natural_basis(self, x): + """ + Convert the element indexed by ``x`` to the natural basis. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).quantum_moebius_algebra() + sage: KL = M.KL() + sage: all(KL(KL._to_natural_basis(x)) == KL.monomial(x) # long time + ....: for x in KL.basis().keys()) + True + """ + M = self.realization_of() + L = M._lattice + E = M.E() + q = M._q + R = M.base_ring() + rank = L.rank_function() + # This is a workaround until #17554 is fixed... + subs = lambda p,q: R.sum( c * q**e for e,c in enumerate(p.list()) ) + return E.sum_of_terms((y, q**(rank(y) - rank(x)) * + subs(L.kazhdan_lusztig_polynomial(x, y), q**-2)) + for y in L.order_filter([x])) + + kazhdan_lusztig = KL + +class MoebiusAlgebraBases(Category_realization_of_parent): + r""" + The category of bases of a Möbius algebra. + + INPUT: + + - ``base`` -- a Möbius algebra + + TESTS:: + + sage: from sage.combinat.posets.moebius_algebra import MoebiusAlgebraBases + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: bases = MoebiusAlgebraBases(M) + sage: M.E() in bases + True + """ + def _repr_(self): + r""" + Return the representation of ``self``. + + EXAMPLES:: + + sage: from sage.combinat.posets.moebius_algebra import MoebiusAlgebraBases + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: MoebiusAlgebraBases(M) + Category of bases of Moebius algebra of Finite lattice + containing 16 elements over Rational Field + """ + return "Category of bases of {}".format(self.base()) + + def super_categories(self): + r""" + The super categories of ``self``. + + EXAMPLES:: + + sage: from sage.combinat.posets.moebius_algebra import MoebiusAlgebraBases + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: bases = MoebiusAlgebraBases(M) + sage: bases.super_categories() + [Category of finite dimensional commutative algebras with basis over Rational Field, + Category of realizations of Moebius algebra of Finite lattice + containing 16 elements over Rational Field] + """ + return [self.base()._category, Realizations(self.base())] + + class ParentMethods: + def _repr_(self): + """ + Text representation of this basis of a Möbius algebra. + + EXAMPLES:: + + sage: M = posets.BooleanLattice(4).moebius_algebra(QQ) + sage: M.E() + Moebius algebra of Finite lattice containing 16 elements + over Rational Field in the natural basis + sage: M.I() + Moebius algebra of Finite lattice containing 16 elements + over Rational Field in the idempotent basis + """ + return "{} in the {} basis".format(self.realization_of(), self._basis_name) + + def product_on_basis(self, x, y): + """ + Return the product of basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: C = L.quantum_moebius_algebra().C() + sage: C.product_on_basis(5, 14) + q^3*C[15] + sage: C.product_on_basis(2, 8) + q^4*C[10] + """ + R = self.realization_of().a_realization() + return self(R(self.monomial(x)) * R(self.monomial(y))) + + @cached_method + def one(self): + """ + Return the element ``1`` of ``self``. + + EXAMPLES:: + + sage: L = posets.BooleanLattice(4) + sage: C = L.quantum_moebius_algebra().C() + sage: all(C.one() * b == b for b in C.basis()) + True + """ + R = self.realization_of().a_realization() + return self(R.one()) + + class ElementMethods: + pass + diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index bd2afa2c60c..912e3001af7 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -12,29 +12,29 @@ :class:`FinitePoset` | A class for finite posets :class:`FinitePosets_n` | A class for finite posets up to isomorphism (i.e. unlabeled posets) :meth:`Poset` | Construct a finite poset from various forms of input data. - :meth:`is_poset` | Tests whether a directed graph is acyclic and transitively reduced. + :meth:`is_poset` | Return ``True`` if a directed graph is acyclic and transitively reduced. List of Poset methods --------------------- -**Comparing & intervals** +**Comparing, intervals and relations** .. csv-table:: :class: contentstable :widths: 30, 70 :delim: | - :meth:`~FinitePoset.is_greater_than` | Return ``True`` if `x` is greater than but not equal to `y` in the poset, and ``False`` otherwise. - :meth:`~FinitePoset.is_gequal` | Return ``True`` if `x` is greater than or equal to `y` in the poset, and ``False`` otherwise. - :meth:`~FinitePoset.is_less_than` | Return ``True`` if `x` is less than but not equal to `y` in the poset, and ``False`` otherwise. - :meth:`~FinitePoset.is_lequal` | Return ``True`` if `x` is less than or equal to `y` in the poset, and ``False`` otherwise. - :meth:`~FinitePoset.compare_elements` | Compares `x` and `y` in the poset. - :meth:`~FinitePoset.closed_interval` | Return a list of the elements `z` such that `x \le z \le y`. - :meth:`~FinitePoset.open_interval` | Return a list of the elements `z` such that `x < z < y`. - :meth:`~FinitePoset.intervals` | Return a list of all intervals of the poset. - :meth:`~FinitePoset.intervals_iterator` | Return an iterator for all the intervals of the poset. - :meth:`~FinitePoset.order_filter` | Return the order filter generated by a list of elements. - :meth:`~FinitePoset.order_ideal` | Return the order ideal generated by a list of elements. + :meth:`~FinitePoset.is_less_than` | Return ``True`` if `x` is strictly less than `y` in the poset. + :meth:`~FinitePoset.is_greater_than` | Return ``True`` if `x` is strictly greater than `y` in the poset. + :meth:`~FinitePoset.is_lequal` | Return ``True`` if `x` is less than or equal to `y` in the poset. + :meth:`~FinitePoset.is_gequal` | Return ``True`` if `x` is greater than or equal to `y` in the poset. + :meth:`~FinitePoset.compare_elements` | Compare two element of the poset. + :meth:`~FinitePoset.closed_interval` | Return the list of elements in a closed interval of the poset. + :meth:`~FinitePoset.open_interval` | Return the list of elements in an open interval of the poset. + :meth:`~FinitePoset.relations` | Return the list of relations in the poset. + :meth:`~FinitePoset.relations_iterator` | Return an iterator over relations in the poset. + :meth:`~FinitePoset.order_filter` | Return the upper set generated by elements. + :meth:`~FinitePoset.order_ideal` | Return the lower set generated by elements. **Covering** @@ -65,16 +65,16 @@ :meth:`~FinitePoset.dimension` | Return the dimension of the poset. :meth:`~FinitePoset.has_bottom` | Return ``True`` if the poset has a unique minimal element. :meth:`~FinitePoset.has_top` | Return ``True`` if the poset has a unique maximal element. - :meth:`~FinitePoset.is_bounded` | Return ``True`` if the poset contains a unique maximal element and a unique minimal element, and False otherwise. - :meth:`~FinitePoset.is_chain` | Return ``True`` if the poset is totally ordered, and False otherwise. - :meth:`~FinitePoset.is_connected` | Return ``True`` if the poset is connected, and ``False`` otherwise. - :meth:`~FinitePoset.is_graded` | Return whether this poset is graded. - :meth:`~FinitePoset.is_ranked` | Return whether this poset is ranked. - :meth:`~FinitePoset.is_ranked` | Return ``True`` if the poset is rank symmetric. - :meth:`~FinitePoset.is_incomparable_chain_free` | Return whether the poset is `(m+n)`-free. - :meth:`~FinitePoset.is_slender` | Return whether the poset ``self`` is slender or not. - :meth:`~FinitePoset.is_join_semilattice` | Return ``True`` is the poset has a join operation, and False otherwise. - :meth:`~FinitePoset.is_meet_semilattice` | Return ``True`` if self has a meet operation, and False otherwise. + :meth:`~FinitePoset.is_bounded` | Return ``True`` if the poset has both unique minimal and unique maximal element. + :meth:`~FinitePoset.is_chain` | Return ``True`` if the poset is totally ordered. + :meth:`~FinitePoset.is_connected` | Return ``True`` if the poset is connected. + :meth:`~FinitePoset.is_graded` | Return ``True`` if all maximal chains of the poset has same length. + :meth:`~FinitePoset.is_ranked` | Return ``True`` if the poset has a rank function. + :meth:`~FinitePoset.is_rank_symmetric` | Return ``True`` if the poset is rank symmetric. + :meth:`~FinitePoset.is_incomparable_chain_free` | Return ``True`` if the poset is (m+n)-free. + :meth:`~FinitePoset.is_slender` | Return ``True`` if the poset is slender. + :meth:`~FinitePoset.is_join_semilattice` | Return ``True`` is the poset has a join operation. + :meth:`~FinitePoset.is_meet_semilattice` | Return ``True`` if the poset has a meet operation. **Minimal and maximal elements** @@ -140,6 +140,7 @@ :delim: | :meth:`~FinitePoset.is_isomorphic` | Return ``True`` if both posets are isomorphic. + :meth:`~FinitePoset.is_induced_subposet` | Return ``True`` if given poset is an induced subposet of this poset. **Polynomials** @@ -190,6 +191,7 @@ :meth:`~FinitePoset.is_linear_extension` | Return whether ``l`` is a linear extension of ``self``. :meth:`~FinitePoset.isomorphic_subposets_iterator` | Return an iterator over the subposets isomorphic to another poset. :meth:`~FinitePoset.isomorphic_subposets` | Return all subposets isomorphic to another poset. + :meth:`~FinitePoset.kazhdan_lusztig_polynomial` | Return the Kazhdan-Lusztig polynomial `P_{x,y}(q)` of ``self``. :meth:`~FinitePoset.lequal_matrix` | Computes the matrix whose ``(i,j)`` entry is 1 if ``self.linear_extension()[i] < self.linear_extension()[j]`` and 0 otherwise. :meth:`~FinitePoset.level_sets` | Return a list l such that l[i+1] is the set of minimal elements of the poset obtained by removing the elements in l[0], l[1], ..., l[i]. :meth:`~FinitePoset.linear_extension` | Return a linear extension of this poset. @@ -229,6 +231,7 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute from sage.misc.misc_c import prod +from sage.functions.other import floor from sage.categories.category import Category from sage.categories.sets_cat import Sets from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets @@ -240,6 +243,7 @@ from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.rings.polynomial.polynomial_ring import polygen +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.graphs.digraph import DiGraph from sage.graphs.digraph_generators import digraphs from sage.combinat.posets.hasse_diagram import HasseDiagram @@ -1820,52 +1824,54 @@ def cover_relations_iterator(self): def relations(self): r""" - Returns a list of all relations of the poset. + Return the list of all relations of the poset. - A relation is a pair of elements `x` and `y` such that `x\leq y` - in ``self``. + A relation is a pair of elements `x` and `y` such that `x \leq y` + in the poset. - Relations are also often called intervals. The number of - intervals is the dimension of the incidence algebra. + The number of relations is the dimension of the incidence + algebra. OUTPUT: A list of pairs (each pair is a list), where the first element of the pair is less than or equal to the second element. - Pairs are produced in a rough sort of lexicographic order, - where earlier elements are from lower levels of the poset. - .. SEEALSO:: :meth:`relations_number`, :meth:`relations_iterator` EXAMPLES:: - sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: Q.relations() + sage: P = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) + sage: P.relations() [[1, 1], [1, 2], [1, 3], [1, 4], [0, 0], [0, 2], [0, 3], [0, 4], [2, 2], [2, 3], [2, 4], [3, 3], [3, 4], [4, 4]] + TESTS:: + + sage: P = Poset() # Test empty poset + sage: P.relations() + [] + AUTHOR: - Rob Beezer (2011-05-04) """ return list(self.relations_iterator()) + intervals = deprecated_function_alias(19360, relations) + def relations_iterator(self, strict=False): r""" - Returns an iterator for all the relations of the poset. - - A relation is a pair of elements `x` and `y` such that `x\leq y` - in ``self``. + Return an iterator for all the relations of the poset. - Relations are also often called intervals. The number of - intervals is the dimension of the incidence algebra. + A relation is a pair of elements `x` and `y` such that `x \leq y` + in the poset. INPUT: - - ``strict`` -- boolean (default ``False``) if ``True``, returns + - ``strict`` -- a boolean (default ``False``) if ``True``, returns an iterator over relations `x < y`, excluding all relations `x \leq x`. @@ -1876,25 +1882,21 @@ def relations_iterator(self, strict=False): .. SEEALSO:: - :meth:`relations_number`, :meth:`relations`, - :meth:`maximal_chains`, :meth:`chains` + :meth:`relations_number`, :meth:`relations`. EXAMPLES:: - sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: type(Q.relations_iterator()) + sage: P = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) + sage: it = P.relations_iterator() + sage: type(it) - sage: [z for z in Q.relations_iterator()] - [[1, 1], [1, 2], [1, 3], [1, 4], [0, 0], [0, 2], [0, 3], - [0, 4], [2, 2], [2, 3], [2, 4], [3, 3], [3, 4], [4, 4]] + sage: it.next(), it.next() + ([1, 1], [1, 2]) sage: P = posets.PentagonPoset() sage: list(P.relations_iterator(strict=True)) [[0, 1], [0, 2], [0, 4], [0, 3], [1, 4], [2, 3], [2, 4], [3, 4]] - sage: len(list(P.relations_iterator())) - 13 - AUTHOR: - Rob Beezer (2011-05-04) @@ -1911,6 +1913,8 @@ def relations_iterator(self, strict=False): for j in hd.breadth_first_search(i): yield [elements[i], elements[j]] + intervals_iterator = deprecated_function_alias(19360, relations_iterator) + def relations_number(self): """ Return the number of relations in the poset. @@ -1942,58 +1946,63 @@ def relations_number(self): """ return sum(1 for x in self.relations_iterator()) - # three useful aliases - intervals = relations + # Maybe this should also be deprecated. intervals_number = relations_number - intervals_iterator = relations_iterator - def is_incomparable_chain_free(self, m, n = None): + def is_incomparable_chain_free(self, m, n=None): r""" - Return ``True`` if the poset is `(m+n)`-free (that is, there is no pair - of incomparable chains of lengths `m` and `n`), and ``False`` if not. + Return ``True`` if the poset is `(m+n)`-free, and ``False`` otherwise. - If ``m`` is a tuple of pairs of chain lengths, returns ``True`` if the poset - does not contain a pair of incomparable chains whose lengths comprise - one of the chain pairs, and ``False`` if not. - A poset is `(m+n)`-free if it contains no induced subposet that is - isomorphic to the poset consisting of two disjoint chains of lengths - `m` and `n`. See, for example, Exercise 15 in Chapter 3 of - [EnumComb1]_. + A poset is `(m+n)`-free if there is no incomparable chains of + lengths `m` and `n`. Three cases have special name: + + - ''interval order'' is `(2+2)`-free + - ''semiorder'' (or ''unit interval order'') is `(1+3)`-free and + `(2+2)`-free + - ''weak order'' is `(1+2)`-free. INPUT: - - ``m`` - tuple of pairs of nonnegative integers - - ``m``, ``n`` - nonnegative integers + - ``m``, ``n`` - positive integers + + It is also possible to give a list of integer pairs as argument. + See below for an example. EXAMPLES:: - sage: P = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: P.is_incomparable_chain_free(1, 1) - False - sage: P.is_incomparable_chain_free(2, 1) + sage: B3 = Posets.BooleanLattice(3) + sage: B3.is_incomparable_chain_free(1, 3) True + sage: B3.is_incomparable_chain_free(2, 2) + False - :: - - sage: P = Poset(((0, 1, 2, 3, 4), ((0, 1), (1, 2), (0, 3), (4, 2)))) - sage: P.is_incomparable_chain_free(((3, 1), (2, 2))) + sage: IP6 = Posets.IntegerPartitions(6) + sage: IP6.is_incomparable_chain_free(1, 3) + False + sage: IP6.is_incomparable_chain_free(2, 2) True - :: + A list of pairs as an argument:: - sage: P = Poset((("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"), (("d", "a"), ("e", "a"), ("f", "a"), ("g", "a"), ("h", "b"), ("f", "b"), ("h", "c"), ("g", "c"), ("h", "d"), ("i", "d"), ("h", "e"), ("i", "e"), ("j", "f"), ("i", "f"), ("j", "g"), ("i", "g"), ("j", "h")))) - sage: P.is_incomparable_chain_free(3, 1) - True - sage: P.is_incomparable_chain_free(2, 2) + sage: B3.is_incomparable_chain_free([[1, 3], [2, 2]]) False - :: + We show how to get an incomparable chain pair:: - sage: [len([p for p in Posets(n) if p.is_incomparable_chain_free(((3, 1), (2, 2)))]) for n in range(6)] # long time - [1, 1, 2, 5, 14, 42] + sage: P = Posets.PentagonPoset() + sage: chains_1_2 = Poset({0:[], 1:[2]}) + sage: incomps = P.isomorphic_subposets(chains_1_2)[0] + sage: sorted(incomps.list()), incomps.cover_relations() + ([1, 2, 3], [[2, 3]]) TESTS:: + sage: Poset().is_incomparable_chain_free(1,1) # Test empty poset + True + + sage: [len([p for p in Posets(n) if p.is_incomparable_chain_free(((3, 1), (2, 2)))]) for n in range(6)] # long time + [1, 1, 2, 5, 14, 42] + sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) sage: Q.is_incomparable_chain_free(2, 20/10) True @@ -2004,7 +2013,7 @@ def is_incomparable_chain_free(self, m, n = None): sage: Q.is_incomparable_chain_free(2, -1) Traceback (most recent call last): ... - ValueError: 2 and -1 must be nonnegative integers. + ValueError: 2 and -1 must be positive integers. sage: P = Poset(((0, 1, 2, 3, 4), ((0, 1), (1, 2), (0, 3), (4, 2)))) sage: P.is_incomparable_chain_free((3, 1)) Traceback (most recent call last): @@ -2051,56 +2060,58 @@ def is_incomparable_chain_free(self, m, n = None): m, n = Integer(m), Integer(n) except TypeError: raise TypeError('%s and %s must be integers.' % (m, n)) - if m < 0 or n < 0: - raise ValueError("%s and %s must be nonnegative integers." % (m, n)) + if m < 1 or n < 1: + raise ValueError("%s and %s must be positive integers." % (m, n)) twochains = digraphs.TransitiveTournament(m) + digraphs.TransitiveTournament(n) return self._hasse_diagram.transitive_closure().subgraph_search(twochains, induced = True) is None def is_lequal(self, x, y): """ - Returns ``True`` if `x` is less than or equal to `y` in the poset, and + Return ``True`` if `x` is less than or equal to `y` in the poset, and ``False`` otherwise. EXAMPLES:: - sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: x,y,z = Q(0),Q(1),Q(4) - sage: Q.is_lequal(x,y) - False - sage: Q.is_lequal(y,x) - False - sage: Q.is_lequal(x,z) - True - sage: Q.is_lequal(y,z) + sage: P = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) + sage: P.is_lequal(2, 4) True - sage: Q.is_lequal(z,z) + sage: P.is_lequal(2, 2) True + sage: P.is_lequal(0, 1) + False + sage: P.is_lequal(3, 2) + False + + .. SEEALSO:: :meth:`is_less_than`, :meth:`is_gequal`. """ i = self._element_to_vertex(x) j = self._element_to_vertex(y) - return (i == j or self._hasse_diagram.is_lequal(i, j)) + return (self._hasse_diagram.is_lequal(i, j)) le = is_lequal def is_less_than(self, x, y): """ - Returns ``True`` if `x` is less than but not equal to `y` in the poset, + Return ``True`` if `x` is less than but not equal to `y` in the poset, and ``False`` otherwise. EXAMPLES:: - sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: x,y,z = Q(0),Q(1),Q(4) - sage: Q.is_less_than(x,y) + sage: P = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) + sage: P.is_less_than(1, 3) + True + sage: P.is_less_than(0, 1) False - sage: Q.is_less_than(y,x) + sage: P.is_less_than(2, 2) False - sage: Q.is_less_than(x,z) - True - sage: Q.is_less_than(y,z) - True - sage: Q.is_less_than(z,z) + + For non-facade posets also ``<`` works:: + + sage: P = Poset({3: [1, 2]}, facade=False) + sage: P(1) < P(2) False + + .. SEEALSO:: :meth:`is_lequal`, :meth:`is_greater_than`. """ i = self._element_to_vertex(x) j = self._element_to_vertex(y) @@ -2110,53 +2121,51 @@ def is_less_than(self, x, y): def is_gequal(self, x, y): """ - Returns ``True`` if `x` is greater than or equal to `y` in the poset, + Return ``True`` if `x` is greater than or equal to `y` in the poset, and ``False`` otherwise. EXAMPLES:: - sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: x,y,z = Q(0),Q(1),Q(4) - sage: Q.is_gequal(x,y) - False - sage: Q.is_gequal(y,x) - False - sage: Q.is_gequal(x,z) - False - sage: Q.is_gequal(z,x) - True - sage: Q.is_gequal(z,y) + sage: P = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) + sage: P.is_gequal(3, 1) True - sage: Q.is_gequal(z,z) + sage: P.is_gequal(2, 2) True + sage: P.is_gequal(0, 1) + False + + .. SEEALSO:: :meth:`is_greater_than`, :meth:`is_lequal`. """ i = self._element_to_vertex(x) j = self._element_to_vertex(y) - return (i == j or self._hasse_diagram.is_lequal(j, i)) + return (self._hasse_diagram.is_lequal(j, i)) ge = is_gequal def is_greater_than(self, x, y): """ - Returns ``True`` if `x` is greater than but not equal to `y` in the + Return ``True`` if `x` is greater than but not equal to `y` in the poset, and ``False`` otherwise. EXAMPLES:: - sage: Q = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) - sage: x,y,z = Q(0),Q(1),Q(4) - sage: Q.is_greater_than(x,y) + sage: P = Poset({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) + sage: P.is_greater_than(3, 1) + True + sage: P.is_greater_than(1, 2) False - sage: Q.is_greater_than(y,x) + sage: P.is_greater_than(3, 3) False - sage: Q.is_greater_than(x,z) + sage: P.is_greater_than(0, 1) False - sage: Q.is_greater_than(z,x) - True - sage: Q.is_greater_than(z,y) + + For non-facade posets also ``>`` works:: + + sage: P = Poset({3: [1, 2]}, facade=False) + sage: P(2) > P(3) True - sage: Q.is_greater_than(z,z) - False + + .. SEEALSO:: :meth:`is_gequal`, :meth:`is_less_than`. """ i = self._element_to_vertex(x) j = self._element_to_vertex(y) @@ -2168,23 +2177,22 @@ def compare_elements(self, x, y): r""" Compare `x` and `y` in the poset. - If ``x`` = ``y``, then ``0`` is returned; - if ``x`` < ``y``, then ``-1`` is returned; - if ``x`` > ``y``, then ``1`` is returned; - and if ``x`` and ``y`` are not comparable, - then ``None`` is returned. + - If `x < y`, return ``-1``. + - If `x = y`, return ``0``. + - If `x > y`, return ``1``. + - If `x` and `y` are not comparable, return ``None``. EXAMPLES:: - sage: P = Poset([[1,2],[4],[3],[4],[]]) - sage: P.compare_elements(0,0) + sage: P = Poset([[1, 2], [4], [3], [4], []]) + sage: P.compare_elements(0, 0) 0 - sage: P.compare_elements(0,4) + sage: P.compare_elements(0, 4) -1 - sage: P.compare_elements(4,0) + sage: P.compare_elements(4, 0) 1 - sage: P.compare_elements(1,2) - + sage: P.compare_elements(1, 2) is None + True """ i, j = map(self._element_to_vertex, (x, y)) if i == j: @@ -2241,7 +2249,7 @@ def bottom(self): sage: Q.bottom() 0 - .. SEEALSO:: :meth:`has_bottom`, :meth:`top`. + .. SEEALSO:: :meth:`has_bottom`, :meth:`top` """ hasse_bot = self._hasse_diagram.bottom() if hasse_bot is None: @@ -2256,14 +2264,19 @@ def has_bottom(self): EXAMPLES:: - sage: P = Poset({0:[3],1:[3],2:[3],3:[4],4:[]}) + sage: P = Poset({0:[3], 1:[3], 2:[3], 3:[4], 4:[]}) sage: P.has_bottom() False - sage: Q = Poset({0:[1],1:[]}) + sage: Q = Poset({0:[1], 1:[]}) sage: Q.has_bottom() True - .. SEEALSO:: :meth:`bottom`, :meth:`has_top`. + .. SEEALSO:: :meth:`bottom`, :meth:`has_top`, :meth:`is_bounded` + + TESTS:: + + sage: Poset().has_top() # Test empty poset + False """ return self._hasse_diagram.has_bottom() @@ -2280,7 +2293,7 @@ def top(self): sage: Q.top() 1 - .. SEEALSO:: :meth:`has_top`, :meth:`bottom`. + .. SEEALSO:: :meth:`has_top`, :meth:`bottom` TESTS:: @@ -2303,14 +2316,19 @@ def has_top(self): EXAMPLES:: - sage: P = Poset({0:[3],1:[3],2:[3],3:[4,5],4:[],5:[]}) + sage: P = Poset({0:[3], 1:[3], 2:[3], 3:[4, 5], 4:[], 5:[]}) sage: P.has_top() False - sage: Q = Poset({0:[1],1:[]}) + sage: Q = Poset({0:[3], 1:[3], 2:[3], 3:[4], 4:[]}) sage: Q.has_top() True - .. SEEALSO:: :meth:`top`, :meth:`has_bottom`. + .. SEEALSO:: :meth:`top`, :meth:`has_bottom`, :meth:`is_bounded` + + TESTS:: + + sage: Poset().has_top() # Test empty poset + False """ return self._hasse_diagram.has_top() @@ -2368,35 +2386,57 @@ def has_isomorphic_subposet(self, other): def is_bounded(self): """ - Return ``True`` if the poset ``self`` is bounded, and ``False`` - otherwise. + Return ``True`` if the poset is bounded, and ``False`` otherwise. - We call a poset bounded if it contains a unique maximal element + A poset is bounded if it contains both a unique maximal element and a unique minimal element. EXAMPLES:: - sage: P = Poset({0:[3],1:[3],2:[3],3:[4,5],4:[],5:[]}) + sage: P = Poset({0:[3], 1:[3], 2:[3], 3:[4, 5], 4:[], 5:[]}) sage: P.is_bounded() False - sage: Q = Poset({0:[1],1:[]}) + sage: Q = Posets.DiamondPoset(5) sage: Q.is_bounded() True + + .. SEEALSO:: :meth:`has_bottom`, :meth:`has_top` + + TESTS:: + + sage: Poset().is_bounded() # Test empty poset + False + sage: Poset({1:[]}).is_bounded() # Here top == bottom + True + sage: ( len([P for P in Posets(5) if P.is_bounded()]) == + ....: Posets(3).cardinality() ) + True """ return self._hasse_diagram.is_bounded() def is_chain(self): """ - Returns True if the poset is totally ordered, and False otherwise. + Return ``True`` if the poset is totally ordered ("chain"), and + ``False`` otherwise. EXAMPLES:: - sage: L = Poset({0:[1],1:[2],2:[3],3:[4]}) - sage: L.is_chain() + sage: I = Poset({0:[1], 1:[2], 2:[3], 3:[4]}) + sage: I.is_chain() True - sage: V = Poset({0:[1,2]}) + + sage: II = Poset({0:[1], 2:[3]}) + sage: II.is_chain() + False + + sage: V = Poset({0:[1, 2]}) sage: V.is_chain() False + + TESTS:: + + sage: [len([P for P in Posets(n) if P.is_chain()]) for n in range(5)] + [1, 1, 1, 1, 1] """ return self._hasse_diagram.is_chain() @@ -2461,18 +2501,28 @@ def is_connected(self): """ Return ``True`` if the poset is connected, and ``False`` otherwise. - Poset is not connected if it can be divided to disjoint parts + A poset is connected if it's Hasse diagram is connected. + + If a poset is not connected, then it can be divided to parts `S_1` and `S_2` so that every element of `S_1` is incomparable to every element of `S_2`. EXAMPLES:: - sage: P=Poset({1:[2,3], 3:[4,5]}) + sage: P = Poset({1:[2, 3], 3:[4, 5]}) sage: P.is_connected() True - sage: P=Poset({1:[2,3], 3:[4,5], 6:[7,8]}) + + sage: P = Poset({1:[2, 3], 3:[4, 5], 6:[7, 8]}) sage: P.is_connected() False + + .. SEEALSO:: :meth:`connected_components` + + TESTS:: + + sage: Poset().is_connected() # Test empty poset + True """ return self._hasse_diagram.is_connected() @@ -2805,80 +2855,72 @@ def rank(self, element=None): def is_ranked(self): r""" - Returns whether this poset is ranked. + Return ``True`` if the poset is ranked, and ``False`` otherwise. - A poset is *ranked* if it admits a rank function. For more information - about the rank function, see :meth:`~sage.combinat.posets.hasse_diagram.HasseDiagram.rank_function`. + A poset is ranked if there is a function `r` from poset elements + to integers so that `r(x)=r(y)+1` when `x` covers `y`. - .. SEEALSO:: :meth:`is_graded`. + Informally said a ranked poset can be "levelized": every element is + on a "level", and every cover relation goes only one level up. EXAMPLES:: - sage: P = Poset([[1],[2],[3],[4],[]]) + sage: P = Poset( ([1, 2, 3, 4], [[1, 2], [2, 4], [3, 4]] )) sage: P.is_ranked() True - sage: Q = Poset([[1,5],[2,6],[3],[4],[],[6,3],[4]]) - sage: Q.is_ranked() - False - sage: P = Poset( ([1,2,3,4],[[1,2],[2,4],[3,4]] )) + + sage: P = Poset([[1, 5], [2, 6], [3], [4],[], [6, 3], [4]]) sage: P.is_ranked() + False + + .. SEEALSO:: :meth:`rank_function`, :meth:`rank`, :meth:`is_graded` + + TESTS:: + + sage: Poset().is_ranked() # Test empty poset True """ return bool(self.rank_function()) def is_graded(self): r""" - Returns whether this poset is graded. + Return ``True`` if the poset is graded, and ``False`` otherwise. - A poset is *graded* if all its maximal chains have the same length. - There are various competing definitions for graded posets (see - :wikipedia:`Graded_poset`). This definition is from section 3.1 of - Richard Stanley's *Enumerative Combinatorics, Vol. 1* [EnumComb1]_. + A poset is graded if all its maximal chains have the same length. - Note that every graded poset is ranked, but the converse is not - true. + There are various competing definitions for graded + posets (see :wikipedia:`Graded_poset`). This definition is from + section 3.1 of Richard Stanley's *Enumerative Combinatorics, + Vol. 1* [EnumComb1]_. - .. SEEALSO:: :meth:`is_ranked` + Every graded poset is ranked. The converse is true + for bounded posets, including lattices. EXAMPLES:: - sage: P = Poset([[1],[2],[3],[4],[]]) + sage: P = Posets.PentagonPoset() # Not even ranked sage: P.is_graded() - True - sage: Q = Poset([[1,5],[2,6],[3],[4],[],[6,3],[4]]) - sage: Q.is_graded() False - sage: P = Poset( ([1,2,3,4],[[1,2],[2,4],[3,4]] )) + + sage: P = Poset({1:[2, 3], 3:[4]}) # Ranked, but not graded sage: P.is_graded() False - sage: P = Poset({1: [2, 3], 4: [5]}) + + sage: P = Poset({1:[3, 4], 2:[3, 4], 5:[6]}) sage: P.is_graded() True - sage: P = Poset({1: [2, 3], 3: [4]}) - sage: P.is_graded() - False - sage: P = Poset({1: [2, 3], 4: []}) + + sage: P = Poset([[1], [2], [], [4], []]) sage: P.is_graded() False - sage: P = Posets.BooleanLattice(4) - sage: P.is_graded() - True - sage: P = RootSystem(['D',4]).root_poset() - sage: P.is_graded() - True - sage: P = Poset({}) - sage: P.is_graded() - True - TESTS: + .. SEEALSO:: :meth:`is_ranked`, :meth:`level_sets` - Here we test that the empty poset is graded:: + TESTS:: - sage: Poset([[],[]]).is_graded() + sage: Poset().is_graded() # Test empty poset True """ - # The code below is not really optimized, but beats looking at - # every maximal chain... if len(self) <= 2: return True # Let's work with the Hasse diagram in order to avoid some @@ -3147,21 +3189,29 @@ def meet_matrix(self): def is_meet_semilattice(self): r""" - Returns True if self has a meet operation, and False otherwise. + Return ``True`` if the poset has a meet operation, and + ``False`` otherwise. - EXAMPLES:: + A meet is the greatest lower bound for given elements, if it exists. - sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]], facade = False) - sage: P.is_meet_semilattice() - True + EXAMPLES:: - sage: P = Poset([[1,2],[3],[3],[]]) + sage: P = Poset({1:[2, 3, 4], 2:[5, 6], 3:[6], 4:[6, 7]}) sage: P.is_meet_semilattice() True - sage: P = Poset({0:[2,3],1:[2,3]}) - sage: P.is_meet_semilattice() + sage: Q = P.dual() + sage: Q.is_meet_semilattice() False + + .. SEEALSO:: :meth:`is_join_semilattice`, :meth:`~sage.categories.finite_posets.FinitePosets.ParentMethods.is_lattice` + + TESTS:: + + sage: Poset().is_meet_semilattice() # Test empty lattice + True + sage: len([P for P in Posets(4) if P.is_meet_semilattice()]) + 5 """ return self._hasse_diagram.is_meet_semilattice() @@ -3178,22 +3228,32 @@ def join_matrix(self): def is_join_semilattice(self): """ - Returns True if the poset has a join operation, and False + Return ``True`` if the poset has a join operation, and ``False`` otherwise. + A join is the least upper bound for given elements, if it exists. + EXAMPLES:: - sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]]) + sage: P = Poset([[1,3,2], [4], [4,5,6], [6], [7], [7], [7], []]) sage: P.is_join_semilattice() True - sage: P = Poset([[1,2],[3],[3],[]]) - sage: P.is_join_semilattice() - True + Elements 3 and 4 have no common upper bound at all; elements + 1 and 2 have upper bounds 3 and 4, but no least upper bound:: - sage: P = Poset({0:[2,3],1:[2,3]}) + sage: P = Poset({1:[3, 4], 2:[3, 4]}) sage: P.is_join_semilattice() False + + .. SEEALSO:: :meth:`is_meet_semilattice`, :meth:`~sage.categories.finite_posets.FinitePosets.ParentMethods.is_lattice` + + TESTS:: + + sage: Poset().is_join_semilattice() # Test empty lattice + True + sage: len([P for P in Posets(4) if P.is_join_semilattice()]) + 5 """ return self._hasse_diagram.is_join_semilattice() @@ -3432,7 +3492,7 @@ def dilworth_decomposition(self): union of chains. According to Dilworth's theorem, the number of chains is equal to - `\alpha` (the posets' width). + `\alpha` (the posets' width). EXAMPLES:: @@ -4289,17 +4349,31 @@ def random_order_ideal(self, direction='down'): def order_filter(self,elements): """ - Returns the order filter generated by the elements of an + Return the order filter generated by the elements of an iterable ``elements``. `I` is an order filter if, for any `x` in `I` and `y` such that - `y \ge x`, then `y` is in `I`. + `y \ge x`, then `y` is in `I`. This is also called upper set or + upset. EXAMPLES:: - sage: B = Posets.BooleanLattice(4) - sage: B.order_filter([3,8]) - [3, 7, 8, 9, 10, 11, 12, 13, 14, 15] + sage: P = Poset((divisors(1000), attrcall("divides"))) + sage: P.order_filter([20, 25]) + [20, 40, 25, 50, 100, 200, 125, 250, 500, 1000] + + .. SEEALSO:: + + :meth:`order_ideal`, :meth:`~sage.categories.posets.Posets.ParentMethods.principal_order_filter`. + + TESTS:: + + sage: P = Poset() # Test empty poset + sage: P.order_filter([]) + [] + sage: C = Posets.ChainPoset(5) + sage: C.order_filter([]) + [] """ vertices = sorted(map(self._element_to_vertex,elements)) of = self._hasse_diagram.order_filter(vertices) @@ -4307,19 +4381,31 @@ def order_filter(self,elements): def order_ideal(self,elements): """ - Returns the order ideal generated by the elements of an + Return the order ideal generated by the elements of an iterable ``elements``. `I` is an order ideal if, for any `x` in `I` and `y` such that - `y \le x`, then `y` is in `I`. + `y \le x`, then `y` is in `I`. This is also called lower set or + downset. EXAMPLES:: - sage: B = Posets.BooleanLattice(4) - sage: B.order_ideal([7,10]) - [0, 1, 2, 3, 4, 5, 6, 7, 8, 10] - sage: B.order_ideal(iter(range(4, 9))) - [0, 1, 2, 3, 4, 5, 6, 7, 8] + sage: P = Poset((divisors(1000), attrcall("divides"))) + sage: P.order_ideal([20, 25]) + [1, 2, 4, 5, 10, 20, 25] + + .. SEEALSO:: + + :meth:`order_filter`, :meth:`~sage.categories.posets.Posets.ParentMethods.principal_order_ideal`. + + TESTS:: + + sage: P = Poset() # Test empty poset + sage: P.order_ideal([]) + [] + sage: C = Posets.ChainPoset(5) + sage: C.order_ideal([]) + [] """ vertices = [self._element_to_vertex(_) for _ in elements] oi = self._hasse_diagram.order_ideal(vertices) @@ -4356,38 +4442,58 @@ def interval(self, x, y): def closed_interval(self, x, y): """ - Return a list of the elements `z` such that `x \le z \le y`. + Return the list of elements `z` such that `x \le z \le y` in the poset. EXAMPLES:: - sage: uc = [[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]] - sage: dag = DiGraph(dict(zip(range(len(uc)),uc))) - sage: P = Poset(dag) - sage: I = set(map(P,[2,5,6,4,7])) - sage: I == set(P.closed_interval(2,7)) - True + sage: P = Poset((divisors(1000), attrcall("divides"))) + sage: P.closed_interval(2, 100) + [2, 4, 10, 20, 50, 100] + + .. SEEALSO:: + + :meth:`open_interval` + + TESTS:: + + sage: C = Posets.ChainPoset(10) + sage: C.closed_interval(3, 3) + [3] + sage: C.closed_interval(8, 5) + [] + sage: A = Posets.AntichainPoset(10) + sage: A.closed_interval(3, 7) + [] """ - return self.interval(x,y) + return [self._vertex_to_element(_) for _ in self._hasse_diagram.interval( + self._element_to_vertex(x),self._element_to_vertex(y))] def open_interval(self, x, y): """ - Return a list of the elements `z` such that `x < z < y`. + Return the list of elements `z` such that `x < z < y` in the poset. EXAMPLES:: - sage: uc = [[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]] - sage: dag = DiGraph(dict(zip(range(len(uc)),uc))) - sage: P = Poset(dag) - sage: I = set(map(P,[5,6,4])) - sage: I == set(P.open_interval(2,7)) - True + sage: P = Poset((divisors(1000), attrcall("divides"))) + sage: P.open_interval(2, 100) + [4, 10, 20, 50] - :: + .. SEEALSO:: - sage: dg = DiGraph({"a":["b","c"], "b":["d"], "c":["d"]}) - sage: P = Poset(dg, facade = False) - sage: P.open_interval("a","d") - [b, c] + :meth:`closed_interval` + + TESTS:: + + sage: C = Posets.ChainPoset(10) + sage: C.open_interval(3, 3) + [] + sage: C.open_interval(3, 4) + [] + sage: C.open_interval(7, 3) + [] + sage: A = Posets.AntichainPoset(10) + sage: A.open_interval(3, 7) + [] """ return [self._vertex_to_element(_) for _ in self._hasse_diagram.open_interval( self._element_to_vertex(x),self._element_to_vertex(y))] @@ -5277,7 +5383,8 @@ def evacuation(self): def is_rank_symmetric(self): """ - Return ``True`` if the poset is rank symmetric, and ``False`` otherwise. + Return ``True`` if the poset is rank symmetric, and ``False`` + otherwise. The poset is expected to be graded and connected. @@ -5287,12 +5394,12 @@ def is_rank_symmetric(self): EXAMPLES:: - sage: P = Poset({1:[2], 2:[3,4], 3:[5], 4:[5]}) - sage: P.is_rank_symmetric() - False - sage: P = Poset({1:[3,4,5], 2:[3,4,5], 3:[6], 4:[7], 5:[7]}) + sage: P = Poset({1:[3, 4, 5], 2:[3, 4, 5], 3:[6], 4:[7], 5:[7]}) sage: P.is_rank_symmetric() True + sage: P = Poset({1:[2], 2:[3, 4], 3:[5], 4:[5]}) + sage: P.is_rank_symmetric() + False TESTS:: @@ -5329,21 +5436,26 @@ def is_slender(self): EXAMPLES:: - sage: P = Poset(([1,2,3,4],[[1,2],[1,3],[2,4],[3,4]]), facade = True) + sage: P = Poset(([1, 2, 3, 4], [[1, 2], [1, 3], [2, 4], [3, 4]])) sage: P.is_slender() True - sage: P = Poset(([1,2,3,4,5],[[1,2],[1,3],[1,4],[2,5],[3,5],[4,5]]), facade = True) + sage: P = Poset(([1,2,3,4,5],[[1,2],[1,3],[1,4],[2,5],[3,5],[4,5]])) sage: P.is_slender() False - sage: W = WeylGroup(['A',2]) + sage: W = WeylGroup(['A', 2]) sage: G = W.bruhat_poset() sage: G.is_slender() True - sage: W = WeylGroup(['A',3]) + sage: W = WeylGroup(['A', 3]) sage: G = W.bruhat_poset() sage: G.is_slender() True + + TESTS:: + + sage: Poset().is_slender() # Test empty poset + True """ for x in self: d = {} @@ -5684,6 +5796,195 @@ def incidence_algebra(self, R, prefix='I'): from sage.combinat.posets.incidence_algebras import IncidenceAlgebra return IncidenceAlgebra(R, self, prefix) + @cached_method(key=lambda self,x,y,l: (x,y)) + def _kl_poly(self, x=None, y=None, canonical_labels=None): + r""" + Cached Kazhdan-Lusztig polynomial of ``self`` for generic `q`. + + .. SEEALSO:: + + :meth:`kazhdan_lusztig_polynomial` + + EXAMPLES:: + + sage: L = posets.SymmetricGroupWeakOrderPoset(4) + sage: L._kl_poly() + 1 + sage: x = '2314' + sage: y = '3421' + sage: L._kl_poly(x, y) + -q + 1 + + AUTHORS: + + - Travis Scrimshaw (27-12-2014) + """ + R = PolynomialRing(ZZ, 'q') + q = R.gen(0) + + # Handle some special cases + if self.cardinality() == 0: + return q.parent().zero() + if not self.rank(): + return q.parent().one() + + if canonical_labels is None: + canonical_labels = x is None and y is None + + if x is not None or y is not None: + if x == y: + return q.parent().one() + if x is None: + x = self.minimal_elements()[0] + if y is None: + y = self.maximal_elements()[0] + if not self.le(x, y): + return q.parent().zero() + P = self.subposet(self.interval(x, y)) + return P.kazhdan_lusztig_polynomial(q=q, canonical_labels=canonical_labels) + + min_elt = self.minimal_elements()[0] + if canonical_labels: + sublat = lambda P: self.subposet(P).canonical_label() + else: + sublat = lambda P: self.subposet(P) + poly = -sum(sublat(self.order_ideal([x])).characteristic_polynomial() + * sublat(self.order_filter([x])).kazhdan_lusztig_polynomial() + for x in self if x != min_elt) + tr = floor(self.rank()/2) + 1 + ret = poly.truncate(tr) + return ret(q=q) + + def kazhdan_lusztig_polynomial(self, x=None, y=None, q=None, canonical_labels=None): + r""" + Return the Kazhdan-Lusztig polynomial `P_{x,y}(q)` of ``self``. + + We follow the definition given in [EPW14]_. Let `G` denote a + graded poset with unique minimal and maximal elements and `\chi_G` + denote the characteristic polynomial of `G`. Let `I_x` and `F^x` + denote the principal order ideal and filter of `x` respectively. + Define the *Kazhdan-Lusztig polynomial* of `G` as the unique + polynomial `P_G(q)` satisfying the following: + + 1. If `\operatorname{rank} G = 0`, then `P_G(q) = 1`. + 2. If `\operatorname{rank} G > 0`, then `\deg P_G(q) < + \frac{1}{2} \operatorname{rank} G`. + 3. We have + + .. MATH:: + + q^{\operatorname{rank} G} P_G(q^{-1}) + = \sum_{x \in G} \chi_{I_x}(q) P_{F^x}(q). + + We then extend this to `P_{x,y}(q)` by considering the subposet + corresponding to the (closed) interval `[x, y]`. We also + define `P_{\emptyset}(q) = 0` (so if `x \not\leq y`, + then `P_{x,y}(q) = 0`). + + INPUT: + + - ``q`` -- (default: `q \in \ZZ[q]`) the indeterminate `q` + - ``x`` -- (default: the minimal element) the element `x` + - ``y`` -- (default: the maximal element) the element `y` + - ``canonical_labels`` -- (optional) for subposets, use the + canonical labeling (this can limit recursive calls for posets + with large amounts of symmetry, but producing the labeling + takes time); if not specified, this is ``True`` if ``x`` + and ``y`` are both not specified and ``False`` otherwise + + EXAMPLES:: + + sage: L = posets.BooleanLattice(3) + sage: L.kazhdan_lusztig_polynomial() + 1 + + :: + + sage: L = posets.SymmetricGroupWeakOrderPoset(4) + sage: L.kazhdan_lusztig_polynomial() + 1 + sage: x = '2314' + sage: y = '3421' + sage: L.kazhdan_lusztig_polynomial(x, y) + -q + 1 + sage: L.kazhdan_lusztig_polynomial(x, y, var('t')) + -t + 1 + + REFERENCES: + + .. [EPW14] Ben Elias, Nicholas Proudfoot, and Max Wakefield. + *The Kazhdan-Lusztig polynomial of a matroid*. 2014. + :arxiv:`1412.7408`. + + AUTHORS: + + - Travis Scrimshaw (27-12-2014) + """ + if not self.is_ranked(): + raise ValueError("poset is not ranked") + if q is None: + q = PolynomialRing(ZZ, 'q').gen(0) + poly = self._kl_poly(x, y, canonical_labels) + return poly(q=q) + + def is_induced_subposet(self, other): + r""" + Return ``True`` if the poset is an induced subposet of ``other``, and + ``False`` otherwise. + + A poset `P` is an induced subposet of `Q` if every element + of `P` is an element of `Q`, and `x \le_P y` iff `x \le_Q y`. + Note that "induced" here has somewhat different meaning compared + to that of graphs. + + INPUT: + + - ``other``, a poset. + + .. NOTE:: + + This method does not check whether the poset is a + *isomorphic* (i.e., up to relabeling) subposet of ``other``, + but only if ``other`` directly contains the poset as an + induced subposet. For isomorphic subposets see + :meth:`has_isomorphic_subposet`. + + EXAMPLES:: + + sage: P = Poset({1:[2, 3]}) + sage: Q = Poset({1:[2, 4], 2:[3]}) + sage: P.is_induced_subposet(Q) + False + sage: R = Poset({0:[1], 1:[3, 4], 3:[5], 4:[2]}) + sage: P.is_induced_subposet(R) + True + + TESTS:: + + sage: P = Poset({2:[1]}) + sage: Poset().is_induced_subposet(P) + True + sage: Poset().is_induced_subposet(Poset()) + True + sage: P.is_induced_subposet(Poset()) + False + + Bad input:: + + sage: Poset().is_induced_subposet('junk') + Traceback (most recent call last): + ... + AttributeError: 'str' object has no attribute 'subposet' + """ + if (not self._is_facade or + (isinstance(other, FinitePoset) and not other._is_facade)): + raise TypeError('the function is not defined on non-facade posets') + # TODO: When we have decided if + # Poset({'x':[42]}) == LatticePoset({'x':[42]}) + # or not, either remove this note or remove .hasse_diagram() below. + return (set(self).issubset(set(other)) and + other.subposet(self).hasse_diagram() == self.hasse_diagram()) + FinitePoset._dual_class = FinitePoset ##### Posets ##### @@ -5803,13 +6104,13 @@ def cardinality(self, from_iterator=False): def is_poset(dig): r""" - Tests whether a directed graph is acyclic and transitively - reduced. + Return ``True`` if a directed graph is acyclic and transitively + reduced, and ``False`` otherwise. EXAMPLES:: sage: from sage.combinat.posets.posets import is_poset - sage: dig = DiGraph({0:[2,3], 1:[3,4,5], 2:[5], 3:[5], 4:[5]}) + sage: dig = DiGraph({0:[2, 3], 1:[3, 4, 5], 2:[5], 3:[5], 4:[5]}) sage: is_poset(dig) False sage: is_poset(dig.transitive_reduction()) diff --git a/src/sage/combinat/q_bernoulli.pyx b/src/sage/combinat/q_bernoulli.pyx index 299d252d33d..6f899a4cf1e 100644 --- a/src/sage/combinat/q_bernoulli.pyx +++ b/src/sage/combinat/q_bernoulli.pyx @@ -1,15 +1,15 @@ """ -`q`-Bernoulli Numbers +`q`-Bernoulli Numbers and Polynomials """ - from sage.rings.integer_ring import ZZ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.misc.cachefunc import cached_function + @cached_function -def q_bernoulli(m,p=None): +def q_bernoulli(m, p=None): r""" - Computes Carlitz's `q`-analogue of the Bernoulli numbers + Compute Carlitz's `q`-analogue of the Bernoulli numbers. For every nonnegative integer `m`, the `q`-Bernoulli number `\beta_m` is a rational function of the indeterminate `q` whose @@ -36,7 +36,7 @@ def q_bernoulli(m,p=None): -1/(q + 1) sage: q_bernoulli(2) q/(q^3 + 2*q^2 + 2*q + 1) - sage: all(q_bernoulli(i)(q=1)==bernoulli(i) for i in range(12)) + sage: all(q_bernoulli(i)(q=1) == bernoulli(i) for i in range(12)) True One can evaluate the rational function by giving a second argument:: @@ -50,7 +50,7 @@ def q_bernoulli(m,p=None): sage: q_bernoulli(-1) Traceback (most recent call last): ... - ValueError: the argument must be a nonnegative integer. + ValueError: the argument must be a nonnegative integer REFERENCES: @@ -60,12 +60,68 @@ def q_bernoulli(m,p=None): """ m = ZZ(m) if m < 0: - raise ValueError("the argument must be a nonnegative integer.") + raise ValueError("the argument must be a nonnegative integer") cdef int i - q = PolynomialRing(ZZ,'q').gen() - result = (q-1)**(1-m)*sum((-1)**(m-i)*m.binomial(i)*(i+1)/(q**(i+1)-1) - for i in range(m+1)) + q = PolynomialRing(ZZ, 'q').gen() + result = (q - 1)**(1-m) * sum((-1)**(m-i)*m.binomial(i)*(i+1)/(q**(i+1)-1) + for i in range(m + 1)) if p is None: return result else: return result(q=p) + + +@cached_function +def q_bernoulli_polynomial(m): + r""" + Compute Carlitz's `q`-analogue of the Bernoulli polynomials. + + For every nonnegative integer `m`, the `q`-Bernoulli polynomial + is a polynomial in one variable `x` with coefficients in `\QQ(q)` whose + value at `q=1` is the usual Bernoulli polynomial `B_m(x)`. + + The original `q`-Bernoulli polynomials introduced by Carlitz were + polynomials in `q^y` with coefficients in `\QQ(q)`. This function returns + these polynomials but expressed in the variable `x=(q^y-1)/(q-1)`. This + allows to let `q=1` to recover the classical Bernoulli polynomials. + + INPUT: + + - `m` -- a nonnegative integer + + OUTPUT: + + A polynomial in one variable `x`. + + EXAMPLES:: + + sage: from sage.combinat.q_bernoulli import q_bernoulli_polynomial, q_bernoulli + sage: q_bernoulli_polynomial(0) + 1 + sage: q_bernoulli_polynomial(1) + (2/(q + 1))*x - 1/(q + 1) + sage: x = q_bernoulli_polynomial(1).parent().gen() + sage: all(q_bernoulli_polynomial(i)(q=1)==bernoulli_polynomial(x,i) for i in range(12)) + True + sage: all(q_bernoulli_polynomial(i)(x=0)==q_bernoulli(i) for i in range(12)) + True + + The function does not accept negative arguments:: + + sage: q_bernoulli_polynomial(-1) + Traceback (most recent call last): + ... + ValueError: the argument must be a nonnegative integer + + REFERENCES: [Ca1948]_, [Ca1954]_ + """ + m = ZZ(m) + if m < 0: + raise ValueError("the argument must be a nonnegative integer") + cdef int i + R = PolynomialRing(ZZ, 'q') + q = R.gen() + x = PolynomialRing(R, 'x').gen() + z = 1 + (q - 1) * x + return sum(m.binomial(i) * x ** (m - i) * z ** i * q_bernoulli(i) + for i in range(m + 1)) diff --git a/src/sage/combinat/quickref.py b/src/sage/combinat/quickref.py index f939e8ba945..64d9cd70fac 100644 --- a/src/sage/combinat/quickref.py +++ b/src/sage/combinat/quickref.py @@ -27,7 +27,7 @@ Constructions and Species:: - sage: for (p, s) in CartesianProduct(P, S): print p, s # not tested + sage: for (p, s) in cartesian_product([P,S]): print p, s # not tested sage: DisjointUnionEnumeratedSets(Family(lambda n: IntegerVectors(n, 3), NonNegativeIntegers)) # not tested Words:: diff --git a/src/sage/combinat/rigged_configurations/__init__.py b/src/sage/combinat/rigged_configurations/__init__.py index 486ff3fee85..75c42614b61 100644 --- a/src/sage/combinat/rigged_configurations/__init__.py +++ b/src/sage/combinat/rigged_configurations/__init__.py @@ -31,5 +31,6 @@ - :ref:`sage.combinat.rigged_configurations.bij_type_A2_even` - :ref:`sage.combinat.rigged_configurations.bij_type_A2_dual` - :ref:`sage.combinat.rigged_configurations.bij_type_D_twisted` +- :ref:`sage.combinat.rigged_configurations.bij_type_D_tri` - :ref:`sage.combinat.rigged_configurations.bij_infinity` """ diff --git a/src/sage/combinat/rigged_configurations/bij_abstract_class.py b/src/sage/combinat/rigged_configurations/bij_abstract_class.py index cb745eb36a8..8a12d5ce409 100644 --- a/src/sage/combinat/rigged_configurations/bij_abstract_class.py +++ b/src/sage/combinat/rigged_configurations/bij_abstract_class.py @@ -432,7 +432,7 @@ def run(self, verbose=False, build_graph=False): self._graph.pop(0) # Remove the dummy at the start from sage.graphs.digraph import DiGraph from sage.graphs.dot2tex_utils import have_dot2tex - self._graph = DiGraph(self._graph) + self._graph = DiGraph(self._graph, format="list_of_edges") if have_dot2tex(): self._graph.set_latex_options(format="dot2tex", edge_labels=True) diff --git a/src/sage/combinat/rigged_configurations/bij_type_D.py b/src/sage/combinat/rigged_configurations/bij_type_D.py index a191ed3e0df..5b660866e24 100644 --- a/src/sage/combinat/rigged_configurations/bij_type_D.py +++ b/src/sage/combinat/rigged_configurations/bij_type_D.py @@ -542,7 +542,7 @@ def run(self, verbose=False, build_graph=False): self._graph.pop(0) # Remove the dummy at the start from sage.graphs.digraph import DiGraph from sage.graphs.dot2tex_utils import have_dot2tex - self._graph = DiGraph(self._graph) + self._graph = DiGraph(self._graph, format="list_of_edges") if have_dot2tex(): self._graph.set_latex_options(format="dot2tex", edge_labels=True) diff --git a/src/sage/combinat/rigged_configurations/bij_type_D_tri.py b/src/sage/combinat/rigged_configurations/bij_type_D_tri.py new file mode 100644 index 00000000000..57069640e5f --- /dev/null +++ b/src/sage/combinat/rigged_configurations/bij_type_D_tri.py @@ -0,0 +1,386 @@ +r""" +Bijection classes for type `D_4^{(3)}`. + +Part of the (internal) classes which runs the bijection between rigged +configurations and KR tableaux of type `D_4^{(3)}`. + +AUTHORS: + +- Travis Scrimshaw (2014-09-10): Initial version + +TESTS:: + + sage: KRT = crystals.TensorProductOfKirillovReshetikhinTableaux(['D', 4, 3], [[2, 1]]) + sage: from sage.combinat.rigged_configurations.bij_type_D_tri import KRTToRCBijectionTypeDTri + sage: bijection = KRTToRCBijectionTypeDTri(KRT(pathlist=[[-1,2]])) + sage: TestSuite(bijection).run() + sage: RC = RiggedConfigurations(['D', 4, 3], [[2, 1]]) + sage: from sage.combinat.rigged_configurations.bij_type_D_tri import RCToKRTBijectionTypeDTri + sage: bijection = RCToKRTBijectionTypeDTri(RC(partition_list=[[],[]])) + sage: TestSuite(bijection).run() +""" + +#***************************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# The full text of the GPL is available at: +# +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.combinat.rigged_configurations.bij_type_A import KRTToRCBijectionTypeA +from sage.combinat.rigged_configurations.bij_type_A import RCToKRTBijectionTypeA + +class KRTToRCBijectionTypeDTri(KRTToRCBijectionTypeA): + r""" + Specific implementation of the bijection from KR tableaux to rigged + configurations for type `D_4^{(3)}`. + + This inherits from type `A_n^{(1)}` because we use the same methods in + some places. + """ + def next_state(self, val): + r""" + Build the next state for type `D_4^{(3)}`. + + TESTS:: + + sage: KRT = crystals.TensorProductOfKirillovReshetikhinTableaux(['D', 4, 3], [[2,1]]) + sage: from sage.combinat.rigged_configurations.bij_type_D_tri import KRTToRCBijectionTypeDTri + sage: bijection = KRTToRCBijectionTypeDTri(KRT(pathlist=[[-1,2]])) + sage: bijection.cur_path.insert(0, []) + sage: bijection.cur_dims.insert(0, [0, 1]) + sage: bijection.cur_path[0].insert(0, [2]) + sage: bijection.next_state(2) + """ + tableau_height = len(self.cur_path[0]) - 1 + + if val == 'E': + self.ret_rig_con[0].insert_cell(0) + self.ret_rig_con[1].insert_cell(0) + if tableau_height == 0: + self.ret_rig_con[0].insert_cell(0) + self._update_vacancy_nums(0) + self._update_vacancy_nums(1) + self._update_partition_values(0) + self._update_partition_values(1) + return + + if val > 0: + # If it is a regular value, we follow the A_n rules + KRTToRCBijectionTypeA.next_state(self, val) + return + + pos_val = -val + + if pos_val == 0: + if len(self.ret_rig_con[0]) > 0: + max_width = self.ret_rig_con[0][0] + else: + max_width = 1 + max_width = self.ret_rig_con[0].insert_cell(max_width) + width_n = max_width + 1 + + # Follow regular A_n rules + for a in reversed(range(tableau_height, 2)): + max_width = self.ret_rig_con[a].insert_cell(max_width) + self._update_vacancy_nums(0) + self._update_partition_values(0) + self._update_vacancy_nums(1) + self._update_partition_values(1) + + # Make the largest string at \nu^{(1)} quasi-singular + p = self.ret_rig_con[0] + num_rows = len(p) + for i in range(num_rows): + if p._list[i] == width_n: + j = i+1 + while j < num_rows and p._list[j] == width_n \ + and p.vacancy_numbers[j] == p.rigging[j]: + j += 1 + p.rigging[j-1] -= 1 + break + return + + case_S = [None] * 2 + pos_val = -val + + if pos_val < 3: + # Always add a cell to the first singular value in the first + # tableau we are updating. + if len(self.ret_rig_con[pos_val - 1]) > 0: + max_width = self.ret_rig_con[pos_val - 1][0] + else: + max_width = 1 + + # Add cells similar to type A_n but we move to the right + for a in range(pos_val - 1, 2): + max_width = self.ret_rig_con[a].insert_cell(max_width) + case_S[a] = max_width + else: + if len(self.ret_rig_con[0]) > 0: + max_width = self.ret_rig_con[0][0] + else: + max_width = 1 + + # Special case for going through 0 + # If we find a quasi-singular string first, then we are in case (Q, S) + # otherwise we will find a singular string and insert 2 cells + P = self.ret_rig_con[0] + num_rows = len(P) + case_QS = False + for i in range(num_rows + 1): + if i == num_rows: + max_width = 0 + if case_QS: + P._list.append(1) + P.vacancy_numbers.append(None) + # Go through our partition until we find a length of greater than 1 + j = len(P._list) - 1 + while j >= 0 and P._list[j] == 1: + j -= 1 + P.rigging.insert(j + 1, None) + width_n = 1 + else: + # Go through our partition until we find a length of greater than 2 + j = len(P._list) - 1 + while j >= 0 and P._list[j] <= 2: + j -= 1 + P._list.insert(j+1, 2) + P.vacancy_numbers.insert(j+1, None) + P.rigging.insert(j+1, None) + break + elif P._list[i] <= max_width: + if P.vacancy_numbers[i] == P.rigging[i]: + max_width = P._list[i] + if case_QS: + P._list[i] += 1 + width_n = P._list[i] + P.rigging[i] = None + else: + j = i - 1 + while j >= 0 and P._list[j] <= max_width + 2: + P.rigging[j+1] = P.rigging[j] # Shuffle it along + j -= 1 + P._list.pop(i) + P._list.insert(j+1, max_width + 2) + P.rigging[j+1] = None + break + elif P.vacancy_numbers[i] - 1 == P.rigging[i] and not case_QS: + case_QS = True + P._list[i] += 1 + P.rigging[i] = None + # No need to set max_width here since we will find a singular string + + # Now go back following the regular C_n (ish) rules + if case_S[1] == max_width: + P = self.ret_rig_con[1] + + # Special case when adding twice to the first row + if P.rigging[0] is None: + P._list[0] += 1 + else: + for i in reversed(range(1, len(P))): + if P.rigging[i] is None: + j = i - 1 + while j >= 0 and P._list[j] == P._list[i]: + P.rigging[j+1] = P.rigging[j] # Shuffle it along + j -= 1 + P._list[j+1] += 1 + P.rigging[j+1] = None + break + else: + max_width = self.ret_rig_con[1].insert_cell(max_width) + + if tableau_height == 0: + if case_S[0] == max_width: + P = self.ret_rig_con[0] + # Since this is on the way back, the added string we want + # to bump will never be the first (largest) string + for i in reversed(range(1, len(P))): + if P.rigging[i] is None: + j = i - 1 + while j >= 0 and P._list[j] == P._list[i]: + P.rigging[j+1] = P.rigging[j] # Shuffle it along + j -= 1 + P._list[j+1] += 1 + P.rigging[j+1] = None + break + else: + max_width = self.ret_rig_con[0].insert_cell(max_width) + + self._update_vacancy_nums(0) + self._update_partition_values(0) + self._update_vacancy_nums(1) + self._update_partition_values(1) + + if case_QS: + # Make the new string quasi-singular + num_rows = len(P) + for i in range(num_rows): + if P._list[i] == width_n: + j = i+1 + while j < num_rows and P._list[j] == width_n \ + and P.vacancy_numbers[j] == P.rigging[j]: + j += 1 + P.rigging[j-1] -= 1 + break + +class RCToKRTBijectionTypeDTri(RCToKRTBijectionTypeA): + r""" + Specific implementation of the bijection from rigged configurations to + tensor products of KR tableaux for type `D_4^{(3)}`. + """ + def next_state(self, height): + r""" + Build the next state for type `D_4^{(3)}`. + + TESTS:: + + sage: RC = RiggedConfigurations(['D', 4, 3], [[2, 1]]) + sage: from sage.combinat.rigged_configurations.bij_type_D_tri import RCToKRTBijectionTypeDTri + sage: bijection = RCToKRTBijectionTypeDTri(RC(partition_list=[[3],[2]])) + sage: bijection.next_state(1) + -3 + """ + ell = [None] * 6 + case_S = [False] * 3 + case_Q = False + b = None + + # Calculate the rank and ell values + + last_size = 0 + for a in range(height, 2): + ell[a] = self._find_singular_string(self.cur_partitions[a], last_size) + + if ell[a] is None: + b = a + 1 + break + else: + last_size = self.cur_partitions[a][ell[a]] + + if b is None: + partition = self.cur_partitions[0] + # Modified version of _find_singular_string() + for i in reversed(range(len(partition))): + if partition[i] >= last_size: + if partition.vacancy_numbers[i] == partition.rigging[i] and i != ell[0]: + if partition[i] == 1: + b = 'E' + else: + last_size = partition[i] + case_S[2] = True + ell[3] = i + break + elif partition.vacancy_numbers[i] - 1 == partition.rigging[i] and not case_Q: + case_Q = True + # Check if the block is singular + block_size = partition[i] + for j in reversed(range(i)): + if partition[j] != block_size: + break + elif partition.vacancy_numbers[j] == partition.rigging[j] and j != ell[0]: + case_Q = False + break + if case_Q: + last_size = partition[i] + 1 + ell[2] = i + + if ell[3] is None: + if not case_Q: + b = 3 + else: + b = 0 + + if b is None: # Going back + if self.cur_partitions[1][ell[1]] == last_size: + ell[4] = ell[1] + case_S[1] = True + else: + ell[4] = self._find_singular_string(self.cur_partitions[1], last_size) + + if ell[4] is None: + b = -3 + else: + last_size = self.cur_partitions[1][ell[4]] + + if b is None: # Final partition + P = self.cur_partitions[0] + if ell[0] is not None and P[ell[0]] == last_size: + ell[5] = ell[0] + case_S[0] = True + else: + # Modified form of _find_singular_string + end = ell[3] + for i in reversed(range(end)): + if P[i] >= last_size and P.vacancy_numbers[i] == P.rigging[i]: + ell[5] = i + break + + if ell[5] is None: + b = -2 + + if b is None: + b = -1 + + # Determine the new rigged configuration by removing boxes from the + # selected string and then making the new string singular + if case_S[1]: + row1 = [self.cur_partitions[1].remove_cell(ell[4], 2)] + else: + row1 = [self.cur_partitions[1].remove_cell(ell[1]), + self.cur_partitions[1].remove_cell(ell[4])] + + if case_S[0]: + row0 = [self.cur_partitions[0].remove_cell(ell[5], 2)] + row0.append( self.cur_partitions[0].remove_cell(ell[3], 2) ) + else: + if case_Q: + if ell[0] < ell[2]: + row0 = [self.cur_partitions[0].remove_cell(ell[2]), + self.cur_partitions[0].remove_cell(ell[0])] + else: + row0 = [self.cur_partitions[0].remove_cell(ell[0]), + self.cur_partitions[0].remove_cell(ell[2])] + if case_S[2]: + quasi = self.cur_partitions[0].remove_cell(ell[3]) + else: + row0 = [self.cur_partitions[0].remove_cell(ell[0])] + if case_S[2]: + row0.append( self.cur_partitions[0].remove_cell(ell[3], 2) ) + + row0.append( self.cur_partitions[0].remove_cell(ell[5]) ) + + self._update_vacancy_numbers(0) + self._update_vacancy_numbers(1) + + for l in row1: + if l is not None: + self.cur_partitions[1].rigging[l] = self.cur_partitions[1].vacancy_numbers[l] + for l in row0: + if l is not None: + self.cur_partitions[0].rigging[l] = self.cur_partitions[0].vacancy_numbers[l] + + # If case (Q,S) holds, then we must make the larger string quasisingular + if case_Q and case_S[2]: + P = self.cur_partitions[0] + vac_num = P.vacancy_numbers[quasi] + P.rigging[quasi] = vac_num + block_len = P[quasi] + j = quasi + 1 + length = len(P) + # Find the place for the quasisingular rigging + while j < length and P[j] == block_len and P.rigging[j] == vac_num: + j += 1 + P.rigging[j-1] = vac_num - 1 + + return(b) + diff --git a/src/sage/combinat/rigged_configurations/bijection.py b/src/sage/combinat/rigged_configurations/bijection.py index 4711be4be84..1fd93fe40ac 100644 --- a/src/sage/combinat/rigged_configurations/bijection.py +++ b/src/sage/combinat/rigged_configurations/bijection.py @@ -8,10 +8,11 @@ - Travis Scrimshaw (2011-04-15): Initial version - Travis Scrimshaw (2012-12-21): Added all non-exceptional bijection types +- Travis Scrimshaw (2014-09-10): Added type `D_4^{(3)}` """ #***************************************************************************** -# Copyright (C) 2011, 2012 Travis Scrimshaw +# Copyright (C) 2011-2014 Travis Scrimshaw # # Distributed under the terms of the GNU General Public License (GPL) # @@ -49,6 +50,9 @@ from sage.combinat.rigged_configurations.bij_type_A2_odd import KRTToRCBijectionTypeA2Odd from sage.combinat.rigged_configurations.bij_type_A2_odd import RCToKRTBijectionTypeA2Odd +from sage.combinat.rigged_configurations.bij_type_D_tri import KRTToRCBijectionTypeDTri +from sage.combinat.rigged_configurations.bij_type_D_tri import RCToKRTBijectionTypeDTri + def KRTToRCBijection(tp_krt): r""" Return the correct KR tableaux to rigged configuration bijection helper class. @@ -83,7 +87,8 @@ def KRTToRCBijection(tp_krt): if ct.dual().type() == 'C': # D_{n+1}^{(2)} return KRTToRCBijectionTypeDTwisted(tp_krt) #if ct.dual().type() == 'F': # E_6^{(2)} - #if ct.dual().type() == 'G': # D_4^{(3)} + if ct.dual().type() == 'G': # D_4^{(3)} + return KRTToRCBijectionTypeDTri(tp_krt) raise NotImplementedError def RCToKRTBijection(rigged_configuration_elt): @@ -120,6 +125,7 @@ def RCToKRTBijection(rigged_configuration_elt): if ct.dual().type() == 'C': # D_{n+1}^{(2)} return RCToKRTBijectionTypeDTwisted(rigged_configuration_elt) #if ct.dual().type() == 'F': # E_6^{(2)} - #if ct.dual().type() == 'G': # D_4^{(3)} + if ct.dual().type() == 'G': # D_4^{(3)} + return RCToKRTBijectionTypeDTri(rigged_configuration_elt) raise NotImplementedError diff --git a/src/sage/combinat/rigged_configurations/kleber_tree.py b/src/sage/combinat/rigged_configurations/kleber_tree.py index 18cecea1f23..031c85902b3 100644 --- a/src/sage/combinat/rigged_configurations/kleber_tree.py +++ b/src/sage/combinat/rigged_configurations/kleber_tree.py @@ -65,6 +65,8 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools + from sage.misc.lazy_attribute import lazy_attribute from sage.misc.cachefunc import cached_method from sage.misc.latex import latex @@ -77,7 +79,6 @@ from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.combinat.root_system.cartan_type import CartanType -from sage.combinat.cartesian_product import CartesianProduct from sage.graphs.digraph import DiGraph from sage.graphs.dot2tex_utils import have_dot2tex @@ -846,7 +847,9 @@ def _children_iter(self, node): L = [range(val + 1) for val in node.up_root.to_vector()] - for root in CartesianProduct(*L).list()[1:]: # First element is the zero element + it = itertools.product(*L) + it.next() # First element is the zero element + for root in it: # Convert the list to an honest root in the root space converted_root = RS.sum_of_terms([[I[i], val] for i, val in enumerate(root)]) diff --git a/src/sage/combinat/rigged_configurations/kr_tableaux.py b/src/sage/combinat/rigged_configurations/kr_tableaux.py index acf1b1c9c85..1078be24fbd 100644 --- a/src/sage/combinat/rigged_configurations/kr_tableaux.py +++ b/src/sage/combinat/rigged_configurations/kr_tableaux.py @@ -44,6 +44,7 @@ from sage.misc.cachefunc import cached_method from sage.misc.abstract_method import abstract_method +from sage.misc.lazy_attribute import lazy_attribute from sage.misc.flatten import flatten from sage.structure.parent import Parent @@ -113,6 +114,7 @@ class KirillovReshetikhinTableaux(CrystalOfWords): - type `A_{2n}^{(2)}` for all `r`, - type `D_{n+1}^{(2)}` when `r < n`, + - type `D_4^{(3)}` when `r = 1`, the filling map is the same as given in [OSS2011]_ except for the rightmost column which is given by the column `[1, 2, \ldots, k, @@ -261,8 +263,11 @@ def __classcall_private__(cls, cartan_type, r, s): if r == ct.dual().classical().rank(): return KRTableauxDTwistedSpin(ct, r, s) return KRTableauxTypeBox(ct, r, s) - #if ct.dual().letter == 'F': # E_6^{(2)} - #if ct.dual().letter == 'G': # D_4^{(3)} + #if ct.dual().type() == 'F': # E_6^{(2)} + if ct.dual().type() == 'G': # D_4^{(3)} + if r == 1: + return KRTableauxTypeBox(ct, r, s) + return KRTableauxTypeDTri2(ct, r, s) raise NotImplementedError #return super(KirillovReshetikhinTableaux, cls).__classcall__(cls, ct, r, s) @@ -421,7 +426,7 @@ def _build_module_generators(self): ([[1, 1, 1], [2, 2, 2]],) """ - @abstract_method + @abstract_method(optional=True) def from_kirillov_reshetikhin_crystal(self, krc): """ Construct an element of ``self`` from the Kirillov-Reshetikhin @@ -488,7 +493,7 @@ def s(self): @cached_method def kirillov_reshetikhin_crystal(self): """ - Return the corresponding KR crystal in the + Return the corresponding KR crystal in the :func:`Kashiwara-Nakashima model `. @@ -844,7 +849,8 @@ class KRTableauxTypeBox(KRTableauxTypeVertical): Kirillov-Reshetikhin tableaux `B^{r,s}` of type: - `A_{2n}^{(2)}` for all `r \leq n`, - - `D_{n+1}^{(2)}` for all `r < n`. + - `D_{n+1}^{(2)}` for all `r < n`, + - `D_4^{(3)}` for `r = 1`. TESTS:: @@ -852,6 +858,8 @@ class KRTableauxTypeBox(KRTableauxTypeVertical): sage: TestSuite(KRT).run() sage: KRT = crystals.KirillovReshetikhin(['D', 4, 2], 2, 2, model='KR') sage: TestSuite(KRT).run() # long time + sage: KRT = crystals.KirillovReshetikhin(['D', 4, 3], 1, 2, model='KR') + sage: TestSuite(KRT).run() # long time """ def _fill(self, weight): r""" @@ -1318,7 +1326,7 @@ def e(self, i): sage: KRT.module_generators[0].e(0) [[-2, 1], [-1, -1]] """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): ret = self.to_kirillov_reshetikhin_crystal().e0() if ret is None: return None @@ -1339,7 +1347,7 @@ def f(self, i): sage: KRT.module_generators[0].f(0) [[1, 1], [2, -1]] """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): ret = self.to_kirillov_reshetikhin_crystal().f0() if ret is None: return None @@ -1361,7 +1369,7 @@ def epsilon(self, i): sage: KRT.module_generators[0].epsilon(0) 2 """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.to_kirillov_reshetikhin_crystal().epsilon0() return TensorProductOfRegularCrystalsElement.epsilon(self, i) @@ -1379,7 +1387,7 @@ def phi(self, i): sage: KRT.module_generators[0].phi(0) 2 """ - if i == 0: + if i == self.parent()._cartan_type.special_node(): return self.to_kirillov_reshetikhin_crystal().phi0() return TensorProductOfRegularCrystalsElement.phi(self, i) @@ -1477,7 +1485,8 @@ def e(self, i): [[1], [3], [-4], [-2]] sage: KRT(-1, -4, 3, 2).e(3) """ - if i == 0: # Only need to do it once since we pull to the KR crystal + if i == self.parent()._cartan_type.special_node(): + # Only need to do it once since we pull to the KR crystal return KirillovReshetikhinTableauxElement.e(self, i) half = KirillovReshetikhinTableauxElement.e(self, i) @@ -1496,7 +1505,8 @@ def f(self, i): sage: KRT(-1, -4, 3, 2).f(3) [[2], [4], [-3], [-1]] """ - if i == 0: # Only need to do it once since we pull to the KR crystal + if i == self.parent()._cartan_type.special_node(): + # Only need to do it once since we pull to the KR crystal return KirillovReshetikhinTableauxElement.f(self, i) half = KirillovReshetikhinTableauxElement.f(self, i) @@ -1517,7 +1527,8 @@ def epsilon(self, i): sage: KRT(-1, -4, 3, 2).epsilon(3) 0 """ - if i == 0: # Don't need to half it since we pull to the KR crystal + if i == self.parent()._cartan_type.special_node(): + # Don't need to half it since we pull to the KR crystal return KirillovReshetikhinTableauxElement.epsilon(self, i) return KirillovReshetikhinTableauxElement.epsilon(self, i) // 2 @@ -1533,7 +1544,8 @@ def phi(self, i): sage: KRT(-1, -4, 3, 2).phi(3) 1 """ - if i == 0: # Don't need to half it since we pull to the KR crystal + if i == self.parent()._cartan_type.special_node(): + # Don't need to half it since we pull to the KR crystal return KirillovReshetikhinTableauxElement.phi(self, i) return KirillovReshetikhinTableauxElement.phi(self, i) // 2 @@ -1640,3 +1652,165 @@ class KRTableauxDTwistedSpin(KRTableauxRectangle): """ Element = KRTableauxSpinElement +class KRTableauxTypeDTri2Element(KirillovReshetikhinTableauxElement): + r""" + A Kirillov-Reshetikhin tableau in `B^{2,s}` of type `D_4^{(3)}`. + """ + def e(self, i): + """ + Perform the action of `e_i` on ``self``. + + .. TODO:: + + Implement a direct action of `e_0` without moving to + rigged configurations. + + EXAMPLES:: + + sage: KRT = crystals.KirillovReshetikhin(['D',4,3], 2, 1, model='KR') + sage: KRT.module_generators[0].e(0) + [[2], [E]] + """ + if i == self.parent().cartan_type().special_node(): + P = self.parent() + from sage.combinat.rigged_configurations.tensor_product_kr_tableaux import TensorProductOfKirillovReshetikhinTableaux + K = TensorProductOfKirillovReshetikhinTableaux(P.cartan_type(), [[2, P.s()]]) + ret = K(self).to_rigged_configuration() + RC = ret.parent() + ret = ret.to_virtual_configuration().e(0) + if ret is None: + return None + ret = RC.from_virtual(ret) + return ret.to_tensor_product_of_kirillov_reshetikhin_tableaux()[0] + return TensorProductOfRegularCrystalsElement.e(self, i) + + def f(self, i): + """ + Perform the action of `f_i` on ``self``. + + .. TODO:: + + Implement a direct action of `f_0` without moving to + rigged configurations. + + EXAMPLES:: + + sage: KRT = crystals.KirillovReshetikhin(['D',4,3], 2, 1, model='KR') + sage: KRT.module_generators[0].f(0) + sage: KRT.module_generators[3].f(0) + [[1], [0]] + """ + if i == self.parent().cartan_type().special_node(): + P = self.parent() + from sage.combinat.rigged_configurations.tensor_product_kr_tableaux import TensorProductOfKirillovReshetikhinTableaux + K = TensorProductOfKirillovReshetikhinTableaux(P.cartan_type(), [[2, P.s()]]) + ret = K(self).to_rigged_configuration() + RC = ret.parent() + ret = ret.to_virtual_configuration().f(0) + if ret is None: + return None + ret = RC.from_virtual(ret) + return ret.to_tensor_product_of_kirillov_reshetikhin_tableaux()[0] + return TensorProductOfRegularCrystalsElement.f(self, i) + + def epsilon(self, i): + r""" + Compute `\varepsilon_i` of ``self``. + + .. TODO:: + + Implement a direct action of `\epsilon_0` without moving to + KR crystals. + + EXAMPLES:: + + sage: KRT = crystals.KirillovReshetikhin(['D',4,3], 2, 2, model='KR') + sage: KRT.module_generators[0].epsilon(0) + 6 + """ + if i == self.parent().cartan_type().special_node(): + P = self.parent() + from sage.combinat.rigged_configurations.tensor_product_kr_tableaux import TensorProductOfKirillovReshetikhinTableaux + K = TensorProductOfKirillovReshetikhinTableaux(P.cartan_type(), [[2, P.s()]]) + rc = K(self).to_rigged_configuration().to_virtual_configuration() + return rc.epsilon(0) + return TensorProductOfRegularCrystalsElement.epsilon(self, i) + + def phi(self, i): + r""" + Compute `\varphi_i` of ``self``. + + .. TODO:: + + Compute `\phi_0` without moving to KR crystals. + + EXAMPLES:: + + sage: KRT = crystals.KirillovReshetikhin(['D',4,3], 2, 2, model='KR') + sage: KRT.module_generators[0].phi(0) + 0 + """ + if i == self.parent().cartan_type().special_node(): + P = self.parent() + from sage.combinat.rigged_configurations.tensor_product_kr_tableaux import TensorProductOfKirillovReshetikhinTableaux + K = TensorProductOfKirillovReshetikhinTableaux(P.cartan_type(), [[2, P.s()]]) + rc = K(self).to_rigged_configuration().to_virtual_configuration() + return rc.phi(0) + return TensorProductOfRegularCrystalsElement.phi(self, i) + +class KRTableauxTypeDTri2(KirillovReshetikhinTableaux): + r""" + Kirillov-Reshetikhin tableaux `B^{2,s}` of type `D_4^{(3)}`. + + .. WARNING:: + + The Kashiwara-Nakashima version is not implemented due to the + non-trivial multiplicities of classical components, so + :meth:`classical_decomposition` does not work. + """ + def __init__(self, cartan_type, r, s): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: KRT = crystals.KirillovReshetikhin(['D', 4, 3], 2, 1, model='KR') + sage: TestSuite(KRT).run() # long time + """ + # We must modify the constructor of KirillovReshetikhinTableaux + self._r = r + self._s = s + self._cartan_type = cartan_type + Parent.__init__(self, category=(RegularCrystals(), FiniteCrystals())) + self.letters = CrystalOfLetters(cartan_type.classical()) + + @lazy_attribute + def module_generators(self): + """ + The module generators of ``self``. + + EXAMPLES:: + + sage: KRT = crystals.KirillovReshetikhin(['D',4,3], 2, 1, model='KR') + sage: KRT.module_generators + ([[1], [2]], [[1], [0]], [[1], [E]], [[E], [E]]) + """ + return self._build_module_generators() + + def _build_module_generators(self): + r""" + Return the module generators of ``self``. + + EXAMPLES:: + + sage: KRT = crystals.KirillovReshetikhin(['D',4,3], 2, 1, model='KR') + sage: KRT._build_module_generators() + ([[1], [2]], [[1], [0]], [[1], [E]], [[E], [E]]) + """ + from sage.combinat.rigged_configurations.rigged_configurations import RiggedConfigurations + RC = RiggedConfigurations(self._cartan_type, [[self._r, self._s]]) + return tuple(mg.to_tensor_product_of_kirillov_reshetikhin_tableaux()[0] + for mg in RC.module_generators) + + Element = KRTableauxTypeDTri2Element + diff --git a/src/sage/combinat/rigged_configurations/rigged_configurations.py b/src/sage/combinat/rigged_configurations/rigged_configurations.py index abfba3bc94b..190a57e9944 100644 --- a/src/sage/combinat/rigged_configurations/rigged_configurations.py +++ b/src/sage/combinat/rigged_configurations/rigged_configurations.py @@ -21,6 +21,8 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools + from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute from sage.structure.global_options import GlobalOptions @@ -32,7 +34,6 @@ from sage.categories.finite_crystals import FiniteCrystals from sage.categories.regular_crystals import RegularCrystals from sage.combinat.root_system.cartan_type import CartanType -from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.rigged_configurations.kleber_tree import KleberTree, VirtualKleberTree from sage.combinat.rigged_configurations.rigged_configuration_element import ( RiggedConfigurationElement, KRRCSimplyLacedElement, KRRCNonSimplyLacedElement, @@ -588,10 +589,9 @@ def module_generators(self): L2 = [] for block in blocks[-1]: L2.append(IterableFunctionCall(self._block_iterator, block)) - L.append(CartesianProduct(*L2)) + L.append(itertools.product(*L2)) - # TODO Find a more efficient method without appealing to the CartesianProduct - C = CartesianProduct(*L) + C = itertools.product(*L) for curBlocks in C: module_gens.append( self.element_class(self, KT_constructor=[shapes[:], self._blocks_to_values(curBlocks[:]), @@ -1265,10 +1265,9 @@ def module_generators(self): L2 = [] for block in blocks[-1]: L2.append(IterableFunctionCall(self._block_iterator, block)) - L.append(CartesianProduct(*L2)) + L.append(itertools.product(*L2)) - # TODO Find a more efficient method without appealing to the CartesianProduct - C = CartesianProduct(*L) + C = itertools.product(*L) for cur_blocks in C: module_gens.append( self.element_class(self, KT_constructor=[shapes[:], self._blocks_to_values(cur_blocks[:]), vac_nums[:]]) ) @@ -1754,7 +1753,7 @@ def module_generators(self): L2 = [] for block in blocks[-1]: L2.append(IterableFunctionCall(self._block_iterator, block)) - L.append(CartesianProduct(*L2)) + L.append(itertools.product(*L2)) # Special case for the final tableau partition = base[-1] @@ -1785,10 +1784,9 @@ def module_generators(self): L2.append(IterableFunctionCall(self._block_iterator_n_odd, block)) else: L2.append(IterableFunctionCall(self._block_iterator, block)) - L.append(CartesianProduct(*L2)) + L.append(itertools.product(*L2)) - # TODO Find a more efficient method without appealing to the CartesianProduct - C = CartesianProduct(*L) + C = itertools.product(*L) for curBlocks in C: module_gens.append( self.element_class(self, KT_constructor=[shapes[:], self._blocks_to_values(curBlocks[:]), vac_nums[:]]) ) diff --git a/src/sage/combinat/root_system/__init__.py b/src/sage/combinat/root_system/__init__.py index f4cfb1b9a9a..6a9be3b1b0c 100644 --- a/src/sage/combinat/root_system/__init__.py +++ b/src/sage/combinat/root_system/__init__.py @@ -35,6 +35,7 @@ - :ref:`sage.combinat.root_system.dynkin_diagram` - :ref:`sage.combinat.root_system.cartan_matrix` - :ref:`sage.combinat.root_system.coxeter_matrix` +- :ref:`sage.combinat.root_system.coxeter_type` Root systems ------------ diff --git a/src/sage/combinat/root_system/all.py b/src/sage/combinat/root_system/all.py index f3b45cbe666..a4a4ecc0c33 100644 --- a/src/sage/combinat/root_system/all.py +++ b/src/sage/combinat/root_system/all.py @@ -2,12 +2,12 @@ Root system features that are imported by default in the interpreter namespace """ from sage.misc.lazy_import import lazy_import -lazy_import('sage.combinat.root_system.associahedron', 'Associahedron') from cartan_type import CartanType from dynkin_diagram import DynkinDiagram -from cartan_matrix import CartanMatrix, cartan_matrix -from coxeter_matrix import coxeter_matrix +from cartan_matrix import CartanMatrix +from coxeter_matrix import CoxeterMatrix, coxeter_matrix +from coxeter_type import CoxeterType from root_system import RootSystem, WeylDim from weyl_group import WeylGroup, WeylGroupElement lazy_import('sage.combinat.root_system.extended_affine_weyl_group', 'ExtendedAffineWeylGroup') diff --git a/src/sage/combinat/root_system/associahedron.py b/src/sage/combinat/root_system/associahedron.py index b007d82c7b8..466f4a34685 100644 --- a/src/sage/combinat/root_system/associahedron.py +++ b/src/sage/combinat/root_system/associahedron.py @@ -1,7 +1,7 @@ r""" Associahedron -.. todo:: +.. TODO:: - fix adjacency matrix - edit graph method to get proper vertex labellings @@ -28,9 +28,7 @@ def Associahedron(cartan_type): r""" - Construct an associahedron - - An Associahedron + Construct an associahedron. The generalized associahedron is a polytopal complex with vertices in one-to-one correspondence with clusters in the cluster complex, and with @@ -38,44 +36,46 @@ def Associahedron(cartan_type): intersect in codimension 1. The associahedron of type `A_n` is one way to realize the classical - associahedron as defined in :wikipedia:`Associahedron` + associahedron as defined in the :wikipedia:`Associahedron`. - A polytopal realization of the associahedron can be found in [CFZ]. The - implementation is based on [CFZ, Theorem 1.5, Remark 1.6, and Corollary - 1.9.]. + A polytopal realization of the associahedron can be found in [CFZ]_. The + implementation is based on [CFZ]_, Theorem 1.5, Remark 1.6, and Corollary + 1.9. EXAMPLES:: - sage: Asso = Associahedron(['A',2]); Asso + sage: Asso = polytopes.associahedron(['A',2]); Asso Generalized associahedron of type ['A', 2] with 5 vertices + sage: sorted(Asso.Hrepresentation(), key=repr) [An inequality (-1, 0) x + 1 >= 0, An inequality (0, -1) x + 1 >= 0, An inequality (0, 1) x + 1 >= 0, An inequality (1, 0) x + 1 >= 0, An inequality (1, 1) x + 1 >= 0] + sage: Asso.Vrepresentation() (A vertex at (1, -1), A vertex at (1, 1), A vertex at (-1, 1), A vertex at (-1, 0), A vertex at (0, -1)) - sage: Associahedron(['B',2]) + sage: polytopes.associahedron(['B',2]) Generalized associahedron of type ['B', 2] with 6 vertices - The two pictures of [CFZ] can be recovered with:: + The two pictures of [CFZ]_ can be recovered with:: - sage: Asso = Associahedron(['A',3]); Asso + sage: Asso = polytopes.associahedron(['A',3]); Asso Generalized associahedron of type ['A', 3] with 14 vertices sage: Asso.plot() Graphics3d Object - sage: Asso = Associahedron(['B',3]); Asso + sage: Asso = polytopes.associahedron(['B',3]); Asso Generalized associahedron of type ['B', 3] with 20 vertices sage: Asso.plot() Graphics3d Object TESTS:: - sage: sorted(Associahedron(['A',3]).vertices()) + sage: sorted(polytopes.associahedron(['A',3]).vertices()) [A vertex at (-3/2, 0, -1/2), A vertex at (-3/2, 0, 3/2), A vertex at (-3/2, 1, -3/2), A vertex at (-3/2, 2, -3/2), A vertex at (-3/2, 2, 3/2), A vertex at (-1/2, -1, -1/2), @@ -84,7 +84,7 @@ def Associahedron(cartan_type): A vertex at (3/2, -2, 3/2), A vertex at (3/2, 0, -3/2), A vertex at (3/2, 2, -3/2), A vertex at (3/2, 2, 3/2)] - sage: sorted(Associahedron(['B',3]).vertices()) + sage: sorted(polytopes.associahedron(['B',3]).vertices()) [A vertex at (-3, 0, 0), A vertex at (-3, 0, 3), A vertex at (-3, 2, -2), A vertex at (-3, 4, -3), A vertex at (-3, 5, -3), A vertex at (-3, 5, 3), @@ -96,24 +96,16 @@ def Associahedron(cartan_type): A vertex at (3, -3, 0), A vertex at (3, 3, -3), A vertex at (3, 5, -3), A vertex at (3, 5, 3)] - sage: Associahedron(['A',4]).f_vector() + sage: polytopes.associahedron(['A',4]).f_vector() (1, 42, 84, 56, 14, 1) - sage: Associahedron(['B',4]).f_vector() + sage: polytopes.associahedron(['B',4]).f_vector() (1, 70, 140, 90, 20, 1) - - REFERENCES: - - - [CFZ] Chapoton, Fomin, Zelevinsky - Polytopal realizations of - generalized associahedra, arXiv:0202004. """ - cartan_type = CartanType( cartan_type ) + cartan_type = CartanType(cartan_type) parent = Associahedra(QQ, cartan_type.rank()) return parent(cartan_type) - - - class Associahedron_class(Polyhedron_QQ_ppl): r""" The Python class of an associahedron @@ -123,55 +115,53 @@ class Associahedron_class(Polyhedron_QQ_ppl): TESTS:: - sage: Asso = Associahedron(['A',2]); Asso + sage: Asso = polytopes.associahedron(['A',2]); Asso Generalized associahedron of type ['A', 2] with 5 vertices sage: TestSuite(Asso).run() """ def _repr_(self): r""" - Returns a string representation of self. + Return a string representation of ``self``. EXAMPLES:: - sage: Associahedron(['A',3])._repr_() + sage: polytopes.associahedron(['A',3])._repr_() "Generalized associahedron of type ['A', 3] with 14 vertices" """ - return 'Generalized associahedron of type %s with %s vertices'\ - %(self._cartan_type,self.n_vertices()) + msg = 'Generalized associahedron of type {} with {} vertices' + return msg.format(self._cartan_type, self.n_vertices()) def cartan_type(self): r""" - Returns the Cartan type + Return the Cartan type of ``self``. EXAMPLES:: - sage: Associahedron(['A',3]).cartan_type() + sage: polytopes.associahedron(['A',3]).cartan_type() ['A', 3] """ return self._cartan_type def vertices_in_root_space(self): r""" - Returns the vertices of ``self`` as elements in the root space + Return the vertices of ``self`` as elements in the root space. EXAMPLES:: - sage: Asso = Associahedron(['A',2]) + sage: Asso = polytopes.associahedron(['A',2]) sage: Asso.vertices() (A vertex at (1, -1), A vertex at (1, 1), A vertex at (-1, 1), A vertex at (-1, 0), A vertex at (0, -1)) sage: Asso.vertices_in_root_space() - (alpha[1] - alpha[2], alpha[1] + alpha[2], -alpha[1] + alpha[2], -alpha[1], -alpha[2]) + (alpha[1] - alpha[2], alpha[1] + alpha[2], -alpha[1] + alpha[2], + -alpha[1], -alpha[2]) """ root_space = self._cartan_type.root_system().root_space() - return tuple( root_space.from_vector(vector(V)) for V in self.vertex_generator() ) - - - - + return tuple(root_space.from_vector(vector(V)) + for V in self.vertex_generator()) class Associahedra(Polyhedra_QQ_ppl): @@ -216,20 +206,19 @@ def _element_constructor_(self, cartan_type, **kwds): sage: parent._element_constructor_(['A',2]) Generalized associahedron of type ['A', 2] with 5 vertices """ - cartan_type = CartanType( cartan_type ) + cartan_type = CartanType(cartan_type) if not cartan_type.is_finite(): raise ValueError("the Cartan type must be finite") root_space = cartan_type.root_system().root_space() # TODO: generalize this as a method of root lattice realization - rhocheck = sum( beta.associated_coroot() for beta in root_space.positive_roots() )/2 + rhocheck = sum(beta.associated_coroot() + for beta in root_space.positive_roots()) / 2 I = root_space.index_set() inequalities = [] for orbit in root_space.almost_positive_roots_decomposition(): c = rhocheck.coefficient(orbit[0].leading_support()) for beta in orbit: - inequalities.append( [c] + [ beta.coefficient(i) for i in I ] ) - associahedron = super(Associahedra, self)._element_constructor_(None, [inequalities,[]]) + inequalities.append([c] + [beta.coefficient(i) for i in I]) + associahedron = super(Associahedra, self)._element_constructor_(None, [inequalities, []]) associahedron._cartan_type = cartan_type return associahedron - - diff --git a/src/sage/combinat/root_system/cartan_matrix.py b/src/sage/combinat/root_system/cartan_matrix.py index 28e29fca36c..6d152d57376 100644 --- a/src/sage/combinat/root_system/cartan_matrix.py +++ b/src/sage/combinat/root_system/cartan_matrix.py @@ -35,6 +35,7 @@ from sage.combinat.root_system.cartan_type import CartanType, CartanType_abstract from sage.combinat.root_system.root_system import RootSystem from sage.sets.family import Family +from sage.graphs.digraph import DiGraph class CartanMatrix(Matrix_integer_sparse, CartanType_abstract): r""" @@ -197,9 +198,10 @@ class CartanMatrix(Matrix_integer_sparse, CartanType_abstract): __metaclass__ = InheritComparisonClasscallMetaclass @staticmethod - def __classcall_private__(cls, *args, **kwds): + def __classcall_private__(cls, data=None, index_set=None, + cartan_type=None, cartan_type_check=True): """ - Normalize input so we can inherit from spare integer matrix. + Normalize input so we can inherit from sparse integer matrix. .. NOTE:: @@ -227,30 +229,31 @@ def __classcall_private__(cls, *args, **kwds): ('a', 'b') """ # Special case with 0 args and kwds has Cartan type - if "cartan_type" in kwds and len(args) == 0: - args = (CartanType(kwds["cartan_type"]),) - if len(args) == 0: + if cartan_type is not None and data is None: + data = CartanType(cartan_type) + + if data is None: data = [] n = 0 index_set = tuple() cartan_type = None subdivisions = None - elif len(args) == 4 and isinstance(args[0], MatrixSpace): # For pickling - return typecall(cls, args[0], args[1], args[2], args[3]) - elif isinstance(args[0], CartanMatrix): - return args[0] + elif isinstance(data, CartanMatrix): + if index_set is not None: + d = {a: index_set[i] for i,a in enumerate(data.index_set())} + return data.relabel(d) + return data else: - cartan_type = None dynkin_diagram = None subdivisions = None from sage.combinat.root_system.dynkin_diagram import DynkinDiagram_class - if isinstance(args[0], DynkinDiagram_class): - dynkin_diagram = args[0] - cartan_type = args[0]._cartan_type + if isinstance(data, DynkinDiagram_class): + dynkin_diagram = data + cartan_type = data._cartan_type else: try: - cartan_type = CartanType(args[0]) + cartan_type = CartanType(data) dynkin_diagram = cartan_type.dynkin_diagram() except (TypeError, ValueError): pass @@ -258,54 +261,74 @@ def __classcall_private__(cls, *args, **kwds): if dynkin_diagram is not None: n = dynkin_diagram.rank() index_set = dynkin_diagram.index_set() - reverse = dict((index_set[i], i) for i in range(len(index_set))) + reverse = {a: i for i,a in enumerate(index_set)} data = {(i, i): 2 for i in range(n)} for (i,j,l) in dynkin_diagram.edge_iterator(): data[(reverse[j], reverse[i])] = -l else: - M = matrix(args[0]) + M = matrix(data) if not is_generalized_cartan_matrix(M): raise ValueError("the input matrix is not a generalized Cartan matrix") n = M.ncols() - if "cartan_type" in kwds: - cartan_type = CartanType(kwds["cartan_type"]) - elif n == 1: - cartan_type = CartanType(['A', 1]) - elif kwds.get("cartan_type_check", True): - cartan_type = find_cartan_type_from_matrix(M) data = M.dict() subdivisions = M._subdivisions - if len(args) == 1: - if cartan_type is not None: - index_set = tuple(cartan_type.index_set()) - elif dynkin_diagram is None: - index_set = tuple(range(n)) - elif len(args) == 2: - index_set = tuple(args[1]) - if len(index_set) != n and len(set(index_set)) != n: - raise ValueError("the given index set is not valid") + if index_set is None: + index_set = tuple(range(n)) else: - raise ValueError("too many arguments") + index_set = tuple(index_set) - mat = typecall(cls, MatrixSpace(ZZ, n, sparse=True), data, cartan_type, index_set) + if len(index_set) != n and len(set(index_set)) != n: + raise ValueError("the given index set is not valid") + + # We can do the Cartan type initialization later as this is not + # a unqiue representation + mat = typecall(cls, MatrixSpace(ZZ, n, sparse=True), data, False, False) + # FIXME: We have to initialize the CartanMatrix part separately because + # of the __cinit__ of the matrix. We should get rid of this workaround + mat._CM_init(cartan_type, index_set, cartan_type_check) mat._subdivisions = subdivisions return mat - def __init__(self, parent, data, cartan_type, index_set): + def _CM_init(self, cartan_type, index_set, cartan_type_check): """ - Initialize ``self``. + Initialize ``self`` as a Cartan matrix. TESTS:: - sage: C = CartanMatrix(['A',1,1]) + sage: C = CartanMatrix(['A',1,1]) # indirect doctest sage: TestSuite(C).run(skip=["_test_category", "_test_change_ring"]) """ - Matrix_integer_sparse.__init__(self, parent, data, False, False) - self._cartan_type = cartan_type self._index_set = index_set self.set_immutable() + if cartan_type is not None: + cartan_type = CartanType(cartan_type) + elif self.nrows() == 1: + cartan_type = CartanType(['A', 1]) + elif cartan_type_check: + # Placeholder so we don't have to reimplement creating a + # Dynkin diagram from a Cartan matrix + self._cartan_type = None + cartan_type = find_cartan_type_from_matrix(self) + + self._cartan_type = cartan_type + + def __reduce__(self): + """ + Used for pickling. + + TESTS:: + + sage: CM = CartanMatrix(['A',4]) + sage: x = loads(dumps(CM)) + sage: x._index_set + (1, 2, 3, 4) + """ + if self._cartan_type: + return (CartanMatrix, (self._cartan_type,)) + return (CartanMatrix, (self.dynkin_diagram(),)) + def root_system(self): """ Return the root system corresponding to ``self``. @@ -462,14 +485,17 @@ def subtype(self, index_set): EXAMPLES:: sage: C = CartanMatrix(['F',4]) - sage: C.subtype([1,2,3]) + sage: S = C.subtype([1,2,3]) + sage: S [ 2 -1 0] [-1 2 -1] [ 0 -2 2] + sage: S.index_set() + (1, 2, 3) """ ind = self.index_set() I = [ind.index(i) for i in index_set] - return CartanMatrix(self.matrix_from_rows_and_columns(I, I)) + return CartanMatrix(self.matrix_from_rows_and_columns(I, I), index_set) def rank(self): r""" @@ -891,88 +917,84 @@ def is_generalized_cartan_matrix(M): return True def find_cartan_type_from_matrix(CM): - """ - Find a Cartan type by direct comparison of matrices given from the - generalized Cartan matrix ``CM`` and return ``None`` if not found. + r""" + Find a Cartan type by direct comparison of Dynkin diagrams given from + the generalized Cartan matrix ``CM`` and return ``None`` if not found. INPUT: - - ``CM`` -- A generalized Cartan matrix + - ``CM`` -- a generalized Cartan matrix EXAMPLES:: sage: from sage.combinat.root_system.cartan_matrix import find_cartan_type_from_matrix - sage: M = matrix([[2,-1,-1], [-1,2,-1], [-1,-1,2]]) - sage: find_cartan_type_from_matrix(M) + sage: CM = CartanMatrix([[2,-1,-1], [-1,2,-1], [-1,-1,2]]) + sage: find_cartan_type_from_matrix(CM) ['A', 2, 1] - sage: M = matrix([[2,-1,0], [-1,2,-2], [0,-1,2]]) - sage: find_cartan_type_from_matrix(M) - ['C', 3] - sage: M = matrix([[2,-1,-2], [-1,2,-1], [-2,-1,2]]) - sage: find_cartan_type_from_matrix(M) + sage: CM = CartanMatrix([[2,-1,0], [-1,2,-2], [0,-1,2]]) + sage: find_cartan_type_from_matrix(CM) + ['C', 3] relabelled by {1: 0, 2: 1, 3: 2} + sage: CM = CartanMatrix([[2,-1,-2], [-1,2,-1], [-2,-1,2]]) + sage: find_cartan_type_from_matrix(CM) """ - n = CM.ncols() - # Build the list to test based upon rank - if n == 1: - return CartanType(['A', 1]) - - test = [['A', n]] - if n >= 2: - if n == 2: - test += [['G',2], ['A',2,2]] - test += [['B',n], ['A',n-1,1]] - if n >= 3: - if n == 3: - test += [['G',2,1], ['D',4,3]] - test += [['C',n], ['BC',n-1,2], ['C',n-1,1]] - if n >= 4: - if n == 4: - test += [['F',4], ['G',2,1], ['D',4,3]] - test += [['D',n], ['B',n-1,1]] - if n == 5: - test += [['F',4,1], ['D',n-1,1]] - elif n == 6: - test.append(['E',6]) - elif n == 7: - test += [['E',7], ['E',6,1]] - elif n == 8: - test += [['E',8], ['E',7,1]] - elif n == 9: - test.append(['E',8,1]) - - # Test every possible Cartan type and its dual - for x in test: - ct = CartanType(x) - if ct.cartan_matrix() == CM: - return ct - if ct == ct.dual(): + types = [] + for S in CM.dynkin_diagram().connected_components_subgraphs(): + S = DiGraph(S) # We need a simple digraph here + n = S.num_verts() + # Build the list to test based upon rank + if n == 1: + types.append(CartanType(['A', 1])) continue - ct = ct.dual() - if ct.cartan_matrix() == CM: - return ct - return None - -def cartan_matrix(t): - """ - Return the Cartan matrix of type `t`. - - .. NOTE:: - - This function is deprecated in favor of - ``CartanMatrix(...)``, to avoid polluting the - global namespace. - EXAMPLES:: - - sage: cartan_matrix(['A', 4]) - doctest:...: DeprecationWarning: cartan_matrix() is deprecated. Use CartanMatrix() instead - See http://trac.sagemath.org/14137 for details. - [ 2 -1 0 0] - [-1 2 -1 0] - [ 0 -1 2 -1] - [ 0 0 -1 2] - """ - from sage.misc.superseded import deprecation - deprecation(14137, 'cartan_matrix() is deprecated. Use CartanMatrix() instead') - return CartanMatrix(t) + test = [['A', n]] + if n >= 2: + if n == 2: + test += [['G',2], ['A',2,2]] + test += [['B',n], ['A',n-1,1]] + if n >= 3: + if n == 3: + test.append(['G',2,1]) + test += [['C',n], ['BC',n-1,2], ['C',n-1,1]] + if n >= 4: + if n == 4: + test.append(['F',4]) + test += [['D',n], ['B',n-1,1]] + if n >= 5: + if n == 5: + test.append(['F',4,1]) + test.append(['D',n-1,1]) + if n == 6: + test.append(['E',6]) + elif n == 7: + test += [['E',7], ['E',6,1]] + elif n == 8: + test += [['E',8], ['E',7,1]] + elif n == 9: + test.append(['E',8,1]) + + # Test every possible Cartan type and its dual + found = False + for x in test: + ct = CartanType(x) + T = DiGraph(ct.dynkin_diagram()) # We need a simple digraph here + iso, match = T.is_isomorphic(S, certify=True, edge_labels=True) + if iso: + types.append(ct.relabel(match)) + found = True + break + + if ct == ct.dual(): + continue # self-dual, so nothing more to test + + ct = ct.dual() + T = DiGraph(ct.dynkin_diagram()) # We need a simple digraph here + iso, match = T.is_isomorphic(S, certify=True, edge_labels=True) + if iso: + types.append(ct.relabel(match)) + found = True + break + if not found: + return None + + return CartanType(types) diff --git a/src/sage/combinat/root_system/cartan_type.py b/src/sage/combinat/root_system/cartan_type.py index 8514ccec51c..17fbc7cd24c 100644 --- a/src/sage/combinat/root_system/cartan_type.py +++ b/src/sage/combinat/root_system/cartan_type.py @@ -279,9 +279,9 @@ .. rubric:: Affine Cartan types -For affine types, we use the usual conventions for affine Coxeter groups: each affine type -is either untwisted (that is arise from the natural affinisation -of a finite Cartan type):: +For affine types, we use the usual conventions for affine Coxeter +groups: each affine type is either untwisted (that is arise from the +natural affinisation of a finite Cartan type):: sage: CartanType(["A", 4, 1]).dynkin_diagram() 0 @@ -451,9 +451,7 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.global_options import GlobalOptions from sage.sets.family import Family -from sage.misc.superseded import deprecated_function_alias from sage.misc.decorators import rename_keyword -from sage.misc.misc import union from __builtin__ import sorted # TODO: @@ -605,6 +603,16 @@ def __call__(self, *args): True sage: CartanType('A2') ['A', 2] + + Check that we can pass any Cartan type as a single element list:: + + sage: CT = CartanType(['A2', 'A2', 'A2']) + sage: CartanType([CT]) + A2xA2xA2 + + sage: CT = CartanType('A2').relabel({1:-1, 2:-2}) + sage: CartanType([CT]) + ['A', 2] relabelled by {1: -1, 2: -2} """ if len(args) == 1: t = args[0] @@ -618,6 +626,10 @@ def __call__(self, *args): if len(t) == 1: # Fix for trac #13774 t = t[0] + # We need to make another check + if isinstance(t, CartanType_abstract): + return t + if isinstance(t, str): if "x" in t: import type_reducible @@ -1085,16 +1097,20 @@ def coxeter_matrix(self): [2 3 1 3] [2 2 3 1] """ - from sage.matrix.constructor import matrix - from sage.rings.all import ZZ - index_set = self.index_set() - reverse = dict((index_set[i], i) for i in range(len(index_set))) - m = matrix(ZZ,len(index_set), lambda i,j: 1 if i==j else 2) - for (i,j,l) in self.coxeter_diagram().edge_iterator(): - m[reverse[i], reverse[j]] = l - m[reverse[j], reverse[i]] = l - m.set_immutable() - return m + from sage.combinat.root_system.coxeter_matrix import CoxeterMatrix + return CoxeterMatrix(self) + + def coxeter_type(self): + """ + Return the Coxeter type for ``self``. + + EXAMPLES:: + + sage: CartanType(['A', 4]).coxeter_type() + Coxeter type of ['A', 4] + """ + from sage.combinat.root_system.coxeter_type import CoxeterType + return CoxeterType(self) def dual(self): """ @@ -1305,20 +1321,9 @@ def is_crystallographic(self): [['E', 6], True], [['E', 7], True], [['E', 8], True], [['F', 4], True], [['G', 2], True], [['I', 5], False], [['H', 3], False], [['H', 4], False]] - - TESTS:: - - sage: all(t.is_crystallographic() for t in CartanType.samples(affine=True)) - True - sage: t = CartanType(['A',3]); t.is_crystalographic() - doctest:...: DeprecationWarning: is_crystalographic is deprecated. Please use is_crystallographic instead. - See http://trac.sagemath.org/14673 for details. - True """ return False - is_crystalographic = deprecated_function_alias(14673, is_crystallographic) - def is_simply_laced(self): """ Return whether this Cartan type is simply laced. @@ -1597,18 +1602,9 @@ def is_crystallographic(self): sage: CartanType(['A', 3, 1]).is_crystallographic() True - - TESTS:: - - sage: t = CartanType(['A',3]); t.is_crystalographic() - doctest:...: DeprecationWarning: is_crystalographic is deprecated. Please use is_crystallographic instead. - See http://trac.sagemath.org/14673 for details. - True """ return True - is_crystalographic = deprecated_function_alias(14673, is_crystallographic) - @cached_method def symmetrizer(self): """ diff --git a/src/sage/combinat/root_system/coxeter_group.py b/src/sage/combinat/root_system/coxeter_group.py index 015f2c1abcb..b1d4aa5b2a6 100644 --- a/src/sage/combinat/root_system/coxeter_group.py +++ b/src/sage/combinat/root_system/coxeter_group.py @@ -16,7 +16,6 @@ from sage.groups.perm_gps.permgroup_element import PermutationGroupElement from sage.combinat.root_system.weyl_group import WeylGroup from sage.structure.unique_representation import UniqueRepresentation -from sage.structure.parent import Parent from sage.combinat.root_system.cartan_type import CartanType from sage.groups.perm_gps.permgroup import PermutationGroup_generic diff --git a/src/sage/combinat/root_system/coxeter_matrix.py b/src/sage/combinat/root_system/coxeter_matrix.py index 499f861f5a8..7344c72e159 100644 --- a/src/sage/combinat/root_system/coxeter_matrix.py +++ b/src/sage/combinat/root_system/coxeter_matrix.py @@ -1,8 +1,10 @@ """ -Coxeter matrices +Coxeter Matrices """ #***************************************************************************** # Copyright (C) 2007 Mike Hansen , +# 2015 Travis Scrimshaw +# 2015 Jean-Philippe Labbe # # Distributed under the terms of the GNU General Public License (GPL) # @@ -15,64 +17,63 @@ # # http://www.gnu.org/licenses/ #***************************************************************************** -from cartan_type import CartanType -from sage.matrix.all import MatrixSpace -from sage.rings.all import ZZ -def coxeter_matrix_as_function(t): - """ - Returns the Coxeter matrix, as a function +from sage.misc.cachefunc import cached_method +from sage.matrix.constructor import matrix +from sage.matrix.matrix_space import MatrixSpace +from sage.misc.classcall_metaclass import ClasscallMetaclass, typecall +from sage.matrix.matrix_generic_dense import Matrix_generic_dense +from sage.graphs.graph import Graph +from sage.rings.all import ZZ, QQ, RR +from sage.rings.infinity import infinity +from sage.combinat.root_system.cartan_type import CartanType +from sage.combinat.root_system.coxeter_type import CoxeterType - INPUT: +class CoxeterMatrix(CoxeterType): + r""" + A Coxeter matrix. - - ``t`` -- a Cartan type + A Coxeter matrix `M = (m_{ij})_{i,j \in I}` is a matrix encoding + a Coxeter system `(W, S)`, where the relations are given by + `(s_i s_j)^{m_{ij}}`. Thus `M` is symmetric and has entries + in `\{1, 2, 3, \ldots, \infty\}` with `m_{ij} = 1` if and only + if `i = j`. - EXAMPLES:: + We represent `m_{ij} = \infty` by any number `m_{ij} \leq -1`. In + particular, we can construct a bilinear form `B = (b_{ij})_{i,j \in I}` + from `M` by - sage: from sage.combinat.root_system.coxeter_matrix import coxeter_matrix_as_function - sage: f = coxeter_matrix_as_function(['A',4]) - sage: matrix([[f(i,j) for j in range(1,5)] for i in range(1,5)]) - [1 3 2 2] - [3 1 3 2] - [2 3 1 3] - [2 2 3 1] - """ - t = CartanType(t) - m = t.coxeter_matrix() - index_set = t.index_set() - reverse = dict((index_set[i], i) for i in range(len(index_set))) - return lambda i,j: m[reverse[i], reverse[j]] + .. MATH:: -def coxeter_matrix(t): - """ - Returns the Coxeter matrix of type t. + b_{ij} = \begin{cases} + m_{ij} & m_{ij} < 0\ (\text{i.e., } m_{ij} = \infty), \\ + -\cos\left( \frac{\pi}{m_{ij}} \right) & \text{otherwise}. + \end{cases} EXAMPLES:: - sage: coxeter_matrix(['A', 4]) + sage: CoxeterMatrix(['A', 4]) [1 3 2 2] [3 1 3 2] [2 3 1 3] [2 2 3 1] - sage: coxeter_matrix(['B', 4]) + sage: CoxeterMatrix(['B', 4]) [1 3 2 2] [3 1 3 2] [2 3 1 4] [2 2 4 1] - sage: coxeter_matrix(['C', 4]) + sage: CoxeterMatrix(['C', 4]) [1 3 2 2] [3 1 3 2] [2 3 1 4] [2 2 4 1] - sage: coxeter_matrix(['D', 4]) + sage: CoxeterMatrix(['D', 4]) [1 3 2 2] [3 1 3 3] [2 3 1 2] [2 3 2 1] - :: - - sage: coxeter_matrix(['E', 6]) + sage: CoxeterMatrix(['E', 6]) [1 2 3 2 2 2] [2 1 2 3 2 2] [3 2 1 3 2 2] @@ -80,18 +81,1120 @@ def coxeter_matrix(t): [2 2 2 3 1 3] [2 2 2 2 3 1] - :: - - sage: coxeter_matrix(['F', 4]) + sage: CoxeterMatrix(['F', 4]) [1 3 2 2] [3 1 4 2] [2 4 1 3] [2 2 3 1] - :: - - sage: coxeter_matrix(['G', 2]) + sage: CoxeterMatrix(['G', 2]) [1 6] [6 1] + + By default, entries representing `\infty` are given by `-1` + in the Coxeter matrix:: + + sage: G = Graph([(0,1,None), (1,2,4), (0,2,oo)]) + sage: CoxeterMatrix(G) + [ 1 3 -1] + [ 3 1 4] + [-1 4 1] + + It is possible to give a number `\leq -1` to represent an infinite label:: + + sage: CoxeterMatrix([[1,-1],[-1,1]]) + [ 1 -1] + [-1 1] + sage: CoxeterMatrix([[1,-3/2],[-3/2,1]]) + [ 1 -3/2] + [-3/2 1] + """ + __metaclass__ = ClasscallMetaclass + + @staticmethod + def __classcall_private__(cls, data=None, index_set=None, coxeter_type=None, + cartan_type=None, coxeter_type_check=True): + r""" + A Coxeter matrix can we created via a graph, a Coxeter type, or + a matrix. + + .. NOTE:: + + To disable the Coxeter type check, use the optional argument + ``coxeter_type_check = False``. + + EXAMPLES:: + + sage: C = CoxeterMatrix(['A',1,1],['a','b']) + sage: C2 = CoxeterMatrix([[1, -1], [-1, 1]]) + sage: C3 = CoxeterMatrix(matrix([[1, -1], [-1, 1]]), [0, 1]) + sage: C == C2 and C == C3 + True + + Check with `\infty` because of the hack of using `-1` to represent + `\infty` in the Coxeter matrix:: + + sage: G = Graph([(0, 1, 3), (1, 2, oo)]) + sage: W1 = CoxeterMatrix([[1, 3, 2], [3, 1, -1], [2, -1, 1]]) + sage: W2 = CoxeterMatrix(G) + sage: W1 == W2 + True + sage: CoxeterMatrix(W1.coxeter_graph()) == W1 + True + + The base ring of the matrix depends on the entries given:: + + sage: CoxeterMatrix([[1,-1],[-1,1]])._matrix.base_ring() + Integer Ring + sage: CoxeterMatrix([[1,-3/2],[-3/2,1]])._matrix.base_ring() + Rational Field + sage: CoxeterMatrix([[1,-1.5],[-1.5,1]])._matrix.base_ring() + Real Field with 53 bits of precision + """ + if not data: + if coxeter_type: + data = CoxeterType(coxeter_type) + elif cartan_type: + data = CoxeterType(CartanType(cartan_type)) + + # Special cases with no arguments passed + if not data: + data = [] + n = 0 + index_set = tuple() + coxeter_type = None + base_ring = ZZ + mat = typecall(cls, MatrixSpace(base_ring, n, sparse=False), data, coxeter_type, index_set) + mat._subdivisions = None + + return mat + + if isinstance(data, CoxeterMatrix): # Initiate from itself + return data + + # Initiate from a graph: + # TODO: Check if a CoxeterDiagram once implemented + if isinstance(data, Graph): + return cls._from_graph(data, coxeter_type_check) + + # Get the Coxeter type + coxeter_type = None + from sage.combinat.root_system.cartan_type import CartanType_abstract + if isinstance(data, CartanType_abstract): + coxeter_type = data.coxeter_type() + else: + try: + coxeter_type = CoxeterType(data) + except (TypeError, ValueError, NotImplementedError): + pass + + # Initiate from a Coxeter type + if coxeter_type: + return cls._from_coxetertype(coxeter_type) + + # TODO:: remove when oo is possible in matrices. + n = len(data[0]) + data = [x if x != infinity else -1 for r in data for x in r] + data = matrix(n, n, data) + # until here + + # Get the index set + if index_set: + index_set = tuple(index_set) + else: + index_set = tuple(range(1,n+1)) + if len(set(index_set)) != n: + raise ValueError("the given index set is not valid") + + return cls._from_matrix(data, coxeter_type, index_set, coxeter_type_check) + + def __init__(self, parent, data, coxeter_type, index_set): + """ + Initialize ``self``. + + TESTS:: + + sage: C = CoxeterMatrix(['A', 2, 1]) + sage: TestSuite(C).run(skip=["_test_category", "_test_change_ring"]) + """ + self._matrix = Matrix_generic_dense(parent, data, False, True) + self._matrix.set_immutable() + + if self._matrix.base_ring() not in [ZZ, QQ]: + self._is_cyclotomic = False + else: + self._is_cyclotomic = True + self._coxeter_type = coxeter_type + + if self._coxeter_type is not None: + if self._coxeter_type.is_finite(): + self._is_finite = True + self._is_affine = False + elif self._coxeter_type.is_affine(): + self._is_finite = False + self._is_affine = True + else: + self._is_finite = False + self._is_affine = False + else: + self._is_finite = False + self._is_affine = False + + self._index_set = index_set + self._rank = self._matrix.nrows() + + self._dict = {(self._index_set[i], self._index_set[j]): self._matrix[i, j] + for i in range(self._rank) for j in range(self._rank)} + + for i,key in enumerate(self._index_set): + self._dict[key] = {key2: self._matrix[i,j] + for j,key2 in enumerate(self._index_set)} + + @classmethod + def _from_matrix(cls, data, coxeter_type, index_set, coxeter_type_check): + """ + Initiate the Coxeter matrix from a matrix. + + TESTS:: + + sage: CM = CoxeterMatrix([[1,2],[2,1]]); CM + [1 2] + [2 1] + sage: CM = CoxeterMatrix([[1,-1],[-1,1]]); CM + [ 1 -1] + [-1 1] + sage: CM = CoxeterMatrix([[1,-1.5],[-1.5,1]]); CM + [ 1.00000000000000 -1.50000000000000] + [-1.50000000000000 1.00000000000000] + sage: CM = CoxeterMatrix([[1,-3/2],[-3/2,1]]); CM + [ 1 -3/2] + [-3/2 1] + sage: CM = CoxeterMatrix([[1,-3/2,5],[-3/2,1,-1],[5,-1,1]]); CM + [ 1 -3/2 5] + [-3/2 1 -1] + [ 5 -1 1] + sage: CM = CoxeterMatrix([[1,-3/2,5],[-3/2,1,oo],[5,oo,1]]); CM + [ 1 -3/2 5] + [-3/2 1 -1] + [ 5 -1 1] + """ + # Check that the data is valid + check_coxeter_matrix(data) + + M = matrix(data) + n = M.ncols() + + base_ring = M.base_ring() + + if not coxeter_type: + if n == 1: + coxeter_type = CoxeterType(['A', 1]) + elif coxeter_type_check: + coxeter_type = recognize_coxeter_type_from_matrix(M, index_set) + else: + coxeter_type = None + + raw_data = M.list() + + mat = typecall(cls, MatrixSpace(base_ring, n, sparse=False), raw_data, + coxeter_type, index_set) + mat._subdivisions = M._subdivisions + + return mat + + @classmethod + def _from_graph(cls, graph, coxeter_type_check): + """ + Initiate the Coxeter matrix from a graph. + + TESTS:: + + sage: CoxeterMatrix(CoxeterMatrix(['A',4,1]).coxeter_graph()) + [1 3 2 2 3] + [3 1 3 2 2] + [2 3 1 3 2] + [2 2 3 1 3] + [3 2 2 3 1] + sage: CoxeterMatrix(CoxeterMatrix(['B',4,1]).coxeter_graph()) + [1 2 3 2 2] + [2 1 3 2 2] + [3 3 1 3 2] + [2 2 3 1 4] + [2 2 2 4 1] + sage: CoxeterMatrix(CoxeterMatrix(['F',4]).coxeter_graph()) + [1 3 2 2] + [3 1 4 2] + [2 4 1 3] + [2 2 3 1] + + sage: G=Graph() + sage: G.add_edge([0,1,oo]) + sage: CoxeterMatrix(G) + [ 1 -1] + [-1 1] + sage: H = Graph() + sage: H.add_edge([0,1,-1.5]) + sage: CoxeterMatrix(H) + [ 1.00000000000000 -1.50000000000000] + [-1.50000000000000 1.00000000000000] + """ + verts = sorted(graph.vertices()) + index_set = tuple(verts) + n = len(index_set) + + # Setup the basis matrix as all 2 except 1 on the diagonal + data = [] + for i in range(n): + data += [[]] + for j in range(n): + if i == j: + data[-1] += [ZZ.one()] + else: + data[-1] += [2] + + for e in graph.edges(): + label = e[2] + if label is None: + label = 3 + elif label == infinity: + label = -1 + elif label not in ZZ and label > -1: + raise ValueError("invalid Coxeter graph label") + elif label == 0 or label == 1: + raise ValueError("invalid Coxeter graph label") + i = verts.index(e[0]) + j = verts.index(e[1]) + data[j][i] = data[i][j] = label + + return cls._from_matrix(data, None, index_set, coxeter_type_check) + + @classmethod + def _from_coxetertype(cls, coxeter_type): + """ + Initiate the Coxeter matrix from a Coxeter type. + + TESTS:: + + sage: CoxeterMatrix(['A',4]).coxeter_type() + Coxeter type of ['A', 4] + sage: CoxeterMatrix(['A',4,1]).coxeter_type() + Coxeter type of ['A', 4, 1] + sage: CoxeterMatrix(['D',4,1]).coxeter_type() + Coxeter type of ['D', 4, 1] + """ + index_set = coxeter_type.index_set() + n = len(index_set) + reverse = {index_set[i]: i for i in range(n)} + data = [[1 if i == j else 2 for j in range(n)] for i in range(n)] + for (i, j, l) in coxeter_type.coxeter_graph().edge_iterator(): + if l == infinity: + l = -1 + data[reverse[i]][reverse[j]] = l + data[reverse[j]][reverse[i]] = l + + return cls._from_matrix(data, coxeter_type, index_set, False) + + @classmethod + def samples(self, finite=None, affine=None, crystallographic=None, higher_rank=None): + """ + Return a sample of the available Coxeter types. + + INPUT: + + - ``finite`` -- (default: ``None``) a boolean or ``None`` + + - ``affine`` -- (default: ``None``) a boolean or ``None`` + + - ``crystallographic`` -- (default: ``None``) a boolean or ``None`` + + - ``higher_rank`` -- (default: ``None``) a boolean or ``None`` + + The sample contains all the exceptional finite and affine + Coxeter types, as well as typical representatives of the + infinite families. + + Here the ``higher_rank`` term denotes non-finite, non-affine, + Coxeter groups (including hyperbolic types). + + .. TODO:: Implement the hyperbolic and compact hyperbolic in the samples. + + EXAMPLES:: + + sage: [CM.coxeter_type() for CM in CoxeterMatrix.samples()] + [ + Coxeter type of ['A', 1], Coxeter type of ['A', 5], + + Coxeter type of ['B', 5], Coxeter type of ['D', 4], + + Coxeter type of ['D', 5], Coxeter type of ['E', 6], + + Coxeter type of ['E', 7], Coxeter type of ['E', 8], + + Coxeter type of ['F', 4], Coxeter type of ['H', 3], + + Coxeter type of ['H', 4], Coxeter type of ['I', 10], + + Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + + [ 1 -1 -1] + [-1 1 -1] + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1], [-1 -1 1], + + [ 1 -2 3 2] + [1 2 3] [-2 1 2 3] + [2 1 7] [ 3 2 1 -8] + [3 7 1], [ 2 3 -8 1] + ] + + The finite, affine and crystallographic options allow + respectively for restricting to (non) finite, (non) affine, + and (non) crystallographic Cartan types:: + + sage: [CM.coxeter_type() for CM in CoxeterMatrix.samples(finite=True)] + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 5], Coxeter type of ['D', 4], + Coxeter type of ['D', 5], Coxeter type of ['E', 6], + Coxeter type of ['E', 7], Coxeter type of ['E', 8], + Coxeter type of ['F', 4], Coxeter type of ['H', 3], + Coxeter type of ['H', 4], Coxeter type of ['I', 10]] + + sage: [CM.coxeter_type() for CM in CoxeterMatrix.samples(affine=True)] + [Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1]] + + sage: [CM.coxeter_type() for CM in CoxeterMatrix.samples(crystallographic=True)] + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 5], Coxeter type of ['D', 4], + Coxeter type of ['D', 5], Coxeter type of ['E', 6], + Coxeter type of ['E', 7], Coxeter type of ['E', 8], + Coxeter type of ['F', 4], Coxeter type of ['A', 2, 1], + Coxeter type of ['B', 5, 1], Coxeter type of ['C', 5, 1], + Coxeter type of ['D', 5, 1], Coxeter type of ['E', 6, 1], + Coxeter type of ['E', 7, 1], Coxeter type of ['E', 8, 1], + Coxeter type of ['F', 4, 1], Coxeter type of ['G', 2, 1]] + + sage: CoxeterMatrix.samples(crystallographic=False) + [ + [1 3 2 2] + [1 3 2] [3 1 3 2] [ 1 -1 -1] [1 2 3] + [3 1 5] [2 3 1 5] [ 1 10] [ 1 -1] [-1 1 -1] [2 1 7] + [2 5 1], [2 2 5 1], [10 1], [-1 1], [-1 -1 1], [3 7 1], + + [ 1 -2 3 2] + [-2 1 2 3] + [ 3 2 1 -8] + [ 2 3 -8 1] + ] + + .. TODO:: add some reducible Coxeter types (suggestions?) + + TESTS:: + + sage: for ct in CoxeterMatrix.samples(): TestSuite(ct).run() + """ + result = self._samples() + if crystallographic is not None: + result = [t for t in result if t.is_crystallographic() == crystallographic] + if finite is not None: + result = [t for t in result if t.is_finite() == finite] + if affine is not None: + result = [t for t in result if t.is_affine() == affine] + if higher_rank is not None: + result = [t for t in result if not t.is_affine() and not t.is_finite()] + return result + + @cached_method + def _samples(self): + """ + Return a sample of all implemented Coxeter types. + + .. NOTE:: This is intended to be used through :meth:`samples`. + + EXAMPLES:: + + sage: [CM.coxeter_type() for CM in CoxeterMatrix._samples()] + [ + Coxeter type of ['A', 1], Coxeter type of ['A', 5], + + Coxeter type of ['B', 5], Coxeter type of ['D', 4], + + Coxeter type of ['D', 5], Coxeter type of ['E', 6], + + Coxeter type of ['E', 7], Coxeter type of ['E', 8], + + Coxeter type of ['F', 4], Coxeter type of ['H', 3], + + Coxeter type of ['H', 4], Coxeter type of ['I', 10], + + Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + + [ 1 -1 -1] + [-1 1 -1] + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1], [-1 -1 1], + + [ 1 -2 3 2] + [1 2 3] [-2 1 2 3] + [2 1 7] [ 3 2 1 -8] + [3 7 1], [ 2 3 -8 1] + ] + """ + finite = [CoxeterMatrix(t) for t in [['A', 1], ['A', 5], ['B', 5], + ['D', 4], ['D', 5], ['E', 6], ['E', 7], + ['E', 8], ['F', 4], ['H', 3], ['H', 4], + ['I', 10]]] + + affine = [CoxeterMatrix(t) for t in [['A', 2, 1], ['B', 5, 1], + ['C', 5, 1], ['D', 5, 1], ['E', 6, 1], + ['E', 7, 1], ['E', 8, 1], ['F', 4, 1], + ['G', 2, 1], ['A', 1, 1]]] + + higher_matrices = [[[1, -1, -1], [-1, 1, -1], [-1, -1, 1]], + [[1, 2, 3], [2, 1, 7], [3, 7, 1]], + [[1, -2, 3, 2], [-2, 1, 2, 3], [3, 2, 1, -8], [2, 3, -8, 1]]] + + higher = [CoxeterMatrix(m) for m in higher_matrices] + + return finite + affine + higher + + def relabel(self, relabelling): + """ + Return a relabelled copy of this Coxeter matrix. + + INPUT: + + - ``relabelling`` -- a function (or dictionary) + + OUTPUT: + + an isomorphic Coxeter type obtained by relabelling the nodes of + the Coxeter graph. Namely, the node with label ``i`` is + relabelled ``f(i)`` (or, by ``f[i]`` if ``f`` is a dictionary). + + EXAMPLES:: + + sage: CoxeterMatrix(['F',4]).relabel({ 1:2, 2:3, 3:4, 4:1}) + [1 4 2 3] + [4 1 3 2] + [2 3 1 2] + [3 2 2 1] + sage: CoxeterMatrix(['F',4]).relabel(lambda x: x+1 if x<4 else 1) + [1 4 2 3] + [4 1 3 2] + [2 3 1 2] + [3 2 2 1] + """ + if isinstance(relabelling, dict): + data = [[self[relabelling[i]][relabelling[j]] + for j in self.index_set()] for i in self.index_set()] + else: + data = [[self[relabelling(i)][relabelling(j)] + for j in self.index_set()] for i in self.index_set()] + + return CoxeterMatrix(data) + + def __reduce__(self): + """ + Used for pickling. + + TESTS:: + + sage: C = CoxeterMatrix(['A',4]) + sage: M = loads(dumps(C)) + sage: M._index_set + (1, 2, 3, 4) + """ + if self._coxeter_type: + return (CoxeterMatrix, (self._coxeter_type,)) + return (CoxeterMatrix, (self._matrix, self._index_set)) + + def _repr_(self): + """ + String representation of the Coxeter matrix. + + EXAMPLES:: + + sage: CM = CoxeterMatrix(['A',3]); CM + [1 3 2] + [3 1 3] + [2 3 1] + sage: CM = CoxeterMatrix([[1,-3/2],[-3/2,1]]); CM + [ 1 -3/2] + [-3/2 1] + """ + return self._matrix.__repr__() + + def _repr_option(self, key): + """ + Metadata about the :meth:`_repr_` output. + + See :meth:`sage.structure.parent._repr_option` for details. + + EXAMPLES:: + + sage: CM = CoxeterMatrix(['A',3]) + sage: CM._repr_option('ascii_art') + True + """ + if key == 'ascii_art' or key == 'element_ascii_art': + return self._matrix.nrows() > 1 + return super(CoxeterMatrix, self)._repr_option(key) + + def _latex_(self): + r""" + Latex representation of the Coxeter matrix. + + EXAMPLES:: + + sage: CM = CoxeterMatrix(['A',3]) + sage: latex(CM) + \left(\begin{array}{rrr} + 1 & 3 & 2 \\ + 3 & 1 & 3 \\ + 2 & 3 & 1 + \end{array}\right) + """ + return self._matrix._latex_() + + + def __iter__(self): + """ + Return an iterator for the rows of the Coxeter matrix. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,8],[8,1]]) + sage: CM.__iter__().next() + (1, 8) + """ + return self._matrix.__iter__() + + def __getitem__(self, key): + """ + Return a dictionary of labels adjacent to a node or + the label of an edge in the Coxeter graph. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,-2],[-2,1]]) + sage: CM = CoxeterMatrix([[1,-2],[-2,1]], ['a','b']) + sage: CM['a'] + {'a': 1, 'b': -2} + sage: CM['b'] + {'a': -2, 'b': 1} + sage: CM['a','b'] + -2 + sage: CM['a','a'] + 1 + """ + return self._dict[key] + + def __hash__(self): + r""" + Return hash of the Coxeter matrix. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,-2],[-2,1]],['a','b']) + sage: CM.__hash__() + 1 + sage: CM = CoxeterMatrix([[1,-3],[-3,1]],['1','2']) + sage: CM.__hash__() + 4 + """ + return self._matrix.__hash__() + + def __eq__(self, other): + r""" + Return if ``self`` and ``other`` are equal, ``False`` otherwise. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,-2],[-2,1]],['a','b']) + sage: CM.__hash__() + 1 + sage: CM = CoxeterMatrix([[1,-3],[-3,1]],['1','2']) + sage: CM.__hash__() + 4 + """ + return self._matrix.__eq__(other._matrix) + + def _matrix_(self, R=None): + """ + Return ``self`` as a matrix over the ring ``R``. + + EXAMPLES:: + + sage: CM = CoxeterMatrix([[1,-3],[-3,1]]) + sage: matrix(CM) + [ 1 -3] + [-3 1] + sage: matrix(CM,RR) + [ 1.00000000000000 -3.00000000000000] + [-3.00000000000000 1.00000000000000] + """ + if R is not None: + return self._matrix.change_ring(R) + else: + return self._matrix + + ########################################################################## + # Coxeter type methods + + def index_set(self): + """ + Return the index set of ``self``. + + EXAMPLES:: + + sage: C = CoxeterMatrix(['A',1,1]) + sage: C.index_set() + (0, 1) + sage: C = CoxeterMatrix(['E',6]) + sage: C.index_set() + (1, 2, 3, 4, 5, 6) + """ + return self._index_set + + def coxeter_type(self): + """ + Return the Coxeter type of ``self`` or ``self`` if unknown. + + EXAMPLES:: + + sage: C = CoxeterMatrix(['A',4,1]) + sage: C.coxeter_type() + Coxeter type of ['A', 4, 1] + + If the Coxeter type is unknown:: + + sage: C = CoxeterMatrix([[1,3,4], [3,1,-1], [4,-1,1]]) + sage: C.coxeter_type() + [ 1 3 4] + [ 3 1 -1] + [ 4 -1 1] + """ + if self._coxeter_type is None: + return self + return self._coxeter_type + + def rank(self): + r""" + Return the rank of ``self``. + + EXAMPLES:: + + sage: CoxeterMatrix(['C',3]).rank() + 3 + sage: CoxeterMatrix(["A2","B2","F4"]).rank() + 8 + """ + return self._rank + + def coxeter_matrix(self): + r""" + Return the Coxeter matrix of ``self``. + + EXAMPLES:: + + sage: CoxeterMatrix(['C',3]).coxeter_matrix() + [1 3 2] + [3 1 4] + [2 4 1] + """ + return self + + def bilinear_form(self): + r""" + Return the bilinear form of ``self``. + + EXAMPLES:: + + sage: CoxeterType(['A', 2, 1]).bilinear_form() + [ 1 -1/2 -1/2] + [-1/2 1 -1/2] + [-1/2 -1/2 1] + sage: CoxeterType(['H', 3]).bilinear_form() + [ 1 -1/2 0] + [ -1/2 1 1/2*E(5)^2 + 1/2*E(5)^3] + [ 0 1/2*E(5)^2 + 1/2*E(5)^3 1] + sage: C = CoxeterMatrix([[1,-1,-1],[-1,1,-1],[-1,-1,1]]) + sage: C.bilinear_form() + [ 1 -1 -1] + [-1 1 -1] + [-1 -1 1] + """ + return CoxeterType.bilinear_form(self) + + @cached_method + def coxeter_graph(self): + """ + Return the Coxeter graph of ``self``. + + EXAMPLES:: + + sage: C = CoxeterMatrix(['A',3]) + sage: C.coxeter_graph() + Graph on 3 vertices + + sage: C = CoxeterMatrix([['A',3],['A',1]]) + sage: C.coxeter_graph() + Graph on 4 vertices + """ + n = self.rank() + I = self.index_set() + val = lambda x: infinity if x == -1 else x + G = Graph([(I[i], I[j], val((self._matrix)[i, j])) + for i in range(n) for j in range(i) + if self._matrix[i, j] not in [1, 2]]) + G.add_vertices(I) + return G.copy(immutable = True) + + def is_simply_laced(self): + """ + Return if ``self`` is simply-laced. + + A Coxeter matrix is simply-laced if all non-diagonal entries are + either 2 or 3. + + EXAMPLES:: + + sage: cm = CoxeterMatrix([[1,3,3,3], [3,1,3,3], [3,3,1,3], [3,3,3,1]]) + sage: cm.is_simply_laced() + True + """ + # We include 1 in this list to account for the diagonal + L = [1, 2, 3] + return all(x in L for row in self for x in row) + + def is_crystallographic(self): + """ + Return if ``self`` is crystallographic. + + A Coxeter matrix is crystallographic if all non-diagonal entries + are either 2, 4, or 6. + + EXAMPLES:: + + sage: CoxeterMatrix(['F',4]).is_crystallographic() + True + sage: CoxeterMatrix(['H',3]).is_crystallographic() + False + """ + # We include 1 in this list to account for the diagonal + L = [1, 2, 3, 4, 6] + return all(x in L for row in self for x in row) + + def is_finite(self): + """ + Return if ``self`` is a finite type or ``False`` if unknown. + + EXAMPLES:: + + sage: M = CoxeterMatrix(['C',4]) + sage: M.is_finite() + True + sage: M = CoxeterMatrix(['D',4,1]) + sage: M.is_finite() + False + sage: M = CoxeterMatrix([[1, -1], [-1, 1]]) + sage: M.is_finite() + False + """ + return self._is_finite + + def is_affine(self): + """ + Return if ``self`` is an affine type or ``False`` if unknown. + + EXAMPLES:: + + sage: M = CoxeterMatrix(['C',4]) + sage: M.is_affine() + False + sage: M = CoxeterMatrix(['D',4,1]) + sage: M.is_affine() + True + sage: M = CoxeterMatrix([[1, 3],[3,1]]) + sage: M.is_affine() + False + sage: M = CoxeterMatrix([[1, -1, 7], [-1, 1, 3], [7, 3, 1]]) + sage: M.is_affine() + False + """ + return self._is_affine + + +##################################################################### +## Type check functions + +def recognize_coxeter_type_from_matrix(coxeter_matrix, index_set): """ - return CartanType(t).coxeter_matrix() + Return the Coxeter type of ``coxeter_matrix`` if known, + otherwise return ``None``. + + EXAMPLES: + + Some infinite ones:: + + sage: C = CoxeterMatrix([[1,3,2],[3,1,-1],[2,-1,1]]) + sage: C.is_finite() # indirect doctest + False + sage: C = CoxeterMatrix([[1,-1,-1],[-1,1,-1],[-1,-1,1]]) + sage: C.is_finite() # indirect doctest + False + + Some finite ones:: + + sage: m = matrix(CoxeterMatrix(['D', 4])) + sage: CoxeterMatrix(m).is_finite() # indirect doctest + True + sage: m = matrix(CoxeterMatrix(['H', 4])) + sage: CoxeterMatrix(m).is_finite() # indirect doctest + True + + sage: CoxeterMatrix(CoxeterType(['A',10]).coxeter_graph()).coxeter_type() + Coxeter type of ['A', 10] + sage: CoxeterMatrix(CoxeterType(['B',10]).coxeter_graph()).coxeter_type() + Coxeter type of ['B', 10] + sage: CoxeterMatrix(CoxeterType(['C',10]).coxeter_graph()).coxeter_type() + Coxeter type of ['B', 10] + sage: CoxeterMatrix(CoxeterType(['D',10]).coxeter_graph()).coxeter_type() + Coxeter type of ['D', 10] + sage: CoxeterMatrix(CoxeterType(['E',6]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 6] + sage: CoxeterMatrix(CoxeterType(['E',7]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 7] + sage: CoxeterMatrix(CoxeterType(['E',8]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 8] + sage: CoxeterMatrix(CoxeterType(['F',4]).coxeter_graph()).coxeter_type() + Coxeter type of ['F', 4] + sage: CoxeterMatrix(CoxeterType(['G',2]).coxeter_graph()).coxeter_type() + Coxeter type of ['G', 2] + sage: CoxeterMatrix(CoxeterType(['H',3]).coxeter_graph()).coxeter_type() + Coxeter type of ['H', 3] + sage: CoxeterMatrix(CoxeterType(['H',4]).coxeter_graph()).coxeter_type() + Coxeter type of ['H', 4] + sage: CoxeterMatrix(CoxeterType(['I',100]).coxeter_graph()).coxeter_type() + Coxeter type of ['I', 100] + + Some affine graphs:: + + sage: CoxeterMatrix(CoxeterType(['A',1,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['A', 1, 1] + sage: CoxeterMatrix(CoxeterType(['A',10,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['A', 10, 1] + sage: CoxeterMatrix(CoxeterType(['B',10,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['B', 10, 1] + sage: CoxeterMatrix(CoxeterType(['C',10,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['C', 10, 1] + sage: CoxeterMatrix(CoxeterType(['D',10,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['D', 10, 1] + sage: CoxeterMatrix(CoxeterType(['E',6,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 6, 1] + sage: CoxeterMatrix(CoxeterType(['E',7,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 7, 1] + sage: CoxeterMatrix(CoxeterType(['E',8,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['E', 8, 1] + sage: CoxeterMatrix(CoxeterType(['F',4,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['F', 4, 1] + sage: CoxeterMatrix(CoxeterType(['G',2,1]).coxeter_graph()).coxeter_type() + Coxeter type of ['G', 2, 1] + + TESTS: + + Check that we detect relabellings:: + + sage: M = CoxeterMatrix([[1,2,3],[2,1,6],[3,6,1]], index_set=['a', 'b', 'c']) + sage: M.coxeter_type() + Coxeter type of ['G', 2, 1] relabelled by {0: 'a', 1: 'b', 2: 'c'} + + sage: from sage.combinat.root_system.coxeter_matrix import recognize_coxeter_type_from_matrix + sage: for C in CoxeterMatrix.samples(): + ....: relabelling_perm = Permutations(C.index_set()).random_element() + ....: relabelling_dict = {C.index_set()[i]: relabelling_perm[i] for i in range(C.rank())} + ....: relabeled_matrix = C.relabel(relabelling_dict)._matrix + ....: recognized_type = recognize_coxeter_type_from_matrix(relabeled_matrix, relabelling_perm) + ....: if C.is_finite() or C.is_affine(): + ....: assert recognized_type == C.coxeter_type() + """ + # First, we build the Coxeter graph of the group without the edge labels + n = ZZ(coxeter_matrix.nrows()) + G = Graph([[index_set[i], index_set[j], coxeter_matrix[i, j]] + for i in range(n) for j in range(i,n) + if coxeter_matrix[i, j] not in [1, 2]]) + G.add_vertices(index_set) + + types = [] + for S in G.connected_components_subgraphs(): + r = S.num_verts() + # Handle the special cases first + if r == 1: + types.append(CoxeterType(['A',1]).relabel({1: S.vertices()[0]})) + continue + if r == 2: # Type B2, G2, or I_2(p) + e = S.edge_labels()[0] + if e == 3: # Can't be 2 because it is connected + ct = CoxeterType(['B',2]) + elif e == 4: + ct = CoxeterType(['G',2]) + elif e > 0 and e < float('inf'): # Remaining non-affine types + ct = CoxeterType(['I',e]) + else: # Otherwise it is infinite dihedral group Z_2 \ast Z_2 + ct = CoxeterType(['A',1,1]) + if not ct.is_affine(): + types.append(ct.relabel({1: S.vertices()[0], 2: S.vertices()[1]})) + else: + types.append(ct.relabel({0: S.vertices()[0], 1: S.vertices()[1]})) + continue + + test = [['A',r], ['B',r], ['A',r-1,1]] + if r >= 3: + if r == 3: + test += [['G',2,1], ['H',3]] + test.append(['C',r-1,1]) + if r >= 4: + if r == 4: + test += [['F',4], ['H',4]] + test += [['D',r], ['B',r-1,1]] + if r >= 5: + if r == 5: + test.append(['F',4,1]) + test.append(['D',r-1,1]) + if r == 6: + test.append(['E',6]) + elif r == 7: + test += [['E',7], ['E',6,1]] + elif r == 8: + test += [['E',8], ['E',7,1]] + elif r == 9: + test.append(['E',8,1]) + + found = False + for ct in test: + ct = CoxeterType(ct) + T = ct.coxeter_graph() + iso, match = T.is_isomorphic(S, certify=True, edge_labels=True) + if iso: + types.append(ct.relabel(match)) + found = True + break + if not found: + return None + + return CoxeterType(types) + +##################################################################### +## Other functions + +def check_coxeter_matrix(m): + """ + Check if ``m`` represents a generalized Coxeter matrix and raise + and error if not. + + EXAMPLES:: + + sage: from sage.combinat.root_system.coxeter_matrix import check_coxeter_matrix + sage: m = matrix([[1,3,2],[3,1,-1],[2,-1,1]]) + sage: check_coxeter_matrix(m) + + sage: m = matrix([[1,3],[3,1],[2,-1]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: not a square matrix + + sage: m = matrix([[1,3,2],[3,1,-1],[2,-1,2]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: the matrix diagonal is not all 1 + + sage: m = matrix([[1,3,3],[3,1,-1],[2,-1,1]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: the matrix is not symmetric + + sage: m = matrix([[1,3,1/2],[3,1,-1],[1/2,-1,1]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: invalid Coxeter label 1/2 + + sage: m = matrix([[1,3,1],[3,1,-1],[1,-1,1]]) + sage: check_coxeter_matrix(m) + Traceback (most recent call last): + ... + ValueError: invalid Coxeter label 1 + """ + mat = matrix(m) + if not mat.is_square(): + raise ValueError("not a square matrix") + for i, row in enumerate(m): + if mat[i, i] != 1: + raise ValueError("the matrix diagonal is not all 1") + for j, val in enumerate(row[i+1:]): + if val != m[j+i+1][i]: + raise ValueError("the matrix is not symmetric") + if val not in ZZ: + if val > -1 and val in RR and val != infinity: + raise ValueError("invalid Coxeter label {}".format(val)) + else: + if val == 1 or val == 0: + raise ValueError("invalid Coxeter label {}".format(val)) + +def coxeter_matrix_as_function(t): + """ + Return the Coxeter matrix, as a function. + + INPUT: + + - ``t`` -- a Cartan type + + EXAMPLES:: + + sage: from sage.combinat.root_system.coxeter_matrix import coxeter_matrix_as_function + sage: f = coxeter_matrix_as_function(['A',4]) + sage: matrix([[f(i,j) for j in range(1,5)] for i in range(1,5)]) + [1 3 2 2] + [3 1 3 2] + [2 3 1 3] + [2 2 3 1] + """ + t = CartanType(t) + m = t.coxeter_matrix() + return lambda i, j: m[i, j] + +def coxeter_matrix(t): + """ + This was deprecated in :trac:`17798` for :class:`CartanMatrix`. + + EXAMPLES:: + + sage: coxeter_matrix(['A', 4]) + doctest:...: DeprecationWarning: coxeter_matrix() is deprecated. Use CoxeterMatrix() instead + See http://trac.sagemath.org/17798 for details. + [1 3 2 2] + [3 1 3 2] + [2 3 1 3] + [2 2 3 1] + """ + from sage.misc.superseded import deprecation + deprecation(17798, 'coxeter_matrix() is deprecated. Use CoxeterMatrix() instead') + return CoxeterMatrix(t) + diff --git a/src/sage/combinat/root_system/coxeter_type.py b/src/sage/combinat/root_system/coxeter_type.py new file mode 100644 index 00000000000..ecbd5ab6a20 --- /dev/null +++ b/src/sage/combinat/root_system/coxeter_type.py @@ -0,0 +1,576 @@ +""" +Coxeter Types +""" +#***************************************************************************** +# Copyright (C) 2015 Travis Scrimshaw , +# 2015 Jean-Philippe Labbe , +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# The full text of the GPL is available at: +# +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.misc.classcall_metaclass import ClasscallMetaclass +from sage.combinat.root_system.cartan_type import CartanType +from sage.matrix.all import MatrixSpace +from sage.symbolic.ring import SR +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.sage_object import SageObject + + +class CoxeterType(SageObject): + """ + Abstract class for Coxeter types. + """ + __metaclass__ = ClasscallMetaclass + + @staticmethod + def __classcall_private__(cls, *x): + """ + Parse input ``x``. + + EXAMPLES:: + + sage: CoxeterType(['A',3]) + Coxeter type of ['A', 3] + """ + if len(x) == 1: + x = x[0] + + if isinstance(x, CoxeterType): + return x + + try: + return CoxeterTypeFromCartanType(CartanType(x)) + except (ValueError, TypeError): + pass + + if len(x) == 1: # In case the input is similar to CoxeterType([['A',2]]) + return CoxeterType(x[0]) + + raise NotImplementedError("Coxeter types not from Cartan types not yet implemented") + + @classmethod + def samples(self, finite=None, affine=None, crystallographic=None): + """ + Return a sample of the available Coxeter types. + + INPUT: + + - ``finite`` -- a boolean or ``None`` (default: ``None``) + + - ``affine`` -- a boolean or ``None`` (default: ``None``) + + - ``crystallographic`` -- a boolean or ``None`` (default: ``None``) + + The sample contains all the exceptional finite and affine + Coxeter types, as well as typical representatives of the + infinite families. + + EXAMPLES:: + + sage: CoxeterType.samples() + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 1], Coxeter type of ['B', 5], + Coxeter type of ['C', 1], Coxeter type of ['C', 5], + Coxeter type of ['D', 4], Coxeter type of ['D', 5], + Coxeter type of ['E', 6], Coxeter type of ['E', 7], + Coxeter type of ['E', 8], Coxeter type of ['F', 4], + Coxeter type of ['H', 3], Coxeter type of ['H', 4], + Coxeter type of ['I', 10], Coxeter type of ['A', 2, 1], + Coxeter type of ['B', 5, 1], Coxeter type of ['C', 5, 1], + Coxeter type of ['D', 5, 1], Coxeter type of ['E', 6, 1], + Coxeter type of ['E', 7, 1], Coxeter type of ['E', 8, 1], + Coxeter type of ['F', 4, 1], Coxeter type of ['G', 2, 1], + Coxeter type of ['A', 1, 1]] + + The finite, affine and crystallographic options allow + respectively for restricting to (non) finite, (non) affine, + and (non) crystallographic Cartan types:: + + sage: CoxeterType.samples(finite=True) + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 1], Coxeter type of ['B', 5], + Coxeter type of ['C', 1], Coxeter type of ['C', 5], + Coxeter type of ['D', 4], Coxeter type of ['D', 5], + Coxeter type of ['E', 6], Coxeter type of ['E', 7], + Coxeter type of ['E', 8], Coxeter type of ['F', 4], + Coxeter type of ['H', 3], Coxeter type of ['H', 4], + Coxeter type of ['I', 10]] + + sage: CoxeterType.samples(affine=True) + [Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1]] + + sage: CoxeterType.samples(crystallographic=True) + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 1], Coxeter type of ['B', 5], + Coxeter type of ['C', 1], Coxeter type of ['C', 5], + Coxeter type of ['D', 4], Coxeter type of ['D', 5], + Coxeter type of ['E', 6], Coxeter type of ['E', 7], + Coxeter type of ['E', 8], Coxeter type of ['F', 4], + Coxeter type of ['A', 2, 1], Coxeter type of ['B', 5, 1], + Coxeter type of ['C', 5, 1], Coxeter type of ['D', 5, 1], + Coxeter type of ['E', 6, 1], Coxeter type of ['E', 7, 1], + Coxeter type of ['E', 8, 1], Coxeter type of ['F', 4, 1], + Coxeter type of ['G', 2, 1], Coxeter type of ['A', 1, 1]] + + sage: CoxeterType.samples(crystallographic=False) + [Coxeter type of ['H', 3], + Coxeter type of ['H', 4], + Coxeter type of ['I', 10]] + + .. TODO:: add some reducible Coxeter types (suggestions?) + + TESTS:: + + sage: for ct in CoxeterType.samples(): TestSuite(ct).run() + """ + result = self._samples() + if crystallographic is not None: + result = [t for t in result if t.is_crystallographic() == crystallographic] + if finite is not None: + result = [t for t in result if t.is_finite() == finite] + if affine is not None: + result = [t for t in result if t.is_affine() == affine] + return result + + @cached_method + def _samples(self): + """ + Return a sample of all implemented Coxeter types. + + .. NOTE:: + + This is intended to be used through :meth:`samples`. + + EXAMPLES:: + + sage: CoxeterType._samples() + [Coxeter type of ['A', 1], Coxeter type of ['A', 5], + Coxeter type of ['B', 1], Coxeter type of ['B', 5], + Coxeter type of ['C', 1], Coxeter type of ['C', 5], + Coxeter type of ['D', 4], Coxeter type of ['D', 5], + Coxeter type of ['E', 6], Coxeter type of ['E', 7], + Coxeter type of ['E', 8], Coxeter type of ['F', 4], + Coxeter type of ['H', 3], Coxeter type of ['H', 4], + Coxeter type of ['I', 10], Coxeter type of ['A', 2, 1], + Coxeter type of ['B', 5, 1], Coxeter type of ['C', 5, 1], + Coxeter type of ['D', 5, 1], Coxeter type of ['E', 6, 1], + Coxeter type of ['E', 7, 1], Coxeter type of ['E', 8, 1], + Coxeter type of ['F', 4, 1], Coxeter type of ['G', 2, 1], + Coxeter type of ['A', 1, 1]] + """ + finite = [CoxeterType(t) for t in [['A', 1], ['A', 5], ['B', 1], ['B', 5], + ['C', 1], ['C', 5], ['D', 4], ['D', 5], + ['E', 6], ['E', 7], ['E', 8], ['F', 4], + ['H', 3], ['H', 4], ['I', 10]]] + + affine = [CoxeterType(t) for t in ['A', 2, 1], ['B', 5, 1], + ['C', 5, 1], ['D', 5, 1], ['E', 6, 1], + ['E', 7, 1], ['E', 8, 1], ['F', 4, 1], + ['G', 2, 1], ['A', 1, 1]] + + return finite + affine + + @abstract_method + def rank(self): + """ + Return the rank of ``self``. + + This is the number of nodes of the associated Coxeter graph. + + EXAMPLES:: + + sage: CoxeterType(['A', 4]).rank() + 4 + sage: CoxeterType(['A', 7, 2]).rank() + 5 + sage: CoxeterType(['I', 8]).rank() + 2 + """ + + @abstract_method + def index_set(self): + """ + Return the index set for ``self``. + + This is the list of the nodes of the associated Coxeter graph. + + EXAMPLES:: + + sage: CoxeterType(['A', 3, 1]).index_set() + (0, 1, 2, 3) + sage: CoxeterType(['D', 4]).index_set() + (1, 2, 3, 4) + sage: CoxeterType(['A', 7, 2]).index_set() + (0, 1, 2, 3, 4) + sage: CoxeterType(['A', 7, 2]).index_set() + (0, 1, 2, 3, 4) + sage: CoxeterType(['A', 6, 2]).index_set() + (0, 1, 2, 3) + sage: CoxeterType(['D', 6, 2]).index_set() + (0, 1, 2, 3, 4, 5) + sage: CoxeterType(['E', 6, 1]).index_set() + (0, 1, 2, 3, 4, 5, 6) + sage: CoxeterType(['E', 6, 2]).index_set() + (0, 1, 2, 3, 4) + sage: CoxeterType(['A', 2, 2]).index_set() + (0, 1) + sage: CoxeterType(['G', 2, 1]).index_set() + (0, 1, 2) + sage: CoxeterType(['F', 4, 1]).index_set() + (0, 1, 2, 3, 4) + """ + + @abstract_method + def coxeter_matrix(self): + """ + Return the Coxeter matrix associated to ``self``. + + EXAMPLES:: + + sage: CoxeterType(['A', 3]).coxeter_matrix() + [1 3 2] + [3 1 3] + [2 3 1] + sage: CoxeterType(['A', 3, 1]).coxeter_matrix() + [1 3 2 3] + [3 1 3 2] + [2 3 1 3] + [3 2 3 1] + """ + + @abstract_method + def coxeter_graph(self): + """ + Return the Coxeter graph associated to ``self``. + + EXAMPLES:: + + sage: CoxeterType(['A', 3]).coxeter_graph() + Graph on 3 vertices + sage: CoxeterType(['A', 3, 1]).coxeter_graph() + Graph on 4 vertices + """ + + @abstract_method + def is_finite(self): + """ + Return whether ``self`` is finite. + + EXAMPLES:: + + sage: CoxeterType(['A',4]).is_finite() + True + sage: CoxeterType(['A',4, 1]).is_finite() + False + """ + + @abstract_method + def is_affine(self): + """ + Return whether ``self`` is affine. + + EXAMPLES:: + + sage: CoxeterType(['A', 3]).is_affine() + False + sage: CoxeterType(['A', 3, 1]).is_affine() + True + """ + + def is_crystallographic(self): + """ + Return whether ``self`` is crystallographic. + + This returns ``False`` by default. Derived class should override this + appropriately. + + EXAMPLES:: + + sage: [ [t, t.is_crystallographic() ] for t in CartanType.samples(finite=True) ] + [[['A', 1], True], [['A', 5], True], + [['B', 1], True], [['B', 5], True], + [['C', 1], True], [['C', 5], True], + [['D', 2], True], [['D', 3], True], [['D', 5], True], + [['E', 6], True], [['E', 7], True], [['E', 8], True], + [['F', 4], True], [['G', 2], True], + [['I', 5], False], [['H', 3], False], [['H', 4], False]] + """ + return False + + def is_simply_laced(self): + """ + Return whether ``self`` is simply laced. + + This returns ``False`` by default. Derived class should override this + appropriately. + + EXAMPLES:: + + sage: [ [t, t.is_simply_laced() ] for t in CartanType.samples() ] + [[['A', 1], True], [['A', 5], True], + [['B', 1], True], [['B', 5], False], + [['C', 1], True], [['C', 5], False], + [['D', 2], True], [['D', 3], True], [['D', 5], True], + [['E', 6], True], [['E', 7], True], [['E', 8], True], + [['F', 4], False], [['G', 2], False], + [['I', 5], False], [['H', 3], False], [['H', 4], False], + [['A', 1, 1], False], [['A', 5, 1], True], + [['B', 1, 1], False], [['B', 5, 1], False], + [['C', 1, 1], False], [['C', 5, 1], False], + [['D', 3, 1], True], [['D', 5, 1], True], + [['E', 6, 1], True], [['E', 7, 1], True], [['E', 8, 1], True], + [['F', 4, 1], False], [['G', 2, 1], False], + [['BC', 1, 2], False], [['BC', 5, 2], False], + [['B', 5, 1]^*, False], [['C', 4, 1]^*, False], + [['F', 4, 1]^*, False], [['G', 2, 1]^*, False], + [['BC', 1, 2]^*, False], [['BC', 5, 2]^*, False]] + """ + return False + + @cached_method + def bilinear_form(self, R=None): + """ + Return the bilinear form over ``R`` associated to ``self``. + + INPUT: + + - ``R`` -- (default: universal cyclotomic field) a ring used to + compute the bilinear form + + EXAMPLES:: + + sage: CoxeterType(['A', 2, 1]).bilinear_form() + [ 1 -1/2 -1/2] + [-1/2 1 -1/2] + [-1/2 -1/2 1] + sage: CoxeterType(['H', 3]).bilinear_form() + [ 1 -1/2 0] + [ -1/2 1 1/2*E(5)^2 + 1/2*E(5)^3] + [ 0 1/2*E(5)^2 + 1/2*E(5)^3 1] + sage: C = CoxeterMatrix([[1,-1,-1],[-1,1,-1],[-1,-1,1]]) + sage: C.bilinear_form() + [ 1 -1 -1] + [-1 1 -1] + [-1 -1 1] + """ + + n = self.rank() + mat = self.coxeter_matrix()._matrix + base_ring = mat.base_ring() + + from sage.rings.universal_cyclotomic_field import UniversalCyclotomicField + UCF = UniversalCyclotomicField() + if UCF.has_coerce_map_from(base_ring): + R = UCF + else: + R = base_ring + # Compute the matrix with entries `- \cos( \pi / m_{ij} )`. + if R is UCF: + val = lambda x: (R.gen(2*x) + ~R.gen(2*x)) / R(-2) if x > -1 else R.one()*x + else: + from sage.functions.trig import cos + from sage.symbolic.constants import pi + val = lambda x: -R(cos(pi / SR(x))) if x > -1 else x + + MS = MatrixSpace(R, n, sparse=True) + MC = MS._get_matrix_class() + + bilinear = MC(MS, entries={(i, j): val(mat[i, j]) + for i in range(n) for j in range(n) + if mat[i, j] != 2}, + coerce=True, copy=True) + bilinear.set_immutable() + return bilinear + + +class CoxeterTypeFromCartanType(CoxeterType, UniqueRepresentation): + """ + A Coxeter type associated to a Cartan type. + """ + @staticmethod + def __classcall_private__(cls, cartan_type): + """ + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: from sage.combinat.root_system.coxeter_type import CoxeterTypeFromCartanType + sage: C1 = CoxeterTypeFromCartanType(['A',3]) + sage: C2 = CoxeterTypeFromCartanType(CartanType(['A',3])) + sage: C1 is C2 + True + """ + return super(CoxeterTypeFromCartanType, cls).__classcall__(cls, + CartanType(cartan_type)) + + def __init__(self, cartan_type): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['A',3]) + sage: TestSuite(C).run() + sage: C = CoxeterType(['H',4]) + sage: TestSuite(C).run() + sage: C = CoxeterType(['C',3,1]) + sage: TestSuite(C).run() + """ + self._cartan_type = cartan_type + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: CoxeterType(['A',3]) + Coxeter type of ['A', 3] + """ + return "Coxeter type of {}".format(self._cartan_type) + + def coxeter_matrix(self): + """ + Return the Coxeter matrix associated to ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['H',3]) + sage: C.coxeter_matrix() + [1 3 2] + [3 1 5] + [2 5 1] + """ + return self._cartan_type.coxeter_matrix() + + def coxeter_graph(self): + """ + Return the Coxeter graph of ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['H',3]) + sage: C.coxeter_graph().edges() + [(1, 2, 3), (2, 3, 5)] + """ + return self._cartan_type.coxeter_diagram() + + def cartan_type(self): + """ + Return the Cartan type used to construct ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['C',3]) + sage: C.cartan_type() + ['C', 3] + """ + return self._cartan_type + + def rank(self): + """ + Return the rank of ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['I', 16]) + sage: C.rank() + 2 + """ + return self._cartan_type.rank() + + def index_set(self): + """ + Return the index set of ``self``. + + EXAMPLES:: + + sage: C = CoxeterType(['A', 4]) + sage: C.index_set() + (1, 2, 3, 4) + """ + return self._cartan_type.index_set() + + def is_finite(self): + """ + Return if ``self`` is a finite type. + + EXAMPLES:: + + sage: C = CoxeterType(['E', 6]) + sage: C.is_finite() + True + """ + return self._cartan_type.is_finite() + + def is_affine(self): + """ + Return if ``self`` is an affine type. + + EXAMPLES:: + + sage: C = CoxeterType(['F', 4, 1]) + sage: C.is_affine() + True + """ + return self._cartan_type.is_affine() + + def is_crystallographic(self): + """ + Return if ``self`` is crystallographic. + + EXAMPLES:: + + sage: C = CoxeterType(['C', 3]) + sage: C.is_crystallographic() + True + + sage: C = CoxeterType(['H', 3]) + sage: C.is_crystallographic() + False + """ + return self._cartan_type.is_crystallographic() + + def is_simply_laced(self): + """ + Return if ``self`` is simply-laced. + + EXAMPLES:: + + sage: C = CoxeterType(['A', 5]) + sage: C.is_simply_laced() + True + + sage: C = CoxeterType(['B', 3]) + sage: C.is_simply_laced() + False + """ + return self._cartan_type.is_simply_laced() + + def relabel(self, relabelling): + """ + Return a relabelled copy of ``self``. + + EXAMPLES:: + + sage: ct = CoxeterType(['A',2]) + sage: ct.relabel({1:-1, 2:-2}) + Coxeter type of ['A', 2] relabelled by {1: -1, 2: -2} + """ + return CoxeterType(self._cartan_type.relabel(relabelling)) + diff --git a/src/sage/combinat/root_system/dynkin_diagram.py b/src/sage/combinat/root_system/dynkin_diagram.py index ee900688c47..bebe22fabbf 100644 --- a/src/sage/combinat/root_system/dynkin_diagram.py +++ b/src/sage/combinat/root_system/dynkin_diagram.py @@ -32,7 +32,6 @@ from sage.graphs.digraph import DiGraph from sage.combinat.root_system.cartan_type import CartanType, CartanType_abstract from sage.combinat.root_system.cartan_matrix import CartanMatrix -from sage.misc.superseded import deprecated_function_alias def DynkinDiagram(*args, **kwds): r""" @@ -564,9 +563,7 @@ def subtype(self, index_set): 0 1 2 3 BC3~ sage: D.subtype([1,2,3]) - O---O=<=O - 1 2 3 - C3 + Dynkin diagram of rank 3 """ return self.cartan_matrix().subtype(index_set).dynkin_diagram() @@ -626,15 +623,11 @@ def is_crystallographic(self): TESTS:: - sage: CartanType(['G',2]).dynkin_diagram().is_crystalographic() - doctest:...: DeprecationWarning: is_crystalographic is deprecated. Please use is_crystallographic instead. - See http://trac.sagemath.org/14673 for details. + sage: CartanType(['G',2]).dynkin_diagram().is_crystallographic() True """ return True - is_crystalographic = deprecated_function_alias(14673, is_crystallographic) - def symmetrizer(self): """ Return the symmetrizer of the corresponding Cartan matrix. diff --git a/src/sage/combinat/root_system/plot.py b/src/sage/combinat/root_system/plot.py index cec3c3ff33c..8305963e7b4 100644 --- a/src/sage/combinat/root_system/plot.py +++ b/src/sage/combinat/root_system/plot.py @@ -416,7 +416,7 @@ :: sage: L = RootSystem(["A",3,1]).ambient_space() - sage: alcoves = CartesianProduct([0,1],[0,1],[0,1]) + sage: alcoves = cartesian_product([[0,1],[0,1],[0,1]]) sage: color = lambda i: "black" if i==0 else None sage: L.plot_alcoves(alcoves=alcoves, color=color, bounding_box=10,wireframe=True).show(frame=False) # long time @@ -671,7 +671,7 @@ def __init__(self, space, # Bounding box from sage.rings.real_mpfr import RR from sage.geometry.polyhedron.all import Polyhedron - from sage.combinat.cartesian_product import CartesianProduct + from itertools import product if bounding_box in RR: bounding_box = [[-bounding_box,bounding_box]] * self.dimension else: @@ -679,7 +679,7 @@ def __init__(self, space, raise TypeError("bounding_box argument doesn't match with the plot dimension") elif not all(len(b)==2 for b in bounding_box): raise TypeError("Invalid bounding box %s"%bounding_box) - self.bounding_box = Polyhedron(vertices=CartesianProduct(*bounding_box)) + self.bounding_box = Polyhedron(vertices=product(*bounding_box)) @cached_method def in_bounding_box(self, x): diff --git a/src/sage/combinat/root_system/root_lattice_realizations.py b/src/sage/combinat/root_system/root_lattice_realizations.py index 047a674a397..06f549760a6 100644 --- a/src/sage/combinat/root_system/root_lattice_realizations.py +++ b/src/sage/combinat/root_system/root_lattice_realizations.py @@ -794,7 +794,7 @@ def positive_real_roots(self): if not self.cartan_type().is_affine(): raise NotImplementedError("only implemented for finite and affine Cartan types") - from sage.combinat.cartesian_product import CartesianProduct + from sage.categories.cartesian_product import cartesian_product from sage.combinat.root_system.root_system import RootSystem from sage.sets.positive_integers import PositiveIntegers from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets @@ -813,19 +813,19 @@ def lift(x): # Add all of the delta shifts delta = self.null_root() if self.cartan_type().is_untwisted_affine(): - C = CartesianProduct(PositiveIntegers(), Q.roots()) + C = cartesian_product([PositiveIntegers(), Q.roots()]) F = Family(C, lambda x: lift(x[1]) + x[0]*delta) D = DisjointUnionEnumeratedSets([P, F]) elif self.cartan_type().type() == 'BC' or self.cartan_type().dual().type() == 'BC': - Cs = CartesianProduct(PositiveIntegers(), Q.short_roots()) - Cl = CartesianProduct(PositiveIntegers(), Q.long_roots()) + Cs = cartesian_product([PositiveIntegers(), Q.short_roots()]) + Cl = cartesian_product([PositiveIntegers(), Q.long_roots()]) Fs = Family(Cl, lambda x: (lift(x[1]) + (2*x[0]-1)*delta) / 2) Fm = Family(Cs, lambda x: lift(x[1]) + x[0]*delta) Fl = Family(Cl, lambda x: lift(x[1]) + 2*x[0]*delta) D = DisjointUnionEnumeratedSets([P, Fs, Fm, Fl]) else: # Other twisted types - Cs = CartesianProduct(PositiveIntegers(), Q.short_roots()) - Cl = CartesianProduct(PositiveIntegers(), Q.long_roots()) + Cs = cartesian_product([PositiveIntegers(), Q.short_roots()]) + Cl = cartesian_product([PositiveIntegers(), Q.long_roots()]) Fs = Family(Cs, lambda x: lift(x[1]) + x[0]*delta) if self.cartan_type().dual() == 'G': # D_4^3 k = 3 @@ -1709,7 +1709,8 @@ def tau_epsilon_operator_on_almost_positive_roots(self, J): REFERENCES: - .. [CFZ] Chapoton, Fomin, Zelevinsky - Polytopal realizations of generalized associahedra + .. [CFZ] Chapoton, Fomin, Zelevinsky - Polytopal realizations of + generalized associahedra, :arxiv:`math/0202004`. """ W = self.weyl_group() t = W.from_reduced_word(J) @@ -1745,7 +1746,7 @@ def tau_plus_minus(self): EXAMPLES: - We explore the example of [CFZ1]_ Eq.(1.3):: + We explore the example of [CFZ]_ Eq.(1.3):: sage: S = RootSystem(['A',2]).root_lattice() sage: taup, taum = S.tau_plus_minus() @@ -1755,10 +1756,6 @@ def tau_plus_minus(self): alpha[1] + alpha[2] , alpha[2] , alpha[1] -alpha[2] , -alpha[2] , alpha[2] alpha[2] , alpha[1] + alpha[2] , -alpha[2] - - REFERENCES: - - .. [CFZ1] Chapoton, Fomin, Zelevinsky - Polytopal realizations of generalized associahedra """ ct = self.cartan_type() L,R = ct.index_set_bipartition() @@ -1790,11 +1787,6 @@ def almost_positive_roots_decomposition(self): [-alpha[2], alpha[2], alpha[1] + alpha[2] + alpha[3] + alpha[4], alpha[1] + 2*alpha[2] + alpha[3] + alpha[4]], [-alpha[3], alpha[3], alpha[2] + alpha[3], alpha[1] + alpha[2] + alpha[4]], [-alpha[4], alpha[4], alpha[2] + alpha[4], alpha[1] + alpha[2] + alpha[3]]] - - REFERENCES: - - .. [CFZ2] Chapoton, Fomin, Zelevinsky - Polytopal realizations of - generalized associahedra """ # TODO: this should use a generic function for computing # orbits under the action of a group: diff --git a/src/sage/combinat/root_system/type_I.py b/src/sage/combinat/root_system/type_I.py index d97f9cd3d6f..bcbcb8dbfbf 100644 --- a/src/sage/combinat/root_system/type_I.py +++ b/src/sage/combinat/root_system/type_I.py @@ -22,7 +22,7 @@ def __init__(self, n): sage: ct.rank() 2 sage: ct.index_set() - [1, 2] + (1, 2) sage: ct.is_irreducible() True @@ -60,9 +60,9 @@ def index_set(self): EXAMPLES:: sage: CartanType(['I', 5]).index_set() - [1, 2] + (1, 2) """ - return [1, 2] + return (1, 2) def coxeter_diagram(self): """ @@ -94,3 +94,4 @@ def coxeter_number(self): 12 """ return self.n + diff --git a/src/sage/combinat/set_partition.py b/src/sage/combinat/set_partition.py index 383c3b9aa2d..b61d29cb9ee 100644 --- a/src/sage/combinat/set_partition.py +++ b/src/sage/combinat/set_partition.py @@ -38,7 +38,6 @@ from sage.rings.infinity import infinity from sage.rings.integer import Integer -from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.misc import IterableFunctionCall from sage.combinat.combinatorial_map import combinatorial_map import sage.combinat.subset as subset @@ -845,7 +844,7 @@ def refinements(self): [{}] """ L = [SetPartitions(part) for part in self] - return [SetPartition(sum(map(list, x), [])) for x in CartesianProduct(*L)] + return [SetPartition(sum(map(list, x), [])) for x in itertools.product(*L)] def coarsenings(self): """ @@ -1118,7 +1117,7 @@ def _iterator_part(self, part): for b in blocs: lb = [IterableFunctionCall(_listbloc, nonzero[i][0], nonzero[i][1], b[i]) for i in range(len(nonzero))] - for x in itertools.imap(lambda x: _union(x), CartesianProduct( *lb )): + for x in itertools.imap(lambda x: _union(x), itertools.product( *lb )): yield x def is_less_than(self, s, t): diff --git a/src/sage/combinat/sf/__init__.py b/src/sage/combinat/sf/__init__.py index 3fd89a5bbe1..2971f6e529a 100644 --- a/src/sage/combinat/sf/__init__.py +++ b/src/sage/combinat/sf/__init__.py @@ -13,6 +13,9 @@ - :ref:`sage.combinat.sf.elementary` - :ref:`sage.combinat.sf.homogeneous` - :ref:`sage.combinat.sf.powersum` +- :ref:`sage.combinat.sf.character` +- :ref:`sage.combinat.sf.orthogonal` +- :ref:`sage.combinat.sf.symplectic` - :ref:`sage.combinat.sf.dual` - :ref:`sage.combinat.sf.orthotriang` - :ref:`sage.combinat.sf.kfpoly` diff --git a/src/sage/combinat/sf/character.py b/src/sage/combinat/sf/character.py new file mode 100644 index 00000000000..e1c1b83ad4b --- /dev/null +++ b/src/sage/combinat/sf/character.py @@ -0,0 +1,496 @@ +""" +Characters of the symmetric group as bases of the symmetric functions + +Just as the Schur functions are the irreducible characters of `Gl_n` +and form a basis of the symmetric functions, the irreducible +symmetric group character basis are the irreducible characters of +of `S_n` when the group is realized as the permutation matrices. + +REFERENCES: + +.. [OZ2015] R. Orellana, M. Zabrocki, *Symmetric group characters + as symmetric functions*, :arxiv:`1510.00438`. +""" + +#***************************************************************************** +# Copyright (C) 2015 Mike Zabrocki 0: + return ~k * self._p.sum(moebius(k/d)*self._p([d]) + for d in divisors(k)) + + def _b_power_k_r(self, k, r): + r""" + An expression involving moebius inversion in the powersum generators. + + For a positive value of ``k``, this expression is + + .. MATH:: + + \sum_{j=0}^r (-1)^{r-j}k^j\binom{r,j} \left( + \frac{1}{k} \sum_{d|k} \mu(d/k) p_d \right)_k. + + INPUT: + + - ``k``, ``r`` -- positive integers + + OUTPUT: + + - an expression in the powersum basis of the symmetric functions + + EXAMPLES:: + + sage: st = SymmetricFunctions(QQ).st() + sage: st._b_power_k_r(1,1) + -p[] + p[1] + sage: st._b_power_k_r(2,2) + p[] + 4*p[1] + p[1, 1] - 4*p[2] - 2*p[2, 1] + p[2, 2] + sage: st._b_power_k_r(3,2) + p[] + 5*p[1] + p[1, 1] - 5*p[3] - 2*p[3, 1] + p[3, 3] + + """ + p = self._p + return p.sum( (-1)**(r-j) * k**j * binomial(r,j) + * p.prod(self._b_power_k(k) - i*p.one() for i in range(j)) + for j in range(r+1) ) + + def _b_power_gamma(self, gamma): + r""" + An expression involving moebius inversion in the powersum generators. + + For a partition `\gamma = (1^{m_1}, 2^{m_2}, \ldots r^{m_r})`, + this expression is + + .. MATH:: + + {\mathbf p}_{\ga} = \sum_{k \geq 1} {\mathbf p}_{k^{m_k}}, + + where + + .. MATH:: + + {\mathbf p}_{k^r} = \sum_{j=0}^r (-1)^{r-j}k^j\binom{r,j} + \left( \frac{1}{k} \sum_{d|k} \mu(d/k) p_d \right)_k~. + + INPUT: + + - ``gamma`` -- a partition + + OUTPUT: + + - an expression in the powersum basis of the symmetric functions + + EXAMPLES:: + + sage: st = SymmetricFunctions(QQ).st() + sage: st._b_power_k_r(1,1) + -p[] + p[1] + sage: st._b_power_k_r(2,2) + p[] + 4*p[1] + p[1, 1] - 4*p[2] - 2*p[2, 1] + p[2, 2] + sage: st._b_power_k_r(3,2) + p[] + 5*p[1] + p[1, 1] - 5*p[3] - 2*p[3, 1] + p[3, 3] + + """ + return self._p.prod( self._b_power_k_r(Integer(k),Integer(r)) + for (k,r) in gamma.to_exp_dict().iteritems() ) + + def _self_to_power_on_basis(self, lam): + r""" + An expansion of the irreducible character in the powersum basis. + + The formula for the irreducible character basis indexed by the + partition ``lam`` is given by the formula + + .. MATH:: + + \sum_{\gamma} \chi^{\lambda}(\gamma) + \frac{{\mathbf p}_\gamma}{z_\gamma}, + + where `\chi^{\lambda}(\gamma)` is the irreducible character + indexed by the partition `\lambda` and evaluated at an element + of cycle structure `\gamma` and `{\mathbf p}_\gamma` is the + power sum expression calculated in the method + :meth:`_b_power_gamma`. + + INPUT: + + - ``lam`` -- a partition + + OUTPUT: + + - an expression in the power sum basis + + EXAMPLES:: + + sage: st = SymmetricFunctions(QQ).st() + sage: st._self_to_power_on_basis([2,1]) + 3*p[1] - 2*p[1, 1] + 1/3*p[1, 1, 1] - 1/3*p[3] + sage: st._self_to_power_on_basis([1,1]) + p[] - p[1] + 1/2*p[1, 1] - 1/2*p[2] + + """ + return self._p.sum( c*self._b_power_gamma(ga) + for (ga, c) in self._p(self._other(lam)) ) + + @cached_method + def _self_to_other_on_basis(self, lam): + r""" + An expansion of the irreducible character basis in the Schur basis. + + Compute the Schur expansion by first computing it in the + powersum basis and the coercing to the Schur basis. + + INPUT: + + - ``lam`` -- a partition + + OUTPUT: + + - an expression in the Schur basis + + EXAMPLES:: + + sage: st = SymmetricFunctions(QQ).st() + sage: st._self_to_other_on_basis(Partition([1,1])) + s[] - s[1] + s[1, 1] + sage: st._self_to_other_on_basis(Partition([2,1])) + 3*s[1] - 2*s[1, 1] - 2*s[2] + s[2, 1] + """ + return self._other(self._self_to_power_on_basis(lam)) + diff --git a/src/sage/combinat/sf/macdonald.py b/src/sage/combinat/sf/macdonald.py index 21e989c24a0..f7472b4aa42 100644 --- a/src/sage/combinat/sf/macdonald.py +++ b/src/sage/combinat/sf/macdonald.py @@ -1391,7 +1391,7 @@ def _m_to_self( self, f ): mu_to_H = lambda mu: self._self_to_m(self(mu)).theta_qt(q=self.t, t=0) out = {} while not g.is_zero(): - sprt = g.support() + sprt = sorted(g.support()) Hmu = mu_to_H(sprt[-1]) fl_sprt = fl(sprt[-1]) out[fl_sprt] = self._base(g.coefficient(sprt[-1]) / Hmu.coefficient(sprt[-1])) @@ -1625,7 +1625,7 @@ def _m_to_self( self, f ): g = f.omega_qt(q=subsval, t=0) out = {} while not g.is_zero(): - sprt = g.support() + sprt = sorted(g.support()) Htmu = self._self_to_m(self(fl(sprt[-1]))).omega_qt(q=subsval, t=0) out[fl(sprt[-1])] = self._base(g.coefficient(sprt[-1]) / Htmu.coefficient(sprt[-1])) g -= out[fl(sprt[-1])] * Htmu diff --git a/src/sage/combinat/sf/orthogonal.py b/src/sage/combinat/sf/orthogonal.py new file mode 100644 index 00000000000..cd8ba3bac09 --- /dev/null +++ b/src/sage/combinat/sf/orthogonal.py @@ -0,0 +1,245 @@ +""" +Orthogonal Symmetric Functions + +AUTHORS: + +- Travis Scrimshaw (2013-11-10): Initial version +""" +#***************************************************************************** +# Copyright (C) 2013 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# The full text of the GPL is available at: +# +# http://www.gnu.org/licenses/ +#***************************************************************************** +import sfa +import sage.libs.lrcalc.lrcalc as lrcalc +from sage.combinat.partition import Partitions +from sage.misc.cachefunc import cached_method +from sage.rings.all import ZZ, QQ, Integer +from sage.matrix.all import matrix + +class SymmetricFunctionAlgebra_orthogonal(sfa.SymmetricFunctionAlgebra_generic): + r""" + The orthogonal symmetric function basis (or orthogonal basis, to be short). + + The orthogonal basis `\{ o_{\lambda} \}` where `\lambda` is taken over + all partitions is defined by the following change of basis with the + Schur functions: + + .. MATH:: + + s_{\lambda} = \sum_{\mu} \left( \sum_{\nu \in H} c^{\lambda}_{\mu\nu} + \right) o_{\mu} + + where `H` is the set of all partitions with even-width rows and + `c^{\lambda}_{\mu\nu}` is the usual Littlewood-Richardson (LR) + coefficients. By the properties of LR coefficients, this can be shown to + be a upper unitriangular change of basis. + + .. NOTE:: + + This is only a filtered basis, not a `\ZZ`-graded basis. However this + does respect the induced `(\ZZ/2\ZZ)`-grading. + + INPUT: + + - ``Sym`` -- an instance of the ring of the symmetric functions + + REFERENCES: + + - [ChariKleber2000]_ + - [KoikeTerada1987]_ + - [ShimozonoZabrocki2006]_ + + EXAMPLES: + + Here are the first few orthogonal symmetric functions, in various bases:: + + sage: Sym = SymmetricFunctions(QQ) + sage: o = Sym.o() + sage: e = Sym.e() + sage: h = Sym.h() + sage: p = Sym.p() + sage: s = Sym.s() + sage: m = Sym.m() + + sage: p(o([1])) + p[1] + sage: m(o([1])) + m[1] + sage: e(o([1])) + e[1] + sage: h(o([1])) + h[1] + sage: s(o([1])) + s[1] + + sage: p(o([2])) + -p[] + 1/2*p[1, 1] + 1/2*p[2] + sage: m(o([2])) + -m[] + m[1, 1] + m[2] + sage: e(o([2])) + -e[] + e[1, 1] - e[2] + sage: h(o([2])) + -h[] + h[2] + sage: s(o([2])) + -s[] + s[2] + + sage: p(o([3])) + -p[1] + 1/6*p[1, 1, 1] + 1/2*p[2, 1] + 1/3*p[3] + sage: m(o([3])) + -m[1] + m[1, 1, 1] + m[2, 1] + m[3] + sage: e(o([3])) + -e[1] + e[1, 1, 1] - 2*e[2, 1] + e[3] + sage: h(o([3])) + -h[1] + h[3] + sage: s(o([3])) + -s[1] + s[3] + + sage: Sym = SymmetricFunctions(ZZ) + sage: o = Sym.o() + sage: e = Sym.e() + sage: h = Sym.h() + sage: s = Sym.s() + sage: m = Sym.m() + sage: p = Sym.p() + sage: m(o([4])) + -m[1, 1] + m[1, 1, 1, 1] - m[2] + m[2, 1, 1] + m[2, 2] + m[3, 1] + m[4] + sage: e(o([4])) + -e[1, 1] + e[1, 1, 1, 1] + e[2] - 3*e[2, 1, 1] + e[2, 2] + 2*e[3, 1] - e[4] + sage: h(o([4])) + -h[2] + h[4] + sage: s(o([4])) + -s[2] + s[4] + + Some examples of conversions the other way:: + + sage: o(h[3]) + o[1] + o[3] + sage: o(e[3]) + o[1, 1, 1] + sage: o(m[2,1]) + o[1] - 2*o[1, 1, 1] + o[2, 1] + sage: o(p[3]) + o[1, 1, 1] - o[2, 1] + o[3] + + Some multiplication:: + + sage: o([2]) * o([1,1]) + o[1, 1] + o[2] + o[2, 1, 1] + o[3, 1] + sage: o([2,1,1]) * o([2]) + o[1, 1] + o[1, 1, 1, 1] + 2*o[2, 1, 1] + o[2, 2] + o[2, 2, 1, 1] + + o[3, 1] + o[3, 1, 1, 1] + o[3, 2, 1] + o[4, 1, 1] + sage: o([1,1]) * o([2,1]) + o[1] + o[1, 1, 1] + 2*o[2, 1] + o[2, 1, 1, 1] + o[2, 2, 1] + + o[3] + o[3, 1, 1] + o[3, 2] + + Examples of the Hopf algebra structure:: + + sage: o([1]).antipode() + -o[1] + sage: o([2]).antipode() + -o[] + o[1, 1] + sage: o([1]).coproduct() + o[] # o[1] + o[1] # o[] + sage: o([2]).coproduct() + o[] # o[] + o[] # o[2] + o[1] # o[1] + o[2] # o[] + sage: o([1]).counit() + 0 + sage: o.one().counit() + 1 + """ + def __init__(self, Sym): + """ + Initialize ``self``. + + TESTS:: + + sage: o = SymmetricFunctions(QQ).o() + sage: TestSuite(o).run() + """ + sfa.SymmetricFunctionAlgebra_generic.__init__(self, Sym, "orthogonal", + 'o', graded=False) + + # We make a strong reference since we use it for our computations + # and so we can define the coercion below (only codomains have + # strong references) + self._s = Sym.schur() + + # Setup the coercions + M = self._s.module_morphism(self._s_to_o_on_basis, codomain=self, + triangular='upper', unitriangular=True) + M.register_as_coercion() + Mi = self.module_morphism(self._o_to_s_on_basis, codomain=self._s, + triangular='upper', unitriangular=True) + Mi.register_as_coercion() + + @cached_method + def _o_to_s_on_basis(self, lam): + r""" + Return the orthogonal symmetric function ``o[lam]`` expanded in + the Schur basis, where ``lam`` is a partition. + + TESTS: + + Check that this is the inverse:: + + sage: o = SymmetricFunctions(QQ).o() + sage: s = SymmetricFunctions(QQ).s() + sage: all(o(s(o[la])) == o[la] for i in range(5) for la in Partitions(i)) + True + sage: all(s(o(s[la])) == s[la] for i in range(5) for la in Partitions(i)) + True + """ + R = self.base_ring() + n = sum(lam) + return self._s._from_dict({ mu: R.sum( (-1)**j * lrcalc.lrcoef_unsafe(lam, mu, nu) + for nu in Partitions(2*j) + if all(nu.arm_length(i,i) == nu.leg_length(i,i)+1 + for i in range(nu.frobenius_rank())) + ) + for j in range(n//2+1) # // 2 for horizontal dominoes + for mu in Partitions(n-2*j) }) + + @cached_method + def _s_to_o_on_basis(self, lam): + r""" + Return the Schur symmetric function ``s[lam]`` expanded in + the orthogonal basis, where ``lam`` is a partition. + + INPUT: + + - ``lam`` -- a partition + + OUTPUT: + + - the expansion of ``s[lam]`` in the orthogonal basis ``self`` + + EXAMPLES:: + + sage: Sym = SymmetricFunctions(QQ) + sage: s = Sym.schur() + sage: o = Sym.orthogonal() + sage: o._s_to_o_on_basis(Partition([])) + o[] + sage: o._s_to_o_on_basis(Partition([4,2,1])) + o[1] + 2*o[2, 1] + o[2, 2, 1] + o[3] + + o[3, 1, 1] + o[3, 2] + o[4, 1] + o[4, 2, 1] + sage: s(o._s_to_o_on_basis(Partition([3,1]))) == s[3,1] + True + """ + R = self.base_ring() + n = sum(lam) + return self._from_dict({ mu: R.sum( lrcalc.lrcoef_unsafe(lam, mu, [2*x for x in nu]) + for nu in Partitions(j) ) + for j in range(n//2+1) # // 2 for horizontal dominoes + for mu in Partitions(n-2*j) }) + diff --git a/src/sage/combinat/sf/powersum.py b/src/sage/combinat/sf/powersum.py index c61ca4dbaab..b97176ab8cc 100644 --- a/src/sage/combinat/sf/powersum.py +++ b/src/sage/combinat/sf/powersum.py @@ -19,6 +19,7 @@ #***************************************************************************** import sfa, multiplicative, classical from sage.combinat.partition import Partition +from sage.rings.arith import divisors class SymmetricFunctionAlgebra_power(multiplicative.SymmetricFunctionAlgebra_multiplicative): def __init__(self, Sym): @@ -164,6 +165,55 @@ def bottom_schur_function(self, partition, degree=None): in s_partition if len(p) == degree], distinct=True) + def eval_at_permutation_roots_on_generators(self, k, rho): + r""" + Evaluate `p_k` at eigenvalues of permutation matrix. + + This function evaluates a symmetric function ``p([k])`` + at the eigenvalues of a permutation matrix with cycle + structure ``\rho``. + + This function evaluates a `p_k` at the roots of unity + + .. MATH:: + + \Xi_{\rho_1},\Xi_{\rho_2},\ldots,\Xi_{\rho_\ell} + + where + + .. MATH:: + + \Xi_{m} = 1,\zeta_m,\zeta_m^2,\ldots,\zeta_m^{m-1} + + and `\zeta_m` is an `m` root of unity. + This is characterized by `p_k[ A , B ] = p_k[A] + p_k[B]` and + `p_k[ \Xi_m ] = 0` unless `m` divides `k` and `p_{rm}[\Xi_m]=m`. + + INPUT: + + - ``k`` -- a non-negative integer + - ``rho`` -- a partition or a list of non-negative integers + + OUTPUT: + + - an element of the base ring + + EXAMPLES:: + + sage: p = SymmetricFunctions(QQ).p() + sage: p.eval_at_permutation_roots_on_generators(3, [6]) + 0 + sage: p.eval_at_permutation_roots_on_generators(3, [3]) + 3 + sage: p.eval_at_permutation_roots_on_generators(3, [1]) + 1 + sage: p.eval_at_permutation_roots_on_generators(3, [3,3]) + 6 + sage: p.eval_at_permutation_roots_on_generators(3, [1,1,1,1,1]) + 5 + """ + return self.base_ring().sum(d*list(rho).count(d) for d in divisors(k)) + class Element(classical.SymmetricFunctionAlgebra_classical.Element): def omega(self): r""" @@ -600,6 +650,59 @@ def expand(self, n, alphabet='x'): condition = lambda part: False return self._expand(condition, n, alphabet) + def eval_at_permutation_roots(self, rho): + r""" + Evaluate at eigenvalues of a permutation matrix. + + Evaluate an element of the power sum basis at the eigenvalues + of a permutation matrix with cycle structure `\rho`. + + This function evaluates an element at the roots of unity + + .. MATH:: + + \Xi_{\rho_1},\Xi_{\rho_2},\ldots,\Xi_{\rho_\ell} + + where + + .. MATH:: + + \Xi_{m} = 1,\zeta_m,\zeta_m^2,\ldots,\zeta_m^{m-1} + + and `\zeta_m` is an `m` root of unity. + These roots of unity represent the eigenvalues of permutation + matrix with cycle structure `\rho`. + + INPUT: + + - ``rho`` -- a partition or a list of non-negative integers + + OUTPUT: + + - an element of the base ring + + EXAMPLES:: + + sage: p = SymmetricFunctions(QQ).p() + sage: p([3,3]).eval_at_permutation_roots([6]) + 0 + sage: p([3,3]).eval_at_permutation_roots([3]) + 9 + sage: p([3,3]).eval_at_permutation_roots([1]) + 1 + sage: p([3,3]).eval_at_permutation_roots([3,3]) + 36 + sage: p([3,3]).eval_at_permutation_roots([1,1,1,1,1]) + 25 + sage: (p[1]+p[2]+p[3]).eval_at_permutation_roots([3,2]) + 5 + """ + p = self.parent() + R = self.base_ring() + on_basis = lambda lam: R.prod( + p.eval_at_permutation_roots_on_generators(k, rho) for k in lam) + return p._apply_module_morphism(self, on_basis, R) + # Backward compatibility for unpickling from sage.structure.sage_object import register_unpickle_override register_unpickle_override('sage.combinat.sf.powersum', 'SymmetricFunctionAlgebraElement_power', SymmetricFunctionAlgebra_power.Element) diff --git a/src/sage/combinat/sf/sf.py b/src/sage/combinat/sf/sf.py index 2bd5e360643..17c7de792b8 100644 --- a/src/sage/combinat/sf/sf.py +++ b/src/sage/combinat/sf/sf.py @@ -21,7 +21,9 @@ #***************************************************************************** from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation -from sage.categories.all import Rings, GradedHopfAlgebras +from sage.categories.graded_hopf_algebras import GradedHopfAlgebras +from sage.categories.fields import Fields +from sage.categories.rings import Rings from sage.combinat.partition import Partitions from sage.combinat.free_module import CombinatorialFreeModule from sage.rings.rational_field import QQ @@ -152,7 +154,11 @@ class SymmetricFunctions(UniqueRepresentation, Parent): the mathematical properties of ``p``:: sage: p.categories() - [Category of bases of Symmetric Functions over Rational Field, Category of graded hopf algebras with basis over Rational Field, ...] + [Category of graded bases of Symmetric Functions over Rational Field, + Category of filtered bases of Symmetric Functions over Rational Field, + Category of bases of Symmetric Functions over Rational Field, + Category of graded hopf algebras with basis over Rational Field, + ...] To start with, ``p`` is a graded algebra, the grading being induced by the size of the partitions. Due to this, the one is the basis @@ -823,16 +829,19 @@ def __init__(self, R): TESTS:: + sage: Sym1 = SymmetricFunctions(FiniteField(23)) + sage: Sym2 = SymmetricFunctions(Integers(23)) sage: TestSuite(Sym).run() """ - assert(R in Rings()) + # change the line below to assert(R in Rings()) once MRO issues from #15536, #15475 are resolved + assert(R in Fields() or R in Rings()) # side effect of this statement assures MRO exists for R self._base = R # Won't be needed when CategoryObject won't override anymore base_ring Parent.__init__(self, category = GradedHopfAlgebras(R).WithRealizations()) def a_realization(self): r""" - Returns a particular realization of ``self`` (the Schur basis). + Return a particular realization of ``self`` (the Schur basis). EXAMPLES:: @@ -865,7 +874,7 @@ def schur(self): return schur.SymmetricFunctionAlgebra_schur(self) s = schur Schur = schur # Currently needed by SymmetricFunctions.__init_extra__ - # and sfa.SymmetricFunctionsBases.corresponding_basis_over + # and sfa.GradedSymmetricFunctionsBases.corresponding_basis_over def powersum(self): r""" @@ -933,9 +942,107 @@ def witt(self, coerce_h=True, coerce_e=False, coerce_p=False): import witt return witt.SymmetricFunctionAlgebra_witt(self, coerce_h=coerce_h, coerce_e=coerce_e, coerce_p=coerce_p) w = witt - # Currently needed by sfa.SymmetricFunctionsBases.corresponding_basis_over + # Currently needed by sfa.GradedSymmetricFunctionsBases.corresponding_basis_over Witt = witt + def irreducible_symmetric_group_character(self): + r""" + The irreducible `S_n` character basis of the Symmetric Functions. + + This basis has the property that if the element indexed by the + partition `\lambda` is evaluated at the roots of a permutation of + cycle structure `\rho` then the value is the irreducible character + `\chi^{(|\rho|-|\lambda|,\lambda)}(\rho)`. + + In terms of methods that are implemented in Sage, if ``n`` is + a sufficiently large integer, then + ``st(lam).character_to_frobenius_image(n)`` is equal the Schur function + indexed by ``[n-sum(lam)]+lam``. + + This basis is introduced in [OZ2015]_. + + .. SEEALSO:: + + :meth:`~sage.combinat.sf.sfa.SymmetricFunctionAlgebra_generic_Element.character_to_frobenius_image`, + :meth:`~sage.combinat.sf.sfa.SymmetricFunctionAlgebra_generic_Element.eval_at_permutation_roots` + + EXAMPLES:: + + sage: SymmetricFunctions(QQ).irreducible_symmetric_group_character() + Symmetric Functions over Rational Field in the irreducible symmetric group character basis + sage: st = SymmetricFunctions(QQ).st() + sage: s = SymmetricFunctions(QQ).s() + sage: s(st([3,2]).character_to_frobenius_image(9)) + s[4, 3, 2] + sage: s(st([3,2]).character_to_frobenius_image(7)) + 0 + sage: s(st([3,2]).character_to_frobenius_image(6)) + -s[2, 2, 2] + sage: list(SymmetricGroup(5).character_table()[-2]) + [4, 2, 0, 1, -1, 0, -1] + sage: list(reversed([st([1]).eval_at_permutation_roots(rho) \ + ....: for rho in Partitions(5)])) + [4, 2, 0, 1, -1, 0, -1] + """ + from character import irreducible_character_basis + return irreducible_character_basis(self, 'st') + st = irreducible_symmetric_group_character + + def induced_trivial_character(self): + r""" + The induced trivial character basis of the Symmetric Functions. + + The trivial character of + + .. MATH:: + + S_{n-|\lambda|} \times S_{\lambda_1} \times S_{\lambda_2} \times + \cdots \times S_{\lambda_\ell(\lambda)} + + induced to the group `S_{n}` is a symmetric function in the + eigenvalues of a permutation matrix. This basis is that character. + + It has the property that if the element indexed by the + partition `\lambda` is evaluated at the roots of a permutation of + cycle structure `\rho` then the value is the coefficient + `\left< h_{(n-|\lambda|,\lambda)}, p_\rho \right>`. + + In terms of methods that are implemented in Sage, if ``n`` is + a sufficiently large integer, then + ``ht(lam).character_to_frobenius_image(n)`` is equal the complete + function indexed by ``[n-sum(lam)]+lam``. + + This basis is introduced in [OZ2015]_. + + .. SEEALSO:: + + :meth:`~sage.combinat.sf.sfa.SymmetricFunctionAlgebra_generic_Element.character_to_frobenius_image`, + :meth:`~sage.combinat.sf.sfa.SymmetricFunctionAlgebra_generic_Element.eval_at_permutation_roots` + + EXAMPLES:: + + sage: SymmetricFunctions(QQ).induced_trivial_character() + Symmetric Functions over Rational Field in the induced trivial character basis + sage: ht = SymmetricFunctions(QQ).ht() + sage: h = SymmetricFunctions(QQ).h() + sage: h(ht([3,2]).character_to_frobenius_image(9)) + h[4, 3, 2] + sage: h(ht([3,2]).character_to_frobenius_image(7)) + h[3, 2, 2] + sage: h(ht([3,2]).character_to_frobenius_image(5)) + h[3, 2] + sage: h(ht([3,2]).character_to_frobenius_image(4)) + 0 + sage: p = SymmetricFunctions(QQ).p() + sage: [h([4,1]).scalar(p(rho)) for rho in Partitions(5)] + [0, 1, 0, 2, 1, 3, 5] + sage: [ht([1]).eval_at_permutation_roots(rho) for rho in Partitions(5)] + [0, 1, 0, 2, 1, 3, 5] + """ + from character import character_basis + return character_basis(self, self.h(), "induced trivial character", 'ht') + ht = induced_trivial_character + def forgotten(self): r""" The forgotten basis of the Symmetric Functions (or the basis dual to @@ -1021,6 +1128,36 @@ def forgotten(self): return self.elementary().dual_basis() f = forgotten + def symplectic(self): + """ + The symplectic basis of the symmetric functions. + + .. SEEALSO:: :class:`~sage.combinat.sf.symplectic.SymmetricFunctionAlgebra_symplectic` + + EXAMPLES:: + + sage: SymmetricFunctions(QQ).symplectic() + Symmetric Functions over Rational Field in the symplectic basis + """ + import symplectic + return symplectic.SymmetricFunctionAlgebra_symplectic(self) + sp = symplectic + + def orthogonal(self): + """ + The orthogonal basis of the symmetric functions. + + .. SEEALSO:: :class:`~sage.combinat.sf.orthogonal.SymmetricFunctionAlgebra_orthogonal` + + EXAMPLES:: + + sage: SymmetricFunctions(QQ).orthogonal() + Symmetric Functions over Rational Field in the orthogonal basis + """ + import orthogonal + return orthogonal.SymmetricFunctionAlgebra_orthogonal(self) + o = orthogonal + def macdonald(self, q='q', t='t'): r""" Returns the entry point for the various Macdonald bases. diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index c99b2bd525f..7d4ce46a23f 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -14,7 +14,7 @@ integer partitions:: sage: s.category() - Category of bases of Symmetric Functions over Rational Field + Category of graded bases of Symmetric Functions over Rational Field sage: s.basis().keys() Partitions @@ -216,6 +216,8 @@ from sage.rings.polynomial.polynomial_element import is_Polynomial from sage.rings.polynomial.multi_polynomial import is_MPolynomial from sage.combinat.partition import _Partitions, Partitions, Partitions_n, Partition +from sage.categories.algebras import Algebras +from sage.categories.hopf_algebras import HopfAlgebras import sage.libs.symmetrica.all as symmetrica # used in eval() from sage.combinat.free_module import CombinatorialFreeModule from sage.matrix.constructor import matrix @@ -330,34 +332,31 @@ def is_SymmetricFunction(x): """ return isinstance(x, SymmetricFunctionAlgebra_generic.Element) -from sage.categories.realizations import Realizations, Category_realization_of_parent +##################################################################### +## Bases categories + +from sage.categories.realizations import Category_realization_of_parent class SymmetricFunctionsBases(Category_realization_of_parent): r""" The category of bases of the ring of symmetric functions. - """ - def __init__(self, base): - r""" - Initialize the bases of the ring of symmetric functions. - INPUT: - - - ``self`` -- a category of bases for the symmetric functions - - ``base`` -- ring of symmetric functions + INPUT: - TESTS:: + - ``self`` -- a category of bases for the symmetric functions + - ``base`` -- ring of symmetric functions - sage: from sage.combinat.sf.sfa import SymmetricFunctionsBases - sage: Sym = SymmetricFunctions(QQ) - sage: bases = SymmetricFunctionsBases(Sym); bases - Category of bases of Symmetric Functions over Rational Field - sage: Sym.schur() in bases - True - """ - Category_realization_of_parent.__init__(self, base) + TESTS:: + sage: from sage.combinat.sf.sfa import SymmetricFunctionsBases + sage: Sym = SymmetricFunctions(QQ) + sage: bases = SymmetricFunctionsBases(Sym); bases + Category of bases of Symmetric Functions over Rational Field + sage: Sym.schur() in bases + True + """ def _repr_(self): r""" - Returns the representation of ``self``. + Return the representation of ``self``. INPUT: @@ -377,22 +376,23 @@ def super_categories(self): r""" The super categories of ``self``. - INPUT: - - - ``self`` -- a category of bases for the symmetric functions - EXAMPLES:: sage: from sage.combinat.sf.sfa import SymmetricFunctionsBases sage: Sym = SymmetricFunctions(QQ) sage: bases = SymmetricFunctionsBases(Sym) sage: bases.super_categories() - [Category of commutative graded hopf algebras with basis over Rational Field, - Category of realizations of Symmetric Functions over Rational Field] + [Category of realizations of Symmetric Functions over Rational Field, + Category of commutative hopf algebras with basis over Rational Field, + Join of Category of realizations of hopf algebras over Rational Field + and Category of graded algebras over Rational Field] """ - from sage.categories.all import CommutativeRings, GradedHopfAlgebrasWithBasis - return [GradedHopfAlgebrasWithBasis(self.base().base_ring()).Commutative(), - Realizations(self.base())] + # FIXME: The last one should also be commutative, but this triggers a + # KeyError when doing the C3 algorithm!!! + cat = HopfAlgebras(self.base().base_ring()) + return [self.base().Realizations(), + cat.Commutative().WithBasis(), + cat.Graded().Realizations()] class ParentMethods: @@ -616,123 +616,6 @@ def degree_on_basis(self, b): """ return sum(b) - def antipode_by_coercion(self, element): - r""" - The antipode of ``element``. - - INPUT: - - - ``element`` -- element in a basis of the ring of symmetric functions - - EXAMPLES:: - - sage: Sym = SymmetricFunctions(QQ) - sage: p = Sym.p() - sage: s = Sym.s() - sage: e = Sym.e() - sage: h = Sym.h() - sage: (h([]) + h([1])).antipode() # indirect doctest - h[] - h[1] - sage: (s([]) + s([1]) + s[2]).antipode() - s[] - s[1] + s[1, 1] - sage: (p([2]) + p([3])).antipode() - -p[2] - p[3] - sage: (e([2]) + e([3])).antipode() - e[1, 1] - e[1, 1, 1] - e[2] + 2*e[2, 1] - e[3] - sage: f = Sym.f() - sage: f([3,2,1]).antipode() - -f[3, 2, 1] - 4*f[3, 3] - 2*f[4, 2] - 2*f[5, 1] - 6*f[6] - - The antipode is an involution:: - - sage: Sym = SymmetricFunctions(ZZ) - sage: s = Sym.s() - sage: all( s[u].antipode().antipode() == s[u] for u in Partitions(4) ) - True - - The antipode is an algebra homomorphism:: - - sage: Sym = SymmetricFunctions(FiniteField(23)) - sage: h = Sym.h() - sage: all( all( (s[u] * s[v]).antipode() == s[u].antipode() * s[v].antipode() - ....: for u in Partitions(3) ) - ....: for v in Partitions(3) ) - True - - TESTS: - - Everything works over `\ZZ`:: - - sage: Sym = SymmetricFunctions(ZZ) - sage: p = Sym.p() - sage: s = Sym.s() - sage: e = Sym.e() - sage: h = Sym.h() - sage: (h([]) + h([1])).antipode() # indirect doctest - h[] - h[1] - sage: (s([]) + s([1]) + s[2]).antipode() - s[] - s[1] + s[1, 1] - sage: (p([2]) + p([3])).antipode() - -p[2] - p[3] - sage: (e([2]) + e([3])).antipode() - e[1, 1] - e[1, 1, 1] - e[2] + 2*e[2, 1] - e[3] - """ - return self.degree_negation(element.omega()) - - def counit(self, element): - r""" - Return the counit of ``element``. - - The counit is the constant term of ``element``. - - INPUT: - - - ``element`` -- element in a basis of the ring of symmetric functions - - EXAMPLES:: - - sage: Sym = SymmetricFunctions(QQ) - sage: m = Sym.monomial() - sage: f = 2*m[2,1] + 3*m[[]] - sage: f.counit() - 3 - """ - return element.degree_zero_coefficient() - - def degree_negation(self, element): - r""" - Return the image of ``element`` under the degree negation - automorphism of the ring of symmetric functions. - - The degree negation is the automorphism which scales every - homogeneous element of degree `k` by `(-1)^k` (for all `k`). - - INPUT: - - - ``element`` -- symmetric function written in ``self`` - - EXAMPLES:: - - sage: Sym = SymmetricFunctions(ZZ) - sage: m = Sym.monomial() - sage: f = 2*m[2,1] + 4*m[1,1] - 5*m[1] - 3*m[[]] - sage: m.degree_negation(f) - -3*m[] + 5*m[1] + 4*m[1, 1] - 2*m[2, 1] - - TESTS: - - Using :meth:`degree_negation` on an element of a different - basis works correctly:: - - sage: e = Sym.elementary() - sage: m.degree_negation(e[3]) - -m[1, 1, 1] - sage: m.degree_negation(m(e[3])) - -m[1, 1, 1] - """ - return self.sum_of_terms([ (lam, (-1)**(sum(lam)%2) * a) - for lam, a in self(element) ]) - def corresponding_basis_over(self, R): r""" Return the realization of symmetric functions corresponding to @@ -1395,8 +1278,217 @@ def coeff_of_m_mu_in_result(mu): distinct=True) return self(r) - class ElementMethods: +class FilteredSymmetricFunctionsBases(Category_realization_of_parent): + r""" + The category of graded bases of the ring of symmetric functions. + + TESTS:: + + sage: from sage.combinat.sf.sfa import FilteredSymmetricFunctionsBases + sage: Sym = SymmetricFunctions(QQ) + sage: bases = FilteredSymmetricFunctionsBases(Sym); bases + Category of filtered bases of Symmetric Functions over Rational Field + sage: Sym.schur() in bases + True + sage: Sym.sp() in bases + True + """ + def _repr_(self): + r""" + Return the representation of ``self``. + + EXAMPLES:: + + sage: from sage.combinat.sf.sfa import FilteredSymmetricFunctionsBases + sage: Sym = SymmetricFunctions(QQ) + sage: bases = FilteredSymmetricFunctionsBases(Sym) + sage: bases._repr_() + 'Category of filtered bases of Symmetric Functions over Rational Field' + """ + return "Category of filtered bases of %s" % self.base() + + def super_categories(self): + r""" + The super categories of ``self``. + + EXAMPLES:: + + sage: from sage.combinat.sf.sfa import FilteredSymmetricFunctionsBases + sage: Sym = SymmetricFunctions(QQ) + sage: bases = FilteredSymmetricFunctionsBases(Sym) + sage: bases.super_categories() + [Category of bases of Symmetric Functions over Rational Field, + Join of Category of hopf algebras with basis over Rational Field + and Category of filtered algebras with basis over Rational Field + and Category of commutative algebras over Rational Field] + """ + cat = HopfAlgebras(self.base().base_ring()).Commutative().WithBasis().Filtered() + return [SymmetricFunctionsBases(self.base()), cat] + +class GradedSymmetricFunctionsBases(Category_realization_of_parent): + r""" + The category of graded bases of the ring of symmetric functions. + + TESTS:: + + sage: from sage.combinat.sf.sfa import GradedSymmetricFunctionsBases + sage: Sym = SymmetricFunctions(QQ) + sage: bases = GradedSymmetricFunctionsBases(Sym); bases + Category of graded bases of Symmetric Functions over Rational Field + sage: Sym.schur() in bases + True + sage: Sym.sp() in bases + False + """ + def _repr_(self): + r""" + Return the representation of ``self``. + + EXAMPLES:: + sage: from sage.combinat.sf.sfa import GradedSymmetricFunctionsBases + sage: Sym = SymmetricFunctions(QQ) + sage: bases = GradedSymmetricFunctionsBases(Sym) + sage: bases._repr_() + 'Category of graded bases of Symmetric Functions over Rational Field' + """ + return "Category of graded bases of %s" % self.base() + + def super_categories(self): + r""" + The super categories of ``self``. + + EXAMPLES:: + + sage: from sage.combinat.sf.sfa import GradedSymmetricFunctionsBases + sage: Sym = SymmetricFunctions(QQ) + sage: bases = GradedSymmetricFunctionsBases(Sym) + sage: bases.super_categories() + [Category of filtered bases of Symmetric Functions over Rational Field, + Category of commutative graded hopf algebras with basis over Rational Field] + """ + cat = HopfAlgebras(self.base().base_ring()).Commutative().WithBasis().Graded() + return [FilteredSymmetricFunctionsBases(self.base()), cat] + + class ParentMethods: + def antipode_by_coercion(self, element): + r""" + The antipode of ``element``. + + INPUT: + + - ``element`` -- element in a basis of the ring of symmetric functions + + EXAMPLES:: + + sage: Sym = SymmetricFunctions(QQ) + sage: p = Sym.p() + sage: s = Sym.s() + sage: e = Sym.e() + sage: h = Sym.h() + sage: (h([]) + h([1])).antipode() # indirect doctest + h[] - h[1] + sage: (s([]) + s([1]) + s[2]).antipode() + s[] - s[1] + s[1, 1] + sage: (p([2]) + p([3])).antipode() + -p[2] - p[3] + sage: (e([2]) + e([3])).antipode() + e[1, 1] - e[1, 1, 1] - e[2] + 2*e[2, 1] - e[3] + sage: f = Sym.f() + sage: f([3,2,1]).antipode() + -f[3, 2, 1] - 4*f[3, 3] - 2*f[4, 2] - 2*f[5, 1] - 6*f[6] + + The antipode is an involution:: + + sage: Sym = SymmetricFunctions(ZZ) + sage: s = Sym.s() + sage: all( s[u].antipode().antipode() == s[u] for u in Partitions(4) ) + True + + The antipode is an algebra homomorphism:: + + sage: Sym = SymmetricFunctions(FiniteField(23)) + sage: h = Sym.h() + sage: all( all( (s[u] * s[v]).antipode() == s[u].antipode() * s[v].antipode() + ....: for u in Partitions(3) ) + ....: for v in Partitions(3) ) + True + + TESTS: + + Everything works over `\ZZ`:: + + sage: Sym = SymmetricFunctions(ZZ) + sage: p = Sym.p() + sage: s = Sym.s() + sage: e = Sym.e() + sage: h = Sym.h() + sage: (h([]) + h([1])).antipode() # indirect doctest + h[] - h[1] + sage: (s([]) + s([1]) + s[2]).antipode() + s[] - s[1] + s[1, 1] + sage: (p([2]) + p([3])).antipode() + -p[2] - p[3] + sage: (e([2]) + e([3])).antipode() + e[1, 1] - e[1, 1, 1] - e[2] + 2*e[2, 1] - e[3] + """ + return self.degree_negation(element.omega()) + + def counit(self, element): + r""" + Return the counit of ``element``. + + The counit is the constant term of ``element``. + + INPUT: + + - ``element`` -- element in a basis of the ring of symmetric functions + + EXAMPLES:: + + sage: Sym = SymmetricFunctions(QQ) + sage: m = Sym.monomial() + sage: f = 2*m[2,1] + 3*m[[]] + sage: f.counit() + 3 + """ + return element.degree_zero_coefficient() + + def degree_negation(self, element): + r""" + Return the image of ``element`` under the degree negation + automorphism of the ring of symmetric functions. + + The degree negation is the automorphism which scales every + homogeneous element of degree `k` by `(-1)^k` (for all `k`). + + INPUT: + + - ``element`` -- symmetric function written in ``self`` + + EXAMPLES:: + + sage: Sym = SymmetricFunctions(ZZ) + sage: m = Sym.monomial() + sage: f = 2*m[2,1] + 4*m[1,1] - 5*m[1] - 3*m[[]] + sage: m.degree_negation(f) + -3*m[] + 5*m[1] + 4*m[1, 1] - 2*m[2, 1] + + TESTS: + + Using :meth:`degree_negation` on an element of a different + basis works correctly:: + + sage: e = Sym.elementary() + sage: m.degree_negation(e[3]) + -m[1, 1, 1] + sage: m.degree_negation(m(e[3])) + -m[1, 1, 1] + """ + return self.sum_of_terms([ (lam, (-1)**(sum(lam)%2) * a) + for lam, a in self(element) ]) + + class ElementMethods: def degree_negation(self): r""" Return the image of ``self`` under the degree negation @@ -1427,10 +1519,6 @@ def degree_zero_coefficient(self): r""" Returns the degree zero coefficient of ``self``. - INPUT: - - - ``self`` -- an element of the symmetric functions - EXAMPLES:: sage: Sym = SymmetricFunctions(QQ) @@ -1441,6 +1529,12 @@ def degree_zero_coefficient(self): """ return self.coefficient([]) +#SymmetricFunctionsBases.Filtered = FilteredSymmetricFunctionsBases +#SymmetricFunctionsBases.Graded = GradedSymmetricFunctionsBases + +##################################################################### +## ABC for bases of the symmetric functions + class SymmetricFunctionAlgebra_generic(CombinatorialFreeModule): r""" Abstract base class for symmetric function algebras. @@ -1457,7 +1551,7 @@ class SymmetricFunctionAlgebra_generic(CombinatorialFreeModule): sage: s(m([2,1])) -2*s[1, 1, 1] + s[2, 1] """ - def __init__(self, Sym, basis_name = None, prefix = None): + def __init__(self, Sym, basis_name=None, prefix=None, graded=True): r""" Initializes the symmetric function algebra. @@ -1466,6 +1560,8 @@ def __init__(self, Sym, basis_name = None, prefix = None): - ``Sym`` -- the ring of symmetric functions - ``basis_name`` -- name of basis (default: ``None``) - ``prefix`` -- prefix used to display basis + - ``graded`` -- (default: ``True``) if ``True``, then the basis is + considered to be graded, otherwise the basis is filtered TESTS:: @@ -1489,9 +1585,13 @@ def __init__(self, Sym, basis_name = None, prefix = None): if prefix is not None: self._prefix = prefix self._sym = Sym + if graded: + cat = GradedSymmetricFunctionsBases(Sym) + else: # Right now, there are no non-filted bases + cat = FilteredSymmetricFunctionsBases(Sym) CombinatorialFreeModule.__init__(self, Sym.base_ring(), _Partitions, - category = SymmetricFunctionsBases(Sym), - bracket = "", prefix = prefix) + category=cat, + bracket="", prefix=prefix) _print_style = 'lex' @@ -2095,8 +2195,8 @@ def transition_matrix(self, basis, n): sage: a = s([3,1])+5*s([1,1,1,1])-s([4]) sage: a 5*s[1, 1, 1, 1] + s[3, 1] - s[4] - sage: mon = a.support() - sage: coeffs = a.coefficients() + sage: mon = sorted(a.support()) + sage: coeffs = [a[i] for i in mon] sage: coeffs [5, 1, -1] sage: mon @@ -5213,6 +5313,99 @@ def hl_creation_operator(self, nu, t = None): for mu in Partitions(d+1, max_length=len(nu)) ) ) + def eval_at_permutation_roots(self, rho): + r""" + Evaluate at eigenvalues of a permutation matrix. + + Evaluate a symmetric function at the eigenvalues of a permutation + matrix whose cycle structure is ``rho``. This computation is + computed by coercing to the power sum basis where the value may + be computed on the generators. + + This function evaluates an element at the roots of unity + + .. MATH:: + + \Xi_{\rho_1},\Xi_{\rho_2},\ldots,\Xi_{\rho_\ell} + + where + + .. MATH:: + + \Xi_{m} = 1,\zeta_m,\zeta_m^2,\ldots,\zeta_m^{m-1} + + and `\zeta_m` is an `m` root of unity. + These roots of unity represent the eigenvalues of permutation + matrix with cycle structure `\rho`. + + INPUT: + + - ``rho`` -- a partition or a list of non-negative integers + + OUTPUT: + + - an element of the base ring + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).s() + sage: s([3,3]).eval_at_permutation_roots([6]) + 0 + sage: s([3,3]).eval_at_permutation_roots([3]) + 1 + sage: s([3,3]).eval_at_permutation_roots([1]) + 0 + sage: s([3,3]).eval_at_permutation_roots([3,3]) + 4 + sage: s([3,3]).eval_at_permutation_roots([1,1,1,1,1]) + 175 + sage: (s[1]+s[2]+s[3]).eval_at_permutation_roots([3,2]) + 2 + """ + p = self.parent().symmetric_function_ring().p() + return p(self).eval_at_permutation_roots(rho) + + def character_to_frobenius_image(self, n): + r""" + Interpret ``self`` as a `Gl_n` character and then take the Frobenius + image of this character of the permutation matrices `S_n` which + naturally sit inside of `Gl_n`. + + To know the value of this character at a permutation of cycle structure + `\rho` the symmetric function ``self`` is evaluated at the + eigenvalues of of a permutation of cycle structure `\rho`. The + Frobenius image is then defined as + `\sum_{\rho \vdash n} f[ \Xi_\rho ] p_\rho/z_\rho`. + + .. SEEALSO:: + :meth:`eval_at_permutation_roots` + + INPUT: + + - ``n`` -- a non-negative integer to interpret ``self`` as + a character of `Gl_n` + + OUTPUT: + + - a symmetric function of degree ``n`` + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).s() + sage: s([1,1]).character_to_frobenius_image(5) + s[3, 1, 1] + s[4, 1] + sage: s([2,1]).character_to_frobenius_image(5) + s[2, 2, 1] + 2*s[3, 1, 1] + 2*s[3, 2] + 3*s[4, 1] + s[5] + sage: s([2,2,2]).character_to_frobenius_image(3) + s[3] + sage: s([2,2,2]).character_to_frobenius_image(4) + s[2, 2] + 2*s[3, 1] + 2*s[4] + sage: s([2,2,2]).character_to_frobenius_image(5) + 2*s[2, 2, 1] + s[3, 1, 1] + 4*s[3, 2] + 3*s[4, 1] + 2*s[5] + """ + p = self.parent().symmetric_function_ring().p() + return self.parent()(p.sum(self.eval_at_permutation_roots(rho) \ + *p(rho)/rho.centralizer_size() for rho in Partitions(n))) SymmetricFunctionAlgebra_generic.Element = SymmetricFunctionAlgebra_generic_Element diff --git a/src/sage/combinat/sf/symplectic.py b/src/sage/combinat/sf/symplectic.py new file mode 100644 index 00000000000..2fcd5c09d12 --- /dev/null +++ b/src/sage/combinat/sf/symplectic.py @@ -0,0 +1,252 @@ +""" +Symplectic Symmetric Functions + +AUTHORS: + +- Travis Scrimshaw (2013-11-10): Initial version +""" +#***************************************************************************** +# Copyright (C) 2013 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# The full text of the GPL is available at: +# +# http://www.gnu.org/licenses/ +#***************************************************************************** +import sfa +import sage.libs.lrcalc.lrcalc as lrcalc +from sage.combinat.partition import Partitions +from sage.misc.cachefunc import cached_method +from sage.rings.all import ZZ, QQ, Integer +from sage.matrix.all import matrix + +class SymmetricFunctionAlgebra_symplectic(sfa.SymmetricFunctionAlgebra_generic): + r""" + The symplectic symmetric function basis (or symplectic basis, to be short). + + The symplectic basis `\{ sp_{\lambda} \}` where `\lambda` is taken over + all partitions is defined by the following change of basis with the + Schur functions: + + .. MATH:: + + s_{\lambda} = \sum_{\mu} \left( \sum_{\nu \in V} c^{\lambda}_{\mu\nu} + \right) sp_{\mu} + + where `V` is the set of all partitions with even-height columns and + `c^{\lambda}_{\mu\nu}` is the usual Littlewood-Richardson (LR) + coefficients. By the properties of LR coefficients, this can be shown to + be a upper unitriangular change of basis. + + .. NOTE:: + + This is only a filtered basis, not a `\ZZ`-graded basis. However this + does respect the induced `(\ZZ/2\ZZ)`-grading. + + INPUT: + + - ``Sym`` -- an instance of the ring of the symmetric functions + + REFERENCES: + + .. [ChariKleber2000] Vyjayanthi Chari and Michael Kleber. + *Symmetric functions and representations of quantum affine algebras*. + :arXiv:`math/0011161v1` + + .. [KoikeTerada1987] K. Koike, I. Terada, *Young-diagrammatic methods for + the representation theory of the classical groups of type Bn, Cn, Dn*. + J. Algebra 107 (1987), no. 2, 466-511. + + .. [ShimozonoZabrocki2006] Mark Shimozono and Mike Zabrocki. + *Deformed universal characters for classical and affine algebras*. + Journal of Algebra, **299** (2006). :arxiv:`math/0404288`. + + EXAMPLES: + + Here are the first few symplectic symmetric functions, in various bases:: + + sage: Sym = SymmetricFunctions(QQ) + sage: sp = Sym.sp() + sage: e = Sym.e() + sage: h = Sym.h() + sage: p = Sym.p() + sage: s = Sym.s() + sage: m = Sym.m() + + sage: p(sp([1])) + p[1] + sage: m(sp([1])) + m[1] + sage: e(sp([1])) + e[1] + sage: h(sp([1])) + h[1] + sage: s(sp([1])) + s[1] + + sage: p(sp([2])) + 1/2*p[1, 1] + 1/2*p[2] + sage: m(sp([2])) + m[1, 1] + m[2] + sage: e(sp([2])) + e[1, 1] - e[2] + sage: h(sp([2])) + h[2] + sage: s(sp([2])) + s[2] + + sage: p(sp([3])) + 1/6*p[1, 1, 1] + 1/2*p[2, 1] + 1/3*p[3] + sage: m(sp([3])) + m[1, 1, 1] + m[2, 1] + m[3] + sage: e(sp([3])) + e[1, 1, 1] - 2*e[2, 1] + e[3] + sage: h(sp([3])) + h[3] + sage: s(sp([3])) + s[3] + + sage: Sym = SymmetricFunctions(ZZ) + sage: sp = Sym.sp() + sage: e = Sym.e() + sage: h = Sym.h() + sage: s = Sym.s() + sage: m = Sym.m() + sage: p = Sym.p() + sage: m(sp([4])) + m[1, 1, 1, 1] + m[2, 1, 1] + m[2, 2] + m[3, 1] + m[4] + sage: e(sp([4])) + e[1, 1, 1, 1] - 3*e[2, 1, 1] + e[2, 2] + 2*e[3, 1] - e[4] + sage: h(sp([4])) + h[4] + sage: s(sp([4])) + s[4] + + Some examples of conversions the other way:: + + sage: sp(h[3]) + sp[3] + sage: sp(e[3]) + sp[1] + sp[1, 1, 1] + sage: sp(m[2,1]) + -sp[1] - 2*sp[1, 1, 1] + sp[2, 1] + sage: sp(p[3]) + sp[1, 1, 1] - sp[2, 1] + sp[3] + + Some multiplication:: + + sage: sp([2]) * sp([1,1]) + sp[1, 1] + sp[2] + sp[2, 1, 1] + sp[3, 1] + sage: sp([2,1,1]) * sp([2]) + sp[1, 1] + sp[1, 1, 1, 1] + 2*sp[2, 1, 1] + sp[2, 2] + sp[2, 2, 1, 1] + + sp[3, 1] + sp[3, 1, 1, 1] + sp[3, 2, 1] + sp[4, 1, 1] + sage: sp([1,1]) * sp([2,1]) + sp[1] + sp[1, 1, 1] + 2*sp[2, 1] + sp[2, 1, 1, 1] + sp[2, 2, 1] + + sp[3] + sp[3, 1, 1] + sp[3, 2] + + Examples of the Hopf algebra structure:: + + sage: sp([1]).antipode() + -sp[1] + sage: sp([2]).antipode() + sp[] + sp[1, 1] + sage: sp([1]).coproduct() + sp[] # sp[1] + sp[1] # sp[] + sage: sp([2]).coproduct() + sp[] # sp[2] + sp[1] # sp[1] + sp[2] # sp[] + sage: sp([1]).counit() + 0 + sage: sp.one().counit() + 1 + """ + def __init__(self, Sym): + """ + Initialize ``self``. + + TESTS:: + + sage: sp = SymmetricFunctions(QQ).sp() + sage: TestSuite(sp).run() + """ + sfa.SymmetricFunctionAlgebra_generic.__init__(self, Sym, "symplectic", + 'sp', graded=False) + + # We make a strong reference since we use it for our computations + # and so we can define the coercion below (only codomains have + # strong references) + self._s = Sym.schur() + + # Setup the coercions + M = self._s.module_morphism(self._s_to_sp_on_basis, codomain=self, + triangular='upper', unitriangular=True) + M.register_as_coercion() + Mi = self.module_morphism(self._sp_to_s_on_basis, codomain=self._s, + triangular='upper', unitriangular=True) + Mi.register_as_coercion() + + @cached_method + def _sp_to_s_on_basis(self, lam): + r""" + Return the symplectic symmetric function ``sp[lam]`` expanded in + the Schur basis, where ``lam`` is a partition. + + TESTS: + + Check that this is the inverse:: + + sage: sp = SymmetricFunctions(QQ).sp() + sage: s = SymmetricFunctions(QQ).s() + sage: all(sp(s(sp[la])) == sp[la] for i in range(5) for la in Partitions(i)) + True + sage: all(s(sp(s[la])) == s[la] for i in range(5) for la in Partitions(i)) + True + """ + R = self.base_ring() + n = sum(lam) + return self._s._from_dict({ mu: R.sum( (-1)**j * lrcalc.lrcoef_unsafe(lam, mu, nu) + for nu in Partitions(2*j) + if all(nu.leg_length(i,i) == nu.arm_length(i,i)+1 + for i in range(nu.frobenius_rank())) + ) + for j in range(n//2+1) # // 2 for horizontal dominoes + for mu in Partitions(n-2*j) }) + + @cached_method + def _s_to_sp_on_basis(self, lam): + r""" + Return the Schur symmetric function ``s[lam]`` expanded in + the symplectic basis, where ``lam`` is a partition. + + INPUT: + + - ``lam`` -- a partition + + OUTPUT: + + - the expansion of ``s[lam]`` in the symplectic basis ``self`` + + EXAMPLES:: + + sage: Sym = SymmetricFunctions(QQ) + sage: s = Sym.schur() + sage: sp = Sym.symplectic() + sage: sp._s_to_sp_on_basis(Partition([])) + sp[] + sage: sp._s_to_sp_on_basis(Partition([4,2,1])) + sp[2, 1] + sp[3] + sp[3, 1, 1] + sp[3, 2] + sp[4, 1] + sp[4, 2, 1] + sage: s(sp._s_to_sp_on_basis(Partition([3,1]))) == s[3,1] + True + """ + R = self.base_ring() + n = sum(lam) + return self._from_dict({ mu: R.sum( lrcalc.lrcoef_unsafe(lam, mu, sum([[x,x] for x in nu], [])) + for nu in Partitions(j) ) + for j in range(n//2+1) # // 2 for vertical dominoes + for mu in Partitions(n-2*j) }) + diff --git a/src/sage/combinat/sidon_sets.py b/src/sage/combinat/sidon_sets.py index 47a0055ec81..d595cb2b554 100644 --- a/src/sage/combinat/sidon_sets.py +++ b/src/sage/combinat/sidon_sets.py @@ -46,12 +46,12 @@ def sidon_sets(N, g = 1): sage: S.cardinality() 8 sage: S.category() - Category of sets + Category of finite sets sage: sid = S.an_element() sage: sid {2} sage: sid.category() - Category of sets + Category of finite sets TESTS:: diff --git a/src/sage/combinat/similarity_class_type.py b/src/sage/combinat/similarity_class_type.py index 36f5bc22194..2231480778b 100644 --- a/src/sage/combinat/similarity_class_type.py +++ b/src/sage/combinat/similarity_class_type.py @@ -176,7 +176,7 @@ class type, it is also possible to compute the number of classes of that type #***************************************************************************** from operator import mul -from itertools import chain +from itertools import chain, product from sage.misc.all import prod from sage.functions.all import factorial from sage.rings.arith import moebius @@ -189,7 +189,6 @@ class type, it is also possible to compute the number of classes of that type from sage.combinat.partition import Partitions, Partition from sage.rings.all import ZZ, QQ, FractionField, divisors from sage.misc.cachefunc import cached_in_parent_method, cached_function -from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.misc import IterableFunctionCall from functools import reduce @@ -1555,7 +1554,7 @@ def ext_orbit_centralizers(input_data, q = None, selftranspose = False): yield (item[0].substitute(q = q**tau.degree()), item[1].substitute(q = q**tau.degree())) elif case == 'sim': tau = data - for item in CartesianProduct(*[IterableFunctionCall(lambda x: ext_orbit_centralizers(x, q = q, selftranspose = selftranspose), PT) for PT in tau]): + for item in product(*[IterableFunctionCall(lambda x: ext_orbit_centralizers(x, q = q, selftranspose = selftranspose), PT) for PT in tau]): size = prod([list(entry)[0] for entry in item]) freq = prod([list(entry)[1] for entry in item]) yield(size, freq) diff --git a/src/sage/combinat/species/composition_species.py b/src/sage/combinat/species/composition_species.py index cbc23c9e43f..88e5544305f 100644 --- a/src/sage/combinat/species/composition_species.py +++ b/src/sage/combinat/species/composition_species.py @@ -42,7 +42,7 @@ def __repr__(self): sage: E = species.SetSpecies(); C = species.CycleSpecies() sage: L = E(C) sage: L.structures(['a','b','c']).random_element() - F-structure: {{'a', 'b', 'c'}}; G-structures: [('a', 'b', 'c')] + F-structure: {{'a', 'b', 'c'}}; G-structures: (('a', 'b', 'c'),) """ f, gs = self._list return "F-structure: %s; G-structures: %s"%(repr(f), repr(gs)) @@ -56,9 +56,9 @@ def transport(self, perm): sage: L = E(C) sage: S = L.structures(['a','b','c']).list() sage: a = S[2]; a - F-structure: {{'a', 'c'}, {'b'}}; G-structures: [('a', 'c'), ('b')] + F-structure: {{'a', 'c'}, {'b'}}; G-structures: (('a', 'c'), ('b')) sage: a.transport(p) - F-structure: {{'a', 'b'}, {'c'}}; G-structures: [('a', 'c'), ('b')] + F-structure: {{'a', 'b'}, {'c'}}; G-structures: (('a', 'c'), ('b')) """ f, gs = self._list pi = self._partition.transport(perm) @@ -75,7 +75,7 @@ def change_labels(self, labels): sage: L = E(C) sage: S = L.structures(['a','b','c']).list() sage: a = S[2]; a - F-structure: {{'a', 'c'}, {'b'}}; G-structures: [('a', 'c'), ('b')] + F-structure: {{'a', 'c'}, {'b'}}; G-structures: (('a', 'c'), ('b')) sage: a.change_labels([1,2,3]) F-structure: {{1, 3}, {2}}; G-structures: [(1, 3), (2)] """ @@ -126,12 +126,12 @@ def _structures(self, structure_class, labels): sage: E = species.SetSpecies(); C = species.CycleSpecies() sage: L = E(C) sage: L.structures(['a','b','c']).list() - [F-structure: {{'a', 'b', 'c'}}; G-structures: [('a', 'b', 'c')], - F-structure: {{'a', 'b', 'c'}}; G-structures: [('a', 'c', 'b')], - F-structure: {{'a', 'c'}, {'b'}}; G-structures: [('a', 'c'), ('b')], - F-structure: {{'a', 'b'}, {'c'}}; G-structures: [('a', 'b'), ('c')], - F-structure: {{'b', 'c'}, {'a'}}; G-structures: [('b', 'c'), ('a')], - F-structure: {{'a'}, {'b'}, {'c'}}; G-structures: [('a'), ('b'), ('c')]] + [F-structure: {{'a', 'b', 'c'}}; G-structures: (('a', 'b', 'c'),), + F-structure: {{'a', 'b', 'c'}}; G-structures: (('a', 'c', 'b'),), + F-structure: {{'a', 'c'}, {'b'}}; G-structures: (('a', 'c'), ('b')), + F-structure: {{'a', 'b'}, {'c'}}; G-structures: (('a', 'b'), ('c')), + F-structure: {{'b', 'c'}, {'a'}}; G-structures: (('b', 'c'), ('a')), + F-structure: {{'a'}, {'b'}, {'c'}}; G-structures: (('a'), ('b'), ('c'))] TESTS:: @@ -152,16 +152,16 @@ def _structures(self, structure_class, labels): sage: [g._list for g in gs] [[1, 2], [1]] """ - from sage.combinat.cartesian_product import CartesianProduct + from itertools import product P = PartitionSpecies() for pi in P.structures(labels): #The labels of the G-structures will be just be the things #in labels - gs = CartesianProduct(*[self._G.structures(part.labels()) for part in pi]) + gs = product(*[self._G.structures(part.labels()) for part in pi]) #The labels of the F-structure will be set objects fs = self._F.structures(list(pi)) - for f, gg in CartesianProduct(fs, gs): + for f, gg in product(fs, gs): yield structure_class(self, labels, pi, f, gg) def _isotypes(self, structure_class, labels): diff --git a/src/sage/combinat/split_nk.py b/src/sage/combinat/split_nk.py deleted file mode 100644 index 93ae9f754f7..00000000000 --- a/src/sage/combinat/split_nk.py +++ /dev/null @@ -1,61 +0,0 @@ -""" -Derecated splits - -Authors: - -- Mike Hansen (2007): original version - -- Vincent Delecroix (2014): deprecation -""" -#***************************************************************************** -# Copyright (C) 2007 Mike Hansen , -# -# 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/ -#***************************************************************************** - -def SplitNK(n, k): - """ - Returns the combinatorial class of splits of a the set range(n) - into a set of size k and a set of size n-k. - - This was deprecated in :trac:`10534`. Use instead - :class:`OrderedSetPartitions`. - - EXAMPLES:: - - sage: from sage.combinat.split_nk import SplitNK - sage: S = SplitNK(5,2) - doctest:...: DeprecationWarning: SplitNk is deprecated and will be - removed. Use OrderedSetPartitions instead. - See http://trac.sagemath.org/10534 for details. - sage: S - Ordered set partitions of {0, 1, 2, 3, 4} into parts of size [2, 3] - sage: S.first() - [{0, 1}, {2, 3, 4}] - sage: S.last() - [{3, 4}, {0, 1, 2}] - sage: S.list() - [[{0, 1}, {2, 3, 4}], - [{0, 2}, {1, 3, 4}], - [{0, 3}, {1, 2, 4}], - [{0, 4}, {1, 2, 3}], - [{1, 2}, {0, 3, 4}], - [{1, 3}, {0, 2, 4}], - [{1, 4}, {0, 2, 3}], - [{2, 3}, {0, 1, 4}], - [{2, 4}, {0, 1, 3}], - [{3, 4}, {0, 1, 2}]] - """ - from sage.misc.superseded import deprecation - from sage.combinat.set_partition_ordered import OrderedSetPartitions - deprecation(10534, "SplitNk is deprecated and will be removed. Use OrderedSetPartitions instead.") - return OrderedSetPartitions(range(n), [k, n-k]) diff --git a/src/sage/combinat/subset.py b/src/sage/combinat/subset.py index 39aff81e561..7e7cb32b201 100644 --- a/src/sage/combinat/subset.py +++ b/src/sage/combinat/subset.py @@ -41,7 +41,7 @@ from sage.rings.arith import binomial from sage.rings.integer_ring import ZZ from sage.rings.integer import Integer -import choose_nk +import combination ZZ_0 = ZZ.zero() @@ -438,7 +438,7 @@ def rank(self, sub): n = self._s.cardinality() r = sum(binomial(n,i) for i in xrange(len(index_list))) - return r + choose_nk.rank(index_list,n) + return r + combination.rank(index_list,n) def unrank(self, r): """ @@ -467,7 +467,7 @@ def unrank(self, r): r -= bin k += 1 bin = binomial(n,k) - return self.element_class([self._s.unrank(i) for i in choose_nk.from_rank(r, n, k)]) + return self.element_class([self._s.unrank(i) for i in combination.from_rank(r, n, k)]) def __call__(self, el): r""" @@ -769,7 +769,7 @@ def rank(self, sub): raise ValueError("{} is not a subset of length {} of {}".format( sub, self._k, self._s)) - return choose_nk.rank(index_list, n) + return combination.rank(index_list, n) def unrank(self, r): """ @@ -792,7 +792,7 @@ def unrank(self, r): if self._k > n or r >= self.cardinality() or r < 0: raise IndexError("index out of range") else: - return self.element_class([lset[i] for i in choose_nk.from_rank(r, n, self._k)]) + return self.element_class([lset[i] for i in combination.from_rank(r, n, self._k)]) def an_element(self): """ @@ -1346,7 +1346,7 @@ def unrank(self, r): r -= binom k += 1 binom = binomial(n,k) - C = choose_nk.from_rank(r, n, k) + C = combination.from_rank(r, n, k) return self.element_class(sorted([self._s.unrank(i) for i in C])) def _an_element_(self): diff --git a/src/sage/combinat/tutorial.py b/src/sage/combinat/tutorial.py index 40156bb03a9..03b7058fc92 100644 --- a/src/sage/combinat/tutorial.py +++ b/src/sage/combinat/tutorial.py @@ -61,8 +61,8 @@ sage: Suits = Set(["Hearts", "Diamonds", "Spades", "Clubs"]) sage: Values = Set([2, 3, 4, 5, 6, 7, 8, 9, 10, - ... "Jack", "Queen", "King", "Ace"]) - sage: Cards = CartesianProduct(Values, Suits) + ....: "Jack", "Queen", "King", "Ace"]) + sage: Cards = cartesian_product([Values, Suits]) There are `4` suits and `13` possible values, and therefore `4\times 13=52` cards in the poker deck:: @@ -77,21 +77,7 @@ Draw a card at random:: sage: Cards.random_element() # random - [6, 'Clubs'] - -A small technical digression is necessary here. The elements of a -Cartesian product are returned in the form of lists:: - - sage: type(Cards.random_element()) - - -A ``Python`` list not being immutable, it cannot be an element of a set, which -would cause us a problem later. We will therefore redefine our -Cartesian product so that its elements are represented by tuples:: - - sage: Cards = CartesianProduct(Values, Suits).map(tuple) - sage: Cards.an_element() - ('King', 'Hearts') + (6, 'Clubs') Now we can define a set of cards:: @@ -136,7 +122,7 @@ We will construct the set of all flushes, so as to determine how many there are:: - sage: Flushes = CartesianProduct(Subsets(Values, 5), Suits) + sage: Flushes = cartesian_product([Subsets(Values, 5), Suits]) sage: Flushes.cardinality() 5148 @@ -157,7 +143,7 @@ function tests whether a given hand is a flush or not:: sage: def is_flush(hand): - ... return len(set(suit for (val, suit) in hand)) == 1 + ....: return len(set(suit for (val, suit) in hand)) == 1 We now draw 10000 hands at random, and count the number of flushes obtained (this takes about 10 seconds):: @@ -165,9 +151,9 @@ sage: n = 10000 sage: nflush = 0 sage: for i in range(n): # long time - ... hand = Hands.random_element() - ... if is_flush(hand): - ... nflush += 1 + ....: hand = Hands.random_element() + ....: if is_flush(hand): + ....: nflush += 1 sage: print n, nflush # random 10000 18 @@ -647,7 +633,7 @@ {2, 4} but this should be used with care because some sets have a -natural indexing other than by `(0,\dots)`. +natural indexing other than by `(0, 1, \dots)`. Conversely, one can calculate the position of an object in this order:: @@ -668,7 +654,7 @@ which is roughly `2\cdot 10^{19728}`:: - sage: n.ndigits() # long time + sage: n.ndigits() 19729 or ask for its `237102124`-th element:: @@ -1029,7 +1015,7 @@ comprehensions provide a much pleasanter syntax:: sage: for s in Subsets(3): - ... print s + ....: print s {} {1} {2} @@ -1095,7 +1081,7 @@ sage: def mersenne(p): return 2^p -1 sage: [ is_prime(p) - ... for p in range(1000) if is_prime(mersenne(p)) ] + ....: for p in range(1000) if is_prime(mersenne(p)) ] [True, True, True, True, True, True, True, True, True, True, True, True, True, True] @@ -1107,24 +1093,24 @@ difference in the length of the calculations:: sage: all( is_prime(mersenne(p)) - ... for p in range(1000) if is_prime(p) ) + ....: for p in range(1000) if is_prime(p) ) False sage: all( [ is_prime(mersenne(p)) - ... for p in range(1000) if is_prime(p)] ) + ....: for p in range(1000) if is_prime(p)] ) False We now try to find the smallest counter-example. In order to do this, we use the ``Sage`` function ``exists``:: sage: exists( (p for p in range(1000) if is_prime(p)), - ... lambda p: not is_prime(mersenne(p)) ) + ....: lambda p: not is_prime(mersenne(p)) ) (True, 11) Alternatively, we could construct an interator on the counter-examples:: sage: counter_examples = \ - ... (p for p in range(1000) - ... if is_prime(p) and not is_prime(mersenne(p))) + ....: (p for p in range(1000) + ....: if is_prime(p) and not is_prime(mersenne(p))) sage: next(counter_examples) 11 sage: next(counter_examples) @@ -1138,10 +1124,10 @@ sage: cubes = [t**3 for t in range(-999,1000)] sage: exists([(x,y) for x in cubes for y in cubes], # long time (3s, 2012) - ... lambda (x,y): x+y == 218) + ....: lambda (x,y): x+y == 218) (True, (-125, 343)) sage: exists(((x,y) for x in cubes for y in cubes), # long time (2s, 2012) - ... lambda (x,y): x+y == 218) + ....: lambda (x,y): x+y == 218) (True, (-125, 343)) Which of the last two is more economical in terms of time? In terms @@ -1236,7 +1222,7 @@ :: sage: counter_examples = (p for p in Primes() - ... if not is_prime(mersenne(p))) + ....: if not is_prime(mersenne(p))) sage: for p in counter_examples: print p # not tested 11 23 @@ -1278,23 +1264,23 @@ apply a function to all the elements:: sage: list(itertools.imap(lambda z: z.cycle_type(), - ... Permutations(3))) + ....: Permutations(3))) [[1, 1, 1], [2, 1], [2, 1], [3], [3], [2, 1]] or select the elements satisfying a certain condition:: sage: list(itertools.ifilter(lambda z: z.has_pattern([1,2]), - ... Permutations(3))) + ....: Permutations(3))) [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2]] In all these situations, ``attrcall`` can be an advantageous alternative to creating an anonymous function:: sage: list(itertools.imap(lambda z: z.cycle_type(), - ... Permutations(3))) + ....: Permutations(3))) [[1, 1, 1], [2, 1], [2, 1], [3], [3], [2, 1]] sage: list(itertools.imap(attrcall("cycle_type"), - ... Permutations(3))) + ....: Permutations(3))) [[1, 1, 1], [2, 1], [2, 1], [3], [3], [2, 1]] Implementation of new interators @@ -1304,8 +1290,8 @@ instead of ``return`` in a function:: sage: def f(n): - ... for i in range(n): - ... yield i + ....: for i in range(n): + ....: yield i After the ``yield``, execution is not halted, but only suspended, ready to be continued from the same point. The result of the function is @@ -1338,12 +1324,12 @@ generate all words of a given length on a given alphabet:: sage: def words(alphabet,l): - ... if l == 0: - ... yield [] - ... else: - ... for word in words(alphabet, l-1): - ... for l in alphabet: - ... yield word + [l] + ....: if l == 0: + ....: yield [] + ....: else: + ....: for word in words(alphabet, l-1): + ....: for l in alphabet: + ....: yield word + [l] sage: [ w for w in words(['a','b'], 3) ] [['a', 'a', 'a'], ['a', 'a', 'b'], ['a', 'b', 'a'], ['a', 'b', 'b'], ['b', 'a', 'a'], ['b', 'a', 'b'], @@ -1367,13 +1353,13 @@ `w_1` and `w_2` are Dyck words:: sage: def dyck_words(l): - ... if l==0: - ... yield '' - ... else: - ... for k in range(l): - ... for w1 in dyck_words(k): - ... for w2 in dyck_words(l-k-1): - ... yield '('+w1+')'+w2 + ....: if l==0: + ....: yield '' + ....: else: + ....: for k in range(l): + ....: for w1 in dyck_words(k): + ....: for w2 in dyck_words(l-k-1): + ....: yield '('+w1+')'+w2 Here are all the Dyck words of length `4`:: @@ -1423,27 +1409,29 @@ Consider a large Cartesian product:: - sage: C = CartesianProduct(Compositions(8), Permutations(20)); C - Cartesian product of Compositions of 8, Standard permutations of 20 + sage: C = cartesian_product([Compositions(8), Permutations(20)]); C + The cartesian product of (Compositions of 8, Standard permutations of 20) sage: C.cardinality() 311411457046609920000 -Clearly, it is impractical to construct the list of all the elements of -this Cartesian product. For the moment, the contruction -``CartesianProduct`` ignores the algebraic properties of its arguments. -This is partially corrected in Sage 4.4.4, with the construction -``cartesian_product``. Eventually, these two constructions will be merged -and, in the following example, `H` will be equipped with the -usual combinatorial operations and also its structure as a product -group:: +Clearly, it is impractical to construct the list of all the elements of this +Cartesian product! And, in the following example, `H` is equipped with the +usual combinatorial operations and also its structure as a product group:: sage: G = DihedralGroup(4) sage: H = cartesian_product([G,G]) + sage: H in Groups() + True + sage: t = H.an_element() + sage: t + ((1,2,3,4), (1,2,3,4)) + sage: t*t + ((1,3)(2,4), (1,3)(2,4)) We now construct the union of two existing disjoint sets:: sage: C = DisjointUnionEnumeratedSets( - ... [ Compositions(4), Permutations(3)] ) + ....: [ Compositions(4), Permutations(3)] ) sage: C Disjoint union of Family (Compositions of 4, Standard permutations of 3) @@ -1482,7 +1470,7 @@ necessary to interrupt it at some point:: sage: for p in U: # not tested - ... print p + ....: print p [] [1] [1, 2] @@ -1539,21 +1527,21 @@ and length `3`, with parts bounded below by `2`, `4` and `2` respectively:: - sage: IntegerVectors(10, 3, min_part = 2, max_part = 5, - ... inner = [2, 4, 2]).list() + sage: IntegerVectors(10, 3, min_part=2, max_part=5, + ....: inner=[2, 4, 2]).list() [[4, 4, 2], [3, 5, 2], [3, 4, 3], [2, 5, 3], [2, 4, 4]] The compositions of `5` with each part at most `3`, and with length `2` or `3`:: - sage: Compositions(5, max_part = 3, - ... min_length = 2, max_length = 3).list() + sage: Compositions(5, max_part=3, + ....: min_length=2, max_length=3).list() [[3, 2], [3, 1, 1], [2, 3], [2, 2, 1], [2, 1, 2], [1, 3, 1], [1, 2, 2], [1, 1, 3]] The strictly decreasing partitions of `5`:: - sage: Partitions(5, max_slope = -1).list() + sage: Partitions(5, max_slope=-1).list() [[5], [4, 1], [3, 2]] These sets share the same underlying algorithmic structure, implemented @@ -1565,16 +1553,16 @@ examples:: sage: IntegerListsLex(10, length=3, - ... min_part = 2, max_part = 5, - ... floor = [2, 4, 2]).list() + ....: min_part=2, max_part=5, + ....: floor=[2, 4, 2]).list() [[4, 4, 2], [3, 5, 2], [3, 4, 3], [2, 5, 3], [2, 4, 4]] - sage: IntegerListsLex(5, min_part = 1, max_part = 3, - ... min_length = 2, max_length = 3).list() + sage: IntegerListsLex(5, min_part=1, max_part=3, + ....: min_length=2, max_length=3).list() [[3, 2], [3, 1, 1], [2, 3], [2, 2, 1], [2, 1, 2], [1, 3, 1], [1, 2, 2], [1, 1, 3]] - sage: IntegerListsLex(5, min_part = 1, max_slope = -1).list() + sage: IntegerListsLex(5, min_part=1, max_slope=-1).list() [[5], [4, 1], [3, 2]] sage: list(Compositions(5, max_length=2)) @@ -1842,7 +1830,7 @@ selecting only the children which are planar:: sage: [len(list(graphs(n, property = lambda G: G.is_planar()))) - ... for n in range(7)] + ....: for n in range(7)] [1, 1, 2, 4, 11, 33, 142] In a similar fashion, one can generate any family of graphs closed diff --git a/src/sage/combinat/words/words.py b/src/sage/combinat/words/words.py index e2e272dd347..a1bb2e3be03 100644 --- a/src/sage/combinat/words/words.py +++ b/src/sage/combinat/words/words.py @@ -1376,11 +1376,10 @@ def iter_morphisms(self, arg=None, codomain=None, min_length=1): # create an iterable of compositions (all "compositions" if arg is # None, or [arg] otherwise) if arg is None: - # TODO in #17927: use IntegerVectors(length=n, min_part=min_length) - from sage.combinat.integer_list import IntegerListsNN + from sage.combinat.integer_lists.nn import IntegerListsNN compositions = IntegerListsNN(length=n, min_part=min_length) elif isinstance(arg, tuple): - from sage.combinat.integer_list import IntegerListsLex + from sage.combinat.integer_lists import IntegerListsLex a, b = arg compositions = IntegerListsLex(min_sum=a, max_sum=b-1, length=n, min_part=min_length) diff --git a/src/sage/data_structures/mutable_poset.py b/src/sage/data_structures/mutable_poset.py index 3275be203ff..2f30ccb4535 100644 --- a/src/sage/data_structures/mutable_poset.py +++ b/src/sage/data_structures/mutable_poset.py @@ -131,8 +131,7 @@ AUTHORS: -- Daniel Krenn (2015-01-21): initial version -- Daniel Krenn (2015-08-28): mapping methods, bug fixes, cleanup +- Daniel Krenn (2015) ACKNOWLEDGEMENT: diff --git a/src/sage/dev/git_interface.py b/src/sage/dev/git_interface.py index 8f8148987b9..3cebbe75805 100644 --- a/src/sage/dev/git_interface.py +++ b/src/sage/dev/git_interface.py @@ -211,16 +211,7 @@ def _execute(self, cmd, *args, **kwds): sage: git._execute('status',foo=True) # --foo is not a valid parameter Traceback (most recent call last): ... - GitError: git returned with non-zero exit code (129) for - "git -c user.email=doc@test.test -c user.name=doctest status --foo". - output to stderr: error: unknown option `foo' - usage: git status [options] [--] ... - - -v, --verbose be verbose - -s, --short show status concisely - -b, --branch show branch information - --porcelain machine-readable output - ... + GitError: git returned with non-zero exit code ... """ exit_code, stdout, stderr, cmd = self._run_git(cmd, args, kwds) if exit_code: @@ -251,16 +242,7 @@ def _execute_silent(self, cmd, *args, **kwds): sage: git._execute_silent('status',foo=True) # --foo is not a valid parameter Traceback (most recent call last): ... - GitError: git returned with non-zero exit code (129) for - "git -c user.email=doc@test.test -c user.name=doctest status --foo". - output to stderr: error: unknown option `foo' - usage: git status [options] [--] ... - - -v, --verbose be verbose - -s, --short show status concisely - -b, --branch show branch information - --porcelain machine-readable output - ... + GitError: git returned with non-zero exit code ... """ exit_code, stdout, stderr, cmd = self._run_git(cmd, args, kwds) if exit_code: @@ -546,9 +528,7 @@ def get_state(self): sage: git._execute_supersilent('rebase', 'branch2') Traceback (most recent call last): ... - GitError: git returned with non-zero exit code (1) for - "git -c user.email=doc@test.test -c user.name=doctest rebase branch2". - ... + GitError: git returned with non-zero exit code ... sage: git.get_state() ('rebase',) sage: git.super_silent.rebase(abort=True) diff --git a/src/sage/functions/other.py b/src/sage/functions/other.py index 1dbe2c36b01..8cb74664a95 100644 --- a/src/sage/functions/other.py +++ b/src/sage/functions/other.py @@ -441,6 +441,8 @@ def __call__(self, x, maximum_bits=20000): 100000000000000000000000000000000000000000000000000 sage: ceil(int(10^50)) 100000000000000000000000000000000000000000000000000 + sage: ceil((1725033*pi - 5419351)/(25510582*pi - 80143857)) + -2 """ try: return x.ceil() @@ -453,39 +455,29 @@ def __call__(self, x, maximum_bits=20000): import numpy return numpy.ceil(x) - x_original = x - from sage.rings.all import RealIntervalField - # If x can be coerced into a real interval, then we should - # try increasing the number of bits of precision until - # we get the ceiling at each of the endpoints is the same. - # The precision will continue to be increased up to maximum_bits - # of precision at which point it will raise a value error. + bits = 53 + while bits < maximum_bits: + try: + x_interval = RealIntervalField(bits)(x) + except TypeError: + # If we cannot compute a numerical enclosure, leave the + # expression unevaluated. + return BuiltinFunction.__call__(self, SR(x)) + try: + return x_interval.unique_ceil() + except ValueError: + bits *= 2 + try: - x_interval = RealIntervalField(bits)(x) - upper_ceil = x_interval.upper().ceil() - lower_ceil = x_interval.lower().ceil() + return ceil(SR(x).full_simplify().canonicalize_radical()) + except ValueError: + pass - while upper_ceil != lower_ceil and bits < maximum_bits: - bits += 100 - x_interval = RealIntervalField(bits)(x) - upper_ceil = x_interval.upper().ceil() - lower_ceil = x_interval.lower().ceil() + raise ValueError("computing ceil(%s) requires more than %s bits of precision (increase maximum_bits to proceed)"%(x, maximum_bits)) - if bits < maximum_bits: - return lower_ceil - else: - try: - return ceil(SR(x).full_simplify().canonicalize_radical()) - except ValueError: - pass - raise ValueError("x (= %s) requires more than %s bits of precision to compute its ceiling"%(x, maximum_bits)) - except TypeError: - # If x cannot be coerced into a RealField, then - # it should be left as a symbolic expression. - return BuiltinFunction.__call__(self, SR(x_original)) def _eval_(self, x): """ @@ -612,6 +604,8 @@ def __call__(self, x, maximum_bits=20000): 99999999999999999999999999999999999999999999999999 sage: floor(int(10^50)) 100000000000000000000000000000000000000000000000000 + sage: floor((1725033*pi - 5419351)/(25510582*pi - 80143857)) + -3 """ try: return x.floor() @@ -624,40 +618,27 @@ def __call__(self, x, maximum_bits=20000): import numpy return numpy.floor(x) - x_original = x - from sage.rings.all import RealIntervalField - # If x can be coerced into a real interval, then we should - # try increasing the number of bits of precision until - # we get the floor at each of the endpoints is the same. - # The precision will continue to be increased up to maximum_bits - # of precision at which point it will raise a value error. bits = 53 - try: - x_interval = RealIntervalField(bits)(x) - upper_floor = x_interval.upper().floor() - lower_floor = x_interval.lower().floor() - - while upper_floor != lower_floor and bits < maximum_bits: - bits += 100 + while bits < maximum_bits: + try: x_interval = RealIntervalField(bits)(x) - upper_floor = x_interval.upper().floor() - lower_floor = x_interval.lower().floor() + except TypeError: + # If we cannot compute a numerical enclosure, leave the + # expression unevaluated. + return BuiltinFunction.__call__(self, SR(x)) + try: + return x_interval.unique_floor() + except ValueError: + bits *= 2 - if bits < maximum_bits: - return lower_floor - else: - try: - return floor(SR(x).full_simplify().canonicalize_radical()) - except ValueError: - pass - raise ValueError("x (= %s) requires more than %s bits of precision to compute its floor"%(x, maximum_bits)) - - except TypeError: - # If x cannot be coerced into a RealField, then - # it should be left as a symbolic expression. - return BuiltinFunction.__call__(self, SR(x_original)) + try: + return floor(SR(x).full_simplify().canonicalize_radical()) + except ValueError: + pass + + raise ValueError("computing floor(%s) requires more than %s bits of precision (increase maximum_bits to proceed)"%(x, maximum_bits)) def _eval_(self, x): """ diff --git a/src/sage/game_theory/normal_form_game.py b/src/sage/game_theory/normal_form_game.py index 695647aed3e..827363f716c 100644 --- a/src/sage/game_theory/normal_form_game.py +++ b/src/sage/game_theory/normal_form_game.py @@ -606,7 +606,6 @@ from collections import MutableMapping from itertools import product from parser import Parser -from sage.combinat.cartesian_product import CartesianProduct from sage.misc.latex import latex from sage.misc.misc import powerset from sage.rings.all import QQ @@ -1549,7 +1548,7 @@ def _solve_enumeration(self, maximization=True): powerset(range(player.num_strategies))] for player in self.players] - potential_support_pairs = [pair for pair in CartesianProduct(*potential_supports) if len(pair[0]) == len(pair[1])] + potential_support_pairs = [pair for pair in product(*potential_supports) if len(pair[0]) == len(pair[1])] equilibria = [] for pair in potential_support_pairs: @@ -2027,7 +2026,7 @@ def is_degenerate(self, certificate=False): potential_supports] potential_support_pairs = [pair for pair in - CartesianProduct(*potential_supports) if + product(*potential_supports) if len(pair[0]) != len(pair[1])] # Sort so that solve small linear systems first diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 3c2fe11daf6..f7bb0626e28 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -31,6 +31,7 @@ .. TODO:: Implement a parent for all geodesics of the hyperbolic plane? + Or implement geodesics as a parent in the subobjects category? """ diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py index d8d3c411467..d2a4008207f 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -97,7 +97,7 @@ def __init__(self): sage: H = HyperbolicPlane() sage: TestSuite(H).run() """ - Parent.__init__(self, category=Sets().WithRealizations()) + Parent.__init__(self, category=Sets().Metric().WithRealizations()) self.a_realization() # We create a realization so at least one is known def _repr_(self): @@ -181,9 +181,10 @@ def super_categories(self): sage: H = HyperbolicPlane() sage: models = HyperbolicModels(H) sage: models.super_categories() - [Category of sets, Category of realizations of Hyperbolic plane] + [Category of metric spaces, + Category of realizations of Hyperbolic plane] """ - return [Sets(), Realizations(self.base())] + return [Sets().Metric(), Realizations(self.base())] class ParentMethods: def _an_element_(self): @@ -204,31 +205,3 @@ def _an_element_(self): """ return self(self.realization_of().PD().get_point(0)) - # TODO: Move to a category of metric spaces once created - @abstract_method - def dist(self, a, b): - """ - Return the distance between ``a`` and ``b``. - - EXAMPLES:: - - sage: PD = HyperbolicPlane().PD() - sage: PD.dist(PD.get_point(0), PD.get_point(I/2)) - arccosh(5/3) - """ - - class ElementMethods: - # TODO: Move to a category of metric spaces once created - def dist(self, other): - """ - Return the distance between ``self`` and ``other``. - - EXAMPLES:: - - sage: UHP = HyperbolicPlane().UHP() - sage: p1 = UHP.get_point(5 + 7*I) - sage: p2 = UHP.get_point(1 + I) - sage: p1.dist(p2) - arccosh(33/7) - """ - return self.parent().dist(self, other) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 73cbf2eee26..6717d0206fe 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -65,10 +65,6 @@ False sage: U.boundary_point_in_model(2) True - -.. TODO:: - - Implement a category for metric spaces. """ #*********************************************************************** diff --git a/src/sage/geometry/hyperplane_arrangement/arrangement.py b/src/sage/geometry/hyperplane_arrangement/arrangement.py index 032d7fcaaa9..bb58ffd52a0 100644 --- a/src/sage/geometry/hyperplane_arrangement/arrangement.py +++ b/src/sage/geometry/hyperplane_arrangement/arrangement.py @@ -1362,16 +1362,17 @@ def vertices(self, exclude_sandwiched=False): sage: H. = HyperplaneArrangements(QQ) sage: chessboard = [] sage: N = 8 - sage: for x0, y0 in CartesianProduct(range(N+1), range(N+1)): - ....: chessboard.extend([x-x0, y-y0]) + sage: for x0 in range(N+1): + ....: for y0 in range(N+1): + ....: chessboard.extend([x-x0, y-y0]) sage: chessboard = H(chessboard) sage: len(chessboard.vertices()) 81 sage: chessboard.vertices(exclude_sandwiched=True) ((0, 0), (0, 8), (8, 0), (8, 8)) """ + import itertools from sage.matroids.all import Matroid - from sage.combinat.cartesian_product import CartesianProduct R = self.parent().base_ring() parallels = self._parallel_hyperplanes() A_list = [parallel[0][1] for parallel in parallels] @@ -1393,7 +1394,7 @@ def skip(b_list): for row, i in enumerate(indices): lhs[row] = A_list[i] b_list = [b_list_list[i] for i in indices] - for b in CartesianProduct(*b_list): + for b in itertools.product(*b_list): for i in range(d): rhs[i] = b[i] vertex = lhs.solve_right(rhs) @@ -1421,7 +1422,7 @@ def _make_region(self, hyperplanes): sage: h._make_region([x, 1-x, y, 1-y]) A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices """ - ieqs = [h.coefficients() for h in hyperplanes] + ieqs = [h.dense_coefficient_list() for h in hyperplanes] from sage.geometry.polyhedron.constructor import Polyhedron return Polyhedron(ieqs=ieqs, ambient_dim=self.dimension(), base_ring=self.parent().base_ring()) @@ -1457,8 +1458,9 @@ def regions(self): sage: chessboard = [] sage: N = 8 - sage: for x0, y0 in CartesianProduct(range(N+1), range(N+1)): - ....: chessboard.extend([x-x0, y-y0]) + sage: for x0 in range(N+1): + ....: for y0 in range(N+1): + ....: chessboard.extend([x-x0, y-y0]) sage: chessboard = H(chessboard) sage: len(chessboard.bounded_regions()) # long time, 359 ms on a Core i7 64 @@ -1471,7 +1473,7 @@ def regions(self): universe = Polyhedron(eqns=[[0] + [0] * dim], base_ring=R) regions = [universe] for hyperplane in self: - ieq = vector(R, hyperplane.coefficients()) + ieq = vector(R, hyperplane.dense_coefficient_list()) pos_half = Polyhedron(ieqs=[ ieq], base_ring=R) neg_half = Polyhedron(ieqs=[-ieq], base_ring=R) subdivided = [] diff --git a/src/sage/geometry/integral_points.pyx b/src/sage/geometry/integral_points.pyx index bde90c18d6d..3881ba5fce7 100644 --- a/src/sage/geometry/integral_points.pyx +++ b/src/sage/geometry/integral_points.pyx @@ -10,13 +10,14 @@ Cython helper methods to compute integral points in polyhedra. # http://www.gnu.org/licenses/ #***************************************************************************** +import copy +import itertools + from sage.matrix.constructor import matrix, column_matrix, vector, diagonal_matrix from sage.rings.all import QQ, RR, ZZ, gcd, lcm from sage.rings.integer cimport Integer from sage.combinat.permutation import Permutation -from sage.combinat.cartesian_product import CartesianProduct from sage.misc.all import prod, uniq -import copy ############################################################################## # The basic idea to enumerate the lattice points in the parallelotope @@ -209,7 +210,7 @@ cpdef loop_over_parallelotope_points(e, d, VDinv, R, lattice, A=None, b=None): s = ZZ.zero() # summation variable gen = lattice(0) q_times_d = vector(ZZ, dim) - for base in CartesianProduct(*[ range(0,i) for i in e ]): + for base in itertools.product(*[ range(0,i) for i in e ]): for i in range(0, dim): s = ZZ.zero() for j in range(0, dim): diff --git a/src/sage/geometry/linear_expression.py b/src/sage/geometry/linear_expression.py index ca4773e0449..d52f0ddb04e 100644 --- a/src/sage/geometry/linear_expression.py +++ b/src/sage/geometry/linear_expression.py @@ -2,8 +2,8 @@ Linear Expressions A linear expression is just a linear polynomial in some (fixed) -variables. This class only implements linear expressions for others to -use. +variables (allowing a nonzero constant term). This class only implements +linear expressions for others to use. EXAMPLES:: @@ -12,6 +12,8 @@ Module of linear expressions in variables x, y, z over Rational Field sage: x + 2*y + 3*z + 4 x + 2*y + 3*z + 4 + sage: L(4) + 0*x + 0*y + 0*z + 4 You can also pass coefficients and a constant term to construct linear expressions:: @@ -23,7 +25,7 @@ sage: L([4, 1, 2, 3]) # note: constant is first in single-tuple notation x + 2*y + 3*z + 4 -The linear expressions are a module under the base ring, so you can +The linear expressions are a module over the base ring, so you can add them and multiply them with scalars:: sage: m = x + 2*y + 3*z + 4 @@ -91,9 +93,9 @@ def __init__(self, parent, coefficients, constant, check=True): self._const = constant if check: if self._coeffs.parent() is not self.parent().ambient_module(): - raise ValueError("cofficients are not in the ambient module") + raise ValueError("coefficients are not in the ambient module") if not self._coeffs.is_immutable(): - raise ValueError("cofficients are not immutable") + raise ValueError("coefficients are not immutable") if self._const.parent() is not self.parent().base_ring(): raise ValueError("the constant is not in the base ring") @@ -161,6 +163,32 @@ def coefficients(self): """ return [self._const] + list(self._coeffs) + dense_coefficient_list = coefficients + + def monomial_coefficients(self, copy=True): + """ + Return a dictionary whose keys are indices of basis elements in + the support of ``self`` and whose values are the corresponding + coefficients. + + INPUT: + + - ``copy`` -- ignored + + EXAMPLES:: + + sage: from sage.geometry.linear_expression import LinearExpressionModule + sage: L. = LinearExpressionModule(QQ) + sage: linear = L([1, 2, 3], 4) + sage: sorted(linear.monomial_coefficients().items()) + [(0, 1), (1, 2), (2, 3), ('b', 4)] + """ + zero = self.parent().base_ring().zero() + d = {i: v for i,v in enumerate(self._coeffs) if v != zero} + if self._const != zero: + d['b'] = self._const + return d + def _repr_vector(self, variable='x'): """ Return a string representation. @@ -467,9 +495,31 @@ def __init__(self, base_ring, names=tuple()): sage: TestSuite(L).run() """ from sage.categories.modules import Modules - super(LinearExpressionModule, self).__init__(base_ring, category=Modules(base_ring)) + super(LinearExpressionModule, self).__init__(base_ring, category=Modules(base_ring).WithBasis().FiniteDimensional()) self._names = names - + + @cached_method + def basis(self): + """ + Return a basis of ``self``. + + EXAMPLES:: + + sage: from sage.geometry.linear_expression import LinearExpressionModule + sage: L = LinearExpressionModule(QQ, ('x', 'y', 'z')) + sage: list(L.basis()) + [x + 0*y + 0*z + 0, + 0*x + y + 0*z + 0, + 0*x + 0*y + z + 0, + 0*x + 0*y + 0*z + 1] + """ + from sage.sets.family import Family + gens = self.gens() + d = {i: g for i,g in enumerate(gens)} + d['b'] = self.element_class(self, self.ambient_module().zero(), + self.base_ring().one()) + return Family(range(len(gens)) + ['b'], lambda i: d[i]) + @cached_method def ngens(self): """ @@ -491,7 +541,7 @@ def ngens(self): @cached_method def gens(self): """ - Return the generators. + Return the generators of ``self``. OUTPUT: @@ -585,7 +635,7 @@ def _element_constructor_(self, arg0, arg1=None): else: # Construct from list/tuple/iterable:: try: - arg0 = arg0.coefficients() + arg0 = arg0.dense_coefficient_list() except AttributeError: arg0 = list(arg0) const = arg0[0] diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 6419ad6736e..c19090f27b5 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -12,6 +12,7 @@ # http://www.gnu.org/licenses/ ######################################################################## +import itertools import six from sage.structure.element import Element, coerce_binop, is_Vector @@ -26,8 +27,6 @@ from sage.graphs.graph import Graph -from sage.combinat.cartesian_product import CartesianProduct - from constructor import Polyhedron @@ -459,8 +458,8 @@ def _is_subpolyhedron(self, other): True """ return all( other_H.contains(self_V) - for other_H, self_V in - CartesianProduct(other.Hrepresentation(), self.Vrepresentation()) ) + for other_H in other.Hrepresentation() \ + for self_V in self.Vrepresentation()) def plot(self, point=None, line=None, polygon=None, # None means unspecified by the user @@ -845,6 +844,14 @@ def cdd_Hrepresentation(self): 1 -1 0 1 0 -1 end + + sage: triangle = Polyhedron(vertices = [[1,0],[0,1],[1,1]],base_ring=AA) + sage: triangle.base_ring() + Algebraic Real Field + sage: triangle.cdd_Hrepresentation() + Traceback (most recent call last): + ... + TypeError: The base ring must be ZZ, QQ, or RDF """ from cdd_file_format import cdd_Hrepresentation try: @@ -1917,8 +1924,17 @@ def base_ring(self): OUTPUT: - Either ``QQ`` (exact arithmetic using gmp, default) or ``RDF`` - (double precision floating-point arithmetic) + The ring over which the polyhedron is defined. Must be a + sub-ring of the reals to define a polyhedron, in particular + comparison must be defined. Popular choices are + + * ``ZZ`` (the ring of integers, lattice polytope), + + * ``QQ`` (exact arithmetic using gmp), + + * ``RDF`` (double precision floating-point arithmetic), or + + * ``AA`` (real algebraic field). EXAMPLES:: @@ -3893,7 +3909,7 @@ def lattice_polytope(self, envelope=False): vertices = [] for v in self.vertex_generator(): vbox = [ set([floor(x),ceil(x)]) for x in v ] - vertices.extend( CartesianProduct(*vbox) ) + vertices.extend( itertools.product(*vbox) ) # construct the (enveloping) lattice polytope from sage.geometry.lattice_polytope import LatticePolytope diff --git a/src/sage/geometry/polyhedron/base_ZZ.py b/src/sage/geometry/polyhedron/base_ZZ.py index 0e72f05b166..8de9c6e5581 100644 --- a/src/sage/geometry/polyhedron/base_ZZ.py +++ b/src/sage/geometry/polyhedron/base_ZZ.py @@ -547,8 +547,8 @@ def _subpoly_parallel_facets(self): edge_vectors.append([ v_prim*i for i in range(d+1) ]) origin = self.ambient_space().zero() parent = self.parent() - from sage.combinat.cartesian_product import CartesianProduct - for edges in CartesianProduct(*edge_vectors): + from itertools import product + for edges in product(*edge_vectors): v = [] point = origin for e in edges: diff --git a/src/sage/geometry/polyhedron/library.py b/src/sage/geometry/polyhedron/library.py index b1368de04bc..e498d34f803 100644 --- a/src/sage/geometry/polyhedron/library.py +++ b/src/sage/geometry/polyhedron/library.py @@ -15,6 +15,7 @@ :delim: | :meth:`~sage.geometry.polyhedron.library.Polytopes.Birkhoff_polytope` + :meth:`~sage.geometry.polyhedron.library.Polytopes.associahedron` :meth:`~sage.geometry.polyhedron.library.Polytopes.buckyball` :meth:`~sage.geometry.polyhedron.library.Polytopes.cross_polytope` :meth:`~sage.geometry.polyhedron.library.Polytopes.cube` @@ -67,6 +68,7 @@ from constructor import Polyhedron from sage.graphs.digraph import DiGraph +from sage.combinat.root_system.associahedron import Associahedron def zero_sum_projection(d): r""" @@ -148,8 +150,6 @@ class Polytopes(): polytopes. """ - flow_polytope = staticmethod(DiGraph.flow_polytope) - def regular_polygon(self, n, exact=True, base_ring=None): """ Return a regular polygon with `n` vertices. @@ -1114,4 +1114,11 @@ def parallelotope(self, generators): par.extend(sum(c) for k in range(1,len(generators)+1) for c in combinations(generators,k)) return Polyhedron(vertices=par, base_ring=R) + # -------------------------------------------------------- + # imports from other files + # -------------------------------------------------------- + associahedron = staticmethod(Associahedron) + + flow_polytope = staticmethod(DiGraph.flow_polytope) + polytopes = Polytopes() diff --git a/src/sage/graphs/base/boost_graph.pyx b/src/sage/graphs/base/boost_graph.pyx index 0663cb34d25..02e6ebe9e81 100644 --- a/src/sage/graphs/base/boost_graph.pyx +++ b/src/sage/graphs/base/boost_graph.pyx @@ -166,6 +166,13 @@ cpdef edge_connectivity(g): sage: edge_connectivity(g) [4, [(0, 1), (0, 2), (0, 3), (0, 4)]] + Vertex-labeled graphs:: + + sage: from sage.graphs.base.boost_graph import edge_connectivity + sage: g = graphs.GridGraph([2,2]) + sage: edge_connectivity(g) + [2, [((0, 0), (0, 1)), ((0, 0), (1, 0))]] + """ from sage.graphs.graph import Graph from sage.graphs.digraph import DiGraph @@ -173,11 +180,12 @@ cpdef edge_connectivity(g): # These variables are automatically deleted when the function terminates. cdef BoostVecGraph g_boost_und cdef BoostVecDiGraph g_boost_dir + cdef list int_to_vertex = g.vertices() if isinstance(g, Graph): boost_graph_from_sage_graph(&g_boost_und, g) sig_check() - return boost_edge_connectivity(&g_boost_und) + ec, edges = boost_edge_connectivity(&g_boost_und) elif isinstance(g, DiGraph): from sage.misc.stopgap import stopgap @@ -186,11 +194,13 @@ cpdef edge_connectivity(g): boost_graph_from_sage_graph(&g_boost_dir, g) sig_check() - return boost_edge_connectivity(&g_boost_dir) + ec, edges = boost_edge_connectivity(&g_boost_dir) else: raise ValueError("The input must be a Sage graph.") + return [ec, [(int_to_vertex[u], int_to_vertex[v]) for (u,v) in edges]] + cdef boost_clustering_coeff(BoostGenGraph *g, vertices): r""" Computes the clustering coefficient of all vertices in the list provided. @@ -394,7 +404,7 @@ cpdef dominator_tree(g, root, return_dict = False): cdef BoostVecDiGraph g_boost_dir cdef vector[v_index] result cdef dict vertex_to_int = {v:i for i,v in enumerate(g.vertices())} - cdef dict int_to_vertex = {i:v for i,v in enumerate(g.vertices())} + cdef list int_to_vertex = g.vertices() if isinstance(g, Graph): boost_graph_from_sage_graph(&g_boost_und, g) @@ -411,7 +421,7 @@ cpdef dominator_tree(g, root, return_dict = False): if return_dict: return {v:(None if result[vertex_to_int[v]] == no_parent else int_to_vertex[ result[vertex_to_int[v]]]) for v in g.vertices()}; - edges = [[int_to_vertex[result[vertex_to_int[v]]], v] for v in g.vertices() if result[vertex_to_int[v]] != no_parent] + edges = [[int_to_vertex[ result[vertex_to_int[v]]], v] for v in g.vertices() if result[vertex_to_int[v]] != no_parent] if g.is_directed(): if len(edges) == 0: @@ -520,17 +530,17 @@ cpdef bandwidth_heuristics(g, algorithm = 'cuthill_mckee'): cdef BoostVecGraph g_boost cdef vector[v_index] result cdef dict vertex_to_int = {v:i for i,v in enumerate(g.vertices())} - cdef dict int_to_vertex = {i:v for i,v in enumerate(g.vertices())} + cdef list int_to_vertex = g.vertices() boost_graph_from_sage_graph(&g_boost, g) result = g_boost.bandwidth_ordering(algorithm=='cuthill_mckee') cdef int n = g.num_verts() - cdef dict pos = {int_to_vertex[result[i]]:i for i in range(n)} + cdef dict pos = {int_to_vertex[ result[i]]:i for i in range(n)} cdef int bandwidth = max([abs(pos[u]-pos[v]) for u,v in g.edges(labels=False)]) sig_off() - return (bandwidth, [int_to_vertex[result[i]] for i in range(n)]) + return (bandwidth, [int_to_vertex[ result[i]] for i in range(n)]) cpdef min_spanning_tree(g, weight_function=None, @@ -628,7 +638,7 @@ cpdef min_spanning_tree(g, cdef BoostVecWeightedGraph g_boost cdef vector[v_index] result cdef dict vertex_to_int = {v:i for i,v in enumerate(g.vertices())} - cdef dict int_to_vertex = {i:v for i,v in enumerate(g.vertices())} + cdef list int_to_vertex = g.vertices() try: boost_weighted_graph_from_sage_graph(&g_boost, g, weight_function) @@ -647,7 +657,7 @@ cpdef min_spanning_tree(g, if result.size() != 2 * (n - 1): return [] else: - edges = [(int_to_vertex[result[2*i]], int_to_vertex[result[2*i+1]]) for i in range(n-1)] + edges = [(int_to_vertex[ result[2*i]], int_to_vertex[ result[2*i+1]]) for i in range(n-1)] return sorted([(min(e[0],e[1]), max(e[0],e[1]), g.edge_label(e[0], e[1])) for e in edges]) diff --git a/src/sage/graphs/bliss.pyx b/src/sage/graphs/bliss.pyx index 6c64a57cc7c..a0b69588b5e 100644 --- a/src/sage/graphs/bliss.pyx +++ b/src/sage/graphs/bliss.pyx @@ -311,3 +311,4 @@ def canonical_form(G, partition=None, return_graph=False, certify=False): return sorted(edges),relabel return sorted(edges) + diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index d7e8f81a587..e20173a7f4e 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -119,94 +119,86 @@ class DiGraph(GenericGraph): - """Directed graph. + r""" + Directed graph. A digraph or directed graph is a set of vertices connected by oriented - edges. For more information, see the - `Wikipedia article on digraphs - `_. + edges. See also the :wikipedia:`Directed_graph`. For a collection of + pre-defined digraphs, see the :mod:`~sage.graphs.digraph_generators` module. - One can very easily create a directed graph in Sage by typing:: + A :class:`DiGraph` object has many methods whose list can be obtained by + typing ``g.`` (i.e. hit the 'tab' key) or by reading the documentation + of :mod:`~sage.graphs.digraph`, :mod:`~sage.graphs.generic_graph`, and + :mod:`~sage.graphs.graph`. - sage: g = DiGraph() - - By typing the name of the digraph, one can get some basic information - about it:: - - sage: g - Digraph on 0 vertices - - This digraph is not very interesting as it is by default the empty - graph. But Sage contains several pre-defined digraph classes that can - be listed this way: - - * Within a Sage sessions, type ``digraphs.`` - (Do not press "Enter", and do not forget the final period "." ) - * Hit "tab". - - You will see a list of methods which will construct named digraphs. For - example:: - - sage: g = digraphs.ButterflyGraph(3) - sage: g.plot() - Graphics object consisting of 81 graphics primitives + INPUT: - You can also use the collection of pre-defined graphs, then create a - digraph from them. :: + By default, a :class:`DiGraph` object is simple (i.e. no *loops* nor + *multiple edges*) and unweighted. This can be easily tuned with the + appropriate flags (see below). - sage: g = DiGraph(graphs.PetersenGraph()) - sage: g.plot() - Graphics object consisting of 50 graphics primitives + - ``data`` -- can be any of the following (see the ``format`` argument): - Calling ``Digraph`` on a graph returns the original graph in which every - edge is replaced by two different edges going toward opposite directions. + #. ``DiGraph()`` -- build a digraph on 0 vertices. - In order to obtain more information about these digraph constructors, - access the documentation by typing ``digraphs.RandomDirectedGNP?``. + #. ``DiGraph(5)`` -- return an edgeless digraph on the 5 vertices 0,...,4. - Once you have defined the digraph you want, you can begin to work on it - by using the almost 200 functions on graphs and digraphs in the Sage - library! If your digraph is named ``g``, you can list these functions as - previously this way + #. ``DiGraph([list_of_vertices,list_of_edges])`` -- returns a digraph with + given vertices/edges. - * Within a Sage session, type ``g.`` - (Do not press "Enter", and do not forget the final period "." ) - * Hit "tab". + To bypass auto-detection, prefer the more explicit + ``DiGraph([V,E],format='vertices_and_edges')``. - As usual, you can get some information about what these functions do by - typing (e.g. if you want to know about the ``diameter()`` method) - ``g.diameter?``. + #. ``DiGraph(list_of_edges)`` -- return a digraph with a given list of + edges (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.add_edges`). - If you have defined a digraph ``g`` having several connected components - ( i.e. ``g.is_connected()`` returns False ), you can print each one of its - connected components with only two lines:: + To bypass auto-detection, prefer the more explicit ``DiGraph(L, + format='list_of_edges')``. - sage: for component in g.connected_components(): - ....: g.subgraph(component).plot() - Graphics object consisting of 50 graphics primitives + #. ``DiGraph({1:[2,3,4],3:[4]})`` -- return a digraph by associating to + each vertex the list of its out-neighbors. - The same methods works for strongly connected components :: + To bypass auto-detection, prefer the more explicit ``DiGraph(D, + format='dict_of_lists')``. - sage: for component in g.strongly_connected_components(): - ....: g.subgraph(component).plot() - Graphics object consisting of 50 graphics primitives + #. ``DiGraph({1: {2: 'a', 3:'b'} ,3:{2:'c'}})`` -- return a digraph by + associating a list of out-neighbors to each vertex and providing its + edge label. + To bypass auto-detection, prefer the more explicit ``DiGraph(D, + format='dict_of_dicts')``. - INPUT: + For digraphs with multiple edges, you can provide a list of labels + instead, e.g.: ``DiGraph({1: {2: ['a1', 'a2'], 3:['b']} + ,3:{2:['c']}})``. - - ``data`` - can be any of the following (see the ``format`` keyword): + #. ``DiGraph(a_matrix)`` -- return a digraph with given (weighted) adjacency + matrix (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.adjacency_matrix`). - #. A dictionary of dictionaries + To bypass auto-detection, prefer the more explicit ``DiGraph(M, + format='adjacency_matrix')``. To take weights into account, use + ``format='weighted_adjacency_matrix'`` instead. - #. A dictionary of lists + #. ``DiGraph(a_nonsquare_matrix)`` -- return a digraph with given + incidence matrix (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.incidence_matrix`). - #. A Sage adjacency matrix or incidence matrix + To bypass auto-detection, prefer the more explicit ``DiGraph(M, + format='incidence_matrix')``. - #. A pygraphviz graph + #. ``DiGraph([V, f])`` -- return a digraph with a vertex set ``V`` and an + edge `u,v` whenever ``f(u,v)`` is ``True``. Example: ``DiGraph([ + [1..10], lambda x,y: abs(x-y).is_square()])`` - #. A NetworkX digraph + #. ``DiGraph('FOC@?OC@_?')`` -- return a digraph from a dig6 string (see + documentation of :meth:`dig6_string`). - #. An igraph Graph (see http://igraph.org/python/) + #. ``DiGraph(another_digraph)`` -- return a digraph from a Sage (di)graph, + `pygraphviz `__ digraph, `NetworkX + `__ digraph, or `igraph + `__ digraph. - ``pos`` - a positioning dictionary: for example, the spring layout from NetworkX for the 5-cycle is:: @@ -229,32 +221,13 @@ class DiGraph(GenericGraph): - ``weighted`` - whether digraph thinks of itself as weighted or not. See self.weighted() - - ``format`` - if None, DiGraph tries to guess- can be - several values, including: - - - ``'adjacency_matrix'`` - a square Sage matrix M, - with M[i,j] equal to the number of edges {i,j} - - - ``'incidence_matrix'`` - a Sage matrix, with one - column C for each edge, where if C represents {i, j}, C[i] is -1 - and C[j] is 1 - - - ``'weighted_adjacency_matrix'`` - a square Sage - matrix M, with M[i,j] equal to the weight of the single edge {i,j}. - Given this format, weighted is ignored (assumed True). - - - ``NX`` - data must be a NetworkX DiGraph. - - .. NOTE:: - - As Sage's default edge labels is ``None`` while NetworkX uses - ``{}``, the ``{}`` labels of a NetworkX digraph are automatically - set to ``None`` when it is converted to a Sage graph. This - behaviour can be overruled by setting the keyword - ``convert_empty_dict_labels_to_None`` to ``False`` (it is - ``True`` by default). - - - ``igraph`` - data must be an igraph directed Graph. + - ``format`` - if set to ``None`` (default), :class:`DiGraph` tries to guess + input's format. To avoid this possibly time-consuming step, one of the + following values can be specified (see description above): ``"int"``, + ``"dig6"``, ``"rule"``, ``"list_of_edges"``, ``"dict_of_lists"``, + ``"dict_of_dicts"``, ``"adjacency_matrix"``, + ``"weighted_adjacency_matrix"``, ``"incidence_matrix"``, ``"NX"``, + ``"igraph"``. - ``sparse`` (boolean) -- ``sparse=True`` is an alias for ``data_structure="sparse"``, and ``sparse=False`` is an alias for @@ -279,7 +252,7 @@ class DiGraph(GenericGraph): ``data_structure='static_sparse'``. - ``vertex_labels`` - Whether to allow any object as a vertex (slower), or - only the integers 0, ..., n-1, where n is the number of vertices. + only the integers `0,...,n-1`, where `n` is the number of vertices. - ``convert_empty_dict_labels_to_None`` - this arguments sets the default edge labels used by NetworkX (empty dictionaries) @@ -462,6 +435,13 @@ class DiGraph(GenericGraph): True sage: type(J_imm._backend) == type(G_imm._backend) True + + From a a list of vertices and a list of edges:: + + sage: G = DiGraph([[1,2,3],[(1,2)]]); G + Digraph on 3 vertices + sage: G.edges() + [(1, 2, None)] """ _directed = True @@ -625,6 +605,15 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if format is None and isinstance(data,list) and \ len(data)>=2 and callable(data[1]): format = 'rule' + + if (format is None and + isinstance(data,list) and + len(data) == 2 and + isinstance(data[0],list) and # a list of two lists, the second of + isinstance(data[1],list) and # which contains iterables (the edges) + (not data[1] or callable(getattr(data[1][0],"__iter__",None)))): + format = "vertices_and_edges" + if format is None and isinstance(data,dict): keys = data.keys() if len(keys) == 0: format = 'dict_of_dicts' @@ -716,6 +705,13 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self.allow_loops(loops,check=False) self.add_vertices(data[0]) self.add_edges((u,v) for u in data[0] for v in data[0] if f(u,v)) + + elif format == "vertices_and_edges": + self.allow_multiple_edges(bool(multiedges), check=False) + self.allow_loops(bool(loops), check=False) + self.add_vertices(data[0]) + self.add_edges(data[1]) + elif format == 'dict_of_dicts': from graph_input import from_dict_of_dicts from_dict_of_dicts(self, data, loops=loops, multiedges=multiedges, weighted=weighted, diff --git a/src/sage/graphs/generators/families.py b/src/sage/graphs/generators/families.py index 303383ef39f..a6ebc74c6b6 100644 --- a/src/sage/graphs/generators/families.py +++ b/src/sage/graphs/generators/families.py @@ -689,6 +689,69 @@ def CubeGraph(n): return r +def GoethalsSeidelGraph(k,r): + r""" + Returns the graph `\text{Goethals-Seidel}(k,r)`. + + The graph `\text{Goethals-Seidel}(k,r)` comes from a construction presented + in Theorem 2.4 of [GS70]_. It relies on a :func:`(v,k)-BIBD + ` with `r` + blocks and a + :func:`~sage.combinat.matrices.hadamard_matrix.hadamard_matrix>` of order + `r+1`. The result is a + :func:`sage.graphs.strongly_regular_db.strongly_regular_graph` on `v(r+1)` + vertices with degree `k=(n+r-1)/2`. + + It appears under this name in Andries Brouwer's `database of strongly + regular graphs `__. + + INPUT: + + - ``k,r`` -- integers + + EXAMPLE:: + + sage: graphs.GoethalsSeidelGraph(3,3) + Graph on 28 vertices + sage: graphs.GoethalsSeidelGraph(3,3).is_strongly_regular(parameters=True) + (28, 15, 6, 10) + + """ + from sage.combinat.designs.bibd import balanced_incomplete_block_design + from sage.combinat.matrices.hadamard_matrix import hadamard_matrix + from sage.matrix.constructor import Matrix + from sage.matrix.constructor import block_matrix + from sage.matrix.constructor import identity_matrix + + v = (k-1)*r+1 + n = v*(r+1) + + # N is the (v times b) incidence matrix of a bibd + N = balanced_incomplete_block_design(v,k).incidence_matrix() + + # L is a (r+1 times r) matrix, where r is the row sum of N + L = hadamard_matrix(r+1).submatrix(0,1) + L = [Matrix(C).transpose() for C in L.columns()] + zero = Matrix(r+1,1,[0]*(r+1)) + + # For every row of N, we replace the 0s with a column of zeros, and we + # replace the ith 1 with the ith column of L. The result is P. + P = [] + for row in N: + Ltmp = L[:] + P.append([Ltmp.pop(0) if i else zero + for i in row]) + + P = block_matrix(P) + + # The final graph + PP = P*P.transpose() + for i in range(n): + PP[i,i] = 0 + + G = Graph(PP, format="seidel_adjacency_matrix") + return G + def DorogovtsevGoltsevMendesGraph(n): """ Construct the n-th generation of the Dorogovtsev-Goltsev-Mendes @@ -1532,6 +1595,93 @@ def PaleyGraph(q): loops=False, name = "Paley graph with parameter %d"%q) return g +def PasechnikGraph(n): + """ + Pasechnik strongly regular graph on `(4n-1)^2` vertices + + A strongly regular graph with parameters of the orthogonal array graph + :func:`OrthogonalArrayBlockGraph + `, also + known as pseudo Latin squares graph `L_{2n-1}(4n-1)`, constructed from a + skew Hadamard matrix of order `4n` following [Pa92]_. + + EXAMPLES:: + + sage: graphs.PasechnikGraph(4).is_strongly_regular(parameters=True) + (225, 98, 43, 42) + sage: graphs.PasechnikGraph(9).is_strongly_regular(parameters=True) # long time + (1225, 578, 273, 272) + """ + from sage.combinat.matrices.hadamard_matrix import skew_hadamard_matrix + from sage.matrix.constructor import identity_matrix, matrix + H = skew_hadamard_matrix(4*n) + M = H[1:].T[1:] - identity_matrix(4*n-1) + G = Graph(M.tensor_product(M.T), format='seidel_adjacency_matrix') + G.relabel() + G.name("Pasechnik Graph_" + str((n))) + return G + +def SquaredSkewHadamardMatrixGraph(n): + """ + Pseudo-`OA(2n,4n-1)`-graph from a skew Hadamard matrix of order `4n` + + A strongly regular graph with parameters of the orthogonal array graph + :func:`OrthogonalArrayBlockGraph + `, also + known as pseudo Latin squares graph `L_{2n}(4n-1)`, constructed from a + skew Hadamard matrix of order `4n`, due to Goethals and Seidel, see [BvL84]_. + + EXAMPLES:: + + sage: graphs.SquaredSkewHadamardMatrixGraph(4).is_strongly_regular(parameters=True) + (225, 112, 55, 56) + sage: graphs.SquaredSkewHadamardMatrixGraph(9).is_strongly_regular(parameters=True) # long time + (1225, 612, 305, 306) + + """ + from sage.combinat.matrices.hadamard_matrix import skew_hadamard_matrix + from sage.matrix.constructor import identity_matrix, matrix + idm = identity_matrix(4*n-1) + e = matrix([1]*(4*n-1)) + H = skew_hadamard_matrix(4*n) + M = H[1:].T[1:] - idm + s = M.tensor_product(M.T) - idm.tensor_product(e.T*e - idm) + G = Graph(s, format='seidel_adjacency_matrix') + G.relabel() + G.name("skewhad^2_" + str((n))) + return G + +def SwitchedSquaredSkewHadamardMatrixGraph(n): + """ + A strongly regular graph in Seidel switching class of `SquaredSkewHadamardMatrixGraph` + + A strongly regular graph in the + :meth:`Seidel switching ` class of the disjoint union of + a 1-vertex graph and the one produced by :func:`Pseudo-L_{2n}(4n-1) + ` + + In this case, the other possible parameter set of a strongly regular graph in the + Seidel switching class of the latter graph (see [BH12]_) coincides with the set + of parameters of the complement of the graph returned by this function. + + EXAMPLES:: + + sage: g=graphs.SwitchedSquaredSkewHadamardMatrixGraph(4) + sage: g.is_strongly_regular(parameters=True) + (226, 105, 48, 49) + sage: from sage.combinat.designs.twographs import twograph_descendant + sage: twograph_descendant(g,0).is_strongly_regular(parameters=True) + (225, 112, 55, 56) + sage: twograph_descendant(g.complement(),0).is_strongly_regular(parameters=True) + (225, 112, 55, 56) + """ + from sage.graphs.generators.families import SquaredSkewHadamardMatrixGraph + G = SquaredSkewHadamardMatrixGraph(n).complement() + G.add_vertex((4*n-1)**2) + G.seidel_switching(range((4*n-1)*(2*n-1))) + G.name("switch skewhad^2+*_" + str((n))) + return G + def HanoiTowerGraph(pegs, disks, labels=True, positions=True): r""" Returns the graph whose vertices are the states of the @@ -2270,3 +2420,53 @@ def RingedTree(k, vertex_labels = True): g.relabel(vertices) return g + + + +def MathonPseudocyclicMergingGraph(M, t): + r""" + Mathon's merging of classes in a pseudo-cyclic 3-class association scheme + + Construct strongly regular graphs from p.97 of [BvL84]_. + + INPUT: + + - ``M`` -- the list of matrices in a pseudo-cyclic 3-class association scheme. + The identity matrix must be the first entry. + + - ``t`` (integer) -- the number of the graph, from 0 to 2. + + TESTS:: + + sage: from sage.graphs.generators.families import MathonPseudocyclicMergingGraph as mer + sage: from sage.graphs.generators.smallgraphs import _EllipticLinesProjectivePlaneScheme as ES + sage: G = mer(ES(3), 0) # long time + sage: G.is_strongly_regular(parameters=True) # long time + (784, 243, 82, 72) + sage: G = mer(ES(3), 1) # long time + sage: G.is_strongly_regular(parameters=True) # long time + (784, 270, 98, 90) + sage: G = mer(ES(3), 2) # long time + sage: G.is_strongly_regular(parameters=True) # long time + (784, 297, 116, 110) + sage: G = mer(ES(2), 2) + Traceback (most recent call last): + ... + AssertionError... + sage: M = ES(3) + sage: M = [M[1],M[0],M[2],M[3]] + sage: G = mer(M, 2) + Traceback (most recent call last): + ... + AssertionError... + """ + from sage.graphs.graph import Graph + from sage.matrix.constructor import identity_matrix + assert (len(M) == 4) + assert (M[0]==identity_matrix(M[0].nrows())) + A = sum(map(lambda x: x.tensor_product(x), M[1:])) + if t > 0: + A += sum(map(lambda x: x.tensor_product(M[0]), M[1:])) + if t > 1: + A += sum(map(lambda x: M[0].tensor_product(x), M[1:])) + return Graph(A) diff --git a/src/sage/graphs/generators/smallgraphs.py b/src/sage/graphs/generators/smallgraphs.py index b6bcebb66be..6abc98956fb 100644 --- a/src/sage/graphs/generators/smallgraphs.py +++ b/src/sage/graphs/generators/smallgraphs.py @@ -4753,3 +4753,73 @@ def WienerArayaGraph(): g.get_pos().pop(0) g.relabel() return g + +def _EllipticLinesProjectivePlaneScheme(k): + r""" + Pseudo-cyclic association scheme for action of `O(3,2^k)` on elliptic lines + + The group `O(3,2^k)` acts naturally on the `q(q-1)/2` lines of `PG(2,2^k)` + skew to the conic preserved by it, see Sect. 12.7.B of [BCN89]_ and Sect. 6.D + in [BvL84]_. Compute the orbitals of this action and return them. + + This is a helper for :func:`sage.graphs.generators.smallgraphs.MathonStronglyRegularGraph`. + + INPUT: + + - ``k`` (integer) -- the exponent of 2 to get the field size + + TESTS:: + + sage: from sage.graphs.generators.smallgraphs import _EllipticLinesProjectivePlaneScheme + sage: _EllipticLinesProjectivePlaneScheme(2) + [ + [1 0 0 0 0 0] [0 1 1 1 1 0] [0 0 0 0 0 1] + [0 1 0 0 0 0] [1 0 1 1 0 1] [0 0 0 0 1 0] + [0 0 1 0 0 0] [1 1 0 0 1 1] [0 0 0 1 0 0] + [0 0 0 1 0 0] [1 1 0 0 1 1] [0 0 1 0 0 0] + [0 0 0 0 1 0] [1 0 1 1 0 1] [0 1 0 0 0 0] + [0 0 0 0 0 1], [0 1 1 1 1 0], [1 0 0 0 0 0] + ] + """ + from sage.libs.gap.libgap import libgap + from sage.matrix.constructor import matrix + from itertools import product + q = 2**k + g0 = libgap.GeneralOrthogonalGroup(3,q) # invariant form x0^2+x1*x2 + g = libgap.Group(libgap.List(g0.GeneratorsOfGroup(),libgap.TransposedMat)) + W = libgap.FullRowSpace(libgap.GF(q), 3) + l=sum(libgap.Elements(libgap.Basis(W))) + gp = libgap.Action(g,libgap.Orbit(g,l,libgap.OnLines),libgap.OnLines) + orbitals = gp.Orbits(list(product(gp.Orbit(1),gp.Orbit(1))),libgap.OnTuples) + mats = map(lambda o: map(lambda x: (int(x[0])-1,int(x[1])-1), o), orbitals) + return map(lambda x: matrix(q*(q-1)/2, lambda i,j: 1 if (i,j) in x else 0), mats) + + +def MathonStronglyRegularGraph(t): + r""" + return one of Mathon's graphs on 784 vertices + + INPUT: + + - ``t`` (integer) -- the number of the graph, from 0 to 2. + + EXAMPLE:: + + sage: from sage.graphs.generators.smallgraphs import MathonStronglyRegularGraph + sage: G = MathonStronglyRegularGraph(0) # long time + sage: G.is_strongly_regular(parameters=True) # long time + (784, 243, 82, 72) + + TESTS:: + + sage: G = graphs.MathonStronglyRegularGraph(1) # long time + sage: G.is_strongly_regular(parameters=True) # long time + (784, 270, 98, 90) + sage: G = graphs.MathonStronglyRegularGraph(2) # long time + sage: G.is_strongly_regular(parameters=True) # long time + (784, 297, 116, 110) + + """ + from sage.graphs.generators.families import MathonPseudocyclicMergingGraph + ES = _EllipticLinesProjectivePlaneScheme(3) + return MathonPseudocyclicMergingGraph(ES, t) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 0c02a3aa719..35523ab9145 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -11990,7 +11990,7 @@ def is_chordal(self, certificate = False, algorithm = "B"): TESTS: - Trac Ticket #11735:: + See :trac:`11735`:: sage: g = Graph({3:[2,1,4],2:[1],4:[1],5:[2,1,4]}) sage: _, g1 = g.is_chordal(certificate=True); g1.is_chordal() @@ -15941,6 +15941,10 @@ def breadth_first_search(self, start, ignore_direction=False, sage: list(D.breadth_first_search(0, ignore_direction=True)) [0, 1, 2] """ + from sage.rings.semirings.non_negative_integer_semiring import NN + if (distance is not None and distance not in NN): + raise ValueError("distance must be a non-negative integer, not {0}".format(distance)) + # Preferably use the Cython implementation if neighbors is None and not isinstance(start, list) and distance is None and hasattr(self._backend,"breadth_first_search") and not report_distance: for v in self._backend.breadth_first_search(start, ignore_direction=ignore_direction): @@ -15957,6 +15961,12 @@ def breadth_first_search(self, start, ignore_direction=False, else: queue = [(start, 0)] + # Non-existing start vertex is detected later if distance > 0. + if distance == 0: + for v in queue: + if not v[0] in self: + raise LookupError("start vertex ({0}) is not a vertex of the graph".format(v[0])) + for v, d in queue: if report_distance: yield v, d @@ -15983,7 +15993,6 @@ def depth_first_search(self, start, ignore_direction=False, INPUT: - - ``start`` - vertex or list of vertices from which to start the traversal @@ -16161,7 +16170,7 @@ def lex_BFS(self,reverse=False,tree=False, initial_vertex = None): TESTS: - There were some problems with the following call in the past (trac 10899) -- now + There were some problems with the following call in the past (:trac:`10899`) -- now it should be fine:: sage: Graph(1).lex_BFS(tree=True) @@ -19931,7 +19940,7 @@ def automorphism_group(self, partition=None, verbosity=0, TESTS: - We get a KeyError when given an invalid partition (trac #6087):: + We get a KeyError when given an invalid partition (:trac:`6087`):: sage: g=graphs.CubeGraph(3) sage: g.relabel() @@ -20405,7 +20414,7 @@ def is_isomorphic(self, other, certify=False, verbosity=0, edge_labels=False): sage: D.is_isomorphic(D,edge_labels=True, certify = True) (True, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5}) - Ensure that trac :trac:`11620` is fixed:: + Ensure that :trac:`11620` is fixed:: sage: G1 = DiGraph([(0, 0, 'c'), (0, 4, 'b'), (0, 5, 'c'), ... (0, 5, 't'), (1, 1, 'c'), (1, 3,'c'), (1, 3, 't'), (1, 5, 'b'), diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 04ad91b9587..e4d1c773fe2 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -424,94 +424,97 @@ class Graph(GenericGraph): Undirected graph. A graph is a set of vertices connected by edges. See also the - :wikipedia:`Wikipedia article on graphs `. + :wikipedia:`Wikipedia article on graphs `. For a + collection of pre-defined graphs, see the + :mod:`~sage.graphs.graph_generators` module. - One can very easily create a graph in Sage by typing:: + A :class:`Graph` object has many methods whose list can be obtained by + typing ``g.`` (i.e. hit the 'tab' key) or by reading the documentation + of :mod:`~sage.graphs.graph`, :mod:`~sage.graphs.generic_graph`, and + :mod:`~sage.graphs.digraph`. - sage: g = Graph() - - By typing the name of the graph, one can get some basic information - about it:: - - sage: g - Graph on 0 vertices - - This graph is not very interesting as it is by default the empty graph. - But Sage contains a large collection of pre-defined graph classes that - can be listed this way: - - * Within a Sage session, type ``graphs.`` - (Do not press "Enter", and do not forget the final period ".") - - * Hit "tab". - - You will see a list of methods which will construct named graphs. For - example:: + INPUT: - sage: g = graphs.PetersenGraph() - sage: g.plot() - Graphics object consisting of 26 graphics primitives + By default, a :class:`Graph` object is simple (i.e. no *loops* nor *multiple + edges*) and unweighted. This can be easily tuned with the appropriate flags + (see below). - or:: + - ``data`` -- can be any of the following (see the ``format`` argument): - sage: g = graphs.ChvatalGraph() - sage: g.plot() - Graphics object consisting of 37 graphics primitives + #. ``Graph()`` -- build a graph on 0 vertices. - In order to obtain more information about these graph constructors, access - the documentation using the command ``graphs.RandomGNP?``. + #. ``Graph(5)`` -- return an edgeless graph on the 5 vertices 0,...,4. - Once you have defined the graph you want, you can begin to work on it - by using the almost 200 functions on graphs in the Sage library! - If your graph is named ``g``, you can list these functions as previously - this way + #. ``Graph([list_of_vertices,list_of_edges])`` -- returns a graph with + given vertices/edges. - * Within a Sage session, type ``g.`` - (Do not press "Enter", and do not forget the final period "." ) + To bypass auto-detection, prefer the more explicit + ``Graph([V,E],format='vertices_and_edges')``. - * Hit "tab". + #. ``Graph(list_of_edges)`` -- return a graph with a given list of edges + (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.add_edges`). - As usual, you can get some information about what these functions do by - typing (e.g. if you want to know about the ``diameter()`` method) - ``g.diameter?``. + To bypass auto-detection, prefer the more explicit ``Graph(L, + format='list_of_edges')``. - If you have defined a graph ``g`` having several connected components - (i.e. ``g.is_connected()`` returns False), you can print each one of its - connected components with only two lines:: + #. ``Graph({1:[2,3,4],3:[4]})`` -- return a graph by associating to each + vertex the list of its neighbors. - sage: for component in g.connected_components(): - ....: g.subgraph(component).plot() - Graphics object consisting of 37 graphics primitives + To bypass auto-detection, prefer the more explicit ``Graph(D, + format='dict_of_lists')``. + #. ``Graph({1: {2: 'a', 3:'b'} ,3:{2:'c'}})`` -- return a graph by + associating a list of neighbors to each vertex and providing its edge + label. - INPUT: + To bypass auto-detection, prefer the more explicit ``Graph(D, + format='dict_of_dicts')``. - - ``data`` -- can be any of the following (see the ``format`` argument): + For graphs with multiple edges, you can provide a list of labels + instead, e.g.: ``Graph({1: {2: ['a1', 'a2'], 3:['b']} ,3:{2:['c']}})``. - #. An integer specifying the number of vertices + #. ``Graph(a_symmetric_matrix)`` -- return a graph with given (weighted) + adjacency matrix (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.adjacency_matrix`). - #. A dictionary of dictionaries + To bypass auto-detection, prefer the more explicit ``Graph(M, + format='adjacency_matrix')``. To take weights into account, use + ``format='weighted_adjacency_matrix'`` instead. - #. A dictionary of lists + #. ``Graph(a_nonsymmetric_matrix)`` -- return a graph with given incidence + matrix (see documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.incidence_matrix`). - #. A Sage adjacency matrix or incidence matrix + To bypass auto-detection, prefer the more explicit ``Graph(M, + format='incidence_matrix')``. - #. A Sage :meth:`Seidel adjacency matrix ` + #. ``Graph([V, f])`` -- return a graph from a vertex set ``V`` and a + *symmetric* function ``f``. The graph contains an edge `u,v` whenever + ``f(u,v)`` is ``True``.. Example: ``Graph([ [1..10], lambda x,y: + abs(x-y).is_square()])`` - #. A pygraphviz graph + #. ``Graph(':I`ES@obGkqegW~')`` -- return a graph from a graph6 or sparse6 + string (see documentation of :meth:`graph6_string` or + :meth:`sparse6_string`). - #. A NetworkX graph + #. ``Graph(a_seidel_matrix, format='seidel_adjacency_matrix')`` -- return + a graph with a given seidel adjacency matrix (see documentation of + :meth:`seidel_adjacency_matrix`). - #. An igraph graph (see http://igraph.org/python/) + #. ``Graph(another_graph)`` -- return a graph from a Sage (di)graph, + `pygraphviz `__ graph, `NetworkX + `__ graph, or `igraph + `__ graph. - - ``pos`` - a positioning dictionary: for example, the - spring layout from NetworkX for the 5-cycle is:: + - ``pos`` - a positioning dictionary (cf. documentation of + :meth:`~sage.graphs.generic_graph.GenericGraph.layout`). For example, to + draw 4 vertices on a square:: - {0: [-0.91679746, 0.88169588], - 1: [ 0.47294849, 1.125 ], - 2: [ 1.125 ,-0.12867615], - 3: [ 0.12743933,-1.125 ], - 4: [-1.125 ,-0.50118505]} + {0: [-1,-1], + 1: [ 1,-1], + 2: [ 1, 1], + 3: [-1, 1]} - ``name`` - (must be an explicitly named parameter, i.e., ``name="complete")`` gives the graph a name @@ -520,57 +523,19 @@ class Graph(GenericGraph): if data is an instance of the ``Graph`` class) - ``multiedges`` - boolean, whether to allow multiple - edges (ignored if data is an instance of the ``Graph`` class) - - - ``weighted`` - whether graph thinks of itself as - weighted or not. See ``self.weighted()`` - - - ``format`` - if None, Graph tries to guess; can take - a number of values, namely: - - - ``'int'`` - an integer specifying the number of vertices in an - edge-free graph with vertices labelled from 0 to n-1 - - - ``'graph6'`` - Brendan McKay's graph6 format, in a - string (if the string has multiple graphs, the first graph is - taken) - - - ``'sparse6'`` - Brendan McKay's sparse6 format, in a - string (if the string has multiple graphs, the first graph is - taken) - - - ``'adjacency_matrix'`` - a square Sage matrix M, - with M[i,j] equal to the number of edges {i,j} - - - ``'weighted_adjacency_matrix'`` - a square Sage - matrix M, with M[i,j] equal to the weight of the single edge {i,j}. - Given this format, weighted is ignored (assumed True). - - - ``'seidel_adjacency_matrix'`` - a symmetric Sage matrix M - with 0s on the diagonal, and the other entries -1 or 1, - `M[i,j]=-1` indicating that {i,j} is an edge, otherwise `M[i,j]=1`. + edges (ignored if data is an instance of the ``Graph`` class). - - ``'incidence_matrix'`` - a Sage matrix, with one - column C for each edge, where if C represents {i, j}, C[i] is -1 - and C[j] is 1 + - ``weighted`` - whether graph thinks of itself as weighted or not. See + :meth:`~sage.graphs.generic_graph.GenericGraph.weighted`. - - ``'elliptic_curve_congruence'`` - data must be an - iterable container of elliptic curves, and the graph produced has - each curve as a vertex (it's Cremona label) and an edge E-F - labelled p if and only if E is congruent to F mod p - - - ``NX`` - data must be a NetworkX Graph. - - .. NOTE:: - - As Sage's default edge labels is ``None`` while NetworkX uses - ``{}``, the ``{}`` labels of a NetworkX graph are automatically - set to ``None`` when it is converted to a Sage graph. This - behaviour can be overruled by setting the keyword - ``convert_empty_dict_labels_to_None`` to ``False`` (it is - ``True`` by default). - - - ``igraph`` - data must be an `igraph `__ graph. + - ``format`` - if set to ``None`` (default), :class:`Graph` tries to guess + input's format. To avoid this possibly time-consuming step, one of the + following values can be specified (see description above): ``"int"``, + ``"graph6"``, ``"sparse6"``, ``"rule"``, ``"list_of_edges"``, + ``"dict_of_lists"``, ``"dict_of_dicts"``, ``"adjacency_matrix"``, + ``"weighted_adjacency_matrix"``, ``"seidel_adjacency_matrix"``, + ``"incidence_matrix"``, ``"elliptic_curve_congruence"``, ``"NX"``, + ``"igraph"``. - ``sparse`` (boolean) -- ``sparse=True`` is an alias for ``data_structure="sparse"``, and ``sparse=False`` is an alias for @@ -595,7 +560,7 @@ class Graph(GenericGraph): ``data_structure='static_sparse'``. Set to ``False`` by default. - ``vertex_labels`` - Whether to allow any object as a vertex (slower), or - only the integers 0, ..., n-1, where n is the number of vertices. + only the integers `0,...,n-1`, where `n` is the number of vertices. - ``convert_empty_dict_labels_to_None`` - this arguments sets the default edge labels used by NetworkX (empty dictionaries) @@ -860,6 +825,21 @@ class Graph(GenericGraph): sage: Graph(g).edges() # optional - python_igraph [(0, 1, {'name': 'a', 'weight': 1}), (0, 2, {'name': 'b', 'weight': 3})] + + When defining an undirected graph from a function ``f``, it is *very* + important that ``f`` be symmetric. If it is not, anything can happen:: + + sage: f_sym = lambda x,y : abs(x-y) == 1 + sage: f_nonsym = lambda x,y : (x-y) == 1 + sage: G_sym = Graph([[4,6,1,5,3,7,2,0], f_sym]) + sage: G_sym.is_isomorphic(graphs.PathGraph(8)) + True + sage: G_nonsym = Graph([[4,6,1,5,3,7,2,0], f_nonsym]) + sage: G_nonsym.size() + 4 + sage: G_nonsym.is_isomorphic(G_sym) + False + By default, graphs are mutable and can thus not be used as a dictionary key:: @@ -907,6 +887,12 @@ class Graph(GenericGraph): ... ValueError: Graph's Seidel adjacency matrix must have 0s on the main diagonal + From a a list of vertices and a list of edges:: + + sage: G = Graph([[1,2,3],[(1,2)]]); G + Graph on 3 vertices + sage: G.edges() + [(1, 2, None)] """ _directed = False @@ -1071,6 +1057,15 @@ def __init__(self, data=None, pos=None, loops=None, format=None, len(data)>=2 and callable(data[1])): format = 'rule' + + if (format is None and + isinstance(data,list) and + len(data) == 2 and + isinstance(data[0],list) and # a list of two lists, the second of + isinstance(data[1],list) and # which contains iterables (the edges) + (not data[1] or callable(getattr(data[1][0],"__iter__",None)))): + format = "vertices_and_edges" + if format is None and isinstance(data,dict): keys = data.keys() if len(keys) == 0: format = 'dict_of_dicts' @@ -1208,6 +1203,13 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self.add_vertices(verts) self.add_edges(e for e in combinations(verts,2) if f(*e)) self.add_edges((v,v) for v in verts if f(v,v)) + + elif format == "vertices_and_edges": + self.allow_multiple_edges(bool(multiedges), check=False) + self.allow_loops(bool(loops), check=False) + self.add_vertices(data[0]) + self.add_edges(data[1]) + elif format == 'dict_of_dicts': from graph_input import from_dict_of_dicts from_dict_of_dicts(self, data, loops=loops, multiedges=multiedges, weighted=weighted, @@ -2390,6 +2392,15 @@ def treewidth(self,k=None,certificate=False): ....: g.delete_edges(list(combinations(bag,2))) sage: g.size() 0 + + :trac:`19358`:: + + sage: g = Graph() + sage: for i in range(3): + ....: for j in range(2): + ....: g.add_path([i,(i,j),(i+1)%3]) + sage: g.treewidth() + 2 """ from sage.misc.cachefunc import cached_function from sage.sets.set import Set @@ -2435,9 +2446,8 @@ def rec(cut,cc): if len(cc)+len(cut) <= k+1: return [(cut,cut.union(cc))] if certificate else True - # The list of potential vertices that could be added to the current cut - extensions = {v for u in cut for v in g.neighbors(u) if v in cc} - for v in extensions: + # We explore all possible extensions of the cut + for v in cc: # New cuts and connected components, with v respectively added and # removed diff --git a/src/sage/graphs/graph_generators.py b/src/sage/graphs/graph_generators.py index 3db5bb3f4da..8f44a191a0c 100644 --- a/src/sage/graphs/graph_generators.py +++ b/src/sage/graphs/graph_generators.py @@ -136,6 +136,7 @@ def __append_to_doc(methods): "LivingstoneGraph", "M22Graph", "MarkstroemGraph", + "MathonStronglyRegularGraph", "McGeeGraph", "McLaughlinGraph", "MeredithGraph", @@ -202,6 +203,7 @@ def __append_to_doc(methods): "fusenes", "FuzzyBallGraph", "GeneralizedPetersenGraph", + "GoethalsSeidelGraph", "HanoiTowerGraph", "HararyGraph", "HyperStarGraph", @@ -209,17 +211,21 @@ def __append_to_doc(methods): "KneserGraph", "LCFGraph", "line_graph_forbidden_subgraphs", + "MathonPseudocyclicMergingGraph", "MycielskiGraph", "MycielskiStep", "NKStarGraph", "NStarGraph", "OddGraph", "PaleyGraph", + "PasechnikGraph", "petersen_family", "planar_graphs", "quadrangulations", "RingedTree", "SierpinskiGasketGraph", + "SquaredSkewHadamardMatrixGraph", + "SwitchedSquaredSkewHadamardMatrixGraph", "strongly_regular_graph", "trees", "triangulations", @@ -1919,6 +1925,7 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None LivingstoneGraph = staticmethod(sage.graphs.generators.smallgraphs.LivingstoneGraph) M22Graph = staticmethod(sage.graphs.generators.smallgraphs.M22Graph) MarkstroemGraph = staticmethod(sage.graphs.generators.smallgraphs.MarkstroemGraph) + MathonStronglyRegularGraph = staticmethod(sage.graphs.generators.smallgraphs.MathonStronglyRegularGraph) McGeeGraph = staticmethod(sage.graphs.generators.smallgraphs.McGeeGraph) McLaughlinGraph = staticmethod(sage.graphs.generators.smallgraphs.McLaughlinGraph) MeredithGraph = staticmethod(sage.graphs.generators.smallgraphs.MeredithGraph) @@ -1974,6 +1981,7 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None FriendshipGraph = staticmethod(sage.graphs.generators.families.FriendshipGraph) FuzzyBallGraph = staticmethod(sage.graphs.generators.families.FuzzyBallGraph) GeneralizedPetersenGraph = staticmethod(sage.graphs.generators.families.GeneralizedPetersenGraph) + GoethalsSeidelGraph = staticmethod(sage.graphs.generators.families.GoethalsSeidelGraph) HanoiTowerGraph = staticmethod(sage.graphs.generators.families.HanoiTowerGraph) HararyGraph = staticmethod(sage.graphs.generators.families.HararyGraph) HyperStarGraph = staticmethod(sage.graphs.generators.families.HyperStarGraph) @@ -1981,15 +1989,19 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None KneserGraph = staticmethod(sage.graphs.generators.families.KneserGraph) LCFGraph = staticmethod(sage.graphs.generators.families.LCFGraph) line_graph_forbidden_subgraphs = staticmethod(sage.graphs.generators.families.line_graph_forbidden_subgraphs) + MathonPseudocyclicMergingGraph = staticmethod(sage.graphs.generators.families.MathonPseudocyclicMergingGraph) MycielskiGraph = staticmethod(sage.graphs.generators.families.MycielskiGraph) MycielskiStep = staticmethod(sage.graphs.generators.families.MycielskiStep) NKStarGraph = staticmethod(sage.graphs.generators.families.NKStarGraph) NStarGraph = staticmethod(sage.graphs.generators.families.NStarGraph) OddGraph = staticmethod(sage.graphs.generators.families.OddGraph) PaleyGraph = staticmethod(sage.graphs.generators.families.PaleyGraph) + PasechnikGraph = staticmethod(sage.graphs.generators.families.PasechnikGraph) petersen_family = staticmethod(sage.graphs.generators.families.petersen_family) RingedTree = staticmethod(sage.graphs.generators.families.RingedTree) SierpinskiGasketGraph = staticmethod(sage.graphs.generators.families.SierpinskiGasketGraph) + SquaredSkewHadamardMatrixGraph = staticmethod(sage.graphs.generators.families.SquaredSkewHadamardMatrixGraph) + SwitchedSquaredSkewHadamardMatrixGraph = staticmethod(sage.graphs.generators.families.SwitchedSquaredSkewHadamardMatrixGraph) strongly_regular_graph = staticmethod(sage.graphs.strongly_regular_db.strongly_regular_graph) trees = staticmethod(sage.graphs.generators.families.trees) WheelGraph = staticmethod(sage.graphs.generators.families.WheelGraph) diff --git a/src/sage/graphs/strongly_regular_db.pyx b/src/sage/graphs/strongly_regular_db.pyx index 553bd53afed..636c7f4efa2 100644 --- a/src/sage/graphs/strongly_regular_db.pyx +++ b/src/sage/graphs/strongly_regular_db.pyx @@ -41,9 +41,9 @@ from sage.graphs.generators.smallgraphs import CameronGraph from sage.graphs.generators.smallgraphs import M22Graph from sage.graphs.generators.smallgraphs import SimsGewirtzGraph from sage.graphs.generators.smallgraphs import HoffmanSingletonGraph -from sage.graphs.generators.smallgraphs import SchlaefliGraph from sage.graphs.generators.smallgraphs import HigmanSimsGraph from sage.graphs.generators.smallgraphs import LocalMcLaughlinGraph +from sage.graphs.generators.smallgraphs import MathonStronglyRegularGraph from sage.graphs.generators.smallgraphs import SuzukiGraph from sage.graphs.graph import Graph from libc.math cimport sqrt, floor @@ -90,7 +90,17 @@ def is_paley(int v,int k,int l,int mu): @cached_function def is_orthogonal_array_block_graph(int v,int k,int l,int mu): r""" - Test whether some Orthogonal Array graph is `(v,k,\lambda,\mu)`-strongly regular. + Test whether some (pseudo)Orthogonal Array graph is `(v,k,\lambda,\mu)`-strongly regular. + + We know how to construct graphs with parameters of an Orthogonal Array (`OA(m,n)`), + also known as Latin squares graphs `L_m(n)`, in several cases where no orthogonal + array is known, or even in some cases for which they are known not to exist. + + Such graphs are usually called pseudo-Latin squares graphs. Namely, Sage + can construct a graph with parameters of an `OA(m,n)`-graph whenever there + exists a skew-Hadamard matrix of order `n+1`, and `m=(n+1)/2` or `m=(n-1)/2`. + The construction in the former case is due to Goethals-Seidel [BvL84]_, and in the + latter case due to Pasechnik [Pa92]_. INPUT: @@ -110,24 +120,48 @@ def is_orthogonal_array_block_graph(int v,int k,int l,int mu): OA(5,8): Graph on 64 vertices sage: g.is_strongly_regular(parameters=True) (64, 35, 18, 20) + sage: t=is_orthogonal_array_block_graph(225,98,43,42); t + (..., 4) + sage: g = t[0](*t[1:]); g + Pasechnik Graph_4: Graph on 225 vertices + sage: g.is_strongly_regular(parameters=True) + (225, 98, 43, 42) + sage: t=is_orthogonal_array_block_graph(225,112,55,56); t + (..., 4) + sage: g = t[0](*t[1:]); g + skewhad^2_4: Graph on 225 vertices + sage: g.is_strongly_regular(parameters=True) + (225, 112, 55, 56) sage: t = is_orthogonal_array_block_graph(5,5,5,5); t + + REFERENCE: + + .. [Pa92] D. V. Pasechnik, + Skew-symmetric association schemes with two classes and strongly + regular graphs of type `L_{2n-1}(4n- 1)`, + Acta Applicandaie Math. 29(1992), 129-138 """ # notations from # http://www.win.tue.nl/~aeb/graphs/OA.html - if not is_square(v): - return - n = int(sqrt(v)) - if k % (n-1): - return - m = k//(n-1) - if (l != (m-1)*(m-2)+n-2 or - mu != m*(m-1)): + from sage.combinat.matrices.hadamard_matrix import skew_hadamard_matrix + try: + m, n = latin_squares_graph_parameters(v,k,l,mu) + except: return if orthogonal_array(m,n,existence=True): from sage.graphs.generators.intersection import OrthogonalArrayBlockGraph return (lambda m,n : OrthogonalArrayBlockGraph(m, n), m,n) + elif n>2 and skew_hadamard_matrix(n+1, existence=True): + if m==(n+1)/2: + from sage.graphs.generators.families import SquaredSkewHadamardMatrixGraph as G + elif m==(n-1)/2: + from sage.graphs.generators.families import PasechnikGraph as G + else: + return + return (G, (n+1)/4) + @cached_function def is_johnson(int v,int k,int l,int mu): r""" @@ -340,13 +374,90 @@ def is_orthogonal_polar(int v,int k,int l,int mu): from sage.graphs.generators.classical_geometries import OrthogonalPolarGraph return (OrthogonalPolarGraph, 2*m, q, "-") +@cached_function +def is_goethals_seidel(int v,int k,int l,int mu): + r""" + Test whether some + :func:`~sage.graphs.graph_generators.GraphGenerators.GoethalsSeidelGraph` graph is + `(v,k,\lambda,\mu)`-strongly regular. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if one + exists, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_goethals_seidel + sage: t = is_goethals_seidel(28, 15, 6, 10); t + [, 3, 3] + sage: g = t[0](*t[1:]); g + Graph on 28 vertices + sage: g.is_strongly_regular(parameters=True) + (28, 15, 6, 10) + + sage: t = is_goethals_seidel(256, 135, 70, 72); t + [, 2, 15] + sage: g = t[0](*t[1:]); g + Graph on 256 vertices + sage: g.is_strongly_regular(parameters=True) + (256, 135, 70, 72) + + sage: t = is_goethals_seidel(5,5,5,5); t + + TESTS:: + + sage: for p in [(16, 9, 4, 6), (28, 15, 6, 10), (64, 35, 18, 20), (120, 63, 30, 36), + ....: (144, 77, 40, 42), (256, 135, 70, 72), (400, 209, 108, 110), + ....: (496, 255, 126, 136), (540, 275, 130, 150), (576, 299, 154, 156), + ....: (780, 399, 198, 210), (784, 405, 208, 210), (976, 495, 238, 264)]: + ....: print is_goethals_seidel(*p) + [, 2, 3] + [, 3, 3] + [, 2, 7] + [, 3, 7] + [, 2, 11] + [, 2, 15] + [, 2, 19] + [, 3, 15] + [, 5, 11] + [, 2, 23] + [, 3, 19] + [, 2, 27] + [, 5, 15] + """ + from sage.combinat.designs.bibd import balanced_incomplete_block_design + from sage.combinat.matrices.hadamard_matrix import hadamard_matrix + + # here we guess the parameters v_bibd,k_bibd and r_bibd of the block design + # + # - the number of vertices v is equal to v_bibd*(r_bibd+1) + # - the degree k of the graph is equal to k=(v+r_bibd-1)/2 + + r_bibd = k - (v-1-k) + v_bibd = v//(r_bibd+1) + k_bibd = (v_bibd-1)/r_bibd + 1 if r_bibd>0 else -1 + + if (v == v_bibd*(r_bibd+1) and + 2*k == v+r_bibd-1 and + 4*l == -2*v + 6*k -v_bibd -k_bibd and + hadamard_matrix(r_bibd+1, existence=True) and + balanced_incomplete_block_design(v_bibd, k_bibd, existence = True)): + from sage.graphs.generators.families import GoethalsSeidelGraph + return [GoethalsSeidelGraph, k_bibd, r_bibd] + @cached_function def is_NOodd(int v,int k,int l,int mu): r""" Test whether some NO^e(2n+1,q) graph is `(v,k,\lambda,\mu)`-strongly regular. - Here `q>2`, for in the case `q=2` this graph is complete. For more information, see - :func:`sage.graphs.generators.classical_geometries.NonisotropicOrthogonalPolarGraph` + Here `q>2`, for in the case `q=2` this graph is complete. For more + information, see + :func:`sage.graphs.graph_generators.GraphGenerators.NonisotropicOrthogonalPolarGraph` and Sect. 7.C of [BvL84]_. INPUT: @@ -415,7 +526,7 @@ def is_NOperp_F5(int v,int k,int l,int mu): Test whether some NO^e,perp(2n+1,5) graph is `(v,k,\lambda,\mu)`-strongly regular. For more information, see - :func:`sage.graphs.generators.classical_geometries.NonisotropicOrthogonalPolarGraph` + :func:`sage.graphs.graph_generators.GraphGenerators.NonisotropicOrthogonalPolarGraph` and Sect. 7.D of [BvL84]_. INPUT: @@ -470,8 +581,7 @@ def is_NO_F2(int v,int k,int l,int mu): Test whether some NO^e,perp(2n,2) graph is `(v,k,\lambda,\mu)`-strongly regular. For more information, see - :func:`sage.graphs.generators.classical_geometries.NonisotropicOrthogonalPolarGraph` - and + :func:`sage.graphs.graph_generators.GraphGenerators.NonisotropicOrthogonalPolarGraph`. INPUT: @@ -521,8 +631,7 @@ def is_NO_F3(int v,int k,int l,int mu): Test whether some NO^e,perp(2n,3) graph is `(v,k,\lambda,\mu)`-strongly regular. For more information, see - :func:`sage.graphs.generators.classical_geometries.NonisotropicOrthogonalPolarGraph` - and + :func:`sage.graphs.graph_generators.GraphGenerators.NonisotropicOrthogonalPolarGraph`. INPUT: @@ -576,7 +685,7 @@ def is_NU(int v,int k,int l,int mu): Test whether some NU(n,q)-graph, is `(v,k,\lambda,\mu)`-strongly regular. Note that n>2; for n=2 there is no s.r.g. For more information, see - :func:`sage.graphs.generators.classical_geometries.NonisotropicUnitaryPolarGraph` + :func:`sage.graphs.graph_generators.GraphGenerators.NonisotropicUnitaryPolarGraph` and series C14 in [Hu75]_. INPUT: @@ -637,6 +746,170 @@ def is_NU(int v,int k,int l,int mu): from sage.graphs.generators.classical_geometries import NonisotropicUnitaryPolarGraph return (NonisotropicUnitaryPolarGraph, n, q) +@cached_function +def is_polhill(int v,int k,int l,int mu): + r""" + Test whether some graph from [Polhill09]_ is `(1024,k,\lambda,\mu)`-strongly regular. + + .. NOTE:: + + This function does not actually explore *all* strongly regular graphs + produced in [Polhill09]_, but only those on 1024 vertices. + + John Polhill offered his help if we attempt to write a code to guess, + given `(v,k,\lambda,\mu)`, which of his construction must be applied to + find the graph. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if the + parameters match, and ``None`` otherwise. + + EXAMPLES:: + + sage: from sage.graphs.strongly_regular_db import is_polhill + sage: t = is_polhill(1024, 231, 38, 56); t + [. at ...>] + sage: g = t[0](*t[1:]); g # not tested (too long) + Graph on 1024 vertices + sage: g.is_strongly_regular(parameters=True) # not tested (too long) + (1024, 231, 38, 56) + sage: t = is_polhill(1024, 264, 56, 72); t + [. at ...>] + sage: t = is_polhill(1024, 297, 76, 90); t + [. at ...>] + sage: t = is_polhill(1024, 330, 98, 110); t + [. at ...>] + sage: t = is_polhill(1024, 462, 206, 210); t + [. at ...>] + + REFERENCE: + + .. [Polhill09] J. Polhill, + Negative Latin square type partial difference sets and + amorphic association schemes with Galois rings, + Journal of Combinatorial Designs 17, no. 3 (2009): 266-282. + http://onlinelibrary.wiley.com/doi/10.1002/jcd.20206/abstract + """ + if (v,k,l,mu) not in [(1024, 231, 38, 56), + (1024, 264, 56, 72), + (1024, 297, 76, 90), + (1024, 330, 98, 110), + (1024, 462, 206, 210)]: + return + + from itertools import product + from sage.categories.cartesian_product import cartesian_product + from sage.rings.finite_rings.integer_mod_ring import IntegerModRing + from copy import copy + + def additive_cayley(vertices): + g = Graph() + g.add_vertices(vertices[0].parent()) + edges = [(x,x+vv) + for vv in set(vertices) + for x in g] + g.add_edges(edges) + g.relabel() + return g + + # D is a Partial Difference Set of (Z4)^2, see section 2. + G = cartesian_product([IntegerModRing(4),IntegerModRing(4)]) + D = [ + [(2,0),(0,1),(0,3),(1,1),(3,3)], + [(1,0),(3,0),(0,2),(1,3),(3,1)], + [(1,2),(3,2),(2,1),(2,3),(2,2)] + ] + D = [map(G,x) for x in D] + + # The K_i are hyperplanes partitionning the nonzero elements of + # GF(2^s)^2. See section 6. + s = 3 + G1 = GF(2**s,'x') + Gp = cartesian_product([G1,G1]) + K = [Gp((x,1)) for x in G1]+[Gp((1,0))] + K = [[x for x in Gp if x[0]*uu+x[1]*vv == 0] for (uu,vv) in K] + + # We now define the P_{i,j}. see section 6. + + P = {} + P[0,1] = range((-1) + 1 , 2**(s-2)+1) + P[1,1] = range((-1) + 2**(s-2)+2 , 2**(s-1)+1) + P[2,1] = range((-1) + 2**(s-1)+2 , 2**(s-1)+2**(s-2)+1) + P[3,1] = range((-1) + 2**(s-1)+2**(s-2)+2, 2**(s)+1) + + P[0,2] = range((-1) + 2**(s-2)+2 , 2**(s-1)+2) + P[1,2] = range((-1) + 2**(s-1)+3 , 2**(s-1)+2**(s-2)+2) + P[2,2] = range((-1) + 2**(s-1)+2**(s-2)+3, 2**(s)+1) + [0] + P[3,2] = range((-1) + 2 , 2**(s-2)+1) + + P[0,3] = range((-1) + 2**(s-1)+3 , 2**(s-1)+2**(s-2)+3) + P[1,3] = range((-1) + 2**(s-1)+2**(s-2)+4, 2**(s)+1) + [0,1] + P[2,3] = range((-1) + 3 , 2**(s-2)+2) + P[3,3] = range((-1) + 2**(s-2)+3 , 2**(s-1)+2) + + P[0,4] = range((-1) + 2**(s-1)+2**(s-2)+4, 2**(s)+1) + P[1,4] = range((-1) + 3 , 2**(s-2)+1) + [2**(s-1)+1,2**(s-1)+2**(s-2)+2] + P[2,4] = range((-1) + 2**(s-2)+3 , 2**(s-1)+1) + [2**(s-1)+2**(s-2)+1,1] + P[3,4] = range((-1) + 2**(s-1)+3 , 2**(s-1)+2**(s-2)+1) + [2**(s-2)+1,0] + + R = {x:copy(P[x]) for x in P} + + for x in P: + P[x] = [K[i] for i in P[x]] + P[x] = set(sum(P[x],[])).difference([Gp((0,0))]) + + P[1,4].add(Gp((0,0))) + P[2,4].add(Gp((0,0))) + P[3,4].add(Gp((0,0))) + + # We now define the R_{i,j}. see *end* of section 6. + + R[0,3] = range((-1) + 2**(s-1)+3 , 2**(s-1)+2**(s-2)+2) + R[1,3] = range((-1) + 2**(s-1)+2**(s-2)+4, 2**(s)+1) + [0,1,2**(s-1)+2**(s-2)+2] + R[0,4] = range((-1) + 2**(s-1)+2**(s-2)+4, 2**(s)+1) + [2**(s-1)+2**(s-2)+2] + R[1,4] = range((-1) + 3 , 2**(s-2)+1) + [2**(s-1)+1] + + for x in R: + R[x] = [K[i] for i in R[x]] + R[x] = set(sum(R[x],[])).difference([Gp((0,0))]) + + R[1,3].add(Gp((0,0))) + R[2,4].add(Gp((0,0))) + R[3,4].add(Gp((0,0))) + + # Dabcd = Da, Db, Dc, Dd (cf. p273) + # D1234 = D1, D2, D3, D4 (cf. p276) + Dabcd = [] + D1234 = [] + + Gprod = cartesian_product([G,Gp]) + for DD,PQ in [(Dabcd,P), (D1234,R)]: + for i in range(1,5): + Dtmp = [product([G.zero()],PQ[0,i]), + product(D[0],PQ[1,i]), + product(D[1],PQ[2,i]), + product(D[2],PQ[3,i])] + Dtmp = map(set,Dtmp) + Dtmp = map(Gprod,sum(map(list,Dtmp),[])) + DD.append(Dtmp) + + # Now that we have the data, we can return the graphs. + if k == 231: + return [lambda :additive_cayley(Dabcd[0])] + if k == 264: + return [lambda :additive_cayley(D1234[2])] + if k == 297: + return [lambda :additive_cayley(D1234[0]+D1234[1]+D1234[2]).complement()] + if k == 330: + return [lambda :additive_cayley(Dabcd[0]+Dabcd[1]+Dabcd[2]).complement()] + if k == 462: + return [lambda :additive_cayley(Dabcd[0]+Dabcd[1])] + def is_RSHCD(int v,int k,int l,int mu): r""" Test whether some RSHCD graph is `(v,k,\lambda,\mu)`-strongly regular. @@ -1011,8 +1284,9 @@ def is_taylor_twograph_srg(int v,int k,int l,int mu): OUTPUT: A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph - :func:`TaylorTwographSRG ` - if the parameters match, and ``None`` otherwise. + :func:`TaylorTwographSRG + ` if the + parameters match, and ``None`` otherwise. EXAMPLES:: @@ -1043,6 +1317,52 @@ def is_taylor_twograph_srg(int v,int k,int l,int mu): return (TaylorTwographSRG, q) return +def is_switch_skewhad(int v, int k, int l, int mu): + r""" + Test whether some `switch skewhad^2+*` is `(v,k,\lambda,\mu)`-strongly regular. + + The `switch skewhad^2+*` graphs appear on `Andries Brouwer's database + `__ and are built by + adding an isolated vertex to the complement of + :func:`~sage.graphs.graph_generators.GraphGenerators.SquaredSkewHadamardMatrixGraph`, + and a :meth:`Seidel switching ` a set of disjoint + `n`-cocliques. + + INPUT: + + - ``v,k,l,mu`` (integers) + + OUTPUT: + + A tuple ``t`` such that ``t[0](*t[1:])`` builds the requested graph if the + parameters match, and ``None`` otherwise. + + EXAMPLES:: + + sage: graphs.strongly_regular_graph(226, 105, 48, 49) + switch skewhad^2+*_4: Graph on 226 vertices + + TESTS:: + + sage: from sage.graphs.strongly_regular_db import is_switch_skewhad + sage: t = is_switch_skewhad(5,5,5,5); t + + """ + from sage.combinat.matrices.hadamard_matrix import skew_hadamard_matrix + from sage.graphs.generators.families import SwitchedSquaredSkewHadamardMatrixGraph + cdef int n + r,s = eigenvalues(v,k,l,mu) + if r is None: + return + if r.switch_OA_srg at ..., 14, 29) """ - from sage.combinat.designs.orthogonal_arrays import orthogonal_array - cdef int n_2_p_1 = v cdef int n = floor(sqrt(n_2_p_1-1)) @@ -1130,6 +1448,41 @@ cdef eigenvalues(int v,int k,int l,int mu): return [(-b+sqrt(D))/2.0, (-b-sqrt(D))/2.0] + +cpdef latin_squares_graph_parameters(int v,int k, int l,int mu): + r""" + Check whether (v,k,l,mu)-strongly regular graph has parameters of an `L_g(n)` s.r.g. + + Also known as pseudo-OA(n,g) case, i.e. s.r.g. with parameters of an OA(n,g)-graph. + Return g and n, if they exist. See Sect. 9.1 of [BH12]_ for details. + + INPUT: + + - ``v,k,l,mu`` -- (integers) parameters of the graph + + OUTPUT: + + - ``(g, n)`` -- parameters of an `L_g(n)` graph, or `None` + + TESTS:: + + sage: from sage.graphs.strongly_regular_db import latin_squares_graph_parameters + sage: latin_squares_graph_parameters(9,4,1,2) + (2, 3) + sage: latin_squares_graph_parameters(5,4,1,2) + """ + cdef int g, n + r,s = eigenvalues(v,k,l,mu) + if r is None: + return + if r < s: + r, s = s, r + g = -s + n = r+g + if v==n**2 and k==g*(n-1) and l==(g-1)*(g-2)+n-2 and mu==g*(g-1): + return g, n + return + def _H_3_cayley_graph(L): r""" return the `L`-Cayley graph of the group `H_3` from Prop. 12 in [JK03]_. @@ -1563,7 +1916,7 @@ def SRG_276_140_58_84(): def SRG_280_135_70_60(): r""" - Return a strongly regular graph with parameters (280, 135, 70, 60). + Return a strongly regular graph with parameters `(280, 135, 70, 60)`. This graph is built from the action of `J_2` on a `3.PGL(2,9)` subgroup it contains. @@ -1589,6 +1942,49 @@ def SRG_280_135_70_60(): g.relabel() return g +def SRG_280_117_44_52(): + r""" + Return a strongly regular graph with parameters `(280, 117, 44, 52)`. + + This graph is built according to a very pretty construction of Mathon and + Rosa [MR85]_: + + The vertices of the graph `G` are all partitions of a set of 9 elements + into `\{\{a,b,c\},\{d,e,f\},\{g,h,i\}\}`. The cross-intersection of two + such partitions `P=\{P_1,P_2,P_3\}` and `P'=\{P'_1,P'_2,P'_3\}` being + defined as `\{P_i \cap P'_j: 1\leq i,j\leq 3\}`, two vertices of `G` are + set to be adjacent if the cross-intersection of their respective + partitions does not contain exactly 7 nonempty sets. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_280_117_44_52 + sage: g=SRG_280_117_44_52() + sage: g.is_strongly_regular(parameters=True) + (280, 117, 44, 52) + + REFERENCE: + + .. [MR85] R. Mathon and A. Rosa, + A new strongly regular graph, + Journal of Combinatorial Theory, Series A 38, no. 1 (1985): 84-86. + http://dx.doi.org/10.1016/0097-3165(85)90025-1 + """ + from sage.graphs.hypergraph_generators import hypergraphs + + # V is the set of partions {{a,b,c},{d,e,f},{g,h,i}} of {0,...,8} + H = hypergraphs.CompleteUniform(9,3) + g = H.intersection_graph() + V = g.complement().cliques_maximal() + V = map(frozenset,V) + + # G is the graph defined on V in which two vertices are adjacent when they + # corresponding partitions cross-intersect on 7 nonempty sets + G = Graph([V, lambda x,y: + sum(any(xxx in yy for xxx in xx) for xx in x for yy in y) != 7], + loops=False) + return G + def strongly_regular_from_two_weight_code(L): r""" Return a strongly regular graph from a two-weight code. @@ -1814,6 +2210,34 @@ def SRG_729_336_153_156(): L = Matrix(GF(3),map(list,L)).transpose() return strongly_regular_from_two_intersection_set(L) +def SRG_729_448_277_272(): + r""" + Return a `(729, 448, 277, 272)`-strongly regular graph. + + This graph is built from a ternary `[140, 6]` code with weights `90, 99`, + found by Axel Kohnert [Kohnert07]_ and shared by Alfred Wassermann. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_729_448_277_272 + sage: G = SRG_729_448_277_272() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (729, 448, 277, 272) + """ + x = ("10111011111111101110101110111100111011111011101111101001001111011011011111100100111101000111101111101100011011001111101110111110101111111001", + "01220121111211011101011101112101220022120121011222010110011010120110112112001101021010101111012211011000020110012221212101011101211122020011", + "22102021112110111120211021122012100012202220112110101200110101202102122120011110020201211110021210110000101200121222122010211022211210110101", + "11010221121101111102210221220221000111011101121102012101101012012022222000211200202012211100111201200001122001211011120102110212212102121001", + "20201121211111111012202022201210001220122121211010121011010020110121220201212002010222011001111012100011010212110021202021102112221012110011", + "02022222111111110112020112011200022102212222110102210110100101102211201211220020002120110011110221100110002121100222120211021112010112220101") + M = Matrix(GF(3),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + def SRG_729_532_391_380(): r""" Return a `(729, 532, 391, 380)`-strongly regular graph. @@ -1955,6 +2379,39 @@ def SRG_625_416_279_272(): M = Matrix(GF(5),[list(l) for l in x]) return strongly_regular_from_two_weight_code(LinearCode(M)) +def SRG_625_468_353_342(): + r""" + Return a `(625, 468, 353, 342)`-strongly regular graph. + + This graph is built from a two-weight code presented in [BJ03]_ (cf. Theorem + 4.1). + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_625_468_353_342 + sage: G = SRG_625_468_353_342() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (625, 468, 353, 342) + + REFERENCE: + + .. [BJ03] I. Bouyukliev and S. Juriaan, + Some new results on optimal codes over `F_5`, + Designs, Codes and Cryptography 30, no. 1 (2003): 97-111, + http://www.moi.math.bas.bg/moiuser/~iliya/pdf_site/gf5srev.pdf + """ + x = ("111111111111111111111111111111000000000", + "111111222222333333444444000000111111000", + "223300133440112240112240133440123400110", + "402340414201142301132013234230044330401") + M = Matrix(GF(5),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + def SRG_243_220_199_200(): r""" Return a `(243, 220, 199, 200)`-strongly regular graph. @@ -2163,6 +2620,44 @@ def SRG_512_219_106_84(): M = Matrix(GF(2),[list(l) for l in x]) return strongly_regular_from_two_weight_code(LinearCode(M)) +def SRG_512_315_202_180(): + r""" + Return a `(512, 315, 202, 180)`-strongly regular graph. + + This graph is built from a projective binary code with weights `32, 40`, + found by Axel Kohnert [Kohnert07]_ and shared by Alfred Wassermann. + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly + regular graph from a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_512_315_202_180 + sage: G = SRG_512_315_202_180() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (512, 315, 202, 180) + + REFERENCE: + + .. [Kohnert07] A. Kohnert, + Constructing two-weight codes with prescribed groups of automorphisms, + Discrete applied mathematics 155, no. 11 (2007): 1451-1457. + http://linearcodes.uni-bayreuth.de/twoweight/ + """ + x=("0100011110111000001011010110110111100010001000001000001001010110101101", + "1000111101110000000110101111101111000100010000010000000010101111001011", + "0001111011100000011101011101011110011000100000100000000101011100010111", + "0011110101000000111010111010111100110001000011000000001010111000101101", + "0111101000000001110101110111111001100010000100000000010101110011001011", + "1111010000000011101011101101110011010100001000000000101011100100010111", + "1110100010000111000111011011100110111000010000000001000111001010101101", + "1101000110001110001110110101001101110000100010000010001110010101001011", + "1010001110011100001101101010011011110001000100000100001100101010010111") + M = Matrix(GF(2),[list(l) for l in x]) + return strongly_regular_from_two_weight_code(LinearCode(M)) + def SRG_256_153_92_90(): r""" Return a `(256, 153, 92, 90)`-strongly regular graph. @@ -2325,6 +2820,45 @@ def SRG_81_50_31_30(): M = Matrix(GF(3),[list(l) for l in x]) return strongly_regular_from_two_weight_code(LinearCode(M)) +def SRG_1288_792_476_504(): + r""" + Return a `(1288, 792, 476, 504)`-strongly regular graph. + + This graph is built on the words of weight 12 in the + :func:`~sage.coding.code_constructions.BinaryGolayCode`. Two of them are + then made adjacent if their symmetric difference has weight 12 (cf + [BvE92]_). + + .. SEEALSO:: + + :func:`strongly_regular_from_two_weight_code` -- build a strongly regular graph from + a two-weight code. + + EXAMPLE:: + + sage: from sage.graphs.strongly_regular_db import SRG_1288_792_476_504 + sage: G = SRG_1288_792_476_504() # long time + sage: G.is_strongly_regular(parameters=True) # long time + (1288, 792, 476, 504) + + REFERENCE: + + .. [BvE92] A. Brouwer and C. Van Eijl, + On the p-Rank of the Adjacency Matrices of Strongly Regular Graphs + Journal of Algebraic Combinatorics (1992), vol.1, n.4, pp329-346, + http://dx.doi.org/10.1023/A%3A1022438616684 + """ + from sage.coding.code_constructions import BinaryGolayCode + C = BinaryGolayCode() + C = [[i for i,v in enumerate(c) if v] + for c in C] + C = [s for s in C if len(s) == 12] + G = Graph([map(frozenset,C), + lambda x,y:len(x.symmetric_difference(y))==12]) + G.relabel() + return G + + cdef bint seems_feasible(int v, int k, int l, int mu): r""" Tests is the set of parameters seems feasible @@ -2474,17 +3008,6 @@ def strongly_regular_graph(int v,int k,int l,int mu=-1,bint existence=False,bint (324,95,22,30)-strongly regular graph is known to exist. Comments: - A realizable set of parameters that Sage cannot realize (help us!):: - - sage: graphs.strongly_regular_graph(1288, 495, 206, existence=True) - True - sage: graphs.strongly_regular_graph(1288, 495, 206) - Traceback (most recent call last): - ... - RuntimeError: Andries Brouwer's database claims that such a (1288,495,206,180)-strongly - regular graph exists, but Sage does not know how to build it. - ... - A large unknown set of parameters (not in Andries Brouwer's database):: sage: graphs.strongly_regular_graph(1394,175,0,25,existence=True) @@ -2517,7 +3040,6 @@ def strongly_regular_graph(int v,int k,int l,int mu=-1,bint existence=False,bint return G constructions = { - ( 27, 16, 10, 8): [SchlaefliGraph], ( 36, 14, 4, 6): [Graph,('c~rLDEOcKTPO`U`HOIj@MWFLQFAaRIT`HIWqPsQQJ'+ 'DXGLqYM@gRLAWLdkEW@RQYQIErcgesClhKefC_ygSGkZ`OyHETdK[?lWStCapVgKK')], ( 50, 7, 0, 1): [HoffmanSingletonGraph], @@ -2549,20 +3071,28 @@ def strongly_regular_graph(int v,int k,int l,int mu=-1,bint existence=False,bint (256, 153, 92, 90): [SRG_256_153_92_90], (275, 112, 30, 56): [McLaughlinGraph], (276, 140, 58, 84): [SRG_276_140_58_84], + (280, 117, 44, 52): [SRG_280_117_44_52], (280, 135, 70, 60): [SRG_280_135_70_60], (416, 100, 36, 20): [SRG_416_100_36_20], (512, 219, 106, 84): [SRG_512_219_106_84], (512, 73, 12, 10): [SRG_512_73_12_10], + (512, 315, 202,180): [SRG_512_315_202_180], (560, 208, 72, 80): [SRG_560_208_72_80], - (625, 416, 279,272): [SRG_625_416_279_272], (625, 364, 213,210): [SRG_625_364_213_210], + (625, 416, 279,272): [SRG_625_416_279_272], + (625, 468, 353,342): [SRG_625_468_353_342], (729, 336, 153,156): [SRG_729_336_153_156], (729, 616, 523,506): [SRG_729_616_523_506], (729, 420, 243,240): [SRG_729_420_243_240], + (729, 448, 277,272): [SRG_729_448_277_272], (729, 560, 433,420): [SRG_729_560_433_420], (729, 476, 313,306): [SRG_729_476_313_306], (729, 532, 391,380): [SRG_729_532_391_380], + (784, 243, 82, 72): [MathonStronglyRegularGraph, 0], + (784, 270, 98, 90): [MathonStronglyRegularGraph, 1], + (784, 297, 116, 110):[MathonStronglyRegularGraph, 2], (1024,825, 668,650): [SRG_1024_825_668_650], + (1288,792, 476,504): [SRG_1288_792_476_504], (1782,416, 100, 96): [SuzukiGraph], } @@ -2576,13 +3106,16 @@ def strongly_regular_graph(int v,int k,int l,int mu=-1,bint existence=False,bint test_functions = [is_paley, is_johnson, is_orthogonal_array_block_graph, is_steiner, is_affine_polar, + is_goethals_seidel, is_orthogonal_polar, is_NOodd, is_NOperp_F5, is_NO_F2, is_NO_F3, is_NU, is_unitary_polar, is_unitary_dual_polar, is_GQqmqp, is_RSHCD, is_twograph_descendant_of_srg, is_taylor_twograph_srg, - is_switch_OA_srg] + is_switch_OA_srg, + is_polhill, + is_switch_skewhad] # Going through all test functions, for the set of parameters and its # complement. diff --git a/src/sage/groups/group_homset.py b/src/sage/groups/group_homset.py index 6e26f9d10a0..e65ecd27d9c 100644 --- a/src/sage/groups/group_homset.py +++ b/src/sage/groups/group_homset.py @@ -14,9 +14,6 @@ from sage.categories.all import HomsetWithBase, Groups import sage.rings.integer_ring -GROUPS = Groups() - - def is_GroupHomset(H): return isinstance(H, GroupHomset_generic) @@ -30,7 +27,7 @@ class GroupHomset_generic(HomsetWithBase): is undefined and morphism.GroupHomomorphism_im_gens is undefined. """ def __init__(self, G, H): - HomsetWithBase.__init__(self, G, H, GROUPS, sage.rings.integer_ringer.ZZ) + HomsetWithBase.__init__(self, G, H, Groups(), sage.rings.integer_ringer.ZZ) def _repr_(self): return "Set of Homomorphisms from %s to %s"%(self.domain(), self.codomain()) diff --git a/src/sage/groups/matrix_gps/coxeter_group.py b/src/sage/groups/matrix_gps/coxeter_group.py index bf1c04ec892..d4a46d4933b 100644 --- a/src/sage/groups/matrix_gps/coxeter_group.py +++ b/src/sage/groups/matrix_gps/coxeter_group.py @@ -23,6 +23,7 @@ from sage.categories.coxeter_groups import CoxeterGroups from sage.combinat.root_system.cartan_type import CartanType, CartanType_abstract +from sage.combinat.root_system.coxeter_matrix import CoxeterMatrix from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_generic from sage.groups.matrix_gps.group_element import MatrixGroupElement_generic from sage.graphs.graph import Graph @@ -31,7 +32,7 @@ from sage.rings.all import ZZ from sage.rings.infinity import infinity from sage.rings.universal_cyclotomic_field import UniversalCyclotomicField - +from sage.misc.superseded import deprecated_function_alias class CoxeterMatrixGroup(FinitelyGeneratedMatrixGroup_generic, UniqueRepresentation): r""" @@ -151,7 +152,7 @@ class CoxeterMatrixGroup(FinitelyGeneratedMatrixGroup_generic, UniqueRepresentat [ 3 1 2 15] [ 2 2 1 7] [ 3 15 7 1] - sage: G2 = W.coxeter_graph() + sage: G2 = W.coxeter_diagram() sage: CoxeterGroup(G2) is W True @@ -202,67 +203,13 @@ def __classcall_private__(cls, data, base_ring=None, index_set=None): sage: W4 = CoxeterGroup(G2) sage: W1 is W4 True - - Check with `\infty` because of the hack of using `-1` to represent - `\infty` in the Coxeter matrix:: - - sage: G = Graph([(0, 1, 3), (1, 2, oo)]) - sage: W1 = CoxeterGroup(matrix([[1, 3, 2], [3,1,-1], [2,-1,1]])) - sage: W2 = CoxeterGroup(G) - sage: W1 is W2 - True - sage: CoxeterGroup(W1.coxeter_graph()) is W1 - True """ - if isinstance(data, CartanType_abstract): - if index_set is None: - index_set = data.index_set() - data = data.coxeter_matrix() - elif isinstance(data, Graph): - G = data - n = G.num_verts() - - # Setup the basis matrix as all 2 except 1 on the diagonal - data = matrix(ZZ, [[2]*n]*n) - for i in range(n): - data[i, i] = ZZ.one() - - verts = G.vertices() - for e in G.edges(): - m = e[2] - if m is None: - m = 3 - elif m == infinity or m == -1: # FIXME: Hack because there is no ZZ\cup\{\infty\} - m = -1 - elif m <= 1: - raise ValueError("invalid Coxeter graph label") - i = verts.index(e[0]) - j = verts.index(e[1]) - data[j, i] = data[i, j] = m - - if index_set is None: - index_set = G.vertices() - else: - try: - data = matrix(data) - except (ValueError, TypeError): - data = CartanType(data).coxeter_matrix() - if not data.is_symmetric(): - raise ValueError("the Coxeter matrix is not symmetric") - if any(d != 1 for d in data.diagonal()): - raise ValueError("the Coxeter matrix diagonal is not all 1") - if any(val <= 1 and val != -1 for i, row in enumerate(data.rows()) - for val in row[i+1:]): - raise ValueError("invalid Coxeter label") - - if index_set is None: - index_set = range(data.nrows()) + data = CoxeterMatrix(data, index_set=index_set) if base_ring is None: base_ring = UniversalCyclotomicField() - data.set_immutable() return super(CoxeterMatrixGroup, cls).__classcall__(cls, - data, base_ring, tuple(index_set)) + data, base_ring, data.index_set()) def __init__(self, coxeter_matrix, base_ring, index_set): """ @@ -301,8 +248,7 @@ def __init__(self, coxeter_matrix, base_ring, index_set): True """ self._matrix = coxeter_matrix - self._index_set = index_set - n = ZZ(coxeter_matrix.nrows()) + n = coxeter_matrix.rank() # Compute the matrix with entries `2 \cos( \pi / m_{ij} )`. MS = MatrixSpace(base_ring, n, sparse=True) MC = MS._get_matrix_class() @@ -313,31 +259,19 @@ def __init__(self, coxeter_matrix, base_ring, index_set): from sage.functions.trig import cos from sage.symbolic.constants import pi val = lambda x: base_ring(2*cos(pi / x)) if x != -1 else base_ring(2) - gens = [MS.one() + MC(MS, entries={(i, j): val(coxeter_matrix[i, j]) + gens = [MS.one() + MC(MS, entries={(i, j): val(coxeter_matrix[index_set[i], index_set[j]]) for j in range(n)}, coerce=True, copy=True) for i in range(n)] - # Compute the matrix with entries `- \cos( \pi / m_{ij} )`. - # This describes the bilinear form corresponding to this - # Coxeter system, and might lead us out of our base ring. - base_field = base_ring.fraction_field() - MS2 = MatrixSpace(base_field, n, sparse=True) - MC2 = MS2._get_matrix_class() - self._bilinear = MC2(MS2, entries={(i, j): val(coxeter_matrix[i, j]) / base_field(-2) - for i in range(n) for j in range(n) - if coxeter_matrix[i, j] != 2}, - coerce=True, copy=True) - self._bilinear.set_immutable() category = CoxeterGroups() # Now we shall see if the group is finite, and, if so, refine # the category to ``category.Finite()``. Otherwise the group is # infinite and we refine the category to ``category.Infinite()``. - is_finite = self._finite_recognition() - if is_finite: + if self._matrix.is_finite(): category = category.Finite() else: category = category.Infinite() - FinitelyGeneratedMatrixGroup_generic.__init__(self, n, base_ring, + FinitelyGeneratedMatrixGroup_generic.__init__(self, ZZ(n), base_ring, gens, category=category) def _finite_recognition(self): @@ -512,7 +446,7 @@ def index_set(self): sage: W = CoxeterGroup([[1,3],[3,1]]) sage: W.index_set() - (0, 1) + (1, 2) sage: W = CoxeterGroup([[1,3],[3,1]], index_set=['x', 'y']) sage: W.index_set() ('x', 'y') @@ -520,7 +454,7 @@ def index_set(self): sage: W.index_set() (1, 2, 3) """ - return self._index_set + return self._matrix.index_set() def coxeter_matrix(self): """ @@ -540,37 +474,29 @@ def coxeter_matrix(self): """ return self._matrix - def coxeter_graph(self): + def coxeter_diagram(self): """ - Return the Coxeter graph of ``self``. + Return the Coxeter diagram of ``self``. EXAMPLES:: sage: W = CoxeterGroup(['H',3], implementation="reflection") - sage: G = W.coxeter_graph(); G + sage: G = W.coxeter_diagram(); G Graph on 3 vertices sage: G.edges() - [(1, 2, None), (2, 3, 5)] + [(1, 2, 3), (2, 3, 5)] sage: CoxeterGroup(G) is W True sage: G = Graph([(0, 1, 3), (1, 2, oo)]) sage: W = CoxeterGroup(G) - sage: W.coxeter_graph() == G + sage: W.coxeter_diagram() == G True - sage: CoxeterGroup(W.coxeter_graph()) is W + sage: CoxeterGroup(W.coxeter_diagram()) is W True """ - G = Graph() - G.add_vertices(self.index_set()) - for i, row in enumerate(self._matrix.rows()): - for j, val in enumerate(row[i+1:]): - if val == 3: - G.add_edge(self._index_set[i], self._index_set[i+1+j]) - elif val > 3: - G.add_edge(self._index_set[i], self._index_set[i+1+j], val) - elif val == -1: # FIXME: Hack because there is no ZZ\cup\{\infty\} - G.add_edge(self._index_set[i], self._index_set[i+1+j], infinity) - return G + return self._matrix.coxeter_graph() + + coxeter_graph = deprecated_function_alias(17798, coxeter_diagram) def bilinear_form(self): r""" @@ -596,7 +522,7 @@ def bilinear_form(self): [ 0 -1/2 1 0] [ 0 -1/2 0 1] """ - return self._bilinear + return self._matrix.bilinear_form() def is_finite(self): """ @@ -689,9 +615,9 @@ def simple_reflection(self, i): [ 0 1 0] [ 0 1 -1] """ - if not i in self._index_set: + if not i in self.index_set(): raise ValueError("%s is not in the index set %s" % (i, self.index_set())) - return self.gen(self._index_set.index(i)) + return self.gen(self.index_set().index(i)) class Element(MatrixGroupElement_generic): """ @@ -723,7 +649,7 @@ def has_right_descent(self, i): sage: map(lambda i: elt.has_right_descent(i), [1, 2, 3]) [True, False, True] """ - i = self.parent()._index_set.index(i) + i = self.parent().index_set().index(i) col = self.matrix().column(i) return all(x <= 0 for x in col) @@ -743,3 +669,4 @@ def canonical_matrix(self): [ 0 1 -1] """ return self.matrix() + diff --git a/src/sage/groups/matrix_gps/homset.py b/src/sage/groups/matrix_gps/homset.py index 0365797e4a5..f62de6173af 100644 --- a/src/sage/groups/matrix_gps/homset.py +++ b/src/sage/groups/matrix_gps/homset.py @@ -44,15 +44,15 @@ def is_MatrixGroupHomset(x): class MatrixGroupHomset(GroupHomset_generic): - def __init__(self, G, H): + def __init__(self, G, H, category=None): r""" Return the homset of two matrix groups. INPUT: - - ``G`` -- a matrix group. + - ``G`` -- a matrix group - - ``H`` -- a matrix group. + - ``H`` -- a matrix group OUTPUT: @@ -74,9 +74,8 @@ def __init__(self, G, H): [4 1], [0 1] ) """ - from sage.categories.groups import Groups from sage.categories.homset import HomsetWithBase - HomsetWithBase.__init__(self, G, H, Groups(), G.base_ring()) + HomsetWithBase.__init__(self, G, H, category, G.base_ring()) def __call__(self, im_gens, check=True): """ diff --git a/src/sage/groups/matrix_gps/matrix_group.py b/src/sage/groups/matrix_gps/matrix_group.py index a40675cb775..4195fa41e8a 100644 --- a/src/sage/groups/matrix_gps/matrix_group.py +++ b/src/sage/groups/matrix_gps/matrix_group.py @@ -400,9 +400,9 @@ def _Hom_(self, G, cat=None): INPUT: - - ``G`` -- group. The codomain. + - ``G`` -- group; the codomain - - ``cat`` -- a category. Must be unset. + - ``cat`` -- category; must be unset OUTPUT: @@ -420,13 +420,22 @@ def _Hom_(self, G, cat=None): [1 0] [1 2] [0 1], [3 4] ) + + TESTS: + + Check that :trac:`19407` is fixed:: + + sage: G = GL(2, GF(2)) + sage: H = GL(3, ZZ) + sage: Hom(G, H) + Set of Homomorphisms from General Linear Group of degree 2 + over Finite Field of size 2 to General Linear Group of degree 3 + over Integer Ring """ - if not (cat is None or (cat is G.category() and cat is self.category())): - raise TypeError if not is_MatrixGroup(G): raise TypeError("G (=%s) must be a matrix group."%G) import homset - return homset.MatrixGroupHomset(self, G) + return homset.MatrixGroupHomset(self, G, cat) def hom(self, x): """ diff --git a/src/sage/groups/matrix_gps/morphism.py b/src/sage/groups/matrix_gps/morphism.py index 775fb239b90..2d4a0e67d47 100644 --- a/src/sage/groups/matrix_gps/morphism.py +++ b/src/sage/groups/matrix_gps/morphism.py @@ -109,6 +109,19 @@ def __init__(self, homset, imgsH, check=True): sage: G = MatrixGroup([MS([3,0,0,1])]) sage: a = G.gens()[0]^2 sage: phi = G.hom([a]) + + TESTS: + + Check that :trac:`19406` is fixed:: + + sage: G = GL(2, GF(3)) + sage: H = GL(3, GF(2)) + sage: mat1 = H([[-1,0,0],[0,0,-1],[0,-1,0]]) + sage: mat2 = H([[1,1,1],[0,0,-1],[-1,0,0]]) + sage: phi = G.hom([mat1, mat2]) + Traceback (most recent call last): + ... + TypeError: images do not define a group homomorphism """ MatrixGroupMorphism.__init__(self, homset) # sets the parent from sage.libs.gap.libgap import libgap @@ -118,7 +131,7 @@ def __init__(self, homset, imgsH, check=True): imgs = [to_libgap(x) for x in imgsH] self._phi = phi = libgap.GroupHomomorphismByImages(G.gap(), H.gap(), gens, imgs) if not phi.IsGroupHomomorphism(): - raise ValueError('The map '+str(gensG)+'-->'+str(imgsH)+' is not a homomorphism') + raise ValueError('the map {}-->{} is not a homomorphism'.format(G.gens(), imgsH)) def gap(self): """ diff --git a/src/sage/homology/algebraic_topological_model.py b/src/sage/homology/algebraic_topological_model.py new file mode 100644 index 00000000000..dd3cc3c304d --- /dev/null +++ b/src/sage/homology/algebraic_topological_model.py @@ -0,0 +1,592 @@ +# -*- coding: utf-8 -*- +r""" +Algebraic topological model for a cell complex + +This file contains two functions, :func:`algebraic_topological_model` +and :func:`algebraic_topological_model_delta_complex`. The second +works more generally: for all simplicial, cubical, and +`\Delta`-complexes. The first only works for simplicial and cubical +complexes, but it is faster in those case. + +AUTHORS: + +- John H. Palmieri (2015-09) +""" + +######################################################################## +# Copyright (C) 2015 John H. Palmieri +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# http://www.gnu.org/licenses/ +######################################################################## + +# TODO: cythonize this. + +from sage.modules.free_module_element import vector +from sage.modules.free_module import VectorSpace +from sage.matrix.constructor import matrix, zero_matrix +from sage.matrix.matrix_space import MatrixSpace +from chain_complex import ChainComplex +from chain_complex_morphism import ChainComplexMorphism +from chain_homotopy import ChainContraction +from sage.rings.rational_field import QQ + +def algebraic_topological_model(K, base_ring=None): + r""" + Algebraic topological model for cell complex ``K`` + with coefficients in the field ``base_ring``. + + INPUT: + + - ``K`` -- either a simplicial complex or a cubical complex + - ``base_ring`` -- coefficient ring; must be a field + + OUTPUT: a pair ``(phi, M)`` consisting of + + - chain contraction ``phi`` + - chain complex `M` + + This construction appears in a paper by Pilarczyk and Réal [PR]_. + Given a cell complex `K` and a field `F`, there is a chain complex + `C` associated to `K` with coefficients in `F`. The *algebraic + topological model* for `K` is a chain complex `M` with trivial + differential, along with chain maps `\pi: C \to M` and `\iota: M + \to C` such that + + - `\pi \iota = 1_M`, and + - there is a chain homotopy `\phi` between `1_C` and `\iota \pi`. + + In particular, `\pi` and `\iota` induce isomorphisms on homology, + and since `M` has trivial differential, it is its own homology, + and thus also the homology of `C`. Thus `\iota` lifts homology + classes to their cycle representatives. + + The chain homotopy `\phi` satisfies some additional properties, + making it a *chain contraction*: + + - `\phi \phi = 0`, + - `\pi \phi = 0`, + - `\phi \iota = 0`. + + Given an algebraic topological model for `K`, it is then easy to + compute cup products and cohomology operations on the cohomology + of `K`, as described in [G-DR03]_ and [PR]_. + + Implementation details: the cell complex `K` must have an + :meth:`~sage.homology.cell_complex.GenericCellComplex.n_cells` + method from which we can extract a list of cells in each + dimension. Combining the lists in increasing order of dimension + then defines a filtration of the complex: a list of cells in which + the boundary of each cell consists of cells earlier in the + list. This is required by Pilarczyk and Réal's algorithm. There + must also be a + :meth:`~sage.homology.cell_complex.GenericCellComplex.chain_complex` + method, to construct the chain complex `C` associated to this + chain complex. + + In particular, this works for simplicial complexes and cubical + complexes. It doesn't work for `\Delta`-complexes, though: the list + of their `n`-cells has the wrong format. + + Note that from the chain contraction ``phi``, one can recover the + chain maps `\pi` and `\iota` via ``phi.pi()`` and + ``phi.iota()``. Then one can recover `C` and `M` from, for + example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, + respectively. + + EXAMPLES:: + + sage: from sage.homology.algebraic_topological_model import algebraic_topological_model + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: phi, M = algebraic_topological_model(RP2, GF(2)) + sage: M.homology() + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + sage: T = cubical_complexes.Torus() + sage: phi, M = algebraic_topological_model(T, QQ) + sage: M.homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + + If you want to work with cohomology rather than homology, just + dualize the outputs of this function:: + + sage: M.dual().homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + sage: M.dual().degree_of_differential() + 1 + sage: phi.dual() + Chain homotopy between: + Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field + and Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + + In degree 0, the inclusion of the homology `M` into the chain + complex `C` sends the homology generator to a single vertex:: + + sage: K = simplicial_complexes.Simplex(2) + sage: phi, M = algebraic_topological_model(K, QQ) + sage: phi.iota().in_degree(0) + [0] + [0] + [1] + + In cohomology, though, one needs the dual of every degree 0 cell + to detect the degree 0 cohomology generator:: + + sage: phi.dual().iota().in_degree(0) + [1] + [1] + [1] + + TESTS:: + + sage: T = cubical_complexes.Torus() + sage: C = T.chain_complex() + sage: H, M = T.algebraic_topological_model() + sage: C.differential(1) * H.iota().in_degree(1).column(0) == 0 + True + sage: C.differential(1) * H.iota().in_degree(1).column(1) == 0 + True + sage: coC = T.chain_complex(cochain=True) + sage: coC.differential(1) * H.dual().iota().in_degree(1).column(0) == 0 + True + sage: coC.differential(1) * H.dual().iota().in_degree(1).column(1) == 0 + True + """ + if not base_ring.is_field(): + raise ValueError('the coefficient ring must be a field') + + # The following are all dictionaries indexed by dimension. + # For each n, gens[n] is an ordered list of the n-cells generating the complex M. + gens = {} + # For each n, phi_dict[n] is a dictionary of the form {idx: + # vector}, where idx is the index of an n-cell in the list of + # n-cells in K, and vector is the image of that n-cell, as an + # element in the free module of (n+1)-chains for K. + phi_dict = {} + # For each n, pi_dict[n] is a dictionary of the same form, except + # that the target vectors should be elements of the chain complex M. + pi_dict = {} + # For each n, iota_dict[n] is a dictionary of the form {cell: + # vector}, where cell is one of the generators for M and vector is + # its image in C, as an element in the free module of n-chains. + iota_dict = {} + + for n in range(K.dimension()+1): + gens[n] = [] + phi_dict[n] = {} + pi_dict[n] = {} + iota_dict[n] = {} + + C = K.chain_complex(base_ring=base_ring) + # old_cells: cells one dimension lower. + old_cells = [] + + for dim in range(K.dimension()+1): + n_cells = K.n_cells(dim) + diff = C.differential(dim) + # diff is sparse and low density. Dense matrices are faster + # over finite fields, but for low density matrices, sparse + # matrices are faster over the rationals. + if base_ring != QQ: + diff = diff.dense_matrix() + + rank = len(n_cells) + old_rank = len(old_cells) + V_old = VectorSpace(base_ring, old_rank) + zero = V_old.zero_vector() + + for c_idx, c in enumerate(zip(n_cells, VectorSpace(base_ring, rank).gens())): + # c is the pair (cell, the corresponding standard basis + # vector in the free module of chains). Separate its + # components, calling them c and c_vec: + c_vec = c[1] + c = c[0] + # No need to set zero values for any of the maps: we will + # assume any unset values are zero. + # From the paper: phi_dict[c] = 0. + + # c_bar = c - phi(bdry(c)) + c_bar = c_vec + bdry_c = diff * c_vec + # Apply phi to bdry_c and subtract from c_bar. + for (idx, coord) in bdry_c.iteritems(): + try: + c_bar -= coord * phi_dict[dim-1][idx] + except KeyError: + pass + + bdry_c_bar = diff * c_bar + + # Evaluate pi(bdry(c_bar)). + pi_bdry_c_bar = zero + + for (idx, coeff) in bdry_c_bar.iteritems(): + try: + pi_bdry_c_bar += coeff * pi_dict[dim-1][idx] + except KeyError: + pass + + # One small typo in the published algorithm: it says + # "if bdry(c_bar) == 0", but should say + # "if pi(bdry(c_bar)) == 0". + if not pi_bdry_c_bar: + # Append c to list of gens. + gens[dim].append(c) + # iota(c) = c_bar + iota_dict[dim][c] = c_bar + # pi(c) = c + pi_dict[dim][c_idx] = c_vec + else: + # Take any u in gens so that lambda_i = != 0. + # u_idx will be the index of the corresponding cell. + for (u_idx, lambda_i) in pi_bdry_c_bar.iteritems(): + # Now find the actual cell. + u = old_cells[u_idx] + if u in gens[dim-1]: + break + + # pi(c) = 0: no need to do anything about this. + for c_j_idx in range(old_rank): + # eta_ij = . + try: + eta_ij = pi_dict[dim-1][c_j_idx][u_idx] + except (KeyError, IndexError): + eta_ij = 0 + if eta_ij: + # Adjust phi(c_j). + try: + phi_dict[dim-1][c_j_idx] += eta_ij * lambda_i**(-1) * c_bar + except KeyError: + phi_dict[dim-1][c_j_idx] = eta_ij * lambda_i**(-1) * c_bar + # Adjust pi(c_j). + try: + pi_dict[dim-1][c_j_idx] += -eta_ij * lambda_i**(-1) * pi_bdry_c_bar + except KeyError: + pi_dict[dim-1][c_j_idx] = -eta_ij * lambda_i**(-1) * pi_bdry_c_bar + + gens[dim-1].remove(u) + del iota_dict[dim-1][u] + old_cells = n_cells + + # Now we have constructed the raw data for M, pi, iota, phi, so we + # have to convert that to data which can be used to construct chain + # complexes, chain maps, and chain contractions. + + # M_data will contain (trivial) matrices defining the differential + # on M. Keep track of the sizes using "M_rows" and "M_cols", which are + # just the ranks of consecutive graded pieces of M. + M_data = {} + M_rows = 0 + # pi_data: the matrices defining pi. Similar for iota_data and phi_data. + pi_data = {} + iota_data = {} + phi_data = {} + for n in range(K.dimension()+1): + n_cells = K.n_cells(n) + # Remove zero entries from pi_dict and phi_dict. + pi_dict[n] = {i: pi_dict[n][i] for i in pi_dict[n] if pi_dict[n][i]} + phi_dict[n] = {i: phi_dict[n][i] for i in phi_dict[n] if phi_dict[n][i]} + # Convert gens to data defining the chain complex M with + # trivial differential. + M_cols = len(gens[n]) + M_data[n] = zero_matrix(base_ring, M_rows, M_cols) + M_rows = M_cols + # Convert the dictionaries for pi, iota, phi to matrices which + # will define chain maps and chain homotopies. + pi_cols = [] + phi_cols = [] + for (idx, c) in enumerate(n_cells): + # First pi: + if idx in pi_dict[n]: + column = vector(base_ring, M_rows) + for (entry, coeff) in pi_dict[n][idx].iteritems(): + # Translate from cells in n_cells to cells in gens[n]. + column[gens[n].index(n_cells[entry])] = coeff + else: + column = vector(base_ring, M_rows) + pi_cols.append(column) + + # Now phi: + try: + column = phi_dict[n][idx] + except KeyError: + column = vector(base_ring, len(K.n_cells(n+1))) + phi_cols.append(column) + # Now iota: + iota_cols = [iota_dict[n][c] for c in gens[n]] + + pi_data[n] = matrix(base_ring, pi_cols).transpose() + iota_data[n] = matrix(base_ring, len(gens[n]), len(n_cells), iota_cols).transpose() + phi_data[n] = matrix(base_ring, phi_cols).transpose() + + M = ChainComplex(M_data, base_ring=base_ring, degree=-1) + pi = ChainComplexMorphism(pi_data, C, M) + iota = ChainComplexMorphism(iota_data, M, C) + phi = ChainContraction(phi_data, pi, iota) + return phi, M + +def algebraic_topological_model_delta_complex(K, base_ring=None): + r""" + Algebraic topological model for cell complex ``K`` + with coefficients in the field ``base_ring``. + + This has the same basic functionality as + :func:`algebraic_topological_model`, but it also works for + `\Delta`-complexes. For simplicial and cubical complexes it is + somewhat slower, though. + + INPUT: + + - ``K`` -- a simplicial complex, a cubical complex, or a + `\Delta`-complex + - ``base_ring`` -- coefficient ring; must be a field + + OUTPUT: a pair ``(phi, M)`` consisting of + + - chain contraction ``phi`` + - chain complex `M` + + See :func:`algebraic_topological_model` for the main + documentation. The difference in implementation between the two: + this uses matrix and vector algebra. The other function does more + of the computations "by hand" and uses cells (given as simplices + or cubes) to index various dictionaries. Since the cells in + `\Delta`-complexes are not as nice, the other function does not + work for them, while this function relies almost entirely on the + structure of the associated chain complex. + + EXAMPLES:: + + sage: from sage.homology.algebraic_topological_model import algebraic_topological_model_delta_complex as AT_model + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: phi, M = AT_model(RP2, GF(2)) + sage: M.homology() + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + sage: T = delta_complexes.Torus() + sage: phi, M = AT_model(T, QQ) + sage: M.homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + + If you want to work with cohomology rather than homology, just + dualize the outputs of this function:: + + sage: M.dual().homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + sage: M.dual().degree_of_differential() + 1 + sage: phi.dual() + Chain homotopy between: + Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field + and Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + + In degree 0, the inclusion of the homology `M` into the chain + complex `C` sends the homology generator to a single vertex:: + + sage: K = delta_complexes.Simplex(2) + sage: phi, M = AT_model(K, QQ) + sage: phi.iota().in_degree(0) + [0] + [0] + [1] + + In cohomology, though, one needs the dual of every degree 0 cell + to detect the degree 0 cohomology generator:: + + sage: phi.dual().iota().in_degree(0) + [1] + [1] + [1] + + TESTS:: + + sage: T = cubical_complexes.Torus() + sage: C = T.chain_complex() + sage: H, M = AT_model(T, QQ) + sage: C.differential(1) * H.iota().in_degree(1).column(0) == 0 + True + sage: C.differential(1) * H.iota().in_degree(1).column(1) == 0 + True + sage: coC = T.chain_complex(cochain=True) + sage: coC.differential(1) * H.dual().iota().in_degree(1).column(0) == 0 + True + sage: coC.differential(1) * H.dual().iota().in_degree(1).column(1) == 0 + True + """ + def conditionally_sparse(m): + """ + Return a sparse matrix if the characteristic is zero. + + Multiplication of matrices with low density seems to be quicker + if the matrices are sparse, when over the rationals. Over + finite fields, dense matrices are faster regardless of + density. + """ + if base_ring == QQ: + return m.sparse_matrix() + else: + return m + + if not base_ring.is_field(): + raise ValueError('the coefficient ring must be a field') + + # The following are all dictionaries indexed by dimension. + # For each n, gens[n] is an ordered list of the n-cells generating the complex M. + gens = {} + pi_data = {} + phi_data = {} + iota_data = {} + + for n in range(-1, K.dimension()+1): + gens[n] = [] + + C = K.chain_complex(base_ring=base_ring) + n_cells = [] + pi_cols = [] + iota_cols = {} + + for dim in range(K.dimension()+1): + # old_cells: cells one dimension lower. + old_cells = n_cells + # n_cells: the standard basis for the vector space C.free_module(dim). + n_cells = C.free_module(dim).gens() + diff = C.differential(dim) + # diff is sparse and low density. Dense matrices are faster + # over finite fields, but for low density matrices, sparse + # matrices are faster over the rationals. + if base_ring != QQ: + diff = diff.dense_matrix() + + rank = len(n_cells) + old_rank = len(old_cells) + + # Create some matrix spaces to try to speed up matrix creation. + MS_pi_t = MatrixSpace(base_ring, old_rank, len(gens[dim-1])) + + pi_old = MS_pi_t.matrix(pi_cols).transpose() + iota_cols_old = iota_cols + iota_cols = {} + pi_cols_old = pi_cols + pi_cols = [] + phi_old = MatrixSpace(base_ring, rank, old_rank, sparse=(base_ring==QQ)).zero() + phi_old_cols = phi_old.columns() + phi_old = conditionally_sparse(phi_old) + to_be_deleted = [] + + zero_vector = vector(base_ring, rank) + pi_nrows = pi_old.nrows() + + for c_idx, c in enumerate(n_cells): + # c_bar = c - phi(bdry(c)): + # Avoid a bug in matrix-vector multiplication (trac 19378): + if not diff: + c_bar = c + pi_bdry_c_bar = False + else: + if base_ring == QQ: + c_bar = c - phi_old * (diff * c) + pi_bdry_c_bar = conditionally_sparse(pi_old) * (diff * c_bar) + else: + c_bar = c - phi_old * diff * c + pi_bdry_c_bar = conditionally_sparse(pi_old) * diff * c_bar + + # One small typo in the published algorithm: it says + # "if bdry(c_bar) == 0", but should say + # "if pi(bdry(c_bar)) == 0". + if not pi_bdry_c_bar: + # Append c to list of gens. + gens[dim].append(c_idx) + # iota(c) = c_bar + iota_cols[c_idx] = c_bar + # pi(c) = c + pi_cols.append(c) + else: + # Take any u in gens so that lambda_i = != 0. + # u_idx will be the index of the corresponding cell. + (u_idx, lambda_i) = pi_bdry_c_bar.leading_item() + for (u_idx, lambda_i) in pi_bdry_c_bar.iteritems(): + if u_idx not in to_be_deleted: + break + # This element/column needs to be deleted from gens and + # iota_old. Do that later. + to_be_deleted.append(u_idx) + # pi(c) = 0. + pi_cols.append(zero_vector) + for c_j_idx, c_j in enumerate(old_cells): + # eta_ij = . + # That is, eta_ij is the u_idx entry in the vector pi_old * c_j: + eta_ij = c_j.dot_product(pi_old.row(u_idx)) + if eta_ij: + # Adjust phi(c_j). + phi_old_cols[c_j_idx] += eta_ij * lambda_i**(-1) * c_bar + # Adjust pi(c_j). + pi_cols_old[c_j_idx] -= eta_ij * lambda_i**(-1) * pi_bdry_c_bar + + # The matrices involved have many zero entries. For + # such matrices, using sparse matrices is faster over + # the rationals, slower over finite fields. + phi_old = matrix(base_ring, phi_old_cols, sparse=(base_ring==QQ)).transpose() + keep = vector(base_ring, pi_nrows, {i:1 for i in range(pi_nrows) + if i not in to_be_deleted}) + cols = [v.pairwise_product(keep) for v in pi_cols_old] + pi_old = MS_pi_t.matrix(cols).transpose() + + # Here cols is a temporary storage for the columns of iota. + cols = [iota_cols_old[i] for i in sorted(iota_cols_old.keys())] + for r in sorted(to_be_deleted, reverse=True): + del cols[r] + del gens[dim-1][r] + iota_data[dim-1] = matrix(base_ring, len(gens[dim-1]), old_rank, cols).transpose() + # keep: rows to keep in pi_cols_old. Start with all + # columns, then delete those in to_be_deleted. + keep = sorted(set(range(pi_nrows)).difference(to_be_deleted)) + # Now cols is a temporary storage for columns of pi. + cols = [v.list_from_positions(keep) for v in pi_cols_old] + pi_data[dim-1] = matrix(base_ring, old_rank, len(gens[dim-1]), cols).transpose() + phi_data[dim-1] = phi_old + + V_gens = VectorSpace(base_ring, len(gens[dim])) + if pi_cols: + cols = [] + for v in pi_cols: + cols.append(V_gens(v.list_from_positions(gens[dim]))) + pi_cols = cols + + pi_data[dim] = matrix(base_ring, rank, len(gens[dim]), pi_cols).transpose() + cols = [iota_cols[i] for i in sorted(iota_cols.keys())] + iota_data[dim] = matrix(base_ring, len(gens[dim]), rank, cols).transpose() + + # M_data will contain (trivial) matrices defining the differential + # on M. Keep track of the sizes using "M_rows" and "M_cols", which are + # just the ranks of consecutive graded pieces of M. + M_data = {} + M_rows = 0 + for n in range(K.dimension()+1): + M_cols = len(gens[n]) + M_data[n] = zero_matrix(base_ring, M_rows, M_cols) + M_rows = M_cols + + M = ChainComplex(M_data, base_ring=base_ring, degree=-1) + + pi = ChainComplexMorphism(pi_data, C, M) + iota = ChainComplexMorphism(iota_data, M, C) + phi = ChainContraction(phi_data, pi, iota) + return phi, M + diff --git a/src/sage/homology/cell_complex.py b/src/sage/homology/cell_complex.py index 8bbd56b3ad6..f0767a22201 100644 --- a/src/sage/homology/cell_complex.py +++ b/src/sage/homology/cell_complex.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" Generic cell complexes @@ -37,6 +38,8 @@ from sage.structure.sage_object import SageObject from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ +from sage.combinat.free_module import CombinatorialFreeModule, CombinatorialFreeModuleElement +from sage.misc.cachefunc import cached_method class GenericCellComplex(SageObject): r""" @@ -410,7 +413,7 @@ def homology(self, dim=None, **kwds): .. note:: The keyword arguments to this function get passed on to - :meth:``chain_complex`` and its homology. + :meth:`chain_complex` and its homology. ALGORITHM: @@ -422,14 +425,14 @@ def homology(self, dim=None, **kwds): CHomP computes homology, not cohomology, and only works over the integers or finite prime fields. Therefore if any of these conditions fails, or if CHomP is not present, or if - ``algorithm`` is set to 'no_chomp', go to plan B: if ``self`` + ``algorithm`` is set to 'no_chomp', go to plan B: if this complex has a ``_homology`` method -- each simplicial complex has this, for example -- then call that. Such a method implements specialized algorithms for the particular type of cell complex. Otherwise, move on to plan C: compute the chain complex of - ``self`` and compute its homology groups. To do this: over a + this complex and compute its homology groups. To do this: over a field, just compute ranks and nullities, thus obtaining dimensions of the homology groups as vector spaces. Over the integers, compute Smith normal form of the boundary matrices @@ -650,6 +653,266 @@ def betti(self, dim=None, subcomplex=None): except AttributeError: return H.dimension() + def n_chains(self, n, base_ring=None, cochains=False): + r""" + Return the free module of chains in degree ``n`` over ``base_ring``. + + INPUTS: + + - ``n`` -- integer + - ``base_ring`` -- ring (optional, default `\ZZ`) + - ``cochains`` -- boolean (optional, default ``False``); if + ``True``, return cochains instead + + The only difference between chains and cochains is + notation. In a simplicial complex, for example, a simplex + ``(0,1,2)`` is written as "(0,1,2)" in the group of chains but + as "\chi_(0,1,2)" in the group of cochains. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: S2.n_chains(1, QQ) + Free module generated by {(2, 3), (0, 2), (1, 3), (1, 2), (0, 3), (0, 1)} over Rational Field + sage: list(simplicial_complexes.Sphere(2).n_chains(1, QQ, cochains=False).basis()) + [(2, 3), (0, 2), (1, 3), (1, 2), (0, 3), (0, 1)] + sage: list(simplicial_complexes.Sphere(2).n_chains(1, QQ, cochains=True).basis()) + [\chi_(2, 3), \chi_(0, 2), \chi_(1, 3), \chi_(1, 2), \chi_(0, 3), \chi_(0, 1)] + """ + return Chains(tuple(self.n_cells(n)), base_ring, cochains) + + # This is cached for speed reasons: it can be very slow to run + # this function. + @cached_method + def algebraic_topological_model(self, base_ring=None): + r""" + Algebraic topological model for this cell complex with + coefficients in ``base_ring``. + + The term "algebraic topological model" is defined by Pilarczyk + and Réal [PR]_. + + This is implemented for simplicial, cubical, and + `\Delta`-complexes, not for arbitrary generic cell complexes. + + INPUT: + + - ``base_ring`` - coefficient ring (optional, default + ``QQ``). Must be a field. + + Denote by `C` the chain complex associated to this cell + complex. The algebraic topological model is a chain complex + `M` with zero differential, with the same homology as `C`, + along with chain maps `\pi: C \to M` and `\iota: M \to C` + satisfying `\iota \pi = 1_M` and `\pi \iota` chain homotopic + to `1_C`. The chain homotopy `\phi` must satisfy + + - `\phi \phi = 0`, + - `\pi \phi = 0`, + - `\phi \iota = 0`. + + Such a chain homotopy is called a *chain contraction*. + + OUTPUT: a pair consisting of + + - chain contraction ``phi`` associated to `C`, `M`, `\pi`, and + `\iota` + - the chain complex `M` + + Note that from the chain contraction ``phi``, one can recover the + chain maps `\pi` and `\iota` via ``phi.pi()`` and + ``phi.iota()``. Then one can recover `C` and `M` from, for + example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, + respectively. + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: phi, M = RP2.algebraic_topological_model(GF(2)) + sage: M.homology() + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + sage: T = simplicial_complexes.Torus() + sage: phi, M = T.algebraic_topological_model(QQ) + sage: M.homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + """ + from algebraic_topological_model import algebraic_topological_model, algebraic_topological_model_delta_complex + from cubical_complex import CubicalComplex + from simplicial_complex import SimplicialComplex + from delta_complex import DeltaComplex + if base_ring is None: + base_ring = QQ + if not isinstance(self, (CubicalComplex, SimplicialComplex, DeltaComplex)): + raise NotImplementedError('only implemented for simplicial, cubical, and Delta complexes') + if isinstance(self, DeltaComplex): + return algebraic_topological_model_delta_complex(self, base_ring) + return algebraic_topological_model(self, base_ring) + + def homology_with_basis(self, base_ring=None, cohomology=False): + r""" + Return the unreduced homology of this complex with + coefficients in ``base_ring`` with a chosen basis. + + This is implemented for simplicial, cubical, and + `\Delta`-complexes, not for arbitrary generic cell complexes. + + INPUTS: + + - ``base_ring`` -- coefficient ring (optional, default + ``QQ``); must be a field + - ``cohomology`` -- boolean (optional, default ``False``); if + ``True``, return cohomology instead of homology + + Homology basis elements are named 'h_{dim,i}' where i ranges + between 0 and `r-1`, if `r` is the rank of the homology + group. Cohomology basis elements are denoted `h^{dim,i}` + instead. + + .. SEEALSO:: + + If ``cohomology`` is ``True``, this returns the cohomology + as a graded module. For the ring structure, use + :meth:`cohomology_ring`. + + EXAMPLES:: + + sage: K = simplicial_complexes.KleinBottle() + sage: H = K.homology_with_basis(QQ); H + Homology module of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets over Rational Field + sage: sorted(H.basis(), key=str) + [h_{0,0}, h_{1,0}] + sage: H = K.homology_with_basis(GF(2)); H + Homology module of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets over Finite Field of size 2 + sage: sorted(H.basis(), key=str) + [h_{0,0}, h_{1,0}, h_{1,1}, h_{2,0}] + + The homology is constructed as a graded object, so for + example, you can ask for the basis in a single degree:: + + sage: H.basis(1) + Finite family {(1, 0): h_{1,0}, (1, 1): h_{1,1}} + sage: S3 = delta_complexes.Sphere(3) + sage: H = S3.homology_with_basis(QQ, cohomology=True) + sage: list(H.basis(3)) + [h^{3,0}] + """ + from homology_vector_space_with_basis import HomologyVectorSpaceWithBasis + if base_ring is None: + base_ring = QQ + return HomologyVectorSpaceWithBasis(base_ring, self, cohomology) + + def cohomology_ring(self, base_ring=None): + r""" + Return the unreduced cohomology with coefficients in + ``base_ring`` with a chosen basis. + + This is implemented for simplicial, cubical, and + `\Delta`-complexes, not for arbitrary generic cell complexes. + The resulting elements are suitable for computing cup + products. For simplicial complexes, they should be suitable + for computing cohomology operations; so far, only mod 2 + cohomology operations have been implemented. + + INPUTS: + + - ``base_ring`` -- coefficient ring (optional, default + ``QQ``); must be a field + + The basis elements in dimension ``dim`` are named 'h^{dim,i}' + where `i` ranges between 0 and `r-1`, if `r` is the rank of + the cohomology group. + + .. NOTE:: + + For all but the smallest complexes, this is likely to be + slower than :meth:`cohomology` (with field coefficients), + possibly by several orders of magnitute. This and its + companion :meth:`homology_with_basis` carry extra + information which allows computation of cup products, for + example, but because of speed issues, you may only wish to + use these if you need that extra information. + + EXAMPLES:: + + sage: K = simplicial_complexes.KleinBottle() + sage: H = K.cohomology_ring(QQ); H + Cohomology ring of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets over Rational Field + sage: sorted(H.basis(), key=str) + [h^{0,0}, h^{1,0}] + sage: H = K.cohomology_ring(GF(2)); H + Cohomology ring of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets over Finite Field of size 2 + sage: sorted(H.basis(), key=str) + [h^{0,0}, h^{1,0}, h^{1,1}, h^{2,0}] + + sage: X = delta_complexes.SurfaceOfGenus(2) + sage: H = X.cohomology_ring(QQ); H + Cohomology ring of Delta complex with 3 vertices and 29 simplices + over Rational Field + sage: sorted(H.basis(1), key=str) + [h^{1,0}, h^{1,1}, h^{1,2}, h^{1,3}] + + sage: H = simplicial_complexes.Torus().cohomology_ring(QQ); H + Cohomology ring of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6) and 14 facets over Rational Field + sage: x = H.basis()[1,0]; x + h^{1,0} + sage: y = H.basis()[1,1]; y + h^{1,1} + + You can compute cup products of cohomology classes:: + + sage: x.cup_product(y) + h^{2,0} + sage: x * y # alternate notation + h^{2,0} + sage: y.cup_product(x) + -h^{2,0} + sage: x.cup_product(x) + 0 + + Cohomology operations:: + + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: K = RP2.suspension() + sage: K.set_immutable() + sage: y = K.cohomology_ring(GF(2)).basis()[2,0]; y + h^{2,0} + sage: y.Sq(1) + h^{3,0} + + To compute the cohomology ring, the complex must be + "immutable". This is only relevant for simplicial complexes, + and most simplicial complexes are immutable, but certain + constructions make them mutable. The suspension is one + example, and this is the reason for calling + ``K.set_immutable()`` above. Another example:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: T = S1.product(S1) + sage: T.is_immutable() + False + sage: T.cohomology_ring() + Traceback (most recent call last): + ... + ValueError: This simplicial complex must be immutable. Call set_immutable(). + sage: T.set_immutable() + sage: T.cohomology_ring() + Cohomology ring of Simplicial complex with 9 vertices and + 18 facets over Rational Field + """ + from homology_vector_space_with_basis import CohomologyRing + if base_ring is None: + base_ring = QQ + return CohomologyRing(base_ring, self) + ############################################################ # end of chain complexes, homology ############################################################ @@ -784,3 +1047,133 @@ def _repr_(self): else: cells_string = " and 1 %s" % cell_name return Name + " complex " + vertex_string + cells_string + + +class Chains(CombinatorialFreeModule): + r""" + Class for the free module of chains and/or cochains in a given + degree. + + INPUT: + + - ``n_cells`` -- tuple of `n`-cells, which thus forms a basis for + this module + - ``base_ring`` -- optional (default `\ZZ`) + - ``cochains`` -- boolean (optional, default ``False``); if + ``True``, return cochains instead + + One difference between chains and cochains is notation. In a + simplicial complex, for example, a simplex ``(0,1,2)`` is written + as "(0,1,2)" in the group of chains but as "\chi_(0,1,2)" in the + group of cochains. + + Also, since the free modules of chains and cochains are dual, + there is a pairing `\langle c, z \rangle`, sending a cochain `c` + and a chain `z` to a scalar. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: C_2 = S2.n_chains(1) + sage: C_2_co = S2.n_chains(1, cochains=True) + sage: x = C_2.basis()[Simplex((0,2))] + sage: y = C_2.basis()[Simplex((1,3))] + sage: z = x+2*y + sage: a = C_2_co.basis()[Simplex((1,3))] + sage: b = C_2_co.basis()[Simplex((0,3))] + sage: c = 3*a-2*b + sage: z + (0, 2) + 2*(1, 3) + sage: c + -2*\chi_(0, 3) + 3*\chi_(1, 3) + sage: c.eval(z) + 6 + """ + def __init__(self, n_cells, base_ring=None, cochains=False): + """ + EXAMPLES:: + + sage: T = cubical_complexes.Torus() + sage: T.n_chains(2, QQ) + Free module generated by {[1,1] x [0,1] x [1,1] x [0,1], + [0,0] x [0,1] x [0,1] x [1,1], [0,0] x [0,1] x [1,1] x [0,1], + [0,0] x [0,1] x [0,0] x [0,1], [0,1] x [1,1] x [0,1] x [0,0], + [0,1] x [0,0] x [0,0] x [0,1], [1,1] x [0,1] x [0,1] x [0,0], + [0,1] x [1,1] x [0,0] x [0,1], [0,0] x [0,1] x [0,1] x [0,0], + [0,1] x [0,0] x [0,1] x [0,0], [0,1] x [0,0] x [1,1] x [0,1], + [0,1] x [1,1] x [1,1] x [0,1], [0,1] x [0,0] x [0,1] x [1,1], + [1,1] x [0,1] x [0,0] x [0,1], [1,1] x [0,1] x [0,1] x [1,1], + [0,1] x [1,1] x [0,1] x [1,1]} over Rational Field + sage: T.n_chains(2).dimension() + 16 + + TESTS:: + + sage: T.n_chains(2).base_ring() + Integer Ring + sage: T.n_chains(8).dimension() + 0 + sage: T.n_chains(-3).dimension() + 0 + """ + if base_ring is None: + base_ring=ZZ + self._cochains = cochains + if cochains: + CombinatorialFreeModule.__init__(self, base_ring, n_cells, + prefix='\\chi', bracket=['_', '']) + else: + CombinatorialFreeModule.__init__(self, base_ring, n_cells, + prefix='', bracket=False) + + class Element(CombinatorialFreeModuleElement): + + def eval(self, other): + """ + Evaluate this cochain on the chain ``other``. + + INPUT: + + - ``other`` -- a chain for the same cell complex in the + same dimension with the same base ring + + OUTPUT: scalar + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: C_2 = S2.n_chains(1) + sage: C_2_co = S2.n_chains(1, cochains=True) + sage: x = C_2.basis()[Simplex((0,2))] + sage: y = C_2.basis()[Simplex((1,3))] + sage: z = x+2*y + sage: a = C_2_co.basis()[Simplex((1,3))] + sage: b = C_2_co.basis()[Simplex((0,3))] + sage: c = 3*a-2*b + sage: z + (0, 2) + 2*(1, 3) + sage: c + -2*\chi_(0, 3) + 3*\chi_(1, 3) + sage: c.eval(z) + 6 + + TESTS:: + + sage: z.eval(c) # z is not a cochain + Traceback (most recent call last): + ... + ValueError: this element is not a cochain + sage: c.eval(c) # can't evaluate a cochain on a cochain + Traceback (most recent call last): + ... + ValueError: the elements are not compatible + """ + if not self.parent()._cochains: + raise ValueError('this element is not a cochain') + if not (other.parent().indices() == self.parent().indices() + and other.base_ring() == self.base_ring() + and not other.parent()._cochains): + raise ValueError('the elements are not compatible') + result = sum(coeff * other.coefficient(cell) for cell, coeff in self) + return result + diff --git a/src/sage/homology/chain_complex_homspace.py b/src/sage/homology/chain_complex_homspace.py index d63f9e690e7..4bfc7e2da34 100644 --- a/src/sage/homology/chain_complex_homspace.py +++ b/src/sage/homology/chain_complex_homspace.py @@ -23,7 +23,9 @@ sage: i = H.identity() sage: x = i.associated_chain_complex_morphism(augmented=True) sage: x - Chain complex morphism from Chain complex with at most 4 nonzero terms over Integer Ring to Chain complex with at most 4 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 4 nonzero terms over Integer Ring + To: Chain complex with at most 4 nonzero terms over Integer Ring sage: x._matrix_dictionary {-1: [1], 0: [1 0 0 0 0 0 0 0 0] [0 1 0 0 0 0 0 0 0] @@ -62,11 +64,15 @@ sage: i = A.identity() sage: x = i.associated_chain_complex_morphism() sage: x - Chain complex morphism from Chain complex with at most 3 nonzero terms over Integer Ring to Chain complex with at most 3 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Integer Ring + To: Chain complex with at most 3 nonzero terms over Integer Ring sage: y = x*4 sage: z = y*y sage: (y+z) - Chain complex morphism from Chain complex with at most 3 nonzero terms over Integer Ring to Chain complex with at most 3 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Integer Ring + To: Chain complex with at most 3 nonzero terms over Integer Ring sage: f = x._matrix_dictionary sage: C = S.chain_complex() sage: G = Hom(C,C) @@ -93,7 +99,7 @@ #***************************************************************************** import sage.categories.homset -import sage.homology.chain_complex_morphism as chain_complex_morphism +from sage.homology.chain_complex_morphism import ChainComplexMorphism def is_ChainComplexHomspace(x): """ @@ -142,4 +148,4 @@ def __call__(self, f): True """ - return chain_complex_morphism.ChainComplexMorphism(f, self.domain(), self.codomain()) + return ChainComplexMorphism(f, self.domain(), self.codomain()) diff --git a/src/sage/homology/chain_complex_morphism.py b/src/sage/homology/chain_complex_morphism.py index 4f3e6ffc9a2..5e79d25db3b 100644 --- a/src/sage/homology/chain_complex_morphism.py +++ b/src/sage/homology/chain_complex_morphism.py @@ -14,7 +14,6 @@ EXAMPLES:: - from sage.matrix.constructor import zero_matrix sage: S = simplicial_complexes.Sphere(1) sage: S Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} @@ -27,7 +26,7 @@ sage: G = Hom(C,C) sage: x = G(f) sage: x - Chain complex morphism from Chain complex with at most 2 nonzero terms over Integer Ring to Chain complex with at most 2 nonzero terms over Integer Ring + Chain complex endomorphism of Chain complex with at most 2 nonzero terms over Integer Ring sage: x._matrix_dictionary {0: [0 0 0] [0 0 0] @@ -52,9 +51,10 @@ # #***************************************************************************** -import sage.matrix.all as matrix -from sage.structure.sage_object import SageObject -from sage.rings.integer_ring import ZZ +from sage.matrix.constructor import block_diagonal_matrix, zero_matrix +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom +from sage.categories.category_types import ChainComplexes def is_ChainComplexMorphism(x): """ @@ -71,14 +71,15 @@ def is_ChainComplexMorphism(x): sage: i = H.identity() sage: x = i.associated_chain_complex_morphism() sage: x # indirect doctest - Chain complex morphism from Chain complex with at most 7 nonzero terms over - Integer Ring to Chain complex with at most 7 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 7 nonzero terms over Integer Ring + To: Chain complex with at most 7 nonzero terms over Integer Ring sage: is_ChainComplexMorphism(x) True """ return isinstance(x,ChainComplexMorphism) -class ChainComplexMorphism(SageObject): +class ChainComplexMorphism(Morphism): """ An element of this class is a morphism of chain complexes. """ @@ -88,7 +89,6 @@ def __init__(self, matrices, C, D, check=True): EXAMPLES:: - from sage.matrix.constructor import zero_matrix sage: S = simplicial_complexes.Sphere(1) sage: S Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} @@ -101,9 +101,7 @@ def __init__(self, matrices, C, D, check=True): sage: G = Hom(C,C) sage: x = G(f) sage: x - Chain complex morphism from Chain complex with at most 2 nonzero terms - over Integer Ring to Chain complex with at most 2 nonzero terms over - Integer Ring + Chain complex endomorphism of Chain complex with at most 2 nonzero terms over Integer Ring sage: x._matrix_dictionary {0: [0 0 0] [0 0 0] @@ -117,9 +115,9 @@ def __init__(self, matrices, C, D, check=True): sage: Y = simplicial_complexes.Simplex(0) sage: g = Hom(X,Y)({0:0, 1:0}) sage: g.associated_chain_complex_morphism() - Chain complex morphism from Chain complex with at most 2 nonzero - terms over Integer Ring to Chain complex with at most 1 nonzero terms - over Integer Ring + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 1 nonzero terms over Integer Ring Check that an error is raised if the matrices are the wrong size:: @@ -130,7 +128,9 @@ def __init__(self, matrices, C, D, check=True): ... ValueError: matrix in degree 0 is not the right size sage: Hom(C,D)({0: matrix(2, 1, [1, 1])}) # 2x1 is right. - Chain complex morphism from Chain complex with at most 1 nonzero terms over Integer Ring to Chain complex with at most 1 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 1 nonzero terms over Integer Ring + To: Chain complex with at most 1 nonzero terms over Integer Ring """ if not C.base_ring() == D.base_ring(): raise NotImplementedError('morphisms between chain complexes of different' @@ -151,9 +151,9 @@ def __init__(self, matrices, C, D, check=True): try: matrices[i] = initial_matrices.pop(i) except KeyError: - matrices[i] = matrix.zero_matrix(C.base_ring(), - D.differential(i).ncols(), - C.differential(i).ncols(), sparse=True) + matrices[i] = zero_matrix(C.base_ring(), + D.differential(i).ncols(), + C.differential(i).ncols(), sparse=True) if check: # All remaining matrices given must be 0x0. if not all(m.ncols() == m.nrows() == 0 for m in initial_matrices.values()): @@ -177,9 +177,13 @@ def __init__(self, matrices, C, D, check=True): mC = matrices[i+d] * C.differential(i) if mC != Dm: raise ValueError('matrices must define a chain complex morphism') - self._matrix_dictionary = matrices - self._domain = C - self._codomain = D + self._matrix_dictionary = {} + for i in matrices: + m = matrices[i] + # Use immutable matrices because they're hashable. + m.set_immutable() + self._matrix_dictionary[i] = m + Morphism.__init__(self, Hom(C,D, ChainComplexes(C.base_ring()))) def in_degree(self, n): """ @@ -212,10 +216,78 @@ def in_degree(self, n): try: return self._matrix_dictionary[n] except KeyError: - from sage.matrix.constructor import zero_matrix - rows = self._codomain.free_module_rank(n) - cols = self._domain.free_module_rank(n) - return zero_matrix(self._domain.base_ring(), rows, cols) + rows = self.codomain().free_module_rank(n) + cols = self.domain().free_module_rank(n) + return zero_matrix(self.domain().base_ring(), rows, cols) + + def to_matrix(self, deg=None): + """ + The matrix representing this chain map. + + If the degree ``deg`` is specified, return the matrix in that + degree; otherwise, return the (block) matrix for the whole + chain map. + + INPUTS: + + - ``deg`` -- (optional, default ``None``) the degree + + EXAMPLES:: + + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: f.to_matrix(0) + [1] + sage: f.to_matrix() + [1|0|] + [-+-+] + [0|0|] + [-+-+] + [0|0|] + """ + if deg is not None: + return self.in_degree(deg) + row = 0 + col = 0 + blocks = [self._matrix_dictionary[n] + for n in sorted(self._matrix_dictionary.keys())] + return block_diagonal_matrix(blocks) + + def dual(self): + """ + The dual chain map to this one. + + That is, the map from the dual of the codomain of this one to + the dual of its domain, represented in each degree by the + transpose of the corresponding matrix. + + EXAMPLES:: + + sage: X = simplicial_complexes.Simplex(1) + sage: Y = simplicial_complexes.Simplex(0) + sage: g = Hom(X,Y)({0:0, 1:0}) + sage: f = g.associated_chain_complex_morphism() + sage: f.in_degree(0) + [1 1] + sage: f.dual() + Chain complex morphism: + From: Chain complex with at most 1 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring + sage: f.dual().in_degree(0) + [1] + [1] + sage: ascii_art(f.domain()) + [-1] + [ 1] + 0 <-- C_0 <----- C_1 <-- 0 + sage: ascii_art(f.dual().codomain()) + [-1 1] + 0 <-- C_1 <-------- C_0 <-- 0 + """ + matrix_dict = self._matrix_dictionary + matrices = {i: matrix_dict[i].transpose() for i in matrix_dict} + return ChainComplexMorphism(matrices, self.codomain().dual(), self.domain().dual()) def __neg__(self): """ @@ -248,7 +320,7 @@ def __neg__(self): f = dict() for i in self._matrix_dictionary.keys(): f[i] = -self._matrix_dictionary[i] - return ChainComplexMorphism(f, self._domain, self._codomain) + return ChainComplexMorphism(f, self.domain(), self.codomain()) def __add__(self,x): """ @@ -278,12 +350,12 @@ def __add__(self,x): [0 0 0 2]} """ - if not isinstance(x,ChainComplexMorphism) or self._codomain != x._codomain or self._domain != x._domain or self._matrix_dictionary.keys() != x._matrix_dictionary.keys(): + if not isinstance(x,ChainComplexMorphism) or self.codomain() != x.codomain() or self.domain() != x.domain() or self._matrix_dictionary.keys() != x._matrix_dictionary.keys(): raise TypeError("Unsupported operation.") f = dict() for i in self._matrix_dictionary.keys(): f[i] = self._matrix_dictionary[i] + x._matrix_dictionary[i] - return ChainComplexMorphism(f, self._domain, self._codomain) + return ChainComplexMorphism(f, self.domain(), self.codomain()) def __mul__(self,x): """ @@ -350,25 +422,27 @@ def __mul__(self,x): sage: f = ChainComplexMorphism({}, C0, C1) sage: g = ChainComplexMorphism({}, C1, C2) sage: g * f - Chain complex morphism from Chain complex with at most 1 nonzero terms over Integer Ring to Chain complex with at most 1 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 1 nonzero terms over Integer Ring + To: Chain complex with at most 1 nonzero terms over Integer Ring sage: f._matrix_dictionary {0: [], 1: []} sage: g._matrix_dictionary {1: [], 2: []} """ - if not isinstance(x,ChainComplexMorphism) or self._domain != x._codomain: + if not isinstance(x,ChainComplexMorphism) or self.domain() != x.codomain(): try: - y = self._domain.base_ring()(x) + y = self.domain().base_ring()(x) except TypeError: raise TypeError("multiplication is not defined") f = dict() for i in self._matrix_dictionary: f[i] = self._matrix_dictionary[i] * y - return ChainComplexMorphism(f,self._domain,self._codomain) + return ChainComplexMorphism(f,self.domain(),self.codomain()) f = dict() for i in self._matrix_dictionary: f[i] = self._matrix_dictionary[i]*x.in_degree(i) - return ChainComplexMorphism(f,x._domain,self._codomain) + return ChainComplexMorphism(f,x.domain(),self.codomain()) def __rmul__(self,x): """ @@ -386,13 +460,13 @@ def __rmul__(self,x): False """ try: - y = self._domain.base_ring()(x) + y = self.domain().base_ring()(x) except TypeError: raise TypeError("multiplication is not defined") f = dict() for i in self._matrix_dictionary.keys(): f[i] = y * self._matrix_dictionary[i] - return ChainComplexMorphism(f,self._domain,self._codomain) + return ChainComplexMorphism(f,self.domain(),self.codomain()) def __sub__(self,x): """ @@ -420,7 +494,6 @@ def __sub__(self,x): [0 0 0 0] [0 0 0 0] [0 0 0 0]} - """ return self + (-x) @@ -435,8 +508,9 @@ def __eq__(self,x): sage: i = H.identity() sage: x = i.associated_chain_complex_morphism() sage: x - Chain complex morphism from Trivial chain complex over Integer Ring - to Trivial chain complex over Integer Ring + Chain complex morphism: + From: Trivial chain complex over Integer Ring + To: Trivial chain complex over Integer Ring sage: f = x._matrix_dictionary sage: C = S.chain_complex() sage: G = Hom(C,C) @@ -445,13 +519,13 @@ def __eq__(self,x): True """ return isinstance(x,ChainComplexMorphism) \ - and self._codomain == x._codomain \ - and self._domain == x._domain \ + and self.codomain() == x.codomain() \ + and self.domain() == x.domain() \ and self._matrix_dictionary == x._matrix_dictionary - def _repr_(self): + def is_identity(self): """ - Return the string representation of ``self``. + True if this is the identity map. EXAMPLES:: @@ -459,12 +533,73 @@ def _repr_(self): sage: H = Hom(S,S) sage: i = H.identity() sage: x = i.associated_chain_complex_morphism() - sage: x - Chain complex morphism from Trivial chain complex over Integer Ring - to Trivial chain complex over Integer Ring - sage: x._repr_() - 'Chain complex morphism from Trivial chain complex over Integer Ring - to Trivial chain complex over Integer Ring' + sage: x.is_identity() + True + """ + return self.to_matrix().is_one() + + def is_surjective(self): """ - return "Chain complex morphism from {} to {}".format(self._domain, self._codomain) + True if this map is surjective. + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: H = Hom(S1, S1) + sage: flip = H({0:0, 1:2, 2:1}) + sage: flip.associated_chain_complex_morphism().is_surjective() + True + + sage: pt = simplicial_complexes.Simplex(0) + sage: inclusion = Hom(pt, S1)({0:2}) + sage: inclusion.associated_chain_complex_morphism().is_surjective() + False + sage: inclusion.associated_chain_complex_morphism(cochain=True).is_surjective() + True + """ + m = self.to_matrix() + return m.rank() == m.nrows() + + def is_injective(self): + """ + True if this map is injective. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: H = Hom(S1, S1) + sage: flip = H({0:0, 1:2, 2:1}) + sage: flip.associated_chain_complex_morphism().is_injective() + True + + sage: pt = simplicial_complexes.Simplex(0) + sage: inclusion = Hom(pt, S1)({0:2}) + sage: inclusion.associated_chain_complex_morphism().is_injective() + True + sage: inclusion.associated_chain_complex_morphism(cochain=True).is_injective() + False + """ + return self.to_matrix().right_nullity() == 0 + + def __hash__(self): + """ + TESTS:: + + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1)}) + sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: hash(f) # random + 17 + """ + return hash(self.domain()) ^ hash(self.codomain()) ^ hash(tuple(self._matrix_dictionary.items())) + + def _repr_type(self): + """ + EXAMPLES:: + + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1)}) + sage: Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)})._repr_type() + 'Chain complex' + """ + return "Chain complex" diff --git a/src/sage/homology/chain_homotopy.py b/src/sage/homology/chain_homotopy.py new file mode 100644 index 00000000000..11350ec6c43 --- /dev/null +++ b/src/sage/homology/chain_homotopy.py @@ -0,0 +1,600 @@ +# -*- coding: utf-8 -*- +r""" +Chain homotopies and chain contractions + +Chain homotopies are standard constructions in homological algebra: +given chain complexes `C` and `D` and chain maps `f, g: C \to D`, say +with differential of degree `-1`, a *chain homotopy* `H` between `f` and +`g` is a collection of maps `H_n: C_n \to D_{n+1}` satisfying + +.. MATH:: + + \partial_D H + H \partial_C = f - g. + +The presence of a chain homotopy defines an equivalence relation +(*chain homotopic*) on chain maps. If `f` and `g` are chain homotopic, +then one can show that `f` and `g` induce the same map on homology. + +Chain contractions are not as well known. The papers [M-AR]_, [RM-A]_, +and [PR]_ provide some references. Given two chain complexes `C` and +`D`, a *chain contraction* is a chain homotopy `H: C \to C` for which +there are chain maps `\pi: C \to D` ("projection") and `\iota: D \to +C` ("inclusion") such that + +- `H` is a chain homotopy between `1_C` and `\iota \pi`, +- `\pi \iota = 1_D`, +- `\pi H = 0`, +- `H \iota = 0`, +- `H H = 0`. + +Such a chain homotopy provides a strong relation between the chain +complexes `C` and `D`; for example, their homology groups are +isomorphic. + +REFERENCES: + +.. [M-AR] H. Molina-Abril and P. Réal, *Homology computation using spanning + trees* in Progress in Pattern Recognition, Image Analysis, + Computer Vision, and Applications, Lecture Notes in Computer + Science, volume 5856, pp 272-278, Springer, Berlin (2009). + +.. [PR] P. Pilarczyk and P. Réal, *Computation of cubical homology, + cohomology, and (co)homological operations via chain contraction*, + Adv. Comput. Math. 41 (2015), pp 253--275. + +.. [RM-A] P. Réal and H. Molina-Abril, *Cell AT-models for digital + volumes* in Torsello, Escolano, Brun (eds.), Graph-Based + Representations in Pattern Recognition, Lecture Notes in + Computer Science, volume 5534, pp. 314-3232, Springer, Berlin (2009). +""" + +######################################################################## +# Copyright (C) 2015 John H. Palmieri +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# http://www.gnu.org/licenses/ +######################################################################## + +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom +from sage.homology.chain_complex_morphism import ChainComplexMorphism + +# In a perfect world, this would inherit from something like +# "TwoMorphism" rather than "Morphism"... +class ChainHomotopy(Morphism): + r""" + A chain homotopy. + + A chain homotopy `H` between chain maps `f, g: C \to D` is a sequence + of maps `H_n: C_n \to D_{n+1}` (if the chain complexes are graded + homologically) satisfying + + .. MATH:: + + \partial_D H + H \partial_C = f - g. + + INPUT: + + - ``matrices`` -- dictionary of matrices, keyed by dimension + - ``f`` -- chain map `C \to D` + - ``g`` (optional) -- chain map `C \to D` + + The dictionary ``matrices`` defines ``H`` by specifying the matrix + defining it in each degree: the entry `m` corresponding to key `i` + gives the linear transformation `C_i \to D_{i+1}`. + + If `f` is specified but not `g`, then `g` can be recovered from + the defining formula. That is, if `g` is not specified, then it + is defined to be `f - \partial_D H - H \partial_C`. + + Note that the degree of the differential on the chain complex `C` + must agree with that for `D`, and those degrees determine the + "degree" of the chain homotopy map: if the degree of the + differential is `d`, then the chain homotopy consists of a + sequence of maps `C_n \to C_{n-d}`. The keys in the dictionary + ``matrices`` specify the starting degrees. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1)}) + sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: g = Hom(C,D)({0: zero_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: identity_matrix(ZZ, 1)}, f, g) + + Note that the maps `f` and `g` are stored in the attributes ``H._f`` + and ``H._g``:: + + sage: H._f + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring + sage: H._f.in_degree(0) + [1] + sage: H._g.in_degree(0) + [0] + + A non-example:: + + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1)}, f, g) + Traceback (most recent call last): + ... + ValueError: the data do not define a valid chain homotopy + """ + def __init__(self, matrices, f, g=None): + r""" + Create a chain homotopy between the given chain maps + from a dictionary of matrices. + + EXAMPLES: + + If ``g`` is not specified, it is set equal to + `f - (H \partial + \partial H)`. :: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({1: matrix(ZZ, 1, 2, (1,0)), 2: matrix(ZZ, 2, 1, (0, 2))}, degree_of_differential=-1) + sage: D = ChainComplex({2: matrix(ZZ, 1, 1, (6,))}, degree_of_differential=-1) + sage: f_d = {1: matrix(ZZ, 1, 2, (0,3)), 2: identity_matrix(ZZ, 1)} + sage: f = Hom(C,D)(f_d) + sage: H_d = {0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 2, (2,2))} + sage: H = ChainHomotopy(H_d, f) + sage: H._g.in_degree(0) + [] + sage: H._g.in_degree(1) + [-13 -9] + sage: H._g.in_degree(2) + [-3] + + TESTS: + + Try to construct a chain homotopy in which the maps do not + have matching domains and codomains:: + + sage: g = Hom(C,C)({}) # the zero chain map + sage: H = ChainHomotopy(H_d, f, g) + Traceback (most recent call last): + ... + ValueError: the chain maps are not compatible + """ + domain = f.domain() + codomain = f.codomain() + deg = domain.degree_of_differential() + # Check that the chain complexes are compatible. This should + # never arise, because first there should be errors in + # constructing the chain maps. But just in case... + if domain.degree_of_differential() != codomain.degree_of_differential(): + raise ValueError('the chain complexes are not compatible') + if g is not None: + # Check that the chain maps are compatible. + if not (domain == g.domain() and codomain == + g.codomain()): + raise ValueError('the chain maps are not compatible') + # Check that the data define a chain homotopy. + for i in domain.differential(): + if i in matrices and i+deg in matrices: + if not (codomain.differential(i-deg) * matrices[i] + matrices[i+deg] * domain.differential(i) == f.in_degree(i) - g.in_degree(i)): + raise ValueError('the data do not define a valid chain homotopy') + elif i in matrices: + if not (codomain.differential(i-deg) * matrices[i] == f.in_degree(i) - g.in_degree(i)): + raise ValueError('the data do not define a valid chain homotopy') + elif i+deg in matrices: + if not (matrices[i+deg] * domain.differential(i) == f.in_degree(i) - g.in_degree(i)): + raise ValueError('the data do not define a valid chain homotopy') + else: + # Define g. + g_data = {} + for i in domain.differential(): + if i in matrices and i+deg in matrices: + g_data[i] = f.in_degree(i) - matrices[i+deg] * domain.differential(i) - codomain.differential(i-deg) * matrices[i] + elif i in matrices: + g_data[i] = f.in_degree(i) - codomain.differential(i-deg) * matrices[i] + elif i+deg in matrices: + g_data[i] = f.in_degree(i) - matrices[i+deg] * domain.differential(i) + g = ChainComplexMorphism(g_data, domain, codomain) + self._matrix_dictionary = {} + for i in matrices: + m = matrices[i] + # Use immutable matrices because they're hashable. + m.set_immutable() + self._matrix_dictionary[i] = m + self._f = f + self._g = g + Morphism.__init__(self, Hom(domain, codomain)) + + def is_algebraic_gradient_vector_field(self): + r""" + An algebraic gradient vector field is a linear map + `H: C \to C` such that `H H = 0`. + + (Some authors also require that `H \partial H = H`, whereas + some make this part of the definition of "homology gradient + vector field. We have made the second choice.) See + Molina-Abril and Réal [M-AR]_ and Réal and Molina-Abril + [RM-A]_ for this and related terminology. + + See also :meth:`is_homology_gradient_vector_field`. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)}) + + The chain complex `C` is chain homotopy equivalent to a copy of + `\ZZ` in degree 0. Two chain maps `C \to C` will be chain + homotopic as long as they agree in degree 0. :: + + sage: f = Hom(C,C)({0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 1, [3]), 2: matrix(ZZ, 1, 1, [3])}) + sage: g = Hom(C,C)({0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 1, [2]), 2: matrix(ZZ, 1, 1, [2])}) + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, f, g) + sage: H.is_algebraic_gradient_vector_field() + True + + A chain homotopy which is not an algebraic gradient vector field:: + + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: identity_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, f, g) + sage: H.is_algebraic_gradient_vector_field() + False + """ + if self.domain() != self.codomain(): + return False + deg = self.domain().degree_of_differential() + matrices = self._matrix_dictionary + for i in matrices: + if i-deg in matrices: + if matrices[i-deg] * matrices[i] != 0: + return False + return True + + def is_homology_gradient_vector_field(self): + r""" + A homology gradient vector field is an algebraic gradient vector + field `H: C \to C` (i.e., a chain homotopy satisfying `H + H = 0`) such that `\partial H \partial = \partial` and `H + \partial H = H`. + + See Molina-Abril and Réal [M-AR]_ and Réal and Molina-Abril + [RM-A]_ for this and related terminology. + + See also :meth:`is_algebraic_gradient_vector_field`. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)}) + + sage: f = Hom(C,C)({0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 1, [3]), 2: matrix(ZZ, 1, 1, [3])}) + sage: g = Hom(C,C)({0: identity_matrix(ZZ, 1), 1: matrix(ZZ, 1, 1, [2]), 2: matrix(ZZ, 1, 1, [2])}) + sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, f, g) + sage: H.is_homology_gradient_vector_field() + True + """ + if not self.is_algebraic_gradient_vector_field(): + return False + deg = self.domain().degree_of_differential() + matrices = self._matrix_dictionary + for i in matrices: + if i+deg in matrices: + diff_i = self.domain().differential(i) + if diff_i * matrices[i+deg] * diff_i != diff_i: + return False + if matrices[i] * self.domain().differential(i-deg) * matrices[i] != matrices[i]: + return False + return True + + def in_degree(self, n): + """ + The matrix representing this chain homotopy in degree ``n``. + + INPUT: + + - ``n`` -- degree + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({1: matrix(ZZ, 0, 2)}) # one nonzero term in degree 1 + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) # one nonzero term in degree 0 + sage: f = Hom(C, D)({}) + sage: H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f) + sage: H.in_degree(1) + [3 1] + + This returns an appropriately sized zero matrix if the chain + homotopy is not defined in degree n:: + + sage: H.in_degree(-3) + [] + """ + try: + return self._matrix_dictionary[n] + except KeyError: + from sage.matrix.constructor import zero_matrix + deg = self.domain().degree_of_differential() + rows = self.codomain().free_module_rank(n-deg) + cols = self.domain().free_module_rank(n) + return zero_matrix(self.domain().base_ring(), rows, cols) + + def dual(self): + r""" + Dual chain homotopy to this one. + + That is, if this one is a chain homotopy between chain maps + `f, g: C \to D`, then its dual is a chain homotopy between the + dual of `f` and the dual of `g`, from `D^*` to `C^*`. It is + represented in each degree by the transpose of the + corresponding matrix. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({1: matrix(ZZ, 0, 2)}) # one nonzero term in degree 1 + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) # one nonzero term in degree 0 + sage: f = Hom(C, D)({}) + sage: H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f) + sage: H.in_degree(1) + [3 1] + sage: H.dual().in_degree(0) + [3] + [1] + """ + matrix_dict = self._matrix_dictionary + deg = self.domain().degree_of_differential() + matrices = {i-deg: matrix_dict[i].transpose() for i in matrix_dict} + return ChainHomotopy(matrices, self._f.dual(), self._g.dual()) + + def __hash__(self): + """ + TESTS:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({1: matrix(ZZ, 0, 2)}) # one nonzero term in degree 1 + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) # one nonzero term in degree 0 + sage: f = Hom(C, D)({}) + sage: H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f) + sage: hash(H) # random + 314159265358979 + """ + return hash(self._f) ^ hash(self._g) ^ hash(tuple(self._matrix_dictionary.items())) + + def _repr_(self): + """ + String representation + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainHomotopy + sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: zero_matrix(ZZ, 1)}) + sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: g = Hom(C,D)({0: zero_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) + sage: ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: identity_matrix(ZZ, 1)}, f, g) + Chain homotopy between: + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring + and Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring + """ + s = 'Chain homotopy between:' + s += '\n {}'.format('\n '.join(self._f._repr_().split('\n'))) + s += '\n and {}'.format('\n '.join(self._g._repr_().split('\n'))) + return s + +class ChainContraction(ChainHomotopy): + r""" + A chain contraction. + + An algebraic gradient vector field `H: C \to C` (that is a chain + homotopy satisfying `H H = 0`) for which there are chain + maps `\pi: C \to D` ("projection") and `\iota: D \to C` + ("inclusion") such that + + - `H` is a chain homotopy between `1_C` and `\iota \pi`, + - `\pi \iota = 1_D`, + - `\pi H = 0`, + - `H \iota = 0`. + + ``H`` is defined by a dictionary ``matrices`` of matrices. + + INPUTS: + + - ``matrices`` -- dictionary of matrices, keyed by dimension + - ``pi`` -- a chain map `C \to D` + - ``iota`` -- a chain map `D \to C` + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainContraction + sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) + + The chain complex `C` is chain homotopy equivalent to `D`, which is just + a copy of `\ZZ` in degree 0, and we construct a chain contraction:: + + sage: pi = Hom(C,D)({0: identity_matrix(ZZ, 1)}) + sage: iota = Hom(D,C)({0: identity_matrix(ZZ, 1)}) + sage: H = ChainContraction({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, pi, iota) + """ + def __init__(self, matrices, pi, iota): + r""" + Create a chain contraction from the given data. + + EXAMPLES:: + + sage: from sage.homology.chain_homotopy import ChainContraction + sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)}) + sage: D = ChainComplex({0: matrix(ZZ, 0, 1)}) + + The chain complex `C` is chain homotopy equivalent to `D`, + which is just a copy of `\ZZ` in degree 0, and we try + construct a chain contraction, but get the map `\iota` wrong:: + + sage: pi = Hom(C,D)({0: identity_matrix(ZZ, 1)}) + sage: iota = Hom(D,C)({0: zero_matrix(ZZ, 1)}) + sage: H = ChainContraction({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, pi, iota) + Traceback (most recent call last): + ... + ValueError: the composite 'pi iota' is not the identity + + Another bad `\iota`:: + + sage: iota = pi # wrong domain, codomain + sage: H = ChainContraction({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, pi, iota) + Traceback (most recent call last): + ... + ValueError: the chain maps are not composable + + `\iota` is okay, but wrong data defining `H`:: + + sage: iota = Hom(D,C)({0: identity_matrix(ZZ, 1)}) + sage: H = ChainContraction({0: zero_matrix(ZZ, 0, 1), 1: identity_matrix(ZZ, 1), 2: identity_matrix(ZZ, 1)}, pi, iota) + Traceback (most recent call last): + ... + ValueError: not an algebraic gradient vector field + """ + from sage.matrix.constructor import identity_matrix + from chain_complex_morphism import ChainComplexMorphism + + if not (pi.domain() == iota.codomain() + and pi.codomain() == iota.domain()): + raise ValueError('the chain maps are not composable') + C = pi.domain() + D = pi.codomain() + base_ring = C.base_ring() + + # Check that the composite 'pi iota' is 1. + for i in D.nonzero_degrees(): + if pi.in_degree(i) * iota.in_degree(i) != identity_matrix(base_ring, D.free_module_rank(i)): + raise ValueError("the composite 'pi iota' is not the identity") + + # Construct the chain map 'id_C'. + id_C_dict = {} + for i in C.nonzero_degrees(): + id_C_dict[i] = identity_matrix(base_ring, C.free_module_rank(i)) + id_C = ChainComplexMorphism(id_C_dict, C, C) + + # Now check that + # - `H` is a chain homotopy between `id_C` and `\iota \pi` + # - `HH = 0` + ChainHomotopy.__init__(self, matrices, id_C, iota * pi) + if not self.is_algebraic_gradient_vector_field(): + raise ValueError('not an algebraic gradient vector field') + # Check that `\pi H = 0`: + deg = C.degree_of_differential() + for i in matrices: + if pi.in_degree(i-deg) * matrices[i] != 0: + raise ValueError('the data do not define a valid chain contraction: pi H != 0') + # Check that `H \iota = 0`: + for i in iota._matrix_dictionary: + if i in matrices: + if matrices[i] * iota.in_degree(i) != 0: + raise ValueError('the data do not define a valid chain contraction: H iota != 0') + self._pi = pi + self._iota = iota + + def pi(self): + r""" + The chain map `\pi` associated to this chain contraction. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: phi, M = S2.algebraic_topological_model(QQ) + sage: phi.pi() + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + sage: phi.pi().in_degree(0) # Every vertex represents a homology class. + [1 1 1 1] + sage: phi.pi().in_degree(1) # No homology in degree 1. + [] + + The degree 2 homology generator is detected on a single simplex:: + + sage: phi.pi().in_degree(2) + [0 0 0 1] + """ + return self._pi + + def iota(self): + r""" + The chain map `\iota` associated to this chain contraction. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: phi, M = S2.algebraic_topological_model(QQ) + sage: phi.iota() + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + + Lifting the degree zero homology class gives a single vertex:: + + sage: phi.iota().in_degree(0) + [0] + [0] + [0] + [1] + + Lifting the degree two homology class gives the signed sum of + all of the 2-simplices:: + + sage: phi.iota().in_degree(2) + [-1] + [-1] + [ 1] + [ 1] + """ + return self._iota + + def dual(self): + """ + The chain contraction dual to this one. + + This is useful when switching from homology to cohomology. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: phi, M = S2.algebraic_topological_model(QQ) + sage: phi.iota() + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + + Lifting the degree zero homology class gives a single vertex, + but the degree zero cohomology class needs to be detected on + every vertex, and vice versa for degree 2:: + + sage: phi.iota().in_degree(0) + [0] + [0] + [0] + [1] + sage: phi.dual().iota().in_degree(0) + [1] + [1] + [1] + [1] + sage: phi.iota().in_degree(2) + [-1] + [-1] + [ 1] + [ 1] + sage: phi.dual().iota().in_degree(2) + [0] + [0] + [0] + [1] + """ + matrix_dict = self._matrix_dictionary + deg = self.domain().degree_of_differential() + matrices = {i-deg: matrix_dict[i].transpose() for i in matrix_dict} + return ChainContraction(matrices, self.iota().dual(), self.pi().dual()) + diff --git a/src/sage/homology/cubical_complex.py b/src/sage/homology/cubical_complex.py index 9214a7b4f6a..07e5e3a19e5 100644 --- a/src/sage/homology/cubical_complex.py +++ b/src/sage/homology/cubical_complex.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" Finite cubical complexes @@ -547,6 +548,67 @@ def _triangulation_(self): simplices.append(S.join(Simplex((v,)), rename_vertices=False)) return simplices + def alexander_whitney(self, dim): + r""" + Subdivide this cube into pairs of cubes. + + This provides a cubical approximation for the diagonal map + `K \to K \times K`. + + INPUT: + + - ``dim`` -- integer between 0 and one more than the + dimension of this cube + + OUTPUT: + + - a list containing triples ``(coeff, left, right)`` + + This uses the algorithm described by Pilarczyk and Réal [PR]_ + on p. 267; the formula is originally due to Serre. Calling + this method ``alexander_whitney`` is an abuse of notation, + since the actual Alexander-Whitney map goes from `C(K \times + L) \to C(K) \otimes C(L)`, where `C(-)` denotes the associated + chain complex, but this subdivision of cubes is at the heart + of it. + + EXAMPLES:: + + sage: from sage.homology.cubical_complex import Cube + sage: C1 = Cube([[0,1], [3,4]]) + sage: C1.alexander_whitney(0) + [(1, [0,0] x [3,3], [0,1] x [3,4])] + sage: C1.alexander_whitney(1) + [(1, [0,1] x [3,3], [1,1] x [3,4]), (-1, [0,0] x [3,4], [0,1] x [4,4])] + sage: C1.alexander_whitney(2) + [(1, [0,1] x [3,4], [1,1] x [4,4])] + """ + from sage.sets.set import Set + N = Set(self.nondegenerate_intervals()) + result = [] + for J in N.subsets(dim): + Jprime = N.difference(J) + nu = 0 + for i in J: + for j in Jprime: + if j +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# http://www.gnu.org/licenses/ +######################################################################## + +# To do: implement morphisms of cubical complexes, with methods +# - domain +# - codomain +# - associated_chain_complex_morphism +# Once this is done, the code here ought to work without modification. + +from sage.categories.graded_algebras_with_basis import GradedAlgebrasWithBasis +from sage.categories.graded_modules_with_basis import GradedModulesWithBasis +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom +from sage.rings.rational_field import QQ + +class InducedHomologyMorphism(Morphism): + r""" + An element of this class is a morphism of (co)homology groups + induced by a map of simplicial complexes. It requires working + with field coefficients. + + INPUTS: + + - ``map`` -- the map of simplicial complexes + - ``base_ring`` -- a field (optional, default ``QQ``) + - ``cohomology`` -- boolean (optional, default ``False``). If + ``True``, return the induced map in cohomology rather than + homology. + + .. note:: + + This is not intended to be used directly by the user, but instead + via the method + :meth:`~sage.homology.simplicial_complex_morphism.SimplicialComplexMorphism.induced_homology_morphism`. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: H = Hom(S1, S1) + sage: f = H({0:0, 1:2, 2:1}) # f switches two vertices + sage: f_star = f.induced_homology_morphism(QQ, cohomology=True) + sage: f_star + Graded algebra endomorphism of Cohomology ring of Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} over Rational Field + Defn: induced by: + Simplicial complex endomorphism of Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + Defn: 0 |--> 0 + 1 |--> 2 + 2 |--> 1 + sage: f_star.to_matrix(1) + [-1] + sage: f_star.to_matrix() + [ 1| 0] + [--+--] + [ 0|-1] + + sage: T = simplicial_complexes.Torus() + sage: y = T.homology_with_basis(QQ).basis()[(1,1)] + sage: y.to_cycle() + (0, 3) - (0, 6) + (3, 6) + + Since `(0,3) - (0,6) + (3,6)` is a cycle representing a homology + class in the torus, we can define a map `S^1 \to T` inducing an + inclusion on `H_1`:: + + sage: Hom(S1, T)({0:0, 1:3, 2:6}) + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6) and 14 facets + Defn: 0 |--> 0 + 1 |--> 3 + 2 |--> 6 + sage: g = Hom(S1, T)({0:0, 1:3, 2: 6}) + sage: g_star = g.induced_homology_morphism(QQ) + sage: g_star.to_matrix(0) + [1] + sage: g_star.to_matrix(1) + [0] + [1] + sage: g_star.to_matrix() + [1|0] + [-+-] + [0|0] + [0|1] + [-+-] + [0|0] + + We can evaluate such a map on (co)homology classes:: + + sage: H = S1.homology_with_basis(QQ) + sage: a = H.basis()[(1,0)] + sage: g_star(a) + h_{1,1} + + sage: T = S1.product(S1, is_mutable=False) + sage: diag = Hom(S1,T).diagonal_morphism() + sage: b,c = list(T.cohomology_ring().basis(1)) + sage: diag_c = diag.induced_homology_morphism(cohomology=True) + sage: diag_c(b) + h^{1,0} + sage: diag_c(c) + h^{1,0} + """ + def __init__(self, map, base_ring=None, cohomology=False): + """ + INPUTS: + + - ``map`` -- the map of simplicial complexes + - ``base_ring`` -- a field (optional, default ``QQ``) + - ``cohomology`` -- boolean (optional, default ``False``). If + ``True``, return the induced map in cohomology rather than + homology. + + EXAMPLES:: + + sage: from sage.homology.homology_morphism import InducedHomologyMorphism + sage: K = simplicial_complexes.RandomComplex(8, 3) + sage: H = Hom(K,K) + sage: id = H.identity() + sage: f = InducedHomologyMorphism(id, QQ) + sage: f.to_matrix(0) == 1 and f.to_matrix(1) == 1 and f.to_matrix(2) == 1 + True + sage: f = InducedHomologyMorphism(id, ZZ) + Traceback (most recent call last): + ... + ValueError: the coefficient ring must be a field + sage: S1 = simplicial_complexes.Sphere(1).barycentric_subdivision() + sage: S1.is_mutable() + True + sage: g = Hom(S1, S1).identity() + sage: h = g.induced_homology_morphism(QQ) + Traceback (most recent call last): + ... + ValueError: the domain and codomain complexes must be immutable + sage: S1.set_immutable() + sage: g = Hom(S1, S1).identity() + sage: h = g.induced_homology_morphism(QQ) + """ + if map.domain().is_mutable() or map.codomain().is_mutable(): + raise ValueError('the domain and codomain complexes must be immutable') + if base_ring is None: + base_ring = QQ + if not base_ring.is_field(): + raise ValueError('the coefficient ring must be a field') + + self._cohomology = cohomology + self._map = map + self._base_ring = base_ring + if cohomology: + domain = map.domain().cohomology_ring(base_ring=base_ring) + codomain = map.codomain().cohomology_ring(base_ring=base_ring) + Morphism.__init__(self, Hom(domain, codomain, + category=GradedAlgebrasWithBasis(base_ring))) + else: + domain = map.domain().homology_with_basis(base_ring=base_ring, cohomology=cohomology) + codomain = map.codomain().homology_with_basis(base_ring=base_ring, cohomology=cohomology) + Morphism.__init__(self, Hom(domain, codomain, + category=GradedModulesWithBasis(base_ring))) + + def base_ring(self): + """ + The base ring for this map + + EXAMPLES:: + + sage: K = simplicial_complexes.Simplex(2) + sage: H = Hom(K,K) + sage: id = H.identity() + sage: id.induced_homology_morphism(QQ).base_ring() + Rational Field + sage: id.induced_homology_morphism(GF(13)).base_ring() + Finite Field of size 13 + """ + return self._base_ring + + def to_matrix(self, deg=None): + """ + The matrix for this map. + + If degree ``deg`` is specified, return the matrix just in that + degree; otherwise, return the block matrix representing the + entire map. + + INPUTS: + + - ``deg`` -- (optional, default ``None``) the degree + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: S1_b = S1.barycentric_subdivision() + sage: S1_b.set_immutable() + sage: d = {(0,): 0, (0,1): 1, (1,): 2, (1,2): 0, (2,): 1, (0,2): 2} + sage: f = Hom(S1_b, S1)(d) + sage: h = f.induced_homology_morphism(QQ) + sage: h.to_matrix(1) + [2] + sage: h.to_matrix() + [1|0] + [-+-] + [0|2] + """ + base_ring = self.base_ring() + if self._cohomology: + domain = self._map.codomain() + codomain = self._map.domain() + else: + domain = self._map.domain() + codomain = self._map.codomain() + phi_codomain, H_codomain = codomain.algebraic_topological_model(base_ring) + phi_domain, H_domain = domain.algebraic_topological_model(base_ring) + mat = phi_codomain.pi().to_matrix(deg) * self._map.associated_chain_complex_morphism(self.base_ring(), cochain=self._cohomology).to_matrix(deg) * phi_domain.iota().to_matrix(deg) + if deg is None: + import numpy as np + betti_domain = [H_domain.free_module_rank(n) + for n in range(domain.dimension()+1)] + betti_codomain = [H_codomain.free_module_rank(n) + for n in range(codomain.dimension()+1)] + # Compute cumulative sums of Betti numbers to get subdivisions: + row_subdivs = list(np.cumsum(betti_codomain[:-1])) + col_subdivs = list(np.cumsum(betti_domain[:-1])) + mat.subdivide(row_subdivs, col_subdivs) + return mat + + def __call__(self, elt): + """ + Evaluate this map on ``elt``, an element of (co)homology. + + INPUT: + + - ``elt`` -- informally, an element of the domain of this + map. More formally, an element of + :class:`homology_vector_space_with_basis.HomologyVectorSpaceWithBasis`. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: f = {0:0, 1:2, 2:1} + sage: H = Hom(S1,S1) + sage: g = H(f) + sage: h = g.induced_homology_morphism(QQ) + sage: x = S1.homology_with_basis().basis()[(1,0)] + sage: x + h_{1,0} + sage: h(x) # indirect doctest + -h_{1,0} + """ + base_ring = self.base_ring() + if self._cohomology: + codomain = self._map.domain().homology_with_basis(base_ring, cohomology=True) + if elt.parent().complex() != self._map.codomain(): + raise ValueError('element is not a cohomology class for the correct complex') + else: + codomain = self._map.codomain().homology_with_basis(base_ring) + if elt.parent().complex() != self._map.domain(): + raise ValueError('element is not a homology class for the correct complex') + + return codomain.from_vector(self.to_matrix() * elt.to_vector()) + + def __eq__(self, other): + """ + Return ``True`` if and only if this map agrees with ``other``. + + INPUTS: + + - ``other`` -- another induced homology morphism + + This automatically returns ``False`` if the morphisms have + different domains, codomains, base rings, or values for their + cohomology flags + + Otherwise, determine this by computing the matrices for this + map and ``other`` using the (same) basis for the homology + vector spaces. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: f = Hom(S1, K)({0: 0, 1:1, 2:2}) + sage: g = Hom(S1, K)({0: 0, 1:0, 2:0}) + sage: f.induced_homology_morphism(QQ) == g.induced_homology_morphism(QQ) + True + sage: f.induced_homology_morphism(QQ) == g.induced_homology_morphism(GF(2)) + False + sage: id = Hom(K, K).identity() # different domain + sage: f.induced_homology_morphism(QQ) == id.induced_homology_morphism(QQ) + False + """ + if (self._map.domain() != other._map.domain() + or self._map.codomain() != other._map.codomain() + or self.base_ring() != other.base_ring() + or self._cohomology != other._cohomology): + return False + dim = min(self._map.domain().dimension(), self._map.codomain().dimension()) + return all(self.to_matrix(d) == other.to_matrix(d) for d in range(dim+1)) + + def is_identity(self): + """ + True if this is the identity map on (co)homology. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: H = Hom(S1, S1) + sage: flip = H({0:0, 1:2, 2:1}) + sage: flip.induced_homology_morphism(QQ).is_identity() + False + sage: flip.induced_homology_morphism(GF(2)).is_identity() + True + sage: rotate = H({0:1, 1:2, 2:0}) + sage: rotate.induced_homology_morphism(QQ).is_identity() + True + """ + return self.to_matrix().is_one() + + def is_surjective(self): + """ + True if this map is surjective on (co)homology. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: H = Hom(S1, K) + sage: f = H({0:0, 1:1, 2:2}) + sage: f.induced_homology_morphism().is_surjective() + True + sage: f.induced_homology_morphism(cohomology=True).is_surjective() + False + """ + m = self.to_matrix() + return m.rank() == m.nrows() + + def is_injective(self): + """ + True if this map is injective on (co)homology. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: H = Hom(S1, K) + sage: f = H({0:0, 1:1, 2:2}) + sage: f.induced_homology_morphism().is_injective() + False + sage: f.induced_homology_morphism(cohomology=True).is_injective() + True + + sage: T = simplicial_complexes.Torus() + sage: g = Hom(S1, T)({0:0, 1:3, 2: 6}) + sage: g_star = g.induced_homology_morphism(QQ) + sage: g.is_injective() + True + """ + return self.to_matrix().right_nullity() == 0 + + def _repr_type(self): + """ + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: f = Hom(S1, K)({0: 0, 1:1, 2:2}) + sage: f.induced_homology_morphism()._repr_type() + 'Graded vector space' + sage: f.induced_homology_morphism(cohomology=True)._repr_type() + 'Graded algebra' + """ + return "Graded vector space" if not self._cohomology else "Graded algebra" + + def _repr_defn(self): + """ + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: K = simplicial_complexes.Simplex(2) + sage: f = Hom(S1, K)({0: 0, 1:1, 2:2}) + sage: print f.induced_homology_morphism()._repr_defn() + induced by: + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 + """ + s = "induced by:" + s += '\n {}'.format('\n '.join(self._map._repr_().split('\n'))) + return s diff --git a/src/sage/homology/homology_vector_space_with_basis.py b/src/sage/homology/homology_vector_space_with_basis.py new file mode 100644 index 00000000000..15c42394f4c --- /dev/null +++ b/src/sage/homology/homology_vector_space_with_basis.py @@ -0,0 +1,834 @@ +# -*- coding: utf-8 -*- +""" +Homology and cohomology with a basis + +This module provides homology and cohomology vector spaces suitable +for computing cup products and cohomology operations. + +REFERENCES: + +.. [G-DR03] R. González-Díaz and P. Réal, *Computation of cohomology + operations on finite simplicial complexes* in Homology, + Homotopy and Applications 5 (2003), 83-93. + +.. [G-DR99] R. González-Díaz and P. Réal, *A combinatorial method for + computing Steenrod squares* in J. Pure Appl. Algebra 139 (1999), 89-108. + +AUTHORS: + +- John H. Palmieri, Travis Scrimshaw (2015-09) +""" + +######################################################################## +# Copyright (C) 2015 John H. Palmieri +# Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# http://www.gnu.org/licenses/ +######################################################################## + +from sage.misc.cachefunc import cached_method +from sage.categories.algebras import Algebras +from sage.categories.modules import Modules +from sage.combinat.free_module import CombinatorialFreeModule, CombinatorialFreeModuleElement +from sage.sets.family import Family +from simplicial_complex import SimplicialComplex + +class HomologyVectorSpaceWithBasis(CombinatorialFreeModule): + r""" + Homology (or cohomology) vector space. + + This provides enough structure to allow the computation of cup + products and cohomology operations. See the class + :class:`CohomologyRing` (which derives from this) for examples. + + It also requires field coefficients (hence the "VectorSpace" in + the name of the class). + + .. NOTE:: + + This is not intended to be created directly by the user, but + instead via the methods + :meth:`~sage.homology.cell_complex.GenericCellComplex.homology_with_basis` and + :meth:`~sage.homology.cell_complex.GenericCellComplex.cohomology_ring` + for the class of :class:`cell + complexes`. + + INPUT: + + - ``base_ring`` -- must be a field + - ``cell_complex`` -- the cell complex whose homology we are + computing + - ``cohomology`` -- (default: ``False``) if ``True``, return + the cohomology as a module + - ``category`` -- (optional) a subcategory of modules with basis + + EXAMPLES: + + Homology classes are denoted by ``h_{d,i}`` where ``d`` is the + degree of the homology class and ``i`` is their index in the list + of basis elements in that degree. Cohomology classes are denoted + ``h^{1,0}``:: + + sage: RP2 = cubical_complexes.RealProjectivePlane() + sage: RP2.homology_with_basis(GF(2)) + Homology module of Cubical complex with 21 vertices and 81 cubes + over Finite Field of size 2 + sage: RP2.cohomology_ring(GF(2)) + Cohomology ring of Cubical complex with 21 vertices and 81 cubes + over Finite Field of size 2 + sage: simplicial_complexes.Torus().homology_with_basis(QQ) + Homology module of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6) and 14 facets over Rational Field + + To access a basis element, use its degree and index (0 or 1 in the 1st + cohomology group of a torus):: + + sage: H = simplicial_complexes.Torus().cohomology_ring(QQ) + sage: H.basis(1) + Finite family {(1, 0): h^{1,0}, (1, 1): h^{1,1}} + sage: x = H.basis()[1,0]; x + h^{1,0} + sage: y = H.basis()[1,1]; y + h^{1,1} + sage: 2*x-3*y + 2*h^{1,0} - 3*h^{1,1} + + You can compute cup products of cohomology classes:: + + sage: x.cup_product(y) + h^{2,0} + sage: y.cup_product(x) + -h^{2,0} + sage: x.cup_product(x) + 0 + + This works with simplicial, cubical, and `\Delta`-complexes:: + + sage: Klein_c = cubical_complexes.KleinBottle() + sage: H = Klein_c.cohomology_ring(GF(2)) + sage: x,y = H.basis(1) + sage: x.cup_product(x) + h^{2,0} + sage: x.cup_product(y) + 0 + sage: y.cup_product(y) + h^{2,0} + + sage: Klein_d = delta_complexes.KleinBottle() + sage: H = Klein_d.cohomology_ring(GF(2)) + sage: u,v = H.basis(1) + sage: u.cup_product(u) + h^{2,0} + sage: u.cup_product(v) + 0 + sage: v.cup_product(v) + h^{2,0} + + The basis elements in the simplicial complex case have been chosen + differently; apply the change of basis `x \mapsto a + b`, `y \mapsto + b` to see the same product structure. :: + + sage: Klein_s = simplicial_complexes.KleinBottle() + sage: H = Klein_s.cohomology_ring(GF(2)) + sage: a,b = H.basis(1) + sage: a.cup_product(a) + 0 + sage: a.cup_product(b) + h^{2,0} + sage: (a+b).cup_product(a+b) + h^{2,0} + sage: b.cup_product(b) + h^{2,0} + """ + def __init__(self, base_ring, cell_complex, cohomology=False, cat=None): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.ProjectivePlane() + sage: H = RP2.homology_with_basis(QQ) + sage: TestSuite(H).run() + sage: H = RP2.homology_with_basis(GF(2)) + sage: TestSuite(H).run() + sage: H = RP2.cohomology_ring(GF(2)) + sage: TestSuite(H).run() + sage: H = RP2.cohomology_ring(GF(5)) + sage: TestSuite(H).run() + sage: H = simplicial_complexes.ComplexProjectivePlane().cohomology_ring() + sage: TestSuite(H).run() + """ + # phi is the associated chain contraction. + # M is the homology chain complex. + phi, M = cell_complex.algebraic_topological_model(base_ring) + if cohomology: + phi = phi.dual() + # We only need the rank of M in each degree, and since + # we're working over a field, we don't need to dualize M + # if working with cohomology. + cat = Modules(base_ring).WithBasis().Graded().or_subcategory(cat) + self._contraction = phi + self._complex = cell_complex + self._cohomology = cohomology + self._graded_indices = {deg: range(M.free_module_rank(deg)) + for deg in range(cell_complex.dimension()+1)} + indices = [(deg, i) for deg in self._graded_indices + for i in self._graded_indices[deg]] + CombinatorialFreeModule.__init__(self, base_ring, indices, category=cat) + + def basis(self, d=None): + """ + Return (the degree ``d`` homogeneous component of) the basis + of this graded vector space. + + INPUT: + + - ``d`` -- (optional) the degree + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.ProjectivePlane() + sage: H = RP2.homology_with_basis(QQ) + sage: H.basis() + Finite family {(0, 0): h_{0,0}} + sage: H.basis(0) + Finite family {(0, 0): h_{0,0}} + sage: H.basis(1) + Finite family {} + sage: H.basis(2) + Finite family {} + """ + if d is None: + return Family(self._indices, self.monomial) + else: + indices = [(d, i) for i in self._graded_indices.get(d, [])] + return Family(indices, self.monomial) + + def degree_on_basis(self, i): + r""" + Return the degree of the basis element indexed by ``i``. + + EXAMPLES:: + + sage: H = simplicial_complexes.Torus().homology_with_basis(GF(7)) + sage: H.degree_on_basis((2,0)) + 2 + """ + return i[0] + + def contraction(self): + r""" + The chain contraction associated to this homology computation. + + That is, to work with chain representatives of homology + classes, we need the chain complex `C` associated to the cell + complex, the chain complex `H` of its homology (with trivial + differential), chain maps `\pi: C \to H` and `\iota: H \to C`, + and a chain contraction `\phi` giving a chain homotopy between + `1_C` and `\iota \circ \pi`. + + OUTPUT: `\phi` + + See :class:`~sage.homology.chain_homotopy.ChainContraction` for information + about chain contractions, and see + :func:`~sage.homology.algebraic_topological_model.algebraic_topological_model` + for the construction of this particular chain contraction `\phi`. + + EXAMPLES:: + + sage: H = simplicial_complexes.Simplex(2).homology_with_basis(QQ) + sage: H.contraction() + Chain homotopy between: + Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field + and Chain complex endomorphism of Chain complex with at most 3 nonzero terms over Rational Field + + From the chain contraction, one can also recover the maps `\pi` + and `\iota`:: + + sage: phi = H.contraction() + sage: phi.pi() + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Rational Field + To: Chain complex with at most 1 nonzero terms over Rational Field + sage: phi.iota() + Chain complex morphism: + From: Chain complex with at most 1 nonzero terms over Rational Field + To: Chain complex with at most 3 nonzero terms over Rational Field + """ + return self._contraction + + def complex(self): + """ + The cell complex whose homology is being computed. + + EXAMPLES:: + + sage: H = simplicial_complexes.Simplex(2).homology_with_basis(QQ) + sage: H.complex() + Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} + """ + return self._complex + + def _repr_(self): + """ + EXAMPLES:: + + sage: simplicial_complexes.Torus().homology_with_basis(QQ) + Homology module of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6) and 14 facets over Rational Field + """ + if self._cohomology: + base = "Cohomology" + else: + base = "Homology" + return base + " module of {} over {}".format(self._complex, self.base_ring()) + + def _repr_term(self, i): + """ + Return ``'h_{i[0],i[1]}'`` for homology, ``'h^{i[0],i[1]}'`` for + cohomology, for the basis element indexed by ``i``. + + EXAMPLES:: + + sage: H = simplicial_complexes.Torus().homology_with_basis(QQ) + sage: H.basis()[1,0] # indirect doctest + h_{1,0} + sage: latex(H.basis()[1,1]) # indirect doctest + h_{1,1} + sage: co = simplicial_complexes.KleinBottle().cohomology_ring(GF(2)) + sage: co.basis()[1,0] # indirect doctest + h^{1,0} + + """ + sym = '^' if self._cohomology else '_' + return 'h{}{{{},{}}}'.format(sym, i[0], i[1]) + + _latex_term = _repr_term + + @cached_method + def _to_cycle_on_basis(self, i): + """ + Return the (co)cycle representative of the basis element + indexed by ``i``. + + .. SEEALSO:: + + :meth:`HomologyVectorSpaceWithBasis.Element.to_cocycle` + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: H = S2.homology_with_basis(QQ) + sage: H._to_cycle_on_basis((2,0)) + -(0, 1, 2) + (0, 1, 3) - (0, 2, 3) + (1, 2, 3) + + sage: S2.cohomology_ring(QQ)._to_cycle_on_basis((2,0)) + \chi_(0, 1, 3) + sage: S2.cohomology_ring(QQ)._to_cycle_on_basis((0,0)) + \chi_(0,) + \chi_(1,) + \chi_(2,) + \chi_(3,) + + sage: RP3 = simplicial_complexes.RealProjectiveSpace(3) + sage: H = RP3.cohomology_ring(GF(2)) + sage: H._to_cycle_on_basis((0,0)) + \chi_(1,) + \chi_(2,) + \chi_(3,) + \chi_(4,) + \chi_(5,) + \chi_(6,) + + \chi_(7,) + \chi_(8,) + \chi_(9,) + \chi_(10,) + \chi_(11,) + sage: H._to_cycle_on_basis((1,0)) + \chi_(1, 2) + \chi_(1, 3) + \chi_(1, 4) + \chi_(1, 7) + + \chi_(1, 10) + \chi_(2, 4) + \chi_(2, 6) + \chi_(2, 9) + + \chi_(2, 10) + \chi_(2, 11) + \chi_(3, 4) + \chi_(3, 5) + + \chi_(3, 11) + \chi_(4, 8) + \chi_(4, 9) + \chi_(5, 9) + + \chi_(5, 10) + \chi_(7, 9) + \chi_(8, 10) + sage: H._to_cycle_on_basis((2,0)) + \chi_(2, 3, 8) + \chi_(2, 7, 8) + \chi_(3, 4, 8) + \chi_(3, 5, 9) + + \chi_(3, 6, 7) + \chi_(3, 6, 8) + \chi_(3, 6, 10) + + \chi_(3, 8, 9) + \chi_(3, 9, 10) + \chi_(4, 5, 7) + + \chi_(4, 5, 9) + \chi_(5, 6, 7) + \chi_(5, 7, 8) + sage: H._to_cycle_on_basis((3,0)) + \chi_(3, 4, 5, 9) + """ + vec = self.contraction().iota().in_degree(i[0]).column(i[1]) + chains = self.complex().n_chains(i[0], self.base_ring(), + cochains=self._cohomology) + return chains.from_vector(vec) + + class Element(CombinatorialFreeModuleElement): + def to_cycle(self): + r""" + (Co)cycle representative of this homogeneous (co)homology class. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: H = S2.homology_with_basis(QQ) + sage: h20 = H.basis()[2,0]; h20 + h_{2,0} + sage: h20.to_cycle() + -(0, 1, 2) + (0, 1, 3) - (0, 2, 3) + (1, 2, 3) + + Chains are written as linear combinations of simplices + `\sigma`. Cochains are written as linear combinations of + characteristic functions `\chi_{\sigma}` for those + simplices:: + + sage: S2.cohomology_ring(QQ).basis()[2,0].to_cycle() + \chi_(0, 1, 3) + sage: S2.cohomology_ring(QQ).basis()[0,0].to_cycle() + \chi_(0,) + \chi_(1,) + \chi_(2,) + \chi_(3,) + """ + if not self.is_homogeneous(): + raise ValueError("only defined for homogeneous elements") + return sum(c * self.parent()._to_cycle_on_basis(i) for i,c in self) + +class CohomologyRing(HomologyVectorSpaceWithBasis): + """ + The cohomology ring. + + .. NOTE:: + + This is not intended to be created directly by the user, but + instead via the + :meth:`cohomology ring` + of a :class:`cell + complex`. + + INPUT: + + - ``base_ring`` -- must be a field + - ``cell_complex`` -- the cell complex whose homology we are + computing + + EXAMPLES:: + + sage: CP2 = simplicial_complexes.ComplexProjectivePlane() + sage: H = CP2.cohomology_ring(QQ) + sage: H.basis(2) + Finite family {(2, 0): h^{2,0}} + sage: x = H.basis(2)[2,0] + + The product structure is the cup product:: + + sage: x.cup_product(x) + h^{4,0} + sage: x * x + h^{4,0} + + There are mod 2 cohomology operations defined, also:: + + sage: Hmod2 = CP2.cohomology_ring(GF(2)) + sage: y = Hmod2.basis(2)[2,0] + sage: y.Sq(2) + h^{4,0} + """ + def __init__(self, base_ring, cell_complex): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.ProjectivePlane() + sage: H = RP2.cohomology_ring(GF(2)) + sage: TestSuite(H).run() + sage: H = RP2.cohomology_ring(GF(5)) + sage: TestSuite(H).run() + """ + cat = Algebras(base_ring).WithBasis().Graded() + HomologyVectorSpaceWithBasis.__init__(self, base_ring, cell_complex, True, cat) + + def _repr_(self): + """ + EXAMPLES:: + + sage: simplicial_complexes.Torus().cohomology_ring(QQ) + Cohomology ring of Simplicial complex with vertex set + (0, 1, 2, 3, 4, 5, 6) and 14 facets over Rational Field + """ + return "Cohomology ring of {} over {}".format(self._complex, self.base_ring()) + + @cached_method + def one(self): + """ + The multiplicative identity element. + + EXAMPLES:: + + sage: H = simplicial_complexes.Torus().cohomology_ring(QQ) + sage: H.one() + h^{0,0} + sage: all(H.one() * x == x == x * H.one() for x in H.basis()) + True + """ + one = self.base_ring().one() + d = {(0,i): one for i in self._graded_indices[0]} + return self._from_dict(d, remove_zeros=False) + + @cached_method + def product_on_basis(self, li, ri): + r""" + The cup product of the basis elements indexed by ``li`` and ``ri`` + in this cohomology ring. + + INPUT: + + - ``li``, ``ri`` -- index of a cohomology class + + .. SEEALSO:: + + :meth:`CohomologyRing.Element.cup_product` -- the + documentation for this method describes the algorithm. + + EXAMPLES:: + + sage: RP3 = simplicial_complexes.RealProjectiveSpace(3) + sage: H = RP3.cohomology_ring(GF(2)) + sage: c = H.basis()[1,0] + sage: c.cup_product(c).cup_product(c) # indirect doctest + h^{3,0} + + sage: T = simplicial_complexes.Torus() + sage: x,y = T.cohomology_ring(QQ).basis(1) + sage: x.cup_product(y) + h^{2,0} + sage: x.cup_product(x) + 0 + + sage: one = T.cohomology_ring(QQ).basis()[0,0] + sage: x.cup_product(one) + h^{1,0} + sage: one.cup_product(y) == y + True + sage: one.cup_product(one) + h^{0,0} + sage: x.cup_product(y) + y.cup_product(x) + 0 + + This also works with cubical complexes:: + + sage: T = cubical_complexes.Torus() + sage: x,y = T.cohomology_ring(QQ).basis(1) + sage: x.cup_product(y) + -h^{2,0} + sage: x.cup_product(x) + 0 + + and `\Delta`-complexes:: + + sage: T_d = delta_complexes.Torus() + sage: a,b = T_d.cohomology_ring(QQ).basis(1) + sage: a.cup_product(b) + h^{2,0} + sage: b.cup_product(a) + -h^{2,0} + sage: RP2 = delta_complexes.RealProjectivePlane() + sage: w = RP2.cohomology_ring(GF(2)).basis()[1,0] + sage: w.cup_product(w) + h^{2,0} + + A non-connected example:: + + sage: K = cubical_complexes.Torus().disjoint_union(cubical_complexes.Torus()) + sage: a,b,c,d = K.cohomology_ring(QQ).basis(1) + sage: x,y = K.cohomology_ring(QQ).basis(0) + sage: a.cup_product(x) == a + True + sage: a.cup_product(y) + 0 + """ + B = self.basis() + scomplex = self.complex() + base_ring = self.base_ring() + deg_left = li[0] + deg_right = ri[0] + deg_tot = deg_left + deg_right + left_cycle = self._to_cycle_on_basis(li) + right_cycle = self._to_cycle_on_basis(ri) + n_chains_left = scomplex.n_chains(deg_left, base_ring) + n_chains_right = scomplex.n_chains(deg_right, base_ring) + + result = {} + H = scomplex.homology_with_basis(base_ring) + for gamma_index in H._graded_indices.get(deg_tot, []): + gamma_coeff = base_ring.zero() + for cell, coeff in H._to_cycle_on_basis((deg_tot, gamma_index)): + if hasattr(cell, 'alexander_whitney'): + # Simplicial and cubical case: each cell has a + # method 'alexander_whitney' which computes + # the appropriate faces. + for (c, left_cell, right_cell) in cell.alexander_whitney(deg_left): + left = n_chains_left(left_cell) + right = n_chains_right(right_cell) + gamma_coeff += c * coeff * left_cycle.eval(left) * right_cycle.eval(right) + else: + # Delta complex case: each "cell" in n_chains + # is just a pair (integer, tuple), where the + # integer is its index in the list, and the + # jth entry of the tuple is the index of its + # jth face in the list of (n-1)-chains. Use + # this data to compute the appropriate faces + # by hand. + left_cell = cell + for i in range(deg_tot, deg_left, -1): + idx = left_cell[1][i] + left_cell = (idx, scomplex.n_cells(i-1)[idx]) + right_cell = cell + for i in range(deg_tot, deg_right, -1): + idx = right_cell[1][0] + right_cell = (idx, scomplex.n_cells(i-1)[idx]) + left = n_chains_left(left_cell) + right = n_chains_right(right_cell) + gamma_coeff += coeff * left_cycle.eval(left) * right_cycle.eval(right) + if gamma_coeff != base_ring.zero(): + result[(deg_tot, gamma_index)] = gamma_coeff + return self._from_dict(result, remove_zeros=False) + + class Element(HomologyVectorSpaceWithBasis.Element): + def cup_product(self, other): + r""" + Return the cup product of this element and ``other``. + + Algorithm: see González-Díaz and Réal [G-DR03]_, p. 88. + Given two cohomology classes, lift them to cocycle + representatives via the chain contraction for this + complex, using + :meth:`~HomologyVectorSpaceWithBasis.Element.to_cycle`. In + the sum of their dimensions, look at all of the homology + classes `\gamma`: lift each of those to a cycle + representative, apply the Alexander-Whitney diagonal map + to each cell in the cycle, evaluate the two cocycles on + these factors, and multiply. The result is the value of + the cup product cocycle on this homology class. After this + has been done for all homology classes, since homology and + cohomology are dual, one can tell which cohomology class + corresponds to the cup product. + + .. SEEALSO:: + + :meth:`CohomologyRing.product_on_basis` + + EXAMPLES:: + + sage: RP3 = simplicial_complexes.RealProjectiveSpace(3) + sage: H = RP3.cohomology_ring(GF(2)) + sage: c = H.basis()[1,0] + sage: c.cup_product(c) + h^{2,0} + sage: c * c * c + h^{3,0} + + We can also take powers:: + + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: a = RP2.cohomology_ring(GF(2)).basis()[1,0] + sage: a**0 + h^{0,0} + sage: a**1 + h^{1,0} + sage: a**2 + h^{2,0} + sage: a**3 + 0 + + A non-connected example:: + + sage: K = cubical_complexes.Torus().disjoint_union(cubical_complexes.Sphere(2)) + sage: a,b = K.cohomology_ring(QQ).basis(2) + sage: a**0 + h^{0,0} + h^{0,1} + + """ + return self * other + + def Sq(self, i): + r""" + Return the result of applying `Sq^i` to this element. + + INPUT: + + - ``i`` -- nonnegative integer + + .. WARNING:: + + This is only implemented for simplicial complexes. + + This cohomology operation is only defined in + characteristic 2. + + Algorithm: see González-Díaz and Réal [G-DR99]_, + Corollary 3.2. + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.RealProjectiveSpace(2) + sage: x = RP2.cohomology_ring(GF(2)).basis()[1,0] + sage: x.Sq(1) + h^{2,0} + + sage: K = RP2.suspension() + sage: K.set_immutable() + sage: y = K.cohomology_ring(GF(2)).basis()[2,0] + sage: y.Sq(1) + h^{3,0} + + sage: RP4 = simplicial_complexes.RealProjectiveSpace(4) + sage: H = RP4.cohomology_ring(GF(2)) + sage: x = H.basis()[1,0] + sage: y = H.basis()[2,0] + sage: z = H.basis()[3,0] + sage: x.Sq(1) == y + True + sage: z.Sq(1) # long time + h^{4,0} + + TESTS:: + + sage: T = cubical_complexes.Torus() + sage: x = T.cohomology_ring(GF(2)).basis()[1,0] + sage: x.Sq(1) + Traceback (most recent call last): + ... + NotImplementedError: Steenrod squares are only implemented for simplicial complexes + sage: S2 = simplicial_complexes.Sphere(2) + sage: x = S2.cohomology_ring(GF(7)).basis()[2,0] + sage: x.Sq(1) + Traceback (most recent call last): + ... + ValueError: Steenrod squares are only defined in characteristic 2 + """ + P = self.parent() + scomplex = P.complex() + if not isinstance(scomplex, SimplicialComplex): + raise NotImplementedError('Steenrod squares are only implemented for simplicial complexes') + base_ring = P.base_ring() + if base_ring.characteristic() != 2: + raise ValueError('Steenrod squares are only defined in characteristic 2') + # We keep the same notation as in [G-DR99]. + # The trivial cases: + if i == 0: + # Sq^0 is the identity. + return self + + # Construct each graded component of ``self`` + ret = P.zero() + H = scomplex.homology_with_basis(base_ring) + deg_comp = {} + for index,coeff in self: + d = deg_comp.get(index[0], {}) + d[index] = coeff + deg_comp[index[0]] = d + + # Do the square on each graded componenet of ``self``. + for j in deg_comp: + # Make it into an actual element + m = j + i + if not P._graded_indices.get(m, []) or i > j: + continue + elt = P._from_dict(deg_comp[j], remove_zeros=False) + if i == j: + ret += elt.cup_product(elt) + continue + + n = j - i + # Now assemble the indices over which the sums take place. + # S(n) is defined to be floor((m+1)/2) + floor(n/2). + S_n = (m+1) // 2 + n // 2 + if n == 0: + sums = [[S_n]] + else: + sums = [[i_n] + l for i_n in range(S_n, m+1) + for l in sum_indices(n-1, i_n, S_n)] + # At this point, 'sums' is a list of lists of the form + # [i_n, i_{n-1}, ..., i_0]. (It is reversed from the + # obvious order because this is closer to the order in + # which the face maps will be applied.) Now we sum over + # these, according to the formula in [G-DR99], Corollary 3.2. + result = {} + cycle = elt.to_cycle() + n_chains = scomplex.n_chains(j, base_ring) + for gamma_index in H._graded_indices.get(m, []): + gamma_coeff = base_ring.zero() + for cell, coeff in H._to_cycle_on_basis((m, gamma_index)): + for indices in sums: + indices = list(indices) + left = cell + right = cell + # Since we are working with a simplicial complex, 'cell' is a simplex. + if not m % 2: + left_endpoint = m + while indices: + right_endpoint = indices[0] - 1 + for k in range(left_endpoint, indices.pop(0), -1): + left = left.face(k) + try: + left_endpoint = indices[0] - 1 + for k in range(right_endpoint, indices.pop(0), -1): + right = right.face(k) + except IndexError: + pass + for k in range(right_endpoint, -1, -1): + right = right.face(k) + else: + right_endpoint = m + while indices: + left_endpoint = indices[0] - 1 + try: + for k in range(right_endpoint, indices.pop(0), -1): + right = right.face(k) + right_endpoint = indices[0] - 1 + except IndexError: + pass + for k in range(left_endpoint, indices.pop(0), -1): + left = left.face(k) + for k in range(right_endpoint, -1, -1): + right = right.face(k) + + left = n_chains(left) + right = n_chains(right) + gamma_coeff += coeff * cycle.eval(left) * cycle.eval(right) + if gamma_coeff != base_ring.zero(): + result[(m, gamma_index)] = gamma_coeff + ret += P._from_dict(result, remove_zeros=False) + return ret + +def sum_indices(k, i_k_plus_one, S_k_plus_one): + r""" + This is a recursive function for computing the indices for the + nested sums in González-Díaz and Réal [G-DR99]_, Corollary 3.2. + + In the paper, given indices `i_n`, `i_{n-1}`, ..., `i_{k+1}`, + given `k`, and given `S(k+1)`, the number `S(k)` is defined to be + + .. MATH:: + + S(k) = -S(k+1) + floor(k/2) + floor((k+1)/2) + i_{k+1}, + + and `i_k` ranges from `S(k)` to `i_{k+1}-1`. There are two special + cases: if `k=0`, then `i_0 = S(0)`. Also, the initial case of + `S(k)` is `S(n)`, which is set in the method :meth:`Sq` before + calling this function. For this function, given `k`, `i_{k+1}`, + and `S(k+1)`, return a list consisting of the allowable possible + indices `[i_k, i_{k-1}, ..., i_1, i_0]` given by the above + formula. + + INPUT: + + - ``k`` -- non-negative integer + - ``i_k_plus_one`` -- the positive integer `i_{k+1}` + - ``S_k_plus_one`` -- the integer `S(k+1)` + + EXAMPLES:: + + sage: from sage.homology.homology_vector_space_with_basis import sum_indices + sage: sum_indices(1, 3, 3) + [[1, 0], [2, 1]] + sage: sum_indices(0, 4, 2) + [[2]] + """ + S_k = -S_k_plus_one + k//2 + (k+1)//2 + i_k_plus_one + if k == 0: + return [[S_k]] + return [[i_k] + l for i_k in range(S_k, i_k_plus_one) + for l in sum_indices(k-1, i_k, S_k)] + diff --git a/src/sage/homology/koszul_complex.py b/src/sage/homology/koszul_complex.py index a5e16aa5e9f..592c2182c5c 100644 --- a/src/sage/homology/koszul_complex.py +++ b/src/sage/homology/koszul_complex.py @@ -14,7 +14,7 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent -from sage.combinat.choose_nk import rank +from sage.combinat.combination import rank from sage.rings.arith import binomial from sage.rings.all import ZZ from sage.matrix.constructor import matrix diff --git a/src/sage/homology/simplicial_complex.py b/src/sage/homology/simplicial_complex.py index 534340fbc16..98c5ab48003 100644 --- a/src/sage/homology/simplicial_complex.py +++ b/src/sage/homology/simplicial_complex.py @@ -158,18 +158,19 @@ from sage.misc.lazy_import import lazy_import from sage.homology.cell_complex import GenericCellComplex from sage.structure.sage_object import SageObject -from sage.structure.category_object import CategoryObject +from sage.structure.parent import Parent from sage.rings.integer import Integer from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.sets.set import Set from sage.rings.integer_ring import ZZ from sage.structure.parent_gens import normalize_names from sage.misc.latex import latex +from sage.misc.misc import union from sage.matrix.constructor import matrix from sage.homology.chain_complex import ChainComplex from sage.graphs.graph import Graph from functools import reduce -lazy_import('sage.categories.category_types', 'SimplicialComplexes') +lazy_import('sage.categories.simplicial_complexes', 'SimplicialComplexes') def lattice_paths(t1, t2, length=None): """ @@ -625,6 +626,50 @@ def product(self, other, rename_vertices=True): answer.append(Simplex(new)) return answer + def alexander_whitney(self, dim): + r""" + Subdivide this simplex into a pair of simplices. + + If this simplex has vertices `v_0`, `v_1`, ..., `v_n`, then + subdivide it into simplices `(v_0, v_1, ..., v_{dim})` and + `(v_{dim}, v_{dim + 1}, ..., v_n)`. + + INPUTS: + + - ``dim`` -- integer between 0 and one more than the + dimension of this simplex + + OUTPUT: + + - a list containing just the triple ``(1, left, right)``, + where ``left`` and ``right`` are the two simplices described + above. + + This method allows one to construct a coproduct from the + `p+q`-chains to the tensor product of the `p`-chains and the + `q`-chains. The number 1 (a Sage integer) is the coefficient + of ``left tensor right`` in this coproduct. (The corresponding + formula is more complicated for the cubes that make up a + cubical complex, and the output format is intended to be + consistent for both cubes and simplices.) + + Calling this method ``alexander_whitney`` is an abuse of + notation, since the actual Alexander-Whitney map goes from + `C(X \times Y) \to C(X) \otimes C(Y)`, where `C(-)` denotes + the chain complex of singular chains, but this subdivision of + simplices is at the heart of it. + + EXAMPLES:: + + sage: s = Simplex((0,1,3,4)) + sage: s.alexander_whitney(0) + [(1, (0,), (0, 1, 3, 4))] + sage: s.alexander_whitney(2) + [(1, (0, 1, 3), (3, 4))] + """ + return [(ZZ.one(), Simplex(self.tuple()[:dim+1]), + Simplex(self.tuple()[dim:]))] + def __cmp__(self, other): """ Return ``True`` iff this simplex is the same as ``other``: that @@ -691,7 +736,7 @@ def _latex_(self): """ return latex(self.__tuple) -class SimplicialComplex(CategoryObject, GenericCellComplex): +class SimplicialComplex(Parent, GenericCellComplex): r""" Define a simplicial complex. @@ -792,7 +837,15 @@ class SimplicialComplex(CategoryObject, GenericCellComplex): True sage: SimplicialComplex(S, is_immutable=False).is_mutable() True - """ + + .. WARNING:: + + Simplicial complexes are not proper parents as they do + not possess element classes. In particular, parents are assumed + to be hashable (and hence immutable) by the coercion framework. + However this is close enough to being a parent with elements + being the faces of ``self`` that we currently allow this abuse. + """ def __init__(self, maximal_faces=None, @@ -833,8 +886,7 @@ def __init__(self, if (maximal_faces is not None and from_characteristic_function is not None): raise ValueError("maximal_faces and from_characteristic_function cannot be both defined") - CategoryObject.__init__(self, category=SimplicialComplexes()) - from sage.misc.misc import union + Parent.__init__(self, category=SimplicialComplexes().Finite()) C = None vertex_set = [] @@ -987,7 +1039,7 @@ def __cmp__(self,right): sage: X == SimplicialComplex([[1,3]]) True """ - if set(self._facets) == set(right._facets): + if isinstance(right, SimplicialComplex) and set(self._facets) == set(right._facets): return 0 else: return -1 @@ -1033,6 +1085,57 @@ def vertices(self): """ return self._vertex_set + def _an_element_(self): + """ + The first facet of this complex. + + EXAMPLES:: + + sage: SimplicialComplex()._an_element_() + () + sage: simplicial_complexes.Sphere(3)._an_element_() + (1, 2, 3, 4) + """ + return self.facets()[0] + + def __contains__(self, x): + """ + True if ``x`` is a simplex which is contained in this complex. + + EXAMPLES:: + + sage: K = SimplicialComplex([(0,1,2), (0,2,3)]) + sage: Simplex((0,2)) in K + True + sage: Simplex((1,3)) in K + False + sage: 0 in K # not a simplex + False + """ + if not isinstance(x, Simplex): + return False + dim = x.dimension() + return x in self.n_faces(dim) + + def __call__(self, simplex): + """ + If ``simplex`` is a simplex in this complex, return it. + Otherwise, raise a ``ValueError``. + + EXAMPLE:: + + sage: K = SimplicialComplex([(0,1,2), (0,2,3)]) + sage: K(Simplex((1,2))) + (1, 2) + sage: K(Simplex((0,1,3))) + Traceback (most recent call last): + ... + ValueError: the simplex is not in this complex + """ + if simplex not in self: + raise ValueError('the simplex is not in this complex') + return simplex + def maximal_faces(self): """ The maximal faces (a.k.a. facets) of this simplicial complex. @@ -2081,8 +2184,6 @@ def add_face(self, face): self._facets = Facets # Update the vertex set - from sage.misc.misc import union - if self._sorted: self._vertex_set = Simplex(sorted(reduce(union, [self._vertex_set, new_face]))) else: @@ -3284,11 +3385,18 @@ def _Hom_(self, other, category=None): sage: T = simplicial_complexes.Sphere(2) sage: H = Hom(S,T) # indirect doctest sage: H - Set of Morphisms from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} in Category of simplicial complexes + Set of Morphisms from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + in Category of finite simplicial complexes sage: f = {0:0,1:1,2:3} sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 3} from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 3 sage: S._Hom_(T, Objects()) Traceback (most recent call last): diff --git a/src/sage/homology/simplicial_complex_homset.py b/src/sage/homology/simplicial_complex_homset.py index 46d3aed15d4..041288618b6 100644 --- a/src/sage/homology/simplicial_complex_homset.py +++ b/src/sage/homology/simplicial_complex_homset.py @@ -6,9 +6,7 @@ - Travis Scrimshaw (2012-08-18): Made all simplicial complexes immutable to work with the homset cache. -EXAMPLES: - -:: +EXAMPLES:: sage: S = simplicial_complexes.Sphere(1) sage: T = simplicial_complexes.Sphere(2) @@ -16,7 +14,12 @@ sage: f = {0:0,1:1,2:3} sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 3} from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 3 sage: x.is_injective() True sage: x.is_surjective() @@ -33,7 +36,7 @@ sage: S = simplicial_complexes.Sphere(1) sage: T = simplicial_complexes.Sphere(2) sage: H = Hom(S,T) - sage: loads(dumps(H))==H + sage: loads(dumps(H)) == H True """ @@ -55,7 +58,7 @@ #***************************************************************************** import sage.categories.homset -import sage.homology.simplicial_complex_morphism as simplicial_complex_morphism +from sage.homology.simplicial_complex_morphism import SimplicialComplexMorphism def is_SimplicialComplexHomset(x): """ @@ -67,7 +70,9 @@ def is_SimplicialComplexHomset(x): sage: T = SimplicialComplex(is_mutable=False) sage: H = Hom(S, T) sage: H - Set of Morphisms from Simplicial complex with vertex set () and facets {()} to Simplicial complex with vertex set () and facets {()} in Category of simplicial complexes + Set of Morphisms from Simplicial complex with vertex set () and facets {()} + to Simplicial complex with vertex set () and facets {()} + in Category of finite simplicial complexes sage: from sage.homology.simplicial_complex_homset import is_SimplicialComplexHomset sage: is_SimplicialComplexHomset(H) True @@ -80,7 +85,7 @@ def __call__(self, f): INPUT: - ``f`` -- a dictionary with keys exactly the vertices of the domain - and values vertices of the codomain + and values vertices of the codomain EXAMPLES:: @@ -90,13 +95,16 @@ def __call__(self, f): sage: H = Hom(S,T) sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 2, 4: 2} from Simplicial complex with vertex set (0, 1, 2, 3, 4) and 5 facets to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3, 4) and 5 facets + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: [0, 1, 2, 3, 4] --> [0, 1, 2, 2, 2] """ - return simplicial_complex_morphism.SimplicialComplexMorphism(f,self.domain(),self.codomain()) + return SimplicialComplexMorphism(f,self.domain(),self.codomain()) def diagonal_morphism(self,rename_vertices=True): r""" - Returns the diagonal morphism in `Hom(S, S \times S)`. + Return the diagonal morphism in `Hom(S, S \times S)`. EXAMPLES:: @@ -104,36 +112,40 @@ def diagonal_morphism(self,rename_vertices=True): sage: H = Hom(S,S.product(S, is_mutable=False)) sage: d = H.diagonal_morphism() sage: d - Simplicial complex morphism {0: 'L0R0', 1: 'L1R1', 2: 'L2R2', 3: 'L3R3'} from - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} - to Simplicial complex with 16 vertices and 96 facets + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + To: Simplicial complex with 16 vertices and 96 facets + Defn: 0 |--> L0R0 + 1 |--> L1R1 + 2 |--> L2R2 + 3 |--> L3R3 sage: T = SimplicialComplex([[0], [1]], is_mutable=False) sage: U = T.product(T,rename_vertices = False, is_mutable=False) sage: G = Hom(T,U) sage: e = G.diagonal_morphism(rename_vertices = False) sage: e - Simplicial complex morphism {0: (0, 0), 1: (1, 1)} from - Simplicial complex with vertex set (0, 1) and facets {(0,), (1,)} - to Simplicial complex with 4 vertices and facets {((1, 1),), ((1, 0),), ((0, 0),), ((0, 1),)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1) and facets {(0,), (1,)} + To: Simplicial complex with 4 vertices and facets {((1, 1),), ((1, 0),), ((0, 0),), ((0, 1),)} + Defn: 0 |--> (0, 0) + 1 |--> (1, 1) """ - - if self._codomain == self._domain.product(self._domain,rename_vertices=rename_vertices): - X = self._domain.product(self._domain,rename_vertices=rename_vertices) - f = dict() - if rename_vertices: - for i in self._domain.vertices().set(): - f[i] = "L"+str(i)+"R"+str(i) - else: - for i in self._domain.vertices().set(): - f[i] = (i,i) - return simplicial_complex_morphism.SimplicialComplexMorphism(f, self._domain,X) + # Preserve whether the codomain is mutable when renaming the vertices. + mutable = self._codomain.is_mutable() + X = self._domain.product(self._domain,rename_vertices=rename_vertices, is_mutable=mutable) + if self._codomain != X: + raise TypeError("diagonal morphism is only defined for Hom(X,XxX)") + f = {} + if rename_vertices: + f = {i: "L{0}R{0}".format(i) for i in self._domain.vertices().set()} else: - raise TypeError("Diagonal morphism is only defined for Hom(X,XxX).") + f = {i: (i,i) for i in self._domain.vertices().set()} + return SimplicialComplexMorphism(f, self._domain, X) def identity(self): """ - Returns the identity morphism of `Hom(S,S)`. + Return the identity morphism of `Hom(S,S)`. EXAMPLES:: @@ -146,21 +158,18 @@ def identity(self): sage: T = SimplicialComplex([[0,1]], is_mutable=False) sage: G = Hom(T,T) sage: G.identity() - Simplicial complex morphism {0: 0, 1: 1} from - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} to - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} + Simplicial complex endomorphism of Simplicial complex with vertex set (0, 1) and facets {(0, 1)} + Defn: 0 |--> 0 + 1 |--> 1 """ - if self.is_endomorphism_set(): - f = dict() - for i in self._domain.vertices().set(): - f[i]=i - return simplicial_complex_morphism.SimplicialComplexMorphism(f,self._domain,self._codomain) - else: - raise TypeError("Identity map is only defined for endomorphism sets.") + if not self.is_endomorphism_set(): + raise TypeError("identity map is only defined for endomorphism sets") + f = {i:i for i in self._domain.vertices().set()} + return SimplicialComplexMorphism(f, self._domain, self._codomain) def an_element(self): """ - Returns a (non-random) element of ``self``. + Return a (non-random) element of ``self``. EXAMPLES:: @@ -169,17 +178,19 @@ def an_element(self): sage: H = Hom(S,T) sage: x = H.an_element() sage: x - Simplicial complex morphism {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0} from Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets to Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6) and 7 facets + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets + To: Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6) and 7 facets + Defn: [0, 1, 2, 3, 4, 5, 6, 7] --> [0, 0, 0, 0, 0, 0, 0, 0] """ X_vertices = self._domain.vertices().set() try: i = next(self._codomain.vertices().set().__iter__()) except StopIteration: - if len(X_vertices) == 0: - return dict() + if not X_vertices: + return {} else: - raise TypeError("There are no morphisms from a non-empty simplicial complex to an empty simplicial comples.") - f = dict() - for x in X_vertices: - f[x]=i - return simplicial_complex_morphism.SimplicialComplexMorphism(f,self._domain,self._codomain) + raise TypeError("there are no morphisms from a non-empty simplicial complex to an empty simplicial complex") + f = {x:i for x in X_vertices} + return SimplicialComplexMorphism(f, self._domain, self._codomain) + diff --git a/src/sage/homology/simplicial_complex_morphism.py b/src/sage/homology/simplicial_complex_morphism.py index 75b4d0172a2..4ad792a2778 100644 --- a/src/sage/homology/simplicial_complex_morphism.py +++ b/src/sage/homology/simplicial_complex_morphism.py @@ -20,7 +20,10 @@ sage: S = SimplicialComplex([[0,2],[1,5],[3,4]], is_mutable=False) sage: H = Hom(S,S.product(S, is_mutable=False)) sage: H.diagonal_morphism() - Simplicial complex morphism {0: 'L0R0', 1: 'L1R1', 2: 'L2R2', 3: 'L3R3', 4: 'L4R4', 5: 'L5R5'} from Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and facets {(3, 4), (1, 5), (0, 2)} to Simplicial complex with 36 vertices and 18 facets + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and facets {(3, 4), (1, 5), (0, 2)} + To: Simplicial complex with 36 vertices and 18 facets + Defn: [0, 1, 2, 3, 4, 5] --> ['L0R0', 'L1R1', 'L2R2', 'L3R3', 'L4R4', 'L5R5'] sage: S = SimplicialComplex([[0,2],[1,5],[3,4]], is_mutable=False) sage: T = SimplicialComplex([[0,2],[1,3]], is_mutable=False) @@ -53,8 +56,13 @@ sage: i = H.identity() sage: j = i.fiber_product(i) sage: j - Simplicial complex morphism {'L1R1': 1, 'L3R3': 3, 'L2R2': 2, 'L0R0': 0} from Simplicial complex with 4 vertices and 4 facets to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} - + Simplicial complex morphism: + From: Simplicial complex with 4 vertices and 4 facets + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: L1R1 |--> 1 + L3R3 |--> 3 + L2R2 |--> 2 + L0R0 |--> 0 sage: S = simplicial_complexes.Sphere(2) sage: T = S.product(SimplicialComplex([[0,1]]), rename_vertices = False, is_mutable=False) sage: H = Hom(T,S) @@ -72,7 +80,10 @@ sage: y = G(g) sage: z = y.fiber_product(x) sage: z # this is the mapping path space - Simplicial complex morphism {'L2R(2, 0)': 2, 'L2R(2, 1)': 2, 'L0R(0, 0)': 0, 'L0R(0, 1)': 0, 'L1R(1, 0)': 1, 'L1R(1, 1)': 1} from Simplicial complex with 6 vertices and 6 facets to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with 6 vertices and 6 facets + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: ['L2R(2, 0)', 'L2R(2, 1)', 'L0R(0, 0)', 'L0R(0, 1)', 'L1R(1, 0)', 'L1R(1, 1)'] --> [2, 2, 0, 0, 1, 1] """ #***************************************************************************** @@ -91,13 +102,15 @@ # #***************************************************************************** -import sage.homology.simplicial_complex as simplicial_complex -import sage.matrix.all as matrix -from sage.structure.sage_object import SageObject +from sage.homology.simplicial_complex import Simplex, SimplicialComplex +from sage.matrix.constructor import matrix, zero_matrix from sage.rings.integer_ring import ZZ from sage.homology.chain_complex_morphism import ChainComplexMorphism from sage.combinat.permutation import Permutation from sage.algebras.steenrod.steenrod_algebra_misc import convert_perm +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom +from sage.categories.simplicial_complexes import SimplicialComplexes def is_SimplicialComplexMorphism(x): """ @@ -116,7 +129,7 @@ def is_SimplicialComplexMorphism(x): """ return isinstance(x,SimplicialComplexMorphism) -class SimplicialComplexMorphism(SageObject): +class SimplicialComplexMorphism(Morphism): """ An element of this class is a morphism of simplicial complexes. """ @@ -143,7 +156,7 @@ def __init__(self,f,X,Y): sage: x.image() == y.image() False """ - if not isinstance(X,simplicial_complex.SimplicialComplex) or not isinstance(Y,simplicial_complex.SimplicialComplex): + if not isinstance(X,SimplicialComplex) or not isinstance(Y,SimplicialComplex): raise ValueError("X and Y must be SimplicialComplexes.") if not set(f.keys()) == X._vertex_set.set(): raise ValueError("f must be a dictionary from the vertex set of X to single values in the vertex set of Y.") @@ -155,12 +168,11 @@ def __init__(self,f,X,Y): fi = [] for j in tup: fi.append(f[j]) - v = simplicial_complex.Simplex(set(fi)) + v = Simplex(set(fi)) if not v in Y_faces[v.dimension()]: raise ValueError("f must be a dictionary from the vertices of X to the vertices of Y.") self._vertex_dictionary = f - self._domain = X - self._codomain = Y + Morphism.__init__(self, Hom(X,Y,SimplicialComplexes())) def __eq__(self,x): """ @@ -172,7 +184,11 @@ def __eq__(self,x): sage: H = Hom(S,S) sage: i = H.identity() sage: i - Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 3} from Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex endomorphism of Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 + 3 |--> 3 sage: f = {0:0,1:1,2:2,3:2} sage: j = H(f) sage: i==j @@ -187,9 +203,8 @@ def __eq__(self,x): sage: l = G(g) sage: k == l True - """ - if not isinstance(x,SimplicialComplexMorphism) or self._codomain != x._codomain or self._domain != x._domain or self._vertex_dictionary != x._vertex_dictionary: + if not isinstance(x,SimplicialComplexMorphism) or self.codomain() != x.codomain() or self.domain() != x.domain() or self._vertex_dictionary != x._vertex_dictionary: return False else: return True @@ -227,8 +242,8 @@ def __call__(self,x,orientation=False): sage: g(Simplex([0,1]), orientation=True) ((0, 1), -1) """ - dim = self._domain.dimension() - if not isinstance(x,simplicial_complex.Simplex) or x.dimension() > dim or not x in self._domain.faces()[x.dimension()]: + dim = self.domain().dimension() + if not isinstance(x,Simplex) or x.dimension() > dim or not x in self.domain().faces()[x.dimension()]: raise ValueError("x must be a simplex of the source of f") tup=x.tuple() fx=[] @@ -239,25 +254,42 @@ def __call__(self,x,orientation=False): oriented = Permutation(convert_perm(fx)).signature() else: oriented = 1 - return (simplicial_complex.Simplex(set(fx)), oriented) + return (Simplex(set(fx)), oriented) else: - return simplicial_complex.Simplex(set(fx)) + return Simplex(set(fx)) - def _repr_(self): + def _repr_type(self): """ - Return a string representation of ``self``. + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(1) + sage: T = simplicial_complexes.Sphere(2) + sage: H = Hom(S,T) + sage: f = {0:0,1:1,2:2} + sage: H(f)._repr_type() + 'Simplicial complex' + """ + return "Simplicial complex" + + def _repr_defn(self): + """ + If there are fewer than 5 vertices, print the image of each vertex + on a separate line. Otherwise, print the map as a single line. EXAMPLES:: - sage: S = simplicial_complexes.Sphere(2) - sage: H = Hom(S,S) - sage: i = H.identity() - sage: i - Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 3} from Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} - sage: i._repr_() - 'Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 3} from Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)}' + sage: S = simplicial_complexes.Simplex(1) + sage: print Hom(S,S).identity()._repr_defn() + 0 |--> 0 + 1 |--> 1 + sage: T = simplicial_complexes.Torus() + sage: print Hom(T,T).identity()._repr_defn() + [0, 1, 2, 3, 4, 5, 6] --> [0, 1, 2, 3, 4, 5, 6] """ - return "Simplicial complex morphism " + str(self._vertex_dictionary) + " from " + self._domain._repr_() + " to " + self._codomain._repr_() + vd = self._vertex_dictionary + if len(vd) < 5: + return '\n'.join("{} |--> {}".format(v, vd[v]) for v in vd) + return "{} --> {}".format(vd.keys(), vd.values()) def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain=False): """ @@ -271,10 +303,17 @@ def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain= sage: f = {0:0,1:1,2:2} sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 2} from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 sage: a = x.associated_chain_complex_morphism() sage: a - Chain complex morphism from Chain complex with at most 2 nonzero terms over Integer Ring to Chain complex with at most 3 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 3 nonzero terms over Integer Ring sage: a._matrix_dictionary {0: [0 0 0] [0 1 0] @@ -288,13 +327,21 @@ def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain= [0 0 1], 2: []} sage: x.associated_chain_complex_morphism(augmented=True) - Chain complex morphism from Chain complex with at most 3 nonzero terms over Integer Ring to Chain complex with at most 4 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Integer Ring + To: Chain complex with at most 4 nonzero terms over Integer Ring sage: x.associated_chain_complex_morphism(cochain=True) - Chain complex morphism from Chain complex with at most 3 nonzero terms over Integer Ring to Chain complex with at most 2 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring sage: x.associated_chain_complex_morphism(augmented=True,cochain=True) - Chain complex morphism from Chain complex with at most 4 nonzero terms over Integer Ring to Chain complex with at most 3 nonzero terms over Integer Ring + Chain complex morphism: + From: Chain complex with at most 4 nonzero terms over Integer Ring + To: Chain complex with at most 3 nonzero terms over Integer Ring sage: x.associated_chain_complex_morphism(base_ring=GF(11)) - Chain complex morphism from Chain complex with at most 2 nonzero terms over Finite Field of size 11 to Chain complex with at most 3 nonzero terms over Finite Field of size 11 + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Finite Field of size 11 + To: Chain complex with at most 3 nonzero terms over Finite Field of size 11 Some simplicial maps which reverse the orientation of a few simplices:: @@ -315,20 +362,18 @@ def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain= {0: [0 1] [1 0], 1: [-1]} """ - max_dim = max(self._domain.dimension(),self._codomain.dimension()) - min_dim = min(self._domain.dimension(),self._codomain.dimension()) + max_dim = max(self.domain().dimension(),self.codomain().dimension()) + min_dim = min(self.domain().dimension(),self.codomain().dimension()) matrices = {} if augmented is True: - m = matrix.Matrix(base_ring,1,1,1) + m = matrix(base_ring,1,1,1) if not cochain: matrices[-1] = m else: matrices[-1] = m.transpose() for dim in range(min_dim+1): -# X_faces = list(self._domain.faces()[dim]) -# Y_faces = list(self._codomain.faces()[dim]) - X_faces = list(self._domain.n_cells(dim)) - Y_faces = list(self._codomain.n_cells(dim)) + X_faces = list(self.domain().n_cells(dim)) + Y_faces = list(self.codomain().n_cells(dim)) num_faces_X = len(X_faces) num_faces_Y = len(Y_faces) mval = [0 for i in range(num_faces_X*num_faces_Y)] @@ -338,33 +383,33 @@ def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain= pass else: mval[X_faces.index(i)+(Y_faces.index(y)*num_faces_X)] = oriented - m = matrix.Matrix(base_ring,num_faces_Y,num_faces_X,mval,sparse=True) + m = matrix(base_ring,num_faces_Y,num_faces_X,mval,sparse=True) if not cochain: matrices[dim] = m else: matrices[dim] = m.transpose() for dim in range(min_dim+1,max_dim+1): try: - l1 = len(self._codomain.n_cells(dim)) + l1 = len(self.codomain().n_cells(dim)) except KeyError: l1 = 0 try: - l2 = len(self._domain.n_cells(dim)) + l2 = len(self.domain().n_cells(dim)) except KeyError: l2 = 0 - m = matrix.zero_matrix(base_ring,l1,l2,sparse=True) + m = zero_matrix(base_ring,l1,l2,sparse=True) if not cochain: matrices[dim] = m else: matrices[dim] = m.transpose() if not cochain: return ChainComplexMorphism(matrices,\ - self._domain.chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain),\ - self._codomain.chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) + self.domain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain),\ + self.codomain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) else: return ChainComplexMorphism(matrices,\ - self._codomain.chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain),\ - self._domain.chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) + self.codomain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain),\ + self.domain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) def image(self): """ @@ -408,41 +453,8 @@ def image(self): Simplicial complex with vertex set (0, 2) and facets {(0, 2)} """ - fa = [self(i) for i in self._domain.facets()] - return simplicial_complex.SimplicialComplex(fa, maximality_check=True) - - def domain(self): - """ - Returns the domain of the morphism. - - EXAMPLES:: - - sage: S = SimplicialComplex([[0,1],[2,3]], is_mutable=False) - sage: T = SimplicialComplex([[0,1]], is_mutable=False) - sage: f = {0:0,1:1,2:0,3:1} - sage: H = Hom(S,T) - sage: x = H(f) - sage: x.domain() - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(2, 3), (0, 1)} - """ - return self._domain - - def codomain(self): - """ - Returns the codomain of the morphism. - - EXAMPLES:: - - sage: S = SimplicialComplex([[0,1],[2,3]], is_mutable=False) - sage: T = SimplicialComplex([[0,1]], is_mutable=False) - sage: f = {0:0,1:1,2:0,3:1} - sage: H = Hom(S,T) - sage: x = H(f) - sage: x.codomain() - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} - - """ - return self._codomain + fa = [self(i) for i in self.domain().facets()] + return SimplicialComplex(fa, maximality_check=True) def is_surjective(self): """ @@ -469,7 +481,7 @@ def is_surjective(self): sage: x.is_surjective() True """ - return self._codomain == self.image() + return self.codomain() == self.image() def is_injective(self): """ @@ -492,7 +504,7 @@ def is_injective(self): True """ - v = [self._vertex_dictionary[i[0]] for i in self._domain.faces()[0]] + v = [self._vertex_dictionary[i[0]] for i in self.domain().faces()[0]] for i in v: if v.count(i) > 1: return False @@ -519,16 +531,21 @@ def is_identity(self): sage: f = {0:0,1:1,2:2,3:3} sage: x = H(f) sage: x - Simplicial complex morphism {0: 0, 1: 1, 2: 2, 3: 3} from Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} to Simplicial complex with vertex set (0, 1, 2, 3, 4) and 5 facets + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} + To: Simplicial complex with vertex set (0, 1, 2, 3, 4) and 5 facets + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 + 3 |--> 3 sage: x.is_identity() False - """ - if self._domain != self._codomain: + if self.domain() != self.codomain(): return False else: f = dict() - for i in self._domain._vertex_set.set(): + for i in self.domain()._vertex_set.set(): f[i] = i if self._vertex_dictionary != f: return False @@ -555,28 +572,31 @@ def fiber_product(self, other, rename_vertices = True): sage: y = G(g) sage: z = x.fiber_product(y) sage: z - Simplicial complex morphism {'L1R2': 1, 'L1R1': 1, 'L2R0': 0, 'L0R0': 0} - from Simplicial complex with 4 vertices and facets - {('L2R0',), ('L1R1',), ('L0R0', 'L1R2')} to Simplicial complex - with vertex set (0, 1, 2) and facets {(2,), (0, 1)} + Simplicial complex morphism: + From: Simplicial complex with 4 vertices and facets {('L2R0',), ('L1R1',), ('L0R0', 'L1R2')} + To: Simplicial complex with vertex set (0, 1, 2) and facets {(2,), (0, 1)} + Defn: L1R2 |--> 1 + L1R1 |--> 1 + L2R0 |--> 0 + L0R0 |--> 0 """ - if self._codomain != other._codomain: + if self.codomain() != other.codomain(): raise ValueError("self and other must have the same codomain.") - X = self._domain.product(other._domain,rename_vertices = rename_vertices) + X = self.domain().product(other.domain(),rename_vertices = rename_vertices) v = [] f = dict() - eff1 = self._domain._vertex_set - eff2 = other._domain._vertex_set + eff1 = self.domain()._vertex_set + eff2 = other.domain()._vertex_set for i in eff1: for j in eff2: - if self(simplicial_complex.Simplex([i])) == other(simplicial_complex.Simplex([j])): + if self(Simplex([i])) == other(Simplex([j])): if rename_vertices: v.append("L"+str(i)+"R"+str(j)) f["L"+str(i)+"R"+str(j)] = self._vertex_dictionary[i] else: v.append((i,j)) f[(i,j)] = self._vertex_dictionary[i] - return SimplicialComplexMorphism(f, X.generated_subcomplex(v), self._codomain) + return SimplicialComplexMorphism(f, X.generated_subcomplex(v), self.codomain()) def mapping_torus(self): r""" @@ -612,15 +632,93 @@ def mapping_torus(self): ... ValueError: self must have the same domain and codomain. """ - if self._domain != self._codomain: + if self.domain() != self.codomain(): raise ValueError("self must have the same domain and codomain.") map_dict = self._vertex_dictionary - interval = simplicial_complex.SimplicialComplex([["I0","I1"],["I1","I2"]]) - product = interval.product(self._domain,False) + interval = SimplicialComplex([["I0","I1"],["I1","I2"]]) + product = interval.product(self.domain(),False) facets = list(product.maximal_faces()) - for facet in self._domain._facets: + for facet in self.domain()._facets: left = [ ("I0",v) for v in facet ] right = [ ("I2",map_dict[v]) for v in facet ] for i in range(facet.dimension()+1): facets.append(tuple(left[:i+1]+right[i:])) - return simplicial_complex.SimplicialComplex(facets) + return SimplicialComplex(facets) + + def induced_homology_morphism(self, base_ring=None, cohomology=False): + """ + The map in (co)homology induced by this map + + INPUTS: + + - ``base_ring`` -- must be a field (optional, default ``QQ``) + + - ``cohomology`` -- boolean (optional, default ``False``). If + ``True``, the map induced in cohomology rather than homology. + + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(1) + sage: T = S.product(S, is_mutable=False) + sage: H = Hom(S,T) + sage: diag = H.diagonal_morphism() + sage: h = diag.induced_homology_morphism(QQ) + sage: h + Graded vector space morphism: + From: Homology module of Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} over Rational Field + To: Homology module of Simplicial complex with 9 vertices and 18 facets over Rational Field + Defn: induced by: + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} + To: Simplicial complex with 9 vertices and 18 facets + Defn: 0 |--> L0R0 + 1 |--> L1R1 + 2 |--> L2R2 + + We can view the matrix form for the homomorphism:: + + sage: h.to_matrix(0) # in degree 0 + [1] + sage: h.to_matrix(1) # in degree 1 + [ 2] + [-1] + sage: h.to_matrix() # the entire homomorphism + [ 1| 0] + [--+--] + [ 0| 2] + [ 0|-1] + [--+--] + [ 0| 0] + + We can evaluate it on (co)homology classes:: + + sage: coh = diag.induced_homology_morphism(QQ, cohomology=True) + sage: coh.to_matrix(1) + [1 1] + sage: x,y = list(T.cohomology_ring(QQ).basis(1)) + sage: coh(x) + h^{1,0} + sage: coh(2*x+3*y) + 5*h^{1,0} + + Note that the complexes must be immutable for this to + work. Many, but not all, complexes are immutable when + constructed:: + + sage: S.is_immutable() + True + sage: S.barycentric_subdivision().is_immutable() + False + sage: S2 = S.suspension() + sage: S2.is_immutable() + False + sage: h = Hom(S,S2)({0: 0, 1:1, 2:2}).induced_homology_morphism() + Traceback (most recent call last): + ... + ValueError: the domain and codomain complexes must be immutable + sage: S2.set_immutable(); S2.is_immutable() + True + sage: h = Hom(S,S2)({0: 0, 1:1, 2:2}).induced_homology_morphism() + """ + from homology_morphism import InducedHomologyMorphism + return InducedHomologyMorphism(self, base_ring, cohomology) diff --git a/src/sage/interfaces/expect.py b/src/sage/interfaces/expect.py index 789da270163..67f267f9154 100644 --- a/src/sage/interfaces/expect.py +++ b/src/sage/interfaces/expect.py @@ -67,6 +67,8 @@ from sage.env import SAGE_EXTCODE, LOCAL_IDENTIFIER from sage.misc.object_multiplexer import Multiplex +from six import reraise as raise_ + BAD_SESSION = -2 # The subprocess is a shared resource. In a multi-threaded @@ -883,7 +885,7 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if except (TypeError, RuntimeError): pass return self._eval_line(line,allow_use_file=allow_use_file, wait_for_prompt=wait_for_prompt, restart_if_needed=False) - raise RuntimeError, "%s\nError evaluating %s in %s"%(msg, line, self), sys.exc_info()[2] + raise_(RuntimeError, "%s\nError evaluating %s in %s"%(msg, line, self), sys.exc_info()[2]) if len(line)>0: try: @@ -1329,7 +1331,7 @@ def __init__(self, parent, value, is_name=False, name=None): # coercion to work properly. except (RuntimeError, ValueError) as x: self._session_number = -1 - raise TypeError, x, sys.exc_info()[2] + raise_(TypeError, x, sys.exc_info()[2]) except BaseException: self._session_number = -1 raise diff --git a/src/sage/interfaces/maxima.py b/src/sage/interfaces/maxima.py index 0acfd3f7463..bb6c9a1370d 100644 --- a/src/sage/interfaces/maxima.py +++ b/src/sage/interfaces/maxima.py @@ -511,7 +511,7 @@ def __init__(self, script_subdirectory=None, logfile=None, server=None, sage: maxima == loads(dumps(m)) True - We make sure labels are turned off (see trac 6816):: + We make sure labels are turned off (see :trac:`6816`):: sage: 'nolabels : true' in maxima._Expect__init_code True @@ -606,7 +606,7 @@ def _start(self): sage: m.is_running() True - Test that we can use more than 256MB RAM (see trac :trac:`6772`):: + Test that we can use more than 256MB RAM (see :trac:`6772`):: sage: a = maxima(10)^(10^5) sage: b = a^600 # long time -- about 10-15 seconds diff --git a/src/sage/interfaces/singular.py b/src/sage/interfaces/singular.py index 2e2190809f5..c9743de9501 100644 --- a/src/sage/interfaces/singular.py +++ b/src/sage/interfaces/singular.py @@ -334,6 +334,8 @@ from sage.misc.misc import get_verbose from sage.misc.superseded import deprecation +from six import reraise as raise_ + class SingularError(RuntimeError): """ Raised if Singular printed an error message @@ -1261,7 +1263,7 @@ def __init__(self, parent, type, value, is_name=False): # coercion to work properly. except SingularError as x: self._session_number = -1 - raise TypeError, x, sys.exc_info()[2] + raise_(TypeError, x, sys.exc_info()[2]) except BaseException: self._session_number = -1 raise diff --git a/src/sage/lfunctions/zero_sums.pyx b/src/sage/lfunctions/zero_sums.pyx index e35bf038bad..6c4535c3d9c 100644 --- a/src/sage/lfunctions/zero_sums.pyx +++ b/src/sage/lfunctions/zero_sums.pyx @@ -1552,7 +1552,8 @@ cdef class LFunctionZeroSum_EllipticCurve(LFunctionZeroSum_abstract): specified by max_Delta. This computation can be run on curves with very large conductor (so long as the conductor is known or quickly computable) when Delta is not too large (see below). - Uses Bober's rank bounding method as described in [Bob-13]. + + Uses Bober's rank bounding method as described in [Bob-13]_. INPUT: diff --git a/src/sage/libs/arb/acb.pxd b/src/sage/libs/arb/acb.pxd index 61336196ee3..0c6ac913efb 100644 --- a/src/sage/libs/arb/acb.pxd +++ b/src/sage/libs/arb/acb.pxd @@ -1,26 +1,166 @@ +from sage.libs.arb.arf cimport arf_t from sage.libs.arb.arb cimport arb_t, arb_struct +from sage.libs.arb.mag cimport mag_t +from sage.libs.flint.types cimport fmpz_t, fmpq_t cdef extern from "acb.h": ctypedef struct acb_struct: arb_struct real arb_struct imag ctypedef acb_struct[1] acb_t + ctypedef acb_struct *acb_ptr + ctypedef const acb_struct *acb_srcptr + + arb_t acb_realref(acb_t x) + arb_t acb_imagref(acb_t x) void acb_init(acb_t x) void acb_clear(acb_t x) + acb_ptr _acb_vec_init(long n) + void _acb_vec_clear(acb_ptr v, long n) bint acb_is_zero(const acb_t z) + bint acb_is_one(const acb_t z) + bint acb_is_finite(const acb_t z) bint acb_is_exact(const acb_t z) + bint acb_is_int(const acb_t z) + void acb_zero(acb_t z) + void acb_one(acb_t z) + void acb_onei(acb_t z) void acb_set(acb_t z, const acb_t x) - void acb_set_ui(acb_t z, unsigned long c) + void acb_set_ui(acb_t z, long x) + void acb_set_si(acb_t z, long x) + void acb_set_fmpz(acb_t z, const fmpz_t x) + void acb_set_arb(acb_t z, const arb_t c) + void acb_set_fmpq(acb_t z, const fmpq_t x, long prec) + void acb_set_round(acb_t z, const acb_t x, long prec) + void acb_set_round_fmpz(acb_t z, const fmpz_t x, long prec) + void acb_set_round_arb(acb_t z, const arb_t x, long prec) + void acb_swap(acb_t z, acb_t x) + + void acb_print(const acb_t x) + void acb_printd(const acb_t z, long digits) + + # void acb_randtest(acb_t z, flint_rand_t state, long prec, long mag_bits) + # void acb_randtest_special(acb_t z, flint_rand_t state, long prec, long mag_bits) + # void acb_randtest_precise(acb_t z, flint_rand_t state, long prec, long mag_bits) + # void acb_randtest_param(acb_t z, flint_rand_t state, long prec, long mag_bits) bint acb_equal(const acb_t x, const acb_t y) + bint acb_eq(const acb_t x, const acb_t y) + bint acb_ne(const acb_t x, const acb_t y) bint acb_overlaps(const acb_t x, const acb_t y) + void acb_get_abs_ubound_arf(arf_t u, const acb_t z, long prec) + void acb_get_abs_lbound_arf(arf_t u, const acb_t z, long prec) + void acb_get_rad_ubound_arf(arf_t u, const acb_t z, long prec) + void acb_get_mag(mag_t u, const acb_t x) + void acb_get_mag_lower(mag_t u, const acb_t x) + bint acb_contains_fmpq(const acb_t x, const fmpq_t y) + bint acb_contains_fmpz(const acb_t x, const fmpz_t y) + bint acb_contains(const acb_t x, const acb_t y) + bint acb_contains_zero(const acb_t x) + long acb_rel_error_bits(const acb_t x) + long acb_rel_accuracy_bits(const acb_t x) + long acb_bits(const acb_t x) + void acb_indeterminate(acb_t x) + void acb_trim(acb_t y, const acb_t x) + bint acb_is_real(const acb_t x) + bint acb_get_unique_fmpz(fmpz_t z, const acb_t x) + + void acb_arg(arb_t r, const acb_t z, long prec) + void acb_abs(arb_t r, const acb_t z, long prec) + void acb_neg(acb_t z, const acb_t x) + void acb_conj(acb_t z, const acb_t x) + void acb_add_ui(acb_t z, const acb_t x, unsigned long y, long prec) + void acb_add_fmpz(acb_t z, const acb_t x, const fmpz_t y, long prec) + void acb_add_arb(acb_t z, const acb_t x, const arb_t y, long prec) void acb_add(acb_t z, const acb_t x, const acb_t y, long prec) + void acb_sub_ui(acb_t z, const acb_t x, unsigned long y, long prec) + void acb_sub_fmpz(acb_t z, const acb_t x, const fmpz_t y, long prec) + void acb_sub_arb(acb_t z, const acb_t x, const arb_t y, long prec) void acb_sub(acb_t z, const acb_t x, const acb_t y, long prec) + void acb_mul_onei(acb_t z, const acb_t x) + void acb_div_onei(acb_t z, const acb_t x) + void acb_mul_ui(acb_t z, const acb_t x, unsigned long y, long prec) + void acb_mul_si(acb_t z, const acb_t x, long y, long prec) + void acb_mul_fmpz(acb_t z, const acb_t x, const fmpz_t y, long prec) + void acb_mul_arb(acb_t z, const acb_t x, const arb_t y, long prec) void acb_mul(acb_t z, const acb_t x, const acb_t y, long prec) + void acb_mul_2exp_si(acb_t z, const acb_t x, long e) + void acb_mul_2exp_fmpz(acb_t z, const acb_t x, const fmpz_t e) + void acb_cube(acb_t z, const acb_t x, long prec) + void acb_addmul(acb_t z, const acb_t x, const acb_t y, long prec) + void acb_addmul_ui(acb_t z, const acb_t x, unsigned long y, long prec) + void acb_addmul_si(acb_t z, const acb_t x, long y, long prec) + void acb_addmul_fmpz(acb_t z, const acb_t x, const fmpz_t y, long prec) + void acb_addmul_arb(acb_t z, const acb_t x, const arb_t y, long prec) + void acb_submul(acb_t z, const acb_t x, const acb_t y, long prec) + void acb_submul_ui(acb_t z, const acb_t x, unsigned long y, long prec) + void acb_submul_si(acb_t z, const acb_t x, long y, long prec) + void acb_submul_fmpz(acb_t z, const acb_t x, const fmpz_t y, long prec) + void acb_submul_arb(acb_t z, const acb_t x, const arb_t y, long prec) + void acb_inv(acb_t z, const acb_t x, long prec) + void acb_div_ui(acb_t z, const acb_t x, unsigned long y, long prec) + void acb_div_si(acb_t z, const acb_t x, long y, long prec) + void acb_div_fmpz(acb_t z, const acb_t x, const fmpz_t y, long prec) void acb_div(acb_t z, const acb_t x, const acb_t y, long prec) + void acb_const_pi(acb_t y, long prec) + + void acb_sqrt(acb_t r, const acb_t z, long prec) + void acb_rsqrt(acb_t r, const acb_t z, long prec) + void acb_pow_fmpz(acb_t y, const acb_t b, const fmpz_t e, long prec) + void acb_pow_ui(acb_t y, const acb_t b, unsigned long e, long prec) + void acb_pow_si(acb_t y, const acb_t b, long e, long prec) + void acb_pow_arb(acb_t z, const acb_t x, const arb_t y, long prec) void acb_pow(acb_t z, const acb_t x, const acb_t y, long prec) + + void acb_exp(acb_t y, const acb_t z, long prec) + void acb_exp_pi_i(acb_t y, const acb_t z, long prec) + void acb_exp_invexp(acb_t s, acb_t t, const acb_t z, long prec) + void acb_log(acb_t y, const acb_t z, long prec) + void acb_log1p(acb_t z, const acb_t x, long prec) + + void acb_sin(acb_t s, const acb_t z, long prec) + void acb_cos(acb_t c, const acb_t z, long prec) + void acb_sin_cos(arb_t s, arb_t c, const acb_t z, long prec) + void acb_tan(acb_t s, const acb_t z, long prec) + void acb_cot(acb_t s, const acb_t z, long prec) + void acb_sin_pi(acb_t s, const acb_t z, long prec) + void acb_cos_pi(acb_t s, const acb_t z, long prec) + void acb_sin_cos_pi(acb_t s, acb_t c, const acb_t z, long prec) + void acb_tan_pi(acb_t s, const acb_t z, long prec) + void acb_cot_pi(acb_t s, const acb_t z, long prec) + + void acb_atan(acb_t s, const acb_t z, long prec) + + void acb_sinh(acb_t s, const acb_t z, long prec) + void acb_cosh(acb_t c, const acb_t z, long prec) + void acb_sinh_cosh(acb_t s, acb_t c, const acb_t z, long prec) + void acb_tanh(acb_t s, const acb_t z, long prec) + void acb_coth(acb_t s, const acb_t z, long prec) + + void acb_rising_ui_bs(acb_t z, const acb_t x, unsigned long n, long prec) + void acb_rising_ui_rs(acb_t z, const acb_t x, unsigned long n, unsigned long step, long prec) + void acb_rising_ui_rec(acb_t z, const acb_t x, unsigned long n, long prec) + void acb_rising_ui(acb_t z, const acb_t x, unsigned long n, long prec) + + void acb_gamma(acb_t y, const acb_t x, long prec) + void acb_rgamma(acb_t y, const acb_t x, long prec) + void acb_lgamma(acb_t y, const acb_t x, long prec) + void acb_digamma(acb_t y, const acb_t x, long prec) + void acb_log_sin_pi(acb_t res, const acb_t z, long prec) + void acb_polygamma(acb_t z, const acb_t s, const acb_t z, long prec) + void acb_barnes_g(acb_t res, const acb_t z, long prec) + void acb_log_barnes_g(acb_t res, const acb_t z, long prec) + + void acb_zeta(acb_t z, const acb_t s, long prec) + void acb_hurwitz_zeta(acb_t z, const acb_t s, const acb_t a, long prec) + + void acb_polylog(acb_t w, const acb_t s, const acb_t z, long prec) + void acb_polylog_si(acb_t w, long s, const acb_t z, long prec) + + void acb_agm1(acb_t m, const acb_t z, long prec) + void acb_agm1_cpx(acb_ptr m, const acb_t z, long len, long prec) diff --git a/src/sage/libs/arb/acb_hypgeom.pxd b/src/sage/libs/arb/acb_hypgeom.pxd new file mode 100644 index 00000000000..7577adf7b84 --- /dev/null +++ b/src/sage/libs/arb/acb_hypgeom.pxd @@ -0,0 +1,61 @@ +from sage.libs.arb.acb cimport acb_t, acb_srcptr +from sage.libs.arb.mag cimport mag_t + +cdef extern from "acb_hypgeom.h": + void acb_hypgeom_pfq_bound_factor(mag_t C, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, unsigned long n) + long acb_hypgeom_pfq_choose_n(acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, long prec) + void acb_hypgeom_pfq_sum_forward(acb_t s, acb_t t, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, long n, long prec) + void acb_hypgeom_pfq_sum_rs(acb_t s, acb_t t, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, long n, long prec) + void acb_hypgeom_pfq_sum_bs(acb_t s, acb_t t, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, long n, long prec) + void acb_hypgeom_pfq_sum_fme(acb_t s, acb_t t, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, long n, long prec) + void acb_hypgeom_pfq_sum(acb_t s, acb_t t, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, long n, long prec) + void acb_hypgeom_pfq_sum_bs_invz(acb_t s, acb_t t, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t w, long n, long prec) + void acb_hypgeom_pfq_sum_invz(acb_t s, acb_t t, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, const acb_t w, long n, long prec) + void acb_hypgeom_pfq_direct(acb_t res, acb_srcptr a, long p, acb_srcptr b, long q, const acb_t z, long n, long prec) + # void acb_hypgeom_pfq_series_direct(acb_poly_t res, const acb_poly_struct * a, long p, const acb_poly_struct * b, long q, const acb_poly_t z, int regularized, long n, long len, long prec) + void acb_hypgeom_u_asymp(acb_t res, const acb_t a, const acb_t b, const acb_t z, long n, long prec) + bint acb_hypgeom_u_use_asymp(const acb_t z, long prec) + # void acb_hypgeom_u_1f1_series(acb_poly_t res, const acb_poly_t a, const acb_poly_t b, const acb_poly_t z, long len, long prec) + void acb_hypgeom_u_1f1(acb_t res, const acb_t a, const acb_t b, const acb_t z, long prec) + void acb_hypgeom_u(acb_t res, const acb_t a, const acb_t b, const acb_t z, long prec) + void acb_hypgeom_m_asymp(acb_t res, const acb_t a, const acb_t b, const acb_t z, bint regularized, long prec) + void acb_hypgeom_m_1f1(acb_t res, const acb_t a, const acb_t b, const acb_t z, bint regularized, long prec) + void acb_hypgeom_m(acb_t res, const acb_t a, const acb_t b, const acb_t z, bint regularized, long prec) + void acb_hypgeom_erf_1f1a(acb_t res, const acb_t z, long prec) + void acb_hypgeom_erf_1f1b(acb_t res, const acb_t z, long prec) + void acb_hypgeom_erf_asymp(acb_t res, const acb_t z, long prec, long prec2) + void acb_hypgeom_erf(acb_t res, const acb_t z, long prec) + void acb_hypgeom_erfc(acb_t res, const acb_t z, long prec) + void acb_hypgeom_erfi(acb_t res, const acb_t z, long prec) + void acb_hypgeom_bessel_j_asymp(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_j_0f1(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_j(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_y(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_i_asymp(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_i_0f1(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_i(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_k_asymp(acb_t res, const acb_t nu, const acb_t z, long prec) + # void acb_hypgeom_bessel_k_0f1_series(acb_poly_t res, const acb_poly_t nu, const acb_poly_t z, long len, long prec) + void acb_hypgeom_bessel_k_0f1(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_bessel_k(acb_t res, const acb_t nu, const acb_t z, long prec) + void acb_hypgeom_gamma_upper_asymp(acb_t res, const acb_t s, const acb_t z, bint modified, long prec) + void acb_hypgeom_gamma_upper_1f1a(acb_t res, const acb_t s, const acb_t z, bint modified, long prec) + void acb_hypgeom_gamma_upper_1f1b(acb_t res, const acb_t s, const acb_t z, bint modified, long prec) + void acb_hypgeom_gamma_upper_singular(acb_t res, long s, const acb_t z, bint modified, long prec) + void acb_hypgeom_gamma_upper(acb_t res, const acb_t s, const acb_t z, bint modified, long prec) + void acb_hypgeom_expint(acb_t res, const acb_t s, const acb_t z, long prec) + void acb_hypgeom_ei_asymp(acb_t res, const acb_t z, long prec) + void acb_hypgeom_ei_2f2(acb_t res, const acb_t z, long prec) + void acb_hypgeom_ei(acb_t res, const acb_t z, long prec) + void acb_hypgeom_si_asymp(acb_t res, const acb_t z, long prec) + void acb_hypgeom_si_1f2(acb_t res, const acb_t z, long prec) + void acb_hypgeom_si(acb_t res, const acb_t z, long prec) + void acb_hypgeom_ci_asymp(acb_t res, const acb_t z, long prec) + void acb_hypgeom_ci_2f3(acb_t res, const acb_t z, long prec) + void acb_hypgeom_ci(acb_t res, const acb_t z, long prec) + void acb_hypgeom_shi(acb_t res, const acb_t z, long prec) + void acb_hypgeom_chi_asymp(acb_t res, const acb_t z, long prec) + void acb_hypgeom_chi_2f3(acb_t res, const acb_t z, long prec) + void acb_hypgeom_chi(acb_t res, const acb_t z, long prec) + void acb_hypgeom_li(acb_t res, const acb_t z, int offset, long prec) + diff --git a/src/sage/libs/arb/arf.pxd b/src/sage/libs/arb/arf.pxd index ced559f8924..74d3f10d515 100644 --- a/src/sage/libs/arb/arf.pxd +++ b/src/sage/libs/arb/arf.pxd @@ -137,3 +137,5 @@ cdef extern from "arf.h": int arf_complex_mul(arf_t e, arf_t f, const arf_t a, const arf_t b, const arf_t c, const arf_t d, long prec, arf_rnd_t rnd) int arf_complex_mul_fallback(arf_t e, arf_t f, const arf_t a, const arf_t b, const arf_t c, const arf_t d, long prec, arf_rnd_t rnd) int arf_complex_sqr(arf_t e, arf_t f, const arf_t a, const arf_t b, long prec, arf_rnd_t rnd) + + long ARF_PREC_EXACT diff --git a/src/sage/libs/cremona/newforms.pyx b/src/sage/libs/cremona/newforms.pyx index 93f9bc95f9e..9f6877fb225 100644 --- a/src/sage/libs/cremona/newforms.pyx +++ b/src/sage/libs/cremona/newforms.pyx @@ -147,7 +147,7 @@ cdef class ECModularSymbol: sage: [M(1/i) for i in range(1,10)] [0, 0, 0, 0, 4, 0, 2, 0, -2] - TESTS (see :trac: `11211`):: + TESTS (see :trac:`11211`):: sage: from sage.libs.cremona.newforms import ECModularSymbol sage: E = EllipticCurve('11a') diff --git a/src/sage/libs/flint/fmpz.pxd b/src/sage/libs/flint/fmpz.pxd index dc497331ce0..99e748be1a4 100644 --- a/src/sage/libs/flint/fmpz.pxd +++ b/src/sage/libs/flint/fmpz.pxd @@ -1,3 +1,5 @@ +# distutils: libraries = flint + from libc.stdio cimport FILE from sage.libs.gmp.types cimport mpz_t from sage.libs.flint.types cimport * @@ -45,6 +47,8 @@ cdef extern from "flint/fmpz.h": int fmpz_fits_si(fmpz_t f) void fmpz_zero(fmpz_t f) void fmpz_one(fmpz_t f) + void fmpz_setbit(fmpz_t f, ulong i) + int fmpz_tstbit(fmpz_t f, ulong i) # Input and output int fmpz_read(fmpz_t) diff --git a/src/sage/matrix/action.pyx b/src/sage/matrix/action.pyx index 23ea977ba36..e9df2451601 100644 --- a/src/sage/matrix/action.pyx +++ b/src/sage/matrix/action.pyx @@ -53,8 +53,10 @@ AUTHOR: #***************************************************************************** # Copyright (C) 2007 Robert Bradshaw # -# Distributed under the terms of the GNU General Public License (GPL) -# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** @@ -63,6 +65,7 @@ import operator from matrix_space import MatrixSpace, is_MatrixSpace from sage.modules.free_module import FreeModule, is_FreeModule +from sage.structure.element cimport coercion_model cdef class MatrixMulAction(Action): @@ -70,8 +73,7 @@ cdef class MatrixMulAction(Action): if not is_MatrixSpace(G): raise TypeError, "Not a matrix space: %s" % G if G.base_ring() is not S.base_ring(): - from sage.structure.element import get_coercion_model - base = get_coercion_model().common_parent(G.base_ring(), S.base_ring()) + base = coercion_model.common_parent(G.base_ring(), S.base_ring()) else: base = G.base_ring() Action.__init__(self, G, S, is_left, operator.mul) diff --git a/src/sage/matrix/constructor.py b/src/sage/matrix/constructor.py index 97348ba1468..fdca50b03a0 100644 --- a/src/sage/matrix/constructor.py +++ b/src/sage/matrix/constructor.py @@ -1001,8 +1001,8 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', *args, **kwds) the matrix will have. See examples below for possible additional arguments. - - ``randomize`` - randomize the elements of the matrix, possibly - controlling the density of non-zero entries. + - ``randomize`` - create a matrix of random elements from the + base ring, possibly controlling the density of non-zero entries. - ``echelon_form`` - creates a matrix in echelon form @@ -1044,15 +1044,12 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', *args, **kwds) additional properties (i.e. when ``algorithm='randomize'``), most of the randomness is controlled by the ``random_element`` method for elements of the base ring of the matrix, so the - documentation of that method may be relevant or useful. Also, - the default is to not create zero entries, unless the - ``density`` keyword is set to something strictly less than - one. + documentation of that method may be relevant or useful. EXAMPLES: Random integer matrices. With no arguments, the majority of the entries - are -1 and 1, never zero, and rarely "large." :: + are zero, -1, and 1, and rarely "large." :: sage: random_matrix(ZZ, 5, 5) [ -8 2 0 0 1] @@ -1062,7 +1059,7 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', *args, **kwds) [ 4 -4 -6 5 0] The ``distribution`` keyword set to ``uniform`` will limit values - between -2 and 2, and never zero. :: + between -2 and 2. :: sage: random_matrix(ZZ, 5, 5, distribution='uniform') [ 1 0 -2 1 1] @@ -1072,8 +1069,8 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', *args, **kwds) [ 0 -2 -1 0 0] The ``x`` and ``y`` keywords can be used to distribute entries uniformly. - When both are used ``x`` is the minimum and ``y`` is one greater than the the maximum. - But still entries are never zero, even if the range contains zero. :: + When both are used ``x`` is the minimum and ``y`` is one greater than + the maximum. :: sage: random_matrix(ZZ, 4, 8, x=70, y=100) [81 82 70 81 78 71 79 94] @@ -1086,7 +1083,8 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', *args, **kwds) [ 3 3 0 3 -5 -2 1] [ 0 -2 -2 2 -3 -4 -2] - If only ``x`` is given, then it is used as the upper bound of a range starting at 0. :: + If only ``x`` is given, then it is used as the upper bound of a range + starting at 0. :: sage: random_matrix(ZZ, 5, 5, x=25) [20 16 8 3 8] @@ -1095,10 +1093,12 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', *args, **kwds) [19 16 17 15 7] [ 0 24 3 17 24] - To allow, and control, zero entries use the ``density`` keyword at a value - strictly below the default of 1.0, even if distributing entries across an - interval that does not contain zero already. Note that for a square matrix it - is only necessary to set a single dimension. :: + To control the number of nonzero entries, use the ``density`` keyword + at a value strictly below the default of 1.0. The ``density`` keyword + is used to compute the number of entries that will be nonzero, but the + same entry may be selected more than once. So the value provided will + be an upper bound for the density of the created matrix. Note that for + a square matrix it is only necessary to set a single dimension. :: sage: random_matrix(ZZ, 5, x=-10, y=10, density=0.75) [-6 1 0 0 0] @@ -1114,8 +1114,8 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', *args, **kwds) [ 0 28 22 0 0] [ 0 0 0 26 24] - It is possible to construct sparse matrices, where it may now be advisable - (but not required) to control the density of nonzero entries. :: + For a matrix with low density it may be advisable to insist on a sparse + representation, as this representation is not selected automatically. :: sage: A=random_matrix(ZZ, 5, 5) sage: A.is_sparse() @@ -1141,10 +1141,9 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', *args, **kwds) generation of random elements, by specifying limits on the absolute value of numerators and denominators (respectively). Entries will be positive and negative (map the absolute value function through the entries to get all - positive values), and zeros are avoided unless the density is set. If either - the numerator or denominator bound (or both) is not used, then the values - default to the distribution for `ZZ` described above that is most frequently - positive or negative one. :: + positive values). If either the numerator or denominator bound (or both) + is not used, then the values default to the distribution for ``ZZ`` + described above. :: sage: random_matrix(QQ, 2, 8, num_bound=20, den_bound=4) [ -1/2 6 13 -12 -2/3 -1/4 5 5] @@ -2158,6 +2157,59 @@ def elementary_matrix(arg0, arg1=None, **kwds): else: return elem.transpose() +@matrix_method +def circulant(v, sparse=None): + r""" + Return the circulant matrix specified by its 1st row `v` + + A circulant `n \times n` matrix specified by the 1st row `v=(v_0...v_{n-1})` is + the matrix $(c_{ij})_{0 \leq i,j\leq n-1}$, where $c_{ij}=v_{j-i \mod b}$. + + INPUT: + + - ``v`` -- a list or a vector of values + + - ``sparse`` -- ``None`` by default; if ``sparse`` is set to ``True``, the output + will be sparse. Respectively, setting it to ``False`` produces dense output. + If ``sparse`` is not set, and if ``v`` is a vector, the output sparsity is determined + by the sparsity of ``v``; else, the output will be dense. + + EXAMPLES:: + + sage: v=[1,2,3,4,8] + sage: matrix.circulant(v) + [1 2 3 4 8] + [8 1 2 3 4] + [4 8 1 2 3] + [3 4 8 1 2] + [2 3 4 8 1] + sage: m = matrix.circulant(vector(GF(3),[0,1,-1],sparse=True)); m + [0 1 2] + [2 0 1] + [1 2 0] + sage: m.is_sparse() + True + + TESTS:: + + sage: m = matrix.circulant(vector(GF(3),[0,1,-1],sparse=False)) + sage: m.is_sparse() + False + sage: matrix.circulant([0,1,-1]).is_sparse() + False + sage: matrix.circulant([0,1,-1], sparse=True).is_sparse() + True + """ + from exceptions import AttributeError + if sparse==None: + try: + sparse = v.is_sparse() + except AttributeError: + sparse = False + n = len(v) + return matrix(n, n, lambda i, j: v[(j-i)%n], sparse=sparse) + + def _determine_block_matrix_grid(sub_matrices): r""" For internal use. This tries to determine the dimensions @@ -4159,6 +4211,3 @@ def ith_to_zero_rotation_matrix(v, i, ring=None): entries[(k, k)] = 1 entries.update({(j,j):aa, (j,i):bb, (i,j):-bb, (i,i):aa}) return matrix(entries, nrows=dim, ring=ring) - - - diff --git a/src/sage/matrix/matrix1.pyx b/src/sage/matrix/matrix1.pyx index 1a05c581e21..d65eba6305d 100644 --- a/src/sage/matrix/matrix1.pyx +++ b/src/sage/matrix/matrix1.pyx @@ -9,18 +9,20 @@ TESTS:: sage: TestSuite(A).run() """ -################################################################################ +#***************************************************************************** # Copyright (C) 2005, 2006 William Stein # -# Distributed under the terms of the GNU General Public License (GPL). -# The full text of the GPL is available at: -# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ -################################################################################ +#***************************************************************************** include "sage/ext/python.pxi" import sage.modules.free_module +from sage.structure.element cimport coercion_model cdef class Matrix(matrix0.Matrix): @@ -1363,8 +1365,6 @@ cdef class Matrix(matrix0.Matrix): top_ring = self._base_ring bottom_ring = other._base_ring if top_ring is not bottom_ring: - from sage.structure.element import get_coercion_model - coercion_model = get_coercion_model() R = coercion_model.common_parent(top_ring, bottom_ring) if top_ring is not R: self = self.change_ring(R) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index e973cfd5bf0..50b32df386e 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -7875,22 +7875,26 @@ cdef class Matrix(matrix1.Matrix): def randomize(self, density=1, nonzero=False, *args, **kwds): """ - Randomize density proportion of the entries of this matrix, leaving - the rest unchanged. + Replace a proportion of the entries of a matrix by random elements, + leaving the remaining entries unchanged. .. note:: - We actually choose at random ``density`` proportion of entries of - the matrix and set them to random elements. It's possible that the - same position can be chosen multiple times, especially for a very - small matrix. + The locations of the entries of the matrix to change are + determined randomly, with the total number of locations + determined by the ``density`` keyword. These locations + are not guaranteed to be distinct. So it is possible + that the same position can be chosen multiple times, + especially for a very small matrix. The exception is + when ``density = 1``, in which case every entry of the + matrix will be changed. INPUT: - - ``density`` - ``float`` (default: 1); rough measure of the - proportion of nonzero entries in the random matrix - - ``nonzero`` - Bool (default: ``False``); whether the new entries - have to be non-zero + - ``density`` - ``float`` (default: ``1``); upper bound for the + proportion of entries that are changed + - ``nonzero`` - Bool (default: ``False``); if ``True``, then new + entries will be nonzero - ``*args, **kwds`` - Remaining parameters may be passed to the ``random_element`` function of the base ring @@ -7927,9 +7931,9 @@ cdef class Matrix(matrix1.Matrix): [0 0] [0 0] - Then we randomize it; the x and y parameters, which determine the - size of the random elements, are passed onto the ZZ random_element - method. + Then we randomize it; the ``x`` and ``y`` keywords, which determine the + size of the random elements, are passed on to the ``random_element`` + method for ``ZZ``. :: diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index 58569480464..6336b8e1c25 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -118,11 +118,13 @@ class MatrixSpace(UniqueRepresentation, parent_gens.ParentWithGens): sage: MatrixSpace(ZZ,10,5) Full MatrixSpace of 10 by 5 dense matrices over Integer Ring sage: MatrixSpace(ZZ,10,5).category() - Category of infinite modules over (euclidean domains and infinite enumerated sets) + Category of infinite modules over (euclidean domains + and infinite enumerated sets and metric spaces) sage: MatrixSpace(ZZ,10,10).category() - Category of infinite algebras over (euclidean domains and infinite enumerated sets) + Category of infinite algebras over (euclidean domains + and infinite enumerated sets and metric spaces) sage: MatrixSpace(QQ,10).category() - Category of infinite algebras over quotient fields + Category of infinite algebras over (quotient fields and metric spaces) TESTS:: diff --git a/src/sage/misc/c3_controlled.pyx b/src/sage/misc/c3_controlled.pyx index dbf8c5f19ac..9b40f1641af 100644 --- a/src/sage/misc/c3_controlled.pyx +++ b/src/sage/misc/c3_controlled.pyx @@ -326,9 +326,9 @@ For a typical category, few bases, if any, need to be added to force sage: x.mro == x.mro_standard False sage: x.all_bases_len() - 90 + 92 sage: x.all_bases_controlled_len() - 97 + 100 The following can be used to search through the Sage named categories for any that requires the addition of some bases:: @@ -343,8 +343,7 @@ for any that requires the addition of some bases:: Category of finite dimensional algebras with basis over Rational Field, Category of finite dimensional hopf algebras with basis over Rational Field, Category of finite permutation groups, - Category of graded hopf algebras with basis over Rational Field, - Category of hopf algebras with basis over Rational Field] + Category of graded hopf algebras with basis over Rational Field] AUTHOR: diff --git a/src/sage/misc/converting_dict.py b/src/sage/misc/converting_dict.py new file mode 100644 index 00000000000..6273b7e5e12 --- /dev/null +++ b/src/sage/misc/converting_dict.py @@ -0,0 +1,291 @@ +r""" +Converting Dictionary + +At the moment, the only class contained in this model is a key +converting dictionary, which applies some function (e.g. type +conversion function) to all arguments used as keys. + +.. It is conceivable that a other dicts might be added later on. + +AUTHORS: + +- Martin von Gagern (2015-01-31): initial version + +EXAMPLES: + +A ``KeyConvertingDict`` will apply a conversion function to all method +arguments which are keys:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d["3"] = 42 + sage: d.items() + [(3, 42)] + +This is used e.g. in the result of a variety, to allow access to the +result no matter how a generator is identified:: + + sage: K. = QQ[] + sage: I = ideal([x^2+2*y-5,x+y+3]) + sage: v = I.variety(AA)[0]; v + {x: 4.464101615137755?, y: -7.464101615137755?} + sage: v.keys()[0].parent() + Multivariate Polynomial Ring in x, y over Algebraic Real Field + sage: v[x] + 4.464101615137755? + sage: v["y"] + -7.464101615137755? +""" + +#***************************************************************************** +# Copyright (C) 2015 Martin von Gagern +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +import collections + +class KeyConvertingDict(dict): + r""" + A dictionary which automatically applys a conversions to its keys. + + The most common application is the case where the conversion + function is the object representing some category, so that key + conversion means a type conversion to adapt keys to that + category. This allows different representations for keys which in + turn makes accessing the correct element easier. + + INPUT: + + - ``key_conversion_function`` -- a function which will be + applied to all method arguments which represent keys. + - ``data`` -- optional dictionary or sequence of key-value pairs + to initialize this mapping. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d["3"] = 42 + sage: d.items() + [(3, 42)] + sage: d[5.0] = 64 + sage: d["05"] + 64 + + """ + + def __init__(self, key_conversion_function, data=None): + r""" + Construct a dictionary with a given conversion function. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d["3"] = 42 + sage: d.items() + [(3, 42)] + sage: KeyConvertingDict(int, {"5": 7}).items() + [(5, 7)] + sage: KeyConvertingDict(int, [("9", 99)]).items() + [(9, 99)] + """ + super(KeyConvertingDict, self).__init__() + self.key_conversion_function = key_conversion_function + if data: + self.update(data) + + def __getitem__(self, key): + r""" + Retrieve an element from the dictionary. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: d["3"] + 42 + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).__getitem__(key) + + def __setitem__(self, key, value): + r""" + Assign an element in the dictionary. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + - ``value`` -- The associated value, will be left unmodified. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d["3"] = 42 + sage: d.items() + [(3, 42)] + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).__setitem__(key, value) + + def __delitem__(self, key): + r""" + Remove a mapping from the dictionary. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: del d["3"] + sage: len(d) + 0 + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).__delitem__(key) + + def __contains__(self, key): + r""" + Test whether a given key is contained in the mapping. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: "3" in d + True + sage: 4 in d + False + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).__contains__(key) + + def has_key(self, key): + r""" + Deprecated; present just for the sake of compatibility. + Use ``key in self`` instead. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: d.has_key("3") + True + sage: d.has_key(4) + False + """ + return key in self + + def pop(self, key, *args): + r""" + Remove and retreive a given element from the dictionary + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + - ``default`` -- The value to return if the element is not mapped, optional. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d[3] = 42 + sage: d.pop("3") + 42 + sage: d.pop("3", 33) + 33 + sage: d.pop("3") + Traceback (most recent call last): + ... + KeyError: ... + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).pop(key, *args) + + def setdefault(self, key, default=None): + r""" + Create a given mapping unless there already exists a mapping + for that key. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + - ``default`` -- The value to associate with the key. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d.setdefault("3") + sage: d.items() + [(3, None)] + """ + key = self.key_conversion_function(key) + return super(KeyConvertingDict, self).setdefault(key, default) + + def update(self, *args, **kwds): + r""" + Update the dictionary with key-value pairs from another dictionary, + sequence of key-value pairs, or keyword arguments. + + INPUT: + + - ``key`` -- A value identifying the element, will be converted. + - ``args`` -- A single dict or sequence of pairs. + - ``kwds`` -- Named elements require that the conversion + function accept strings. + + EXAMPLES:: + + sage: from sage.misc.converting_dict import KeyConvertingDict + sage: d = KeyConvertingDict(int) + sage: d.update([("3",1),(4,2)]) + sage: d[3] + 1 + sage: d.update({"5": 7, "9": 12}) + sage: d[9] + 12 + sage: d = KeyConvertingDict(QQ['x']) + sage: d.update(x=42) + sage: d + {x: 42} + """ + f = self.key_conversion_function + u = super(KeyConvertingDict, self).update + if args: + if len(args) != 1: + raise TypeError("update expected at most 1 argument") + arg = args[0] + if isinstance(arg, collections.Mapping): + seq = ((f(k), arg[k]) for k in arg) + else: + seq = ((f(k), v) for k, v in arg) + u(seq) + if kwds: + seq = ((f(k), v) for k, v in kwds.iteritems()) + u(seq) diff --git a/src/sage/misc/functional.py b/src/sage/misc/functional.py index 607cca30874..5920e8e9c7b 100644 --- a/src/sage/misc/functional.py +++ b/src/sage/misc/functional.py @@ -124,7 +124,8 @@ def category(x): sage: V = VectorSpace(QQ,3) sage: category(V) - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis over + (quotient fields and metric spaces) """ try: return x.category() diff --git a/src/sage/misc/misc.py b/src/sage/misc/misc.py index e3a3e453223..8deb81cad68 100644 --- a/src/sage/misc/misc.py +++ b/src/sage/misc/misc.py @@ -1722,7 +1722,33 @@ def random_sublist(X, s): return [a for a in X if random.random() <= s] +def some_tuples(elements, repeat, bound): + r""" + Return an iterator over at most ``bound`` number of ``repeat``-tuples of + ``elements``. + + TESTS:: + + sage: from sage.misc.misc import some_tuples + sage: l = some_tuples([0,1,2,3], 2, 3) + sage: l + + sage: len(list(l)) + 3 + + sage: l = some_tuples(range(50), 3, 10) + sage: len(list(l)) + 10 + .. TODO:: + + Currently, this only return an iterator over the first element of the + cartesian product. It would be smarter to return something more + "random like" as it is used in tests. However, this should remain + deterministic. + """ + from itertools import islice, product + return islice(product(elements, repeat=repeat), bound) def powerset(X): r""" diff --git a/src/sage/misc/mrange.py b/src/sage/misc/mrange.py index 8bff4902c60..711059bb601 100644 --- a/src/sage/misc/mrange.py +++ b/src/sage/misc/mrange.py @@ -680,7 +680,7 @@ def cantor_product(*args, **kwds): TypeError: 'toto' is an invalid keyword argument for this function """ from itertools import count - from sage.combinat.integer_list import IntegerListsLex + from sage.combinat.integer_lists import IntegerListsLex m = len(args) # numer of factors lengths = [None] * m # None or length of factors diff --git a/src/sage/misc/sage_unittest.py b/src/sage/misc/sage_unittest.py index 7642066a69c..ba4327caffc 100644 --- a/src/sage/misc/sage_unittest.py +++ b/src/sage/misc/sage_unittest.py @@ -527,12 +527,12 @@ def some_elements(self, S=None): sage: list(tester.some_elements()) [0, 1, 2, 3, 4] - sage: C = CartesianProduct(Z, Z, Z, Z) + sage: C = cartesian_product([Z]*4) sage: len(C) 390625 sage: tester = InstanceTester(C, elements = C, max_runs=4) sage: list(tester.some_elements()) - [[0, 0, 0, 0], [0, 0, 0, 1], [0, 0, 0, 2], [0, 0, 0, 3]] + [(0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)] """ if S is None: if self._elements is None: diff --git a/src/sage/modular/abvar/abvar.py b/src/sage/modular/abvar/abvar.py index f70c34e56b4..21f26fb18eb 100644 --- a/src/sage/modular/abvar/abvar.py +++ b/src/sage/modular/abvar/abvar.py @@ -831,7 +831,7 @@ def __add__(self, other): sage: B + J0(33)[2] Abelian subvariety of dimension 2 of J0(33) - TESTS: This exposed a bug in HNF (see trac #4527):: + TESTS: This exposed a bug in HNF (see :trac:`4527`):: sage: A = J0(206).new_subvariety().decomposition()[3] ; A # long time Simple abelian subvariety 206d(1,206) of dimension 4 of J0(206) diff --git a/src/sage/modular/arithgroup/arithgroup_element.pyx b/src/sage/modular/arithgroup/arithgroup_element.pyx index 3402a75d254..46021d08082 100644 --- a/src/sage/modular/arithgroup/arithgroup_element.pyx +++ b/src/sage/modular/arithgroup/arithgroup_element.pyx @@ -137,19 +137,19 @@ cdef class ArithmeticSubgroupElement(MultiplicativeGroupElement): yield self.__x[1,1] def __repr__(self): - """ - Return the string representation of self. + r""" + Return the string representation of ``self``. EXAMPLES:: sage: Gamma1(5)([6,1,5,1]).__repr__() '[6 1]\n[5 1]' """ - return "%s"%self.__x + return "%s" % self.__x def _latex_(self): - """ - Return latex representation of self. + r""" + Return latex representation of ``self``. EXAMPLES:: diff --git a/src/sage/modular/arithgroup/congroup_gammaH.py b/src/sage/modular/arithgroup/congroup_gammaH.py index 09487983532..388e48eec28 100644 --- a/src/sage/modular/arithgroup/congroup_gammaH.py +++ b/src/sage/modular/arithgroup/congroup_gammaH.py @@ -109,9 +109,11 @@ def _normalize_H(H, level): Normalize representatives for a given subgroup H of the units modulo level. - NOTE: This function does *not* make any attempt to find a minimal - set of generators for H. It simply normalizes the inputs for use - in hashing. + .. NOTE:: + + This function does *not* make any attempt to find a minimal + set of generators for H. It simply normalizes the inputs for use + in hashing. EXAMPLES:: @@ -137,8 +139,8 @@ def _normalize_H(H, level): H.remove(1) return H -class GammaH_class(CongruenceSubgroup): +class GammaH_class(CongruenceSubgroup): r""" The congruence subgroup `\Gamma_H(N)` for some subgroup `H \trianglelefteq (\ZZ / N\ZZ)^\times`, which is the subgroup of `{\rm @@ -147,7 +149,7 @@ class GammaH_class(CongruenceSubgroup): TESTS: - We test calculation of various invariants of the group: :: + We test calculation of various invariants of the group:: sage: GammaH(33,[2]).projective_index() 96 @@ -164,7 +166,7 @@ class GammaH_class(CongruenceSubgroup): sage: Gamma1(23).genus() 12 - We calculate the dimensions of some modular forms spaces: :: + We calculate the dimensions of some modular forms spaces:: sage: GammaH(33,[2]).dimension_cusp_forms(2) 5 @@ -175,7 +177,7 @@ class GammaH_class(CongruenceSubgroup): sage: GammaH(32079, [21676]).dimension_cusp_forms(20) 180266112 - We can sometimes show that there are no weight 1 cusp forms: :: + We can sometimes show that there are no weight 1 cusp forms:: sage: GammaH(20, [9]).dimension_cusp_forms(1) 0 @@ -183,8 +185,9 @@ class GammaH_class(CongruenceSubgroup): def __init__(self, level, H, Hlist=None): r""" - The congruence subgroup `\Gamma_H(N)`. The subgroup H - must be input as a list. + The congruence subgroup `\Gamma_H(N)`. + + The subgroup `H` must be given as a list. EXAMPLES:: @@ -483,13 +486,16 @@ def _coset_reduction_data_first_coord(G): of the reduction step (the first coordinate). INPUT: - G -- a congruence subgroup Gamma_0(N), Gamma_1(N), or Gamma_H(N). + + G -- a congruence subgroup Gamma_0(N), Gamma_1(N), or Gamma_H(N). OUTPUT: - A list v such that - v[u] = (min(u*h: h in H), - gcd(u,N) , - an h such that h*u = min(u*h: h in H)). + + A list v such that + + v[u] = (min(u*h: h in H), + gcd(u,N) , + an h such that h*u = min(u*h: h in H)). EXAMPLES:: @@ -558,11 +564,13 @@ def _coset_reduction_data_second_coord(G): of the reduction step (the second coordinate). INPUT: - self + + self OUTPUT: - a dictionary v with keys the divisors of N such that v[d] - is the subgroup {h in H : h = 1 (mod N/d)}. + + a dictionary v with keys the divisors of N such that v[d] + is the subgroup {h in H : h = 1 (mod N/d)}. EXAMPLES:: @@ -624,19 +632,24 @@ def _reduce_coset(self, uu, vv): Compute a canonical form for a given Manin symbol. INPUT: + Two integers (uu,vv) that define an element of `(Z/NZ)^2`. - uu -- an integer - vv -- an integer + + - uu -- an integer + - vv -- an integer OUTPUT: - pair of integers that are equivalent to (uu,vv). - NOTE: We do *not* require that gcd(uu,vv,N) = 1. If the gcd is - not 1, we return (0,0). + pair of integers that are equivalent to (uu,vv). + + .. NOTE:: + + We do *not* require that gcd(uu,vv,N) = 1. If the gcd is + not 1, we return (0,0). EXAMPLES: - An example at level 9.:: + An example at level 9:: sage: G = GammaH(9,[7]); G Congruence Subgroup Gamma_H(9) with H generated by [7] @@ -735,6 +748,7 @@ def reduce_cusp(self, c): def _reduce_cusp(self, c): r""" Compute a minimal representative for the given cusp c. + Returns a pair (c', t), where c' is the minimal representative for the given cusp, and t is either 1 or -1, as explained below. Largely for internal use. @@ -745,9 +759,13 @@ def _reduce_cusp(self, c): Two cusps `u1/v1` and `u2/v2` are equivalent modulo `\Gamma_H(N)` if and only if - `v1 = h*v2 (mod N)` and `u1 = h^(-1)*u2 (mod gcd(v1,N))` + + - `v1 = h*v2 (mod N)` and `u1 = h^(-1)*u2 (mod gcd(v1,N))` + or - `v1 = -h*v2 (mod N)` and `u1 = -h^(-1)*u2 (mod gcd(v1,N))` + + - `v1 = -h*v2 (mod N)` and `u1 = -h^(-1)*u2 (mod gcd(v1,N))` + for some `h \in H`. Then t is 1 or -1 as c and c' fall into the first or second case, respectively. diff --git a/src/sage/modular/arithgroup/congroup_sl2z.py b/src/sage/modular/arithgroup/congroup_sl2z.py index fd52d7d6478..54d3e50e8ed 100644 --- a/src/sage/modular/arithgroup/congroup_sl2z.py +++ b/src/sage/modular/arithgroup/congroup_sl2z.py @@ -3,7 +3,7 @@ AUTHORS: -- Niles Johnson (2010-08): Trac #3893: ``random_element()`` should pass on ``*args`` and ``**kwds``. +- Niles Johnson (2010-08): :trac:`3893`: ``random_element()`` should pass on ``*args`` and ``**kwds``. """ diff --git a/src/sage/modular/congroup.py b/src/sage/modular/congroup.py index 3964217b393..41fa177d900 100644 --- a/src/sage/modular/congroup.py +++ b/src/sage/modular/congroup.py @@ -9,7 +9,7 @@ # are *DIFFERENT* than the bindings for Gamma0, etc. that # get imported in all.py. # -# See trac #5059. +# See :trac:`5059` # ########################################################### diff --git a/src/sage/modular/cusps.py b/src/sage/modular/cusps.py index 116b0ac5c29..e158f4b287a 100644 --- a/src/sage/modular/cusps.py +++ b/src/sage/modular/cusps.py @@ -766,7 +766,7 @@ def is_gamma0_equiv(self, other, N, transformation = None): x = -x0 * ZZ(a/g) # now x*v1*v2 + a = 0 (mod N) - # the rest is all added in trac 10926 + # the rest is all added in :trac:`10926` s1p = s1+x*v1 M = N//g diff --git a/src/sage/modular/dims.py b/src/sage/modular/dims.py index d453192cd68..7a6951e0d10 100644 --- a/src/sage/modular/dims.py +++ b/src/sage/modular/dims.py @@ -300,7 +300,7 @@ def dimension_new_cusp_forms(X, k=2, p=0): sage: dimension_new_cusp_forms(Gamma1(30),3) 12 - Check that Trac #12640 is fixed:: + Check that :trac:`12640` is fixed:: sage: dimension_new_cusp_forms(DirichletGroup(1)(1), 12) 1 @@ -314,7 +314,7 @@ def dimension_new_cusp_forms(X, k=2, p=0): if N <= 2: return Gamma0(N).dimension_new_cusp_forms(k,p=p) else: - # Gamma1(N) for N<=2 just returns Gamma0(N), which has no eps parameter. See Trac #12640. + # Gamma1(N) for N<=2 just returns Gamma0(N), which has no eps parameter. See :trac:`12640`. return Gamma1(N).dimension_new_cusp_forms(k,eps=X,p=p) elif isinstance(X, (int,long,Integer)): return Gamma0(X).dimension_new_cusp_forms(k,p=p) @@ -413,7 +413,7 @@ def dimension_cusp_forms(X, k=2): sage: dimension_cusp_forms(e^2,2) 1 - Check that Trac #12640 is fixed:: + Check that :trac:`12640` is fixed:: sage: dimension_cusp_forms(DirichletGroup(1)(1), 12) 1 diff --git a/src/sage/modular/dirichlet.py b/src/sage/modular/dirichlet.py index 6bb3cf40cc9..9ee54f5d394 100644 --- a/src/sage/modular/dirichlet.py +++ b/src/sage/modular/dirichlet.py @@ -1095,9 +1095,9 @@ def jacobi_sum(self, char, check=True): sage: sum([g(x)*g(1-x) for x in IntegerModRing(N)]) 11 - And sums where exactly one character is nontrivial (see trac #6393):: + And sums where exactly one character is nontrivial (see :trac:`6393`):: - sage: G=DirichletGroup(5); X=G.list(); Y=X[0]; Z=X[1] + sage: G = DirichletGroup(5); X=G.list(); Y=X[0]; Z=X[1] sage: Y.jacobi_sum(Z) -1 sage: Z.jacobi_sum(Y) diff --git a/src/sage/modular/hecke/ambient_module.py b/src/sage/modular/hecke/ambient_module.py index 869255f525f..fce23f22343 100644 --- a/src/sage/modular/hecke/ambient_module.py +++ b/src/sage/modular/hecke/ambient_module.py @@ -350,7 +350,7 @@ def degeneracy_map(self, codomain, t=1): Domain: Modular Symbols subspace of dimension 4 of Modular Symbols space ... Codomain: Modular Symbols space of dimension 4 for Gamma_0(5) of weight ... - We check for a subtle caching bug that came up in work on trac #10453:: + We check for a subtle caching bug that came up in work on :trac:`10453`:: sage: loads(dumps(J0(33).decomposition()[0].modular_symbols())) Modular Symbols subspace of dimension 2 of Modular Symbols space of dimension 9 for Gamma_0(33) of weight 2 with sign 0 over Rational Field @@ -845,7 +845,7 @@ def old_submodule(self, p=None): sage: M.old_submodule() Modular Symbols subspace of dimension 3 of Modular Symbols space of dimension 4 and level 16, weight 3, character [-1, 1], sign 1, over Rational Field - Illustrate that trac 10664 is fixed:: + Illustrate that :trac:`10664` is fixed:: sage: ModularSymbols(DirichletGroup(42)[7], 6, sign=1).old_subspace(3) Modular Symbols subspace of dimension 0 of Modular Symbols space of dimension 40 and level 42, weight 6, character [-1, -1], sign 1, over Rational Field diff --git a/src/sage/modular/modform/constructor.py b/src/sage/modular/modform/constructor.py index febc937fd4c..44ec6faff0c 100644 --- a/src/sage/modular/modform/constructor.py +++ b/src/sage/modular/modform/constructor.py @@ -243,12 +243,12 @@ def ModularForms(group = 1, sage: m.T(2).charpoly('x') x^4 - 917*x^2 - 42284 - This came up in a subtle bug (trac #5923):: + This came up in a subtle bug (:trac:`5923`):: sage: ModularForms(gp(1), gap(12)) Modular Forms space of dimension 2 for Modular Group SL(2,Z) of weight 12 over Rational Field - This came up in another bug (related to trac #8630):: + This came up in another bug (related to :trac:`8630`):: sage: chi = DirichletGroup(109, CyclotomicField(3)).0 sage: ModularForms(chi, 2, base_ring = CyclotomicField(15)) diff --git a/src/sage/modular/modform/element.py b/src/sage/modular/modform/element.py index b85a6573941..afd26301a54 100644 --- a/src/sage/modular/modform/element.py +++ b/src/sage/modular/modform/element.py @@ -466,17 +466,19 @@ def q_expansion(self, prec=None): O(q^1) sage: f.q_expansion(0) O(q^0) + sage: f.q_expansion(-1) + Traceback (most recent call last): + ... + ValueError: prec (= -1) must be non-negative """ if prec is None: prec = self.parent().prec() prec = rings.Integer(prec) - if prec < 0: - raise ValueError("prec (=%s) must be at least 0"%prec) try: current_prec, f = self.__q_expansion except AttributeError: current_prec = 0 - f = self.parent()._q_expansion_ring()(0, -1) + f = self.parent()._q_expansion_ring()(0, 0) if current_prec == prec: return f diff --git a/src/sage/modular/modform/half_integral.py b/src/sage/modular/modform/half_integral.py index 6eede485fd3..8f346be4fef 100644 --- a/src/sage/modular/modform/half_integral.py +++ b/src/sage/modular/modform/half_integral.py @@ -89,7 +89,7 @@ def half_integral_weight_modform_basis(chi, k, prec): q^4 - 2*q^5 - 2*q^6 + 4*q^7 + 4*q^9 + O(q^10), q^5 - 2*q^7 - 2*q^9 + O(q^10)] - This example once raised an error (see trac #5792). + This example once raised an error (see :trac:`5792`). :: diff --git a/src/sage/modular/modform/hecke_operator_on_qexp.py b/src/sage/modular/modform/hecke_operator_on_qexp.py index c652d316971..080109dce6e 100644 --- a/src/sage/modular/modform/hecke_operator_on_qexp.py +++ b/src/sage/modular/modform/hecke_operator_on_qexp.py @@ -188,14 +188,14 @@ def hecke_operator_on_basis(B, n, k, eps=None, TESTS: - This shows that the problem with finite fields reported at trac #8281 is solved:: + This shows that the problem with finite fields reported at :trac:`8281` is solved:: sage: bas_mod5 = [f.change_ring(GF(5)) for f in victor_miller_basis(12, 20)] sage: hecke_operator_on_basis(bas_mod5, 2, 12) [4 0] [0 1] - This shows that empty input is handled sensibly (trac #12202):: + This shows that empty input is handled sensibly (:trac:`12202`):: sage: x = hecke_operator_on_basis([], 3, 12); x [] diff --git a/src/sage/modular/modform/space.py b/src/sage/modular/modform/space.py index c6b9a09a129..75e5580c681 100644 --- a/src/sage/modular/modform/space.py +++ b/src/sage/modular/modform/space.py @@ -1495,7 +1495,7 @@ def cuspidal_submodule(self): sage: N.cuspidal_submodule().dimension() 1 - We check that a bug noticed on trac #10450 is fixed:: + We check that a bug noticed on :trac:`10450` is fixed:: sage: M = ModularForms(6, 10) sage: W = M.span_of_basis(M.basis()[0:2]) @@ -1690,7 +1690,7 @@ def eisenstein_submodule(self): sage: M.eisenstein_submodule() Eisenstein subspace of dimension 1 of Modular Forms space of dimension 2 for Congruence Subgroup Gamma0(11) of weight 2 over Rational Field - We check that a bug noticed on trac #10450 is fixed:: + We check that a bug noticed on :trac:`10450` is fixed:: sage: M = ModularForms(6, 10) sage: W = M.span_of_basis(M.basis()[0:2]) diff --git a/src/sage/modular/modform_hecketriangle/abstract_ring.py b/src/sage/modular/modform_hecketriangle/abstract_ring.py index 9f80cd3de45..f5ba198020a 100644 --- a/src/sage/modular/modform_hecketriangle/abstract_ring.py +++ b/src/sage/modular/modform_hecketriangle/abstract_ring.py @@ -18,13 +18,11 @@ from sage.rings.all import FractionField, PolynomialRing, PowerSeriesRing, ZZ, QQ, infinity from sage.algebras.free_algebra import FreeAlgebra -from sage.rings.arith import bernoulli, sigma from sage.structure.parent import Parent from sage.misc.cachefunc import cached_method -from hecke_triangle_groups import HeckeTriangleGroup -from constructor import FormsRing, FormsSpace, rational_type +from constructor import FormsRing, FormsSpace from series_constructor import MFSeriesConstructor @@ -132,9 +130,9 @@ def _latex_(self): from sage.misc.latex import latex return "\\mathcal{{ {} }}_{{n={}}}({})".format(self._analytic_type.latex_space_name(), self._group.n(), latex(self._base_ring)) - def _element_constructor_(self, x): + def _element_constructor_(self, el): r""" - Return ``x`` coerced/converted into this forms ring. + Return ``el`` coerced/converted into this forms ring. EXAMPLES:: @@ -152,14 +150,36 @@ def _element_constructor_(self, x): False sage: MR(el).parent() == MR True + + sage: el = MR.Delta().full_reduce() + sage: MRinf = ModularFormsRing(n=infinity) + sage: MRinf(el) + (E4*f_i^4 - 2*E4^2*f_i^2 + E4^3)/4096 + sage: el.parent() + CuspForms(n=3, k=12, ep=1) over Integer Ring + sage: MRinf(el).parent() + ModularFormsRing(n=+Infinity) over Integer Ring """ from graded_ring_element import FormsRingElement - if isinstance(x, FormsRingElement): - x = self._rat_field(x._rat) + if isinstance(el, FormsRingElement): + if (self.hecke_n() == infinity and el.hecke_n() == ZZ(3)): + el_f = el._reduce_d()._rat + (x,y,z,d) = self.pol_ring().gens() + + num_sub = el_f.numerator().subs( x=(y**2 + 3*x)/ZZ(4), y=(9*x*y - y**3)/ZZ(8), z=(3*z - y)/ZZ(2)) + denom_sub = el_f.denominator().subs( x=(y**2 + 3*x)/ZZ(4), y=(9*x*y - y**3)/ZZ(8), z=(3*z - y)/ZZ(2)) + new_num = num_sub.numerator()*denom_sub.denominator() + new_denom = denom_sub.numerator()*num_sub.denominator() + + el = self._rat_field(new_num) / self._rat_field(new_denom) + elif self.group() == el.group(): + el = self._rat_field(el._rat) + else: + raise ValueError("{} has group {} != {}".format(el, el.group(), self.group())) else: - x = self._rat_field(x) - return self.element_class(self, x) + el = self._rat_field(el) + return self.element_class(self, el) def _coerce_map_from_(self, S): r""" @@ -171,6 +191,8 @@ def _coerce_map_from_(self, S): sage: MR1 = QuasiWeakModularFormsRing(base_ring=CC) sage: MR2 = ModularFormsRing() sage: MR3 = CuspFormsRing() + sage: MR4 = ModularFormsRing(n=infinity) + sage: MR5 = ModularFormsRing(n=4) sage: MR3.has_coerce_map_from(MR2) False sage: MR1.has_coerce_map_from(MR2) @@ -181,6 +203,10 @@ def _coerce_map_from_(self, S): False sage: MR1.has_coerce_map_from(ZZ) True + sage: MR4.has_coerce_map_from(MR2) + True + sage: MR4.has_coerce_map_from(MR5) + False sage: from sage.modular.modform_hecketriangle.space import ModularForms, CuspForms sage: MF2 = ModularForms(k=6, ep=-1) @@ -189,14 +215,19 @@ def _coerce_map_from_(self, S): True sage: MR2.has_coerce_map_from(MF3) True + sage: MR4.has_coerce_map_from(MF2) + True """ from space import FormsSpace_abstract + from functors import _common_subgroup if ( isinstance(S, FormsRing_abstract)\ - and self._group == S._group\ + and self._group == _common_subgroup(self._group, S._group)\ and self._analytic_type >= S._analytic_type\ and self.base_ring().has_coerce_map_from(S.base_ring()) ): return True + elif isinstance(S, FormsRing_abstract): + return False elif isinstance(S, FormsSpace_abstract): raise RuntimeError( "This case should not occur." ) # return self._coerce_map_from_(S.graded_ring()) diff --git a/src/sage/modular/modform_hecketriangle/abstract_space.py b/src/sage/modular/modform_hecketriangle/abstract_space.py index 0cc399ad054..76f28b569b4 100644 --- a/src/sage/modular/modform_hecketriangle/abstract_space.py +++ b/src/sage/modular/modform_hecketriangle/abstract_space.py @@ -122,9 +122,9 @@ def _latex_(self): from sage.misc.latex import latex return "{}_{{ n={} }}({},\ {})({})".format(self._analytic_type.latex_space_name(), self._group.n(), self._weight, self._ep, latex(self._base_ring)) - def _element_constructor_(self, x): + def _element_constructor_(self, el): r""" - Return ``x`` coerced into this forms space. + Return ``el`` coerced into this forms space. EXAMPLES:: @@ -141,6 +141,14 @@ def _element_constructor_(self, x): sage: MF(Delta).parent() == MF True + sage: E2 = MF.E2() + sage: e2 = QuasiWeakModularForms(n=infinity, k=2, ep=-1)(E2) + sage: e2 + 1 - 24*q^2 - 72*q^4 + O(q^5) + sage: e2.parent() + QuasiWeakModularForms(n=+Infinity, k=2, ep=-1) over Integer Ring + sage: e2.as_ring_element() + (-f_i + 3*E2)/2 sage: MF(x^3) 1 + 720*q + 179280*q^2 + 16954560*q^3 + 396974160*q^4 + O(q^5) sage: MF(x^3).parent() == MF @@ -226,35 +234,49 @@ def _element_constructor_(self, x): """ from graded_ring_element import FormsRingElement - if isinstance(x, FormsRingElement): - return self.element_class(self, x._rat) + if isinstance(el, FormsRingElement): + if (self.hecke_n() == infinity and el.hecke_n() == ZZ(3)): + el_f = el._reduce_d()._rat + (x,y,z,d) = self.pol_ring().gens() + + num_sub = el_f.numerator().subs( x=(y**2 + 3*x)/ZZ(4), y=(9*x*y - y**3)/ZZ(8), z=(3*z - y)/ZZ(2)) + denom_sub = el_f.denominator().subs( x=(y**2 + 3*x)/ZZ(4), y=(9*x*y - y**3)/ZZ(8), z=(3*z - y)/ZZ(2)) + new_num = num_sub.numerator()*denom_sub.denominator() + new_denom = denom_sub.numerator()*num_sub.denominator() + + el = self._rat_field(new_num) / self._rat_field(new_denom) + elif self.group() == el.group(): + el = el._rat + else: + raise ValueError("{} has group {} != {}".format(el, el.group(), self.group())) + return self.element_class(self, el) # This assumes that the series corresponds to a _weakly # holomorphic_ (quasi) form. It also assumes that the form is # holomorphic at -1 for n=infinity (this assumption however # can be changed in construct_form # resp. construct_quasi_form)) - P = parent(x) + P = parent(el) if is_LaurentSeriesRing(P) or is_PowerSeriesRing(P): if (self.is_modular()): - return self.construct_form(x) + return self.construct_form(el) else: - return self.construct_quasi_form(x) - if is_FreeModuleElement(x) and (self.module() is P or self.ambient_module() is P): - return self.element_from_ambient_coordinates(x) - if (not self.is_ambient()) and (isinstance(x, list) or isinstance(x, tuple) or is_FreeModuleElement(x)) and len(x) == self.rank(): + return self.construct_quasi_form(el) + if is_FreeModuleElement(el) and (self.module() is P or self.ambient_module() is P): + return self.element_from_ambient_coordinates(el) + if (not self.is_ambient()) and (isinstance(el, list) or isinstance(el, tuple) or is_FreeModuleElement(el)) and len(el) == self.rank(): try: - return self.element_from_coordinates(x) + return self.element_from_coordinates(el) except (ArithmeticError, TypeError): pass if self.ambient_module() and self.ambient_module().has_coerce_map_from(P): - return self.element_from_ambient_coordinates(self.ambient_module()(x)) - if (isinstance(x,list) or isinstance(x, tuple)) and len(x) == self.degree(): + return self.element_from_ambient_coordinates(self.ambient_module()(el)) + if (isinstance(el,list) or isinstance(el, tuple)) and len(el) == self.degree(): try: - return self.element_from_ambient_coordinates(x) + return self.element_from_ambient_coordinates(el) except (ArithmeticError, TypeError): pass - return self.element_class(self, x) + return self.element_class(self, el) def _coerce_map_from_(self, S): r""" @@ -268,6 +290,8 @@ def _coerce_map_from_(self, S): sage: MF3 = ModularForms(n=4, k=24, ep=-1) sage: MF4 = CuspForms(n=4, k=0, ep=1) sage: MF5 = ZeroForm(n=4, k=10, ep=-1) + sage: MF6 = QuasiWeakModularForms(n=3, k=24, ep=1) + sage: MF7 = QuasiWeakModularForms(n=infinity, k=24, ep=1) sage: subspace1 = MF3.subspace([MF3.gen(0), MF3.gen(1)]) sage: subspace2 = MF3.subspace([MF3.gen(2)]) sage: subspace3 = MF3.subspace([MF3.gen(0), MF3.gen(0)+MF3.gen(2)]) @@ -282,6 +306,10 @@ def _coerce_map_from_(self, S): False sage: MF1.has_coerce_map_from(ZZ) True + sage: MF7.has_coerce_map_from(MF6) + True + sage: MF7.has_coerce_map_from(MF2) + False sage: MF3.has_coerce_map_from(subspace1) True sage: subspace1.has_coerce_map_from(MF3) diff --git a/src/sage/modular/modform_hecketriangle/functors.py b/src/sage/modular/modform_hecketriangle/functors.py index 0b81ed0295e..f4e7c3e0c2b 100644 --- a/src/sage/modular/modform_hecketriangle/functors.py +++ b/src/sage/modular/modform_hecketriangle/functors.py @@ -92,6 +92,35 @@ def _get_base_ring(ring, var_name="d"): return base_ring +def _common_subgroup(group1, group2): + r""" + Return a common (Hecke triangle) subgroup of both given groups + ``group1`` and ``group2`` if it exists. Otherwise return ``None``. + + EXAMPLES:: + + sage: from sage.modular.modform_hecketriangle.functors import _common_subgroup + sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup + sage: _common_subgroup(HeckeTriangleGroup(n=3), HeckeTriangleGroup(n=infinity)) + Hecke triangle group for n = +Infinity + sage: _common_subgroup(HeckeTriangleGroup(n=infinity), HeckeTriangleGroup(n=3)) + Hecke triangle group for n = +Infinity + sage: _common_subgroup(HeckeTriangleGroup(n=4), HeckeTriangleGroup(n=infinity)) is None + True + sage: _common_subgroup(HeckeTriangleGroup(n=4), HeckeTriangleGroup(n=4)) + Hecke triangle group for n = 4 + """ + + if group1 == group2: + return group1 + elif (group1.n() == 3) and (group2.n() == infinity): + return group2 + elif (group1.n() == infinity) and (group2.n() == 3): + return group1 + else: + return None + + def ConstantFormsSpaceFunctor(group): r""" Construction functor for the space of constant forms. @@ -458,19 +487,21 @@ def merge(self, other): other = other._ambient_space_functor if isinstance(other, FormsSpaceFunctor): - if not (self._group == other._group): + group = _common_subgroup(self._group, other._group) + if group == None: return None analytic_type = self._analytic_type + other._analytic_type if (self._k == other._k) and (self._ep == other._ep): - return FormsSpaceFunctor(analytic_type, self._group, self._k, self._ep) + return FormsSpaceFunctor(analytic_type, group, self._k, self._ep) else: - return FormsRingFunctor(analytic_type, self._group, True) + return FormsRingFunctor(analytic_type, group, True) elif isinstance(other, FormsRingFunctor): - if not (self._group == other._group): + group = _common_subgroup(self._group, other._group) + if group == None: return None red_hom = other._red_hom analytic_type = self._analytic_type + other._analytic_type - return FormsRingFunctor(analytic_type, self._group, red_hom) + return FormsRingFunctor(analytic_type, group, red_hom) def __eq__(self, other): r""" @@ -647,17 +678,19 @@ def merge(self, other): other = other._ambient_space_functor if isinstance(other, FormsSpaceFunctor): - if not (self._group == other._group): + group = _common_subgroup(self._group, other._group) + if group == None: return None red_hom = self._red_hom analytic_type = self._analytic_type + other._analytic_type - return FormsRingFunctor(analytic_type, self._group, red_hom) + return FormsRingFunctor(analytic_type, group, red_hom) elif isinstance(other, FormsRingFunctor): - if not (self._group == other._group): + group = _common_subgroup(self._group, other._group) + if group == None: return None red_hom = self._red_hom & other._red_hom analytic_type = self._analytic_type + other._analytic_type - return FormsRingFunctor(analytic_type, self._group, red_hom) + return FormsRingFunctor(analytic_type, group, red_hom) def __eq__(self, other): r""" diff --git a/src/sage/modular/modform_hecketriangle/graded_ring_element.py b/src/sage/modular/modform_hecketriangle/graded_ring_element.py index d743dec533e..f6e669d6a63 100644 --- a/src/sage/modular/modform_hecketriangle/graded_ring_element.py +++ b/src/sage/modular/modform_hecketriangle/graded_ring_element.py @@ -713,6 +713,12 @@ def _add_(self,other): 2 - 32*q + 736*q^2 - 896*q^3 + 6368*q^4 + O(q^5) sage: (MF.E4() + MF.f_i()^2).parent() ModularForms(n=+Infinity, k=4, ep=1) over Integer Ring + + sage: el = ModularForms(n=3).Delta() + MF.E4()*MF.E6() + sage: el + (E4*f_i^4 - 2*E4^2*f_i^2 + E4^3 + 4096*E4^2*f_i)/4096 + sage: el.parent() + ModularFormsRing(n=+Infinity) over Integer Ring """ return self.parent()(self._rat+other._rat) @@ -763,6 +769,12 @@ def _sub_(self,other): 64*q - 512*q^2 + 1792*q^3 - 4096*q^4 + O(q^5) sage: (MF.E4() - MF.f_i()^2).parent() ModularForms(n=+Infinity, k=4, ep=1) over Integer Ring + + sage: el = ModularForms(n=3).Delta() - MF.E4() + sage: el + (E4*f_i^4 - 2*E4^2*f_i^2 + E4^3 - 4096*E4)/4096 + sage: el.parent() + ModularFormsRing(n=+Infinity) over Integer Ring """ #reduce at the end? See example "sage: ((E4+E6)-E6).parent()" @@ -838,6 +850,12 @@ def _mul_(self,other): q + 8*q^2 + 12*q^3 - 64*q^4 + O(q^5) sage: (MF.E4()*MF.f_inf()).parent() ModularForms(n=+Infinity, k=8, ep=1) over Integer Ring + + sage: el = ModularForms(n=3).E2()*MF.E6() + sage: el + 1 - 8*q - 272*q^2 - 1760*q^3 - 2560*q^4 + O(q^5) + sage: el.parent() + QuasiModularForms(n=+Infinity, k=8, ep=1) over Integer Ring """ res = self.parent().rat_field()(self._rat*other._rat) @@ -918,6 +936,12 @@ def _div_(self,other): 1/2 - 4*q - 236*q^2 - 2128*q^3 + 49428*q^4 + O(q^5) sage: (MF.f_i()/(MF.E4() + MF.f_i()^2)).parent() MeromorphicModularForms(n=+Infinity, k=-2, ep=-1) over Integer Ring + + sage: el = ModularForms(n=3).E2()/MF.E2() + sage: el + 1 + 8*q + 48*q^2 + 480*q^3 + 4448*q^4 + O(q^5) + sage: el.parent() + QuasiMeromorphicModularForms(n=+Infinity, k=0, ep=1) over Integer Ring """ res = self.parent().rat_field()(self._rat/other._rat) diff --git a/src/sage/modular/modform_hecketriangle/readme.py b/src/sage/modular/modform_hecketriangle/readme.py index 3f39d16b300..418c2a175db 100644 --- a/src/sage/modular/modform_hecketriangle/readme.py +++ b/src/sage/modular/modform_hecketriangle/readme.py @@ -2,8 +2,8 @@ Overview of Hecke triangle groups and modular forms for Hecke triangle groups AUTHORS: -- Jonas Jermann (2013): initial version +- Jonas Jermann (2013): initial version Hecke triangle groups and elements: @@ -460,7 +460,7 @@ - **Block decomposition of elements:** - For each group element a very specfic conjugacy representative + For each group element a very specific conjugacy representative can be obtained. For hyperbolic and parabolic elements the representative is a product ``V(j)``-matrices. They all have non-negative trace and the number of factors is called @@ -1093,7 +1093,7 @@ - **Theta subgroup:** The Hecke triangle group corresponding to ``n=infinity`` is also completely supported. In particular the (special) behavior around - the cusp ``-1`` is considered and can be specfied. + the cusp ``-1`` is considered and can be specified. EXAMPLES:: @@ -1146,6 +1146,15 @@ sage: MF.q_basis(m=-1, order_1=-1, min_exp=-1) q^-1 - 203528/7*q^5 + O(q^6) + Elements with respect to the full group are automatically coerced + to elements of the Theta subgroup if necessary:: + + sage: el = QuasiMeromorphicModularFormsRing(n=3).Delta().full_reduce() + E2 + sage: el + (E4*f_i^4 - 2*E4^2*f_i^2 + E4^3 + 4096*E2)/4096 + sage: el.parent() + QuasiModularFormsRing(n=+Infinity) over Integer Ring + - **Determine exact coefficients from numerical ones:** There is some experimental support for replacing numerical coefficients with diff --git a/src/sage/modular/modsym/ambient.py b/src/sage/modular/modsym/ambient.py index 5ef072af57a..d59a2e6475e 100644 --- a/src/sage/modular/modsym/ambient.py +++ b/src/sage/modular/modsym/ambient.py @@ -531,7 +531,7 @@ def _action_on_modular_symbols(self, g): INPUT: - ``g`` (list) -- `g=[a,b,c,d]` where `a,b,c,d` are integers + `g` (list) -- `g=[a,b,c,d]` where `a,b,c,d` are integers defining a `2\times2` integer matrix. OUTPUT: @@ -539,10 +539,10 @@ def _action_on_modular_symbols(self, g): (matrix) The matrix of the action of `g` on this Modular Symbol space, with respect to the standard basis. - .. note:: + .. NOTE:: - Use _matrix_of_operator_on_modular_symbols for more general - operators. + Use ``_matrix_of_operator_on_modular_symbols`` for more general + operators. EXAMPLES:: @@ -810,7 +810,7 @@ def modular_symbol_sum(self, x, check=True): The sum `\sum_{i=0}^{k-2} a_i [ i, \alpha, \beta ]` as an element of this modular symbol space. - EXAMPLES: + EXAMPLES:: sage: M = ModularSymbols(11,4) sage: R.=QQ[] @@ -2787,7 +2787,7 @@ def _cuspidal_new_submodule_dimension_formula(self): r""" Return the dimension of the new cuspidal subspace, via the formula. - EXAMPLES: + EXAMPLES:: sage: M = ModularSymbols(100,2) sage: M._cuspidal_new_submodule_dimension_formula() @@ -2898,19 +2898,15 @@ def boundary_space(self): def _hecke_image_of_ith_basis_vector(self, n, i): """ Return `T_n(e_i)`, where `e_i` is the - `i`th basis vector of this ambient space. + `i` th basis vector of this ambient space. INPUT: - - - ``n`` - an integer which should be prime. - + - ``n`` -- an integer which should be prime. OUTPUT: - - - ``modular symbol`` - element of this ambient space - + - ``modular symbol`` -- element of this ambient space EXAMPLES:: @@ -3100,7 +3096,7 @@ def _cuspidal_new_submodule_dimension_formula(self): r""" Return the dimension of the new cuspidal subspace, via the formula. - EXAMPLES: + EXAMPLES:: sage: M = ModularSymbols(Gamma1(22),2) sage: M._cuspidal_new_submodule_dimension_formula() @@ -3807,16 +3803,13 @@ def _hecke_images(self, i, v): INPUT: + - ``i`` -- nonnegative integer - - ``i`` - nonnegative integer - - - ``v`` - a list of positive integer - + - ``v`` -- a list of positive integer OUTPUT: - - - ``matrix`` - whose rows are the Hecke images + - ``matrix`` -- whose rows are the Hecke images EXAMPLES:: @@ -3829,8 +3822,6 @@ def _hecke_images(self, i, v): [ 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0] [ 0 1 0 2 0 -1 1 1 0 0 0 0 0 0 0] [ 0 1 1 -1 -1 0 -1 1 1 0 1 2 0 -2 2] - - """ if self.weight() != 2: raise NotImplementedError("hecke images only implemented when the weight is 2") diff --git a/src/sage/modular/modsym/manin_symbol.pyx b/src/sage/modular/modsym/manin_symbol.pyx index 6e4e6351c8b..d4da00430ad 100644 --- a/src/sage/modular/modsym/manin_symbol.pyx +++ b/src/sage/modular/modsym/manin_symbol.pyx @@ -436,13 +436,13 @@ def _print_polypart(i, j): EXAMPLES:: - sage: from sage.modular.modsym.manin_symbol import _print_polypart - sage: _print_polypart(2,3) - 'X^2*Y^3' - sage: _print_polypart(2,0) - 'X^2' - sage: _print_polypart(0,1) - 'Y' + sage: from sage.modular.modsym.manin_symbol import _print_polypart + sage: _print_polypart(2,3) + 'X^2*Y^3' + sage: _print_polypart(2,0) + 'X^2' + sage: _print_polypart(0,1) + 'Y' """ if i > 1: xpart = "X^%s"%i diff --git a/src/sage/modular/modsym/modsym.py b/src/sage/modular/modsym/modsym.py index 48b9afdeb64..d52b6ac64ea 100644 --- a/src/sage/modular/modsym/modsym.py +++ b/src/sage/modular/modsym/modsym.py @@ -48,7 +48,7 @@ sage: t2*t5 - t5*t2 == 0 True -This tests the bug reported in trac #1220:: +This tests the bug reported in :trac:`1220`:: sage: G = GammaH(36, [13, 19]) sage: G.modular_symbols() diff --git a/src/sage/modular/modsym/modular_symbols.py b/src/sage/modular/modsym/modular_symbols.py index b13170bf0b4..96aec36e734 100644 --- a/src/sage/modular/modsym/modular_symbols.py +++ b/src/sage/modular/modsym/modular_symbols.py @@ -117,7 +117,7 @@ def __getitem__(self, j): return [self.__alpha, self.__beta][j] def _latex_(self): - """ + r""" Return Latex representation of this modular symbol. EXAMPLES:: diff --git a/src/sage/modular/modsym/p1list.pyx b/src/sage/modular/modsym/p1list.pyx index 1ab36b2a6b5..20acee773f0 100644 --- a/src/sage/modular/modsym/p1list.pyx +++ b/src/sage/modular/modsym/p1list.pyx @@ -764,8 +764,8 @@ cdef class P1List: return sage.modular.modsym.p1list._make_p1list, (self.__N, ) def __getitem__(self, n): - """ - Standard indexing/slicing function for the class P1List. + r""" + Standard indexing/slicing function for the class ``P1List``. EXAMPLES:: diff --git a/src/sage/modular/modsym/space.py b/src/sage/modular/modsym/space.py index 7bf69c3fa9d..7faa104c390 100644 --- a/src/sage/modular/modsym/space.py +++ b/src/sage/modular/modsym/space.py @@ -182,7 +182,7 @@ def compact_system_of_eigenvalues(self, v, names='alpha', nz=None): TESTS: - Verify that Trac #12772 is fixed:: + Verify that :trac:`12772` is fixed:: sage: M = ModularSymbols(1,12,sign=1).cuspidal_subspace().new_subspace() sage: A = M.decomposition()[0] @@ -2318,15 +2318,16 @@ def _matrix_of_galois_action(self, t, P): INPUT: - - `t` -- integer - - `P` -- list of cusps + - `t` -- integer - EXAMPLES:: + - `P` -- list of cusps + + EXAMPLES: We compute the matrix of the element of the Galois group associated to 5 and 31 for level 32. In the first case the Galois action is trivial, and in the second it is - nontrivial.:: + nontrivial. :: sage: M = ModularSymbols(32) sage: P = [c for c in Gamma0(32).cusps() if not c.is_infinity()] diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 85db5e993d1..c688167722d 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -654,7 +654,8 @@ class FreeModule_generic(Module): def __init__(self, base_ring, rank, degree, sparse=False, coordinate_ring=None, category=None): """ - Create the free module of given rank over the given base_ring. + Create the free module of given rank ``rank`` over the given base + ring ``base_ring``. INPUT: @@ -672,10 +673,11 @@ def __init__(self, base_ring, rank, degree, sparse=False, - ``category`` -- category (default: None) If ``base_ring`` is a field, then the default category is the - category of vector spaces over that field; otherwise it is the - category of free modules over that ring. In addition, the - category is intersected with the category of finite enumerated - sets if the ring is finite or the rank is 0. + category of finite-dimensional vector spaces over that field; + otherwise it is the category of finite-dimensional free modules + over that ring. In addition, the category is intersected with the + category of finite enumerated sets if the ring is finite or the + rank is 0. EXAMPLES:: @@ -683,17 +685,22 @@ def __init__(self, base_ring, rank, degree, sparse=False, Ambient free module of rank 3 over the integral domain Multivariate Polynomial Ring in x0, x1, x2 over Rational Field sage: FreeModule(GF(7),3).category() - Category of vector spaces with basis over (finite fields - and subquotients of monoids and quotients of semigroups) + Category of finite dimensional vector spaces with basis over + (finite fields and subquotients of monoids and quotients of semigroups) sage: V = QQ^4; V.category() - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis over + (quotient fields and metric spaces) sage: V = GF(5)**20; V.category() - Category of vector spaces with basis over (finite fields - and subquotients of monoids and quotients of semigroups) + Category of finite dimensional vector spaces with basis over + (finite fields and subquotients of monoids + and quotients of semigroups) sage: FreeModule(ZZ,3).category() - Category of modules with basis over (euclidean domains and infinite enumerated sets) + Category of finite dimensional modules with basis over + (euclidean domains and infinite enumerated sets + and metric spaces) sage: (QQ^0).category() - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis + over (quotient fields and metric spaces) TESTS:: @@ -734,7 +741,7 @@ def __init__(self, base_ring, rank, degree, sparse=False, if category is None: from sage.categories.all import FreeModules - category = FreeModules(base_ring.category()) + category = FreeModules(base_ring.category()).FiniteDimensional() super(FreeModule_generic, self).__init__(base_ring, category=category) self.__coordinate_ring = coordinate_ring @@ -903,7 +910,7 @@ def _an_element_(self): def _element_constructor_(self, x, coerce=True, copy=True, check=True): r""" - Create an element of this free module from x. + Create an element of this free module from ``x``. The ``coerce`` and ``copy`` arguments are passed on to the underlying element constructor. If @@ -968,7 +975,7 @@ def _element_constructor_(self, x, coerce=True, copy=True, check=True): def is_submodule(self, other): """ - Return True if self is a submodule of other. + Return ``True`` if ``self`` is a submodule of ``other``. EXAMPLES:: @@ -1002,11 +1009,9 @@ def is_submodule(self, other): sage: M.is_submodule(N) True - Since basis() is not implemented in general, submodule testing does - not work for all PID's. However, trivial cases are already used - (and useful) for coercion, e.g. - - :: + Since :meth:`basis` is not implemented in general, submodule + testing does not work for all PID's. However, trivial cases are + already used (and useful) for coercion, e.g. :: sage: QQ(1/2) * vector(ZZ['x']['y'],[1,2,3,4]) (1/2, 1, 3/2, 2) @@ -3067,10 +3072,10 @@ def _Hom_(self, Y, category): sage: type(H) sage: H - Set of Morphisms from Vector space of dimension 2 over - Rational Field to Ambient free module of rank 3 over the - principal ideal domain Integer Ring in Category of vector - spaces with basis over quotient fields + Set of Morphisms from Vector space of dimension 2 over Rational Field + to Ambient free module of rank 3 over the principal ideal domain Integer Ring + in Category of finite dimensional vector spaces with basis over + (quotient fields and metric spaces) """ if Y.base_ring().is_field(): import vector_space_homspace diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index 587bc33f032..6a63c1f3427 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -2077,23 +2077,33 @@ cdef class FreeModuleElement(Vector): # abstract base class def dict(self, copy=True): """ - Return dictionary of nonzero entries of self. + Return dictionary of nonzero entries of ``self``. + + More precisely, this returns a dictionary whose keys are indices + of basis elements in the support of ``self`` and whose values are + the corresponding coefficients. INPUT: - - ``copy`` -- bool (default: True) + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` OUTPUT: - - Python dictionary + - Python dictionary EXAMPLES:: sage: v = vector([0,0,0,0,1/2,0,3/14]) sage: v.dict() {4: 1/2, 6: 3/14} + sage: sorted(v.support()) + [4, 6] - In some cases when copy=False, we get back a dangerous reference:: + In some cases, when ``copy=False``, we get back a dangerous + reference:: sage: v = vector({0:5, 2:3/7}, sparse=True) sage: v.dict(copy=False) @@ -2110,6 +2120,8 @@ cdef class FreeModuleElement(Vector): # abstract base class e[i] = c return e + monomial_coefficients = dict + ############################# # Plotting ############################# @@ -4834,30 +4846,41 @@ cdef class FreeModuleElement_generic_sparse(FreeModuleElement): def dict(self, copy=True): """ - Return dictionary of nonzero entries of self. + Return dictionary of nonzero entries of ``self``. + + More precisely, this returns a dictionary whose keys are indices + of basis elements in the support of ``self`` and whose values are + the corresponding coefficients. INPUT: - - ``copy`` -- bool (default: True) + - ``copy`` -- (default: ``True``) if ``self`` is internally + represented by a dictionary ``d``, then make a copy of ``d``; + if ``False``, then this can cause undesired behavior by + mutating ``d`` OUTPUT: - - Python dictionary + - Python dictionary EXAMPLES:: sage: v = vector([0,0,0,0,1/2,0,3/14], sparse=True) sage: v.dict() {4: 1/2, 6: 3/14} + sage: sorted(v.support()) + [4, 6] """ if copy: return dict(self._entries) else: return self._entries + monomial_coefficients = dict + def list(self, copy=True): """ - Return list of elements of self. + Return list of elements of ``self``. INPUT: diff --git a/src/sage/modules/free_module_homspace.py b/src/sage/modules/free_module_homspace.py index 5d674f304e9..b455db03d87 100644 --- a/src/sage/modules/free_module_homspace.py +++ b/src/sage/modules/free_module_homspace.py @@ -27,11 +27,12 @@ sage: V2 = FreeModule(IntegerRing(),2) sage: H = Hom(V3,V2) sage: H - Set of Morphisms from Ambient free module of rank 3 over the - principal ideal domain Integer Ring to Ambient free module - of rank 2 over the principal ideal domain Integer Ring in - Category of modules with basis over (euclidean domains and - infinite enumerated sets) + Set of Morphisms from Ambient free module of rank 3 over + the principal ideal domain Integer Ring + to Ambient free module of rank 2 + over the principal ideal domain Integer Ring + in Category of finite dimensional modules with basis over + (euclidean domains and infinite enumerated sets and metric spaces) sage: B = H.basis() sage: len(B) 6 diff --git a/src/sage/modules/with_basis/morphism.py b/src/sage/modules/with_basis/morphism.py index 4fb91391cc0..17f1d211f2f 100644 --- a/src/sage/modules/with_basis/morphism.py +++ b/src/sage/modules/with_basis/morphism.py @@ -380,10 +380,11 @@ def __call__(self, *args): x = args[self._position] assert(x.parent() is self.domain()) + mc = x.monomial_coefficients(copy=False) if self._is_module_with_basis_over_same_base_ring: - return self.codomain().linear_combination( (self._on_basis(*(before+(index,)+after)), coeff ) for (index, coeff) in args[self._position] ) + return self.codomain().linear_combination( (self._on_basis(*(before+(index,)+after)), coeff ) for (index, coeff) in mc.iteritems() ) else: - return sum(( coeff * self._on_basis(*(before+(index,)+after)) for (index, coeff) in args[self._position]), self._zero) + return sum(( coeff * self._on_basis(*(before+(index,)+after)) for (index, coeff) in mc.iteritems() ), self._zero) # As per the specs of Map, we should in fact implement _call_. # However we currently need to abuse Map.__call__ (which strict diff --git a/src/sage/plot/plot_field.py b/src/sage/plot/plot_field.py index 0ea97fad579..030a19a757d 100644 --- a/src/sage/plot/plot_field.py +++ b/src/sage/plot/plot_field.py @@ -258,7 +258,7 @@ def plot_slope_field(f, xrange, yrange, **kwds): TESTS: Verify that we're not getting warnings due to use of headless quivers - (trac #11208):: + (:trac:`11208`):: sage: x,y = var('x y') sage: import numpy # bump warnings up to errors for testing purposes diff --git a/src/sage/quadratic_forms/genera/genus.py b/src/sage/quadratic_forms/genera/genus.py index 60f2f8f7c74..1f978f0f1ad 100644 --- a/src/sage/quadratic_forms/genera/genus.py +++ b/src/sage/quadratic_forms/genera/genus.py @@ -17,24 +17,23 @@ from sage.rings.integer import Integer from sage.rings.finite_rings.constructor import FiniteField + def Genus(A): - """ - Given a nonsingular symmetric matrix A, return the genus of A. + r""" + Given a nonsingular symmetric matrix `A`, return the genus of `A`. INPUT: - - A -- a symmetric matrix with coefficients in ZZ + - `A` -- a symmetric matrix with coefficients in `\ZZ` OUTPUT: - A GenusSymbol_global_ring object, encoding the Conway-Sloane - genus symbol of the quadratic form whose Gram matrix is A. + A ``GenusSymbol_global_ring`` object, encoding the Conway-Sloane + genus symbol of the quadratic form whose Gram matrix is `A`. EXAMPLES:: - sage: from sage.quadratic_forms.genera.genus import GenusSymbol_global_ring sage: from sage.quadratic_forms.genera.genus import Genus - sage: A = Matrix(ZZ, 2, 2, [1,1,1,2]) sage: Genus(A) Genus of [1 1] @@ -43,7 +42,6 @@ def Genus(A): return GenusSymbol_global_ring(A) - def LocalGenusSymbol(A,p): """ Given a nonsingular symmetric matrix A, return the local symbol of A at the prime p. @@ -55,8 +53,8 @@ def LocalGenusSymbol(A,p): OUTPUT: - A Genus_Symbol_p_adic_ring object, encoding the Conway-Sloane - genus symbol at p of the quadratic form whose Gram matrix is A. + A Genus_Symbol_p_adic_ring object, encoding the Conway-Sloane + genus symbol at p of the quadratic form whose Gram matrix is A. EXAMPLES:: @@ -91,7 +89,7 @@ def is_GlobalGenus(G): OUTPUT: - boolean + boolean EXAMPLES:: @@ -144,7 +142,7 @@ def is_2_adic_genus(genus_symbol_quintuple_list): OUTPUT: - boolean + boolean EXAMPLES:: @@ -209,7 +207,7 @@ def canonical_2_adic_compartments(genus_symbol_quintuple_list): OUTPUT: - a list of lists of integers. + a list of lists of integers. EXAMPLES:: @@ -283,7 +281,7 @@ def canonical_2_adic_trains(genus_symbol_quintuple_list, compartments=None): OUTPUT: - a list of lists of distinct integers. + a list of lists of distinct integers. EXAMPLES:: @@ -325,12 +323,13 @@ def canonical_2_adic_trains(genus_symbol_quintuple_list, compartments=None): sage: canonical_2_adic_trains(G2.symbol_tuple_list()) [] - NOTES: + .. NOTE:: See Conway-Sloane 3rd edition, pp. 381-382 for definitions and examples. - TO DO: - - Add a non-trivial example in the doctest here! + .. TODO:: + + Add a non-trivial example in the doctest here! """ ## Recompute compartments if none are passed. if compartments is None: @@ -384,7 +383,7 @@ def canonical_2_adic_reduction(genus_symbol_quintuple_list): OUTPUT: - a list of lists of distinct integers. + a list of lists of distinct integers. EXAMPLES:: @@ -415,12 +414,13 @@ def canonical_2_adic_reduction(genus_symbol_quintuple_list): sage: canonical_2_adic_reduction(G2.symbol_tuple_list()) [[0, 2, -1, 0, 0]] - NOTES: + .. NOTE:: See Conway-Sloane 3rd edition, pp. 381-382 for definitions and examples. - TO DO: - - Add an example where sign walking occurs! + .. TODO:: + + Add an example where sign walking occurs! """ canonical_symbol = genus_symbol_quintuple_list # Canonical determinants: @@ -469,7 +469,7 @@ def basis_complement(B): OUTPUT: - a rectangular matrix over a field + a rectangular matrix over a field EXAMPLES:: @@ -515,7 +515,7 @@ def signature_pair_of_matrix(A): OUTPUT: - a pair (tuple) of integers. + a pair (tuple) of integers. EXAMPLES:: @@ -564,7 +564,10 @@ def p_adic_symbol(A, p, val): val = valuation of the maximal elementary divisor of A needed to obtain enough precision calculation is modulo p to the val+3 - TODO: Some description of the definition of the genus symbol. + + .. TODO:: + + Some description of the definition of the genus symbol. INPUT: @@ -574,7 +577,7 @@ def p_adic_symbol(A, p, val): OUTPUT: - a list of lists of integers + a list of lists of integers EXAMPLES:: @@ -638,7 +641,7 @@ def is_even_matrix(A): OUTPUT: - a pair of the form (boolean, integer) + a pair of the form (boolean, integer) EXAMPLES:: @@ -671,8 +674,8 @@ def split_odd(A): OUTPUT: - a pair (u, B) consisting of an odd integer u and an odd - integral symmetric matrix B. + a pair (u, B) consisting of an odd integer u and an odd + integral symmetric matrix B. EXAMPLES:: @@ -764,7 +767,7 @@ def trace_diag_mod_8(A): OUTPUT: - an integer + an integer EXAMPLES:: @@ -816,7 +819,7 @@ def two_adic_symbol(A, val): OUTPUT: - a list of lists of integers (representing a Conway-Sloane 2-adic symbol) + a list of lists of integers (representing a Conway-Sloane 2-adic symbol) EXAMPLES:: @@ -890,29 +893,6 @@ def two_adic_symbol(A, val): return [ [s[0]+m0] + s[1:] for s in sym + two_adic_symbol(A, val) ] - - - -## Removed because it was unused and undocumented! -# -#def is_trivial_symbol(p, sym): -# """ -# """ -# if len(sym) != 1: -# return False -# if sym[0] != 0 or sym[2] != 1: -# return False -# if p != 2: -# return True -# return sym[3] == 1 and sym[1] % 8 == sym[4] - - - - - - - - class Genus_Symbol_p_adic_ring(object): """ Local genus symbol over a p-adic ring. @@ -924,19 +904,19 @@ def __init__(self, prime, symbol, check = True): The genus symbol of a component p^m*A for odd prime = p is of the form (m,n,d), where - m = valuation of the component - n = rank of A - d = det(A) in {1,u} for normalized quadratic non-residue u. + - m = valuation of the component + - n = rank of A + - d = det(A) in {1,u} for normalized quadratic non-residue u. The genus symbol of a component 2^m*A is of the form (m,n,s,d,o), where - m = valuation of the component - n = rank of A - d = det(A) in {1,3,5,7} - s = 0 (or 1) if even (or odd) - o = oddity of A (= 0 if s = 0) in Z/8Z - = the trace of the diagonalization of A + - m = valuation of the component + - n = rank of A + - d = det(A) in {1,3,5,7} + - s = 0 (or 1) if even (or odd) + - o = oddity of A (= 0 if s = 0) in Z/8Z + = the trace of the diagonalization of A The genus symbol is a list of such symbols (ordered by m) for each of the Jordan blocks A_1,...,A_t. @@ -949,7 +929,6 @@ def __init__(self, prime, symbol, check = True): doubling conventions straight throughout! This is especially noticeable in the determinant and excess methods!! - INPUT: - prime -- a prime integer > 0 @@ -958,7 +937,7 @@ def __init__(self, prime, symbol, check = True): OUTPUT: - None + None EXAMPLES:: @@ -992,31 +971,29 @@ def __init__(self, prime, symbol, check = True): self._canonical_symbol = None def __repr__(self): - """ + r""" Gives a string representation for the p-adic genus symbol INPUT: - None + None OUTPUT: - a string + a string EXAMPLES:: sage: from sage.quadratic_forms.genera.genus import two_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring - sage: A = diagonal_matrix(ZZ, [1,2,3,4]) sage: s2 = two_adic_symbol(A, 2); s2 [[0, 2, 3, 1, 4], [1, 1, 1, 1, 1], [2, 1, 1, 1, 1]] sage: G = Genus_Symbol_p_adic_ring(2, s2) sage: G.__repr__() 'Genus symbol at 2 : [[0, 2, 3, 1, 4], [1, 1, 1, 1, 1], [2, 1, 1, 1, 1]]' - """ - return "Genus symbol at %s : %s"%(self._prime, self._symbol) + return "Genus symbol at %s : %s" % (self._prime, self._symbol) def __eq__(self, other): @@ -1025,11 +1002,11 @@ def __eq__(self, other): INPUT: - a Genus_Symbol_p_adic_ring object + a Genus_Symbol_p_adic_ring object OUTPUT: - boolean + boolean EXAMPLES:: @@ -1064,11 +1041,13 @@ def __ne__(self, other): INPUT: - a Genus_Symbol_p_adic_ring object + a ``Genus_Symbol_p_adic_ring`` object OUTPUT: - boolean + boolean + + EXAMPLES:: sage: from sage.quadratic_forms.genera.genus import p_adic_symbol sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring @@ -1109,11 +1088,11 @@ def canonical_symbol(self): INPUT: - None + None OUTPUT: - a list of lists of integers + a list of lists of integers EXAMPLES:: @@ -1156,13 +1135,13 @@ def canonical_symbol(self): sage: G3.canonical_symbol() [[0, 3, 1], [1, 1, -1]] + .. NOTE:: - NOTES: + See Conway-Sloane 3rd edition, pp. 381-382 for definitions and examples. - See Conway-Sloane 3rd edition, pp. 381-382 for definitions and examples. + .. TODO:: - TO DO: - - Add an example where sign walking occurs! + Add an example where sign walking occurs! """ symbol = self._symbol if self._prime == 2: @@ -1180,11 +1159,11 @@ def symbol_tuple_list(self): INPUT: - None + None OUTPUT: - list of lists of integers + list of lists of integers EXAMPLES:: @@ -1220,11 +1199,11 @@ def number_of_blocks(self): INPUT: - None + None OUTPUT: - integer >= 0 + integer >= 0 EXAMPLES:: @@ -1258,11 +1237,11 @@ def determinant(self): INPUT: - None + None OUTPUT: - an integer + an integer EXAMPLES:: @@ -1291,16 +1270,17 @@ def rank(self): """ Returns the dimension of a quadratic form associated to this genus symbol. - TO DO: DELETE THIS METHOD IN FAVOR OF THE dimension() METHOD BELOW! + .. TODO:: + DELETE THIS METHOD IN FAVOR OF THE dimension() METHOD BELOW! INPUT: - None + None OUTPUT: - an integer >= 0 + an integer >= 0 EXAMPLES:: @@ -1324,18 +1304,17 @@ def rank(self): """ return sum([ s[1] for s in self._symbol ]) - def dimension(self): """ Returns the dimension of a quadratic form associated to this genus symbol. INPUT: - None + None OUTPUT: - an integer >= 0 + an integer >= 0 EXAMPLES:: @@ -1372,15 +1351,15 @@ def excess(self): REFERENCE: - Conway and Sloane Book, 3rd edition, pp 370-371. + Conway and Sloane Book, 3rd edition, pp 370-371. INPUT: - None + None OUTPUT: - an integer + an integer EXAMPLES:: @@ -1448,11 +1427,11 @@ def trains(self): INPUT: - None + None OUTPUT: - a list of integers >= 0 + a list of integers >= 0 EXAMPLES:: @@ -1483,11 +1462,11 @@ def compartments(self): INPUT: - None + None OUTPUT: - a list of integers >= 0 + a list of integers >= 0 EXAMPLES:: @@ -1534,7 +1513,7 @@ def __init__(self, A, max_elem_divisors=None): OUTPUT: - None + None EXAMPLES:: @@ -1565,29 +1544,26 @@ def __init__(self, A, max_elem_divisors=None): def __repr__(self): - """ + r""" Returns a string representing the global genus symbol. INPUT: - None + None OUTPUT: - a string + a string EXAMPLES:: sage: from sage.quadratic_forms.genera.genus import GenusSymbol_global_ring - sage: A = DiagonalQuadraticForm(ZZ, [1,2,3,4]).Hessian_matrix() sage: GS = GenusSymbol_global_ring(A) sage: GS.__repr__() 'Genus of [2 0 0 0]\n[0 4 0 0]\n[0 0 6 0]\n[0 0 0 8]' - """ - return "Genus of %s"%self._representative - + return "Genus of %s" % self._representative def __eq__(self, other): @@ -1596,11 +1572,11 @@ def __eq__(self, other): INPUT: - a GenusSymbol_global_ring object + a ``GenusSymbol_global_ring`` object OUTPUT: - boolean + boolean EXAMPLES:: @@ -1642,11 +1618,11 @@ def __ne__(self, other): INPUT: - a GenusSymbol_global_ring object + a ``GenusSymbol_global_ring`` object OUTPUT: - boolean + boolean EXAMPLES:: @@ -1681,11 +1657,11 @@ def signature_pair_of_matrix(self): INPUT: - None + None OUTPUT: - a pair of integers (p, n) each >= 0 + a pair of integers (p, n) each >= 0 EXAMPLES:: @@ -1709,11 +1685,11 @@ def determinant(self): INPUT: - None + None OUTPUT: - an integer + an integer EXAMPLES:: diff --git a/src/sage/repl/image.py b/src/sage/repl/image.py index 34e065a094c..174703ca40d 100644 --- a/src/sage/repl/image.py +++ b/src/sage/repl/image.py @@ -14,8 +14,9 @@ sage: from sage.repl.image import Image sage: img = Image('RGB', (256, 256), 'white') sage: pixels = img.pixels() - sage: for x, y in CartesianProduct(range(img.width()), range(img.height())): - ....: pixels[x, y] = (x, y, 100) + sage: for x in range(img.width()): + ....: for y in range(img.height()): + ....: pixels[x, y] = (x, y, 100) sage: img 256x256px 24-bit RGB image sage: type(img) diff --git a/src/sage/repl/ipython_kernel/install.py b/src/sage/repl/ipython_kernel/install.py index 55e03622122..fe8aafbce85 100644 --- a/src/sage/repl/ipython_kernel/install.py +++ b/src/sage/repl/ipython_kernel/install.py @@ -1,39 +1,38 @@ """ -Installing the Sage IPython Kernel +Installing the SageMath Jupyter Kernel and extensions -Kernels have to register themselves with IPython so that they appear -in the IPython notebook's kernel drop-down. This is done by +Kernels have to register themselves with Jupyter so that they appear +in the Jupyter notebook's kernel drop-down. This is done by :class:`SageKernelSpec`. """ import os import errno -from jupyter_client.kernelspec import get_kernel_spec, install_kernel_spec -from IPython.paths import get_ipython_dir - from sage.env import ( SAGE_ROOT, SAGE_DOC, SAGE_LOCAL, SAGE_EXTCODE, SAGE_VERSION ) -from sage.misc.temporary_file import tmp_dir +from jupyter_core.paths import ENV_JUPYTER_PATH +JUPYTER_PATH = ENV_JUPYTER_PATH[0] class SageKernelSpec(object): def __init__(self): """ - Utility to manage Sage kernels + Utility to manage SageMath kernels and extensions EXAMPLES:: sage: from sage.repl.ipython_kernel.install import SageKernelSpec sage: spec = SageKernelSpec() sage: spec._display_name # random output - 'Sage 6.6.beta2' + 'SageMath 6.9' """ - self._display_name = 'Sage {0}'.format(SAGE_VERSION) - self._ipython_dir = get_ipython_dir() + self._display_name = 'SageMath {0}'.format(SAGE_VERSION) + self.nbextensions_dir = os.path.join(JUPYTER_PATH, "nbextensions") + self.kernel_dir = os.path.join(JUPYTER_PATH, "kernels", self.identifier()) self._mkdirs() def _mkdirs(self): @@ -45,40 +44,33 @@ def _mkdirs(self): sage: from sage.repl.ipython_kernel.install import SageKernelSpec sage: spec = SageKernelSpec() sage: spec._mkdirs() - sage: nbextensions = os.path.join(spec._ipython_dir, 'nbextensions') - sage: os.path.exists(nbextensions) + sage: os.path.isdir(spec.nbextensions_dir) True """ - def mkdir_p(*path_components): - path = os.path.join(*path_components) + def mkdir_p(path): try: os.makedirs(path) - except OSError as err: - if err.errno == errno.EEXIST and os.path.isdir(path): - pass - else: + except OSError: + if not os.path.isdir(path): raise - mkdir_p(self._ipython_dir, 'nbextensions') + mkdir_p(self.nbextensions_dir) + mkdir_p(self.kernel_dir) @classmethod - def identifier(self): + def identifier(cls): """ - Internal identifier for the Sage kernel - - OUTPUT: + Internal identifier for the SageMath kernel - String. + OUTPUT: the string ``"sagemath"``. EXAMPLES:: sage: from sage.repl.ipython_kernel.install import SageKernelSpec - sage: SageKernelSpec.identifier() # random output - 'sage_6_6_beta3' - sage: SageKernelSpec.identifier().startswith('sage_') - True + sage: SageKernelSpec.identifier() + 'sagemath' """ - return 'Sage {0}'.format(SAGE_VERSION).lower().replace(' ', '_').replace('.', '_') - + return 'sagemath' + def symlink(self, src, dst): """ Symlink ``src`` to ``dst`` @@ -103,38 +95,48 @@ def symlink(self, src, dst): if err.errno == errno.EEXIST: return os.symlink(src, dst) - + def use_local_mathjax(self): """ - Symlink Sage's Mathjax Install to the IPython notebook. + Symlink SageMath's Mathjax install to the Jupyter notebook. EXAMPLES:: sage: from sage.repl.ipython_kernel.install import SageKernelSpec - sage: from IPython.paths import get_ipython_dir sage: spec = SageKernelSpec() sage: spec.use_local_mathjax() - sage: ipython_dir = get_ipython_dir() - sage: mathjax = os.path.join(ipython_dir, 'nbextensions', 'mathjax') - sage: os.path.exists(mathjax) + sage: mathjax = os.path.join(spec.nbextensions_dir, 'mathjax') + sage: os.path.isdir(mathjax) True """ src = os.path.join(SAGE_LOCAL, 'share', 'mathjax') - dst = os.path.join(self._ipython_dir, 'nbextensions', 'mathjax') + dst = os.path.join(self.nbextensions_dir, 'mathjax') self.symlink(src, dst) def use_local_jsmol(self): + """ + Symlink jsmol to the Jupyter notebook. + + EXAMPLES:: + + sage: from sage.repl.ipython_kernel.install import SageKernelSpec + sage: spec = SageKernelSpec() + sage: spec.use_local_jsmol() + sage: jsmol = os.path.join(spec.nbextensions_dir, 'jsmol') + sage: os.path.isdir(jsmol) + True + """ src = os.path.join(SAGE_LOCAL, 'share', 'jsmol') - dst = os.path.join(self._ipython_dir, 'nbextensions', 'jsmol') + dst = os.path.join(self.nbextensions_dir, 'jsmol') self.symlink(src, dst) def _kernel_cmd(self): """ - Helper to construct the Sage kernel command. - + Helper to construct the SageMath kernel command. + OUTPUT: - List of strings. The command to start a new Sage kernel. + List of strings. The command to start a new SageMath kernel. EXAMPLES:: @@ -142,7 +144,7 @@ def _kernel_cmd(self): sage: spec = SageKernelSpec() sage: spec._kernel_cmd() ['/.../sage', - '-python', + '--python', '-m', 'sage.repl.ipython_kernel', '-f', @@ -150,40 +152,34 @@ def _kernel_cmd(self): """ return [ os.path.join(SAGE_ROOT, 'sage'), - '-python', + '--python', '-m', 'sage.repl.ipython_kernel', '-f', '{connection_file}', ] - + def kernel_spec(self): """ Return the kernel spec as Python dictionary OUTPUT: - A dictionary. See the IPython documentation for details. + A dictionary. See the Jupyter documentation for details. EXAMPLES:: sage: from sage.repl.ipython_kernel.install import SageKernelSpec sage: spec = SageKernelSpec() sage: spec.kernel_spec() - {'argv': ..., 'display_name': 'Sage ...'} + {'argv': ..., 'display_name': 'SageMath ...'} """ return dict( argv=self._kernel_cmd(), display_name=self._display_name, ) - + def _install_spec(self): """ - Install the Sage IPython kernel - - It is safe to call this method multiple times, only one Sage - kernel spec is ever installed for any given Sage - version. However, it resets the IPython kernel spec directory - so additional resources symlinked there are lost. See - :meth:`symlink_resources`. + Install the SageMath Jupyter kernel EXAMPLES:: @@ -191,21 +187,17 @@ def _install_spec(self): sage: spec = SageKernelSpec() sage: spec._install_spec() # not tested """ + jsonfile = os.path.join(self.kernel_dir, "kernel.json") import json - temp = tmp_dir() - kernel_spec = os.path.join(temp, 'kernel.json') - with open(kernel_spec, 'w') as f: + with open(jsonfile, 'w') as f: json.dump(self.kernel_spec(), f) - identifier = self.identifier() - install_kernel_spec(temp, identifier, user=True, replace=True) - self._spec = get_kernel_spec(identifier) def _symlink_resources(self): """ Symlink miscellaneous resources - This method symlinks additional resources (like the Sage - documentation) into the Sage kernel directory. This is + This method symlinks additional resources (like the SageMath + documentation) into the SageMath kernel directory. This is necessary to make the help links in the notebook work. EXAMPLES:: @@ -215,25 +207,23 @@ def _symlink_resources(self): sage: spec._install_spec() # not tested sage: spec._symlink_resources() # not tested """ - assert self._spec, 'call _install_spec() first' - spec_dir = self._spec.resource_dir path = os.path.join(SAGE_EXTCODE, 'notebook-ipython') for filename in os.listdir(path): self.symlink( os.path.join(path, filename), - os.path.join(spec_dir, filename) + os.path.join(self.kernel_dir, filename) ) self.symlink( os.path.join(SAGE_DOC, 'output', 'html', 'en'), - os.path.join(spec_dir, 'doc') + os.path.join(self.kernel_dir, 'doc') ) - + @classmethod def update(cls): """ - Configure the IPython notebook for the Sage kernel - - This method does everything necessary to use the Sage kernel, + Configure the Jupyter notebook for the SageMath kernel + + This method does everything necessary to use the SageMath kernel, you should never need to call any of the other methods directly. @@ -249,7 +239,7 @@ def update(cls): instance._install_spec() instance._symlink_resources() - + def have_prerequisites(debug=True): """ Check that we have all prerequisites to run the Jupyter notebook. diff --git a/src/sage/repl/ipython_kernel/kernel.py b/src/sage/repl/ipython_kernel/kernel.py index 9b00d8e25c9..eb61032dbd7 100644 --- a/src/sage/repl/ipython_kernel/kernel.py +++ b/src/sage/repl/ipython_kernel/kernel.py @@ -1,8 +1,8 @@ """ The Sage ZMQ Kernel -Version of the IPython kernel when running Sage inside the IPython -notebook or remote IPython sessions. +Version of the Jupyter kernel when running Sage inside the Jupyter +notebook or remote Jupyter sessions. """ #***************************************************************************** @@ -27,7 +27,7 @@ class SageZMQInteractiveShell(SageNotebookInteractiveShell, ZMQInteractiveShell) pass -class SageKernel(IPythonKernel): +class SageKernel(IPythonKernel): implementation = 'sage' implementation_version = SAGE_VERSION @@ -35,11 +35,11 @@ class SageKernel(IPythonKernel): def __init__(self, **kwds): """ - The Sage IPython Kernel + The Sage Jupyter Kernel INPUT: - See the IPython documentation + See the Jupyter documentation EXAMPLES:: @@ -54,8 +54,8 @@ def __init__(self, **kwds): def banner(self): r""" The Sage Banner - - The value of this property is displayed in the IPython + + The value of this property is displayed in the Jupyter notebook. OUTPUT: @@ -75,11 +75,11 @@ def banner(self): @property def help_links(self): r""" - Help in the IPython Notebook - + Help in the Jupyter Notebook + OUTPUT: - See the IPython documentation. + See the Jupyter documentation. EXAMPLES:: @@ -87,12 +87,12 @@ def help_links(self): sage: sk = SageKernel.__new__(SageKernel) sage: sk.help_links [{'text': 'Sage Documentation', - 'url': '/kernelspecs/sage_.../doc/index.html'}, + 'url': '../kernelspecs/sagemath/doc/index.html'}, ...] """ from sage.repl.ipython_kernel.install import SageKernelSpec identifier = SageKernelSpec.identifier() - kernel_url = lambda x: '/kernelspecs/{0}/{1}'.format(identifier, x) + kernel_url = lambda x: '../kernelspecs/{0}/{1}'.format(identifier, x) return [ { 'text': 'Sage Documentation', @@ -119,7 +119,7 @@ def help_links(self): 'url': kernel_url('doc/reference/index.html'), }, { - 'text': 'Developers Guide', + 'text': "Developer's Guide", 'url': kernel_url('doc/developer/index.html'), }, { diff --git a/src/sage/rings/all.py b/src/sage/rings/all.py index d4158bcc8a7..b14ecf33ba7 100644 --- a/src/sage/rings/all.py +++ b/src/sage/rings/all.py @@ -181,4 +181,4 @@ from sage.rings.contfrac import (CFF, ContinuedFractionField) # asymptotic ring -from asymptotic.all import AsymptoticRing \ No newline at end of file +from asymptotic.all import * diff --git a/src/sage/rings/asymptotic/all.py b/src/sage/rings/asymptotic/all.py index 554d02cc4d7..daf2b157307 100644 --- a/src/sage/rings/asymptotic/all.py +++ b/src/sage/rings/asymptotic/all.py @@ -1 +1,2 @@ -from asymptotic_ring import AsymptoticRing +from sage.misc.lazy_import import lazy_import +lazy_import('sage.rings.asymptotic.asymptotic_ring', 'AsymptoticRing') diff --git a/src/sage/rings/asymptotic/asymptotic_ring.py b/src/sage/rings/asymptotic/asymptotic_ring.py index 0fff2cb67f9..a2bacd54ade 100644 --- a/src/sage/rings/asymptotic/asymptotic_ring.py +++ b/src/sage/rings/asymptotic/asymptotic_ring.py @@ -1,56 +1,94 @@ r""" Asymptotic Ring -This module implements a ring (called :class:`AsymptoticRing`) for -computations with :class:`asymptotic expressions -`. +This module provides a ring (called :class:`AsymptoticRing`) for +computations with :wikipedia:`asymptotic expansions `. -Definition -========== -An asymptotic expression is a sum; its summands are the following: +.. _asymptotic_ring_definition: + +(Informal) Definition +===================== + +An asymptotic expansion is a sum such as + +.. MATH:: + + 5z^3 + 4z^2 + O(z) + +as `z \to \infty` or + +.. MATH:: + + 3x^{42}y^2 + 7x^3y^3 + O(x^2) + O(y) + +as `x` and `y` tend to `\infty`. It is a truncated series (after a +finite number of terms), which approximates a function. + +The summands of the asymptotic expansions are partially ordered. In +this module these summands are the following: - Exact terms `c\cdot g` with a coefficient `c` and an element `g` of - an :ref:`growth group `. + a growth group (:ref:`see below `). - `O`-terms `O(g)` (see :wikipedia:`Big O notation `; - also called *Bachmann--Landau notation*) for some :mod:`growth group - element ` `g` (:ref:`see below - `). + also called *Bachmann--Landau notation*) for a growth group + element `g` (:ref:`again see below `). + +See +:wikipedia:`the Wikipedia article on asymptotic expansions ` +for more details. +Further examples of such elements can be found :ref:`here `. -Examples of such elements can be found :ref:`below `. .. _asymptotic_ring_growth: -Growth Elements ---------------- +Growth Groups and Elements +-------------------------- -The elements of a :mod:`growth group -` are equipped with a partial -order and usually contain a variable. Examples are (among many -other possibilities) +The elements of a :doc:`growth group ` are equipped with +a partial order and usually contain a variable. Examples---the order +is described below these examples---are -- elements of the form `z^q` for some integer or rational `q` (growth - groups ``z^ZZ`` or ``z^QQ``), +- elements of the form `z^q` for some integer or rational `q` + (growth groups with :ref:`description strings ` + ``z^ZZ`` or ``z^QQ``), -- elements of the form `\log(z)^q` for some integer or rational `q` (growth - groups ``log(z)^ZZ`` or ``log(z)^QQ``), +- elements of the form `\log(z)^q` for some integer or rational `q` + (growth groups ``log(z)^ZZ`` or ``log(z)^QQ``), - elements of the form `a^z` for some rational `a` (growth group ``QQ^z``), or -- more sophisticated constructions like products `x^r \log(x)^s \cdot - a^y \cdot y^q` (this corresponds to an element of the growth group - ``x^QQ * \log(x)^ZZ * QQ^y * y^QQ``). +- more sophisticated constructions like products + `x^r \cdot \log(x)^s \cdot a^y \cdot y^q` + (this corresponds to an element of the growth group + ``x^QQ * log(x)^ZZ * QQ^y * y^QQ``). -The order in all these examples is the growth as `x`, `y`, or `z` -(independently) tend to `\infty`. For elements only using the -variable `z` this means, `g_1 \leq g_2` if +The order in all these examples is induced by the magnitude of the +elements as `x`, `y`, or `z` (independently) tend to `\infty`. For +elements only using the variable `z` this means that `g_1 \leq g_2` if .. MATH:: \lim_{z\to\infty} \frac{g_1}{g_2} \leq 1. +.. NOTE:: + + Asymptotic rings where the variable tend to some value distinct from + `\infty` are not yet implemented. + +To find out more about + +- growth groups, + +- on how they are created and + +- about the above used *descriptions strings* + +see the top of the module :doc:`growth group `. + + .. WARNING:: As this code is experimental, a warning is thrown when an @@ -60,63 +98,93 @@ TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.MonomialGrowthGroup(ZZ, 'x') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. See http://trac.sagemath.org/17601 for details. - sage: T = atm.ExactTermMonoid(G, ZZ) + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: T = GenericTermMonoid(G, ZZ) + sage: R. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. See http://trac.sagemath.org/17601 for details. - sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + .. _asymptotic_ring_intro: Introductory Examples ===================== +We start this series of examples by defining two asymptotic rings. + + +Two Rings +--------- + +A Univariate Asymptotic Ring +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + First, we construct the following (very simple) asymptotic ring in the variable `z`:: sage: A. = AsymptoticRing(growth_group='z^QQ', coefficient_ring=ZZ); A Asymptotic Ring over Integer Ring A typical element of this ring is - :: sage: A.an_element() - -z^(3/2) + O(z^(1/2)) + z^(3/2) + O(z^(1/2)) This element consists of two summands: the exact term with coefficient -`-1` and growth `z^{3/2}` and the `O`-term `O(z^{1/2})`. Note that the +`1` and growth `z^{3/2}` and the `O`-term `O(z^{1/2})`. Note that the growth of `z^{3/2}` is larger than the growth of `z^{1/2}` as -`z\to\infty`, thus this expression cannot be simplified (which would +`z\to\infty`, thus this expansion cannot be simplified (which would be done automatically, see below). +Elements can be constructed via the generator `z` and the function +:func:`~sage.rings.big_oh.O`, for example + +:: + + sage: 4*z^2 + O(z) + 4*z^2 + O(z) + +A Multivariate Asymptotic Ring +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Next, we construct a more sophisticated asymptotic ring in the variables `x` and `y` by - :: - sage: B. = AsymptoticRing(growth_group='x^QQ * \log(x)^ZZ * QQ^y * y^QQ', coefficient_ring=QQ); B # not implemented + sage: B. = AsymptoticRing(growth_group='x^QQ * log(x)^ZZ * QQ^y * y^QQ', coefficient_ring=QQ); B + Asymptotic Ring over Rational Field + +Again, we can look at a typical (nontrivial) element:: + + sage: B.an_element() + 1/8*x^(3/2)*log(x)^3*(1/8)^y*y^(3/2) + O(x^(1/2)*log(x)*(1/2)^y*y^(1/2)) -Again, we can look at a typical element:: +Again, elements can be created using the generators `x` and `y`, as well as +the function :func:`~sage.rings.big_oh.O`:: - sage: B.an_element() # not implemented + sage: log(x)*y/42 + O(1/2^y) + 1/42*log(x)*y + O((1/2)^y) Arithmetical Operations ----------------------- -With the asymptotic rings constructed above (or more precisely with -their elements) we can do a lot of arithmetical -calculations. +In this section we explain how to perform various arithmetical +operations with the elements of the asymptotic rings constructed +above. -We start our calculations in the ring +The Ring Operations Plus and Times +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We start our calculations in the ring :: sage: A @@ -124,7 +192,7 @@ Of course, we can perform the usual ring operations `+` and `*`:: - sage: z^2 + 3*z*(1 - z) + sage: z^2 + 3*z*(1-z) -2*z^2 + 3*z sage: (3*z + 2)^3 27*z^3 + 54*z^2 + 36*z + 8 @@ -132,52 +200,227 @@ In addition to that, special powers---our growth group ``z^QQ`` allows the exponents to be out of `\QQ`---can also be computed:: - sage: (z^(5/2) + z^(1/7)) * z^(-1/5) + sage: (z^(5/2)+z^(1/7)) * z^(-1/5) z^(23/10) + z^(-2/35) -The central concepts of computations with asymptotic expressions is +The central concepts of computations with asymptotic expansions is that the `O`-notation can be used. For example, we have - :: sage: z^3 + z^2 + z + O(z^2) z^3 + O(z^2) -and more advanced - +where the result is simplified automatically. A more sophisticated example is :: - sage: (z + 2*z^2 + 3*z^3 + 4*z^4) * (O(z) + z^2) + sage: (z+2*z^2+3*z^3+4*z^4) * (O(z)+z^2) 4*z^6 + O(z^5) -The division of asymptotic expressions is implemented as well:: - sage: A. = AsymptoticRing('z^QQ', QQ, default_prec=10) - sage: 1/(z - 1) - 1/z + z^(-2) + z^(-3) + z^(-4) + ... + z^(-10) + O(z^(-11)) - sage: (1 + 4*z)/(z + z^2 + z^3) - 4*z^(-2) - 3*z^(-3) - z^(-4) + 4*z^(-5) - 3*z^(-6) - ... - z^(-16) + O(z^(-17)) +Division +^^^^^^^^ -.. TODO:: +The asymptotic expansions support division. For example, we can +expand `1/(z-1)` to a geometric series:: - arithmetic in the ring + sage: 1 / (z-1) + z^(-1) + z^(-2) + z^(-3) + z^(-4) + ... + z^(-20) + O(z^(-21)) - :: +A default precision (parameter ``default_prec`` of +:class:`AsymptoticRing`) is predefined. Thus, only the first `20` +summands are calculated. However, if we only want the first `5` exact +terms, we cut of the rest by using +:: + + sage: (1 / (z-1)).truncate(5) + z^(-1) + z^(-2) + z^(-3) + z^(-4) + z^(-5) + O(z^(-6)) + +or + +:: + + sage: 1 / (z-1) + O(z^(-6)) + z^(-1) + z^(-2) + z^(-3) + z^(-4) + z^(-5) + O(z^(-6)) + +Of course, we can work with more complicated expansions as well:: + + sage: (4*z+1) / (z^3+z^2+z+O(z^0)) + 4*z^(-2) - 3*z^(-3) - z^(-4) + O(z^(-5)) + +Not all elements are invertible, for instance, + +:: + + sage: 1 / O(z) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot invert O(z). + +is not invertible, since it includes `0`. + + +Powers, Expontials and Logarithms +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +It works as simple as it can be; just use the usual operators ``^``, +``exp`` and ``log``. For example, we obtain the usual series expansion +of the logarithm +:: + + sage: -log(1-1/z) + z^(-1) + 1/2*z^(-2) + 1/3*z^(-3) + ... + O(z^(-21)) + +as `z \to \infty`. + +Similarly, we can apply the exponential function of an asymptotic expansion:: + + sage: exp(1/z) + 1 + z^(-1) + 1/2*z^(-2) + 1/6*z^(-3) + 1/24*z^(-4) + ... + O(z^(-20)) + +Arbitrary powers work as well; for example, we have +:: + + sage: (1 + 1/z + O(1/z^5))^(1 + 1/z) + 1 + z^(-1) + z^(-2) + 1/2*z^(-3) + 1/3*z^(-4) + O(z^(-5)) + + +Multivariate Arithmetic +^^^^^^^^^^^^^^^^^^^^^^^ + +Now let us move on to arithmetic in the multivariate ring + +:: + + sage: B + Asymptotic Ring over Rational Field + +.. TODO:: + + write this part - sage: B # not tested More Examples ============= -.. TODO:: - write more examples +The mathematical constant e as a limit +-------------------------------------- + +The base of the natural logarithm `e` satisfies the equation + +.. MATH:: + + e = \lim_{n\to\infty} \left(1+\frac{1}{n}\right)^n + +By using asymptotic expansions, we obtain the more precise result +:: + + sage: E. = AsymptoticRing(growth_group='n^ZZ', coefficient_ring=SR, default_prec=5); E + Asymptotic Ring over Symbolic Ring + sage: (1 + 1/n)^n + e - 1/2*e*n^(-1) + 11/24*e*n^(-2) - 7/16*e*n^(-3) + 2447/5760*e*n^(-4) + O(n^(-5)) + + +Selected Technical Details +========================== + + +Coercions and Functorial Constructions +-------------------------------------- + +The :class:`AsymptoticRing` fully supports +`coercion <../../../../coercion/index.html>`_. For example, the coefficient ring is automatically extended when needed:: + + sage: A + Asymptotic Ring over Integer Ring + sage: (z + 1/2).parent() + Asymptotic Ring over Rational Field + +Here, the coefficient ring was extended to allow `1/2` as a +coefficent. Another example is +:: + + sage: C. = AsymptoticRing(growth_group='c^ZZ', coefficient_ring=ZZ['e']) + sage: C.an_element() + e^3*c^3 + O(c) + sage: C.an_element() / 7 + 1/7*e^3*c^3 + O(c) + +Here the result's coefficient ring is the newly found +:: + + sage: (C.an_element() / 7).parent() + Asymptotic Ring over + Univariate Polynomial Ring in e over Rational Field + +Not only the coefficient ring can be extended, but the growth group as +well. For example, we can add/multiply elements of the asymptotic +rings ``A`` and ``C`` to get an expansion of new asymptotic ring:: + + sage: r = c*z + c/2 + O(z); r + c*z + 1/2*c + O(z) + sage: r.parent() + Asymptotic Ring over + Univariate Polynomial Ring in e over Rational Field + + +Data Structures +--------------- + +The summands of an +:class:`asymptotic expansion ` are wrapped +:doc:`growth group elements `. +This wrapping is done by the +:doc:`term monoid module `. +However, inside an +:class:`asymptotic expansion ` these summands +(terms) are stored together with their growth-relationship, i.e., each +summand knows its direct predecessors and successors. As a data +structure a special poset (namely a +:mod:`mutable poset `) +is used. We can have a look at this:: + + sage: b = x^3*y + x^2*y + x*y^2 + O(x) + O(y) + sage: print b.summands.repr_full(reverse=True) + poset(x*y^2, x^3*y, x^2*y, O(x), O(y)) + +-- oo + | +-- no successors + | +-- predecessors: x*y^2, x^3*y + +-- x*y^2 + | +-- successors: oo + | +-- predecessors: O(x), O(y) + +-- x^3*y + | +-- successors: oo + | +-- predecessors: x^2*y + +-- x^2*y + | +-- successors: x^3*y + | +-- predecessors: O(x), O(y) + +-- O(x) + | +-- successors: x*y^2, x^2*y + | +-- predecessors: null + +-- O(y) + | +-- successors: x*y^2, x^2*y + | +-- predecessors: null + +-- null + | +-- successors: O(x), O(y) + | +-- no predecessors + + +Various +======= AUTHORS: -- Benjamin Hackl (2015-06): initial version -- Benjamin Hackl (2015-07): improvement user interface (short notation) -- Daniel Krenn (2015-08): various improvents, review; documentation +- Benjamin Hackl (2015) +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + Classes and Methods =================== @@ -185,6 +428,7 @@ # ***************************************************************************** # Copyright (C) 2015 Benjamin Hackl +# 2015 Daniel Krenn # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -193,31 +437,36 @@ # http://www.gnu.org/licenses/ # ***************************************************************************** -from sage.structure.element import RingElement -from sage.rings.ring import Ring +from sage.rings.ring import Algebra +from sage.structure.element import CommutativeAlgebraElement from sage.structure.unique_representation import UniqueRepresentation from sage.misc.superseded import experimental - -class AsymptoticExpression(RingElement): +class AsymptoticExpansion(CommutativeAlgebraElement): r""" - Class for asymptotic expressions, i.e., the elements of an + Class for asymptotic expansions, i.e., the elements of an :class:`AsymptoticRing`. INPUT: - - ``parent`` -- the parent of the asymptotic expression. + - ``parent`` -- the parent of the asymptotic expansion. - ``summands`` -- the summands as a :class:`~sage.data_structures.mutable_poset.MutablePoset`, which represents the underlying structure. - ``simplify`` -- a boolean (default: ``True``). It controls - automatic simplification (absorption) of the asymptotic expression. + automatic simplification (absorption) of the asymptotic expansion. + + - ``convert`` -- a boolean (default: ``True``). If set, then the + ``summands`` are converted to the asymptotic ring (the parent of this + expansion). If not, then the summands are taken as they are. In + that case, the caller must ensure that the parent of the terms is + set correctly. EXAMPLES: - There are several ways to create asymptotic expressions; usually + There are several ways to create asymptotic expansions; usually this is done by using the corresponding :class:`asymptotic rings `:: sage: R_x. = AsymptoticRing(growth_group='x^QQ', coefficient_ring=QQ); R_x @@ -225,7 +474,7 @@ class AsymptoticExpression(RingElement): sage: R_y. = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=ZZ); R_y Asymptotic Ring over Integer Ring - At this point, `x` and `y` are already asymptotic expressions:: + At this point, `x` and `y` are already asymptotic expansions:: sage: type(x) @@ -239,16 +488,16 @@ class AsymptoticExpression(RingElement): 27*x + 54*x^(2/3) + 36*x^(1/3) + 8 One of the central ideas behind computing with asymptotic - expressions is that the `O`-notation (see + expansions is that the `O`-notation (see :wikipedia:`Big_O_notation`) can be used. For example, we have:: - sage: (x + 2*x^2 + 3*x^3 + 4*x^4) * (O(x) + x^2) + sage: (x+2*x^2+3*x^3+4*x^4) * (O(x)+x^2) 4*x^6 + O(x^5) In particular, :meth:`~sage.rings.big_oh.O` can be used to - construct the asymptotic expressions. With the help of the + construct the asymptotic expansions. With the help of the :meth:`summands`, we can also have a look at the inner structure - of an asymptotic expression:: + of an asymptotic expansion:: sage: expr1 = x + 2*x^2 + 3*x^3 + 4*x^4; expr2 = O(x) + x^2 sage: print(expr1.summands.repr_full()) @@ -311,13 +560,13 @@ class AsymptoticExpression(RingElement): .. SEEALSO:: - :mod:`sage.rings.asymptotic.growth_group`, - :mod:`sage.rings.asymptotic.term_monoid`, - :mod:`sage.data_structures.mutable_poset`. + :doc:`growth_group`, + :doc:`term_monoid`, + :mod:`~sage.data_structures.mutable_poset`. """ - def __init__(self, parent, summands, simplify=True): + def __init__(self, parent, summands, simplify=True, convert=True): r""" - See :class:`AsymptoticExpression` for more information. + See :class:`AsymptoticExpansion` for more information. TESTS:: @@ -335,7 +584,7 @@ def __init__(self, parent, summands, simplify=True): sage: from sage.rings.asymptotic.growth_group import GrowthGroup sage: from sage.rings.asymptotic.term_monoid import TermMonoid sage: G = GrowthGroup('x^ZZ'); x = G.gen() - sage: OT = TermMonoid('O', G); ET = TermMonoid('exact', G, ZZ) + sage: OT = TermMonoid('O', G, ZZ); ET = TermMonoid('exact', G, ZZ) sage: R = AsymptoticRing(G, ZZ) sage: lst = [ET(x, 1), ET(x^2, 2), OT(x^3), ET(x^4, 4)] sage: expr = R(lst, simplify=False); expr # indirect doctest @@ -378,10 +627,58 @@ def __init__(self, parent, summands, simplify=True): | +-- no successors sage: R(lst, simplify=True) # indirect doctest 4*x^4 + O(x^3) + + :: + + sage: R. = AsymptoticRing(growth_group='x^QQ', coefficient_ring=QQ) + sage: e = R(x^2 + O(x)) + sage: from sage.rings.asymptotic.asymptotic_ring import AsymptoticExpansion + sage: S = AsymptoticRing(growth_group='x^QQ', coefficient_ring=ZZ) + sage: for s in AsymptoticExpansion(S, e.summands).summands.elements_topological(): + ....: print s.parent() + O-Term Monoid x^QQ with implicit coefficients in Integer Ring + Exact Term Monoid x^QQ with coefficients in Integer Ring + sage: for s in AsymptoticExpansion(S, e.summands, + ....: convert=False).summands.elements_topological(): + ....: print s.parent() + O-Term Monoid x^QQ with implicit coefficients in Rational Field + Exact Term Monoid x^QQ with coefficients in Rational Field + + :: + + sage: AsymptoticExpansion(S, R(1/2).summands) + Traceback (most recent call last): + ... + ValueError: Cannot include 1/2 with parent + Exact Term Monoid x^QQ with coefficients in Rational Field in + Asymptotic Ring over Integer Ring + > *previous* ValueError: 1/2 is not a coefficient in + Exact Term Monoid x^QQ with coefficients in Integer Ring. """ - super(AsymptoticExpression, self).__init__(parent=parent) + super(AsymptoticExpansion, self).__init__(parent=parent) + + from sage.data_structures.mutable_poset import MutablePoset + if not isinstance(summands, MutablePoset): + raise TypeError('Summands %s are not in a mutable poset as expected ' + 'when creating an element of %s.' % (summands, parent)) + + if convert: + from misc import combine_exceptions + from term_monoid import TermMonoid + def convert_terms(element): + T = TermMonoid(term=element.parent(), asymptotic_ring=parent) + try: + return T(element) + except (ValueError, TypeError) as e: + raise combine_exceptions( + ValueError('Cannot include %s with parent %s in %s' % + (element, element.parent(), parent)), e) + new_summands = summands.copy() + new_summands.map(convert_terms, topological=True, reverse=True) + self._summands_ = new_summands + else: + self._summands_ = summands - self._summands_ = summands if simplify: self._simplify_() @@ -389,14 +686,14 @@ def __init__(self, parent, summands, simplify=True): @property def summands(self): r""" - The summands of this asymptotic expression stored in the + The summands of this asymptotic expansion stored in the underlying data structure (a :class:`~sage.data_structures.mutable_poset.MutablePoset`). EXAMPLES:: sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) - sage: expr = 7 * x^12 + x^5 + O(x^3) + sage: expr = 7*x^12 + x^5 + O(x^3) sage: expr.summands poset(O(x^3), x^5, 7*x^12) @@ -407,9 +704,29 @@ def summands(self): return self._summands_ + def __hash__(self): + r""" + A hash value for this element. + + .. WARNING:: + + This hash value uses the string representation and might not be + always right. + + TESTS:: + + sage: R_log = AsymptoticRing(growth_group='log(x)^QQ', coefficient_ring=QQ) + sage: lx = R_log(log(SR.var('x'))) + sage: elt = (O(lx) + lx^3)^4 + sage: hash(elt) # random + -4395085054568712393 + """ + return hash(str(self)) + + def __nonzero__(self): r""" - Return whether this asymptotic expression is not identically zero. + Return whether this asymptotic expansion is not identically zero. INPUT: @@ -426,7 +743,7 @@ def __nonzero__(self): False sage: bool(x) # indirect doctest True - sage: bool(7 * x^12 + x^5 + O(x^3)) # indirect doctest + sage: bool(7*x^12 + x^5 + O(x^3)) # indirect doctest True """ return bool(self._summands_) @@ -434,7 +751,7 @@ def __nonzero__(self): def __eq__(self, other): r""" - Return if this asymptotic expression is equal to ``other``. + Return whether this asymptotic expansion is equal to ``other``. INPUT: @@ -456,18 +773,66 @@ def __eq__(self, other): True sage: O(x) == O(x) False + + TESTS:: + + sage: x == None + False + + :: + + sage: x == 'x' + False + """ + if other is None: + return False + try: + return not bool(self - other) + except (TypeError, ValueError): + return False + + + def __ne__(self, other): + r""" + Return whether this asymptotic expansion is not equal to ``other``. + + INPUT: + + - ``other`` -- an object. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + EXAMPLES:: + + sage: R. = AsymptoticRing('x^ZZ', QQ) + sage: (1 + 2*x + 3*x^2) != (3*x^2 + 2*x + 1) # indirect doctest + False + sage: O(x) != O(x) + True + + TESTS:: + + sage: x != None + True """ - return not bool(self - other) + return not self == other def has_same_summands(self, other): r""" - Return if this asymptotic expression and ``other`` have the + Return whether this asymptotic expansion and ``other`` have the same summands. INPUT: - - ``other`` -- an asymptotic expression. + - ``other`` -- an asymptotic expansion. OUTPUT: @@ -476,11 +841,11 @@ def has_same_summands(self, other): .. NOTE:: While for example ``O(x) == O(x)`` yields ``False``, - these expressions *do* have the same summands and this method + these expansions *do* have the same summands and this method returns ``True``. Moreover, this method uses the coercion model in order to - find a common parent for this asymptotic expression and + find a common parent for this asymptotic expansion and ``other``. EXAMPLES:: @@ -491,7 +856,14 @@ def has_same_summands(self, other): True sage: O(x_ZZ) == O(x_QQ) False + + TESTS:: + + sage: x_ZZ.has_same_summands(None) + False """ + if other is None: + return False from sage.structure.element import have_same_parent if have_same_parent(self, other): return self._has_same_summands_(other) @@ -504,12 +876,12 @@ def has_same_summands(self, other): def _has_same_summands_(self, other): r""" - Return, if this :class:`AsymptoticExpression` has the same + Return whether this :class:`AsymptoticExpansion` has the same summands as ``other``. INPUT: - - ``other`` -- an :class:`AsymptoticExpression`. + - ``other`` -- an :class:`AsymptoticExpansion`. OUTPUT: @@ -517,7 +889,7 @@ def _has_same_summands_(self, other): .. NOTE:: - This method compares two :class:`AsymptoticExpression` + This method compares two :class:`AsymptoticExpansion` with the same parent. EXAMPLES:: @@ -538,7 +910,7 @@ def _has_same_summands_(self, other): def _simplify_(self): r""" - Simplify this asymptotic expression. + Simplify this asymptotic expansion. INPUT: @@ -546,25 +918,25 @@ def _simplify_(self): OUTPUT: - Nothing, but modifies this asymptotic expression. + Nothing, but modifies this asymptotic expansion. .. NOTE:: This method is usually called during initialization of - this asymptotic expression. + this asymptotic expansion. .. NOTE:: - This asymptotic expression is simplified by letting - `O`-terms that are included in this expression absorb all + This asymptotic expansion is simplified by letting + `O`-terms that are included in this expansion absorb all terms with smaller growth. TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: OT = atm.TermMonoid('O', G); ET = atm.TermMonoid('exact', G, ZZ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: OT = TermMonoid('O', G, ZZ); ET = TermMonoid('exact', G, ZZ) sage: R = AsymptoticRing(G, ZZ) sage: lst = [ET(x, 1), ET(x^2, 2), OT(x^3), ET(x^4, 4)] sage: expr = R(lst, simplify=False); expr # indirect doctest @@ -574,12 +946,12 @@ def _simplify_(self): sage: R(lst) # indirect doctest 4*x^4 + O(x^3) """ - self.summands.merge(reverse=True) + self._summands_.merge(reverse=True) def _repr_(self): r""" - A representation string for this asymptotic expression. + A representation string for this asymptotic expansion. INPUT: @@ -592,9 +964,9 @@ def _repr_(self): EXAMPLES:: sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) - sage: (5*x^2 + 12*x) * (x^3 + O(x)) # indirect doctest + sage: (5*x^2+12*x) * (x^3+O(x)) # indirect doctest 5*x^5 + 12*x^4 + O(x^3) - sage: (5*x^2 - 12*x) * (x^3 + O(x)) # indirect doctest + sage: (5*x^2-12*x) * (x^3+O(x)) # indirect doctest 5*x^5 - 12*x^4 + O(x^3) """ s = ' + '.join(repr(elem) for elem in @@ -607,15 +979,15 @@ def _repr_(self): def _add_(self, other): r""" - Add ``other`` to this asymptotic expression. + Add ``other`` to this asymptotic expansion. INPUT: - - ``other`` -- an :class:`AsymptoticExpression`. + - ``other`` -- an :class:`AsymptoticExpansion`. OUTPUT: - The sum as an :class:`AsymptoticExpression`. + The sum as an :class:`AsymptoticExpansion`. EXAMPLES:: @@ -626,7 +998,7 @@ def _add_(self, other): sage: expr1 + expr2 # indirect doctest x^321 + x^123 - If an `O`-term is added to an asymptotic expression, then + If an `O`-term is added to an asymptotic expansion, then the `O`-term absorbs everything it can:: sage: x^123 + x^321 + O(x^555) # indirect doctest @@ -639,25 +1011,25 @@ def _add_(self, other): sage: O(x) + x O(x) """ - smds = self.summands.copy().union(other.summands) - return self.parent()(summands=smds) + return self.parent()(self.summands.union(other.summands), + simplify=True, convert=False) def _sub_(self, other): r""" - Subtract ``other`` from this asymptotic expression. + Subtract ``other`` from this asymptotic expansion. INPUT: - - ``other`` -- an :class:`AsymptoticExpression`. + - ``other`` -- an :class:`AsymptoticExpansion`. OUTPUT: - The difference as an :class:`AsymptoticExpression`. + The difference as an :class:`AsymptoticExpansion`. .. NOTE:: - Subtraction of two asymptotic expressions is implemented + Subtraction of two asymptotic expansions is implemented by means of addition: `e_1 - e_2 = e_1 + (-1)\cdot e_2`. EXAMPLES:: @@ -669,47 +1041,50 @@ def _sub_(self, other): sage: O(x) - O(x) O(x) """ - return self + (-1) * other + return self + self.parent().coefficient_ring(-1)*other def _mul_term_(self, term): r""" - Helper method: multiply this asymptotic expression by the + Helper method: multiply this asymptotic expansion by the asymptotic term ``term``. INPUT: - ``term`` -- an asymptotic term (see - :mod:`~sage.rings.asymptotic.term_monoid`). + :doc:`term_monoid`). OUTPUT: - The product as an :class:`AsymptoticExpression`. + The product as an :class:`AsymptoticExpansion`. TESTS:: - sage: import sage.rings.asymptotic.term_monoid as atm + sage: from sage.rings.asymptotic.term_monoid import OTermMonoid sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) - sage: T = atm.OTermMonoid(R.growth_group) + sage: T = OTermMonoid(R.growth_group, ZZ) sage: expr = 10*x^2 + O(x) sage: t = T(R.growth_group.gen()) sage: expr._mul_term_(t) O(x^3) """ - return self.parent()([term * elem for elem in self.summands.elements()]) + from term_monoid import ExactTerm + simplify = not isinstance(term, ExactTerm) + return self.parent()(self.summands.mapped(lambda element: term * element), + simplify=simplify, convert=False) def _mul_(self, other): r""" - Multiply ``other`` by this asymptotic expression. + Multiply this asymptotic expansion by another asymptotic expansion ``other``. INPUT: - - ``other`` -- an :class:`AsymptoticExpression`. + - ``other`` -- an :class:`AsymptoticExpansion`. OUTPUT: - The product as an :class:`AsymptoticExpression`. + The product as an :class:`AsymptoticExpansion`. EXAMPLES:: @@ -727,8 +1102,39 @@ def _mul_(self, other): of the underlying poset shall be implemented at a later point. """ - return self.parent()(sum(self._mul_term_(term_other) for - term_other in other.summands.elements())) + return sum(self._mul_term_(term_other) for + term_other in other.summands.elements()) + + + def _rmul_(self, other): + r""" + Multiply this asymptotic expansion by an element ``other`` of its + coefficient ring. + + INPUT: + + - ``other`` -- an element of the coefficient ring. + + OUTPUT: + + An :class:`AsymptoticExpansion`. + + TESTS:: + + sage: A. = AsymptoticRing(growth_group='QQ^a * a^QQ * log(a)^QQ', coefficient_ring=ZZ) + sage: 2*a # indirect doctest + 2*a + """ + if other.is_zero(): + return self.parent().zero() + + from term_monoid import TermMonoid + E = TermMonoid('exact', asymptotic_ring=self.parent()) + e = E(self.parent().growth_group.one(), coefficient=other) + return self._mul_term_(e) + + + _lmul_ = _rmul_ def _div_(self, other): @@ -737,11 +1143,11 @@ def _div_(self, other): INPUT: - - ``other`` -- an asymptotic expression. + - ``other`` -- an asymptotic expansion. OUTPUT: - An asymptotic expression. + An asymptotic expansion. EXAMPLES:: @@ -749,7 +1155,7 @@ def _div_(self, other): sage: 1/x^42 x^(-42) sage: (1 + 4*x) / (x + 2*x^2) - 2*1/x - 1/2*x^(-2) + 1/4*x^(-3) - 1/8*x^(-4) + 1/16*x^(-5) + O(x^(-6)) + 2*x^(-1) - 1/2*x^(-2) + 1/4*x^(-3) - 1/8*x^(-4) + 1/16*x^(-5) + O(x^(-6)) sage: x / O(x) Traceback (most recent call last): ... @@ -758,23 +1164,25 @@ def _div_(self, other): return self * ~other - def __invert__(self): + def __invert__(self, precision=None): r""" Return the multiplicative inverse of this element. INPUT: - Nothing. + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. OUTPUT: - An asymptotic expression. + An asymptotic expansion. .. WARNING:: Due to truncation of infinite expansions, the element returned by this method might not fulfill - ``el * el._invert_() == 1``. + ``el * ~el == 1``. .. TODO:: @@ -784,100 +1192,149 @@ def __invert__(self): EXAMPLES:: - sage: R. = AsymptoticRing('x^ZZ', QQ, default_prec=4) + sage: R. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ, default_prec=4) sage: ~x - 1/x + x^(-1) sage: ~(x^42) x^(-42) sage: ex = ~(1 + x); ex - 1/x - x^(-2) + x^(-3) - x^(-4) + O(x^(-5)) - sage: ex * (1 + x) + x^(-1) - x^(-2) + x^(-3) - x^(-4) + O(x^(-5)) + sage: ex * (1+x) 1 + O(x^(-4)) sage: ~(1 + O(1/x)) - 1 + O(1/x) + 1 + O(x^(-1)) + + TESTS:: + + sage: A. = AsymptoticRing(growth_group='a^ZZ', coefficient_ring=ZZ) + sage: (1 / a).parent() + Asymptotic Ring over Rational Field + sage: (a / 2).parent() + Asymptotic Ring over Rational Field + + :: + + sage: ~A(0) + Traceback (most recent call last): + ... + ZeroDivisionError: Division by zero in 0. + + :: + + sage: B. = AsymptoticRing(growth_group='s^ZZ * t^ZZ', coefficient_ring=QQ) + sage: ~(s + t) + Traceback (most recent call last): + ... + ValueError: Expansion s + t cannot be inverted since there are + several maximal elements s, t. """ - if len(self.summands) == 0: + if not self.summands: raise ZeroDivisionError('Division by zero in %s.' % (self,)) elif len(self.summands) == 1: - return self.parent()(~next(self.summands.elements())) + element = next(self.summands.elements()) + return self.parent()._create_element_in_extension_( + ~element, element.parent()) max_elem = tuple(self.summands.maximal_elements()) if len(max_elem) != 1: - raise ValueError('Expression %s cannot be inverted, since there ' - 'are several maximal elements: %s.' % (self, max_elem)) - + raise ValueError('Expansion %s cannot be inverted since there ' + 'are several maximal elements %s.' % + (self, ', '.join(str(e) for e in + sorted(max_elem, key=str)))) max_elem = max_elem[0] + imax_elem = ~max_elem - one = self.parent().one() - geom = one - self._mul_term_(imax_elem) + if imax_elem.parent() is max_elem.parent(): + new_self = self + else: + new_self = self.parent()._create_element_in_extension_( + imax_elem, max_elem.parent()).parent()(self) + + one = new_self.parent().one() + geom = one - new_self._mul_term_(imax_elem) expanding = True result = one while expanding: - new_result = (geom * result + one).truncate() + new_result = (geom*result + one).truncate(precision=precision) if new_result.has_same_summands(result): expanding = False result = new_result return result._mul_term_(imax_elem) - def truncate(self, prec=None): + invert = __invert__ + + + def truncate(self, precision=None): r""" - Truncate this asymptotic expression. + Truncate this asymptotic expansion. INPUT: - - ``prec`` -- a positive integer or ``None``. Number of + - ``precision`` -- a positive integer or ``None``. Number of summands that are kept. If ``None`` (default value) is given, then ``default_prec`` from the parent is used. OUTPUT: - An asymptotic expression. + An asymptotic expansion. .. NOTE:: - For example, truncating an asymptotic expression with - ``prec=20`` does not yield an expression with exactly 20 + For example, truncating an asymptotic expansion with + ``precision=20`` does not yield an expansion with exactly 20 summands! Rather than that, it keeps the 20 summands - with the largest growth, and adds an appropriate - `O`-Term. + with the largest growth, and adds appropriate + `O`-Terms. EXAMPLES:: sage: R. = AsymptoticRing('x^ZZ', QQ) sage: ex = sum(x^k for k in range(5)); ex x^4 + x^3 + x^2 + x + 1 - sage: ex.truncate(prec=2) + sage: ex.truncate(precision=2) x^4 + x^3 + O(x^2) - sage: ex.truncate(prec=0) + sage: ex.truncate(precision=0) O(x^4) sage: ex.truncate() x^4 + x^3 + x^2 + x + 1 """ - if prec is None: - prec = self.parent().default_prec + if precision is None: + precision = self.parent().default_prec - if len(self.summands) <= prec: + if len(self.summands) <= precision: return self - else: - g = self.summands.elements_topological(reverse=True) - main_part = self.parent()([g.next() for _ in range(prec)]) - return main_part + (self - main_part).O() + summands = self.summands.copy() + from term_monoid import TermMonoid + def convert_terms(element): + if convert_terms.count < precision: + convert_terms.count += 1 + return element + T = TermMonoid(term='O', asymptotic_ring=self.parent()) + return T(element) + convert_terms.count = 0 + summands.map(convert_terms, topological=True, reverse=True) + return self.parent()(summands, simplify=True, convert=False) - def __pow__(self, power): + + def __pow__(self, exponent, precision=None): r""" - Takes this element to the given ``power``. + Calculate the power of this asymptotic expansion to the given ``exponent``. INPUT: - - ``power`` -- an element. + - ``exponent`` -- an element. + + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. OUTPUT: - An asymptotic expression. + An asymptotic expansion. TESTS:: @@ -886,58 +1343,117 @@ def __pow__(self, power): x^(1/7) sage: R_ZZ. = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=ZZ) sage: y^(1/7) - Traceback (most recent call last): - ... - ValueError: Growth Group y^ZZ disallows taking y to the power of 1/7. + y^(1/7) + sage: (y^(1/7)).parent() + Asymptotic Ring over Rational Field sage: (x^(1/2) + O(x^0))^15 x^(15/2) + O(x^7) - sage: (y^2+O(y))^(1/2) - Traceback (most recent call last): - ... - NotImplementedError: Taking the sum y^2 + O(y) to the - non-integer power 1/2 is not implemented. + sage: (y^2 + O(y))^(1/2) # not tested, see #19316 + y + O(1) sage: (y^2 + O(y))^(-2) y^(-4) + O(y^(-5)) :: + sage: B. = AsymptoticRing(growth_group='z^QQ * log(z)^QQ', coefficient_ring=QQ) + sage: (z^2 + O(z))^(1/2) + z + O(1) + + :: + + sage: A. = AsymptoticRing('QQ^x * x^SR * log(x)^ZZ', QQ) + sage: x * 2^x + 2^x*x + sage: 5^x * 2^x + 10^x + sage: 2^log(x) + x^(log(2)) + sage: 2^(x + 1/x) + 2^x + log(2)*2^x*x^(-1) + 1/2*log(2)^2*2^x*x^(-2) + ... + O(2^x*x^(-20)) + sage: _.parent() + Asymptotic Ring over Symbolic Ring + + See :trac:`19110`:: + sage: O(x)^(-1) Traceback (most recent call last): ... - ZeroDivisionError: Cannot invert O(x). + ZeroDivisionError: Cannot take O(x) to exponent -1. + > *previous* ZeroDivisionError: rational division by zero + + :: + + sage: B. = AsymptoticRing(growth_group='z^QQ * log(z)^QQ', coefficient_ring=QQ, default_prec=5) + sage: z^(1+1/z) + z + log(z) + 1/2*z^(-1)*log(z)^2 + 1/6*z^(-2)*log(z)^3 + + 1/24*z^(-3)*log(z)^4 + O(z^(-4)*log(z)^5) + + :: + + sage: B(0)^(-7) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot take 0 to the negative exponent -7. + sage: B(0)^SR.var('a') + Traceback (most recent call last): + ... + NotImplementedError: Taking 0 to the exponent a not implemented. + + :: + + sage: C. = AsymptoticRing(growth_group='s^QQ * t^QQ', coefficient_ring=QQ) + sage: (s + t)^s + Traceback (most recent call last): + ... + ValueError: Cannot take s + t to the exponent s. + > *previous* ValueError: log(s + t) cannot be constructed since + there are several maximal elements s, t. """ - if len(self.summands) > 1: - from sage.rings.integer_ring import ZZ - if power not in ZZ: - raise NotImplementedError('Taking the sum %s to the ' - 'non-integer power %s is not ' - 'implemented.' % (self, power)) - return super(AsymptoticExpression, self).__pow__(power) + if not self.summands: + if exponent == 0: + return self.parent().one() + elif exponent > 0: + return self.parent().zero() + elif exponent < 0: + raise ZeroDivisionError('Cannot take %s to the negative exponent %s.' % + (self, exponent)) + else: + raise NotImplementedError('Taking %s to the exponent %s not implemented.' % + (self, exponent)) - P = self.parent() - if power not in P.growth_group.base(): - raise ValueError('%s disallows taking %s ' - 'to the power of %s.' % - (P.growth_group, self, power)) - - from sage.rings.asymptotic.term_monoid import TermWithCoefficient - expr = next(self.summands.elements()) - if isinstance(expr, TermWithCoefficient): - new_growth = expr.growth**power - new_coeff = expr.coefficient**power - return P(expr.parent()(new_growth, new_coeff)) + elif len(self.summands) == 1: + element = next(self.summands.elements()) + if isinstance(exponent, AsymptoticExpansion) and element.is_constant(): + return exponent.rpow(base=element.coefficient, precision=precision) + try: + return self.parent()._create_element_in_extension_( + element ** exponent, element.parent()) + except (ArithmeticError, TypeError, ValueError): + if not isinstance(exponent, AsymptoticExpansion): + raise + + from sage.rings.integer_ring import ZZ + try: + exponent = ZZ(exponent) + except (TypeError, ValueError): + pass else: - if power >= 0: - new_growth = expr.growth**power - return P(expr.parent()(new_growth)) - else: - return (~self)**(-power) + return super(AsymptoticExpansion, self).__pow__(exponent) + + try: + return (exponent * self.log(precision=precision)).exp(precision=precision) + except (TypeError, ValueError, ZeroDivisionError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('Cannot take %s to the exponent %s.' % (self, exponent)), e) + pow = __pow__ + def O(self): r""" - Convert all terms in this asymptotic expression to `O`-terms. + Convert all terms in this asymptotic expansion to `O`-terms. INPUT: @@ -945,7 +1461,7 @@ def O(self): OUTPUT: - An asymptotic expression. + An asymptotic expansion. EXAMPLES:: @@ -954,42 +1470,654 @@ def O(self): O(x) sage: type(O(x)) - sage: expr = 42 * x^42 + x^10 + O(x^2); expr + sage: expr = 42*x^42 + x^10 + O(x^2); expr 42*x^42 + x^10 + O(x^2) sage: expr.O() O(x^42) + sage: O(AR(0)) + 0 + sage: (2*x).O() + O(x) - TESTS:: + .. SEEALSO:: - sage: O(AR(0)) + :func:`sage.rings.power_series_ring.PowerSeriesRing`, + :func:`sage.rings.laurent_series_ring.LaurentSeriesRing`. + """ + return sum(self.parent().create_summand('O', growth=element) + for element in self.summands.maximal_elements()) + + + def log(self, base=None, precision=None): + r""" + The logarithm of this asymptotic expansion. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. + + OUTPUT: + + An asymptotic expansion. + + .. NOTE:: + + Computing the logarithm of an asymptotic expansion + is possible if and only if there is exactly one maximal + summand in the expansion. + + ALGORITHM: + + If the expansion has more than one summand, + the asymptotic expansion for `\log(1+t)` as `t` tends to `0` + is used. + + .. TODO:: + + As soon as `L`-terms are implemented, this + implementation has to be adapted as well in order to + yield correct results. + + EXAMPLES:: + + sage: R. = AsymptoticRing(growth_group='x^ZZ * log(x)^ZZ', coefficient_ring=QQ) + sage: log(x) + log(x) + sage: log(x^2) + 2*log(x) + sage: log(x-1) + log(x) - x^(-1) - 1/2*x^(-2) - 1/3*x^(-3) - ... + O(x^(-21)) + + TESTS:: + + sage: log(R(1)) + 0 + sage: log(R(0)) + Traceback (most recent call last): + ... + ArithmeticError: Cannot compute log(0) in + Asymptotic Ring over Rational Field. + sage: C. = AsymptoticRing(growth_group='s^ZZ * t^ZZ', coefficient_ring=QQ) + sage: log(s + t) + Traceback (most recent call last): + ... + ValueError: log(s + t) cannot be constructed since there are + several maximal elements s, t. + """ + P = self.parent() + + if not self.summands: + raise ArithmeticError('Cannot compute log(0) in %s.' % (self.parent(),)) + + elif len(self.summands) == 1: + if self.is_one(): + return P.zero() + element = next(self.summands.elements()) + return sum(P._create_element_in_extension_(l, element.parent()) + for l in element.log_term(base=base)) + + max_elem = tuple(self.summands.maximal_elements()) + if len(max_elem) != 1: + raise ValueError('log(%s) cannot be constructed since there ' + 'are several maximal elements %s.' % + (self, ', '.join(str(e) for e in + sorted(max_elem, key=str)))) + max_elem = max_elem[0] + + imax_elem = ~max_elem + if imax_elem.parent() is max_elem.parent(): + new_self = self + else: + new_self = P._create_element_in_extension_( + imax_elem, max_elem.parent()).parent()(self) + + one = new_self.parent().one() + geom = one - new_self._mul_term_(imax_elem) + + from sage.rings.integer_ring import ZZ + expanding = True + result = -geom + geom_k = geom + k = ZZ(1) + while expanding: + k += ZZ(1) + geom_k *= geom + new_result = (result - geom_k * ~k).truncate(precision=precision) + if new_result.has_same_summands(result): + expanding = False + result = new_result + + result += new_self.parent()(max_elem).log() + if base: + from sage.functions.log import log + result = result / log(base) + return result + + + def is_little_o_of_one(self): + r""" + Return whether this expansion is of order `o(1)`. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: A. = AsymptoticRing('x^ZZ * log(x)^ZZ', QQ) + sage: (x^4 * log(x)^(-2) + x^(-4) * log(x)^2).is_little_o_of_one() + False + sage: (x^(-1) * log(x)^1234 + x^(-2) + O(x^(-3))).is_little_o_of_one() + True + sage: (log(x) - log(x-1)).is_little_o_of_one() + True + + :: + + sage: A. = AsymptoticRing('x^QQ * y^QQ * log(y)^ZZ', QQ) + sage: (x^(-1/16) * y^32 + x^32 * y^(-1/16)).is_little_o_of_one() + False + sage: (x^(-1) * y^(-3) + x^(-3) * y^(-1)).is_little_o_of_one() + True + sage: (x^(-1) * y / log(y)).is_little_o_of_one() + False + sage: (log(y-1)/log(y) - 1).is_little_o_of_one() + True + """ + return all(term.is_little_o_of_one() for term in self.summands.maximal_elements()) + + + def rpow(self, base, precision=None): + r""" + Return the power of ``base`` to this asymptotic expansion. + + INPUT: + + - ``base`` -- an element or ``'e'``. + + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. + + OUTPUT: + + An asymptotic expansion. + + EXAMPLES:: + + sage: A. = AsymptoticRing('x^ZZ', QQ) + sage: (1/x).rpow('e', precision=5) + 1 + x^(-1) + 1/2*x^(-2) + 1/6*x^(-3) + 1/24*x^(-4) + O(x^(-5)) + + TESTS:: + + sage: x.rpow(SR.var('y')) Traceback (most recent call last): ... - ValueError: Cannot build O(0). + ArithmeticError: Cannot construct y^x in Growth Group x^ZZ + > *previous* TypeError: unsupported operand parent(s) for '*': + 'Growth Group x^ZZ' and 'Growth Group SR^x' + """ + if isinstance(base, AsymptoticExpansion): + return base.__pow__(self, precision=precision) + + P = self.parent() + + # first: remove terms from a copy of this term such that a + # term in o(1) remains + + expr_o = self.summands.copy() + large_terms = [] + for term in self.summands.elements_topological(): + if not term.is_little_o_of_one(): + large_terms.append(term) + expr_o.remove(term.growth) + + expr_o = P(expr_o) + + # next: try to take the exponential function of the large elements + + try: + large_result = P.prod( + P._create_element_in_extension_(term.rpow(base), + term.parent()) + for term in large_terms) + except (TypeError, ValueError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('Cannot construct the power of %s to the ' + 'exponent %s in %s.' % + (base, self, self.parent())), e) + + # then: expand expr_o + + if not expr_o: + return large_result + + + if base == 'e': + geom = expr_o + else: + from sage.functions.log import log + geom = expr_o * log(base) + P = geom.parent() + + expanding = True + result = P.one() + geom_k = P.one() + from sage.rings.integer_ring import ZZ + k = ZZ(0) + while expanding: + k += ZZ(1) + geom_k *= geom + new_result = (result + geom_k / k.factorial()).truncate(precision=precision) + if new_result.has_same_summands(result): + expanding = False + result = new_result + + return result * large_result + + + def exp(self, precision=None): + r""" + Return the exponential of (i.e., the power of `e` to) this asymptotic expansion. + + INPUT: + + - ``precision`` -- the precision used for truncating the + expansion. If ``None`` (default value) is used, the + default precision of the parent is used. + + OUTPUT: + + An asymptotic expansion. + + .. NOTE:: + + The exponential function of this expansion can only be + computed exactly if the respective growth element can be + constructed in the underlying growth group. + + ALGORITHM: + + If the corresponding growth can be constructed, return + the exact exponential function. Otherwise, if this term + is `o(1)`, try to expand the series and truncate + according to the given precision. + + .. TODO:: + + As soon as `L`-terms are implemented, this + implementation has to be adapted as well in order to + yield correct results. + + EXAMPLES:: + + sage: A. = AsymptoticRing('(e^x)^ZZ * x^ZZ * log(x)^ZZ', SR) + sage: exp(x) + e^x + sage: exp(2*x) + (e^x)^2 + sage: exp(x + log(x)) + e^x*x + + :: + + sage: (x^(-1)).exp(precision=7) + 1 + x^(-1) + 1/2*x^(-2) + 1/6*x^(-3) + ... + O(x^(-7)) + + TESTS:: + + sage: A. = AsymptoticRing('(e^x)^ZZ * x^QQ * log(x)^QQ', SR) + sage: exp(log(x)) + x + sage: log(exp(x)) + x + + :: + + sage: exp(x+1) + e*e^x + """ + return self.rpow('e', precision=precision) + + + def substitute(self, rules=None, domain=None, **kwds): + r""" + Substitute the given ``rules`` in this asymptotic expansion. + + INPUT: + + - ``rules`` -- a dictionary. + + - ``kwds`` -- keyword arguments will be added to the + substitution ``rules``. + + - ``domain`` -- (default: ``None``) a parent. The neutral + elements `0` and `1` (rules for the keys ``'_zero_'`` and + ``'_one_'``, see note box below) are taken out of this + domain. If ``None``, then this is determined automatically. + + OUTPUT: + + An object. + + .. NOTE:: + + The neutral element of the asymptotic ring is replaced by + the value to the key ``'_zero_'``; the neutral element of + the growth group is replaced by the value to the key + ``'_one_'``. + + EXAMPLES:: + + sage: A. = AsymptoticRing(growth_group='(e^x)^QQ * x^ZZ * log(x)^ZZ', coefficient_ring=QQ, default_prec=5) + + :: + + sage: (e^x * x^2 + log(x)).subs(x=SR('s')) + s^2*e^s + log(s) + sage: _.parent() + Symbolic Ring + + :: + + sage: (x^3 + x + log(x)).subs(x=x+5).truncate(5) + x^3 + 15*x^2 + 76*x + log(x) + 130 + O(x^(-1)) + sage: _.parent() + Asymptotic Ring <(e^x)^QQ * x^ZZ * log(x)^ZZ> over Rational Field + + :: + + sage: (e^x * x^2 + log(x)).subs(x=2*x) + 4*(e^x)^2*x^2 + log(x) + log(2) + sage: _.parent() + Asymptotic Ring <(e^x)^QQ * x^QQ * log(x)^QQ> over Symbolic Ring + + :: + + sage: (x^2 + log(x)).subs(x=4*x+2).truncate(5) + 16*x^2 + 16*x + log(x) + log(4) + 4 + 1/2*x^(-1) + O(x^(-2)) + sage: _.parent() + Asymptotic Ring <(e^x)^QQ * x^ZZ * log(x)^ZZ> over Symbolic Ring + + :: + + sage: (e^x * x^2 + log(x)).subs(x=RIF(pi)) + 229.534211738584? + sage: _.parent() + Real Interval Field with 53 bits of precision .. SEEALSO:: - :func:`sage.rings.power_series_ring.PowerSeriesRing`, - :func:`sage.rings.laurent_series_ring.LaurentSeriesRing`. + :meth:`sage.symbolic.expression.Expression.subs` + + TESTS:: + + sage: x.subs({'y': -1}) + Traceback (most recent call last): + ... + ValueError: Cannot substitute y in x since it is not a generator of + Asymptotic Ring <(e^x)^QQ * x^ZZ * log(x)^ZZ> over Rational Field. + sage: B. = AsymptoticRing(growth_group='u^QQ * v^QQ * w^QQ', coefficient_ring=QQ) + sage: (1/u).subs({'u': 0}) + Traceback (most recent call last): + ... + TypeError: Cannot apply the substitution rules {u: 0} on u^(-1) in + Asymptotic Ring over Rational Field. + > *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Asymptotic Ring over Rational Field. + >> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Exact Term Monoid u^QQ * v^QQ * w^QQ with coefficients in Rational Field. + >...> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Growth Group u^QQ * v^QQ * w^QQ. + >...> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Growth Group u^QQ. + >...> *previous* ZeroDivisionError: rational division by zero + sage: (1/u).subs({'u': 0, 'v': SR.var('v')}) + Traceback (most recent call last): + ... + TypeError: Cannot apply the substitution rules {u: 0, v: v} on u^(-1) in + Asymptotic Ring over Rational Field. + > *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Asymptotic Ring over Rational Field. + >> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Exact Term Monoid u^QQ * v^QQ * w^QQ with coefficients in Rational Field. + >...> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Growth Group u^QQ * v^QQ * w^QQ. + >...> *previous* ZeroDivisionError: Cannot substitute in u^(-1) in + Growth Group u^QQ. + >...> *previous* ZeroDivisionError: rational division by zero + + :: + + sage: u.subs({u: 0, 'v': SR.var('v')}) + 0 + sage: v.subs({u: 0, 'v': SR.var('v')}) + v + sage: _.parent() + Symbolic Ring + + :: + + sage: u.subs({SR.var('u'): -1}) + Traceback (most recent call last): + ... + TypeError: Cannot substitute u in u since it is neither an + asymptotic expansion nor a string + (but a ). + + :: + + sage: u.subs({u: 1, 'u': 1}) + 1 + sage: u.subs({u: 1}, u=1) + 1 + sage: u.subs({u: 1, 'u': 2}) + Traceback (most recent call last): + ... + ValueError: Cannot substitute in u: duplicate key u. + sage: u.subs({u: 1}, u=3) + Traceback (most recent call last): + ... + ValueError: Cannot substitute in u: duplicate key u. """ - if not self: - raise ValueError('Cannot build O(%s).' % (self,)) - return sum(self.parent().create_summand('O', growth=element) - for element in self.summands.maximal_elements()) + # check if nothing to do + if not rules and not kwds: + return self + + # init and process keyword arguments + gens = self.parent().gens() + locals = kwds or dict() + + # update with rules + if isinstance(rules, dict): + for k, v in rules.iteritems(): + if not isinstance(k, str) and k not in gens: + raise TypeError('Cannot substitute %s in %s ' + 'since it is neither an ' + 'asymptotic expansion ' + 'nor a string (but a %s).' % + (k, self, type(k))) + k = str(k) + if k in locals and locals[k] != v: + raise ValueError('Cannot substitute in %s: ' + 'duplicate key %s.' % (self, k)) + locals[k] = v + elif rules is not None: + raise TypeError('Substitution rules %s have to be a dictionary.' % + (rules,)) + + # fill up missing rules + for g in gens: + locals.setdefault(str(g), g) + + # check if all keys are generators + gens_str = tuple(str(g) for g in gens) + for k in locals: + if str(k) not in gens_str: + raise ValueError('Cannot substitute %s in %s ' + 'since it is not a generator of %s.' % + (k, self, self.parent())) + + # determine 0 and 1 + if domain is None and \ + ('_zero_' not in locals or '_one_' not in locals): + P = self.parent() + for g in gens: + G = locals[str(g)].parent() + if G is not P: + domain = G + break + else: + domain = P + locals.setdefault('_zero_', domain.zero()) + locals.setdefault('_one_', domain.one()) + + # do the actual substitution + try: + return self._substitute_(locals) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import combine_exceptions + rules = '{' + ', '.join( + '%s: %s' % (k, v) + for k, v in sorted(locals.iteritems(), + key=lambda k: str(k[0])) + if not k.startswith('_') and + not any(k == str(g) and v is g for g in gens)) + '}' + raise combine_exceptions( + TypeError('Cannot apply the substitution rules %s on %s ' + 'in %s.' % (rules, self, self.parent())), e) + + + subs = substitute -class AsymptoticRing(Ring, UniqueRepresentation): + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this asymptotic expansion. + + INPUT: + + - ``rules`` -- a dictionary. + The neutral element of the asymptotic ring is replaced by the value + to key ``'_zero_'``. + + OUTPUT: + + An object. + + TESTS:: + + sage: A. = AsymptoticRing(growth_group='z^QQ', coefficient_ring=QQ) + sage: z._substitute_({'z': SR.var('a')}) + a + sage: _.parent() + Symbolic Ring + sage: A(0)._substitute_({'_zero_': 'zero'}) + 'zero' + sage: (1/z)._substitute_({'z': 4}) + 1/4 + sage: _.parent() + Rational Field + sage: (1/z)._substitute_({'z': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in z^(-1) in + Asymptotic Ring over Rational Field. + > *previous* ZeroDivisionError: Cannot substitute in z^(-1) in + Exact Term Monoid z^QQ with coefficients in Rational Field. + >> *previous* ZeroDivisionError: Cannot substitute in z^(-1) in + Growth Group z^QQ. + >...> *previous* ZeroDivisionError: rational division by zero + """ + if not self.summands: + return rules['_zero_'] + from sage.symbolic.operators import add_vararg + try: + return add_vararg( + *tuple(s._substitute_(rules) + for s in self.summands.elements_topological())) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + + def symbolic_expression(self, R=None): + r""" + Return this asymptotic expansion as a symbolic expression. + + INPUT: + + - ``R`` -- (a subring of) the symbolic ring or ``None``. + The output is will be an element of ``R``. If ``None``, + then the symbolic ring is used. + + OUTPUT: + + A symbolic expression. + + EXAMPLES:: + + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^QQ * log(y)^QQ * QQ^z * z^QQ', coefficient_ring=QQ) + sage: SR(A.an_element()) # indirect doctest + 1/8*(1/8)^z*x^3*y^(3/2)*z^(3/2)*log(y)^(3/2) + + Order((1/2)^z*x*sqrt(y)*sqrt(z)*sqrt(log(y))) + + TESTS:: + + sage: a = A.an_element(); a + 1/8*x^3*y^(3/2)*log(y)^(3/2)*(1/8)^z*z^(3/2) + + O(x*y^(1/2)*log(y)^(1/2)*(1/2)^z*z^(1/2)) + sage: a.symbolic_expression() + 1/8*(1/8)^z*x^3*y^(3/2)*z^(3/2)*log(y)^(3/2) + + Order((1/2)^z*x*sqrt(y)*sqrt(z)*sqrt(log(y))) + sage: _.parent() + Symbolic Ring + + :: + + sage: from sage.symbolic.ring import SymbolicRing + sage: class MySymbolicRing(SymbolicRing): + ....: pass + sage: mySR = MySymbolicRing() + sage: a.symbolic_expression(mySR).parent() is mySR + True + """ + if R is None: + from sage.symbolic.ring import SR + R = SR + + return self.substitute(dict((g, R(R.var(str(g)))) + for g in self.parent().gens()), + domain=R) + + + _symbolic_ = symbolic_expression # will be used by SR._element_constructor_ + + +class AsymptoticRing(Algebra, UniqueRepresentation): r""" - A ring consisting of :class:`asymptotic expressions `. + A ring consisting of :class:`asymptotic expansions `. INPUT: - ``growth_group`` -- either a partially ordered group (see - :mod:`~sage.rings.asymptotic.growth_group`) or a string + :doc:`growth_group`) or a string describing such a growth group (see :class:`~sage.rings.asymptotic.growth_group.GrowthGroupFactory`). - ``coefficient_ring`` -- the ring which contains the - coefficients of the expressions. + coefficients of the expansions. - ``default_prec`` -- a positive integer. This is the number of summands that are kept before truncating an infinite series. @@ -1013,8 +2141,8 @@ class AsymptoticRing(Ring, UniqueRepresentation): This is equivalent to the following code, which explicitly specifies the underlying growth group:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_QQ = agg.GrowthGroup('x^QQ') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_QQ = GrowthGroup('x^QQ') sage: R2_x. = AsymptoticRing(growth_group=G_QQ, coefficient_ring=QQ); R2_x Asymptotic Ring over Rational Field @@ -1030,8 +2158,8 @@ class AsymptoticRing(Ring, UniqueRepresentation): sage: R_log = AsymptoticRing(growth_group='log(x)^ZZ', coefficient_ring=QQ); R_log Asymptotic Ring over Rational Field - Other growth groups are available. See :mod:`~sage.rings.asymptotic.asymptotic_ring` for - a lot more examples. + Other growth groups are available. See :doc:`asymptotic_ring` for + more examples. Below there are some technical details. @@ -1054,14 +2182,29 @@ class AsymptoticRing(Ring, UniqueRepresentation): sage: R1_x.has_coerce_map_from(QQ) True + + TESTS:: + + sage: from sage.rings.asymptotic.asymptotic_ring import AsymptoticRing as AR_class + sage: class AR(AR_class): + ....: class Element(AR_class.Element): + ....: __eq__ = AR_class.Element.has_same_summands + sage: A = AR(growth_group='z^QQ', coefficient_ring=QQ) + sage: from itertools import islice + sage: TestSuite(A).run( # not tested # long time # see #19424 + ....: verbose=True, + ....: elements=tuple(islice(A.some_elements(), 10)), + ....: skip=('_test_some_elements', # to many elements + ....: '_test_distributivity')) # due to cancellations: O(z) != O(z^2) """ + # enable the category framework for elements - Element = AsymptoticExpression + Element = AsymptoticExpansion @staticmethod - def __classcall__(cls, growth_group, coefficient_ring, names=None, - category=None, default_prec=None): + def __classcall__(cls, growth_group=None, coefficient_ring=None, + names=None, category=None, default_prec=None): r""" Normalizes the input in order to ensure a unique representation of the parent. @@ -1075,8 +2218,8 @@ def __classcall__(cls, growth_group, coefficient_ring, names=None, are unique. Also, this enables the use of the generation framework:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: MG = agg.GrowthGroup('x^ZZ') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: MG = GrowthGroup('x^ZZ') sage: AR1 = AsymptoticRing(growth_group=MG, coefficient_ring=ZZ) sage: AR2. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) sage: AR1 is AR2 @@ -1088,14 +2231,94 @@ def __classcall__(cls, growth_group, coefficient_ring, names=None, sage: AR. = AsymptoticRing(growth_group='log(x)^ZZ', coefficient_ring=ZZ) Traceback (most recent call last): ... - ValueError: Growth Group log(x)^ZZ does not have a generator. + ValueError: Growth Group log(x)^ZZ does not provide any + generators but name 'lx' given. + + The names of the generators have to agree with the names used in + the growth group except for univariate rings:: + + sage: A. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ); A + Asymptotic Ring over Integer Ring + sage: icecream + x + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ); A + Asymptotic Ring over Integer Ring + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Names 'y', 'x' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Names 'a', 'b' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Names 'x', 'b' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Name 'x' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Names 'x', 'y', 'z' do not coincide with + generators 'x', 'y' of Growth Group x^ZZ * y^ZZ. + + TESTS:: + + sage: AsymptoticRing(growth_group=None, coefficient_ring=ZZ) + Traceback (most recent call last): + ... + ValueError: Growth group not specified. Cannot continue. + sage: AsymptoticRing(growth_group='x^ZZ', coefficient_ring=None) + Traceback (most recent call last): + ... + ValueError: Coefficient ring not specified. Cannot continue. + sage: AsymptoticRing(growth_group='x^ZZ', coefficient_ring='icecream') + Traceback (most recent call last): + ... + ValueError: icecream is not a ring. Cannot continue. """ + from sage.categories.sets_cat import Sets + from sage.categories.rings import Rings + + Sets_parent_class = Sets().parent_class + while issubclass(cls, Sets_parent_class): + cls = cls.__base__ + if isinstance(growth_group, str): - from sage.rings.asymptotic.growth_group import GrowthGroup + from growth_group import GrowthGroup growth_group = GrowthGroup(growth_group) - if names is not None and not growth_group.gens_monomial(): - raise ValueError("%s does not have a generator." % (growth_group,)) + if growth_group is None: + raise ValueError('Growth group not specified. Cannot continue.') + + if coefficient_ring is None: + raise ValueError('Coefficient ring not specified. Cannot continue.') + if coefficient_ring not in Rings(): + raise ValueError('%s is not a ring. Cannot continue.' % (coefficient_ring,)) + + strgens = tuple(str(g) for g in growth_group.gens_monomial()) + def format_names(N): + return ('s ' if len(N) != 1 else ' ') + ', '.join("'%s'" % n for n in N) + if names and not strgens: + raise ValueError('%s does not provide any generators but name%s given.' % + (growth_group, format_names(names))) + elif names is not None and len(names) == 1 and len(strgens) == 1: + pass + elif names is not None and names != strgens: + raise ValueError('Name%s do not coincide with generator%s of %s.' % + (format_names(names), format_names(strgens), growth_group)) + + if category is None: + from sage.categories.commutative_algebras import CommutativeAlgebras + from sage.categories.rings import Rings + category = CommutativeAlgebras(Rings()) if default_prec is None: from sage.misc.defaults import series_precision @@ -1108,8 +2331,7 @@ def __classcall__(cls, growth_group, coefficient_ring, names=None, @experimental(trac_number=17601) - def __init__(self, growth_group, coefficient_ring, category=None, - default_prec=None): + def __init__(self, growth_group, coefficient_ring, category, default_prec): r""" See :class:`AsymptoticRing` for more information. @@ -1127,52 +2349,12 @@ def __init__(self, growth_group, coefficient_ring, category=None, sage: R3 = AsymptoticRing('x^ZZ') Traceback (most recent call last): ... - TypeError: __classcall__() takes at least 3 arguments (2 given) - - :: - - sage: AsymptoticRing(growth_group=None, coefficient_ring=ZZ) - Traceback (most recent call last): - ... - ValueError: Growth group not specified. Cannot continue. - sage: AsymptoticRing(growth_group='x^ZZ', coefficient_ring=None) - Traceback (most recent call last): - ... ValueError: Coefficient ring not specified. Cannot continue. - sage: AsymptoticRing(growth_group='x^ZZ', coefficient_ring='icecream') - Traceback (most recent call last): - ... - ValueError: icecream is not a ring. Cannot continue. - - :: - - sage: AsymptoticRing('x^ZZ', QQ, category=Posets()) - Traceback (most recent call last): - ... - ValueError: (Category of posets,) is not a subcategory of Category of rings """ - from sage.categories.rings import Rings - - if growth_group is None: - raise ValueError('Growth group not specified. Cannot continue.') - elif coefficient_ring is None: - raise ValueError('Coefficient ring not specified. Cannot continue.') - elif coefficient_ring not in Rings(): - raise ValueError('%s is not a ring. Cannot continue.' % (coefficient_ring,)) - - if category is None: - category = Rings() - else: - if not isinstance(category, tuple): - category = (category,) - if not any(cat.is_subcategory(Rings()) for cat in category): - raise ValueError('%s is not a subcategory of %s' % (category, - Rings())) - self._coefficient_ring_ = coefficient_ring self._growth_group_ = growth_group self._default_prec_ = default_prec - super(AsymptoticRing, self).__init__(base=coefficient_ring, + super(AsymptoticRing, self).__init__(base_ring=coefficient_ring, category=category) @@ -1189,7 +2371,7 @@ def growth_group(self): .. SEEALSO:: - :mod:`sage.rings.asymptotic.growth_group` + :doc:`growth_group` """ return self._growth_group_ @@ -1215,7 +2397,7 @@ def default_prec(self): This is the parameter used to determine how many summands are kept before truncating an infinite series (which occur - when inverting asymptotic expressions). + when inverting asymptotic expansions). EXAMPLES:: @@ -1229,6 +2411,50 @@ def default_prec(self): return self._default_prec_ + def change_parameter(self, **kwds): + r""" + Return an asymptotic ring with a change in one or more of the given parameters. + + INPUT: + + - ``growth_group`` -- (default: ``None``) the new growth group. + + - ``coefficient_ring`` -- (default: ``None``) the new coefficient ring. + + - ``category`` -- (default: ``None``) the new category. + + - ``default_prec`` -- (default: ``None``) the new default precision. + + OUTPUT: + + An asymptotic ring. + + EXAMPLES:: + + sage: A = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) + sage: A.change_parameter(coefficient_ring=QQ) + Asymptotic Ring over Rational Field + + TESTS:: + + sage: A.change_parameter(coefficient_ring=ZZ) is A + True + """ + parameters = ('growth_group', 'coefficient_ring', 'default_prec') + values = dict() + for parameter in parameters: + values[parameter] = kwds.get(parameter, getattr(self, parameter)) + values['category'] = self.category() + if isinstance(values['growth_group'], str): + from growth_group import GrowthGroup + values['growth_group'] = GrowthGroup(values['growth_group']) + if all(values[parameter] is getattr(self, parameter) + for parameter in parameters) and values['category'] is self.category(): + return self + from misc import underlying_class + return underlying_class(self)(**values) + + @staticmethod def _create_empty_summands_(): r""" @@ -1249,14 +2475,60 @@ def _create_empty_summands_(): poset() """ from sage.data_structures.mutable_poset import MutablePoset - from sage.rings.asymptotic.term_monoid import \ - can_absorb, absorption + from term_monoid import can_absorb, absorption return MutablePoset(key=lambda element: element.growth, can_merge=can_absorb, merge=absorption) - def _element_constructor_(self, data, summands=None, simplify=True): + def _create_element_in_extension_(self, term, old_term_parent=None): + r""" + Create an element in an extension of this asymptotic ring which + is chosen according to the input. + + INPUT: + + - ``term`` -- the element data. + + - ``old_term_parent`` -- the parent of ``term`` is compared to this + parent. If both are the same or ``old_parent`` is ``None``, + then the result is an expansion in this (``self``) asymptotic ring. + + OUTPUT: + + An element. + + EXAMPLES:: + + sage: A = AsymptoticRing('z^ZZ', ZZ) + sage: a = next(A.an_element().summands.elements_topological()) + sage: B = AsymptoticRing('z^QQ', QQ) + sage: b = next(B.an_element().summands.elements_topological()) + sage: c = A._create_element_in_extension_(a, a.parent()) + sage: next(c.summands.elements_topological()).parent() + O-Term Monoid z^ZZ with implicit coefficients in Integer Ring + sage: c = A._create_element_in_extension_(b, a.parent()) + sage: next(c.summands.elements_topological()).parent() + O-Term Monoid z^QQ with implicit coefficients in Rational Field + + TESTS:: + + sage: c = A._create_element_in_extension_(b, None) + sage: next(c.summands.elements_topological()).parent() + O-Term Monoid z^QQ with implicit coefficients in Rational Field + """ + if old_term_parent is None or term.parent() is old_term_parent: + parent = self + else: + # Insert an 'if' here once terms can have different + # coefficient rings, as this will be for L-terms. + parent = self.change_parameter( + growth_group=term.parent().growth_group, + coefficient_ring=term.parent().coefficient_ring) + return parent(term, simplify=False, convert=False) + + + def _element_constructor_(self, data, simplify=True, convert=True): r""" Convert a given object to this asymptotic ring. @@ -1265,91 +2537,213 @@ def _element_constructor_(self, data, summands=None, simplify=True): - ``data`` -- an object representing the element to be initialized. - - ``summands`` -- (default: ``None``) if given, then this is - directly passed to the element constructor (i.e., no - conversion is performed). - - ``simplify`` -- (default: ``True``) if set, then the constructed element is simplified (terms are absorbed) automatically. + - ``convert`` -- (default: ``True``) passed on to the element + constructor. If set, then the ``summands`` are converted to + the asymptotic ring (the parent of this expansion). If not, + then the summands are taken as they are. In that case, the + caller must ensure that the parent of the terms is set + correctly. + OUTPUT: An element of this asymptotic ring. - .. NOTE:: - - Either ``data`` or ``summands`` has to be given. If - ``summands`` is specified, then no positional argument - may be passed (except for ``int(0)``). - TESTS:: sage: AR = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) - sage: AR(5) + sage: AR(5) # indirect doctest 5 - sage: AR(3*x^2) + sage: AR(3*x^2) # indirect doctest 3*x^2 sage: x = ZZ['x'].gen(); x.parent() Univariate Polynomial Ring in x over Integer Ring sage: AR(x) x - sage: y = ZZ['y'].gen(); AR(y) + sage: y = ZZ['y'].gen(); AR(y) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Polynomial y is not in + Asymptotic Ring over Integer Ring + > *previous* ValueError: Growth y is not in + Exact Term Monoid x^ZZ with coefficients in Integer Ring. + >> *previous* ValueError: y is not in Growth Group x^ZZ. + + :: + + sage: A = AsymptoticRing(growth_group='p^ZZ', coefficient_ring=QQ) + sage: P.

= QQ[] + sage: A(p) # indirect doctest + p + sage: A(p^11) # indirect doctest + p^11 + sage: A(2*p^11) # indirect doctest + 2*p^11 + sage: A(3*p^4 + 7/3*p - 8) # indirect doctest + 3*p^4 + 7/3*p - 8 + + :: + + sage: S = AsymptoticRing(growth_group='x^ZZ * y^ZZ', coefficient_ring=QQ) + sage: var('x, y') + (x, y) + sage: S(x + y) # indirect doctest + x + y + sage: S(2*x - 4*x*y^6) # indirect doctest + -4*x*y^6 + 2*x + + :: + + sage: A. = AsymptoticRing('a^ZZ * b^ZZ', QQ) + sage: 1/a + a^(-1) + + :: + + sage: P. = ZZ[] + sage: A(a + b) + a + b + sage: A(a + c) Traceback (most recent call last): ... - TypeError: Cannot convert y to an asymptotic expression. + ValueError: Polynomial a + c is not in + Asymptotic Ring over Rational Field + > *previous* ValueError: Growth c is not in + Exact Term Monoid a^ZZ * b^ZZ with coefficients in Rational Field. + >> *previous* ValueError: c is not in Growth Group a^ZZ * b^ZZ. + >...> *previous* ValueError: c is not in any of the factors of + Growth Group a^ZZ * b^ZZ :: - sage: AR(1234, summands=6789) + sage: M = AsymptoticRing('m^ZZ', ZZ) + sage: N = AsymptoticRing('n^ZZ', QQ) + sage: N(M.an_element()) # indirect doctest Traceback (most recent call last): ... - ValueError: Input is ambiguous: 1234 as well as summands=6789 are specified. + ValueError: Cannot include m^3 with parent + Exact Term Monoid m^ZZ with coefficients in Integer Ring + in Asymptotic Ring over Rational Field + > *previous* ValueError: m^3 is not in Growth Group n^ZZ + + :: + + sage: M([1]) # indirect doctest + Traceback (most recent call last): + ... + TypeError: Not all list entries of [1] are asymptotic terms, + so cannot create an asymptotic expansion in + Asymptotic Ring over Integer Ring. + sage: M(SR.var('a') + 1) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Symbolic expression a + 1 is not in + Asymptotic Ring over Integer Ring. + > *previous* ValueError: a is not in + Exact Term Monoid m^ZZ with coefficients in Integer Ring. + >> *previous* ValueError: Factor a of a is neither a coefficient + (in Integer Ring) nor growth (in Growth Group m^ZZ). """ - if summands is not None: - if type(data) != int or data != 0: - raise ValueError('Input is ambiguous: ' - '%s as well as summands=%s ' - 'are specified.' % (data, summands)) - return self.element_class(self, summands, simplify=simplify) + from sage.data_structures.mutable_poset import MutablePoset + if isinstance(data, MutablePoset): + return self.element_class(self, data, simplify=simplify, convert=convert) if type(data) == self.element_class and data.parent() == self: return data - if isinstance(data, AsymptoticExpression): - return self.element_class(self, data.summands, simplify=simplify) + if isinstance(data, AsymptoticExpansion): + return self.element_class(self, data.summands, + simplify=simplify, convert=convert) - from sage.rings.asymptotic.term_monoid import GenericTerm + from term_monoid import GenericTerm if isinstance(data, GenericTerm): data = (data,) if isinstance(data, (list, tuple)): if not all(isinstance(elem, GenericTerm) for elem in data): raise TypeError('Not all list entries of %s ' - 'are asymptotic terms.' % (data,)) + 'are asymptotic terms, so cannot create an ' + 'asymptotic expansion in %s.' % (data, self)) summands = AsymptoticRing._create_empty_summands_() summands.union_update(data) - return self.element_class(self, summands, simplify=simplify) + return self.element_class(self, summands, + simplify=simplify, convert=convert) - if data == 0: + if not data: summands = AsymptoticRing._create_empty_summands_() - return self.element_class(self, summands, simplify=simplify) + return self.element_class(self, summands, + simplify=simplify, convert=False) try: - summand = self.create_summand('exact', growth=data) - except (TypeError, ValueError): - pass - else: - return summand - - try: - coefficient = self.coefficient_ring(data) - except (TypeError, ValueError): - pass - else: - return self.create_summand('exact', growth=1, coefficient=coefficient) - - raise TypeError('Cannot convert %s to an asymptotic ' - 'expression.' % (data,)) + P = data.parent() + except AttributeError: + return self.create_summand('exact', data) + + from misc import combine_exceptions + from sage.symbolic.ring import SymbolicRing + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + from sage.rings.polynomial.multi_polynomial_ring_generic import is_MPolynomialRing + from sage.rings.power_series_ring import is_PowerSeriesRing + + if isinstance(P, SymbolicRing): + from sage.symbolic.operators import add_vararg + if data.operator() == add_vararg: + summands = [] + for summand in data.operands(): + # TODO: check if summand is an O-Term here + # (see #19425, #19426) + try: + summands.append(self.create_summand('exact', summand)) + except ValueError as e: + raise combine_exceptions( + ValueError('Symbolic expression %s is not in %s.' % + (data, self)), e) + return sum(summands) + + elif is_PolynomialRing(P): + p = P.gen() + try: + return sum(self.create_summand('exact', growth=p**i, + coefficient=c) + for i, c in enumerate(data)) + except ValueError as e: + raise combine_exceptions( + ValueError('Polynomial %s is not in %s' % (data, self)), e) + + elif is_MPolynomialRing(P): + try: + return sum(self.create_summand('exact', growth=g, coefficient=c) + for c, g in iter(data)) + except ValueError as e: + raise combine_exceptions( + ValueError('Polynomial %s is not in %s' % (data, self)), e) + + elif is_PowerSeriesRing(P): + raise NotImplementedError( + 'Cannot convert %s from the %s to an asymptotic expansion ' + 'in %s, since growths at other points than +oo are not yet ' + 'supported.' % (data, P, self)) + # Delete lines above as soon as we can deal with growths + # other than the that at going to +oo. + p = P.gen() + try: + result = self(data.polynomial()) + except ValueError as e: + raise combine_exceptions( + ValueError('Powerseries %s is not in %s' % (data, self)), e) + prec = data.precision_absolute() + if prec < sage.rings.infinity.PlusInfinity(): + try: + result += self.create_summand('O', growth=p**prec) + except ValueError as e: + raise combine_exceptions( + ValueError('Powerseries %s is not in %s' % + (data, self)), e) + return result + + return self.create_summand('exact', data) def _coerce_map_from_(self, R): @@ -1398,6 +2792,8 @@ def _coerce_map_from_(self, R): return if self.coefficient_ring.has_coerce_map_from(R): return True + if self.growth_group.has_coerce_map_from(R): + return True elif isinstance(R, AsymptoticRing): if self.growth_group.has_coerce_map_from(R.growth_group) and \ self.coefficient_ring.has_coerce_map_from(R.coefficient_ring): @@ -1440,21 +2836,22 @@ def _an_element_(self): OUTPUT: - An :class:`AsymptoticExpression`. + An :class:`AsymptoticExpansion`. EXAMPLES:: sage: AsymptoticRing(growth_group='z^QQ', coefficient_ring=ZZ).an_element() - -z^(3/2) + O(z^(1/2)) + z^(3/2) + O(z^(1/2)) sage: AsymptoticRing(growth_group='z^ZZ', coefficient_ring=QQ).an_element() - -1/8*z^3 + O(z) + 1/8*z^3 + O(z) sage: AsymptoticRing(growth_group='z^QQ', coefficient_ring=QQ).an_element() - -1/8*z^(3/2) + O(z^(1/2)) + 1/8*z^(3/2) + O(z^(1/2)) """ - from sage.rings.asymptotic.term_monoid import TermMonoid - E = TermMonoid('exact', self.growth_group, self.coefficient_ring) - O = TermMonoid('O', self.growth_group, self.coefficient_ring) - return -self(E.an_element())**3 + self(O.an_element()) + from term_monoid import TermMonoid + E = TermMonoid('exact', asymptotic_ring=self) + O = TermMonoid('O', asymptotic_ring=self) + return self(E.an_element(), simplify=False, convert=False)**3 + \ + self(O.an_element(), simplify=False, convert=False) def some_elements(self): @@ -1476,22 +2873,23 @@ def some_elements(self): sage: from itertools import islice sage: A = AsymptoticRing(growth_group='z^QQ', coefficient_ring=ZZ) sage: tuple(islice(A.some_elements(), 10)) - (-z^(3/2) + O(z^(1/2)), + (z^(3/2) + O(z^(1/2)), O(z^(1/2)), - -z^(3/2) + O(z^(-1/2)), - z^(3/2) + O(z^(1/2)), + z^(3/2) + O(z^(-1/2)), + -z^(3/2) + O(z^(1/2)), O(z^(-1/2)), O(z^2), - -z^6 + O(z^(1/2)), - z^(3/2) + O(z^(-1/2)), + z^6 + O(z^(1/2)), + -z^(3/2) + O(z^(-1/2)), O(z^2), - -z^(3/2) + O(z^(-2))) + z^(3/2) + O(z^(-2))) """ from sage.misc.mrange import cantor_product - from sage.rings.asymptotic.term_monoid import TermMonoid - E = TermMonoid('exact', self.growth_group, self.coefficient_ring) - O = TermMonoid('O', self.growth_group, self.coefficient_ring) - return iter(-self(e)**3 + self(o) + from term_monoid import TermMonoid + E = TermMonoid('exact', asymptotic_ring=self) + O = TermMonoid('O', asymptotic_ring=self) + return iter(self(e, simplify=False, convert=False)**3 + + self(o, simplify=False, convert=False) for e, o in cantor_product( E.some_elements(), O.some_elements())) @@ -1506,7 +2904,7 @@ def gens(self): OUTPUT: - A tuple of asymptotic expressions. + A tuple of asymptotic expansions. .. NOTE:: @@ -1521,10 +2919,14 @@ def gens(self): sage: AR. = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) sage: AR.gens() (x,) + sage: B. = AsymptoticRing(growth_group='y^ZZ * z^ZZ', coefficient_ring=QQ) + sage: B.gens() + (y, z) """ - from sage.rings.asymptotic.growth_group import MonomialGrowthGroup - if isinstance(self.growth_group, MonomialGrowthGroup): - return self.create_summand('exact', growth=self.growth_group.gen(), coefficient=1), + return tuple(self.create_summand('exact', + growth=g, + coefficient=self.coefficient_ring(1)) + for g in self.growth_group.gens_monomial()) def gen(self, n=0): @@ -1537,7 +2939,7 @@ def gen(self, n=0): OUTPUT: - An asymptotic expression. + An asymptotic expansion. EXAMPLES:: @@ -1566,29 +2968,32 @@ def ngens(self): sage: AR.ngens() 1 """ - from sage.rings.asymptotic.growth_group import MonomialGrowthGroup - if isinstance(self.growth_group, MonomialGrowthGroup): - return 1 - else: - return 0 + return len(self.growth_group.gens_monomial()) - def create_summand(self, type, growth, **kwds): + def create_summand(self, type, data=None, **kwds): r""" - Create a simple asymptotic expression consisting of a single + Create a simple asymptotic expansion consisting of a single summand. INPUT: - ``type`` -- 'O' or 'exact'. + - ``data`` -- the element out of which a summand has to be created. + - ``growth`` -- an element of the :meth:`growth_group`. - ``coefficient`` -- an element of the :meth:`coefficient_ring`. + .. NOTE:: + + Either ``growth`` and ``coefficient`` or ``data`` have to + be specified. + OUTPUT: - An asymptotic expression. + An asymptotic expansion. .. NOTE:: @@ -1599,15 +3004,292 @@ def create_summand(self, type, growth, **kwds): EXAMPLES:: sage: R = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ) - sage: R.create_summand('O', growth=x^2) + sage: R.create_summand('O', x^2) O(x^2) sage: R.create_summand('exact', growth=x^456, coefficient=123) 123*x^456 + sage: R.create_summand('exact', data=12*x^13) + 12*x^13 + + TESTS:: + + sage: R.create_summand('exact', data='12*x^13') + 12*x^13 + sage: R.create_summand('exact', data='x^13 * 12') + 12*x^13 + sage: R.create_summand('exact', data='x^13') + x^13 + sage: R.create_summand('exact', data='12') + 12 + sage: R.create_summand('exact', data=12) + 12 + + :: + + sage: R.create_summand('O', growth=42*x^2, coefficient=1) + Traceback (most recent call last): + ... + ValueError: Growth 42*x^2 is not in O-Term Monoid x^ZZ with implicit coefficients in Integer Ring. + > *previous* ValueError: 42*x^2 is not in Growth Group x^ZZ. + + :: + + sage: AR. = AsymptoticRing('z^QQ', QQ) + sage: AR.create_summand('exact', growth='z^2') + Traceback (most recent call last): + ... + TypeError: Cannot create exact term: only 'growth' but + no 'coefficient' specified. """ - from sage.rings.asymptotic.term_monoid import TermMonoid - TM = TermMonoid(type, self.growth_group, self.coefficient_ring) + from term_monoid import TermMonoid + TM = TermMonoid(type, asymptotic_ring=self) + + if data is None: + try: + data = kwds.pop('growth') + except KeyError: + raise TypeError("Neither 'data' nor 'growth' are specified.") + if type == 'exact' and kwds.get('coefficient') is None: + raise TypeError("Cannot create exact term: only 'growth' " + "but no 'coefficient' specified.") + if type == 'exact' and kwds.get('coefficient') == 0: - return self(kwds['coefficient']) + return self.zero() + + return self(TM(data, **kwds), simplify=False, convert=False) + + + def variable_names(self): + r""" + Return the names of the variables. + + OUTPUT: + + A tuple of strings. + + EXAMPLES:: + + sage: A = AsymptoticRing(growth_group='x^ZZ * QQ^y', coefficient_ring=QQ) + sage: A.variable_names() + ('x', 'y') + """ + return self.growth_group.variable_names() + + + def construction(self): + r""" + Return the construction of this asymptotic ring. + + OUTPUT: + + A pair whose first entry is an + :class:`asymptotic ring construction functor ` + and its second entry the coefficient ring. + + EXAMPLES:: + + sage: A = AsymptoticRing(growth_group='x^ZZ * QQ^y', coefficient_ring=QQ) + sage: A.construction() + (AsymptoticRing, Rational Field) + + .. SEEALSO:: + + :doc:`asymptotic_ring`, + :class:`AsymptoticRing`, + :class:`AsymptoticRingFunctor`. + """ + return AsymptoticRingFunctor(self.growth_group), self.coefficient_ring + + +from sage.categories.pushout import ConstructionFunctor +class AsymptoticRingFunctor(ConstructionFunctor): + r""" + A :class:`construction functor ` + for :class:`asymptotic rings `. + + INPUT: + + - ``growth_group`` -- a partially ordered group (see + :class:`AsymptoticRing` or + :doc:`growth_group` for details). + + EXAMPLES:: + + sage: AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ).construction() # indirect doctest + (AsymptoticRing, Rational Field) + + .. SEEALSO:: + + :doc:`asymptotic_ring`, + :class:`AsymptoticRing`, + :class:`sage.rings.asymptotic.growth_group.AbstractGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.growth_group.ExponentialGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.growth_group.MonomialGrowthGroupFunctor`, + :class:`sage.categories.pushout.ConstructionFunctor`. + + TESTS:: + + sage: X = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: Y = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=QQ) + sage: cm = sage.structure.element.get_coercion_model() + sage: cm.record_exceptions() + sage: cm.common_parent(X, Y) + Asymptotic Ring over Rational Field + sage: sage.structure.element.coercion_traceback() # not tested + + :: + + sage: from sage.categories.pushout import pushout + sage: pushout(AsymptoticRing(growth_group='x^ZZ', coefficient_ring=ZZ), QQ) + Asymptotic Ring over Rational Field + """ + + rank = 13 + + + def __init__(self, growth_group): + r""" + See :class:`AsymptoticRingFunctor` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.asymptotic_ring import AsymptoticRingFunctor + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: AsymptoticRingFunctor(GrowthGroup('x^ZZ')) + AsymptoticRing + """ + self.growth_group = growth_group + + from sage.categories.rings import Rings + super(ConstructionFunctor, self).__init__( + Rings(), Rings()) + + + def _repr_(self): + r""" + Return a representation string of this functor. + + OUTPUT: + + A string. + + TESTS:: + + sage: AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ).construction()[0] # indirect doctest + AsymptoticRing + """ + return 'AsymptoticRing<%s>' % (self.growth_group._repr_(condense=True),) + + + def _apply_functor(self, coefficient_ring): + r""" + Apply this functor to the given ``coefficient_ring``. + + INPUT: + + - ``base`` - anything :class:`~sage.rings.asymptotic.growth_group.MonomialGrowthGroup` accepts. + + OUTPUT: + + An :class:`AsymptoticRing`. + + EXAMPLES:: + + sage: A = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: F, C = A.construction() + sage: F(C) # indirect doctest + Asymptotic Ring over Rational Field + """ + return AsymptoticRing(growth_group=self.growth_group, + coefficient_ring=coefficient_ring) + + + def merge(self, other): + r""" + Merge this functor with ``other`` if possible. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A functor or ``None``. + + EXAMPLES:: + + sage: X = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: Y = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=QQ) + sage: F_X = X.construction()[0] + sage: F_Y = Y.construction()[0] + sage: F_X.merge(F_X) + AsymptoticRing + sage: F_X.merge(F_Y) + AsymptoticRing + """ + if self == other: + return self + + if isinstance(other, AsymptoticRingFunctor): + from sage.structure.element import get_coercion_model + cm = get_coercion_model() + try: + G = cm.common_parent(self.growth_group, other.growth_group) + except TypeError: + pass + else: + return AsymptoticRingFunctor(G) - return self(TM(growth, **kwds)) + + def __eq__(self, other): + r""" + Return whether this functor is equal to ``other``. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: X = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: Y = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=QQ) + sage: F_X = X.construction()[0] + sage: F_Y = Y.construction()[0] + sage: F_X == F_X + True + sage: F_X == F_Y + False + """ + return type(self) == type(other) and \ + self.growth_group == other.growth_group + + + def __ne__(self, other): + r""" + Return whether this functor is not equal to ``other``. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: X = AsymptoticRing(growth_group='x^ZZ', coefficient_ring=QQ) + sage: Y = AsymptoticRing(growth_group='y^ZZ', coefficient_ring=QQ) + sage: F_X = X.construction()[0] + sage: F_Y = Y.construction()[0] + sage: F_X != F_X + False + sage: F_X != F_Y + True + """ + return not self.__eq__(other) diff --git a/src/sage/rings/asymptotic/growth_group.py b/src/sage/rings/asymptotic/growth_group.py index af0ffe1c518..ac2f3517f9c 100644 --- a/src/sage/rings/asymptotic/growth_group.py +++ b/src/sage/rings/asymptotic/growth_group.py @@ -1,28 +1,17 @@ r""" (Asymptotic) Growth Groups -This module adds support for (asymptotic) growth groups. Such groups -are equipped with a partial order: the elements can be seen as -functions, and their behavior as the argument(s) get large (tend to -`\infty`) is compared. +This module provides support for (asymptotic) growth groups. -Besides an abstract base class :class:`GenericGrowthGroup`, this module -contains concrete realizations of growth groups. At the moment there -is +Such groups are equipped with a partial order: the elements can be +seen as functions, and the behavior as their argument (or arguments) +gets large (tend to `\infty`) is compared. -- :class:`MonomialGrowthGroup` (whose elements are powers of a fixed symbol). +Growth groups are used for the calculations done in the +:doc:`asymptotic ring `. There, take a look at the +:ref:`informal definition `, where +examples of growth groups and elements are given as well. -More complex growth groups can be constructed via cartesian products -(to be implemented). - -These growth groups are used behind the scenes when performing -calculations in an asymptotic ring (to be implemented). - -AUTHORS: - -- Benjamin Hackl (2015-01): initial version -- Daniel Krenn (2015-05-29): initial version and review -- Benjamin Hackl (2015-07): short representation strings .. WARNING:: @@ -32,696 +21,2862 @@ TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ); G + sage: from sage.rings.asymptotic.growth_group import \ + ....: GenericGrowthGroup, GrowthGroup + sage: GenericGrowthGroup(ZZ) doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. See http://trac.sagemath.org/17601 for details. Growth Group Generic(ZZ) - sage: G = agg.MonomialGrowthGroup(ZZ, 'x'); G + sage: GrowthGroup('x^ZZ * log(x)^ZZ') doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. See http://trac.sagemath.org/17601 for details. - Growth Group x^ZZ -""" + Growth Group x^ZZ * log(x)^ZZ -#***************************************************************************** -# Copyright (C) 2014--2015 Benjamin Hackl -# 2014--2015 Daniel Krenn -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +.. _growth_group_description: -import sage +Description of Growth Groups +============================ -def repr_short_to_parent(s): - r""" - Helper method for the growth group factory, which converts a short - representation string to a parent. +Many growth groups can be described by a string, which can also be used to +create them. For example, the string ``'x^QQ * log(x)^ZZ * QQ^y * y^QQ'`` +represents a growth group with the following properties: - INPUT: +- It is a growth group in the two variables `x` and `y`. - - ``s`` -- a string, short representation of a parent. +- Its elements are of the form - OUTPUT: + .. MATH:: - A parent. + x^r \cdot \log(x)^s \cdot a^y \cdot y^q - The possible short representations are shown in the examples below. + for `r\in\QQ`, `s\in\ZZ`, `a\in\QQ` and `q\in\QQ`. - EXAMPLES:: +- The order is with respect to `x\to\infty` and `y\to\infty` independently + of each other. - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.repr_short_to_parent('ZZ') - Integer Ring - sage: agg.repr_short_to_parent('QQ') - Rational Field - sage: agg.repr_short_to_parent('SR') - Symbolic Ring +- To compare such elements, they are split into parts belonging to + only one variable. In the example above, - TESTS:: + .. MATH:: - sage: agg.repr_short_to_parent('abcdef') - Traceback (most recent call last): - ... - ValueError: Cannot create a parent out of 'abcdef'. - """ - if s == 'ZZ': - return sage.rings.integer_ring.ZZ - elif s == 'QQ': - return sage.rings.rational_field.QQ - elif s == 'SR': - return sage.symbolic.ring.SR - else: - raise ValueError("Cannot create a parent out of '%s'." % (s,)) + x^{r_1} \cdot \log(x)^{s_1} \leq x^{r_2} \cdot \log(x)^{s_2} + if `(r_1, s_1) \leq (r_2, s_2)` lexicographically. This reflects the fact + that elements `x^r` are larger than elements `\log(x)^s` as `x\to\infty`. + The factors belonging to the variable `y` are compared analogously. -def parent_to_repr_short(P): - r""" - Helper method which generates a short(er) representation string - out of a parent. + The results of these comparisons are then put together using the + :wikipedia:`product order `, i.e., `\leq` if each component + satisfies `\leq`. - INPUT: - - ``P`` -- a parent. +Each description string consists of ordered factors---yes, this means +``*`` is noncommutative---of strings describing "elementary" growth +groups (see the examples below). As stated in the example above, these +factors are split by their variable; factors with the same variable are +grouped. Reading such factors from left to right determines the order: +Comparing elements of two factors (growth groups) `L` and `R`, then all +elements of `L` are considered to be larger than each element of `R`. - OUTPUT: - A string. +.. _growth_group_creating: - EXAMPLES:: +Creating a Growth Group +======================= - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.parent_to_repr_short(ZZ) - 'ZZ' - sage: agg.parent_to_repr_short(QQ) - 'QQ' - sage: agg.parent_to_repr_short(SR) - 'SR' - sage: agg.parent_to_repr_short(ZZ[x]) - '(Univariate Polynomial Ring in x over Integer Ring)' - """ - if P is sage.rings.integer_ring.ZZ: - return 'ZZ' - elif P is sage.rings.rational_field.QQ: - return 'QQ' - elif P is sage.symbolic.ring.SR: - return 'SR' - else: - rep = repr(P) - if ' ' in rep: - rep = '(' + rep + ')' - return rep +For many purposes the factory ``GrowthGroup`` (see +:class:`GrowthGroupFactory`) is the most convenient way to generate a +growth group. +:: + sage: from sage.rings.asymptotic.growth_group import GrowthGroup -class GenericGrowthElement(sage.structure.element.MultiplicativeGroupElement): - r""" - A basic implementation of a generic growth element. +Here are some examples:: - Growth elements form a group by multiplication, and (some of) the - elements can be compared to each other, i.e., all elements form a - poset. + sage: GrowthGroup('z^ZZ') + Growth Group z^ZZ + sage: M = GrowthGroup('z^QQ'); M + Growth Group z^QQ - INPUT: +Each of these two generated groups is a :class:`MonomialGrowthGroup`, +whose elements are powers of a fixed symbol (above ``'z'``). +For the order of the elements it is assumed that `z\to\infty`. - - ``parent`` -- a :class:`GenericGrowthGroup`. +.. NOTE:: - - ``raw_element`` -- an element from the base of the parent. + Growth groups where the variable tend to some value distinct from + `\infty` are not yet implemented. - EXAMPLES:: +To create elements of `M`, a generator can be used:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: g = agg.GenericGrowthElement(G, 42); g - GenericGrowthElement(42) - sage: g.parent() - Growth Group Generic(ZZ) - sage: G(raw_element=42) == g - True - """ + sage: z = M.gen() + sage: z^(3/5) + z^(3/5) - def __init__(self, parent, raw_element): - r""" - See :class:`GenericGrowthElement` for more information. +Strings can also be parsed:: - EXAMPLES:: + sage: M('z^7') + z^7 - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: G(raw_element=42) - GenericGrowthElement(42) +Similarly, we can construct logarithmic factors by:: - TESTS:: + sage: GrowthGroup('log(z)^QQ') + Growth Group log(z)^QQ - sage: G(raw_element=42).category() - Category of elements of Growth Group Generic(ZZ) +which again creates a +:class:`MonomialGrowthGroup`. An :class:`ExponentialGrowthGroup` is generated in the same way. Our factory gives +:: - :: + sage: E = GrowthGroup('QQ^z'); E + Growth Group QQ^z - sage: agg.GenericGrowthElement(None, 0) - Traceback (most recent call last): - ... - ValueError: The parent must be provided - """ - if parent is None: - raise ValueError('The parent must be provided') - super(GenericGrowthElement, self).__init__(parent=parent) +and a typical element looks like this:: - self._raw_element_ = parent.base()(raw_element) + sage: E.an_element() + (1/2)^z +More complex groups are created in a similar fashion. For example +:: - def _repr_(self): - r""" - A representation string for this generic element. + sage: C = GrowthGroup('QQ^z * z^QQ * log(z)^QQ'); C + Growth Group QQ^z * z^QQ * log(z)^QQ - INPUT: +This contains elements of the form +:: - Nothing. + sage: C.an_element() + (1/2)^z*z^(1/2)*log(z)^(1/2) - OUTPUT: +The group `C` itself is a cartesian product; to be precise a +:class:`~sage.rings.asymptotic.growth_group_cartesian.UnivariateProduct`. We +can see its factors:: - A string. + sage: C.cartesian_factors() + (Growth Group QQ^z, Growth Group z^QQ, Growth Group log(z)^QQ) - EXAMPLES:: +Multivariate constructions are also possible:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: G(raw_element=42) # indirect doctest - GenericGrowthElement(42) - """ - return 'GenericGrowthElement(%s)' % (self._raw_element_,) + sage: GrowthGroup('x^QQ * y^QQ') + Growth Group x^QQ * y^QQ +This gives a +:class:`~sage.rings.asymptotic.growth_group_cartesian.MultivariateProduct`. - def __hash__(self): - r""" - Return the hash of this element. +Both these cartesian products are derived from the class +:class:`~sage.rings.asymptotic.growth_group_cartesian.GenericProduct`. Moreover +all growth groups have the abstract base class +:class:`GenericGrowthGroup` in common. - INPUT: +Some Examples +^^^^^^^^^^^^^ - Nothing. +:: - OUTPUT: + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_x = GrowthGroup('x^ZZ'); G_x + Growth Group x^ZZ + sage: G_xy = GrowthGroup('x^ZZ * y^ZZ'); G_xy + Growth Group x^ZZ * y^ZZ + sage: G_xy.an_element() + x*y + sage: x = G_xy('x'); y = G_xy('y') + sage: x^2 + x^2 + sage: elem = x^21*y^21; elem^2 + x^42*y^42 - An integer. +A monomial growth group itself is totally ordered, all elements +are comparable. However, this does **not** hold for cartesian +products:: - EXAMPLES:: + sage: e1 = x^2*y; e2 = x*y^2 + sage: e1 <= e2 or e2 <= e1 + False - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ); - sage: hash(G(raw_element=42)) # random - 5656565656565656 - """ - return hash((self.parent(), self._raw_element_)) +In terms of uniqueness, we have the following behaviour:: + sage: GrowthGroup('x^ZZ * y^ZZ') is GrowthGroup('y^ZZ * x^ZZ') + True - def _mul_(self, other): - r""" - Abstract multiplication method for generic elements. +The above is ``True`` since the order of the factors does not play a role here; they use different variables. But when using the same variable, it plays a role:: - INPUT: + sage: GrowthGroup('x^ZZ * log(x)^ZZ') is GrowthGroup('log(x)^ZZ * x^ZZ') + False - - ``other`` -- a :class:`GenericGrowthElement`. +In this case the components are ordered lexicographically, which +means that in the second growth group, ``log(x)`` is assumed to +grow faster than ``x`` (which is nonsense, mathematically). See +:class:`CartesianProduct ` +for more details or see :ref:`above ` +for a more extensive description. - OUTPUT: +Short notation also allows the construction of more complicated +growth groups:: - A :class:`GenericGrowthElement` representing the product with - ``other``. + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^QQ * y^QQ') + sage: G.an_element() + (1/2)^x*x*log(x)^(1/2)*y^(1/2) + sage: x, y = var('x y') + sage: G(2^x * log(x) * y^(1/2)) * G(x^(-5) * 5^x * y^(1/3)) + 10^x*x^(-5)*log(x)*y^(5/6) - .. NOTE:: +AUTHORS: - Inherited classes must override this. +- Benjamin Hackl (2015) +- Daniel Krenn (2015) - EXAMPLES:: +ACKNOWLEDGEMENT: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: g = G.an_element() - sage: g * g - Traceback (most recent call last): - ... - NotImplementedError: Only implemented in concrete realizations. - """ - raise NotImplementedError('Only implemented in concrete realizations.') +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. +- Benjamin Hackl is supported by the Google Summer of Code 2015. - def __invert__(self): - r""" - Return the inverse of this growth element. +Classes and Methods +=================== +""" - OUTPUT: +#***************************************************************************** +# Copyright (C) 2014--2015 Benjamin Hackl +# 2014--2015 Daniel Krenn +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** - An instance of :class:`GenericGrowthElement`. +import sage +from sage.misc.lazy_import import lazy_import +lazy_import('sage.rings.asymptotic.growth_group_cartesian', 'CartesianProductGrowthGroups') - TESTS:: - sage: from sage.rings.asymptotic.growth_group import GrowthGroup - sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup - sage: G = GenericGrowthGroup(ZZ) - sage: ~G.an_element() - Traceback (most recent call last): - ... - NotImplementedError: Inversion of GenericGrowthElement(1) not implemented - (in this abstract method). - sage: G.an_element()^7 - Traceback (most recent call last): - ... - NotImplementedError: Only implemented in concrete realizations. - sage: P = GrowthGroup('x^ZZ') - sage: ~P.an_element() - 1/x - """ - raise NotImplementedError('Inversion of %s not implemented ' - '(in this abstract method).' % (self,)) +class Variable(sage.structure.unique_representation.CachedRepresentation, + sage.structure.sage_object.SageObject): + r""" + A class managing the variable of a growth group. + INPUT: - def __eq__(self, other): - r""" - Return if this growth element is equal to ``other``. + - ``var`` -- an object whose representation string is used as the + variable. It has to be a valid Python identifier. ``var`` can + also be a tuple (or other iterable) of such objects. - INPUT: + - ``repr`` -- (default: ``None``) if specified, then this string + will be displayed instead of ``var``. Use this to get + e.g. ``log(x)^ZZ``: ``var`` is then used to specify the variable `x`. - - ``other`` -- an element. + - ``ignore`` -- (default: ``None``) a tuple (or other iterable) + of strings which are not variables. - OUTPUT: + TESTS:: - A boolean. + sage: from sage.rings.asymptotic.growth_group import Variable + sage: v = Variable('x'); repr(v), v.variable_names() + ('x', ('x',)) + sage: v = Variable('x1'); repr(v), v.variable_names() + ('x1', ('x1',)) + sage: v = Variable('x_42'); repr(v), v.variable_names() + ('x_42', ('x_42',)) + sage: v = Variable(' x'); repr(v), v.variable_names() + ('x', ('x',)) + sage: v = Variable('x '); repr(v), v.variable_names() + ('x', ('x',)) + sage: v = Variable(''); repr(v), v.variable_names() + ('', ()) + + :: + + sage: v = Variable(('x', 'y')); repr(v), v.variable_names() + ('x, y', ('x', 'y')) + sage: v = Variable(('x', 'log(y)')); repr(v), v.variable_names() + ('x, log(y)', ('x', 'y')) + sage: v = Variable(('x', 'log(x)')); repr(v), v.variable_names() + Traceback (most recent call last): + ... + ValueError: Variable names ('x', 'x') are not pairwise distinct. - .. NOTE:: + :: - This function uses the coercion model to find a common - parent for the two operands. + sage: v = Variable('log(x)'); repr(v), v.variable_names() + ('log(x)', ('x',)) + sage: v = Variable('log(log(x))'); repr(v), v.variable_names() + ('log(log(x))', ('x',)) - The comparison of two elements with the same parent is done in - :meth:`_eq_`. + :: - EXAMPLES:: + sage: v = Variable('x', repr='log(x)'); repr(v), v.variable_names() + ('log(x)', ('x',)) - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: G.an_element() == G.an_element() - True - sage: G(raw_element=42) == G(raw_element=7) - False + :: - :: + sage: v = Variable('e^x', ignore=('e',)); repr(v), v.variable_names() + ('e^x', ('x',)) - sage: G_ZZ = agg.GenericGrowthGroup(ZZ) - sage: G_QQ = agg.GenericGrowthGroup(QQ) - sage: G_ZZ(raw_element=1) == G_QQ(raw_element=1) + :: + + sage: v = Variable('(e^n)', ignore=('e',)); repr(v), v.variable_names() + ('e^n', ('n',)) + sage: v = Variable('(e^(n*log(n)))', ignore=('e',)); repr(v), v.variable_names() + ('e^(n*log(n))', ('n',)) + """ + def __init__(self, var, repr=None, ignore=None): + r""" + See :class:`Variable` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('blub') + blub + sage: Variable('blub') is Variable('blub') True :: - sage: P_ZZ = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P_QQ = agg.MonomialGrowthGroup(QQ, 'x') - sage: P_ZZ.gen() == P_QQ.gen() - True - sage: ~P_ZZ.gen() == P_ZZ.gen() - False - sage: ~P_ZZ(1) == P_ZZ(1) - True + sage: Variable('(:-)') + Traceback (most recent call last): + ... + TypeError: Malformed expression: : !!! - + sage: Variable('(:-)', repr='icecream') + Traceback (most recent call last): + ... + ValueError: ':-' is not a valid name for a variable. """ - from sage.structure.element import have_same_parent - if have_same_parent(self, other): - return self._eq_(other) + from sage.symbolic.ring import isidentifier + from misc import split_str_by_op + + if not isinstance(var, (list, tuple)): + var = (var,) + var = tuple(''.join(split_str_by_op(str(v), None)) for v in var) # we strip off parentheses + + if ignore is None: + ignore = tuple() + + if repr is None: + var_bases = tuple(i for i in sum(iter( + self.extract_variable_names(v) + if not isidentifier(v) else (v,) + for v in var), tuple()) if i not in ignore) + var_repr = ', '.join(var) + else: + for v in var: + if not isidentifier(v): + raise ValueError("'%s' is not a valid name for a variable." % (v,)) + var_bases = var + var_repr = str(repr).strip() - from sage.structure.element import get_coercion_model - import operator - try: - return get_coercion_model().bin_op(self, other, operator.eq) - except TypeError: - return False + if len(var_bases) != len(set(var_bases)): + raise ValueError('Variable names %s are not pairwise distinct.' % + (var_bases,)) + self.var_bases = var_bases + self.var_repr = var_repr - def _eq_(self, other): + def __hash__(self): + r""" + Return the hash of this variable. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: hash(Variable('blub')) # random + -123456789 + """ + return hash((self.var_repr,) + self.var_bases) + + + def __eq__(self, other): r""" - Return if this :class:`GenericGrowthElement` is equal to ``other``. + Compare whether this variable equals ``other``. INPUT: - - ``other`` -- a :class:`GenericGrowthElement`. + - ``other`` -- another variable. OUTPUT: A boolean. - .. NOTE:: - - This function compares two instances of - :class:`GenericGrowthElement`. - - EXAMPLES:: + TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: e1 = P(raw_element=1) - sage: e1._eq_(P.gen()) - True - sage: e2 = e1^4 - sage: e2 == e1^2 * e1 * e1 + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x') == Variable('x') True - sage: e2 == e1 + sage: Variable('x') == Variable('y') False """ - return self._raw_element_ == other._raw_element_ + return self.var_repr == other.var_repr and self.var_bases == other.var_bases - def __le__(self, other): + def __ne__(self, other): r""" - Return if this growth element is at most (less than or equal - to) ``other``. + Return whether this variable does not equal ``other``. INPUT: - - ``other`` -- an element. + - ``other`` -- another variable. OUTPUT: A boolean. - .. NOTE:: + TESTS:: - This function uses the coercion model to find a common - parent for the two operands. + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x') != Variable('x') + False + sage: Variable('x') != Variable('y') + True + """ + return not self == other - The comparison of two elements with the same parent is done in - :meth:`_le_`. - EXAMPLES:: + def _repr_(self): + r""" + Return a representation string of this variable. - sage: import sage.rings.asymptotic.growth_group as agg - sage: P_ZZ = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P_QQ = agg.MonomialGrowthGroup(QQ, 'x') - sage: P_ZZ.gen() <= P_QQ.gen()^2 - True - sage: ~P_ZZ.gen() <= P_ZZ.gen() - True - """ - from sage.structure.element import have_same_parent - if have_same_parent(self, other): - return self._le_(other) + TESTS:: - from sage.structure.element import get_coercion_model - import operator - try: - return get_coercion_model().bin_op(self, other, operator.le) - except TypeError: - return False + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('blub') # indirect doctest + blub + """ + return self.var_repr - def _le_(self, other): + def variable_names(self): r""" - Return if this :class:`GenericGrowthElement` is at most (less - than or equal to) ``other``. + Return the names of the variables. - INPUT: + OUTPUT: - - ``other`` -- a :class:`GenericGrowthElement`. + A tuple of strings. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x').variable_names() + ('x',) + sage: Variable('log(x)').variable_names() + ('x',) + """ + return self.var_bases + + + def is_monomial(self): + r""" + Return whether this is a monomial variable. OUTPUT: A boolean. + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x').is_monomial() + True + sage: Variable('log(x)').is_monomial() + False + """ + return len(self.var_bases) == 1 and self.var_bases[0] == self.var_repr + + + @staticmethod + def extract_variable_names(s): + r""" + Determine the name of the variable for the given string. + + INPUT: + + - ``s`` -- a string. + + OUTPUT: + + A tuple of strings. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable.extract_variable_names('') + () + sage: Variable.extract_variable_names('x') + ('x',) + sage: Variable.extract_variable_names('exp(x)') + ('x',) + sage: Variable.extract_variable_names('sin(cos(ln(x)))') + ('x',) + + :: + + sage: Variable.extract_variable_names('log(77w)') + ('w',) + sage: Variable.extract_variable_names('log(x') + Traceback (most recent call last): + .... + TypeError: Bad function call: log(x !!! + sage: Variable.extract_variable_names('x)') + Traceback (most recent call last): + .... + TypeError: Malformed expression: x) !!! + sage: Variable.extract_variable_names('log)x(') + Traceback (most recent call last): + .... + TypeError: Malformed expression: log) !!! x( + sage: Variable.extract_variable_names('log(x)+y') + ('x', 'y') + sage: Variable.extract_variable_names('icecream(summer)') + ('summer',) + + :: + + sage: Variable.extract_variable_names('a + b') + ('a', 'b') + sage: Variable.extract_variable_names('a+b') + ('a', 'b') + sage: Variable.extract_variable_names('a +b') + ('a', 'b') + sage: Variable.extract_variable_names('+a') + ('a',) + sage: Variable.extract_variable_names('a+') + Traceback (most recent call last): + ... + TypeError: Malformed expression: a+ !!! + sage: Variable.extract_variable_names('b!') + ('b',) + sage: Variable.extract_variable_names('-a') + ('a',) + sage: Variable.extract_variable_names('a*b') + ('a', 'b') + sage: Variable.extract_variable_names('2^q') + ('q',) + sage: Variable.extract_variable_names('77') + () + + :: + + sage: Variable.extract_variable_names('a + (b + c) + d') + ('a', 'b', 'c', 'd') + """ + from sage.symbolic.ring import SR + if s == '': + return () + return tuple(str(s) for s in SR(s).variables()) + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this variable. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import Variable + sage: Variable('x^2')._substitute_({'x': SR.var('z')}) + z^2 + sage: _.parent() + Symbolic Ring + + :: + + sage: Variable('1/x')._substitute_({'x': 'z'}) + Traceback (most recent call last): + ... + TypeError: Cannot substitute in 1/x in + . + > *previous* TypeError: unsupported operand parent(s) for '/': + 'Integer Ring' and '' + sage: Variable('1/x')._substitute_({'x': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in 1/x in + . + > *previous* ZeroDivisionError: rational division by zero + """ + from sage.misc.sage_eval import sage_eval + try: + return sage_eval(self.var_repr, locals=rules) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + +# The following function is used in the classes GenericGrowthElement and +# GenericProduct.Element as a method. +def _is_lt_one_(self): + r""" + Return whether this element is less than `1`. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ'); x = G(x) + sage: (x^42).is_lt_one() # indirect doctest + False + sage: (x^(-42)).is_lt_one() # indirect doctest + True + """ + one = self.parent().one() + return self <= one and self != one + + +# The following function is used in the classes GenericGrowthElement and +# GenericProduct.Element as a method. +def _log_(self, base=None): + r""" + Return the logarithm of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ') + sage: x, = G.gens_monomial() + sage: log(x) # indirect doctest + log(x) + sage: log(x^5) # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: When calculating log(x^5) a factor 5 != 1 appeared, + which is not contained in Growth Group x^ZZ * log(x)^ZZ. + + :: + + sage: G = GrowthGroup('QQ^x * x^ZZ') + sage: x, = G.gens_monomial() + sage: el = x.rpow(2); el + 2^x + sage: log(el) # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: When calculating log(2^x) a factor log(2) != 1 + appeared, which is not contained in Growth Group QQ^x * x^ZZ. + sage: log(el, base=2) # indirect doctest + x + + :: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: x = GenericGrowthGroup(ZZ).an_element() + sage: log(x) # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Cannot determine logarithmized factorization of + GenericGrowthElement(1) in abstract base class. + + :: + + sage: x = GrowthGroup('x^ZZ').an_element() + sage: log(x) # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: Cannot build log(x) since log(x) is not in + Growth Group x^ZZ. + + TESTS:: + + sage: G = GrowthGroup("(e^x)^QQ * x^ZZ") + sage: x, = G.gens_monomial() + sage: log(exp(x)) # indirect doctest + x + sage: G.one().log() # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: log(1) is zero, which is not contained in + Growth Group (e^x)^QQ * x^ZZ. + + :: + + sage: G = GrowthGroup("(e^x)^ZZ * x^ZZ") + sage: x, = G.gens_monomial() + sage: log(exp(x)) # indirect doctest + x + sage: G.one().log() # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: log(1) is zero, which is not contained in + Growth Group (e^x)^ZZ * x^ZZ. + + :: + + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ') + sage: x, y = G.gens_monomial() + sage: (x * y).log() # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: Calculating log(x*y) results in a sum, + which is not contained in + Growth Group QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ. + """ + from misc import log_string + + log_factor = self.log_factor(base=base) + if not log_factor: + raise ArithmeticError('%s is zero, ' + 'which is not contained in %s.' % + (log_string(self, base), self.parent())) + + if len(log_factor) != 1: + raise ArithmeticError('Calculating %s results in a sum, ' + 'which is not contained in %s.' % + (log_string(self, base), self.parent())) + g, c = log_factor[0] + if c != 1: + raise ArithmeticError('When calculating %s a factor %s != 1 ' + 'appeared, which is not contained in %s.' % + (log_string(self, base), c, self.parent())) + return g + + +# The following function is used in the classes GenericGrowthElement and +# GenericProduct.Element as a method. +def _log_factor_(self, base=None): + r""" + Return the logarithm of the factorization of this + element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is a growth + element and the second a multiplicative coefficient. + + ALGORITHM: + + This function factors the given element and calculates + the logarithm of each of these factors. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ') + sage: x, y = G.gens_monomial() + sage: (x * y).log_factor() # indirect doctest + ((log(x), 1), (log(y), 1)) + sage: (x^123).log_factor() # indirect doctest + ((log(x), 123),) + sage: (G('2^x') * x^2).log_factor(base=2) # indirect doctest + ((x, 1), (log(x), 2/log(2))) + + :: + + sage: G(1).log_factor() + () + + :: + + sage: log(x).log_factor() # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: Cannot build log(log(x)) since log(log(x)) is + not in Growth Group QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ. + + .. SEEALSO:: + + :meth:`~sage.rings.asymptotic.growth_group.GenericGrowthElement.factors`, + :meth:`~sage.rings.asymptotic.growth_group.GenericGrowthElement.log`. + + TESTS:: + + sage: G = GrowthGroup("(e^x)^ZZ * x^ZZ * log(x)^ZZ") + sage: x, = G.gens_monomial() + sage: (exp(x) * x).log_factor() # indirect doctest + ((x, 1), (log(x), 1)) + """ + log_factor = self._log_factor_(base=base) + + for g, c in log_factor: + if hasattr(g, 'parent') and \ + isinstance(g.parent(), GenericGrowthGroup): + continue + from misc import log_string + raise ArithmeticError('Cannot build %s since %s ' + 'is not in %s.' % (log_string(self, base), + g, self.parent())) + + return log_factor + + +# The following function is used in the classes GenericGrowthElement and +# GenericProduct.Element as a method. +def _rpow_(self, base): + r""" + Calculate the power of ``base`` to this element. + + INPUT: + + - ``base`` -- an element. + + OUTPUT: + + A growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x * x^ZZ') + sage: x = G('x') + sage: x.rpow(2) # indirect doctest + 2^x + sage: x.rpow(1/2) # indirect doctest + (1/2)^x + + :: + + sage: x.rpow(0) # indirect doctest + Traceback (most recent call last): + ... + ValueError: 0 is not an allowed base for calculating the power to x. + sage: (x^2).rpow(2) # indirect doctest + Traceback (most recent call last): + ... + ArithmeticError: Cannot construct 2^(x^2) in Growth Group QQ^x * x^ZZ + > *previous* TypeError: unsupported operand parent(s) for '*': + 'Growth Group QQ^x * x^ZZ' and 'Growth Group ZZ^(x^2)' + + :: + + sage: G = GrowthGroup('QQ^(x*log(x)) * x^ZZ * log(x)^ZZ') + sage: x = G('x') + sage: (x * log(x)).rpow(2) # indirect doctest + 2^(x*log(x)) + """ + if base == 0: + raise ValueError('%s is not an allowed base for calculating the ' + 'power to %s.' % (base, self)) + + var = str(self) + + try: + element = self._rpow_element_(base) + except ValueError: + if base == 'e': + from sage.rings.integer_ring import ZZ + from misc import repr_op + M = MonomialGrowthGroup(ZZ, repr_op('e', '^', var), + ignore_variables=('e',)) + element = M(raw_element=ZZ(1)) + else: + E = ExponentialGrowthGroup(base.parent(), var) + element = E(raw_element=base) + + try: + return self.parent().one() * element + except (TypeError, ValueError) as e: + from misc import combine_exceptions, repr_op + raise combine_exceptions( + ArithmeticError('Cannot construct %s in %s' % + (repr_op(base, '^', var), self.parent())), e) + + +class GenericGrowthElement(sage.structure.element.MultiplicativeGroupElement): + r""" + A basic implementation of a generic growth element. + + Growth elements form a group by multiplication, and (some of) the + elements can be compared to each other, i.e., all elements form a + poset. + + INPUT: + + - ``parent`` -- a :class:`GenericGrowthGroup`. + + - ``raw_element`` -- an element from the base of the parent. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import (GenericGrowthGroup, + ....: GenericGrowthElement) + sage: G = GenericGrowthGroup(ZZ) + sage: g = GenericGrowthElement(G, 42); g + GenericGrowthElement(42) + sage: g.parent() + Growth Group Generic(ZZ) + sage: G(raw_element=42) == g + True + """ + + def __init__(self, parent, raw_element): + r""" + See :class:`GenericGrowthElement` for more information. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G(raw_element=42) + GenericGrowthElement(42) + + TESTS:: + + sage: G(raw_element=42).category() + Category of elements of Growth Group Generic(ZZ) + + :: + + sage: G = GenericGrowthGroup(ZZ) + sage: G(raw_element=42).category() + Category of elements of Growth Group Generic(ZZ) + + :: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthElement + sage: GenericGrowthElement(None, 0) + Traceback (most recent call last): + ... + ValueError: The parent must be provided + """ + if parent is None: + raise ValueError('The parent must be provided') + super(GenericGrowthElement, self).__init__(parent=parent) + + self._raw_element_ = parent.base()(raw_element) + + + def _repr_(self): + r""" + A representation string for this generic element. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G(raw_element=42) # indirect doctest + GenericGrowthElement(42) + sage: H = GenericGrowthGroup(ZZ, 'h') + sage: H(raw_element=42) # indirect doctest + GenericGrowthElement(42, h) + """ + vars = ', '.join(self.parent()._var_.variable_names()) + if vars: + vars = ', ' + vars + return 'GenericGrowthElement(%s%s)' % (self._raw_element_, vars) + + + def __hash__(self): + r""" + Return the hash of this element. + + INPUT: + + Nothing. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ); + sage: hash(G(raw_element=42)) # random + 5656565656565656 + """ + return hash((self.parent(), self._raw_element_)) + + + def _mul_(self, other): + r""" + Abstract multiplication method for generic elements. + + INPUT: + + - ``other`` -- a :class:`GenericGrowthElement`. + + OUTPUT: + + A :class:`GenericGrowthElement` representing the product with + ``other``. + + .. NOTE:: + + Inherited classes must override this. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: g = G.an_element() + sage: g*g + Traceback (most recent call last): + ... + NotImplementedError: Only implemented in concrete realizations. + """ + raise NotImplementedError('Only implemented in concrete realizations.') + + + def __invert__(self): + r""" + Return the inverse of this growth element. + + OUTPUT: + + An instance of :class:`GenericGrowthElement`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: ~G.an_element() + Traceback (most recent call last): + ... + NotImplementedError: Inversion of GenericGrowthElement(1) not implemented + (in this abstract method). + sage: G.an_element()^7 + Traceback (most recent call last): + ... + NotImplementedError: Only implemented in concrete realizations. + sage: P = GrowthGroup('x^ZZ') + sage: ~P.an_element() + x^(-1) + """ + raise NotImplementedError('Inversion of %s not implemented ' + '(in this abstract method).' % (self,)) + + + def __eq__(self, other): + r""" + Return whether this growth element is equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + The comparison of two elements with the same parent is done in + :meth:`_eq_`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G.an_element() == G.an_element() + True + sage: G(raw_element=42) == G(raw_element=7) + False + + :: + + sage: G_ZZ = GenericGrowthGroup(ZZ) + sage: G_QQ = GenericGrowthGroup(QQ) + sage: G_ZZ(raw_element=1) == G_QQ(raw_element=1) + True + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_ZZ = GrowthGroup('x^ZZ') + sage: P_QQ = GrowthGroup('x^QQ') + sage: P_ZZ.gen() == P_QQ.gen() + True + sage: ~P_ZZ.gen() == P_ZZ.gen() + False + sage: ~P_ZZ(1) == P_ZZ(1) + True + """ + from sage.structure.element import have_same_parent + if have_same_parent(self, other): + return self._eq_(other) + + from sage.structure.element import get_coercion_model + import operator + try: + return get_coercion_model().bin_op(self, other, operator.eq) + except TypeError: + return False + + + def _eq_(self, other): + r""" + Return whether this :class:`GenericGrowthElement` is equal to ``other``. + + INPUT: + + - ``other`` -- a :class:`GenericGrowthElement`. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function compares two instances of + :class:`GenericGrowthElement`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: e1 = P(raw_element=1) + sage: e1._eq_(P.gen()) + True + sage: e2 = e1^4 + sage: e2 == e1^2*e1*e1 + True + sage: e2 == e1 + False + """ + return self._raw_element_ == other._raw_element_ + + + def __ne__(self, other): + r""" + Return whether this growth element is not equal to ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: G.one() != G(1) + False + sage: G.one() != G.one() + False + sage: G(1) != G(1) + False + """ + return not self == other + + + def __le__(self, other): + r""" + Return whether this growth element is at most (less than or equal + to) ``other``. + + INPUT: + + - ``other`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + The comparison of two elements with the same parent is done in + :meth:`_le_`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_ZZ = GrowthGroup('x^ZZ') + sage: P_QQ = GrowthGroup('x^QQ') + sage: P_ZZ.gen() <= P_QQ.gen()^2 + True + sage: ~P_ZZ.gen() <= P_ZZ.gen() + True + """ + from sage.structure.element import have_same_parent + if have_same_parent(self, other): + return self._le_(other) + + from sage.structure.element import get_coercion_model + import operator + try: + return get_coercion_model().bin_op(self, other, operator.le) + except TypeError: + return False + + + def _le_(self, other): + r""" + Return whether this :class:`GenericGrowthElement` is at most (less + than or equal to) ``other``. + + INPUT: + + - ``other`` -- a :class:`GenericGrowthElement`. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function compares two instances of + :class:`GenericGrowthElement`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: e1 = G(raw_element=1); e2 = G(raw_element=2) + sage: e1 <= e2 # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Only implemented in concrete realizations. + """ + raise NotImplementedError('Only implemented in concrete realizations.') + + + log = _log_ + log_factor = _log_factor_ + + + def _log_factor_(self, base=None): + r""" + Helper method for calculating the logarithm of the factorization + of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is either a growth + element or something out of which we can construct a growth element + and the second a multiplicative coefficient. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(QQ) + sage: G.an_element().log_factor() # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Cannot determine logarithmized factorization of + GenericGrowthElement(1/2) in abstract base class. + """ + raise NotImplementedError('Cannot determine logarithmized factorization ' + 'of %s in abstract base class.' % (self,)) + + + rpow = _rpow_ + + + def _rpow_element_(self, base): + r""" + Return an element which is the power of ``base`` to this + element. + + INPUT: + + - ``base`` -- an element. + + OUTPUT: + + Nothing since a ``ValueError`` is raised in this generic method. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x') + sage: x = G(raw_element=3) + sage: x._rpow_element_(2) is None + Traceback (most recent call last): + ... + ValueError: Cannot compute 2 to the generic element 3^x. + """ + raise ValueError('Cannot compute %s to the generic element %s.' % + (base, self)) + + + def factors(self): + r""" + Return the atomic factors of this growth element. An atomic factor + cannot be split further. + + INPUT: + + Nothing. + + OUTPUT: + + A tuple of growth elements. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: G.an_element().factors() + (x,) + """ + return (self,) + + + is_lt_one = _is_lt_one_ + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this generic growth element. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G(raw_element=42)._substitute_({}) + Traceback (most recent call last): + ... + TypeError: Cannot substitute in GenericGrowthElement(42) in + Growth Group Generic(ZZ). + > *previous* TypeError: Cannot substitute in the abstract base class + Growth Group Generic(ZZ). + """ + from misc import substitute_raise_exception + substitute_raise_exception(self, TypeError( + 'Cannot substitute in the abstract ' + 'base class %s.' % (self.parent(),))) + + +class GenericGrowthGroup( + sage.structure.unique_representation.UniqueRepresentation, + sage.structure.parent.Parent): + r""" + A basic implementation for growth groups. + + INPUT: + + - ``base`` -- one of SageMath's parents, out of which the elements + get their data (``raw_element``). + + - ``category`` -- (default: ``None``) the category of the newly + created growth group. It has to be a subcategory of ``Join of + Category of groups and Category of posets``. This is also the + default category if ``None`` is specified. + + - ``ignore_variables`` -- (default: ``None``) a tuple (or other + iterable) of strings. The specified names are not considered as + variables. + + .. NOTE:: + + This class should be derived for concrete implementations. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ); G + Growth Group Generic(ZZ) + + .. SEEALSO:: + + :class:`MonomialGrowthGroup`, + :class:`ExponentialGrowthGroup` + """ + # TODO: implement some sort of 'assume', where basic assumptions + # for the variables can be stored. --> within the cartesian product + + # enable the category framework for elements + Element = GenericGrowthElement + + + # set everything up to determine category + from sage.categories.sets_cat import Sets + from sage.categories.posets import Posets + from sage.categories.magmas import Magmas + from sage.categories.additive_magmas import AdditiveMagmas + + _determine_category_subcategory_mapping_ = [ + (Sets(), Sets(), True), + (Posets(), Posets(), False)] + + _determine_category_axiom_mapping_ = [] + + + @staticmethod + def __classcall__(cls, base, var=None, category=None, ignore_variables=None): + r""" + Normalizes the input in order to ensure a unique + representation. + + For more information see :class:`GenericGrowthGroup`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: P1 = MonomialGrowthGroup(ZZ, 'x') + sage: P2 = MonomialGrowthGroup(ZZ, ZZ['x'].gen()) + sage: P3 = MonomialGrowthGroup(ZZ, SR.var('x')) + sage: P1 is P2 and P2 is P3 + True + sage: P4 = MonomialGrowthGroup(ZZ, buffer('xylophone', 0, 1)) + sage: P1 is P4 + True + sage: P5 = MonomialGrowthGroup(ZZ, 'x ') + sage: P1 is P5 + True + + :: + + sage: L1 = MonomialGrowthGroup(QQ, log(x)) + sage: L2 = MonomialGrowthGroup(QQ, 'log(x)') + sage: L1 is L2 + True + + Test determining of the category (GenericGrowthGroup):: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(ZZ, 'x').category() # indirect doctest + Category of posets + sage: GenericGrowthGroup(ZZ, 'x', category=Groups()).category() # indirect doctest + Category of groups + + Test determining of the category (MonomialGrowthGroup):: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: MonomialGrowthGroup(ZZ, 'x').category() # indirect doctest + Join of Category of commutative groups and Category of posets + sage: MonomialGrowthGroup(ZZ, 'x', category=Monoids()).category() # indirect doctest + Category of monoids + sage: W = Words([0, 1]) + sage: W.category() + Category of sets + sage: MonomialGrowthGroup(W, 'x').category() # indirect doctest + Category of sets + + Test determining of the category (ExponentialGrowthGroup):: + + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: ExponentialGrowthGroup(ZZ, 'x').category() # indirect doctest + Join of Category of commutative monoids and Category of posets + sage: ExponentialGrowthGroup(QQ, 'x').category() # indirect doctest + Join of Category of commutative groups and Category of posets + sage: ExponentialGrowthGroup(ZZ, 'x', category=Groups()).category() # indirect doctest + Category of groups + sage: ExponentialGrowthGroup(QQ, 'x', category=Monoids()).category() # indirect doctest + Category of monoids + """ + if not isinstance(base, sage.structure.parent.Parent): + raise TypeError('%s is not a valid base.' % (base,)) + + if var is None: + var = Variable('') + elif not isinstance(var, Variable): + var = Variable(var, ignore=ignore_variables) + + from sage.categories.posets import Posets + if category is None: + # The following block can be removed once #19269 is fixed. + from sage.rings.integer_ring import ZZ + from sage.rings.rational_field import QQ + from sage.rings.polynomial.polynomial_ring import is_PolynomialRing + if base is ZZ or base is QQ or \ + is_PolynomialRing(base) and \ + (base.base_ring() is ZZ or base.base_ring() is QQ): + initial_category = Posets() + else: + initial_category = None + + from misc import transform_category + category = transform_category( + base.category(), + cls._determine_category_subcategory_mapping_, + cls._determine_category_axiom_mapping_, + initial_category=initial_category) + + return super(GenericGrowthGroup, cls).__classcall__( + cls, base, var, category) + + + @sage.misc.superseded.experimental(trac_number=17601) + def __init__(self, base, var, category): + r""" + See :class:`GenericGrowthElement` for more information. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(ZZ).category() + Category of posets + + :: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: MonomialGrowthGroup(ZZ, 'x') + Growth Group x^ZZ + sage: MonomialGrowthGroup(QQ, SR.var('n')) + Growth Group n^QQ + sage: MonomialGrowthGroup(ZZ, ZZ['y'].gen()) + Growth Group y^ZZ + sage: MonomialGrowthGroup(QQ, 'log(x)') + Growth Group log(x)^QQ + + :: + + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: ExponentialGrowthGroup(QQ, 'x') + Growth Group QQ^x + sage: ExponentialGrowthGroup(SR, ZZ['y'].gen()) + Growth Group SR^y + + TESTS:: + + sage: G = GenericGrowthGroup(ZZ) + sage: G.is_parent_of(G(raw_element=42)) + True + sage: G2 = GenericGrowthGroup(ZZ, category=FiniteGroups() & Posets()) + sage: G2.category() + Join of Category of finite groups and Category of finite posets + + :: + + sage: G = GenericGrowthGroup('42') + Traceback (most recent call last): + ... + TypeError: 42 is not a valid base. + + :: + + sage: MonomialGrowthGroup('x', ZZ) + Traceback (most recent call last): + ... + TypeError: x is not a valid base. + sage: MonomialGrowthGroup('x', 'y') + Traceback (most recent call last): + ... + TypeError: x is not a valid base. + + :: + + sage: ExponentialGrowthGroup('x', ZZ) + Traceback (most recent call last): + ... + TypeError: x is not a valid base. + sage: ExponentialGrowthGroup('x', 'y') + Traceback (most recent call last): + ... + TypeError: x is not a valid base. + + """ + self._var_ = var + super(GenericGrowthGroup, self).__init__(category=category, + base=base) + + + def _repr_short_(self): + r""" + A short representation string of this abstract growth group. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(QQ)._repr_short_() + 'Generic(QQ)' + sage: GenericGrowthGroup(QQ) + Growth Group Generic(QQ) + sage: GenericGrowthGroup(QQ, ('a', 'b')) + Growth Group Generic(QQ, a, b) + """ + from misc import parent_to_repr_short + vars = ', '.join(self._var_.variable_names()) + if vars: + vars = ', ' + vars + return 'Generic(%s%s)' % (parent_to_repr_short(self.base()), vars) + + + def _repr_(self, condense=False): + r""" + A representations string of this growth group. + + INPUT: + + - ``condense`` -- (default: ``False``) if set, then a shorter + output is returned, e.g. the prefix-string ``Growth Group`` + is not show in this case. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ') # indirect doctest + Growth Group x^ZZ + sage: GrowthGroup('log(x)^QQ') # indirect doctest + Growth Group log(x)^QQ + + TESTS:: + + sage: GrowthGroup('log(x)^QQ')._repr_(condense=True) + 'log(x)^QQ' + """ + pre = 'Growth Group ' if not condense else '' + return '%s%s' % (pre, self._repr_short_()) + + + def __hash__(self): + r""" + Return the hash of this group. + + INPUT: + + Nothing. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import (GenericGrowthGroup, + ....: GrowthGroup) + sage: hash(GenericGrowthGroup(ZZ)) # random + 4242424242424242 + + :: + + sage: P = GrowthGroup('x^ZZ') + sage: hash(P) # random + -1234567890123456789 + + :: + + sage: P = GrowthGroup('QQ^x') + sage: hash(P) # random + -1234567890123456789 + """ + return hash((self.__class__, self.base(), self._var_)) + + + def _an_element_(self): + r""" + Return an element of ``self``. + + INPUT: + + Nothing. + + OUTPUT: + + An element of ``self``. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import (GenericGrowthGroup, + ....: GrowthGroup) + sage: GenericGrowthGroup(ZZ).an_element() # indirect doctest + GenericGrowthElement(1) + sage: GrowthGroup('z^ZZ').an_element() # indirect doctest + z + sage: GrowthGroup('log(z)^QQ').an_element() # indirect doctest + log(z)^(1/2) + sage: GrowthGroup('QQ^(x*log(x))').an_element() # indirect doctest + (1/2)^(x*log(x)) + """ + return self.element_class(self, self.base().an_element()) + + + def some_elements(self): + r""" + Return some elements of this growth group. + + See :class:`TestSuite` for a typical use case. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: tuple(GrowthGroup('z^ZZ').some_elements()) + (1, z, z^(-1), z^2, z^(-2), z^3, z^(-3), + z^4, z^(-4), z^5, z^(-5), ...) + sage: tuple(GrowthGroup('z^QQ').some_elements()) + (z^(1/2), z^(-1/2), z^2, z^(-2), + 1, z, z^(-1), z^42, + z^(2/3), z^(-2/3), z^(3/2), z^(-3/2), + z^(4/5), z^(-4/5), z^(5/4), z^(-5/4), ...) + """ + return iter(self.element_class(self, e) + for e in self.base().some_elements()) + + + def _create_element_in_extension_(self, raw_element): + r""" + Create an element in an extension of this growth group which + is chosen according to the input ``raw_element``. + + INPUT: + + - ``raw_element`` -- the element data. + + OUTPUT: + + An element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: G._create_element_in_extension_(3).parent() + Growth Group z^ZZ + sage: G._create_element_in_extension_(1/2).parent() + Growth Group z^QQ + """ + if raw_element.parent() is self.base(): + parent = self + else: + from misc import underlying_class + parent = underlying_class(self)(raw_element.parent(), self._var_, + category=self.category()) + return parent(raw_element=raw_element) + + + def le(self, left, right): + r""" + Return whether the growth of ``left`` is at most (less than or + equal to) the growth of ``right``. + + INPUT: + + - ``left`` -- an element. + + - ``right`` -- an element. + + OUTPUT: + + A boolean. + + .. NOTE:: + + This function uses the coercion model to find a common + parent for the two operands. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: x = G.gen() + sage: G.le(x, x^2) + True + sage: G.le(x^2, x) + False + sage: G.le(x^0, 1) + True + """ + return self(left) <= self(right) + + + def _element_constructor_(self, data, raw_element=None): + r""" + Convert a given object to this growth group. + + INPUT: + + - ``data`` -- an object representing the element to be + initialized. + + - ``raw_element`` -- (default: ``None``) if given, then this is + directly passed to the element constructor (i.e., no conversion + is performed). + + OUTPUT: + + An element of this growth group. + + .. NOTE:: + + Either ``data`` or ``raw_element`` has to be given. If + ``raw_element`` is specified, then no positional argument + may be passed. + + This method calls :meth:`_convert_`, which does the actual + conversion from ``data``. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G_ZZ = GenericGrowthGroup(ZZ) + sage: z = G_ZZ(raw_element=42); z # indirect doctest + GenericGrowthElement(42) + sage: z is G_ZZ(z) # indirect doctest + True + + :: + + sage: G_QQ = GenericGrowthGroup(QQ) + sage: q = G_QQ(raw_element=42) # indirect doctest + sage: q is z + False + sage: G_ZZ(q) # indirect doctest + GenericGrowthElement(42) + sage: G_QQ(z) # indirect doctest + GenericGrowthElement(42) + sage: q is G_ZZ(q) # indirect doctest + False + + :: + + sage: G_ZZ() + Traceback (most recent call last): + ... + ValueError: No input specified. Cannot continue. + sage: G_ZZ('blub') # indirect doctest + Traceback (most recent call last): + ... + ValueError: blub is not in Growth Group Generic(ZZ). + sage: G_ZZ('x', raw_element=42) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Input is ambigous: x as well as raw_element=42 are specified. + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: x = GrowthGroup('x^ZZ')(raw_element=1) # indirect doctest + sage: G_y = GrowthGroup('y^ZZ') + sage: G_y(x) # indirect doctest + Traceback (most recent call last): + ... + ValueError: x is not in Growth Group y^ZZ. + + :: + + sage: GrowthGroup('QQ^x')(GrowthGroup('ZZ^x')('2^x')) + 2^x + """ + from misc import underlying_class, combine_exceptions + + if raw_element is None: + if isinstance(data, int) and data == 0: + raise ValueError('No input specified. Cannot continue.') + + elif isinstance(data, self.element_class): + if data.parent() == self: + return data + if self._var_ != data.parent()._var_: + raise ValueError('%s is not in %s.' % (data, self)) + raw_element = data._raw_element_ + + elif isinstance(data, self.Element): + if self._var_ == data.parent()._var_: + try: + raw_element = self.base()(data._raw_element_) + except (TypeError, ValueError) as e: + raise combine_exceptions( + ValueError('%s is not in %s.' % (data, self)), e) + + elif isinstance(data, GenericGrowthElement): + if data.is_one(): + return self.one() + + else: + raw_element = self._convert_(data) + + if raw_element is None: + raise ValueError('%s is not in %s.' % (data, self)) + elif not isinstance(data, int) or data != 0: + raise ValueError('Input is ambigous: ' + '%s as well as raw_element=%s ' + 'are specified.' % (data, raw_element)) + + return self.element_class(self, raw_element) + + + def _convert_(self, data): + r""" + Convert ``data`` to something the constructor of the + element class accepts (``raw_element``). + + INPUT: + + - ``data`` -- an object. + + OUTPUT: + + An element of the base ring or ``None`` (when no such element + can be constructed). + + .. NOTE:: + + This method always returns ``None`` in this abstract base + class, and should be overridden in inherited class. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: G = GenericGrowthGroup(ZZ) + sage: G._convert_('icecream') is None + True + """ + pass + + + def _coerce_map_from_(self, S): + r""" + Return whether ``S`` coerces into this growth group. + + INPUT: + + - ``S`` -- a parent. + + OUTPUT: + + A boolean. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: G_QQ = GrowthGroup('x^QQ') + sage: G_ZZ.has_coerce_map_from(G_QQ) # indirect doctest + False + sage: G_QQ.has_coerce_map_from(G_ZZ) # indirect doctest + True + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_x_ZZ = GrowthGroup('x^ZZ') + sage: P_x_QQ = GrowthGroup('x^QQ') + sage: P_x_ZZ.has_coerce_map_from(P_x_QQ) # indirect doctest + False + sage: P_x_QQ.has_coerce_map_from(P_x_ZZ) # indirect doctest + True + sage: P_y_ZZ = GrowthGroup('y^ZZ') + sage: P_y_ZZ.has_coerce_map_from(P_x_ZZ) # indirect doctest + False + sage: P_x_ZZ.has_coerce_map_from(P_y_ZZ) # indirect doctest + False + sage: P_y_ZZ.has_coerce_map_from(P_x_QQ) # indirect doctest + False + sage: P_x_QQ.has_coerce_map_from(P_y_ZZ) # indirect doctest + False + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_x_ZZ = GrowthGroup('ZZ^x') + sage: P_x_QQ = GrowthGroup('QQ^x') + sage: P_x_ZZ.has_coerce_map_from(P_x_QQ) # indirect doctest + False + sage: P_x_QQ.has_coerce_map_from(P_x_ZZ) # indirect doctest + True + sage: P_y_ZZ = GrowthGroup('ZZ^y') + sage: P_y_ZZ.has_coerce_map_from(P_x_ZZ) # indirect doctest + False + sage: P_x_ZZ.has_coerce_map_from(P_y_ZZ) # indirect doctest + False + sage: P_y_ZZ.has_coerce_map_from(P_x_QQ) # indirect doctest + False + sage: P_x_QQ.has_coerce_map_from(P_y_ZZ) # indirect doctest + False + + :: + + sage: GrowthGroup('x^QQ').has_coerce_map_from(GrowthGroup('QQ^x')) # indirect doctest + False + """ + from misc import underlying_class + if isinstance(S, underlying_class(self)) and self._var_ == S._var_: + if self.base().has_coerce_map_from(S.base()): + return True + + + def _pushout_(self, other): + r""" + Construct the pushout of this and the other growth group. This is called by + :func:`sage.categories.pushout.pushout`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.categories.pushout import pushout + sage: cm = sage.structure.element.get_coercion_model() + sage: A = GrowthGroup('QQ^x') + sage: B = GrowthGroup('y^ZZ') + + When using growth groups with disjoint variable lists, then a + pushout can be constructed:: + + sage: A._pushout_(B) + Growth Group QQ^x * y^ZZ + sage: cm.common_parent(A, B) + Growth Group QQ^x * y^ZZ + + In general, growth groups of the same variable cannot be + combined automatically, since there is no order relation between the two factors:: + + sage: C = GrowthGroup('x^QQ') + sage: cm.common_parent(A, C) + Traceback (most recent call last): + ... + TypeError: no common canonical parent for objects with parents: + 'Growth Group QQ^x' and 'Growth Group x^QQ' + + However, combining is possible if the factors with the same variable + overlap:: + + sage: cm.common_parent(GrowthGroup('x^ZZ * log(x)^ZZ'), GrowthGroup('exp(x)^ZZ * x^ZZ')) + Growth Group exp(x)^ZZ * x^ZZ * log(x)^ZZ + sage: cm.common_parent(GrowthGroup('x^ZZ * log(x)^ZZ'), GrowthGroup('y^ZZ * x^ZZ')) + Growth Group x^ZZ * log(x)^ZZ * y^ZZ + + :: + + sage: cm.common_parent(GrowthGroup('x^ZZ'), GrowthGroup('y^ZZ')) + Growth Group x^ZZ * y^ZZ + + :: + + sage: cm.record_exceptions() + sage: cm.common_parent(GrowthGroup('x^ZZ'), GrowthGroup('y^ZZ')) + Growth Group x^ZZ * y^ZZ + sage: sage.structure.element.coercion_traceback() # not tested + """ + if not isinstance(other, GenericGrowthGroup) and \ + not (other.construction() is not None and + isinstance(other.construction()[0], AbstractGrowthGroupFunctor)): + return + + if set(self.variable_names()).isdisjoint(set(other.variable_names())): + from sage.categories.cartesian_product import cartesian_product + return cartesian_product([self, other]) + + + def gens_monomial(self): + r""" + Return a tuple containing monomial generators of this growth + group. + + INPUT: + + Nothing. + + OUTPUT: + + An empty tuple. + + .. NOTE:: + + A generator is called monomial generator if the variable + of the underlying growth group is a valid identifier. For + example, ``x^ZZ`` has ``x`` as a monomial generator, + while ``log(x)^ZZ`` or ``icecream(x)^ZZ`` do not have + monomial generators. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(ZZ).gens_monomial() + () + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^QQ').gens_monomial() + (x,) + sage: GrowthGroup('QQ^x').gens_monomial() + () + """ + return tuple() + + + def gens(self): + r""" + Return a tuple of all generators of this growth group. + + INPUT: + + Nothing. + + OUTPUT: + + A tuple whose entries are growth elements. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P.gens() + (x,) + sage: GrowthGroup('log(x)^ZZ').gens() + (log(x),) + """ + return (self(raw_element=self.base().one()),) + + + def gen(self, n=0): + r""" + Return the `n`-th generator (as a group) of this growth group. + + INPUT: + + - ``n`` -- default: `0`. + + OUTPUT: + + A :class:`MonomialGrowthElement`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P.gen() + x + + :: + + sage: P = GrowthGroup('QQ^x') + sage: P.gen() + Traceback (most recent call last): + ... + IndexError: tuple index out of range + """ + return self.gens()[n] + + + def ngens(self): + r""" + Return the number of generators (as a group) of this growth group. + + INPUT: + + Nothing. + + OUTPUT: + + A Python integer. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P.ngens() + 1 + sage: GrowthGroup('log(x)^ZZ').ngens() + 1 + + :: + + sage: P = GrowthGroup('QQ^x') + sage: P.ngens() + 0 + """ + return len(self.gens()) + + + def variable_names(self): + r""" + Return the names of the variables. + + OUTPUT: + + A tuple of strings. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: GenericGrowthGroup(ZZ).variable_names() + () + + :: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ').variable_names() + ('x',) + sage: GrowthGroup('log(x)^ZZ').variable_names() + ('x',) + + :: + + sage: GrowthGroup('QQ^x').variable_names() + ('x',) + sage: GrowthGroup('QQ^(x*log(x))').variable_names() + ('x',) + """ + return self._var_.variable_names() + + + CartesianProduct = CartesianProductGrowthGroups + + +from sage.categories.pushout import ConstructionFunctor +class AbstractGrowthGroupFunctor(ConstructionFunctor): + r""" + A base class for the functors constructing growth groups. + + INPUT: + + - ``var`` -- a string or list of strings (or anything else + :class:`Variable` accepts). + + - ``domain`` -- a category. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('z^QQ').construction()[0] # indirect doctest + MonomialGrowthGroup[z] + + .. SEEALSO:: + + :doc:`asymptotic_ring`, + :class:`ExponentialGrowthGroupFunctor`, + :class:`MonomialGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.asymptotic_ring.AsymptoticRingFunctor`, + :class:`sage.categories.pushout.ConstructionFunctor`. + """ + + _functor_name = 'AbstractGrowthGroup' + + rank = 13 + + def __init__(self, var, domain): + r""" + See :class:`AbstractGrowthGroupFunctor` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import AbstractGrowthGroupFunctor + sage: AbstractGrowthGroupFunctor('x', Groups()) + AbstractGrowthGroup[x] + """ + if var is None: + var = Variable('') + elif not isinstance(var, Variable): + var = Variable(var) + self.var = var + super(ConstructionFunctor, self).__init__( + domain, sage.categories.monoids.Monoids() & sage.categories.posets.Posets()) + + + def _repr_(self): + r""" + Return a representation string of this functor. + + OUTPUT: + + A string. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('QQ^t').construction()[0] # indirect doctest + ExponentialGrowthGroup[t] + """ + return '%s[%s]' % (self._functor_name, self.var) + + + def merge(self, other): + r""" + Merge this functor with ``other`` of possible. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A functor or ``None``. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F = GrowthGroup('QQ^t').construction()[0] + sage: G = GrowthGroup('t^QQ').construction()[0] + sage: F.merge(F) + ExponentialGrowthGroup[t] + sage: F.merge(G) is None + True + """ + if self == other: + return self + + + def __eq__(self, other): + r""" + Return whether this functor is equal to ``other``. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F = GrowthGroup('QQ^t').construction()[0] + sage: G = GrowthGroup('t^QQ').construction()[0] + sage: F == F + True + sage: F == G + False + """ + return type(self) == type(other) and self.var == other.var + + + def __ne__(self, other): + r""" + Return whether this functor is not equal to ``other``. + + INPUT: + + - ``other`` -- a functor. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F = GrowthGroup('QQ^t').construction()[0] + sage: G = GrowthGroup('t^QQ').construction()[0] + sage: F != F + False + sage: F != G + True + """ + return not self == other + + +class MonomialGrowthElement(GenericGrowthElement): + r""" + An implementation of monomial growth elements. + + INPUT: + + - ``parent`` -- a :class:`MonomialGrowthGroup`. + + - ``raw_element`` -- an element from the base ring of the parent. + + This ``raw_element`` is the exponent of the created monomial + growth element. + + A monomial growth element represents a term of the type + `\operatorname{variable}^{\operatorname{exponent}}`. The multiplication + corresponds to the addition of the exponents. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: P = MonomialGrowthGroup(ZZ, 'x') + sage: e1 = P(1); e1 + 1 + sage: e2 = P(raw_element=2); e2 + x^2 + sage: e1 == e2 + False + sage: P.le(e1, e2) + True + sage: P.le(e1, P.gen()) and P.le(P.gen(), e2) + True + """ + + @property + def exponent(self): + r""" + The exponent of this growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P(x^42).exponent + 42 + """ + return self._raw_element_ + + + def _repr_(self): + r""" + A representation string for this monomial growth element. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^QQ') + sage: P(1)._repr_() + '1' + sage: P(x^5) # indirect doctest + x^5 + sage: P(x^(1/2)) # indirect doctest + x^(1/2) + + TESTS:: + + sage: P(x^-1) # indirect doctest + x^(-1) + sage: P(x^-42) # indirect doctest + x^(-42) + """ + from sage.rings.integer_ring import ZZ + from misc import repr_op + + var = repr(self.parent()._var_) + if self.exponent.is_zero(): + return '1' + elif self.exponent.is_one(): + return var + elif self.exponent in ZZ and self.exponent > 0: + return repr_op(var, '^') + str(self.exponent) + else: + return repr_op(var, '^') + '(' + str(self.exponent) + ')' + + + def _mul_(self, other): + r""" + Multiply this monomial growth element with another. + + INPUT: + + - ``other`` -- a :class:`MonomialGrowthElement` + + OUTPUT: + + The product as a :class:`MonomialGrowthElement`. + .. NOTE:: - This function compares two instances of - :class:`GenericGrowthElement`. + Two monomial growth elements are multiplied by adding + their exponents. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: a = P(x^2) + sage: b = P(x^3) + sage: c = a._mul_(b); c + x^5 + sage: c == a*b + True + sage: a*b*a # indirect doctest + x^7 + """ + return self.parent()(raw_element=self.exponent + other.exponent) + - TESTS:: + def __invert__(self): + r""" + Return the multiplicative inverse of this monomial growth element. - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: e1 = G(raw_element=1); e2 = G(raw_element=2) - sage: e1 <= e2 # indirect doctest - Traceback (most recent call last): - ... - NotImplementedError: Only implemented in concrete realizations. - """ - raise NotImplementedError('Only implemented in concrete realizations.') + INPUT: + Nothing. -class GenericGrowthGroup( - sage.structure.parent.Parent, - sage.structure.unique_representation.UniqueRepresentation): - r""" - A basic implementation for growth groups. + OUTPUT: - INPUT: + The multiplicative inverse as a :class:`MonomialGrowthElement`. - - ``base`` -- one of SageMath's parents, out of which the elements - get their data (``raw_element``). + EXAMPLES:: - - ``category`` -- (default: ``None``) the category of the newly - created growth group. It has to be a subcategory of ``Join of - Category of groups and Category of posets``. This is also the - default category if ``None`` is specified. + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: e1 = P(raw_element=2) + sage: e2 = e1.__invert__(); e2 + x^(-2) + sage: e2 == ~e1 + True + sage: Q = GrowthGroup('x^NN'); Q + Growth Group x^((Non negative integer semiring)) + sage: e3 = ~Q('x'); e3 + x^(-1) + sage: e3.parent() + Growth Group x^ZZ + """ + return self.parent()._create_element_in_extension_(-self.exponent) - .. NOTE:: - This class should be derived for concrete implementations. + def __pow__(self, exponent): + r""" + Calculate the power of this growth element to the given ``exponent``. - EXAMPLES:: + INPUT: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ); G - Growth Group Generic(ZZ) + - ``exponent`` -- a number. This can be anything that is a + valid right hand side of ``*`` with elements of the + parent's base. - .. SEEALSO:: + OUTPUT: - :class:`MonomialGrowthGroup` - """ - # TODO: implement some sort of 'assume', where basic assumptions - # for the variables can be stored. --> within the cartesian product + The result of this exponentiation, a :class:`MonomialGrowthElement`. - # enable the category framework for elements - Element = GenericGrowthElement + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: x = P.gen() + sage: a = x^7; a + x^7 + sage: a^(1/2) + x^(7/2) + sage: (a^(1/2)).parent() + Growth Group x^QQ + sage: a^(1/7) + x + sage: (a^(1/7)).parent() + Growth Group x^QQ + sage: P = GrowthGroup('x^QQ') + sage: b = P.gen()^(7/2); b + x^(7/2) + sage: b^12 + x^42 + """ + return self.parent()._create_element_in_extension_(self.exponent * exponent) - @sage.misc.superseded.experimental(trac_number=17601) - def __init__(self, base, category=None): + def _log_factor_(self, base=None): r""" - See :class:`GenericGrowthElement` for more information. + Helper method for calculating the logarithm of the factorization + of this element. - EXAMPLES:: + INPUT: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.GenericGrowthGroup(ZZ).category() - Join of Category of groups and Category of posets + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is either a growth + element or something out of which we can construct a growth element + and the second a multiplicative coefficient. TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: G.is_parent_of(G(raw_element=42)) - True - sage: G2 = agg.GenericGrowthGroup(ZZ, category=FiniteGroups() & Posets()) - sage: G2.category() - Join of Category of finite groups and Category of finite posets - sage: G3 = agg.GenericGrowthGroup(ZZ, category=Rings()) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^QQ') + sage: G('x').log_factor() # indirect doctest Traceback (most recent call last): ... - ValueError: (Category of rings,) is not a subcategory of - Join of Category of groups and Category of posets + ArithmeticError: Cannot build log(x) since log(x) is not in + Growth Group x^QQ. :: - sage: G = agg.GenericGrowthGroup('42') + sage: G = GrowthGroup('exp(x)^ZZ * x^ZZ') + sage: log(G('exp(x)'), base=2) Traceback (most recent call last): ... - TypeError: 42 is not a valid base + ArithmeticError: When calculating log(exp(x), base=2) a factor + 1/log(2) != 1 appeared, which is not contained in + Growth Group exp(x)^ZZ * x^ZZ. """ - if not isinstance(base, sage.structure.parent.Parent): - raise TypeError('%s is not a valid base' % (base,)) - from sage.categories.groups import Groups - from sage.categories.posets import Posets - - if category is None: - category = Groups() & Posets() + if self.is_one(): + return tuple() + coefficient = self.exponent + + var = str(self.parent()._var_) + + from misc import split_str_by_op + split = split_str_by_op(var, '^') + if len(split) == 2: + b, e = split + if base is None and b == 'e' or \ + base is not None and b == str(base): + return ((e, coefficient),) + + if var.startswith('exp('): + assert(var[-1] == ')') + v = var[4:-1] else: - if not isinstance(category, tuple): - category = (category,) - if not any(cat.is_subcategory(Groups() & Posets()) for cat in - category): - raise ValueError('%s is not a subcategory of %s' - % (category, Groups() & Posets())) - super(GenericGrowthGroup, self).__init__(category=category, - base=base) + v = 'log(%s)' % (var,) + if base is not None: + from sage.functions.log import log + coefficient = coefficient / log(base) + return ((v, coefficient),) - def _repr_short_(self): + + def _rpow_element_(self, base): r""" - A short representation string of this abstract growth group. + Return an element which is the power of ``base`` to this + element. INPUT: - Nothing. + - ``base`` -- an element. OUTPUT: - A string. - - EXAMPLES:: - - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.GenericGrowthGroup(QQ)._repr_short_() - 'Generic(QQ)' - sage: agg.GenericGrowthGroup(QQ) - Growth Group Generic(QQ) - """ - return 'Generic(%s)' % (parent_to_repr_short(self.base()),) + A growth element. + .. NOTE:: - def _repr_(self, condense=False): - r""" - A representations string of this growth group. + The parent of the result can be different from the parent + of this element. - INPUT: + A ``ValueError`` is raised if the calculation is not possible + within this method. (Then the calling method should take care + of the calculation.) - - ``condense`` -- (default: ``False``) if set, then a shorter - output is returned, e.g. the prefix-string ``Growth Group`` - is not show in this case. + TESTS:: - OUTPUT: + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: x = G('x') + sage: x._rpow_element_(2) + Traceback (most recent call last): + ... + ValueError: Variable x is not a log of something. - A string. + The previous example does not work since the result would not + live in a monomial growth group. When using + :meth:`~GenericGrowthElement.rpow`, this + case is handeled by the calling method :meth:`_rpow_`. - EXAMPLES:: + :: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.MonomialGrowthGroup(ZZ, 'x') # indirect doctest + sage: G = GrowthGroup('log(x)^ZZ') + sage: lx = G(raw_element=1); lx + log(x) + sage: rp = lx._rpow_element_('e'); rp + x + sage: rp.parent() Growth Group x^ZZ - sage: agg.MonomialGrowthGroup(QQ, 'log(x)') # indirect doctest - Growth Group log(x)^QQ - TESTS:: + :: - sage: agg.MonomialGrowthGroup(QQ, 'log(x)')._repr_(condense=True) - 'log(x)^QQ' + sage: G = GrowthGroup('log(x)^SR') + sage: lx = G('log(x)') + sage: lx._rpow_element_(2) + x^(log(2)) """ - pre = 'Growth Group ' if not condense else '' - return '%s%s' % (pre, self._repr_short_()) + var = str(self.parent()._var_) + if not(var.startswith('log(') and self.exponent.is_one()): + raise ValueError('Variable %s is not a log of something.' % (var,)) + new_var = var[4:-1] + if base == 'e': + from sage.rings.integer_ring import ZZ + M = MonomialGrowthGroup(ZZ, new_var) + return M(raw_element=ZZ(1)) + else: + from sage.functions.log import log + new_exponent = log(base) + M = MonomialGrowthGroup(new_exponent.parent(), new_var) + return M(raw_element=new_exponent) - def __hash__(self): + def _le_(self, other): r""" - Return the hash of this group. + Return whether this :class:`MonomialGrowthElement` is at most + (less than or equal to) ``other``. INPUT: - Nothing. + - ``other`` -- a :class:`MonomialGrowthElement`. OUTPUT: - An integer. + A boolean. - EXAMPLES:: + .. NOTE:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: hash(agg.GenericGrowthGroup(ZZ)) # random - 4242424242424242 + This function compares two instances of + :class:`MonomialGrowthElement`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_ZZ = GrowthGroup('x^ZZ') + sage: P_QQ = GrowthGroup('x^QQ') + sage: P_ZZ.gen() <= P_QQ.gen()^2 # indirect doctest + True """ - return hash((self.__class__, self.base())) + return self.exponent <= other.exponent - def _an_element_(self): + def _substitute_(self, rules): r""" - Return an element of ``self``. + Substitute the given ``rules`` in this monomial growth element. INPUT: - Nothing. + - ``rules`` -- a dictionary. + The neutral element of the group is replaced by the value + to key ``'_one_'``. OUTPUT: - An element of ``self``. + An object. - EXAMPLES:: + TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.GenericGrowthGroup(ZZ).an_element() # indirect doctest - GenericGrowthElement(1) - sage: agg.MonomialGrowthGroup(ZZ, 'z').an_element() # indirect doctest - z - sage: agg.MonomialGrowthGroup(QQ, 'log(z)').an_element() # indirect doctest - log(z)^(1/2) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: G(x^42)._substitute_({'x': SR.var('z')}) + z^42 + sage: _.parent() + Symbolic Ring + sage: G(x^3)._substitute_({'x': 2}) + 8 + sage: _.parent() + Integer Ring + sage: G(1 / x)._substitute_({'x': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in x^(-1) in Growth Group x^ZZ. + > *previous* ZeroDivisionError: rational division by zero + sage: G(1)._substitute_({'_one_': 'one'}) + 'one' """ - return self.element_class(self, self.base().an_element()) + if self.is_one(): + return rules['_one_'] + try: + return self.parent()._var_._substitute_(rules) ** self.exponent + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) - def some_elements(self): - r""" - Return some elements of this growth group. +class MonomialGrowthGroup(GenericGrowthGroup): + r""" + A growth group dealing with powers of a fixed object/symbol. - See :class:`TestSuite` for a typical use case. + The elements :class:`MonomialGrowthElement` of this group represent powers + of a fixed base; the group law is the multiplication, which corresponds + to the addition of the exponents of the monomials. - INPUT: + INPUT: - Nothing. + - ``base`` -- one of SageMath's parents, out of which the elements + get their data (``raw_element``). - OUTPUT: + As monomials are represented by this group, the elements in + ``base`` are the exponents of these monomials. - An iterator. + - ``var`` -- an object. - EXAMPLES:: + The string representation of ``var`` acts as a base of the + monomials represented by this group. - sage: import sage.rings.asymptotic.growth_group as agg - sage: tuple(agg.MonomialGrowthGroup(ZZ, 'z').some_elements()) - (1, z, 1/z, z^2, z^(-2), z^3, z^(-3), - z^4, z^(-4), z^5, z^(-5), ...) - sage: tuple(agg.MonomialGrowthGroup(QQ, 'z').some_elements()) - (z^(1/2), z^(-1/2), z^2, z^(-2), - 1, z, 1/z, z^42, - z^(2/3), z^(-2/3), z^(3/2), z^(-3/2), - z^(4/5), z^(-4/5), z^(5/4), z^(-5/4), ...) - """ - return iter(self.element_class(self, e) - for e in self.base().some_elements()) + - ``category`` -- (default: ``None``) the category of the newly + created growth group. It has to be a subcategory of ``Join of + Category of groups and Category of posets``. This is also the + default category if ``None`` is specified. + EXAMPLES:: - def le(self, left, right): - r""" - Return if the growth of ``left`` is at most (less than or - equal to) the growth of ``right``. + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: P = MonomialGrowthGroup(ZZ, 'x'); P + Growth Group x^ZZ + sage: MonomialGrowthGroup(ZZ, log(SR.var('y'))) + Growth Group log(y)^ZZ - INPUT: + .. SEEALSO:: - - ``left`` -- an element. + :class:`GenericGrowthGroup` - - ``right`` -- an element. + TESTS:: - OUTPUT: + sage: L1 = MonomialGrowthGroup(QQ, log(x)) + sage: L2 = MonomialGrowthGroup(QQ, 'log(x)') + sage: L1 is L2 + True + """ - A boolean. + # enable the category framework for elements + Element = MonomialGrowthElement - .. NOTE:: - This function uses the coercion model to find a common - parent for the two operands. + # set everything up to determine category + from sage.categories.sets_cat import Sets + from sage.categories.posets import Posets + from sage.categories.magmas import Magmas + from sage.categories.additive_magmas import AdditiveMagmas - EXAMPLES:: + _determine_category_subcategory_mapping_ = [ + (Sets(), Sets(), True), + (Posets(), Posets(), False), + (AdditiveMagmas(), Magmas(), False)] - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.MonomialGrowthGroup(ZZ, 'x') - sage: x = G.gen() - sage: G.le(x, x^2) - True - sage: G.le(x^2, x) - False - sage: G.le(x^0, 1) - True - """ - return self(left) <= self(right) + _determine_category_axiom_mapping_ = [ + ('AdditiveAssociative', 'Associative', False), + ('AdditiveUnital', 'Unital', False), + ('AdditiveInverse', 'Inverse', False), + ('AdditiveCommutative', 'Commutative', False)] - def one(self): + def _repr_short_(self): r""" - Return the neutral element of this growth group. + A short representation string of this monomial growth group. INPUT: @@ -729,258 +2884,333 @@ def one(self): OUTPUT: - An element of this group. + A string. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: e1 = agg.MonomialGrowthGroup(ZZ, 'x').one(); e1 - 1 - sage: e1.is_idempotent() - True + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup + sage: MonomialGrowthGroup(ZZ, 'a') # indirect doctest + Growth Group a^ZZ + + + TESTS:: + + sage: MonomialGrowthGroup(ZZ, 'a')._repr_short_() + 'a^ZZ' + sage: MonomialGrowthGroup(QQ, 'a')._repr_short_() + 'a^QQ' + sage: MonomialGrowthGroup(PolynomialRing(QQ, 'x'), 'a')._repr_short_() + 'a^QQ[x]' """ - return self(1) + from misc import parent_to_repr_short, repr_op + return repr_op(self._var_, '^', parent_to_repr_short(self.base())) - def _element_constructor_(self, data, raw_element=None): + def _convert_(self, data): r""" - Convert a given object to this growth group. + Convert ``data`` to something the constructor of the + element class accepts (``raw_element``). INPUT: - - ``data`` -- an object representing the element to be - initialized. - - - ``raw_element`` -- (default: ``None``) if given, then this is - directly passed to the element constructor (i.e., no conversion - is performed). + - ``data`` -- an object. OUTPUT: - An element of this growth group. - - .. NOTE:: - - Either ``data`` or ``raw_element`` has to be given. If - ``raw_element`` is specified, then no positional argument - may be passed. - - This method calls :meth:`_convert_`, which does the actual - conversion from ``data``. + An element of the base ring or ``None`` (when no such element + can be constructed). TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.GenericGrowthGroup(ZZ) - sage: z = G_ZZ(raw_element=42); z # indirect doctest - GenericGrowthElement(42) - sage: z is G_ZZ(z) # indirect doctest + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^ZZ') + sage: P._convert_('icecream') is None True + sage: P(1) # indirect doctest + 1 + sage: P('x') # indirect doctest + x :: - sage: G_QQ = agg.GenericGrowthGroup(QQ) - sage: q = G_QQ(raw_element=42) # indirect doctest - sage: q is z - False - sage: G_ZZ(q) # indirect doctest - GenericGrowthElement(42) - sage: G_QQ(z) # indirect doctest - GenericGrowthElement(42) - sage: q is G_ZZ(q) # indirect doctest - False + sage: P(x) # indirect doctest + x + sage: P(x^-333) # indirect doctest + x^(-333) + sage: P(log(x)^2) # indirect doctest + Traceback (most recent call last): + ... + ValueError: log(x)^2 is not in Growth Group x^ZZ. :: - sage: G_ZZ() - Traceback (most recent call last): - ... - ValueError: No input specified. Cannot continue. - sage: G_ZZ('blub') # indirect doctest + sage: PR. = ZZ[]; x.parent() + Univariate Polynomial Ring in x over Integer Ring + sage: P(x^2) # indirect doctest + x^2 + + :: + + sage: PSR. = ZZ[[]] + sage: P(x^42) # indirect doctest + x^42 + sage: P(x^12 + O(x^17)) Traceback (most recent call last): ... - ValueError: Cannot convert blub. - sage: G_ZZ('x', raw_element=42) # indirect doctest + ValueError: x^12 + O(x^17) is not in Growth Group x^ZZ. + + :: + + sage: R. = ZZ[] + sage: P(x^4242) # indirect doctest + x^4242 + sage: P(w^4242) # indirect doctest Traceback (most recent call last): ... - ValueError: Input is ambigous: x as well as raw_element=42 are specified. + ValueError: w^4242 is not in Growth Group x^ZZ. :: - sage: G_x = agg.MonomialGrowthGroup(ZZ, 'x') - sage: x = G_x(raw_element=1) # indirect doctest - sage: G_y = agg.MonomialGrowthGroup(ZZ, 'y') - sage: G_y(x) # indirect doctest + sage: PSR. = ZZ[[]] + sage: P(x^7) # indirect doctest + x^7 + sage: P(w^7) # indirect doctest Traceback (most recent call last): ... - ValueError: Cannot convert x. + ValueError: w^7 is not in Growth Group x^ZZ. + + :: + + sage: P('x^7') + x^7 + sage: P('1/x') + x^(-1) + sage: P('x^(-2)') + x^(-2) + sage: P('x^-2') + x^(-2) + + :: + + sage: P('1') + 1 + + :: + + sage: GrowthGroup('x^QQ')(GrowthGroup('x^ZZ')(1)) + 1 """ - if raw_element is None: - if isinstance(data, self.element_class): - if data.parent() == self: - return data - try: - if self._var_ != data.parent()._var_: - raise ValueError('Cannot convert %s.' % (data,)) - except AttributeError: - pass - raw_element = data._raw_element_ - elif isinstance(data, int) and data == 0: - raise ValueError('No input specified. Cannot continue.') - else: - raw_element = self._convert_(data) - if raw_element is None: - raise ValueError('Cannot convert %s.' % (data,)) - elif not isinstance(data, int) or data != 0: - raise ValueError('Input is ambigous: ' - '%s as well as raw_element=%s ' - 'are specified.' % (data, raw_element)) + if data == 1 or data == '1': + return self.base().zero() + var = repr(self._var_) + if str(data) == var: + return self.base().one() - return self.element_class(self, raw_element) + try: + P = data.parent() + except AttributeError: + if var not in str(data): + return # this has to end here + from sage.symbolic.ring import SR + return self._convert_(SR(data)) + + from sage.symbolic.ring import SymbolicRing + from sage.rings.polynomial.polynomial_ring import PolynomialRing_general + from sage.rings.polynomial.multi_polynomial_ring_generic import \ + MPolynomialRing_generic + from sage.rings.power_series_ring import PowerSeriesRing_generic + import operator + if isinstance(P, SymbolicRing): + if data.operator() == operator.pow: + base, exponent = data.operands() + if str(base) == var: + return exponent + elif isinstance(P, (PolynomialRing_general, MPolynomialRing_generic)): + if data.is_monomial() and len(data.variables()) == 1: + if var == str(data.variables()[0]): + return data.degree() + elif isinstance(P, PowerSeriesRing_generic): + if hasattr(data, 'variables') and len(data.variables()) == 1: + from sage.rings.integer_ring import ZZ + if data.is_monomial() and data.precision_absolute() not in ZZ: + if var == str(data.variables()[0]): + return data.degree() + elif len(P.variable_names()) == 1 and \ + var == str(data.variable()[0]): + from sage.rings.integer_ring import ZZ + if data.is_monomial() and data.precision_absolute() not in ZZ: + return data.degree() - def _convert_(self, data): + def gens_monomial(self): r""" - Convert ``data`` to something the constructor of the - element class accepts (``raw_element``). + Return a tuple containing monomial generators of this growth + group. INPUT: - - ``data`` -- an object. + Nothing. OUTPUT: - An element of the base ring or ``None`` (when no such element - can be constructed). + A tuple containing elements of this growth group. .. NOTE:: - This method always returns ``None`` in this abstract base - class, and should be overridden in inherited class. + A generator is called monomial generator if the variable + of the underlying growth group is a valid identifier. For + example, ``x^ZZ`` has ``x`` as a monomial generator, + while ``log(x)^ZZ`` or ``icecream(x)^ZZ`` do not have + monomial generators. TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: G._convert_('icecream') is None - True + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ').gens_monomial() + (x,) + sage: GrowthGroup('log(x)^QQ').gens_monomial() + () """ - pass + if not self._var_.is_monomial(): + return tuple() + return (self(raw_element=self.base().one()),) - def _coerce_map_from_(self, S): + def construction(self): r""" - Return if ``S`` coerces into this growth group. + Return the construction of this growth group. - INPUT: + OUTPUT: - - ``S`` -- a parent. + A pair whose first entry is a + :class:`monomial construction functor ` + and its second entry the base. - OUTPUT: + EXAMPLES:: - A boolean. + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ').construction() + (MonomialGrowthGroup[x], Integer Ring) + """ + return MonomialGrowthGroupFunctor(self._var_), self.base() - .. NOTE:: - Another growth group ``S`` coerces into this growth group - if and only if the base of ``S`` coerces into the base of - this growth group. +class MonomialGrowthGroupFunctor(AbstractGrowthGroupFunctor): + r""" + A :class:`construction functor ` + for :class:`monomial growth groups `. - EXAMPLES:: + INPUT: - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.MonomialGrowthGroup(ZZ, 'x') - sage: G_QQ = agg.MonomialGrowthGroup(QQ, 'x') - sage: bool(G_ZZ.has_coerce_map_from(G_QQ)) # indirect doctest - False - sage: bool(G_QQ.has_coerce_map_from(G_ZZ)) # indirect doctest - True - """ - if isinstance(S, GenericGrowthGroup): - if self.base().has_coerce_map_from(S.base()): - return True + - ``var`` -- a string or list of strings (or anything else + :class:`Variable` accepts). + EXAMPLES:: - def gens_monomial(self): - r""" - Return a monomial generator of this growth group, in case - one exists. + sage: from sage.rings.asymptotic.growth_group import GrowthGroup, MonomialGrowthGroupFunctor + sage: GrowthGroup('z^QQ').construction()[0] + MonomialGrowthGroup[z] - INPUT: + .. SEEALSO:: - Nothing. + :doc:`asymptotic_ring`, + :class:`AbstractGrowthGroupFunctor`, + :class:`ExponentialGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.asymptotic_ring.AsymptoticRingFunctor`, + :class:`sage.categories.pushout.ConstructionFunctor`. - OUTPUT: + TESTS:: - An element of this growth group or ``None``. + sage: from sage.rings.asymptotic.growth_group import GrowthGroup, MonomialGrowthGroupFunctor + sage: cm = sage.structure.element.get_coercion_model() + sage: A = GrowthGroup('x^QQ') + sage: B = MonomialGrowthGroupFunctor('x')(ZZ['t']) + sage: cm.common_parent(A, B) + Growth Group x^QQ[t] + """ - .. NOTE:: + _functor_name = 'MonomialGrowthGroup' - A generator is called monomial generator if the variable - of the underlying growth group is a valid identifier. For - example, ``x^ZZ`` has ``x`` as a monomial generator, - while ``log(x)^ZZ`` or ``icecream(x)^ZZ`` do not have - monomial generators. - This method is only implemented for concrete growth - group implementations. + def __init__(self, var): + r""" + See :class:`MonomialGrowthGroupFunctor` for details. TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.GenericGrowthGroup(ZZ).gens_monomial() - Traceback (most recent call last): - ... - NotImplementedError: Only implemented for concrete growth group - implementations. + sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroupFunctor + sage: MonomialGrowthGroupFunctor('x') + MonomialGrowthGroup[x] """ - raise NotImplementedError("Only implemented for concrete growth group" - " implementations.") + super(MonomialGrowthGroupFunctor, self).__init__(var, + sage.categories.commutative_additive_monoids.CommutativeAdditiveMonoids()) -class MonomialGrowthElement(GenericGrowthElement): + def _apply_functor(self, base): + r""" + Apply this functor to the given ``base``. + + INPUT: + + - ``base`` - anything :class:`MonomialGrowthGroup` accepts. + + OUTPUT: + + A monomial growth group. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F, R = GrowthGroup('z^QQ').construction() + sage: F(R) # indirect doctest + Growth Group z^QQ + """ + return MonomialGrowthGroup(base, self.var) + + +class ExponentialGrowthElement(GenericGrowthElement): r""" - An implementation of monomial growth elements. + An implementation of exponential growth elements. INPUT: - - ``parent`` -- a :class:`MonomialGrowthGroup`. + - ``parent`` -- an :class:`ExponentialGrowthGroup`. - ``raw_element`` -- an element from the base ring of the parent. - This ``raw_element`` is the exponent of the created monomial + This ``raw_element`` is the base of the created exponential growth element. - A monomial growth element represents a term of the type - `\operatorname{variable}^{\operatorname{exponent}}`. The multiplication - corresponds to the addition of the exponents. + An exponential growth element represents a term of the type + `\operatorname{base}^{\operatorname{variable}}`. The multiplication + corresponds to the multiplication of the bases. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') sage: e1 = P(1); e1 1 sage: e2 = P(raw_element=2); e2 - x^2 + 2^x sage: e1 == e2 False sage: P.le(e1, e2) True - sage: P.le(e1, P.gen()) and P.le(P.gen(), e2) + sage: P.le(e1, P(1)) and P.le(P(1), e2) True """ @property - def exponent(self): + def base(self): r""" - The exponent of this growth element. + The base of this exponential growth element. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P(x^42).exponent + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') + sage: P(42^x).base 42 """ return self._raw_element_ @@ -988,7 +3218,7 @@ def exponent(self): def _repr_(self): r""" - A representation string for this monomial growth element. + A representation string for this exponential growth element. INPUT: @@ -1000,72 +3230,75 @@ def _repr_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(QQ, 'x') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('QQ^x') sage: P(1)._repr_() '1' - sage: P(x^5) # indirect doctest - x^5 - sage: P(x^(1/2)) # indirect doctest - x^(1/2) + sage: P(5^x) # indirect doctest + 5^x + sage: P((1/2)^x) # indirect doctest + (1/2)^x TESTS:: - sage: P(x^-1) # indirect doctest - 1/x - sage: P(x^-42) # indirect doctest - x^(-42) + sage: P((-1)^x) # indirect doctest + (-1)^x + + :: + + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: G = ExponentialGrowthGroup(ZZ['x'], 'y'); G + Growth Group ZZ[x]^y + sage: G('(1-x)^y') + (-x + 1)^y + sage: G('(1+x)^y') + (x + 1)^y """ from sage.rings.integer_ring import ZZ + from misc import repr_op - if self.exponent == 0: + var = repr(self.parent()._var_) + if self.base.is_one(): return '1' - elif self.exponent == 1: - return self.parent()._var_ - elif self.exponent == -1: - return '1/' + self.parent()._var_ - elif self.exponent in ZZ and self.exponent > 0: - return self.parent()._var_ + '^' + str(self.exponent) - else: - return self.parent()._var_ + '^(' + str(self.exponent) + ')' + return repr_op(str(self.base), '^', var) def _mul_(self, other): r""" - Multiply this monomial growth element with another. + Multiply this exponential growth element with another. INPUT: - - ``other`` -- a :class:`MonomialGrowthElement` + - ``other`` -- a :class:`ExponentialGrowthElement` OUTPUT: - The product as a :class:`MonomialGrowthElement`. + The product as a :class:`ExponentialGrowthElement`. .. NOTE:: - Two monomial growth elements are multiplied by adding - their exponents. + Two exponential growth elements are multiplied by + multiplying their bases. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: a = P(x^2) - sage: b = P(x^3) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') + sage: a = P(2^x) + sage: b = P(3^x) sage: c = a._mul_(b); c - x^5 - sage: c == a * b + 6^x + sage: c == a*b True - sage: a * b * a # indirect doctest - x^7 + sage: a*b*a # indirect doctest + 12^x """ - return self.parent()(raw_element=self.exponent + other.exponent) + return self.parent()(raw_element=self.base * other.base) def __invert__(self): r""" - Return the multiplicative inverse of this monomial growth element. + Return the multiplicative inverse of this exponential growth element. INPUT: @@ -1073,69 +3306,107 @@ def __invert__(self): OUTPUT: - The multiplicative inverse as a :class:`MonomialGrowthElement`. + The multiplicative inverse as a :class:`ExponentialGrowthElement`. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') sage: e1 = P(raw_element=2) sage: e2 = e1.__invert__(); e2 - x^(-2) + (1/2)^x sage: e2 == ~e1 True + sage: e2.parent() + Growth Group QQ^x + + :: + + sage: (~P(raw_element=1)).parent() + Growth Group QQ^x """ - return self.parent()(raw_element=-self.exponent) + return self.parent()._create_element_in_extension_(1 / self.base) - def __pow__(self, power): + def __pow__(self, exponent): r""" - Raises this growth element to the given ``power``. + Calculate the power of this growth element to the given ``exponent``. INPUT: - - ``power`` -- a number. This can be anything that is a - valid right hand side of ``*`` with elements of the + - ``exponent`` -- a number. This can be anything that is valid to be + on the right hand side of ``*`` with an elements of the parent's base. OUTPUT: - The result of this exponentiation, a :class:`MonomialGrowthElement`. + The result of this exponentiation as an :class:`ExponentialGrowthElement`. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: x = P.gen() - sage: a = x^7; a - x^7 - sage: a^(1/2) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('ZZ^x') + sage: a = P(7^x); a + 7^x + sage: b = a^(1/2); b + sqrt(7)^x + sage: b.parent() + Growth Group SR^x + sage: b^12 + 117649^x + """ + return self.parent()._create_element_in_extension_(self.base ** exponent) + + + def _log_factor_(self, base=None): + r""" + Helper method for calculating the logarithm of the factorization + of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is either a growth + element or something out of which we can construct a growth element + and the second is a multiplicative coefficient. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x') + sage: G('4^x').log_factor(base=2) # indirect doctest Traceback (most recent call last): ... - ValueError: Growth Group x^ZZ disallows taking x^7 to the power of 1/2. - sage: P = agg.MonomialGrowthGroup(QQ, 'x') - sage: b = P.gen()^(7/2); b - x^(7/2) - sage: b^12 - x^42 + ArithmeticError: Cannot build log(4^x, base=2) since x is not in + Growth Group QQ^x. """ - new_exponent = self.exponent * power - P = self.parent() - if new_exponent in P.base(): - return P(raw_element=new_exponent) + if self.is_one(): + return tuple() + b = self.base + if base is None and hasattr(b, 'is_monomial') and b.is_monomial() and \ + b.variable_name() == 'e': + coefficient = b.valuation() + elif base is None and str(b) == 'e': + coefficient = self.parent().base().one() else: - raise ValueError('%s disallows taking %s to the power ' - 'of %s.' % (P, self, power)) + from sage.functions.log import log + coefficient = log(b, base=base) + + return ((str(self.parent()._var_), coefficient),) def _le_(self, other): r""" - Return if this :class:`MonomialGrowthElement` is at most + Return whether this :class:`ExponentialGrowthElement` is at most (less than or equal to) ``other``. INPUT: - - ``other`` -- a :class:`MonomialGrowthElement`. + - ``other`` -- a :class:`ExponentialGrowthElement`. OUTPUT: @@ -1144,39 +3415,78 @@ def _le_(self, other): .. NOTE:: This function compares two instances of - :class:`MonomialGrowthElement`. + :class:`ExponentialGrowthElement`. TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P_ZZ = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P_QQ = agg.MonomialGrowthGroup(QQ, 'x') - sage: P_ZZ.gen() <= P_QQ.gen()^2 # indirect doctest + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P_ZZ = GrowthGroup('ZZ^x') + sage: P_SR = GrowthGroup('SR^x') + sage: P_ZZ(2^x) <= P_SR(sqrt(3)^x)^2 # indirect doctest True """ - return self.exponent <= other.exponent + return bool(abs(self.base) <= abs(other.base)) -class MonomialGrowthGroup(GenericGrowthGroup): + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this exponential growth element. + + INPUT: + + - ``rules`` -- a dictionary. + The neutral element of the group is replaced by the value + to key ``'_one_'``. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x') + sage: G((1/2)^x)._substitute_({'x': SR.var('z')}) + (1/2)^z + sage: _.parent() + Symbolic Ring + sage: G((1/2)^x)._substitute_({'x': 2}) + 1/4 + sage: _.parent() + Rational Field + sage: G(1)._substitute_({'_one_': 'one'}) + 'one' + """ + if self.is_one(): + return rules['_one_'] + try: + return self.base ** self.parent()._var_._substitute_(rules) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + +class ExponentialGrowthGroup(GenericGrowthGroup): r""" - A growth group dealing with powers of a fixed object/symbol. + A growth group dealing with expressions involving a fixed + variable/symbol as the exponent. - The elements :class:`MonomialGrowthElement` of this group represent powers - of a fixed base; the group law is the multiplication, which corresponds - to the addition of the exponents of the monomials. + The elements :class:`ExponentialGrowthElement` of this group + represent exponential functions with bases from a fixed base + ring; the group law is the multiplication. INPUT: - ``base`` -- one of SageMath's parents, out of which the elements get their data (``raw_element``). - As monomials are represented by this group, the elements in - ``base`` are the exponents of these monomials. + As exponential expressions are represented by this group, + the elements in ``base`` are the bases of these exponentials. - ``var`` -- an object. - The string representation of ``var`` acts as a base of the - monomials represented by this group. + The string representation of ``var`` acts as an exponent of the + elements represented by this group. - ``category`` -- (default: ``None``) the category of the newly created growth group. It has to be a subcategory of ``Join of @@ -1185,11 +3495,9 @@ class MonomialGrowthGroup(GenericGrowthGroup): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x'); P - Growth Group x^ZZ - sage: agg.MonomialGrowthGroup(ZZ, log(SR.var('y'))) - Growth Group log(y)^ZZ + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: P = ExponentialGrowthGroup(QQ, 'x'); P + Growth Group QQ^x .. SEEALSO:: @@ -1197,83 +3505,32 @@ class MonomialGrowthGroup(GenericGrowthGroup): """ # enable the category framework for elements - Element = MonomialGrowthElement + Element = ExponentialGrowthElement - @staticmethod - def __classcall__(cls, base, var, category=None): - r""" - Normalizes the input in order to ensure a unique - representation. - - For more information see :class:`MonomialGrowthGroup`. - - TESTS:: - - sage: import sage.rings.asymptotic.growth_group as agg - sage: P1 = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P2 = agg.MonomialGrowthGroup(ZZ, ZZ['x'].gen()) - sage: P3 = agg.MonomialGrowthGroup(ZZ, SR.var('x')) - sage: P1 is P2 and P2 is P3 - True - sage: P4 = agg.MonomialGrowthGroup(ZZ, buffer('xylophone', 0, 1)) - sage: P1 is P4 - True - sage: P5 = agg.MonomialGrowthGroup(ZZ, 'x ') - sage: P1 is P5 - True - - :: - - sage: L1 = agg.MonomialGrowthGroup(QQ, log(x)) - sage: L2 = agg.MonomialGrowthGroup(QQ, 'log(x)') - sage: L1 is L2 - True - """ - var = str(var).strip() - return super(MonomialGrowthGroup, cls).__classcall__( - cls, base, var, category) - - - @sage.misc.superseded.experimental(trac_number=17601) - def __init__(self, base, var, category): - r""" - For more information see :class:`MonomialGrowthGroup`. - - EXAMPLES:: - - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.MonomialGrowthGroup(ZZ, 'x') - Growth Group x^ZZ - sage: agg.MonomialGrowthGroup(QQ, SR.var('n')) - Growth Group n^QQ - sage: agg.MonomialGrowthGroup(ZZ, ZZ['y'].gen()) - Growth Group y^ZZ - sage: agg.MonomialGrowthGroup(QQ, 'log(x)') - Growth Group log(x)^QQ - - TESTS:: + # set everything up to determine category + from sage.categories.sets_cat import Sets + from sage.categories.posets import Posets + from sage.categories.magmas import Magmas + from sage.categories.groups import Groups + from sage.categories.division_rings import DivisionRings - sage: agg.MonomialGrowthGroup('x', ZZ) - Traceback (most recent call last): - ... - TypeError: x is not a valid base - """ - if not var: - raise ValueError('Empty var is not allowed.') - if var[0] in '0123456789=+-*/^%': - # This restriction is mainly for optical reasons on the - # representation. Feel free to relax this if needed. - raise ValueError("The variable name '%s' is inappropriate." % - (var,)) - self._var_ = var + _determine_category_subcategory_mapping_ = [ + (Sets(), Sets(), True), + (Posets(), Posets(), False), + (Magmas(), Magmas(), False), + (DivisionRings(), Groups(), False)] - super(MonomialGrowthGroup, self).__init__(category=category, base=base) + _determine_category_axiom_mapping_ = [ + ('Associative', 'Associative', False), + ('Unital', 'Unital', False), + ('Inverse', 'Inverse', False), + ('Commutative', 'Commutative', False)] def _repr_short_(self): r""" - A short representation string of this monomial growth group. + A short representation string of this exponential growth group. INPUT: @@ -1285,48 +3542,25 @@ def _repr_short_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.MonomialGrowthGroup(ZZ, 'a') # indirect doctest - Growth Group a^ZZ + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroup + sage: ExponentialGrowthGroup(QQ, 'a') # indirect doctest + Growth Group QQ^a TESTS:: - sage: agg.MonomialGrowthGroup(ZZ, 'a')._repr_short_() - 'a^ZZ' - sage: agg.MonomialGrowthGroup(QQ, 'a')._repr_short_() - 'a^QQ' - sage: agg.MonomialGrowthGroup(PolynomialRing(QQ, 'x'), 'a')._repr_short_() - 'a^(Univariate Polynomial Ring in x over Rational Field)' - """ - return '%s^%s' % (self._var_, parent_to_repr_short(self.base())) - - - def __hash__(self): - r""" - Return the hash of this group. - - INPUT: - - Nothing. - - OUTPUT: - - An integer. - - EXAMPLES:: - - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: hash(P) # random - -1234567890123456789 + sage: ExponentialGrowthGroup(QQ, 'a')._repr_short_() + 'QQ^a' + sage: ExponentialGrowthGroup(PolynomialRing(QQ, 'x'), 'a')._repr_short_() + 'QQ[x]^a' """ - return hash((super(MonomialGrowthGroup, self).__hash__(), self._var_)) + from misc import parent_to_repr_short, repr_op + return repr_op(parent_to_repr_short(self.base()), '^', self._var_) def _convert_(self, data): r""" - Convert ``data`` to something the constructor of the + Converts given ``data`` to something the constructor of the element class accepts (``raw_element``). INPUT: @@ -1340,140 +3574,120 @@ def _convert_(self, data): TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('QQ^x') sage: P._convert_('icecream') is None True sage: P(1) # indirect doctest 1 - sage: P('x') # indirect doctest - x + sage: P('2^x') # indirect doctest + 2^x :: - sage: P(x) # indirect doctest - x - sage: P(x^-333) # indirect doctest - x^(-333) - sage: P(log(x)^2) # indirect doctest + sage: P(2^x) # indirect doctest + 2^x + sage: P((-333)^x) # indirect doctest + (-333)^x + sage: P(0) # indirect doctest Traceback (most recent call last): ... - ValueError: Cannot convert log(x)^2. - - :: - - sage: PR. = ZZ[]; x.parent() - Univariate Polynomial Ring in x over Integer Ring - sage: P(x^2) # indirect doctest - x^2 + ValueError: 0 is not in Growth Group QQ^x. :: - sage: PSR. = ZZ[[]] - sage: P(x^42) # indirect doctest - x^42 - sage: P(x^12 + O(x^17)) - Traceback (most recent call last): - ... - ValueError: Cannot convert x^12 + O(x^17). + sage: P('7^x') + 7^x + sage: P('(-2)^x') + (-2)^x :: - sage: R. = ZZ[] - sage: P(x^4242) # indirect doctest - x^4242 - sage: P(w^4242) # indirect doctest - Traceback (most recent call last): - ... - ValueError: Cannot convert w^4242. + sage: P = GrowthGroup('SR^x') + sage: P(sqrt(3)^x) + sqrt(3)^x + sage: P((3^(1/3))^x) + (3^(1/3))^x + sage: P(e^x) + e^x + sage: P(exp(2*x)) + (e^2)^x :: - sage: PSR. = ZZ[[]] - sage: P(x^7) # indirect doctest - x^7 - sage: P(w^7) # indirect doctest - Traceback (most recent call last): - ... - ValueError: Cannot convert w^7. + sage: GrowthGroup('QQ^x')(GrowthGroup('ZZ^x')(1)) + 1 """ - if data == 1: - return self.base().zero() - if str(data) == self._var_: + if data == '1' or isinstance(data, int) and data == 1: return self.base().one() - + var = repr(self._var_) try: P = data.parent() except AttributeError: - return # this has to end here + if data == 1: + return self.base().one() + s = str(data) + if var not in s: + return # this has to end here + + elif s.endswith('^' + var): + return self.base()(s.replace('^' + var, '') + .replace('(', '').replace(')', '')) + else: + return # end of parsing - from sage.symbolic.ring import SR - from sage.rings.polynomial.polynomial_ring import PolynomialRing_general - from sage.rings.polynomial.multi_polynomial_ring_generic import \ - MPolynomialRing_generic - from sage.rings.power_series_ring import PowerSeriesRing_generic + from sage.symbolic.ring import SymbolicRing import operator - if P is SR: - if data.operator() == operator.pow: + from sage.symbolic.operators import mul_vararg + if isinstance(P, SymbolicRing): + op = data.operator() + if op == operator.pow: base, exponent = data.operands() - if str(base) == self._var_: - return exponent - elif isinstance(P, (PolynomialRing_general, MPolynomialRing_generic)): - if data.is_monomial() and len(data.variables()) == 1: - if self._var_ == str(data.variables()[0]): - return data.degree() - elif isinstance(P, PowerSeriesRing_generic): - if hasattr(data, 'variables') and len(data.variables()) == 1: - from sage.rings.integer_ring import ZZ - if data.is_monomial() and data.precision_absolute() not in ZZ: - if self._var_ == str(data.variables()[0]): - return data.degree() - elif len(P.variable_names()) == 1 and \ - self._var_ == str(data.variable()[0]): - from sage.rings.integer_ring import ZZ - if data.is_monomial() and data.precision_absolute() not in ZZ: - return data.degree() + if str(exponent) == var: + return base + elif exponent.operator() == mul_vararg: + return base ** (exponent / P(var)) + elif isinstance(op, sage.functions.log.Function_exp): + from sage.functions.log import exp + base = exp(1) + exponent = data.operands()[0] + if str(exponent) == var: + return base + elif exponent.operator() == mul_vararg: + return base ** (exponent / P(var)) + + elif data == 1: # can be expensive, so let's put it at the end + return self.base().one() - def _coerce_map_from_(self, S): + def some_elements(self): r""" - Return if ``S`` coerces into this growth group. + Return some elements of this exponential growth group. + + See :class:`TestSuite` for a typical use case. INPUT: - - ``S`` -- a parent. + Nothing. OUTPUT: - A boolean. + An iterator. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P_x_ZZ = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P_x_QQ = agg.MonomialGrowthGroup(QQ, 'x') - sage: bool(P_x_ZZ.has_coerce_map_from(P_x_QQ)) # indirect doctest - False - sage: bool(P_x_QQ.has_coerce_map_from(P_x_ZZ)) # indirect doctest - True - sage: P_y_ZZ = agg.MonomialGrowthGroup(ZZ, 'y') - sage: bool(P_y_ZZ.has_coerce_map_from(P_x_ZZ)) # indirect doctest - False - sage: bool(P_x_ZZ.has_coerce_map_from(P_y_ZZ)) # indirect doctest - False - sage: bool(P_y_ZZ.has_coerce_map_from(P_x_QQ)) # indirect doctest - False - sage: bool(P_x_QQ.has_coerce_map_from(P_y_ZZ)) # indirect doctest - False + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: tuple(GrowthGroup('QQ^z').some_elements()) + ((1/2)^z, (-1/2)^z, 2^z, (-2)^z, 1, (-1)^z, + 42^z, (2/3)^z, (-2/3)^z, (3/2)^z, (-3/2)^z, ...) """ - if super(MonomialGrowthGroup, self)._coerce_map_from_(S): - if self._var_ == S._var_: - return True + return iter(self.element_class(self, e) + for e in self.base().some_elements() if e != 0) - def gens_monomial(self): + def gens(self): r""" - Return a tuple containing monomial generators of this growth + Return a tuple of all generators of this exponential growth group. INPUT: @@ -1482,98 +3696,108 @@ def gens_monomial(self): OUTPUT: - A tuple containing elements of this growth group. - - .. NOTE:: - - A generator is called monomial generator if the variable - of the underlying growth group is a valid identifier. For - example, ``x^ZZ`` has ``x`` as a monomial generator, - while ``log(x)^ZZ`` or ``icecream(x)^ZZ`` do not have - monomial generators. + An empty tuple. - TESTS:: + EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.MonomialGrowthGroup(ZZ, 'x').gens_monomial() - (x,) - sage: agg.MonomialGrowthGroup(QQ, 'log(x)').gens_monomial() + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: E = GrowthGroup('ZZ^x') + sage: E.gens() () """ - if self._var_.startswith('log(') and self._var_.endswith(')'): - return () - return (self(raw_element=self.base().one()),) + return tuple() - def gens(self): + def construction(self): r""" - Return a tuple of all generators of this monomial growth - group. - - INPUT: - - Nothing. + Return the construction of this growth group. OUTPUT: - A tuple whose entries are instances of - :class:`MonomialGrowthElement`. + A pair whose first entry is an + :class:`exponential construction functor ` + and its second entry the base. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P.gens() - (x,) - sage: agg.MonomialGrowthGroup(ZZ, 'log(x)').gens() - (log(x),) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('QQ^x').construction() + (ExponentialGrowthGroup[x], Rational Field) """ - return (self(raw_element=self.base().one()),) + return ExponentialGrowthGroupFunctor(self._var_), self.base() - def gen(self, n=0): - r""" - Return the `n`-th generator of this growth group. +class ExponentialGrowthGroupFunctor(AbstractGrowthGroupFunctor): + r""" + A :class:`construction functor ` + for :class:`exponential growth groups `. - INPUT: + INPUT: - - ``n`` -- default: `0`. + - ``var`` -- a string or list of strings (or anything else + :class:`Variable` accepts). - OUTPUT: + EXAMPLES:: - A :class:`MonomialGrowthElement`. + sage: from sage.rings.asymptotic.growth_group import GrowthGroup, ExponentialGrowthGroupFunctor + sage: GrowthGroup('QQ^z').construction()[0] + ExponentialGrowthGroup[z] - EXAMPLES:: + .. SEEALSO:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P.gen() - x + :doc:`asymptotic_ring`, + :class:`AbstractGrowthGroupFunctor`, + :class:`MonomialGrowthGroupFunctor`, + :class:`sage.rings.asymptotic.asymptotic_ring.AsymptoticRingFunctor`, + :class:`sage.categories.pushout.ConstructionFunctor`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup, ExponentialGrowthGroupFunctor + sage: cm = sage.structure.element.get_coercion_model() + sage: A = GrowthGroup('QQ^x') + sage: B = ExponentialGrowthGroupFunctor('x')(ZZ['t']) + sage: cm.common_parent(A, B) + Growth Group QQ[t]^x + """ + + _functor_name = 'ExponentialGrowthGroup' + + + def __init__(self, var): + r""" + See :class:`ExponentialGrowthGroupFunctor` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import ExponentialGrowthGroupFunctor + sage: ExponentialGrowthGroupFunctor('x') + ExponentialGrowthGroup[x] """ - return self.gens()[n] + super(ExponentialGrowthGroupFunctor, self).__init__(var, + sage.categories.monoids.Monoids()) - def ngens(self): + + def _apply_functor(self, base): r""" - Return the number of generators of this monomial growth group. + Apply this functor to the given ``base``. INPUT: - Nothing. + - ``base`` - anything :class:`ExponentialGrowthGroup` accepts. OUTPUT: - A Python integer. + An exponential growth group. EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: P = agg.MonomialGrowthGroup(ZZ, 'x') - sage: P.ngens() - 1 - sage: agg.MonomialGrowthGroup(ZZ, 'log(x)').ngens() - 1 + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: F, R = GrowthGroup('QQ^z').construction() + sage: F(R) # indirect doctest + Growth Group QQ^z """ - return len(self.gens()) + return ExponentialGrowthGroup(base, self.var) class GrowthGroupFactory(sage.structure.factory.UniqueFactory): @@ -1584,38 +3808,156 @@ class GrowthGroupFactory(sage.structure.factory.UniqueFactory): - ``specification`` -- a string. + - keyword arguments are passed on to the growth group + constructor. + If the keyword ``ignore_variables`` is not specified, then + ``ignore_variables=('e',)`` (to ignore ``e`` as a variable name) + is used. + OUTPUT: An asymptotic growth group. + .. NOTE:: + + An instance of this factory is available as ``GrowthGroup``. + EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.GrowthGroup('x^ZZ') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ') Growth Group x^ZZ - sage: agg.GrowthGroup('log(x)^QQ') + sage: GrowthGroup('log(x)^QQ') Growth Group log(x)^QQ + + This factory can also be used to construct Cartesian products + of growth groups:: + + sage: GrowthGroup('x^ZZ * y^ZZ') + Growth Group x^ZZ * y^ZZ + sage: GrowthGroup('x^ZZ * log(x)^ZZ') + Growth Group x^ZZ * log(x)^ZZ + sage: GrowthGroup('x^ZZ * log(x)^ZZ * y^QQ') + Growth Group x^ZZ * log(x)^ZZ * y^QQ + sage: GrowthGroup('QQ^x * x^ZZ * y^QQ * QQ^z') + Growth Group QQ^x * x^ZZ * y^QQ * QQ^z + sage: GrowthGroup('exp(x)^ZZ * x^ZZ') + Growth Group exp(x)^ZZ * x^ZZ + sage: GrowthGroup('(e^x)^ZZ * x^ZZ') + Growth Group (e^x)^ZZ * x^ZZ + + TESTS:: + + sage: G = GrowthGroup('(e^(n*log(n)))^ZZ') + sage: G, G._var_ + (Growth Group (e^(n*log(n)))^ZZ, e^(n*log(n))) + sage: G = GrowthGroup('(e^n)^ZZ') + sage: G, G._var_ + (Growth Group (e^n)^ZZ, e^n) + sage: G = GrowthGroup('(e^(n*log(n)))^ZZ * (e^n)^ZZ * n^ZZ * log(n)^ZZ') + sage: G, tuple(F._var_ for F in G.cartesian_factors()) + (Growth Group (e^(n*log(n)))^ZZ * (e^n)^ZZ * n^ZZ * log(n)^ZZ, + (e^(n*log(n)), e^n, n, log(n))) + + sage: TestSuite(GrowthGroup('x^ZZ')).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_inverse() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass + + :: + + sage: TestSuite(GrowthGroup('QQ^y')).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_inverse() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass + + :: + + sage: TestSuite(GrowthGroup('x^QQ * log(x)^ZZ')).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_inverse() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass """ def create_key_and_extra_args(self, specification, **kwds): r""" Given the arguments and keyword, create a key that uniquely determines this object. - EXAMPLES:: + TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.GrowthGroup.create_key_and_extra_args('x^ZZ') - (('x^ZZ',), {}) - sage: agg.GrowthGroup.create_key_and_extra_args('asdf') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup.create_key_and_extra_args('asdf') Traceback (most recent call last): ... - ValueError: 'asdf' is not a valid string describing a growth group. + ValueError: 'asdf' is not a valid substring of 'asdf' describing a growth group. """ - factors = tuple(s.strip() for s in specification.split('*')) + from misc import split_str_by_op + factors = split_str_by_op(specification, '*') + factors = tuple(f.replace('**', '^') for f in factors) + for f in factors: if '^' not in f: - raise ValueError("'%s' is not a valid string describing " - "a growth group." % (f,)) + raise ValueError("'%s' is not a valid substring of '%s' describing " + "a growth group." % (f, specification)) + + kwds.setdefault('ignore_variables', ('e',)) return factors, kwds @@ -1626,52 +3968,84 @@ def create_object(self, version, factors, **kwds): TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: agg.GrowthGroup('as^df') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('as^df') # indirect doctest + Traceback (most recent call last): + ... + ValueError: 'as^df' is not a valid substring of as^df + describing a growth group. + > *previous* ValueError: Cannot create a parent out of 'as'. + >> *previous* SyntaxError: unexpected EOF while parsing (, line 1) + > *and* ValueError: Cannot create a parent out of 'df'. + >> *previous* NameError: name 'df' is not defined + sage: GrowthGroup('x^y^z') + Traceback (most recent call last): + ... + ValueError: 'x^y^z' is an ambigous substring of + a growth group description of 'x^y^z'. + Use parentheses to make it unique. + sage: GrowthGroup('(x^y)^z') Traceback (most recent call last): ... - ValueError: 'as^df' is not a valid string describing a growth group. - sage: agg.GrowthGroup('x^y^z') + ValueError: '(x^y)^z' is not a valid substring of (x^y)^z + describing a growth group. + > *previous* ValueError: Cannot create a parent out of 'x^y'. + >> *previous* NameError: name 'x' is not defined + > *and* ValueError: Cannot create a parent out of 'z'. + >> *previous* NameError: name 'z' is not defined + sage: GrowthGroup('x^(y^z)') Traceback (most recent call last): ... - ValueError: Cannot decode x^y^z. + ValueError: 'x^(y^z)' is not a valid substring of x^(y^z) + describing a growth group. + > *previous* ValueError: Cannot create a parent out of 'x'. + >> *previous* NameError: name 'x' is not defined + > *and* ValueError: Cannot create a parent out of 'y^z'. + >> *previous* NameError: name 'y' is not defined """ - if len(factors) > 1: - raise NotImplementedError('Cartesian product of growth groups not ' - 'yet implemented.') - # note: implementation already prepared for cartesian products! - + from misc import repr_short_to_parent, split_str_by_op groups = [] for factor in factors: - b_and_e = factor.split('^') - if len(b_and_e) != 2: - raise ValueError('Cannot decode %s.' % (factor,)) - (b, e) = b_and_e + split = split_str_by_op(factor, '^') + if len(split) != 2: + raise ValueError("'%s' is an ambigous substring of a growth group " + "description of '%s'. Use parentheses to make it " + "unique." % (factor, ' * '.join(factors))) + b, e = split try: - # monomial growth group: 'var^base' - groups.append( - MonomialGrowthGroup(repr_short_to_parent(e), b, **kwds)) - continue - except (TypeError, ValueError): - pass - - raise ValueError("'%s' is not a valid string describing " - "a growth group." % (factor,)) - # todo: once exponential growth groups are implemented, - # move line above to the bottom of this loop - + B = repr_short_to_parent(b) + except ValueError as exc_b: + B = None try: - # exponential growth group: 'base^var' - groups.append( - ExponentialGrowthGroup(repr_short_to_parent(b), e, **kwds)) - continue - except (TypeError, ValueError): - pass + E = repr_short_to_parent(e) + except ValueError as exc_e: + E = None + + if B is None and E is None: + from misc import combine_exceptions + raise combine_exceptions( + ValueError("'%s' is not a valid substring of %s describing " + "a growth group." % (factor, ' * '.join(factors))), + exc_b, exc_e) + elif B is None and E is not None: + groups.append(MonomialGrowthGroup(E, b, **kwds)) + elif B is not None and E is None: + groups.append(ExponentialGrowthGroup(B, e, **kwds)) + else: + raise ValueError("'%s' is an ambigous substring of a growth group " + "description of '%s'." % (factor, ' * '.join(factors))) - # todo: groups --> lists with groups over same variable. - return groups[0] + if len(groups) == 1: + return groups[0] + from sage.categories.cartesian_product import cartesian_product + return cartesian_product(groups) -GrowthGroup = GrowthGroupFactory("GrowthGroup") +GrowthGroup = GrowthGroupFactory("GrowthGroup") +r""" +A factory for growth groups. +This is an instance of :class:`GrowthGroupFactory` whose documentation +provides more details. +""" diff --git a/src/sage/rings/asymptotic/growth_group_cartesian.py b/src/sage/rings/asymptotic/growth_group_cartesian.py new file mode 100644 index 00000000000..05d5f2da9b0 --- /dev/null +++ b/src/sage/rings/asymptotic/growth_group_cartesian.py @@ -0,0 +1,1257 @@ +r""" +Cartesian Products of Growth Groups + +See :doc:`growth_group` for a description. + +AUTHORS: + +- Benjamin Hackl (2015) +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + +.. WARNING:: + + As this code is experimental, warnings are thrown when a growth + group is created for the first time in a session (see + :class:`sage.misc.superseded.experimental`). + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup, GrowthGroup + sage: GenericGrowthGroup(ZZ) + doctest:...: FutureWarning: This class/method/function is marked as + experimental. It, its functionality or its interface might change + without a formal deprecation. + See http://trac.sagemath.org/17601 for details. + Growth Group Generic(ZZ) + sage: GrowthGroup('x^ZZ * log(x)^ZZ') + doctest:...: FutureWarning: This class/method/function is marked as + experimental. It, its functionality or its interface might change + without a formal deprecation. + See http://trac.sagemath.org/17601 for details. + Growth Group x^ZZ * log(x)^ZZ + +TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: A = GrowthGroup('QQ^x * x^ZZ'); A + Growth Group QQ^x * x^ZZ + sage: A.construction() + (The cartesian_product functorial construction, + (Growth Group QQ^x, Growth Group x^ZZ)) + sage: A.construction()[1][0].construction() + (ExponentialGrowthGroup[x], Rational Field) + sage: A.construction()[1][1].construction() + (MonomialGrowthGroup[x], Integer Ring) + sage: B = GrowthGroup('x^ZZ * y^ZZ'); B + Growth Group x^ZZ * y^ZZ + sage: B.construction() + (The cartesian_product functorial construction, + (Growth Group x^ZZ, Growth Group y^ZZ)) + sage: C = GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ'); C + Growth Group x^ZZ * log(x)^ZZ * y^ZZ + sage: C.construction() + (The cartesian_product functorial construction, + (Growth Group x^ZZ * log(x)^ZZ, Growth Group y^ZZ)) + sage: C.construction()[1][0].construction() + (The cartesian_product functorial construction, + (Growth Group x^ZZ, Growth Group log(x)^ZZ)) + sage: C.construction()[1][1].construction() + (MonomialGrowthGroup[y], Integer Ring) + +:: + + sage: cm = sage.structure.element.get_coercion_model() + sage: D = GrowthGroup('QQ^x * x^QQ') + sage: cm.common_parent(A, D) + Growth Group QQ^x * x^QQ + sage: E = GrowthGroup('ZZ^x * x^QQ') + sage: cm.record_exceptions() # not tested, see #19411 + sage: cm.common_parent(A, E) + Growth Group QQ^x * x^QQ + sage: for t in cm.exception_stack(): # not tested, see #19411 + ....: print t + +:: + + sage: A.an_element() + (1/2)^x*x + sage: tuple(E.an_element()) + (1, x^(1/2)) + +Classes and Methods +=================== +""" + +#***************************************************************************** +# Copyright (C) 2014--2015 Benjamin Hackl +# 2014--2015 Daniel Krenn +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +import sage + + +class CartesianProductFactory(sage.structure.factory.UniqueFactory): + r""" + Create various types of cartesian products depending on its input. + + INPUT: + + - ``growth_groups`` -- a tuple (or other iterable) of growth groups. + + - ``order`` -- (default: ``None``) if specified, then this order + is taken for comparing two cartesian product elements. If ``order`` is + ``None`` this is determined automatically. + + .. NOTE:: + + The cartesian product of growth groups is again a growth + group. In particular, the resulting structure is partially + ordered. + + The order on the product is determined as follows: + + - Cartesian factors with respect to the same variable are + ordered lexicographically. This causes + ``GrowthGroup('x^ZZ * log(x)^ZZ')`` and + ``GrowthGroup('log(x)^ZZ * x^ZZ')`` to produce two + different growth groups. + + - Factors over different variables are equipped with the + product order (i.e. the comparison is component-wise). + + Also, note that the sets of variables of the cartesian + factors have to be either equal or disjoint. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: A = GrowthGroup('x^ZZ'); A + Growth Group x^ZZ + sage: B = GrowthGroup('log(x)^ZZ'); B + Growth Group log(x)^ZZ + sage: C = cartesian_product([A, B]); C # indirect doctest + Growth Group x^ZZ * log(x)^ZZ + sage: C._le_ == C.le_lex + True + sage: D = GrowthGroup('y^ZZ'); D + Growth Group y^ZZ + sage: E = cartesian_product([A, D]); E # indirect doctest + Growth Group x^ZZ * y^ZZ + sage: E._le_ == E.le_product + True + sage: F = cartesian_product([C, D]); F # indirect doctest + Growth Group x^ZZ * log(x)^ZZ * y^ZZ + sage: F._le_ == F.le_product + True + sage: cartesian_product([A, E]); G # indirect doctest + Traceback (most recent call last): + ... + ValueError: The growth groups (Growth Group x^ZZ, Growth Group x^ZZ * y^ZZ) + need to have pairwise disjoint or equal variables. + sage: cartesian_product([A, B, D]) # indirect doctest + Growth Group x^ZZ * log(x)^ZZ * y^ZZ + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group_cartesian import CartesianProductFactory + sage: CartesianProductFactory('factory')([A, B], category=Groups() & Posets()) + Growth Group x^ZZ * log(x)^ZZ + sage: CartesianProductFactory('factory')([], category=Sets()) + Traceback (most recent call last): + ... + TypeError: Cannot create cartesian product without factors. + """ + def create_key_and_extra_args(self, growth_groups, category, **kwds): + r""" + Given the arguments and keywords, create a key that uniquely + determines this object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group_cartesian import CartesianProductFactory + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: A = GrowthGroup('x^ZZ') + sage: CartesianProductFactory('factory').create_key_and_extra_args( + ....: [A], category=Sets(), order='blub') + (((Growth Group x^ZZ,), Category of sets), {'order': 'blub'}) + """ + return (tuple(growth_groups), category), kwds + + + def create_object(self, version, args, **kwds): + r""" + Create an object from the given arguments. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: cartesian_product([GrowthGroup('x^ZZ')]) # indirect doctest + Growth Group x^ZZ + """ + growth_groups, category = args + if not growth_groups: + raise TypeError('Cannot create cartesian product without factors.') + order = kwds.pop('order', None) + if order is not None: + return GenericProduct(growth_groups, category, order=order, **kwds) + + vg = tuple((g.variable_names(), g) for g in growth_groups) + + # check if all groups have a variable + if not all(v for v, _ in vg): + raise NotImplementedError('Growth groups %s have no variable.' % + tuple(g for g in growth_groups + if not g.variable_names())) + + # sort by variables + from itertools import groupby, product + vgs = tuple((v, tuple(gs)) for v, gs in + groupby(sorted(vg, key=lambda k: k[0]), key=lambda k: k[0])) + + # check whether variables are pairwise disjoint + for u, w in product(iter(v for v, _ in vgs), repeat=2): + if u != w and not set(u).isdisjoint(set(w)): + raise ValueError('The growth groups %s need to have pairwise ' + 'disjoint or equal variables.' % (growth_groups,)) + + # build cartesian products + u_groups = list() + for _, gs in vgs: + gs = tuple(g for _, g in gs) + if len(gs) > 1: + u_groups.append(UnivariateProduct(gs, category, **kwds)) + else: + u_groups.append(gs[0]) + + if len(u_groups) > 1: + m_group = MultivariateProduct(tuple(u_groups), category, **kwds) + else: + m_group = u_groups[0] + return m_group + + +CartesianProductGrowthGroups = CartesianProductFactory('CartesianProductGrowthGroups') + + +from sage.combinat.posets.cartesian_product import CartesianProductPoset +from growth_group import GenericGrowthGroup +class GenericProduct(CartesianProductPoset, GenericGrowthGroup): + r""" + A cartesian product of growth groups. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^QQ') + sage: L = GrowthGroup('log(x)^ZZ') + sage: C = cartesian_product([P, L], order='lex'); C # indirect doctest + Growth Group x^QQ * log(x)^ZZ + sage: C.an_element() + x^(1/2)*log(x) + + :: + + sage: Px = GrowthGroup('x^QQ') + sage: Lx = GrowthGroup('log(x)^ZZ') + sage: Cx = cartesian_product([Px, Lx], order='lex') # indirect doctest + sage: Py = GrowthGroup('y^QQ') + sage: C = cartesian_product([Cx, Py], order='product'); C # indirect doctest + Growth Group x^QQ * log(x)^ZZ * y^QQ + sage: C.an_element() + x^(1/2)*log(x)*y^(1/2) + + .. SEEALSO:: + + :class:`~sage.sets.cartesian_product.CartesianProduct`, + :class:`~sage.combinat.posets.cartesian_product.CartesianProductPoset`. + """ + + __classcall__ = CartesianProductPoset.__classcall__ + + + def __init__(self, sets, category, **kwds): + r""" + See :class:`GenericProduct` for details. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ * y^ZZ') # indirect doctest + Growth Group x^ZZ * y^ZZ + """ + order = kwds.pop('order') + CartesianProductPoset.__init__(self, sets, category, order, **kwds) + + vars = sum(iter(factor.variable_names() + for factor in self.cartesian_factors()), + tuple()) + from itertools import groupby + from growth_group import Variable + Vars = Variable(tuple(v for v, _ in groupby(vars)), repr=self._repr_short_()) + + GenericGrowthGroup.__init__(self, sets[0], Vars, self.category(), **kwds) + + + __hash__ = CartesianProductPoset.__hash__ + + + def some_elements(self): + r""" + Return some elements of this cartesian product of growth groups. + + See :class:`TestSuite` for a typical use case. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from itertools import islice + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^y * x^QQ * log(x)^ZZ') + sage: tuple(islice(G.some_elements(), 10)) + (x^(1/2)*(1/2)^y, + x^(-1/2)*log(x)*(-1/2)^y, + x^2*log(x)^(-1)*2^y, + x^(-2)*log(x)^2*(-2)^y, + log(x)^(-2), + x*log(x)^3*(-1)^y, + x^(-1)*log(x)^(-3)*42^y, + x^42*log(x)^4*(2/3)^y, + x^(2/3)*log(x)^(-4)*(-2/3)^y, + x^(-2/3)*log(x)^5*(3/2)^y) + """ + from itertools import izip + return iter( + self(c) for c in + izip(*tuple(F.some_elements() for F in self.cartesian_factors()))) + + + def _create_element_in_extension_(self, element): + r""" + Create an element in an extension of this cartesian product of + growth groups which is chosen according to the input ``element``. + + INPUT: + + - ``element`` -- a tuple. + + OUTPUT: + + An element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ * log(z)^ZZ') + sage: z = G('z')[0] + sage: lz = G('log(z)')[1] + sage: G._create_element_in_extension_((z^3, lz)).parent() + Growth Group z^ZZ * log(z)^ZZ + sage: G._create_element_in_extension_((z^(1/2), lz)).parent() + Growth Group z^QQ * log(z)^ZZ + + :: + + sage: G._create_element_in_extension_((3, 3, 3)) + Traceback (most recent call last): + ... + ValueError: Cannot create (3, 3, 3) as a cartesian product like + Growth Group z^ZZ * log(z)^ZZ. + """ + factors = self.cartesian_factors() + if len(element) != len(factors): + raise ValueError('Cannot create %s as a cartesian product like %s.' % + (element, self)) + + if all(n.parent() is f for n, f in zip(element, factors)): + parent = self + else: + from misc import underlying_class + parent = underlying_class(self)(tuple(n.parent() for n in element), + category=self.category()) + return parent(element) + + + def _element_constructor_(self, data): + r""" + Converts the given object to an element of this cartesian + product. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * y^ZZ') + sage: G_log = GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ') + + Conversion from the symbolic ring works:: + + sage: x,y = var('x y') + sage: G(x^-3*y^2) + x^(-3)*y^2 + sage: G(x^4), G(y^2) + (x^4, y^2) + sage: G(1) + 1 + + Even more complex expressions can be parsed:: + + sage: G_log(x^42*log(x)^-42*y^42) + x^42*log(x)^(-42)*y^42 + + TESTS:: + + sage: G = GrowthGroup('x^ZZ * y^ZZ') + sage: G('x'), G('y') + (x, y) + + :: + + sage: G_log(log(x)) + log(x) + + :: + + sage: G(G.cartesian_factors()[0].gen()) + x + + :: + + sage: GrowthGroup('QQ^x * x^QQ')(['x^(1/2)']) + x^(1/2) + sage: l = GrowthGroup('x^ZZ * log(x)^ZZ')(['x', 'log(x)']); l + x*log(x) + sage: type(l) + + sage: GrowthGroup('QQ^x * x^QQ')(['2^log(x)']) + Traceback (most recent call last): + ... + ValueError: ['2^log(x)'] is not in Growth Group QQ^x * x^QQ. + > *previous* ValueError: 2^log(x) is not in any of the factors of + Growth Group QQ^x * x^QQ + sage: GrowthGroup('QQ^x * x^QQ')(['2^log(x)', 'x^55']) + Traceback (most recent call last): + ... + ValueError: ['2^log(x)', 'x^55'] is not in Growth Group QQ^x * x^QQ. + > *previous* ValueError: 2^log(x) is not in any of the factors of + Growth Group QQ^x * x^QQ + + TESTS:: + + sage: n = GrowthGroup('n^ZZ * log(n)^ZZ')('n') + sage: G = GrowthGroup('QQ^n * n^ZZ * log(n)^ZZ') + sage: G(n).value + (1, n, 1) + """ + def convert_factors(data, raw_data): + try: + return self._convert_factors_(data) + except ValueError as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('%s is not in %s.' % (raw_data, self)), e) + + if data == 1: + return self.one() + + elif data is None: + raise ValueError('%s cannot be converted.' % (data,)) + + elif type(data) == self.element_class and data.parent() == self: + return data + + elif isinstance(data, str): + from misc import split_str_by_op + return convert_factors(split_str_by_op(data, '*'), data) + + elif hasattr(data, 'parent'): + P = data.parent() + + if P is self: + return data + + elif P is sage.symbolic.ring.SR: + from sage.symbolic.operators import mul_vararg + if data.operator() == mul_vararg: + return convert_factors(data.operands(), data) + + # room for other parents (e.g. polynomial ring et al.) + + try: + return super(GenericProduct, self)._element_constructor_(data) + except (TypeError, ValueError): + pass + if isinstance(data, (tuple, list, + sage.sets.cartesian_product.CartesianProduct.Element)): + return convert_factors(tuple(data), data) + + return convert_factors((data,), data) + + + _repr_ = GenericGrowthGroup._repr_ + + + def _repr_short_(self): + r""" + A short (shorter than :meth:`._repr_`) representation string + for this cartesian product of growth groups. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^QQ') + sage: L = GrowthGroup('log(x)^ZZ') + sage: cartesian_product([P, L], order='lex')._repr_short_() + 'x^QQ * log(x)^ZZ' + """ + return ' * '.join(S._repr_short_() for S in self.cartesian_factors()) + + + def _convert_factors_(self, factors): + r""" + Helper method. Try to convert some ``factors`` to an + element of one of the cartesian factors and return the product of + all these factors. + + INPUT: + + - ``factors`` -- a tuple or other iterable. + + OUTPUT: + + An element of this cartesian product. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^QQ * y^QQ') + sage: e1 = G._convert_factors_([x^2]) + sage: (e1, e1.parent()) + (x^2, Growth Group x^ZZ * log(x)^QQ * y^QQ) + """ + from sage.misc.misc_c import prod + + def get_factor(data): + for factor in self.cartesian_factors(): + try: + return factor, factor(data) + except (ValueError, TypeError): + pass + raise ValueError('%s is not in any of the factors of %s' % (data, self)) + + return prod(self.cartesian_injection(*get_factor(f)) + for f in factors) + + + def cartesian_injection(self, factor, element): + r""" + Inject the given element into this cartesian product at the given factor. + + INPUT: + + - ``factor`` -- a growth group (a factor of this cartesian product). + + - ``element`` -- an element of ``factor``. + + OUTPUT: + + An element of this cartesian product. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * y^QQ') + sage: G.cartesian_injection(G.cartesian_factors()[1], 'y^7') + y^7 + """ + return self(tuple((f.one() if f != factor else element) + for f in self.cartesian_factors())) + + + def _coerce_map_from_(self, S): + r""" + Return whether ``S`` coerces into this growth group. + + INPUT: + + - ``S`` -- a parent. + + OUTPUT: + + A boolean. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: A = GrowthGroup('QQ^x * x^QQ') + sage: B = GrowthGroup('QQ^x * x^ZZ') + sage: A.has_coerce_map_from(B) # indirect doctest + True + sage: B.has_coerce_map_from(A) # indirect doctest + False + """ + if CartesianProductPoset.has_coerce_map_from(self, S): + return True + + elif isinstance(S, GenericProduct): + factors = S.cartesian_factors() + else: + factors = (S,) + + if all(any(g.has_coerce_map_from(f) for g in self.cartesian_factors()) + for f in factors): + return True + + + def _pushout_(self, other): + r""" + Construct the pushout of this and the other growth group. This is called by + :func:`sage.categories.pushout.pushout`. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.categories.pushout import pushout + sage: cm = sage.structure.element.get_coercion_model() + sage: A = GrowthGroup('QQ^x * x^ZZ') + sage: B = GrowthGroup('x^ZZ * log(x)^ZZ') + sage: A._pushout_(B) + Growth Group QQ^x * x^ZZ * log(x)^ZZ + sage: pushout(A, B) + Growth Group QQ^x * x^ZZ * log(x)^ZZ + sage: cm.discover_coercion(A, B) + ((map internal to coercion system -- copy before use) + Conversion map: + From: Growth Group QQ^x * x^ZZ + To: Growth Group QQ^x * x^ZZ * log(x)^ZZ, + (map internal to coercion system -- copy before use) + Conversion map: + From: Growth Group x^ZZ * log(x)^ZZ + To: Growth Group QQ^x * x^ZZ * log(x)^ZZ) + sage: cm.common_parent(A, B) + Growth Group QQ^x * x^ZZ * log(x)^ZZ + + :: + + sage: C = GrowthGroup('QQ^x * x^QQ * y^ZZ') + sage: D = GrowthGroup('x^ZZ * log(x)^QQ * QQ^z') + sage: C._pushout_(D) + Growth Group QQ^x * x^QQ * log(x)^QQ * y^ZZ * QQ^z + sage: cm.common_parent(C, D) + Growth Group QQ^x * x^QQ * log(x)^QQ * y^ZZ * QQ^z + sage: A._pushout_(D) + Growth Group QQ^x * x^ZZ * log(x)^QQ * QQ^z + sage: cm.common_parent(A, D) + Growth Group QQ^x * x^ZZ * log(x)^QQ * QQ^z + sage: cm.common_parent(B, D) + Growth Group x^ZZ * log(x)^QQ * QQ^z + sage: cm.common_parent(A, C) + Growth Group QQ^x * x^QQ * y^ZZ + sage: E = GrowthGroup('log(x)^ZZ * y^ZZ') + sage: cm.common_parent(A, E) + Traceback (most recent call last): + ... + TypeError: no common canonical parent for objects with parents: + 'Growth Group QQ^x * x^ZZ' and 'Growth Group log(x)^ZZ * y^ZZ' + + :: + + sage: F = GrowthGroup('z^QQ') + sage: pushout(C, F) + Growth Group QQ^x * x^QQ * y^ZZ * z^QQ + + :: + + sage: pushout(GrowthGroup('QQ^x * x^ZZ'), GrowthGroup('ZZ^x * x^QQ')) + Growth Group QQ^x * x^QQ + sage: cm.common_parent(GrowthGroup('QQ^x * x^ZZ'), GrowthGroup('ZZ^x * x^QQ')) + Growth Group QQ^x * x^QQ + """ + from growth_group import GenericGrowthGroup, AbstractGrowthGroupFunctor + from misc import merge_overlapping + from misc import underlying_class + + if isinstance(other, GenericProduct): + Ofactors = other.cartesian_factors() + elif isinstance(other, GenericGrowthGroup): + Ofactors = (other,) + elif (other.construction() is not None and + isinstance(other.construction()[0], AbstractGrowthGroupFunctor)): + Ofactors = (other,) + else: + return + + def pushout_univariate_factors(self, other, var, Sfactors, Ofactors): + try: + return merge_overlapping( + Sfactors, Ofactors, + lambda f: (underlying_class(f), f._var_.var_repr)) + except ValueError: + pass + + cm = sage.structure.element.get_coercion_model() + try: + Z = cm.common_parent(*Sfactors+Ofactors) + return (Z,), (Z,) + except TypeError: + pass + + def subfactors(F): + for f in F: + if isinstance(f, GenericProduct): + for g in subfactors(f.cartesian_factors()): + yield g + else: + yield f + + try: + return merge_overlapping( + tuple(subfactors(Sfactors)), tuple(subfactors(Ofactors)), + lambda f: (underlying_class(f), f._var_.var_repr)) + except ValueError: + pass + + from sage.structure.coerce_exceptions import CoercionException + raise CoercionException( + 'Cannot construct the pushout of %s and %s: The factors ' + 'with variables %s are not overlapping, ' + 'no common parent was found, and ' + 'splitting the factors was unsuccessful.' % (self, other, var)) + + + class it: + def __init__(self, it): + self.it = it + self.var = None + self.factors = None + def next(self): + try: + self.var, factors = next(self.it) + self.factors = tuple(factors) + except StopIteration: + self.var = None + self.factors = tuple() + + from itertools import groupby + S = it(groupby(self.cartesian_factors(), key=lambda k: k.variable_names())) + O = it(groupby(Ofactors, key=lambda k: k.variable_names())) + + newS = [] + newO = [] + + S.next() + O.next() + while S.var is not None or O.var is not None: + if S.var is not None and S.var < O.var: + newS.extend(S.factors) + newO.extend(S.factors) + S.next() + elif O.var is not None and S.var > O.var: + newS.extend(O.factors) + newO.extend(O.factors) + O.next() + else: + SL, OL = pushout_univariate_factors(self, other, S.var, + S.factors, O.factors) + newS.extend(SL) + newO.extend(OL) + S.next() + O.next() + + assert(len(newS) == len(newO)) + + if (len(self.cartesian_factors()) == len(newS) and + len(other.cartesian_factors()) == len(newO)): + # We had already all factors in each of the self and + # other, thus splitting it in subproblems (one for + # each factor) is the strategy to use. If a pushout is + # possible :func:`sage.categories.pushout.pushout` + # will manage this by itself. + return + + from sage.categories.pushout import pushout + from sage.categories.cartesian_product import cartesian_product + return pushout(cartesian_product(newS), cartesian_product(newO)) + + + def gens_monomial(self): + r""" + Return a tuple containing monomial generators of this growth group. + + INPUT: + + Nothing. + + OUTPUT: + + A tuple containing elements of this growth group. + + .. NOTE:: + + This method calls the ``gens_monomial()`` method on the + individual factors of this cartesian product and + concatenates the respective outputs. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ * y^QQ * log(z)^ZZ') + sage: G.gens_monomial() + (x, y) + + TESTS:: + + sage: all(g.parent() == G for g in G.gens_monomial()) + True + """ + return sum(iter( + tuple(self.cartesian_injection(factor, g) for g in factor.gens_monomial()) + for factor in self.cartesian_factors()), + tuple()) + + + def variable_names(self): + r""" + Return the names of the variables. + + OUTPUT: + + A tuple of strings. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: GrowthGroup('x^ZZ * log(x)^ZZ * y^QQ * log(z)^ZZ').variable_names() + ('x', 'y', 'z') + """ + vars = sum(iter(factor.variable_names() + for factor in self.cartesian_factors()), + tuple()) + from itertools import groupby + return tuple(v for v, _ in groupby(vars)) + + + class Element(CartesianProductPoset.Element): + + from growth_group import _is_lt_one_ + is_lt_one = _is_lt_one_ + + + def _repr_(self): + r""" + A representation string for this cartesian product element. + + INPUT: + + Nothing. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: P = GrowthGroup('x^QQ') + sage: L = GrowthGroup('log(x)^ZZ') + sage: cartesian_product([P, L], order='lex').an_element()._repr_() + 'x^(1/2)*log(x)' + """ + s = '*'.join(repr(v) for v in self.value if not v.is_one()) + if s == '': + return '1' + return s + + + def __pow__(self, exponent): + r""" + Calculate the power of this growth element to the given + ``exponent``. + + INPUT: + + - ``exponent`` -- a number. + + OUTPUT: + + A growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * y^QQ * z^ZZ') + sage: x, y, z = G.gens_monomial() + sage: (x^5 * y * z^5)^(1/5) # indirect doctest + x*y^(1/5)*z + + :: + + sage: G = GrowthGroup('x^QQ * log(x)^QQ'); x = G('x') + sage: (x^(21/5) * log(x)^7)^(1/42) # indirect doctest + x^(1/10)*log(x)^(1/6) + """ + return self.parent()._create_element_in_extension_( + tuple(x ** exponent for x in self.cartesian_factors())) + + + def factors(self): + r""" + Return the atomic factors of this growth element. An atomic factor + cannot be split further and is not the identity (`1`). + + INPUT: + + Nothing. + + OUTPUT: + + A tuple of growth elements. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ') + sage: x, y = G.gens_monomial() + sage: x.factors() + (x,) + sage: f = (x * y).factors(); f + (x, y) + sage: tuple(factor.parent() for factor in f) + (Growth Group x^ZZ, Growth Group y^ZZ) + sage: f = (x * log(x)).factors(); f + (x, log(x)) + sage: tuple(factor.parent() for factor in f) + (Growth Group x^ZZ, Growth Group log(x)^ZZ) + + :: + + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ * log(log(x))^ZZ * y^QQ') + sage: x, y = G.gens_monomial() + sage: f = (x * log(x) * y).factors(); f + (x, log(x), y) + sage: tuple(factor.parent() for factor in f) + (Growth Group x^ZZ, Growth Group log(x)^ZZ, Growth Group y^QQ) + + :: + + sage: G.one().factors() + () + """ + return sum(iter(f.factors() + for f in self.cartesian_factors() + if f != f.parent().one()), + tuple()) + + + from growth_group import _log_factor_, _log_ + log = _log_ + log_factor = _log_factor_ + + + def _log_factor_(self, base=None): + r""" + Helper method for calculating the logarithm of the factorization + of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of pairs, where the first entry is either a growth + element or something out of which we can construct a growth element + and the second a multiplicative coefficient. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ') + sage: x, y = G.gens_monomial() + sage: (x * y).log_factor() # indirect doctest + ((log(x), 1), (log(y), 1)) + """ + if self.is_one(): + return tuple() + + def try_create_growth(g): + try: + return self.parent()(g) + except (TypeError, ValueError): + return g + + try: + return sum(iter(tuple((try_create_growth(g), c) + for g, c in factor._log_factor_(base=base)) + for factor in self.cartesian_factors() + if factor != factor.parent().one()), + tuple()) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ArithmeticError('Cannot build log(%s) in %s.' % + (self, self.parent())), e) + + + from growth_group import _rpow_ + rpow = _rpow_ + + + def _rpow_element_(self, base): + r""" + Return an element which is the power of ``base`` to this + element. + + INPUT: + + - ``base`` -- an element. + + OUTPUT: + + A growth element. + + .. NOTE:: + + The parent of the result can be different from the parent + of this element. + + A ``ValueError`` is raised if the calculation is not possible + within this method. (Then the calling method should take care + of the calculation.) + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ') + sage: lx = log(G('x')) + sage: rp = lx._rpow_element_('e'); rp + x + sage: rp.parent() + Growth Group x^ZZ + """ + factors = self.factors() + if len(factors) != 1: + raise ValueError # calling method has to deal with it... + from growth_group import MonomialGrowthGroup + factor = factors[0] + if not isinstance(factor.parent(), MonomialGrowthGroup): + raise ValueError # calling method has to deal with it... + return factor._rpow_element_(base) + + + def exp(self): + r""" + The exponential of this element. + + INPUT: + + Nothing. + + OUTPUT: + + A growth element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ * log(log(x))^ZZ') + sage: x = G('x') + sage: exp(log(x)) + x + sage: exp(log(log(x))) + log(x) + + :: + + sage: exp(x) + Traceback (most recent call last): + ... + ArithmeticError: Cannot construct e^x in + Growth Group x^ZZ * log(x)^ZZ * log(log(x))^ZZ + > *previous* TypeError: unsupported operand parent(s) for '*': + 'Growth Group x^ZZ * log(x)^ZZ * log(log(x))^ZZ' and + 'Growth Group (e^x)^ZZ' + + TESTS:: + + sage: E = GrowthGroup("(e^y)^QQ * y^QQ * log(y)^QQ") + sage: y = E('y') + sage: log(exp(y)) + y + sage: exp(log(y)) + y + """ + return self.rpow('e') + + + def __invert__(self): + r""" + Return the multiplicative inverse of this cartesian product. + + OUTPUT: + + An growth element. + + .. NOTE:: + + The result may live in a larger parent than we started with. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('ZZ^x * x^ZZ') + sage: g = G('2^x * x^3') + sage: (~g).parent() + Growth Group QQ^x * x^ZZ + """ + return self.parent()._create_element_in_extension_( + tuple(~x for x in self.cartesian_factors())) + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this + cartesian product growth element. + + INPUT: + + - ``rules`` -- a dictionary. + The neutral element of the group is replaced by the value + to key ``'_one_'``. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^QQ * log(x)^QQ') + sage: G(x^3 * log(x)^5)._substitute_({'x': SR.var('z')}) + z^3*log(z)^5 + sage: _.parent() + Symbolic Ring + sage: G(x^3 * log(x)^5)._substitute_({'x': 2.2}) # rel tol 1e-6 + 3.24458458945 + sage: _.parent() + Real Field with 53 bits of precision + sage: G(1 / x)._substitute_({'x': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in x^(-1) in + Growth Group x^QQ * log(x)^QQ. + > *previous* ZeroDivisionError: Cannot substitute in x^(-1) in + Growth Group x^QQ. + >> *previous* ZeroDivisionError: rational division by zero + sage: G(1)._substitute_({'_one_': 'one'}) + 'one' + """ + if self.is_one(): + return rules['_one_'] + from sage.symbolic.operators import mul_vararg + try: + return mul_vararg( + *tuple(x._substitute_(rules) + for x in self.cartesian_factors())) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + + CartesianProduct = CartesianProductGrowthGroups + + +class UnivariateProduct(GenericProduct): + r""" + A cartesian product of growth groups with the same variables. + + .. NOTE:: + + A univariate product of growth groups is ordered + lexicographically. This is motivated by the assumption + that univariate growth groups can be ordered in a chain + with respect to the growth they model (e.g. + ``x^ZZ * log(x)^ZZ``: polynomial growth dominates + logarithmic growth). + + .. SEEALSO:: + + :class:`MultivariateProduct`, + :class:`GenericProduct`. + """ + + def __init__(self, sets, category, **kwargs): + r""" + See :class:`UnivariateProduct` for details. + + TEST:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: type(GrowthGroup('x^ZZ * log(x)^ZZ')) # indirect doctest + + """ + super(UnivariateProduct, self).__init__( + sets, category, order='lex', **kwargs) + + + CartesianProduct = CartesianProductGrowthGroups + + +class MultivariateProduct(GenericProduct): + r""" + A cartesian product of growth groups with pairwise disjoint + (or equal) variable sets. + + .. NOTE:: + + A multivariate product of growth groups is ordered by + means of the product order, i.e. component-wise. This is + motivated by the assumption that different variables are + considered to be independent (e.g. ``x^ZZ * y^ZZ``). + + .. SEEALSO:: + + :class:`UnivariateProduct`, + :class:`GenericProduct`. + """ + def __init__(self, sets, category, **kwargs): + r""" + + TEST:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: type(GrowthGroup('x^ZZ * y^ZZ')) # indirect doctest + + """ + super(MultivariateProduct, self).__init__( + sets, category, order='product', **kwargs) + + + CartesianProduct = CartesianProductGrowthGroups diff --git a/src/sage/rings/asymptotic/misc.py b/src/sage/rings/asymptotic/misc.py new file mode 100644 index 00000000000..0b142d7698d --- /dev/null +++ b/src/sage/rings/asymptotic/misc.py @@ -0,0 +1,693 @@ +r""" +Asymptotic Expansions --- Miscellaneous + +AUTHORS: + +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + + +Functions, Classes and Methods +============================== +""" + +#***************************************************************************** +# Copyright (C) 2015 Daniel Krenn +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +import sage + + +def repr_short_to_parent(s): + r""" + Helper method for the growth group factory, which converts a short + representation string to a parent. + + INPUT: + + - ``s`` -- a string, short representation of a parent. + + OUTPUT: + + A parent. + + The possible short representations are shown in the examples below. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import repr_short_to_parent + sage: repr_short_to_parent('ZZ') + Integer Ring + sage: repr_short_to_parent('QQ') + Rational Field + sage: repr_short_to_parent('SR') + Symbolic Ring + sage: repr_short_to_parent('NN') + Non negative integer semiring + + TESTS:: + + sage: repr_short_to_parent('abcdef') + Traceback (most recent call last): + ... + ValueError: Cannot create a parent out of 'abcdef'. + > *previous* NameError: name 'abcdef' is not defined + """ + from sage.misc.sage_eval import sage_eval + try: + P = sage_eval(s) + except Exception as e: + raise combine_exceptions( + ValueError("Cannot create a parent out of '%s'." % (s,)), e) + + from sage.misc.lazy_import import LazyImport + if type(P) is LazyImport: + P = P._get_object() + + from sage.structure.parent import is_Parent + if not is_Parent(P): + raise ValueError("'%s' does not describe a parent." % (s,)) + return P + + +def parent_to_repr_short(P): + r""" + Helper method which generates a short(er) representation string + out of a parent. + + INPUT: + + - ``P`` -- a parent. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import parent_to_repr_short + sage: parent_to_repr_short(ZZ) + 'ZZ' + sage: parent_to_repr_short(QQ) + 'QQ' + sage: parent_to_repr_short(SR) + 'SR' + sage: parent_to_repr_short(ZZ['x']) + 'ZZ[x]' + sage: parent_to_repr_short(QQ['d, k']) + '(QQ[d, k])' + sage: parent_to_repr_short(QQ['e']) + 'QQ[e]' + sage: parent_to_repr_short(SR[['a, r']]) + '(SR[[a, r]])' + sage: parent_to_repr_short(Zmod(3)) + '(Ring of integers modulo 3)' + sage: parent_to_repr_short(Zmod(3)['g']) + '(Univariate Polynomial Ring in g over Ring of integers modulo 3)' + """ + def abbreviate(P): + if P is sage.rings.integer_ring.ZZ: + return 'ZZ' + elif P is sage.rings.rational_field.QQ: + return 'QQ' + elif P is sage.symbolic.ring.SR: + return 'SR' + raise ValueError('Cannot abbreviate %s.' % (P,)) + + poly = sage.rings.polynomial.polynomial_ring.is_PolynomialRing(P) or \ + sage.rings.polynomial.multi_polynomial_ring_generic.is_MPolynomialRing(P) + from sage.rings import multi_power_series_ring + power = sage.rings.power_series_ring.is_PowerSeriesRing(P) or \ + multi_power_series_ring.is_MPowerSeriesRing(P) + + if poly or power: + if poly: + op, cl = ('[', ']') + else: + op, cl = ('[[', ']]') + try: + s = abbreviate(P.base_ring()) + op + ', '.join(P.variable_names()) + cl + except ValueError: + s = str(P) + else: + try: + s = abbreviate(P) + except ValueError: + s = str(P) + + if ' ' in s: + s = '(' + s + ')' + return s + + +def split_str_by_op(string, op, strip_parentheses=True): + r""" + Split the given string into a tuple of substrings arising by + splitting by ``op`` and taking care of parentheses. + + INPUT: + + - ``string`` -- a string. + + - ``op`` -- a string. This is used by + :python:`str.split `. + Thus, if this is ``None``, then any whitespace string is a + separator and empty strings are removed from the result. + + - ``strip_parentheses`` -- (default: ``True``) a boolean. + + OUTPUT: + + A tuple of strings. + + TESTS:: + + sage: from sage.rings.asymptotic.misc import split_str_by_op + sage: split_str_by_op('x^ZZ', '*') + ('x^ZZ',) + sage: split_str_by_op('log(x)^ZZ * y^QQ', '*') + ('log(x)^ZZ', 'y^QQ') + sage: split_str_by_op('log(x)**ZZ * y**QQ', '*') + ('log(x)**ZZ', 'y**QQ') + sage: split_str_by_op('a^b * * c^d', '*') + Traceback (most recent call last): + ... + ValueError: 'a^b * * c^d' is invalid since a '*' follows a '*'. + sage: split_str_by_op('a^b * (c*d^e)', '*') + ('a^b', 'c*d^e') + + :: + + sage: split_str_by_op('(a^b)^c', '^') + ('a^b', 'c') + sage: split_str_by_op('a^(b^c)', '^') + ('a', 'b^c') + + :: + + sage: split_str_by_op('(a) + (b)', op='+', strip_parentheses=True) + ('a', 'b') + sage: split_str_by_op('(a) + (b)', op='+', strip_parentheses=False) + ('(a)', '(b)') + sage: split_str_by_op(' ( t ) ', op='+', strip_parentheses=False) + ('( t )',) + + :: + + sage: split_str_by_op(' ( t ) ', op=None) + ('t',) + sage: split_str_by_op(' ( t )s', op=None) + ('(t)s',) + sage: split_str_by_op(' ( t ) s', op=None) + ('t', 's') + """ + factors = list() + balanced = True + if string and op is not None and string.startswith(op): + raise ValueError("'%s' is invalid since it starts with a '%s'." % + (string, op)) + for s in string.split(op): + if not s: + factors[-1] += op + balanced = False + continue + if not s.strip(): + raise ValueError("'%s' is invalid since a '%s' follows a '%s'." % + (string, op, op)) + if not balanced: + s = factors.pop() + (op if op else '') + s + balanced = s.count('(') == s.count(')') + factors.append(s) + + if not balanced: + raise ValueError("Parentheses in '%s' are not balanced." % (string,)) + + def strip(s): + s = s.strip() + if not s: + return s + if strip_parentheses and s[0] == '(' and s[-1] == ')': + s = s[1:-1] + return s.strip() + + return tuple(strip(f) for f in factors) + + +def repr_op(left, op, right=None): + r""" + Create a string ``left op right`` with + taking care of parentheses in its operands. + + INPUT: + + - ``left`` -- an element. + + - ``op`` -- a string. + + - ``right`` -- an alement. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import repr_op + sage: repr_op('a^b', '^', 'c') + '(a^b)^c' + + TESTS:: + + sage: repr_op('a-b', '^', 'c') + '(a-b)^c' + sage: repr_op('a+b', '^', 'c') + '(a+b)^c' + """ + left = str(left) + right = str(right) if right is not None else '' + + def add_parentheses(s, op): + if op == '^': + signals = ('^', '/', '*', '-', '+', ' ') + else: + return s + if any(sig in s for sig in signals): + return '(%s)' % (s,) + else: + return s + + return add_parentheses(left, op) + op +\ + add_parentheses(right, op) + + +def combine_exceptions(e, *f): + r""" + Helper function which combines the messages of the given exceptions. + + INPUT: + + - ``e`` -- an exception. + + - ``*f`` -- exceptions. + + OUTPUT: + + An exception. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import combine_exceptions + sage: raise combine_exceptions(ValueError('Outer.'), TypeError('Inner.')) + Traceback (most recent call last): + ... + ValueError: Outer. + > *previous* TypeError: Inner. + sage: raise combine_exceptions(ValueError('Outer.'), + ....: TypeError('Inner1.'), TypeError('Inner2.')) + Traceback (most recent call last): + ... + ValueError: Outer. + > *previous* TypeError: Inner1. + > *and* TypeError: Inner2. + sage: raise combine_exceptions(ValueError('Outer.'), + ....: combine_exceptions(TypeError('Middle.'), + ....: TypeError('Inner.'))) + Traceback (most recent call last): + ... + ValueError: Outer. + > *previous* TypeError: Middle. + >> *previous* TypeError: Inner. + """ + import re + msg = ('\n *previous* ' + + '\n *and* '.join("%s: %s" % (ff.__class__.__name__, str(ff)) for ff in f)) + msg = re.sub(r'^([>]* \*previous\*)', r'>\1', msg, flags=re.MULTILINE) + msg = re.sub(r'^([>]* \*and\*)', r'>\1', msg, flags=re.MULTILINE) + msg = str(e.args if len(e.args) > 1 else e.args[0]) + msg + e.args = (msg,) + return e + + +def substitute_raise_exception(element, e): + r""" + Raise an error describing what went wrong with the substitution. + + INPUT: + + - ``element`` -- an element. + + - ``e`` -- an exception which is included in the raised error + message. + + OUTPUT: + + Raise an exception of the same type as ``e``. + + TESTS:: + + sage: from sage.rings.asymptotic.misc import substitute_raise_exception + sage: substitute_raise_exception(x, Exception('blub')) + Traceback (most recent call last): + ... + Exception: Cannot substitute in x in Symbolic Ring. + > *previous* Exception: blub + """ + raise combine_exceptions( + type(e)('Cannot substitute in %s in %s.' % + (element, element.parent())), e) + + +def underlying_class(P): + r""" + Return the underlying class (class without the attached + categories) of the given instance. + + OUTPUT: + + A class. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import underlying_class + sage: type(QQ) + + sage: underlying_class(QQ) + + """ + cls = type(P) + if not hasattr(P, '_is_category_initialized') or not P._is_category_initialized(): + return cls + from sage.structure.misc import is_extension_type + if is_extension_type(cls): + return cls + + from sage.categories.sets_cat import Sets + Sets_parent_class = Sets().parent_class + while issubclass(cls, Sets_parent_class): + cls = cls.__base__ + return cls + + +def merge_overlapping(A, B, key=None): + r""" + Merge the two overlapping tuples/lists. + + INPUT: + + - ``A`` -- a list or tuple (type has to coincide with type of ``B``). + + - ``B`` -- a list or tuple (type has to coincide with type of ``A``). + + - ``key`` -- (default: ``None``) a function. If ``None``, then the + identity is used. This ``key``-function applied on an element + of the list/tuple is used for comparison. Thus elements with the + same key are considered as equal. + + OUTPUT: + + A pair of lists or tuples (depending on the type of ``A`` and ``B``). + + .. NOTE:: + + Suppose we can decompose the list `A=ac` and `B=cb` with + lists `a`, `b`, `c`, where `c` is nonempty. Then + :func:`merge_overlapping` returns the pair `(acb, acb)`. + + Suppose a ``key``-function is specified and `A=ac_A` and + `B=c_Bb`, where the list of keys of the elements of `c_A` + equals the list of keys of the elements of `c_B`. Then + :func:`merge_overlapping` returns the pair `(ac_Ab, ac_Bb)`. + + After unsuccessfully merging `A=ac` and `B=cb`, + a merge of `A=ca` and `B=bc` is tried. + + TESTS:: + + sage: from sage.rings.asymptotic.misc import merge_overlapping + sage: def f(L, s): + ....: return list((ell, s) for ell in L) + sage: key = lambda k: k[0] + sage: merge_overlapping(f([0..3], 'a'), f([5..7], 'b'), key) + Traceback (most recent call last): + ... + ValueError: Input does not have an overlap. + sage: merge_overlapping(f([0..2], 'a'), f([4..7], 'b'), key) + Traceback (most recent call last): + ... + ValueError: Input does not have an overlap. + sage: merge_overlapping(f([4..7], 'a'), f([0..2], 'b'), key) + Traceback (most recent call last): + ... + ValueError: Input does not have an overlap. + sage: merge_overlapping(f([0..3], 'a'), f([3..4], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a'), (4, 'b')], + [(0, 'a'), (1, 'a'), (2, 'a'), (3, 'b'), (4, 'b')]) + sage: merge_overlapping(f([3..4], 'a'), f([0..3], 'b'), key) + ([(0, 'b'), (1, 'b'), (2, 'b'), (3, 'a'), (4, 'a')], + [(0, 'b'), (1, 'b'), (2, 'b'), (3, 'b'), (4, 'a')]) + sage: merge_overlapping(f([0..1], 'a'), f([0..4], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'b'), (3, 'b'), (4, 'b')], + [(0, 'b'), (1, 'b'), (2, 'b'), (3, 'b'), (4, 'b')]) + sage: merge_overlapping(f([0..3], 'a'), f([0..1], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a')], + [(0, 'b'), (1, 'b'), (2, 'a'), (3, 'a')]) + sage: merge_overlapping(f([0..3], 'a'), f([1..3], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a')], + [(0, 'a'), (1, 'b'), (2, 'b'), (3, 'b')]) + sage: merge_overlapping(f([1..3], 'a'), f([0..3], 'b'), key) + ([(0, 'b'), (1, 'a'), (2, 'a'), (3, 'a')], + [(0, 'b'), (1, 'b'), (2, 'b'), (3, 'b')]) + sage: merge_overlapping(f([0..6], 'a'), f([3..4], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a'), (4, 'a'), (5, 'a'), (6, 'a')], + [(0, 'a'), (1, 'a'), (2, 'a'), (3, 'b'), (4, 'b'), (5, 'a'), (6, 'a')]) + sage: merge_overlapping(f([0..3], 'a'), f([1..2], 'b'), key) + ([(0, 'a'), (1, 'a'), (2, 'a'), (3, 'a')], + [(0, 'a'), (1, 'b'), (2, 'b'), (3, 'a')]) + sage: merge_overlapping(f([1..2], 'a'), f([0..3], 'b'), key) + ([(0, 'b'), (1, 'a'), (2, 'a'), (3, 'b')], + [(0, 'b'), (1, 'b'), (2, 'b'), (3, 'b')]) + sage: merge_overlapping(f([1..3], 'a'), f([1..3], 'b'), key) + ([(1, 'a'), (2, 'a'), (3, 'a')], + [(1, 'b'), (2, 'b'), (3, 'b')]) + """ + if key is None: + Akeys = A + Bkeys = B + else: + Akeys = tuple(key(a) for a in A) + Bkeys = tuple(key(b) for b in B) + + def find_overlapping_index(A, B): + if len(B) > len(A) - 2: + raise StopIteration + matches = iter(i for i in xrange(1, len(A) - len(B)) + if A[i:i+len(B)] == B) + return next(matches) + + def find_mergedoverlapping_index(A, B): + """ + Return in index i where to merge two overlapping tuples/lists ``A`` and ``B``. + + Then ``A + B[i:]`` or ``A[:-i] + B`` are the merged tuples/lists. + + Adapted from http://stackoverflow.com/a/30056066/1052778. + """ + matches = iter(i for i in xrange(min(len(A), len(B)), 0, -1) + if A[-i:] == B[:i]) + return next(matches, 0) + + i = find_mergedoverlapping_index(Akeys, Bkeys) + if i > 0: + return A + B[i:], A[:-i] + B + + i = find_mergedoverlapping_index(Bkeys, Akeys) + if i > 0: + return B[:-i] + A, B + A[i:] + + try: + i = find_overlapping_index(Akeys, Bkeys) + except StopIteration: + pass + else: + return A, A[:i] + B + A[i+len(B):] + + try: + i = find_overlapping_index(Bkeys, Akeys) + except StopIteration: + pass + else: + return B[:i] + A + B[i+len(A):], B + + raise ValueError('Input does not have an overlap.') + + +def log_string(element, base=None): + r""" + Return a representation of the log of the given element to the + given base. + + INPUT: + + - ``element`` -- an object. + + - ``base`` -- an object or ``None``. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import log_string + sage: log_string(3) + 'log(3)' + sage: log_string(3, base=42) + 'log(3, base=42)' + """ + basestr = ', base=' + str(base) if base else '' + return 'log(%s%s)' % (element, basestr) + + +def transform_category(category, + subcategory_mapping, axiom_mapping, + initial_category=None): + r""" + Transform ``category`` to a new category according to the given + mappings. + + INPUT: + + - ``category`` -- a category. + + - ``subcategory_mapping`` -- a list (or other iterable) of triples + ``(from, to, mandatory)``, where + + - ``from`` and ``to`` are categories and + - ``mandatory`` is a boolean. + + - ``axiom_mapping`` -- a list (or other iterable) of triples + ``(from, to, mandatory)``, where + + - ``from`` and ``to`` are strings describing axioms and + - ``mandatory`` is a boolean. + + - ``initial_category`` -- (default: ``None``) a category. When + transforming the given category, this ``initial_category`` is + used as a starting point of the result. This means the resulting + category will be a subcategory of ``initial_category``. + If ``initial_category`` is ``None``, then the + :class:`category of objects ` + is used. + + OUTPUT: + + A category. + + .. NOTE:: + + Consider a subcategory mapping ``(from, to, mandatory)``. If + ``category`` is a subcategory of ``from``, then the + returned category will be a subcategory of ``to``. Otherwise and + if ``mandatory`` is set, then an error is raised. + + Consider an axiom mapping ``(from, to, mandatory)``. If + ``category`` is has axiom ``from``, then the + returned category will have axiom ``to``. Otherwise and + if ``mandatory`` is set, then an error is raised. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.misc import transform_category + sage: from sage.categories.additive_semigroups import AdditiveSemigroups + sage: from sage.categories.additive_monoids import AdditiveMonoids + sage: from sage.categories.additive_groups import AdditiveGroups + sage: S = [ + ....: (Sets(), Sets(), True), + ....: (Posets(), Posets(), False), + ....: (AdditiveMagmas(), Magmas(), False)] + sage: A = [ + ....: ('AdditiveAssociative', 'Associative', False), + ....: ('AdditiveUnital', 'Unital', False), + ....: ('AdditiveInverse', 'Inverse', False), + ....: ('AdditiveCommutative', 'Commutative', False)] + sage: transform_category(Objects(), S, A) + Traceback (most recent call last): + ... + ValueError: Category of objects is not + a subcategory of Category of sets. + sage: transform_category(Sets(), S, A) + Category of sets + sage: transform_category(Posets(), S, A) + Category of posets + sage: transform_category(AdditiveSemigroups(), S, A) + Category of semigroups + sage: transform_category(AdditiveMonoids(), S, A) + Category of monoids + sage: transform_category(AdditiveGroups(), S, A) + Category of groups + sage: transform_category(AdditiveGroups().AdditiveCommutative(), S, A) + Category of commutative groups + + :: + + sage: transform_category(AdditiveGroups().AdditiveCommutative(), S, A, + ....: initial_category=Posets()) + Join of Category of commutative groups + and Category of posets + + :: + + sage: transform_category(ZZ.category(), S, A) + Category of commutative groups + sage: transform_category(QQ.category(), S, A) + Category of commutative groups + sage: transform_category(SR.category(), S, A) + Category of commutative groups + sage: transform_category(Fields(), S, A) + Category of commutative groups + sage: transform_category(ZZ['t'].category(), S, A) + Category of commutative groups + + :: + + sage: A[-1] = ('Commutative', 'AdditiveCommutative', True) + sage: transform_category(Groups(), S, A) + Traceback (most recent call last): + ... + ValueError: Category of groups does not have + axiom Commutative. + """ + if initial_category is None: + from sage.categories.objects import Objects + result = Objects() + else: + result = initial_category + + for A, B, mandatory in subcategory_mapping: + if category.is_subcategory(A): + result &= B + elif mandatory: + raise ValueError('%s is not a subcategory of %s.' % + (category, A)) + + axioms = category.axioms() + for A, B, mandatory in axiom_mapping: + if A in axioms: + result = result._with_axiom(B) + elif mandatory: + raise ValueError('%s does not have axiom %s.' % + (category, A)) + + return result diff --git a/src/sage/rings/asymptotic/term_monoid.py b/src/sage/rings/asymptotic/term_monoid.py index 7d9a9bbd475..0d18e1d900b 100644 --- a/src/sage/rings/asymptotic/term_monoid.py +++ b/src/sage/rings/asymptotic/term_monoid.py @@ -1,14 +1,14 @@ r""" -Asymptotic Term Monoid +(Asymptotic) Term Monoids This module implements asymptotic term monoids. The elements of these monoids are used behind the scenes when performing calculations in an -asymptotic ring (to be implemented). +:doc:`asymptotic ring `. The monoids build upon the (asymptotic) growth groups. While growth elements only model the growth of a function as it tends towards infinity (or tends towards another fixed point; see -:mod:`~sage.rings.asymptotic.growth_group` for more details), an +:doc:`growth_group` for more details), an asymptotic term additionally specifies its "type" and performs the actual arithmetic operations (multiplication and partial addition/absorption of terms). @@ -21,7 +21,15 @@ - :class:`TermWithCoefficient` -- abstract base class for asymptotic terms with coefficients. - :class:`ExactTerm` -- this class represents a growth element - multiplied with some non-zero coefficient from a base ring. + multiplied with some non-zero coefficient from a coefficient ring. + +A characteristic property of asymptotic terms is that some terms are +able to "absorb" other terms (see +:meth:`~sage.rings.asymptotic.term_monoid.GenericTerm.absorb`). For +instance, `O(x^2)` is able to absorb `O(x)` (with result +`O(x^2)`), and `3\cdot x^5` is able to absorb `-2\cdot x^5` (with result +`x^5`). Essentially, absorption can be interpreted as the +addition of "compatible" terms (partial addition). .. WARNING:: @@ -31,14 +39,9 @@ TESTS:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.MonomialGrowthGroup(ZZ, 'x') - doctest:...: FutureWarning: This class/method/function is marked as - experimental. It, its functionality or its interface might change - without a formal deprecation. - See http://trac.sagemath.org/17601 for details. - sage: T = atm.TermWithCoefficientMonoid(G, ZZ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ * log(x)^ZZ') doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. @@ -66,11 +69,11 @@ by which other terms. We start by defining the necessary term monoids and some terms:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: OT = atm.OTermMonoid(growth_group=G) - sage: ET = atm.ExactTermMonoid(growth_group=G, base_ring=QQ) + sage: from sage.rings.asymptotic.term_monoid import OTermMonoid, ExactTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: OT = OTermMonoid(growth_group=G, coefficient_ring=QQ) + sage: ET = ExactTermMonoid(growth_group=G, coefficient_ring=QQ) sage: ot1 = OT(x); ot2 = OT(x^2) sage: et1 = ET(x^2, 2) @@ -172,6 +175,7 @@ Long story short: we consider terms with different coefficients that have equal growth to be incomparable. + Various ======= @@ -182,11 +186,24 @@ AUTHORS: -- Benjamin Hackl (2015-01): initial version -- Benjamin Hackl, Daniel Krenn (2015-05): conception of the asymptotic ring -- Benjamin Hackl (2015-06): refactoring caused by refactoring growth groups -- Daniel Krenn (2015-07): extensive review and patches -- Benjamin Hackl (2015-07): cross-review; short notation +- Benjamin Hackl (2015) +- Daniel Krenn (2015) + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + + +ACKNOWLEDGEMENT: + +- Benjamin Hackl, Clemens Heuberger and Daniel Krenn are supported by the + Austrian Science Fund (FWF): P 24644-N26. + +- Benjamin Hackl is supported by the Google Summer of Code 2015. + Classes and Methods =================== @@ -205,12 +222,13 @@ import sage + def absorption(left, right): r""" Let one of the two passed terms absorb the other. Helper function used by - :class:`~sage.rings.asymptotic_ring.AsymptoticExpression`. + :class:`~sage.rings.asymptotic.asymptotic_ring.AsymptoticExpansion`. .. NOTE:: @@ -233,19 +251,18 @@ def absorption(left, right): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: T = atm.TermMonoid('O', G) - sage: atm.absorption(T(x^2), T(x^3)) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (TermMonoid, absorption) + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), ZZ) + sage: absorption(T(x^2), T(x^3)) O(x^3) - sage: atm.absorption(T(x^3), T(x^2)) + sage: absorption(T(x^3), T(x^2)) O(x^3) :: - sage: T = atm.TermMonoid('exact', G, ZZ) - sage: atm.absorption(T(x^2), T(x^3)) + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ'), ZZ) + sage: absorption(T(x^2), T(x^3)) Traceback (most recent call last): ... ArithmeticError: Absorption between x^2 and x^3 is not possible. @@ -265,7 +282,7 @@ def can_absorb(left, right): other. Helper function used by - :class:`~sage.rings.asymptotic_ring.AsymptoticExpression`. + :class:`~sage.rings.asymptotic.asymptotic_ring.AsymptoticExpansion`. INPUT: @@ -284,13 +301,12 @@ def can_absorb(left, right): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: T = atm.TermMonoid('O', G) - sage: atm.can_absorb(T(x^2), T(x^3)) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (TermMonoid, can_absorb) + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), ZZ) + sage: can_absorb(T(x^2), T(x^3)) True - sage: atm.can_absorb(T(x^3), T(x^2)) + sage: can_absorb(T(x^3), T(x^2)) True """ return left.can_absorb(right) or right.can_absorb(left) @@ -309,10 +325,10 @@ class GenericTerm(sage.structure.element.MonoidElement): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) sage: t1 = T(x); t2 = T(x^2); (t1, t2) (Generic Term with growth x, Generic Term with growth x^2) sage: t1 * t2 @@ -333,36 +349,32 @@ def __init__(self, parent, growth): TESTS:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, ZZ) sage: T(x^2) Generic Term with growth x^2 :: - sage: atm.GenericTerm(parent=None, growth=x) + sage: from sage.rings.asymptotic.term_monoid import GenericTerm + sage: GenericTerm(parent=None, growth=x) Traceback (most recent call last): ... ValueError: The parent must be provided - sage: atm.GenericTerm(T, agg.GrowthGroup('y^ZZ').gen()) + sage: GenericTerm(T, GrowthGroup('y^ZZ').gen()) Traceback (most recent call last): ... - ValueError: y is not in the parent's specified growth group + ValueError: y is not in Growth Group x^ZZ """ - from sage.rings.asymptotic.growth_group import GenericGrowthElement - if parent is None: raise ValueError('The parent must be provided') - if growth is None or not isinstance(growth, GenericGrowthElement): - raise ValueError('The growth must be provided and must inherit ' - 'from GenericGrowthElement') - elif growth not in parent.growth_group: - raise ValueError("%s is not in the parent's " - "specified growth group" % growth) - - self.growth = growth + try: + self.growth = parent.growth_group(growth) + except (ValueError, TypeError): + raise ValueError("%s is not in %s" % (growth, parent.growth_group)) + super(GenericTerm, self).__init__(parent=parent) @@ -386,10 +398,10 @@ def _mul_(self, other): TESTS:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, ZZ) sage: t1 = T(x); t2 = T(x^2) sage: t1, t2 (Generic Term with growth x, Generic Term with growth x^2) @@ -421,10 +433,10 @@ def __div__(self, other): TESTS:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) sage: t1 = T(x); t2 = T(x^2) sage: t1 / t2 # indirect doctest Traceback (most recent call last): @@ -461,10 +473,10 @@ def _div_(self, other): TESTS:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) sage: t1 = T(x); t2 = T(x^2) sage: t1 / t2 # indirect doctest Traceback (most recent call last): @@ -485,10 +497,10 @@ def __invert__(self): TESTS:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) sage: ~T(x) # indirect doctest Traceback (most recent call last): ... @@ -499,6 +511,138 @@ def __invert__(self): '(in this abstract method).' % (self,)) + def __pow__(self, exponent): + r""" + Calculate the power of this element to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + Raise a :python:`NotImplementedError` + since it is an abstract base class. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: t = GenericTermMonoid(G, ZZ).an_element(); t + Generic Term with growth z + sage: t^3 # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError: Taking powers of Generic Term with growth z + not implemented (in this abstract method). + """ + raise NotImplementedError('Taking powers of %s not implemented ' + '(in this abstract method).' % (self,)) + + + def _calculate_pow_test_zero_(self, exponent): + r""" + Helper function for :meth:`__pow__` which calculates the power of this + element to the given ``exponent`` only if zero to this exponent is possible. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: t = GenericTermMonoid(G, ZZ).an_element(); t + Generic Term with growth z + sage: t._calculate_pow_test_zero_(3) + Generic Term with growth z^3 + sage: t._calculate_pow_test_zero_(-2) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot take Generic Term with growth z to exponent -2. + > *previous* ZeroDivisionError: rational division by zero + + :: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: TermMonoid('O', G, QQ)('z')._calculate_pow_test_zero_(-1) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot take O(z) to exponent -1. + > *previous* ZeroDivisionError: rational division by zero + """ + # This assumes `0 = O(g)` for any `g` in the growth group, which + # is valid in the case of a variable going to `\infty`. + # Once non-standard asymptoptics are supported, this has to be + # rewritten. + # See also #19083, comment 64, 27. + + zero = self.parent().coefficient_ring.zero() + try: + zero ** exponent + except (TypeError, ValueError, ZeroDivisionError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ZeroDivisionError('Cannot take %s to exponent %s.' % + (self, exponent)), e) + return self._calculate_pow_(exponent) + + + def _calculate_pow_(self, exponent, new_coefficient=None): + r""" + Helper function for :meth:`__pow__` which calculates the power of this + element to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + - ``new_coefficient`` -- if not ``None`` this is passed on to the + construction of the element (in particular, not taken to any power). + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: t = GenericTermMonoid(G, ZZ).an_element(); t + Generic Term with growth z + sage: t._calculate_pow_(3) + Generic Term with growth z^3 + sage: t._calculate_pow_(3, new_coefficient=2) + Traceback (most recent call last): + ... + ValueError: Coefficient 2 is not 1, but Generic Term Monoid z^ZZ with + (implicit) coefficients in Integer Ring does not support coefficients. + sage: t._calculate_pow_(-2) + Generic Term with growth z^(-2) + sage: t._calculate_pow_(-2, new_coefficient=2) + Traceback (most recent call last): + ... + ValueError: Coefficient 2 is not 1, but Generic Term Monoid z^ZZ with + (implicit) coefficients in Integer Ring does not support coefficients. + """ + try: + g = self.growth ** exponent + except (ValueError, TypeError, ZeroDivisionError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('Cannot take %s to the exponent %s.' % (self, exponent)), e) + + return self.parent()._create_element_in_extension_(g, new_coefficient) + + def can_absorb(self, other): r""" Check whether this asymptotic term is able to absorb @@ -521,10 +665,10 @@ def can_absorb(self, other): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GenericGrowthGroup(ZZ) - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GenericGrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GenericGrowthGroup(ZZ) + sage: T = GenericTermMonoid(G, QQ) sage: g1 = G(raw_element=21); g2 = G(raw_element=42) sage: t1 = T(g1); t2 = T(g2) sage: t1.can_absorb(t2) # indirect doctest @@ -570,11 +714,11 @@ def absorb(self, other, check=True): of this operation. We start by defining some parents and elements:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_QQ = agg.GrowthGroup('x^QQ'); x = G_QQ.gen() - sage: OT = atm.OTermMonoid(G_QQ) - sage: ET = atm.ExactTermMonoid(growth_group=G_QQ, base_ring=QQ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G_QQ = GrowthGroup('x^QQ'); x = G_QQ.gen() + sage: OT = TermMonoid('O', G_QQ, coefficient_ring=ZZ) + sage: ET = TermMonoid('exact', G_QQ, coefficient_ring=QQ) sage: ot1 = OT(x); ot2 = OT(x^2) sage: et1 = ET(x, 100); et2 = ET(x^2, 2) sage: et3 = ET(x^2, 1); et4 = ET(x^2, -2) @@ -657,10 +801,10 @@ def _absorb_(self, other): First, we define some asymptotic terms:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) sage: t1 = T(x); t2 = T(x^2) When it comes to absorption, note that the method @@ -684,6 +828,88 @@ def _absorb_(self, other): raise NotImplementedError('Not implemented in abstract base classes') + def log_term(self, base=None): + r""" + Determine the logarithm of this term. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of terms. + + .. NOTE:: + + This abstract method raises a + :python:`NotImplementedError`. + See :class:`ExactTerm` and :class:`OTerm` for a concrete + implementation. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ'), QQ) + sage: T.an_element().log_term() + Traceback (most recent call last): + ... + NotImplementedError: This method is not implemented in + this abstract base class. + + :: + + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: T = TermWithCoefficientMonoid(GrowthGroup('x^ZZ'), QQ) + sage: T.an_element().log_term() + Traceback (most recent call last): + ... + NotImplementedError: This method is not implemented in + this abstract base class. + + .. SEEALSO:: + + :meth:`ExactTerm.log_term`, + :meth:`OTerm.log_term`. + """ + raise NotImplementedError('This method is not implemented in this ' + 'abstract base class.') + + + def _log_growth_(self, base=None): + r""" + Helper function to calculate the logarithm of the growth of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of terms. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T(x^2)._log_growth_() + (O(log(x)),) + sage: T(x^1234).log_term() # indirect doctest + (O(log(x)),) + + .. SEEALSO:: + + :meth:`ExactTerm.log_term`, + :meth:`OTerm.log_term`. + """ + return tuple(self.parent()._create_element_in_extension_(g, c) + for g, c in self.growth.log_factor(base=base)) + + def __le__(self, other): r""" Return whether the growth of this term is less than @@ -706,17 +932,17 @@ def __le__(self, other): First, we define some asymptotic terms (and their parents):: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: GT = atm.GenericTermMonoid(G) - sage: OT = atm.OTermMonoid(G) - sage: ET_ZZ = atm.ExactTermMonoid(G, ZZ) - sage: ET_QQ = atm.ExactTermMonoid(G, QQ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: GT = GenericTermMonoid(G, QQ) + sage: OT = TermMonoid('O', G, QQ) + sage: ET_ZZ = TermMonoid('exact', G, ZZ) + sage: ET_QQ = TermMonoid('exact', G, QQ) sage: g1 = GT(x); g2 = GT(x^2); g1, g2 (Generic Term with growth x, Generic Term with growth x^2) sage: o1 = OT(x^-1); o2 = OT(x^3); o1, o2 - (O(1/x), O(x^3)) + (O(x^(-1)), O(x^3)) sage: t1 = ET_ZZ(x^2, 5); t2 = ET_QQ(x^3, 2/7); t1, t2 (5*x^2, 2/7*x^3) @@ -728,7 +954,7 @@ def __le__(self, other): sage: g1 <= g2 True sage: o1, g1 - (O(1/x), Generic Term with growth x) + (O(x^(-1)), Generic Term with growth x) sage: o1 <= g1 False @@ -792,10 +1018,10 @@ def _le_(self, other): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) sage: t1 = T(x^-2); t2 = T(x^5); t1, t2 (Generic Term with growth x^(-2), Generic Term with growth x^5) sage: t1._le_(t2) @@ -828,9 +1054,9 @@ def __eq__(self, other): sage: from sage.rings.asymptotic.growth_group import GrowthGroup sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, ....: ExactTermMonoid, OTermMonoid) - sage: GT = GenericTermMonoid(GrowthGroup('x^ZZ')) + sage: GT = GenericTermMonoid(GrowthGroup('x^ZZ'), QQ) sage: ET = ExactTermMonoid(GrowthGroup('x^ZZ'), ZZ) - sage: OT = OTermMonoid(GrowthGroup('x^ZZ')) + sage: OT = OTermMonoid(GrowthGroup('x^ZZ'), QQ) sage: g = GT.an_element(); e = ET.an_element(); o = OT.an_element() sage: g, e, o (Generic Term with growth x, x, O(x)) @@ -877,7 +1103,7 @@ def _eq_(self, other): sage: from sage.rings.asymptotic.growth_group import GrowthGroup sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid - sage: T = GenericTermMonoid(GrowthGroup('x^ZZ')) + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ'), QQ) sage: t = T.an_element() sage: t == t True @@ -885,7 +1111,7 @@ def _eq_(self, other): :: sage: from sage.rings.asymptotic.term_monoid import OTermMonoid - sage: OT = OTermMonoid(GrowthGroup('x^ZZ')) + sage: OT = OTermMonoid(GrowthGroup('x^ZZ'), QQ) sage: t = OT.an_element(); t O(x) sage: t == OT(x) # indirect doctest @@ -896,6 +1122,108 @@ def _eq_(self, other): return self.growth == other.growth + def is_constant(self): + r""" + Return whether this term is an (exact) constant. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + .. NOTE:: + + Only :class:`ExactTerm` with constant growth (`1`) are + constant. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, TermMonoid) + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: t = T.an_element(); t + Generic Term with growth x*log(x) + sage: t.is_constant() + False + + :: + + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), QQ) + sage: T('x').is_constant() + False + sage: T(1).is_constant() + False + """ + return False + + + def is_little_o_of_one(self): + r""" + Return whether this generic term is of order `o(1)`. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, + ....: TermWithCoefficientMonoid) + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ'), QQ) + sage: T.an_element().is_little_o_of_one() + Traceback (most recent call last): + ... + NotImplementedError: Cannot check whether Generic Term with growth x is o(1) + in the abstract base class + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field. + sage: T = TermWithCoefficientMonoid(GrowthGroup('x^ZZ'), QQ) + sage: T.an_element().is_little_o_of_one() + Traceback (most recent call last): + ... + NotImplementedError: Cannot check whether Term with coefficient 1/2 and growth x + is o(1) in the abstract base class + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field. + """ + raise NotImplementedError('Cannot check whether %s is o(1) in the ' + 'abstract base class %s.' % (self, self.parent())) + + + def rpow(self, base): + r""" + Return the power of ``base`` to this generic term. + + INPUT: + + - ``base`` -- an element or ``'e'``. + + OUTPUT: + + A term. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: T = GenericTermMonoid(GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T.an_element().rpow('e') + Traceback (most recent call last): + ... + NotImplementedError: Cannot take e to the exponent + Generic Term with growth x*log(x) in the abstract base class + Generic Term Monoid x^ZZ * log(x)^ZZ with (implicit) coefficients in Rational Field. + """ + raise NotImplementedError('Cannot take %s to the exponent %s in the ' + 'abstract base class %s.' % (base, self, self.parent())) + + def _repr_(self): r""" A representation string for this generic term. @@ -910,10 +1238,10 @@ def _repr_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(growth_group=G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) sage: T(x)._repr_() 'Generic Term with growth x' sage: T(x^7)._repr_() @@ -922,8 +1250,41 @@ def _repr_(self): return 'Generic Term with growth ' + repr(self.growth) -class GenericTermMonoid(sage.structure.parent.Parent, - sage.structure.unique_representation.UniqueRepresentation): + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this generic term. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + Nothing since a + :python:`TypeError` + is raised. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: t = GenericTermMonoid(GrowthGroup('x^ZZ'), ZZ).an_element() + sage: t._substitute_({}) + Traceback (most recent call last): + ... + TypeError: Cannot substitute in Generic Term with growth x in + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring. + > *previous* TypeError: Cannot substitute in the abstract base class + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring. + """ + from misc import substitute_raise_exception + substitute_raise_exception(self, TypeError( + 'Cannot substitute in the abstract ' + 'base class %s.' % (self.parent(),))) + + +class GenericTermMonoid(sage.structure.unique_representation.UniqueRepresentation, + sage.structure.parent.Parent): r""" Parent for generic asymptotic terms. @@ -932,6 +1293,9 @@ class GenericTermMonoid(sage.structure.parent.Parent, - ``growth_group`` -- a growth group (i.e. an instance of :class:`~sage.rings.asymptotic.growth_group.GenericGrowthGroup`). + - ``coefficient_ring`` -- a ring which contains the (maybe implicit) + coefficients of the elements. + - ``category`` -- The category of the parent can be specified in order to broaden the base structure. It has to be a subcategory of ``Join of Category of Monoids and Category of posets``. This @@ -949,69 +1313,118 @@ class GenericTermMonoid(sage.structure.parent.Parent, EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_x = agg.GrowthGroup('x^ZZ'); x = G_x.gen() - sage: G_y = agg.GrowthGroup('y^QQ'); y = G_y.gen() - sage: T_x_ZZ = atm.GenericTermMonoid(G_x); T_y_QQ = atm.GenericTermMonoid(G_y) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G_x = GrowthGroup('x^ZZ'); x = G_x.gen() + sage: G_y = GrowthGroup('y^QQ'); y = G_y.gen() + sage: T_x_ZZ = GenericTermMonoid(G_x, QQ) + sage: T_y_QQ = GenericTermMonoid(G_y, QQ) sage: T_x_ZZ - Generic Term Monoid x^ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field sage: T_y_QQ - Generic Term Monoid y^QQ + Generic Term Monoid y^QQ with (implicit) coefficients in Rational Field """ # enable the category framework for elements Element = GenericTerm - @sage.misc.superseded.experimental(trac_number=17601) - def __init__(self, growth_group, category=None): + + @staticmethod + def __classcall__(cls, growth_group, coefficient_ring, category=None): r""" - See :class:`GenericTermMonoid` for more information. + Normalize the input in order to ensure a unique + representation of the parent. - EXAMPLES:: + TESTS:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_x = agg.GrowthGroup('x^ZZ') - sage: T_x = atm.GenericTermMonoid(G_x); T_x - Generic Term Monoid x^ZZ - sage: T_x.growth_group - Growth Group x^ZZ - sage: G_y = agg.GrowthGroup('y^QQ') - sage: T_y = atm.GenericTermMonoid(G_y); T_y - Generic Term Monoid y^QQ - sage: T_x is T_y - False + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: T = GenericTermMonoid(G, QQ) + sage: from sage.rings.asymptotic.misc import underlying_class + sage: underlying_class(T)(G, QQ) is T + True :: - sage: atm.GenericTermMonoid(None) + sage: GenericTermMonoid(None, ZZ) # indirect doctest + Traceback (most recent call last): + ... + ValueError: No growth group specified. + sage: GenericTermMonoid(int, ZZ) # indirect doctest + Traceback (most recent call last): + ... + TypeError: is not a valid growth group. + sage: GenericTermMonoid(G, None) # indirect doctest + Traceback (most recent call last): + ... + ValueError: No coefficient ring specified. + sage: GenericTermMonoid(G, int) # indirect doctest Traceback (most recent call last): ... - ValueError: Growth Group has to be specified + TypeError: is not a valid coefficient ring. """ - from sage.categories.monoids import Monoids - from sage.categories.posets import Posets - from sage.rings.asymptotic.growth_group import GenericGrowthGroup + if growth_group is None: + raise ValueError('No growth group specified.') + if not isinstance(growth_group, sage.structure.parent.Parent): + raise TypeError('%s is not a valid growth group.' % (growth_group,)) + + if coefficient_ring is None: + raise ValueError('No coefficient ring specified.') + if not isinstance(coefficient_ring, sage.structure.parent.Parent): + raise TypeError('%s is not a valid coefficient ring.' % (coefficient_ring,)) if category is None: + from sage.categories.monoids import Monoids + from sage.categories.posets import Posets category = Monoids() & Posets() - else: - if not isinstance(category, tuple): - category = (category,) - if not any(cat.is_subcategory(Monoids() & Posets()) for cat in - category): - raise ValueError('%s is not a subcategory of %s' - % (category, Monoids() & Posets())) - if growth_group is None: - raise ValueError('Growth Group has to be specified') - else: - if not isinstance(growth_group, GenericGrowthGroup): - raise ValueError('%s does not inherit from %s' - % (growth_group, GenericGrowthGroup())) + + return super(GenericTermMonoid, cls).__classcall__( + cls, growth_group, coefficient_ring, category) + + + @sage.misc.superseded.experimental(trac_number=17601) + def __init__(self, growth_group, coefficient_ring, category): + r""" + See :class:`GenericTermMonoid` for more information. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid, TermWithCoefficientMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_x = GrowthGroup('x^ZZ') + sage: T_x = GenericTermMonoid(G_x, QQ); T_x + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + sage: T_x.growth_group + Growth Group x^ZZ + sage: G_y = GrowthGroup('y^QQ') + sage: T_y = GenericTermMonoid(G_y, QQ); T_y + Generic Term Monoid y^QQ with (implicit) coefficients in Rational Field + sage: T_x is T_y + False + + :: + + sage: GenericTermMonoid(None, None) + Traceback (most recent call last): + ... + ValueError: No growth group specified. + + :: + + sage: G = GrowthGroup('x^ZZ') + sage: T_ZZ = TermWithCoefficientMonoid(G, ZZ); T_ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring + sage: T_QQ = TermWithCoefficientMonoid(G, QQ); T_QQ + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + sage: T_QQ.category() + Join of Category of monoids and Category of posets + """ self._growth_group_ = growth_group + self._coefficient_ring_ = coefficient_ring super(GenericTermMonoid, self).__init__(category=category) + @property def growth_group(self): r""" @@ -1019,15 +1432,30 @@ def growth_group(self): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.ExactTermMonoid(G, ZZ).growth_group + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: TermMonoid('exact', GrowthGroup('x^ZZ'), ZZ).growth_group Growth Group x^ZZ """ return self._growth_group_ + @property + def coefficient_ring(self): + r""" + The coefficient ring of this term monoid, i.e. the ring where + the coefficients are from. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: GenericTermMonoid(GrowthGroup('x^ZZ'), ZZ).coefficient_ring + Integer Ring + """ + return self._coefficient_ring_ + + def _repr_(self): r""" A representation string for this generic term monoid. @@ -1042,16 +1470,15 @@ def _repr_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GenericGrowthGroup(ZZ) - sage: atm.GenericTermMonoid(G)._repr_() - 'Generic Term Monoid Generic(ZZ)' - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.GenericTermMonoid(growth_group=G)._repr_() - 'Generic Term Monoid x^ZZ' + sage: from sage.rings.asymptotic.growth_group import (GenericGrowthGroup, GrowthGroup) + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: GenericTermMonoid(GenericGrowthGroup(ZZ), QQ)._repr_() + 'Generic Term Monoid Generic(ZZ) with (implicit) coefficients in Rational Field' + sage: GenericTermMonoid(GrowthGroup('x^ZZ'), QQ)._repr_() + 'Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field' """ - return 'Generic Term Monoid %s' % (self.growth_group._repr_short_(), ) + return 'Generic Term Monoid %s with (implicit) coefficients in %s' % \ + (self.growth_group._repr_short_(), self.coefficient_ring) def _coerce_map_from_(self, S): @@ -1069,55 +1496,72 @@ def _coerce_map_from_(self, S): .. NOTE:: Another generic term monoid ``S`` coerces into this term - monoid if and only if the growth group of ``S`` coerces - into the growth group of this term monoid. + monoid if and only if both, the growth group of ``S`` coerces + into the growth group of this term monoid and the coefficient + ring of ``S`` coerces into the coefficient ring of this term + monoid. EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.GrowthGroup('x^ZZ') - sage: T_ZZ = atm.GenericTermMonoid(growth_group=G_ZZ); T_ZZ - Generic Term Monoid x^ZZ - sage: G_QQ = agg.GrowthGroup('x^QQ') - sage: T_QQ = atm.GenericTermMonoid(growth_group=G_QQ); T_QQ - Generic Term Monoid x^QQ + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: T_ZZ = GenericTermMonoid(G_ZZ, QQ); T_ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + sage: G_QQ = GrowthGroup('x^QQ') + sage: T_QQ = GenericTermMonoid(G_QQ, QQ); T_QQ + Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field sage: T_QQ.has_coerce_map_from(T_ZZ) # indirect doctest True + sage: T_QQ_ZZ = GenericTermMonoid(G_QQ, ZZ); T_QQ_ZZ + Generic Term Monoid x^QQ with (implicit) coefficients in Integer Ring + sage: T_QQ.has_coerce_map_from(T_QQ_ZZ) + True + sage: T_QQ_ZZ.has_coerce_map_from(T_QQ) + False + + :: + + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: TC_ZZ = TermWithCoefficientMonoid(G_ZZ, ZZ); TC_ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring + sage: TC_QQ = TermWithCoefficientMonoid(G_QQ, QQ); TC_QQ + Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field + sage: TC_QQ.has_coerce_map_from(TC_ZZ) # indirect doctest + True + sage: TC_ZZ.has_coerce_map_from(TC_QQ) # indirect doctest + False """ if isinstance(S, self.__class__): - if self.growth_group.has_coerce_map_from(S.growth_group): + if self.growth_group.has_coerce_map_from(S.growth_group) and \ + self.coefficient_ring.has_coerce_map_from(S.coefficient_ring): return True - def _element_constructor_(self, data): + def _element_constructor_(self, data, coefficient=None): r""" Convert the given object to this term monoid. INPUT: - - ``data`` -- an object representing the element to be - initialized. + - ``data`` -- a growth element or an object representing the + element to be initialized. + + - ``coefficient`` -- (default: ``None``) + an element of the coefficient ring. OUTPUT: An element of this term monoid. - .. NOTE:: - - The object ``data`` is either an asymptotic term that is - to be coerced into this term monoid, or an asymptotic - growth element that is used for creating an element - of this term monoid. - EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.GrowthGroup('x^ZZ') - sage: G_QQ = agg.GrowthGroup('x^QQ') - sage: T_ZZ = atm.GenericTermMonoid(growth_group=G_ZZ) - sage: T_QQ = atm.GenericTermMonoid(growth_group=G_QQ) + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: G_QQ = GrowthGroup('x^QQ') + sage: T_ZZ = GenericTermMonoid(G_ZZ, QQ) + sage: T_QQ = GenericTermMonoid(G_QQ, QQ) sage: term1 = T_ZZ(G_ZZ.gen()) sage: term2 = T_QQ(G_QQ.gen()^2) @@ -1125,16 +1569,16 @@ def _element_constructor_(self, data): a common parent has to be found:: sage: term1.parent() - Generic Term Monoid x^ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field sage: term2.parent() - Generic Term Monoid x^QQ + Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field sage: term1 <= term2 True In this case, this works because ``T_ZZ``, the parent of ``term1``, coerces into ``T_QQ``:: - sage: T_QQ.coerce(term1) + sage: T_QQ.coerce(term1) # coercion does not fail Generic Term with growth x The conversion of growth elements also works for the creation @@ -1151,21 +1595,261 @@ def _element_constructor_(self, data): sage: T_ZZ(10 * x^2) Traceback (most recent call last): ... - ValueError: Input is ambiguous: cannot convert 10*x^2 to a generic term. + ValueError: 10*x^2 is not in Generic Term Monoid x^ZZ + with (implicit) coefficients in Rational Field. + > *previous* ValueError: Factor 10*x^2 of 10*x^2 is neither a + coefficient (in Rational Field) nor growth (in Growth Group x^ZZ). + + :: + + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G = GrowthGroup('x^ZZ') + sage: T = TermWithCoefficientMonoid(G, ZZ) + sage: t1 = T(x^2, 5); t1 # indirect doctest + Term with coefficient 5 and growth x^2 + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: O_ZZ = TermMonoid('O', G_ZZ, QQ) + sage: O_ZZ(x^11) + O(x^11) + + :: + + sage: T(G.gen()^10) + Term with coefficient 1 and growth x^10 + sage: T(G.gen()^10, coefficient=10) + Term with coefficient 10 and growth x^10 + sage: T(x^123) + Term with coefficient 1 and growth x^123 + + :: + + sage: T(x) + Term with coefficient 1 and growth x + + :: + + sage: G_log = GrowthGroup('log(x)^ZZ') + sage: T_log = TermWithCoefficientMonoid(G_log, ZZ) + sage: T_log(log(x)) + Term with coefficient 1 and growth log(x) + """ if isinstance(data, self.element_class) and data.parent() == self: return data + elif isinstance(data, TermWithCoefficient): + return self._create_element_(data.growth, data.coefficient) elif isinstance(data, GenericTerm): - return self.element_class(self, data.growth) + return self._create_element_(data.growth, None) elif isinstance(data, int) and data == 0: - raise ValueError('No input specified. Cannot continue.') - else: + raise ValueError('No input specified. Cannot continue ' + 'creating an element of %s.' % (self,)) + + from misc import combine_exceptions + if coefficient is not None: try: data = self.growth_group(data) - return self.element_class(self, data) - except: - raise ValueError('Input is ambiguous: cannot convert %s to a ' - 'generic term.' % (data,)) + except (ValueError, TypeError) as e: + raise combine_exceptions( + ValueError('Growth %s is not in %s.' % (data, self)), e) + return self._create_element_(data, coefficient) + + try: + growth, coefficient = self._split_growth_and_coefficient_(data) + except ValueError as e: + raise combine_exceptions( + ValueError('%s is not in %s.' % (data, self)), e) + + return self._create_element_(growth, coefficient) + + + def _create_element_(self, growth, coefficient): + r""" + Helper method which creates an element by using the ``element_class``. + + INPUT: + + - ``growth`` -- a growth element. + + - ``coefficient`` -- an element of the coefficient ring. + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: T_ZZ = GenericTermMonoid(G_ZZ, QQ) + sage: T_ZZ(G_ZZ.gen()) # indirect doctest + Generic Term with growth x + """ + if coefficient is not None and coefficient != self.coefficient_ring.one(): + raise ValueError('Coefficient %s is not 1, but %s does not ' + 'support coefficients.' % (coefficient, self)) + return self.element_class(self, growth) + + + def _create_element_in_extension_(self, growth, coefficient): + r""" + Create an element in an extension of this term monoid which + is chosen according to the input. + + INPUT: + + - ``growth`` and ``coefficient`` -- the element data. + + OUTPUT: + + An element. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: T = TermMonoid('exact', G, ZZ) + sage: T._create_element_in_extension_(G.an_element(), 3) + 3*z + sage: T._create_element_in_extension_(G.an_element(), 3/2).parent() + Exact Term Monoid z^ZZ with coefficients in Rational Field + """ + if (growth.parent() is self.growth_group) and \ + (coefficient is None or coefficient.parent() is self.coefficient_ring): + parent = self + else: + from misc import underlying_class + parent = underlying_class(self)(growth.parent(), + coefficient.parent() + if coefficient is not None + else self.coefficient_ring, + category=self.category()) + return parent(growth, coefficient) + + + def _split_growth_and_coefficient_(self, data): + r""" + Split given ``data`` into a growth element and a coefficient. + + INPUT: + + ``data`` -- an element. + + OUTPUT: + + A pair ``(growth, coefficient``). + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: T = TermMonoid('exact', G, QQ) + sage: T._split_growth_and_coefficient_('2*x^3') + (x^3, 2) + + :: + + sage: T._split_growth_and_coefficient_('2.7 * x^3') + Traceback (most recent call last): + ... + ValueError: Factor 2.7 of 2.7 * x^3 is neither a coefficient + (in Rational Field) nor growth (in Growth Group x^ZZ). + + :: + + sage: G = GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ') + sage: T = TermMonoid('exact', G, QQ) + sage: T._split_growth_and_coefficient_('3/4 * 2^x * log(x)') + (2^x*log(x), 3/4) + sage: T._split_growth_and_coefficient_('3 * x^2 * 4 * log(x) * x') + (x^3*log(x), 12) + sage: var('x') + x + sage: T._split_growth_and_coefficient_(log(x)^5 * x^2 * 4) + (x^2*log(x)^5, 4) + + :: + + sage: T = TermMonoid('exact', G, SR) + sage: T._split_growth_and_coefficient_(log(x)^5 * x^2 * 4) + (x^2*log(x)^5, 4) + sage: var('y') + y + sage: T._split_growth_and_coefficient_(2^x * y * 4) + (2^x, 4*y) + """ + factors = self._get_factors_(data) + + growth_group = self.growth_group + coefficient_ring = self.coefficient_ring + + coefficients = [] + growths = [] + for f in factors: + try: + growths.append(growth_group(f)) + continue + except (ValueError, TypeError): + pass + + try: + coefficients.append(coefficient_ring(f)) + continue + except (ValueError, TypeError): + pass + + raise ValueError('Factor %s of %s is neither a coefficient (in %s) ' + 'nor growth (in %s).' % + (f, data, coefficient_ring, growth_group)) + + from sage.misc.misc_c import prod + growth = prod(growths) if growths else growth_group.one() + coefficient = prod(coefficients) if coefficients else coefficient_ring.one() + return (growth, coefficient) + + + def _get_factors_(self, data): + r""" + Split given ``data`` into separate factors. + + INPUT: + + - ``data`` -- an object. + + OUTPUT: + + A tuple. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: T_ZZ = TermMonoid('exact', G_ZZ, QQ) + sage: T_ZZ._get_factors_(x^2 * log(x)) + (x^2, log(x)) + """ + if isinstance(data, str): + from misc import split_str_by_op + return split_str_by_op(data, '*') + + try: + P = data.parent() + except AttributeError: + return (data,) + + from sage.symbolic.ring import SymbolicRing + if isinstance(P, SymbolicRing): + from sage.symbolic.operators import mul_vararg + if data.operator() == mul_vararg: + return tuple(data.operands()) + + return (data,) def _an_element_(self): @@ -1182,12 +1866,12 @@ def _an_element_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.OTermMonoid(G).an_element() # indirect doctest + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (GenericTermMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ') + sage: TermMonoid('O', G, QQ).an_element() # indirect doctest O(x) - sage: atm.GenericTermMonoid(G).an_element() # indirect doctest + sage: GenericTermMonoid(G, QQ).an_element() # indirect doctest Generic Term with growth x """ return self(self.growth_group.an_element()) @@ -1209,11 +1893,11 @@ def some_elements(self): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: tuple(atm.OTermMonoid(G).some_elements()) - (O(1), O(x), O(1/x), O(x^2), O(x^(-2)), O(x^3), ...) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: tuple(TermMonoid('O', G, QQ).some_elements()) + (O(1), O(x), O(x^(-1)), O(x^2), O(x^(-2)), O(x^3), ...) """ return iter(self(g) for g in self.growth_group.some_elements()) @@ -1235,10 +1919,10 @@ def le(self, left, right): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.GenericTermMonoid(growth_group=G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = GenericTermMonoid(G, QQ) sage: t1 = T(x); t2 = T(x^2) sage: T.le(t1, t2) True @@ -1262,10 +1946,10 @@ class OTerm(GenericTerm): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: OT = atm.OTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import OTermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: OT = OTermMonoid(G, QQ) sage: t1 = OT(x^-7); t2 = OT(x^5); t3 = OT(x^42) sage: t1, t2, t3 (O(x^(-7)), O(x^5), O(x^42)) @@ -1303,10 +1987,10 @@ def _repr_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: OT = atm.OTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: OT = TermMonoid('O', G, QQ) sage: t1 = OT(x); t2 = OT(x^2); t3 = OT(x^3) sage: t1._repr_(), t2._repr_() ('O(x)', 'O(x^2)') @@ -1326,10 +2010,10 @@ def __invert__(self): TESTS:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.OTermMonoid(G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = TermMonoid('O', G, QQ) sage: ~T(x) # indirect doctest Traceback (most recent call last): ... @@ -1338,6 +2022,38 @@ def __invert__(self): raise ZeroDivisionError('Cannot invert %s.' % (self,)) + def __pow__(self, exponent): + r""" + Calculate the power of this :class:`OTerm` to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + An :class:`OTerm`. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: t = TermMonoid('O', G, ZZ).an_element(); t + O(z) + sage: t^3 # indirect doctest + O(z^3) + sage: t^(1/2) # indirect doctest + O(z^(1/2)) + sage: t^(-1) # indirect doctest + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot take O(z) to exponent -1. + > *previous* ZeroDivisionError: rational division by zero + """ + return self._calculate_pow_test_zero_(exponent) + + def can_absorb(self, other): r""" Check whether this `O`-term can absorb ``other``. @@ -1360,9 +2076,9 @@ def can_absorb(self, other): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: OT = atm.TermMonoid('O', agg.GrowthGroup('x^ZZ')) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: OT = TermMonoid('O', GrowthGroup('x^ZZ'), QQ) sage: t1 = OT(x^21); t2 = OT(x^42) sage: t1.can_absorb(t2) False @@ -1399,10 +2115,10 @@ def _absorb_(self, other): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: OT = atm.OTermMonoid(growth_group=G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: OT = TermMonoid('O', G, QQ) sage: ot1 = OT(x); ot2 = OT(x^2) sage: ot1.absorb(ot1) O(x) @@ -1416,47 +2132,315 @@ def _absorb_(self, other): return self -class OTermMonoid(GenericTermMonoid): - r""" - Parent for asymptotic big `O`-terms. + def log_term(self, base=None): + r""" + Determine the logarithm of this O-term. - INPUT: + INPUT: - - ``growth_group`` -- a growth group. + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. - - ``category`` -- The category of the parent can be specified - in order to broaden the base structure. It has to be a subcategory - of ``Join of Category of monoids and Category of posets``. This - is also the default category if ``None`` is specified. + OUTPUT: - EXAMPLES:: + A tuple of terms. - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_x_ZZ = agg.GrowthGroup('x^ZZ') - sage: G_y_QQ = agg.GrowthGroup('y^QQ') - sage: OT_x_ZZ = atm.OTermMonoid(G_x_ZZ); OT_x_ZZ - Asymptotic O-Term Monoid x^ZZ - sage: OT_y_QQ = atm.OTermMonoid(G_y_QQ); OT_y_QQ - Asymptotic O-Term Monoid y^QQ + .. NOTE:: - `O`-term monoids can also be created by using the - :class:`term factory `:: + This method returns a tuple with the summands that come from + applying the rule `\log(x\cdot y) = \log(x) + \log(y)`. - sage: atm.TermMonoid('O', G_x_ZZ) is OT_x_ZZ - True - sage: atm.TermMonoid('O', agg.GrowthGroup('x^QQ')) - Asymptotic O-Term Monoid x^QQ - """ - # enable the category framework for elements - Element = OTerm + EXAMPLES:: + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T(x^2).log_term() + (O(log(x)),) + sage: T(x^1234).log_term() + (O(log(x)),) - def _coerce_map_from_(self, S): - r""" - Return whether ``S`` coerces into this term monoid. + :: - INPUT: + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ'), QQ) + sage: T('x * y').log_term() + (O(log(x)), O(log(y))) + + .. SEEALSO:: + + :meth:`ExactTerm.log_term`. + """ + return self._log_growth_(base=base) + + + def is_little_o_of_one(self): + r""" + Return whether this O-term is of order `o(1)`. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), QQ) + sage: T(x).is_little_o_of_one() + False + sage: T(1).is_little_o_of_one() + False + sage: T(x^(-1)).is_little_o_of_one() + True + + :: + + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * y^ZZ'), QQ) + sage: T('x * y^(-1)').is_little_o_of_one() + False + sage: T('x^(-1) * y').is_little_o_of_one() + False + sage: T('x^(-2) * y^(-3)').is_little_o_of_one() + True + + :: + + sage: T = TermMonoid('O', GrowthGroup('x^QQ * log(x)^QQ'), QQ) + sage: T('x * log(x)^2').is_little_o_of_one() + False + sage: T('x^2 * log(x)^(-1234)').is_little_o_of_one() + False + sage: T('x^(-1) * log(x)^4242').is_little_o_of_one() + True + sage: T('x^(-1/100) * log(x)^(1000/7)').is_little_o_of_one() + True + """ + return self.growth.is_lt_one() + + + def rpow(self, base): + r""" + Return the power of ``base`` to this O-term. + + INPUT: + + - ``base`` -- an element or ``'e'``. + + OUTPUT: + + A term. + + .. NOTE:: + + For :class:`OTerm`, the powers can only be + constructed for exponents `O(1)` or if ``base`` is `1`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T(1).rpow('e') + O(1) + sage: T(1).rpow(2) + O(1) + + :: + + sage: T.an_element().rpow(1) + 1 + sage: T('x^2').rpow(1) + 1 + + :: + + sage: T.an_element().rpow('e') + Traceback (most recent call last): + ... + ValueError: Cannot take e to the exponent O(x*log(x)) in + O-Term Monoid x^ZZ * log(x)^ZZ with implicit coefficients in Rational Field + sage: T('log(x)').rpow('e') + Traceback (most recent call last): + ... + ValueError: Cannot take e to the exponent O(log(x)) in + O-Term Monoid x^ZZ * log(x)^ZZ with implicit coefficients in Rational Field + """ + if self.is_one() and base != 0: + return self + if base == 1: + P = self.parent() + return ExactTermMonoid(P.growth_group, P.coefficient_ring).one() + raise ValueError('Cannot take %s to the exponent %s in %s' % + (base, self, self.parent())) + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this O-Term. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('O', GrowthGroup('x^ZZ'), ZZ) + sage: t = T.an_element(); t + O(x) + sage: t._substitute_({'x': SR.var('z')}) + Order(z) + sage: t._substitute_({'x': SR.var('z'), 'O': function('Oh')}) + Oh(z) + sage: u = AsymptoticRing('x^ZZ', ZZ)('2*x'); u + 2*x + sage: t._substitute_({'x': u}) + O(x) + sage: T(1/x)._substitute_({'x': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in O(x^(-1)) in + O-Term Monoid x^ZZ with implicit coefficients in Integer Ring. + > *previous* ZeroDivisionError: Cannot substitute in x^(-1) in + Growth Group x^ZZ. + >> *previous* ZeroDivisionError: rational division by zero + sage: t._substitute_({'x': 3}) + O(3) + sage: t._substitute_({'x': 'null'}) + Traceback (most recent call last): + ... + ArithmeticError: Cannot substitute in O(x) in + O-Term Monoid x^ZZ with implicit coefficients in Integer Ring. + > *previous* ArithmeticError: O(null) not defined + """ + try: + g = self.growth._substitute_(rules) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + try: + return rules['O'](g) + except KeyError: + pass + + try: + P = g.parent() + except AttributeError: + pass + else: + from asymptotic_ring import AsymptoticRing + from sage.symbolic.ring import SymbolicRing + + if isinstance(P, AsymptoticRing): + return g.O() + + elif isinstance(P, SymbolicRing): + return g.Order() + + try: + return sage.rings.big_oh.O(g) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + +class OTermMonoid(GenericTermMonoid): + r""" + Parent for asymptotic big `O`-terms. + + INPUT: + + - ``growth_group`` -- a growth group. + + - ``category`` -- The category of the parent can be specified + in order to broaden the base structure. It has to be a subcategory + of ``Join of Category of monoids and Category of posets``. This + is also the default category if ``None`` is specified. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import OTermMonoid + sage: G_x_ZZ = GrowthGroup('x^ZZ') + sage: G_y_QQ = GrowthGroup('y^QQ') + sage: OT_x_ZZ = OTermMonoid(G_x_ZZ, QQ); OT_x_ZZ + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + sage: OT_y_QQ = OTermMonoid(G_y_QQ, QQ); OT_y_QQ + O-Term Monoid y^QQ with implicit coefficients in Rational Field + + `O`-term monoids can also be created by using the + :class:`term factory `:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: TermMonoid('O', G_x_ZZ, QQ) is OT_x_ZZ + True + sage: TermMonoid('O', GrowthGroup('x^QQ'), QQ) + O-Term Monoid x^QQ with implicit coefficients in Rational Field + """ + + # enable the category framework for elements + Element = OTerm + + + def _create_element_(self, growth, coefficient): + r""" + Helper method which creates an element by using the ``element_class``. + + INPUT: + + - ``growth`` -- a growth element. + + - ``coefficient`` -- an element of the coefficient ring (will be + ignored since we create an O-Term). + + OUTPUT: + + An O-term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: T = TermMonoid('O', G, QQ) + sage: T(G.gen()) # indirect doctest + O(x) + sage: T(G.gen(), SR.var('y')) # indirect doctest + Traceback (most recent call last): + ... + ValueError: Cannot create O(x) since given coefficient y + is not valid in O-Term Monoid x^ZZ with implicit coefficients in + Rational Field. + > *previous* TypeError: unable to convert y to a rational + """ + if coefficient is not None: + try: + self.coefficient_ring(coefficient) + except (TypeError, ValueError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ValueError('Cannot create O(%s) since given coefficient %s ' + 'is not valid in %s.' % + (growth, coefficient, self)), e) + return self.element_class(self, growth) + + + def _coerce_map_from_(self, S): + r""" + Return whether ``S`` coerces into this term monoid. + + INPUT: - ``S`` -- a parent. @@ -1478,13 +2462,13 @@ def _coerce_map_from_(self, S): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() - sage: G_QQ = agg.GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() - sage: OT_ZZ = atm.OTermMonoid(G_ZZ) - sage: OT_QQ = atm.OTermMonoid(G_QQ) - sage: ET = atm.ExactTermMonoid(G_ZZ, ZZ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G_ZZ = GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() + sage: G_QQ = GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() + sage: OT_ZZ = TermMonoid('O', G_ZZ, QQ) + sage: OT_QQ = TermMonoid('O', G_QQ, QQ) + sage: ET = TermMonoid('exact', G_ZZ, ZZ) Now, the :class:`OTermMonoid` whose growth group is over the integer ring has to coerce into the :class:`OTermMonoid` with @@ -1520,13 +2504,14 @@ def _repr_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: atm.OTermMonoid(G)._repr_() - 'Asymptotic O-Term Monoid x^ZZ' + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: TermMonoid('O', G, QQ)._repr_() + 'O-Term Monoid x^ZZ with implicit coefficients in Rational Field' """ - return 'Asymptotic O-Term Monoid %s' % (self.growth_group._repr_short_(),) + return 'O-Term Monoid %s with implicit coefficients in %s' % \ + (self.growth_group._repr_short_(), self.coefficient_ring) class TermWithCoefficient(GenericTerm): @@ -1541,19 +2526,19 @@ class TermWithCoefficient(GenericTerm): - ``growth`` -- an asymptotic growth element of the parent's growth group. - - ``coefficient`` -- an element of the parent's base ring. + - ``coefficient`` -- an element of the parent's coefficient ring. EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: CT_ZZ = atm.TermWithCoefficientMonoid(G, ZZ) - sage: CT_QQ = atm.TermWithCoefficientMonoid(G, QQ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: CT_ZZ = TermWithCoefficientMonoid(G, ZZ) + sage: CT_QQ = TermWithCoefficientMonoid(G, QQ) sage: CT_ZZ(x^2, 5) - Asymptotic Term with coefficient 5 and growth x^2 + Term with coefficient 5 and growth x^2 sage: CT_QQ(x^3, 3/8) - Asymptotic Term with coefficient 3/8 and growth x^3 + Term with coefficient 3/8 and growth x^3 """ def __init__(self, parent, growth, coefficient): @@ -1564,27 +2549,29 @@ def __init__(self, parent, growth, coefficient): First, we define some monoids:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: CT_ZZ = atm.TermWithCoefficientMonoid(G, ZZ) - sage: CT_QQ = atm.TermWithCoefficientMonoid(G, QQ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: CT_ZZ = TermWithCoefficientMonoid(G, ZZ) + sage: CT_QQ = TermWithCoefficientMonoid(G, QQ) - The coefficients have to be from the given base ring:: + The coefficients have to be from the given coefficient ring:: sage: t = CT_ZZ(x, 1/2) Traceback (most recent call last): ... - ValueError: 1/2 is not in Integer Ring + ValueError: 1/2 is not a coefficient in + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring. sage: t = CT_QQ(x, 1/2); t - Asymptotic Term with coefficient 1/2 and growth x + Term with coefficient 1/2 and growth x For technical reasons, the coefficient 0 is not allowed:: sage: t = CT_ZZ(x^42, 0) Traceback (most recent call last): ... - ValueError: 0 is not a valid coefficient. + ValueError: Zero coefficient 0 is not allowed in + Generic Term Monoid x^ZZ with (implicit) coefficients in Integer Ring. The conversion of growth elements also works for the creation of terms with coefficient:: @@ -1592,15 +2579,18 @@ def __init__(self, parent, growth, coefficient): sage: x = SR('x'); x.parent() Symbolic Ring sage: CT_ZZ(x^42, 42) - Asymptotic Term with coefficient 42 and growth x^42 + Term with coefficient 42 and growth x^42 """ - if coefficient not in parent.base_ring(): - raise ValueError('%s is not in %s' % (coefficient, - parent.base_ring())) - elif coefficient == 0: - raise ValueError('0 is not a valid coefficient') - - self.coefficient = parent.base_ring()(coefficient) + try: + coefficient = parent.coefficient_ring(coefficient) + except (ValueError, TypeError): + raise ValueError('%s is not a coefficient in %s.' % + (coefficient, parent)) + if coefficient == 0: + raise ValueError('Zero coefficient %s is not allowed in %s.' % + (coefficient, parent)) + + self.coefficient = coefficient super(TermWithCoefficient, self).__init__(parent=parent, growth=growth) @@ -1618,14 +2608,14 @@ def _repr_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.TermWithCoefficientMonoid(G, ZZ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = TermWithCoefficientMonoid(G, ZZ) sage: T(x^2, 5)._repr_() - 'Asymptotic Term with coefficient 5 and growth x^2' + 'Term with coefficient 5 and growth x^2' """ - return 'Asymptotic Term with coefficient %s and growth %s' % \ + return 'Term with coefficient %s and growth %s' % \ (self.coefficient, self.growth) @@ -1650,11 +2640,11 @@ def _mul_(self, other): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: CT = atm.TermWithCoefficientMonoid(G, ZZ) - sage: ET = atm.ExactTermMonoid(G, ZZ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (TermWithCoefficientMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: CT = TermWithCoefficientMonoid(G, ZZ) + sage: ET = TermMonoid('exact', G, ZZ) This method handles the multiplication of abstract terms with coefficient (i.e. :class:`TermWithCoefficient`) and @@ -1663,7 +2653,7 @@ def _mul_(self, other): sage: t1 = CT(x^2, 2); t2 = CT(x^3, 3) sage: t1 * t2 - Asymptotic Term with coefficient 6 and growth x^5 + Term with coefficient 6 and growth x^5 And now, an example for exact terms:: @@ -1675,6 +2665,92 @@ def _mul_(self, other): self.coefficient * other.coefficient) + def _calculate_pow_(self, exponent): + r""" + Helper function for :meth:`~ExactTerm.__pow__` which calculates + the power of this element to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + A term. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: T = TermWithCoefficientMonoid(G, ZZ) + sage: t = T('2*z'); t + Term with coefficient 2 and growth z + sage: t._calculate_pow_(3) + Term with coefficient 8 and growth z^3 + sage: t._calculate_pow_(-2) + Term with coefficient 1/4 and growth z^(-2) + + :: + + sage: T = TermWithCoefficientMonoid(G, CIF) + sage: T(G.an_element(), coefficient=CIF(RIF(-1,1), RIF(-1,1)))._calculate_pow_(I) + Traceback (most recent call last): + ... + ArithmeticError: Cannot take Term with coefficient 0.? + 0.?*I and + growth z to the exponent I in Generic Term Monoid z^ZZ with + (implicit) coefficients in Complex Interval Field with + 53 bits of precision since its coefficient 0.? + 0.?*I + cannot be taken to this exponent. + > *previous* ValueError: Can't take the argument of + interval strictly containing zero + """ + try: + c = self.coefficient ** exponent + except (TypeError, ValueError, ZeroDivisionError) as e: + from misc import combine_exceptions + raise combine_exceptions( + ArithmeticError('Cannot take %s to the exponent %s in %s since its ' + 'coefficient %s cannot be taken to this exponent.' % + (self, exponent, self.parent(), self.coefficient)), e) + return super(TermWithCoefficient, self)._calculate_pow_(exponent, new_coefficient=c) + + + def _log_coefficient_(self, base=None): + r""" + Helper function to calculate the logarithm of the coefficient of this element. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of terms. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T(3*x^2)._log_coefficient_() + (log(3),) + sage: T(x^1234).log_term() # indirect doctest + (1234*log(x),) + + .. SEEALSO:: + + :meth:`ExactTerm.log_term`, + :meth:`OTerm.log_term`. + """ + if self.coefficient.is_one(): + return tuple() + from sage.functions.log import log + return (self.parent()._create_element_in_extension_( + self.parent().growth_group.one(), log(self.coefficient, base=base)),) + + def _le_(self, other): r""" Return whether this asymptotic term with coefficient grows @@ -1747,280 +2823,84 @@ def _eq_(self, other): sage: from sage.rings.asymptotic.growth_group import GrowthGroup sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid sage: T = TermWithCoefficientMonoid(GrowthGroup('x^ZZ'), ZZ) - sage: t = T.an_element(); t - Asymptotic Term with coefficient 1 and growth x - sage: t == T(x, 1) - True - sage: t == T(x, 2) - False - sage: t == T(x^2, 1) - False - """ - return super(TermWithCoefficient, self)._eq_(other) and \ - self.coefficient == other.coefficient - - -class TermWithCoefficientMonoid(GenericTermMonoid): - r""" - This class implements the base structure for parents of - asymptotic terms possessing a coefficient from some coefficient - ring. In particular, this is also the parent for - :class:`TermWithCoefficient`. - - INPUT: - - - ``growth_group`` -- a growth group. - - - ``category`` -- The category of the parent can be specified - in order to broaden the base structure. It has to be a subcategory - of ``Join of Category of monoids and Category of posets``. This - is also the default category if ``None`` is specified. - - - ``base_ring`` -- the ring which contains the - coefficients of the elements. - - EXAMPLES:: - - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() - sage: G_QQ = agg.GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() - sage: TC_ZZ = atm.TermWithCoefficientMonoid(G_ZZ, QQ); TC_ZZ - Term Monoid x^ZZ with coefficients from Rational Field - sage: TC_QQ = atm.TermWithCoefficientMonoid(G_QQ, QQ); TC_QQ - Term Monoid x^QQ with coefficients from Rational Field - sage: TC_ZZ == TC_QQ or TC_ZZ is TC_QQ - False - sage: TC_QQ.coerce_map_from(TC_ZZ) - Conversion map: - From: Term Monoid x^ZZ with coefficients from Rational Field - To: Term Monoid x^QQ with coefficients from Rational Field - """ - - # enable the category framework for elements - Element = TermWithCoefficient - - @sage.misc.superseded.experimental(trac_number=17601) - def __init__(self, growth_group, base_ring, category=None): - r""" - For more information see :class:`TermWithCoefficientMonoid`. - - EXAMPLES:: - - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T_ZZ = atm.TermWithCoefficientMonoid(G, ZZ); T_ZZ - Term Monoid x^ZZ with coefficients from Integer Ring - sage: T_QQ = atm.TermWithCoefficientMonoid(G, QQ); T_QQ - Term Monoid x^ZZ with coefficients from Rational Field - sage: T_QQ.category() - Join of Category of monoids and Category of posets - - TESTS:: - - sage: T = atm.TermWithCoefficientMonoid(G, None) - Traceback (most recent call last): - ... - ValueError: None is not a valid base ring. - """ - from sage.categories.rings import Rings - if base_ring not in Rings(): - raise ValueError('%s is not a valid base ring.' % (base_ring,)) - self._base_ring_ = base_ring - super(TermWithCoefficientMonoid, - self).__init__(growth_group=growth_group, category=category) - - def base_ring(self): - r""" - The base ring of this term monoid, i.e. the ring where - the coefficients are from. - - EXAMPLES:: - - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.ExactTermMonoid(G, ZZ).base_ring() - Integer Ring - """ - return self._base_ring_ - - - def _coerce_map_from_(self, S): - r""" - Return whether ``S`` coerces into this term monoid. - - INPUT: - - - ``S`` -- a parent. - - OUTPUT: - - A boolean. - - .. NOTE:: - - Another term monoid ``S`` coerces into this - :class:`TermWithCoefficientMonoid` - if both, the base ring as well as the growth - group underlying ``S`` coerce into the base ring and the - growth group underlying this term monoid, respectively. - - EXAMPLES:: - - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.GrowthGroup('x^ZZ') - sage: G_QQ = agg.GrowthGroup('x^QQ') - sage: TC_ZZ = atm.TermWithCoefficientMonoid(G_ZZ, ZZ); TC_ZZ - Term Monoid x^ZZ with coefficients from Integer Ring - sage: TC_QQ = atm.TermWithCoefficientMonoid(G_QQ, QQ); TC_QQ - Term Monoid x^QQ with coefficients from Rational Field - sage: TC_QQ.has_coerce_map_from(TC_ZZ) # indirect doctest - True - sage: TC_ZZ.has_coerce_map_from(TC_QQ) # indirect doctest - False - """ - if isinstance(S, TermWithCoefficientMonoid): - return (super(TermWithCoefficientMonoid, self)._coerce_map_from_(S) and - self.base_ring().has_coerce_map_from(S.base_ring())) - - - def _element_constructor_(self, data, coefficient=None): - r""" - Construct an asymptotic term with coefficient or convert - the given object ``data`` to this term monoid. - - INPUT: - - - ``data`` -- a growth element or an object representing the - element to be initialized. - - - ``coefficient`` -- an element of the base ring. - - OUTPUT: - - An asymptotic term. - - .. NOTE:: - - The object ``data`` is either an asymptotic term with - coefficient that is to be coerced into this term monoid, - or an asymptotic growth element that is used together - with ``coefficient`` in order to create an element of - this term monoid. + sage: t = T.an_element(); t + Term with coefficient 1 and growth x + sage: t == T(x, 1) + True + sage: t == T(x, 2) + False + sage: t == T(x^2, 1) + False + """ + return super(TermWithCoefficient, self)._eq_(other) and \ + self.coefficient == other.coefficient - EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ') - sage: T = atm.TermWithCoefficientMonoid(G, ZZ) - sage: x.parent() == SR - True - sage: t1 = T(x^2, 5); t1 # indirect doctest - Asymptotic Term with coefficient 5 and growth x^2 +class TermWithCoefficientMonoid(GenericTermMonoid): + r""" + This class implements the base structure for parents of + asymptotic terms possessing a coefficient from some coefficient + ring. In particular, this is also the parent for + :class:`TermWithCoefficient`. - TESTS:: + INPUT: - sage: T(5 * x^5) - Asymptotic Term with coefficient 5 and growth x^5 - sage: T(G.gen()^10) - Traceback (most recent call last): - ... - ValueError: Coefficient is not specified. Cannot continue. - sage: T(G.gen()^10, coefficient=10) - Asymptotic Term with coefficient 10 and growth x^10 - sage: T(x^123) - Asymptotic Term with coefficient 1 and growth x^123 + - ``growth_group`` -- a growth group. - :: + - ``category`` -- The category of the parent can be specified + in order to broaden the base structure. It has to be a subcategory + of ``Join of Category of monoids and Category of posets``. This + is also the default category if ``None`` is specified. - sage: T(x) - Asymptotic Term with coefficient 1 and growth x + - ``coefficient_ring`` -- the ring which contains the + coefficients of the elements. - :: + EXAMPLES:: - sage: G_log = agg.GrowthGroup('log(x)^ZZ') - sage: T_log = atm.TermWithCoefficientMonoid(G_log, ZZ) - sage: T_log(log(x)) - Asymptotic Term with coefficient 1 and growth log(x) - """ - if isinstance(data, self.element_class) and data.parent() == self: - return data - elif isinstance(data, TermWithCoefficient): - return self.element_class(self, data.growth, data.coefficient) - elif isinstance(data, int) and data == 0: - raise ValueError('No input specified. Cannot continue.') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: G_ZZ = GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() + sage: G_QQ = GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() + sage: TC_ZZ = TermWithCoefficientMonoid(G_ZZ, QQ); TC_ZZ + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + sage: TC_QQ = TermWithCoefficientMonoid(G_QQ, QQ); TC_QQ + Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field + sage: TC_ZZ == TC_QQ or TC_ZZ is TC_QQ + False + sage: TC_QQ.coerce_map_from(TC_ZZ) + Conversion map: + From: Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + To: Generic Term Monoid x^QQ with (implicit) coefficients in Rational Field + """ - try: - if coefficient is not None: - data = self.growth_group(data) - return self.element_class(self, data, coefficient) - else: - P = data.parent() - from sage.symbolic.ring import SR - import operator - from sage.symbolic.operators import mul_vararg - if P is SR: - op = data.operator() - if op == mul_vararg: - data, coef_tmp = data.operands() - data = self.growth_group(data) - elif op in (operator.pow, None) or\ - isinstance(op, sage.functions.log.Function_log): - coef_tmp = 1 - data = self.growth_group(data) - else: - coeffs = data.coefficients() - if type(coeffs) == list: - # (multivariate) polynomial ring - coef_tmp = coeffs[0] - data = self.growth_group(data / coef_tmp) - elif type(coeffs) == dict: - # power series ring - coef_tmp = coeffs.values()[0] - data = self.growth_group(data / coef_tmp) - - return self.element_class(self, data, coef_tmp) - except (ValueError, AttributeError): - if coefficient is None: - raise ValueError('Coefficient is not specified. ' - 'Cannot continue.') - elif coefficient not in self.base_ring(): - raise ValueError('%s is not in %s' - % (coefficient, self.base_ring())) - elif coefficient == 0: - raise ValueError('0 is not a valid coefficient.') - raise ValueError('Input is ambiguous: cannot convert %s with ' - 'coefficient %s to a term with coefficient.' - % (data, coefficient)) + # enable the category framework for elements + Element = TermWithCoefficient - def _repr_(self): + def _create_element_(self, growth, coefficient): r""" - A representation string for this monoid of terms with - coefficient. + Helper method which creates an element by using the ``element_class``. INPUT: - Nothing. + - ``growth`` -- a growth element. + + - ``coefficient`` -- an element of the coefficient ring. OUTPUT: - A string. + A term. - EXAMPLES:: + TESTS:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.TermWithCoefficientMonoid(G, ZZ)._repr_() - 'Term Monoid x^ZZ with coefficients from Integer Ring' + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G_ZZ = GrowthGroup('x^ZZ') + sage: T_ZZ = TermMonoid('exact', G_ZZ, QQ) + sage: T_ZZ(G_ZZ.gen(), 4/3) # indirect doctest + 4/3*x """ - return 'Term Monoid %s with coefficients from ' \ - '%s' % (self.growth_group._repr_short_(), self.base_ring()) + return self.element_class(self, growth, coefficient) def _an_element_(self): @@ -2037,18 +2917,18 @@ def _an_element_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.TermWithCoefficientMonoid(G, ZZ).an_element() # indirect doctest - Asymptotic Term with coefficient 1 and growth x - sage: atm.ExactTermMonoid(G, ZZ).an_element() # indirect doctest + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (TermWithCoefficientMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ') + sage: TermWithCoefficientMonoid(G, ZZ).an_element() # indirect doctest + Term with coefficient 1 and growth x + sage: TermMonoid('exact', G, ZZ).an_element() # indirect doctest x - sage: atm.ExactTermMonoid(G, QQ).an_element() # indirect doctest + sage: TermMonoid('exact', G, QQ).an_element() # indirect doctest 1/2*x """ return self(self.growth_group.an_element(), - self.base_ring().an_element()) + self.coefficient_ring.an_element()) def some_elements(self): @@ -2068,10 +2948,10 @@ def some_elements(self): EXAMPLES:: sage: from itertools import islice - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('z^QQ') - sage: T = atm.ExactTermMonoid(G, ZZ) + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^QQ') + sage: T = TermMonoid('exact', G, ZZ) sage: tuple(islice(T.some_elements(), 10)) (z^(1/2), z^(-1/2), -z^(1/2), z^2, -z^(-1/2), 2*z^(1/2), z^(-2), -z^2, 2*z^(-1/2), -2*z^(1/2)) @@ -2079,7 +2959,7 @@ def some_elements(self): from sage.misc.mrange import cantor_product return iter(self(g, c) for g, c in cantor_product( self.growth_group.some_elements(), - iter(c for c in self.base_ring().some_elements() if c != 0))) + iter(c for c in self.coefficient_ring.some_elements() if c != 0))) class ExactTerm(TermWithCoefficient): @@ -2095,14 +2975,14 @@ class ExactTerm(TermWithCoefficient): - ``growth`` -- an asymptotic growth element from ``parent.growth_group``. - - ``coefficient`` -- an element from ``parent.base_ring()``. + - ``coefficient`` -- an element from ``parent.coefficient_ring``. EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: ET = atm.ExactTermMonoid(G, QQ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import (ExactTermMonoid, TermMonoid) + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: ET = ExactTermMonoid(G, QQ) Asymptotic exact terms may be multiplied (with the usual rules applying):: @@ -2114,7 +2994,7 @@ class ExactTerm(TermWithCoefficient): They may also be multiplied with `O`-terms:: - sage: OT = atm.OTermMonoid(G) + sage: OT = TermMonoid('O', G, QQ) sage: ET(x^2, 42) * OT(x) O(x^3) @@ -2145,14 +3025,6 @@ class ExactTerm(TermWithCoefficient): Symbolic Ring sage: ET(5*x^2) 5*x^2 - sage: x = ZZ['x'].gen(); x.parent() - Univariate Polynomial Ring in x over Integer Ring - sage: ET(5*x^2) - 5*x^2 - sage: x = ZZ[['x']].gen(); x.parent() - Power Series Ring in x over Integer Ring - sage: ET(5*x^2) - 5*x^2 """ def _repr_(self): @@ -2169,10 +3041,10 @@ def _repr_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: ET = atm.ExactTermMonoid(G, ZZ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: ET = TermMonoid('exact', G, ZZ) sage: et1 = ET(x^2, 2); et1 2*x^2 @@ -2185,15 +3057,16 @@ def _repr_(self): sage: ET(x^0, 42) 42 """ - if self.growth._raw_element_ == 0: - return '%s' % self.coefficient + g = repr(self.growth) + c = repr(self.coefficient) + if g == '1': + return c + elif c == '1': + return '%s' % (g,) + elif c == '-1': + return '-%s' % (g,) else: - if self.coefficient == 1: - return '%s' % self.growth - elif self.coefficient == -1: - return '-%s' % self.growth - else: - return '%s*%s' % (self.coefficient, self.growth) + return '%s*%s' % (c, g) def __invert__(self): @@ -2206,19 +3079,50 @@ def __invert__(self): TESTS:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: T = atm.ExactTermMonoid(G, QQ) - sage: ~T(x, 1/2) # indirect doctest - 2*1/x + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = TermMonoid('exact', G, ZZ) + sage: ~T(x, 2) # indirect doctest + 1/2*x^(-1) + sage: (~T(x, 2)).parent() + Exact Term Monoid x^ZZ with coefficients in Rational Field """ try: c = ~self.coefficient except ZeroDivisionError: raise ZeroDivisionError('Cannot invert %s since its coefficient %s ' 'cannot be inverted.' % (self, self.coefficient)) - return self.parent()(~self.growth, c) + g = ~self.growth + return self.parent()._create_element_in_extension_(g, c) + + + def __pow__(self, exponent): + r""" + Calculate the power of this :class:`ExactTerm` to the given ``exponent``. + + INPUT: + + - ``exponent`` -- an element. + + OUTPUT: + + An :class:`ExactTerm`. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^ZZ') + sage: T = TermMonoid('exact', G, ZZ) + sage: t = T('2*z'); t + 2*z + sage: t^3 # indirect doctest + 8*z^3 + sage: t^(1/2) # indirect doctest + sqrt(2)*z^(1/2) + """ + return self._calculate_pow_(exponent) def can_absorb(self, other): @@ -2244,9 +3148,9 @@ def can_absorb(self, other): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: ET = atm.TermMonoid('exact', agg.GrowthGroup('x^ZZ'), ZZ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: ET = TermMonoid('exact', GrowthGroup('x^ZZ'), ZZ) sage: t1 = ET(x^21, 1); t2 = ET(x^21, 2); t3 = ET(x^42, 1) sage: t1.can_absorb(t2) True @@ -2281,10 +3185,10 @@ def _absorb_(self, other): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: ET = atm.ExactTermMonoid(G, QQ) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: ET = TermMonoid('exact', G, QQ) Asymptotic exact terms can absorb other asymptotic exact terms with the same growth:: @@ -2303,12 +3207,245 @@ def _absorb_(self, other): ArithmeticError: x^5 cannot absorb 2*x^2 """ coeff_new = self.coefficient + other.coefficient - if coeff_new == 0: + if coeff_new.is_zero(): return None else: return self.parent()(self.growth, coeff_new) + def log_term(self, base=None): + r""" + Determine the logarithm of this exact term. + + INPUT: + + - ``base`` -- the base of the logarithm. If ``None`` + (default value) is used, the natural logarithm is taken. + + OUTPUT: + + A tuple of terms. + + .. NOTE:: + + This method returns a tuple with the summands that come from + applying the rule `\log(x\cdot y) = \log(x) + \log(y)`. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * log(x)^ZZ'), SR) + sage: T(3*x^2).log_term() + (log(3), 2*log(x)) + sage: T(x^1234).log_term() + (1234*log(x),) + sage: T(49*x^7).log_term(base=7) + (log(49)/log(7), 7/log(7)*log(x)) + + :: + + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * log(x)^ZZ * y^ZZ * log(y)^ZZ'), SR) + sage: T('x * y').log_term() + (log(x), log(y)) + sage: T('4 * x * y').log_term(base=2) + (log(4)/log(2), 1/log(2)*log(x), 1/log(2)*log(y)) + + .. SEEALSO:: + + :meth:`OTerm.log_term`. + """ + return self._log_coefficient_(base=base) + self._log_growth_(base=base) + + + def is_constant(self): + r""" + Return whether this term is an (exact) constant. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + .. NOTE:: + + Only :class:`ExactTerm` with constant growth (`1`) are + constant. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * log(x)^ZZ'), QQ) + sage: T('x * log(x)').is_constant() + False + sage: T('3*x').is_constant() + False + sage: T(1/2).is_constant() + True + sage: T(42).is_constant() + True + """ + return self.growth.is_one() + + + def is_little_o_of_one(self): + r""" + Return whether this exact term is of order `o(1)`. + + INPUT: + + Nothing. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ'), QQ) + sage: T(x).is_little_o_of_one() + False + sage: T(1).is_little_o_of_one() + False + sage: T(x^(-1)).is_little_o_of_one() + True + + :: + + sage: T = TermMonoid('exact', GrowthGroup('x^ZZ * y^ZZ'), QQ) + sage: T('x * y^(-1)').is_little_o_of_one() + False + sage: T('x^(-1) * y').is_little_o_of_one() + False + sage: T('x^(-2) * y^(-3)').is_little_o_of_one() + True + + :: + + sage: T = TermMonoid('exact', GrowthGroup('x^QQ * log(x)^QQ'), QQ) + sage: T('x * log(x)^2').is_little_o_of_one() + False + sage: T('x^2 * log(x)^(-1234)').is_little_o_of_one() + False + sage: T('x^(-1) * log(x)^4242').is_little_o_of_one() + True + sage: T('x^(-1/100) * log(x)^(1000/7)').is_little_o_of_one() + True + """ + return self.growth.is_lt_one() + + + def rpow(self, base): + r""" + Return the power of ``base`` to this exact term. + + INPUT: + + - ``base`` -- an element or ``'e'``. + + OUTPUT: + + A term. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: T = TermMonoid('exact', GrowthGroup('QQ^x * x^ZZ * log(x)^ZZ'), QQ) + sage: T('x').rpow(2) + 2^x + sage: T('log(x)').rpow('e') + x + sage: T('42*log(x)').rpow('e') + x^42 + sage: T('3*x').rpow(2) + 8^x + + :: + + sage: T('3*x^2').rpow(2) + Traceback (most recent call last): + ... + ArithmeticError: Cannot construct 2^(x^2) in + Growth Group QQ^x * x^ZZ * log(x)^ZZ + > *previous* TypeError: unsupported operand parent(s) for '*': + 'Growth Group QQ^x * x^ZZ * log(x)^ZZ' and 'Growth Group ZZ^(x^2)' + """ + P = self.parent() + + if self.is_constant(): + if not hasattr(base, 'parent'): + base = P.coefficient_ring(base) + return P._create_element_in_extension_( + P.growth_group.one(), base ** self.coefficient) + + elem = P._create_element_in_extension_( + self.growth.rpow(base), P.coefficient_ring.one()) + return elem ** self.coefficient + + + def _substitute_(self, rules): + r""" + Substitute the given ``rules`` in this exact term. + + INPUT: + + - ``rules`` -- a dictionary. + + OUTPUT: + + An object. + + TESTS:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: E = TermMonoid('exact', GrowthGroup('x^ZZ'), ZZ) + sage: e = E.an_element(); e + x + sage: e._substitute_({'x': SR.var('z')}) + z + sage: E(2/x)._substitute_({'x': 0}) + Traceback (most recent call last): + ... + ZeroDivisionError: Cannot substitute in 2*x^(-1) in + Exact Term Monoid x^ZZ with coefficients in Integer Ring. + > *previous* ZeroDivisionError: Cannot substitute in x^(-1) in + Growth Group x^ZZ. + >> *previous* ZeroDivisionError: rational division by zero + sage: (e*e)._substitute_({'x': 'something'}) + 'somethingsomething' + sage: E(1/x)._substitute_({'x': 'something'}) + '' + sage: E(1/x)._substitute_({'x': ZZ}) + Traceback (most recent call last): + ... + ValueError: Cannot substitute in x^(-1) in + Exact Term Monoid x^ZZ with coefficients in Integer Ring. + > *previous* ValueError: Cannot substitute in x^(-1) in Growth Group x^ZZ. + >> *previous* ValueError: rank (=-1) must be nonnegative + """ + try: + g = self.growth._substitute_(rules) + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + c = self.coefficient + + try: + return c * g + except (ArithmeticError, TypeError, ValueError) as e: + from misc import substitute_raise_exception + substitute_raise_exception(self, e) + + class ExactTermMonoid(TermWithCoefficientMonoid): r""" Parent for asymptotic exact terms, implemented in @@ -2323,35 +3460,38 @@ class ExactTermMonoid(TermWithCoefficientMonoid): of ``Join of Category of monoids and Category of posets``. This is also the default category if ``None`` is specified. - - ``base_ring`` -- the ring which contains the coefficients of + - ``coefficient_ring`` -- the ring which contains the coefficients of the elements. EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G_ZZ = agg.GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() - sage: G_QQ = agg.GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() - sage: ET_ZZ = atm.ExactTermMonoid(G_ZZ, ZZ); ET_ZZ - Exact Term Monoid x^ZZ with coefficients from Integer Ring - sage: ET_QQ = atm.ExactTermMonoid(G_QQ, QQ); ET_QQ - Exact Term Monoid x^QQ with coefficients from Rational Field + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import ExactTermMonoid + sage: G_ZZ = GrowthGroup('x^ZZ'); x_ZZ = G_ZZ.gen() + sage: G_QQ = GrowthGroup('x^QQ'); x_QQ = G_QQ.gen() + sage: ET_ZZ = ExactTermMonoid(G_ZZ, ZZ); ET_ZZ + Exact Term Monoid x^ZZ with coefficients in Integer Ring + sage: ET_QQ = ExactTermMonoid(G_QQ, QQ); ET_QQ + Exact Term Monoid x^QQ with coefficients in Rational Field sage: ET_QQ.coerce_map_from(ET_ZZ) Conversion map: - From: Exact Term Monoid x^ZZ with coefficients from Integer Ring - To: Exact Term Monoid x^QQ with coefficients from Rational Field + From: Exact Term Monoid x^ZZ with coefficients in Integer Ring + To: Exact Term Monoid x^QQ with coefficients in Rational Field Exact term monoids can also be created using the :class:`term factory `:: - sage: atm.TermMonoid('exact', G_ZZ, ZZ) is ET_ZZ + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: TermMonoid('exact', G_ZZ, ZZ) is ET_ZZ True - sage: atm.TermMonoid('exact', agg.GrowthGroup('x^ZZ'), QQ) - Exact Term Monoid x^ZZ with coefficients from Rational Field + sage: TermMonoid('exact', GrowthGroup('x^ZZ'), QQ) + Exact Term Monoid x^ZZ with coefficients in Rational Field """ + # enable the category framework for elements Element = ExactTerm + def _repr_(self): r""" A representation string for this exact term monoid. @@ -2366,14 +3506,14 @@ def _repr_(self): EXAMPLES:: - sage: import sage.rings.asymptotic.term_monoid as atm - sage: import sage.rings.asymptotic.growth_group as agg - sage: G = agg.GrowthGroup('x^ZZ'); x = G.gen() - sage: atm.ExactTermMonoid(G, QQ)._repr_() - 'Exact Term Monoid x^ZZ with coefficients from Rational Field' + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: TermMonoid('exact', G, QQ)._repr_() + 'Exact Term Monoid x^ZZ with coefficients in Rational Field' """ - return 'Exact Term Monoid %s with coefficients from %s' % \ - (self.growth_group._repr_short_(), self.base_ring()) + return 'Exact Term Monoid %s with coefficients in %s' % \ + (self.growth_group._repr_short_(), self.coefficient_ring) class TermMonoidFactory(sage.structure.factory.UniqueFactory): @@ -2385,14 +3525,22 @@ class TermMonoidFactory(sage.structure.factory.UniqueFactory): - :class:`ExactTermMonoid`. + .. NOTE:: + + An instance of this factory is available as ``TermMonoid``. + INPUT: - - ``term`` -- the kind of term that shall be created. Either - ``'exact'`` or ``'O'`` (capital letter ``O``). + - ``term`` -- the kind of term that shall be created. Either a string + ``'exact'`` or ``'O'`` (capital letter ``O``), + or an existing instance of a term. - ``growth_group`` -- a growth group. - - ``base_ring`` -- the base ring for coefficients. + - ``coefficient_ring`` -- a ring. + + - ``asymptotic_ring`` -- if specified, then ``growth_group`` and + ``coefficient_ring`` are taken from this asymptotic ring. OUTPUT: @@ -2400,15 +3548,85 @@ class TermMonoidFactory(sage.structure.factory.UniqueFactory): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: OT = atm.TermMonoid('O', G); OT - Asymptotic O-Term Monoid x^ZZ - sage: ET = atm.TermMonoid('exact', G, ZZ); ET - Exact Term Monoid x^ZZ with coefficients from Integer Ring + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: TermMonoid('O', G, QQ) + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + sage: TermMonoid('exact', G, ZZ) + Exact Term Monoid x^ZZ with coefficients in Integer Ring + + :: + + sage: R = AsymptoticRing(growth_group=G, coefficient_ring=QQ) + sage: TermMonoid('exact', asymptotic_ring=R) + Exact Term Monoid x^ZZ with coefficients in Rational Field + sage: TermMonoid('O', asymptotic_ring=R) + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + + TESTS:: + + sage: TermMonoid(TermMonoid('O', G, ZZ), asymptotic_ring=R) + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + sage: TermMonoid(TermMonoid('exact', G, ZZ), asymptotic_ring=R) + Exact Term Monoid x^ZZ with coefficients in Rational Field + sage: from sage.rings.asymptotic.term_monoid import GenericTermMonoid + sage: TermMonoid(GenericTermMonoid(G, ZZ), asymptotic_ring=R) + Generic Term Monoid x^ZZ with (implicit) coefficients in Rational Field + + :: + + sage: TestSuite(TermMonoid('exact', GrowthGroup('x^ZZ'), QQ)).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass + + :: + + sage: TestSuite(TermMonoid('O', GrowthGroup('x^QQ'), ZZ)).run(verbose=True) # long time + running ._test_an_element() . . . pass + running ._test_associativity() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_one() . . . pass + running ._test_pickling() . . . pass + running ._test_prod() . . . pass + running ._test_some_elements() . . . pass """ - def create_key_and_extra_args(self, term, growth_group, base_ring=None, + def create_key_and_extra_args(self, term, + growth_group=None, coefficient_ring=None, + asymptotic_ring=None, **kwds): r""" Given the arguments and keyword, create a key that uniquely @@ -2416,43 +3634,64 @@ def create_key_and_extra_args(self, term, growth_group, base_ring=None, EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.TermMonoid.create_key_and_extra_args('O', G) - (('O', Growth Group x^ZZ, None), {}) - sage: atm.TermMonoid.create_key_and_extra_args('exact', G, ZZ) - (('exact', Growth Group x^ZZ, Integer Ring), {}) - sage: atm.TermMonoid.create_key_and_extra_args('exact', G) + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: TermMonoid.create_key_and_extra_args('O', G, QQ) + ((, + Growth Group x^ZZ, Rational Field), {}) + sage: TermMonoid.create_key_and_extra_args('exact', G, ZZ) + ((, + Growth Group x^ZZ, Integer Ring), {}) + sage: TermMonoid.create_key_and_extra_args('exact', G) Traceback (most recent call last): ... - ValueError: A base ring has to be specified + ValueError: A coefficient ring has to be specified + to create a term monoid of type 'exact' TESTS:: - sage: atm.TermMonoid.create_key_and_extra_args('icecream', G) + sage: TermMonoid.create_key_and_extra_args('icecream', G) Traceback (most recent call last): ... - ValueError: icecream has to be either 'exact' or 'O' - sage: atm.TermMonoid.create_key_and_extra_args('O', ZZ) + ValueError: Term specification 'icecream' has to be either + 'exact' or 'O' or an instance of an existing term. + sage: TermMonoid.create_key_and_extra_args('O', ZZ) Traceback (most recent call last): ... ValueError: Integer Ring has to be an asymptotic growth group """ - if term not in ['O', 'exact']: - raise ValueError("%s has to be either 'exact' or 'O'" % term) + if isinstance(term, GenericTermMonoid): + from misc import underlying_class + term_class = underlying_class(term) + elif term == 'O': + term_class = OTermMonoid + elif term == 'exact': + term_class = ExactTermMonoid + else: + raise ValueError("Term specification '%s' has to be either 'exact' or 'O' " + "or an instance of an existing term." % term) + + if asymptotic_ring is not None and \ + (growth_group is not None or coefficient_ring is not None): + raise ValueError("Input ambiguous: asymptotic ring %s as well as " + "growth group %s or coefficient ring %s are given." % + (asymptotic_ring, growth_group, coefficient_ring)) - from sage.rings.asymptotic.growth_group import GenericGrowthGroup + if asymptotic_ring is not None: + growth_group = asymptotic_ring.growth_group + coefficient_ring = asymptotic_ring.coefficient_ring + + from growth_group import GenericGrowthGroup if not isinstance(growth_group, GenericGrowthGroup): raise ValueError("%s has to be an asymptotic growth group" % growth_group) - if term == 'exact' and base_ring is None: - raise ValueError("A base ring has to be specified") - elif term == 'O': - base_ring = None + if coefficient_ring is None: + raise ValueError("A coefficient ring has to be specified to " + "create a term monoid of type '%s'" % (term,)) - return (term, growth_group, base_ring), kwds + return (term_class, growth_group, coefficient_ring), kwds def create_object(self, version, key, **kwds): @@ -2461,20 +3700,21 @@ def create_object(self, version, key, **kwds): EXAMPLES:: - sage: import sage.rings.asymptotic.growth_group as agg - sage: import sage.rings.asymptotic.term_monoid as atm - sage: G = agg.GrowthGroup('x^ZZ') - sage: atm.TermMonoid('O', G) # indirect doctest - Asymptotic O-Term Monoid x^ZZ - sage: atm.TermMonoid('exact', G, ZZ) # indirect doctest - Exact Term Monoid x^ZZ with coefficients from Integer Ring + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoid + sage: G = GrowthGroup('x^ZZ') + sage: TermMonoid('O', G, QQ) # indirect doctest + O-Term Monoid x^ZZ with implicit coefficients in Rational Field + sage: TermMonoid('exact', G, ZZ) # indirect doctest + Exact Term Monoid x^ZZ with coefficients in Integer Ring """ - - term, growth_group, base_ring = key - if term == 'O': - return OTermMonoid(growth_group, **kwds) - else: - return ExactTermMonoid(growth_group, base_ring, **kwds) + term_class, growth_group, coefficient_ring = key + return term_class(growth_group, coefficient_ring, **kwds) TermMonoid = TermMonoidFactory("TermMonoid") +r""" +A factory for asymptotic term monoids. +This is an instance of :class:`TermMonoidFactory` whose documentation +provides more details. +""" diff --git a/src/sage/rings/big_oh.py b/src/sage/rings/big_oh.py index 95b8f764597..0e98a4a5bec 100644 --- a/src/sage/rings/big_oh.py +++ b/src/sage/rings/big_oh.py @@ -1,5 +1,12 @@ """ Big O for various types (power series, p-adics, etc.) + +.. SEEALSO:: + + - `asymptotic expansions <../../asymptotic_expansions_index.html>`_ + - `p-adic numbers <../../../padics/index.html>`_ + - `power series <../../../power_series/index.html>`_ + - `polynomials <../../../polynomial_rings/index.html>`_ """ import sage.rings.arith as arith @@ -71,6 +78,16 @@ def O(*x, **kwds): sage: K(11^-12, 15) 11^-12 + O(11^15) + We can also work with :doc:`asymptotic expansions + `:: + + sage: A. = AsymptoticRing(growth_group='QQ^n * n^QQ * log(n)^QQ', coefficient_ring=QQ); A + doctest:...: FutureWarning: + This class/method/function is marked as experimental. ... + Asymptotic Ring over Rational Field + sage: O(n) + O(n) + TESTS:: sage: var('x, y') diff --git a/src/sage/rings/complex_ball_acb.pxd b/src/sage/rings/complex_ball_acb.pxd index 094ce48b392..0f9882f36ef 100644 --- a/src/sage/rings/complex_ball_acb.pxd +++ b/src/sage/rings/complex_ball_acb.pxd @@ -1,7 +1,7 @@ from sage.libs.arb.acb cimport acb_t from sage.rings.complex_interval cimport ComplexIntervalFieldElement from sage.rings.real_arb cimport RealBall -from sage.structure.element cimport Element +from sage.structure.element cimport RingElement cdef void ComplexIntervalFieldElement_to_acb( acb_t target, @@ -11,9 +11,9 @@ cdef int acb_to_ComplexIntervalFieldElement( ComplexIntervalFieldElement target, const acb_t source) except -1 -cdef class ComplexBall(Element): +cdef class ComplexBall(RingElement): cdef acb_t value cdef ComplexBall _new(self) - cpdef ComplexIntervalFieldElement _interval(self) + cpdef ComplexIntervalFieldElement _complex_mpfi_(self, parent) cpdef RealBall real(self) cpdef RealBall imag(self) diff --git a/src/sage/rings/complex_ball_acb.pyx b/src/sage/rings/complex_ball_acb.pyx index 0c5b9e6ec29..7540f736738 100644 --- a/src/sage/rings/complex_ball_acb.pyx +++ b/src/sage/rings/complex_ball_acb.pyx @@ -1,15 +1,40 @@ r""" -Arbitrary Precision Complex Intervals using Arb +Arbitrary Precision Complex Balls using Arb AUTHORS: - Clemens Heuberger (2014-10-25): Initial version. -This is a rudimentary binding to the optional `Arb library -`_; it may be useful to refer to its -documentation for more details. +This is a binding to the `Arb library `_; it +may be useful to refer to its documentation for more details. -You may have to run ``sage -i arb`` to use the arb library. +Parts of the documentation for this module are copied or adapted from +Arb's own documentation, licenced under the GNU General Public License +version 2, or later. + +.. SEEALSO:: + + - :mod:`Real intervals using Arb ` + - :mod:`Complex interval field (using MPFI) ` + - :mod:`Complex intervals (using MPFI) ` + +Data Structure +============== + +A :class:`ComplexBall` represents a complex number with error bounds. It wraps +an Arb object of type ``acb_t``, which consists of a pair of real number balls +representing the real and imaginary part with separate error bounds. + +A :class:`ComplexBall` thus represents a rectangle `[m_1-r_1, m_1+r_1] + +[m_2-r_2, m_2+r_2] i` in the complex plane. This is used in Arb instead of a +disk or square representation (consisting of a complex floating-point midpoint +with a single radius), since it allows implementing many operations more +conveniently by splitting into ball operations on the real and imaginary parts. +It also allows tracking when complex numbers have an exact (for example exactly +zero) real part and an inexact imaginary part, or vice versa. + +Comparison +========== .. WARNING:: @@ -19,47 +44,55 @@ You may have to run ``sage -i arb`` to use the arb library. to a ball enclosing the set `\{t^2 : t \in x\}` and not the (generally larger) set `\{tu : t \in x, u \in x\}`. -Comparison -========== - Two elements are equal if and only if they are the same object or if both are exact and equal:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb + sage: from sage.rings.complex_ball_acb import CBF doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. See http://trac.sagemath.org/17218 for details. - sage: a = CBF(1, 2) # optional - arb - sage: b = CBF(1, 2) # optional - arb - sage: a is b # optional - arb + sage: a = CBF(1, 2) + sage: b = CBF(1, 2) + sage: a is b False - sage: a == b # optional - arb + sage: a == b True - sage: a = CBF(1/3, 1/5) # optional - arb - sage: b = CBF(1/3, 1/5) # optional - arb - sage: a.is_exact() # optional - arb + sage: a = CBF(1/3, 1/5) + sage: b = CBF(1/3, 1/5) + sage: a.is_exact() False - sage: b.is_exact() # optional - arb + sage: b.is_exact() False - sage: a is b # optional - arb + sage: a is b False - sage: a == b # optional - arb + sage: a == b False A ball is non-zero if and only if it does not contain zero. :: - sage: a = CBF(RIF(-0.5, 0.5)) # optional - arb - sage: bool(a) # optional - arb + sage: a = CBF(RIF(-0.5, 0.5)) + sage: bool(a) False - sage: a != 0 # optional - arb + sage: a != 0 False - sage: b = CBF(1/3, 1/5) # optional - arb - sage: bool(b) # optional - arb + sage: b = CBF(1/3, 1/5) + sage: bool(b) True - sage: b != 0 # optional - arb + sage: b != 0 True +Coercion +======== + +Automatic coercions work as expected:: + + sage: from sage.rings.real_arb import RealBallField + sage: bpol = 1/3*CBF(i) + AA(sqrt(2)) + (polygen(RealBallField(20), 'x') + QQbar(i)) + sage: bpol + x + [1.41421 +/- 5.09e-6] + [1.33333 +/- 3.97e-6]*I + sage: bpol.parent() + Univariate Polynomial Ring in x over Complex ball field with 20 bits precision + Classes and Methods =================== """ @@ -76,12 +109,23 @@ include "sage/ext/python.pxi" include "sage/ext/stdsage.pxi" import sage.categories.fields + +cimport sage.rings.integer +cimport sage.rings.rational + from sage.libs.arb.arb cimport * from sage.libs.arb.acb cimport * +from sage.libs.arb.acb_hypgeom cimport * +from sage.libs.arb.arf cimport arf_t, arf_init, arf_get_mpfr, arf_set_mpfr, arf_clear, arf_set_mag, arf_set +from sage.libs.arb.mag cimport mag_t, mag_init, mag_clear, mag_add, mag_set_d, MAG_BITS, mag_is_inf, mag_is_finite, mag_zero +from sage.libs.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.misc.superseded import experimental +from sage.rings.complex_field import ComplexField from sage.rings.complex_interval_field import ComplexIntervalField from sage.rings.real_arb cimport mpfi_to_arb, arb_to_mpfi from sage.rings.real_arb import RealBallField +from sage.structure.element cimport Element, ModuleElement from sage.structure.parent cimport Parent from sage.structure.unique_representation import UniqueRepresentation @@ -133,8 +177,8 @@ cdef int acb_to_ComplexIntervalFieldElement( class ComplexBallField(UniqueRepresentation, Parent): r""" - An approximation of the field of complex numbers using mid-rad - intervals, also known as balls. + An approximation of the field of complex numbers using pairs of mid-rad + intervals. INPUT: @@ -142,18 +186,18 @@ class ComplexBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb; indirect doctest - sage: CBF(1) # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: CBF = ComplexBallField() # indirect doctest + sage: CBF(1) 1.000000000000000 TESTS:: - sage: ComplexBallField(0) # optional - arb + sage: ComplexBallField(0) Traceback (most recent call last): ... ValueError: Precision must be at least 2. - sage: ComplexBallField(1) # optional - arb + sage: ComplexBallField(1) Traceback (most recent call last): ... ValueError: Precision must be at least 2. @@ -161,20 +205,20 @@ class ComplexBallField(UniqueRepresentation, Parent): Element = ComplexBall @staticmethod - def __classcall__(cls, long precision=53): + def __classcall__(cls, long precision=53, category=None): r""" Normalize the arguments for caching. TESTS:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField(53) is ComplexBallField() # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: ComplexBallField(53) is ComplexBallField() True """ - return super(ComplexBallField, cls).__classcall__(cls, precision) + return super(ComplexBallField, cls).__classcall__(cls, precision, category) @experimental(17218) - def __init__(self, precision): + def __init__(self, precision, category): r""" Initialize the complex ball field. @@ -184,16 +228,74 @@ class ComplexBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: CBF(1) # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: CBF = ComplexBallField() + sage: CBF(1) 1.000000000000000 + + TESTS:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF.base() + Real ball field with 53 bits precision + sage: CBF.base_ring() + Real ball field with 53 bits precision + + There are direct coercions from ZZ and QQ (for which arb provides + construction functions):: + + sage: CBF.coerce_map_from(ZZ) + Conversion map: + From: Integer Ring + To: Complex ball field with 53 bits precision + sage: CBF.coerce_map_from(QQ) + Conversion map: + From: Rational Field + To: Complex ball field with 53 bits precision + + Various other coercions are available through real ball fields or CLF:: + + sage: CBF.coerce_map_from(RLF) + Composite map: + From: Real Lazy Field + To: Complex ball field with 53 bits precision + Defn: Conversion map: + From: Real Lazy Field + To: Real ball field with 53 bits precision + then + Conversion map: + From: Real ball field with 53 bits precision + To: Complex ball field with 53 bits precision + sage: CBF.has_coerce_map_from(AA) + True + sage: CBF.has_coerce_map_from(QuadraticField(-1)) + True + sage: CBF.has_coerce_map_from(QQbar) + True + sage: CBF.has_coerce_map_from(CLF) + True """ if precision < 2: raise ValueError("Precision must be at least 2.") - super(ComplexBallField, self).__init__(category=[sage.categories.fields.Fields()]) + real_field = RealBallField(precision) + super(ComplexBallField, self).__init__( + base=real_field, + category=category or [sage.categories.fields.Fields()]) self._prec = precision - self.RealBallField = RealBallField(precision) + from sage.rings.integer_ring import ZZ + from sage.rings.rational_field import QQ + from sage.rings.real_lazy import CLF + self._populate_coercion_lists_([ZZ, QQ, real_field, CLF]) + + def _real_field(self): + """ + TESTS:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF._real_field() + Real ball field with 53 bits precision + """ + return self._base def _repr_(self): r""" @@ -201,40 +303,206 @@ class ComplexBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField() # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: ComplexBallField() Complex ball field with 53 bits precision - sage: ComplexBallField(106) # optional - arb + sage: ComplexBallField(106) Complex ball field with 106 bits precision """ return "Complex ball field with {} bits precision".format(self._prec) - def _coerce_map_from_(self, S): - r""" - Currently, there is no coercion. + def construction(self): + """ + Return the construction of a complex ball field as the algebraic + closure of the real ball field with the same precision. EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField()._coerce_map_from_(CIF) # optional - arb + sage: from sage.rings.complex_ball_acb import CBF + sage: functor, base = CBF.construction() + sage: functor, base + (AlgebraicClosureFunctor, Real ball field with 53 bits precision) + sage: functor(base) is CBF + True + """ + from sage.categories.pushout import AlgebraicClosureFunctor + return (AlgebraicClosureFunctor(), self._base) + + def ngens(self): + r""" + Return 1 as the only generator is the imaginary unit. + + EXAMPLE:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF.ngens() + 1 + """ + return 1 + + def gen(self, i): + r""" + For i = 0, return the imaginary unit in this complex ball field. + + EXAMPLE:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF.0 + 1.000000000000000*I + sage: CBF.gen(1) + Traceback (most recent call last): + ... + ValueError: only one generator + """ + if i == 0: + return self(0, 1) + else: + raise ValueError("only one generator") + + def gens(self): + r""" + Return the tuple of generators of this complex ball field, i.e. + ``(i,)``. + + EXAMPLE:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF.gens() + (1.000000000000000*I,) + sage: CBF.gens_dict() + {'1.000000000000000*I': 1.000000000000000*I} + """ + return (self(0, 1),) + + def _coerce_map_from_(self, other): + r""" + Parents that canonically coerce into complex ball fields include: + + - anything that coerces into the corresponding real ball field; + + - real and complex ball fields with a larger precision; + + - various exact or lazy parents representing subsets of the complex + numbers, such as ``QQbar``, ``CLF``, and number fields equipped + with complex embeddings. + + TESTS:: + + sage: from sage.rings.complex_ball_acb import CBF, ComplexBallField + sage: from sage.rings.real_arb import RBF, RealBallField + sage: CBF.coerce_map_from(CBF) + Identity endomorphism of Complex ball field with 53 bits precision + sage: CBF.coerce_map_from(ComplexBallField(100)) + Conversion map: + From: Complex ball field with 100 bits precision + To: Complex ball field with 53 bits precision + sage: CBF.has_coerce_map_from(ComplexBallField(42)) False - sage: ComplexBallField()._coerce_map_from_(SR) # optional - arb + sage: CBF.has_coerce_map_from(RealBallField(54)) + True + sage: CBF.has_coerce_map_from(RealBallField(52)) + False + + Check that there are no coercions from interval or floating-point parents:: + + sage: CBF.has_coerce_map_from(RIF) + False + sage: CBF.has_coerce_map_from(CIF) + False + sage: CBF.has_coerce_map_from(RR) + False + sage: CBF.has_coerce_map_from(CC) False """ - return False + if isinstance(other, (RealBallField, ComplexBallField)): + return (other._prec >= self._prec) - def _element_constructor_(self, *args, **kwds): + def _element_constructor_(self, x=None, y=None): r""" - Construct a :class:`ComplexBall`. + Convert (x, y) to an element of this complex ball field, perhaps + non-canonically. + + INPUT: + + - ``x``, ``y`` (optional) -- either a complex number, interval or ball, + or two real ones (see examples below for more information on accepted + number types). + + .. SEEALSO:: :meth:`sage.rings.real_arb.RealBallField._element_constructor_` EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional: arb - sage: CBF = ComplexBallField() # optional: arb - sage: CBF(1) # optional: arb; indirect doctest + sage: from sage.rings.real_arb import RBF + sage: from sage.rings.complex_ball_acb import CBF, ComplexBallField + sage: CBF() + 0 + sage: CBF(1) # indirect doctest 1.000000000000000 + sage: CBF(1, 1) + 1.000000000000000 + 1.000000000000000*I + sage: CBF(pi, sqrt(2)) + [3.141592653589793 +/- 5.61e-16] + [1.414213562373095 +/- 4.10e-16]*I + sage: CBF(I) + 1.000000000000000*I + sage: CBF(pi+I/3) + [3.141592653589793 +/- 5.61e-16] + [0.3333333333333333 +/- 7.04e-17]*I + sage: CBF(QQbar(i/7)) + [0.1428571428571428 +/- 9.09e-17]*I + sage: CBF(AA(sqrt(2))) + [1.414213562373095 +/- 4.10e-16] + sage: CBF(CIF(0, 1)) + 1.000000000000000*I + sage: CBF(RBF(1/3)) + [0.3333333333333333 +/- 7.04e-17] + sage: CBF(RBF(1/3), RBF(1/6)) + [0.3333333333333333 +/- 7.04e-17] + [0.1666666666666667 +/- 7.04e-17]*I + sage: CBF(1/3) + [0.3333333333333333 +/- 7.04e-17] + sage: CBF(1/3, 1/6) + [0.3333333333333333 +/- 7.04e-17] + [0.1666666666666667 +/- 7.04e-17]*I + sage: ComplexBallField(106)(1/3, 1/6) + [0.33333333333333333333333333333333 +/- 6.94e-33] + [0.16666666666666666666666666666666 +/- 7.70e-33]*I + sage: CBF(infinity, NaN) + [+/- inf] + nan*I + sage: CBF(x) + Traceback (most recent call last): + ... + TypeError: unable to convert x to a ComplexBall + + TESTS:: + + sage: CBF(1+I, 2) + Traceback (most recent call last): + ... + TypeError: unable to convert I + 1 to a RealBall """ - return self.element_class(self, *args, **kwds) + try: + return self.element_class(self, x, y) + except TypeError: + pass + + if y is None: + try: + x = self._base(x) + return self.element_class(self, x) + except (TypeError, ValueError): + pass + try: + y = self._base(x.imag()) + x = self._base(x.real()) + return self.element_class(self, x, y) + except (AttributeError, TypeError): + pass + try: + x = ComplexIntervalField(self._prec)(x) + return self.element_class(self, x) + except TypeError: + pass + raise TypeError("unable to convert {} to a ComplexBall".format(x)) + else: + x = self._base(x) + y = self._base(y) + return self.element_class(self, x, y) def _an_element_(self): r""" @@ -242,12 +510,11 @@ class ComplexBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional: arb - sage: CBF = ComplexBallField() # optional: arb - sage: CBF._an_element_() # optional: arb; indirect doctest - [0.3333333333333333 +/- 1.49e-17] + [0.1666666666666667 +/- 4.26e-17]*I + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF.an_element() # indirect doctest + [0.3333333333333333 +/- 1.49e-17] - [0.1666666666666667 +/- 4.26e-17]*I """ - return self(1.0/3, 1.0/6) + return self(1.0/3, -1.0/6) def precision(self): """ @@ -255,8 +522,8 @@ class ComplexBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField().precision() # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: ComplexBallField().precision() 53 """ return self._prec @@ -267,8 +534,8 @@ class ComplexBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField().is_exact() # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: ComplexBallField().is_exact() False """ return False @@ -279,8 +546,8 @@ class ComplexBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField().is_finite() # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: ComplexBallField().is_finite() False """ return False @@ -291,12 +558,37 @@ class ComplexBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField().characteristic() # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: ComplexBallField().characteristic() 0 """ return 0 + def some_elements(self): + """ + Complex ball fields contain elements with exact, inexact, infinite, or + undefined real and imaginary parts. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF.some_elements() + [1.000000000000000, + -0.5000000000000000*I, + 1.000000000000000 + [0.3333333333333333 +/- 1.49e-17]*I, + [-0.3333333333333333 +/- 1.49e-17] + 0.2500000000000000*I, + [-2.175556475109056e+181961467118333366510562 +/- 1.29e+181961467118333366510545], + [+/- inf], + [0.3333333333333333 +/- 1.49e-17] + [+/- inf]*I, + [+/- inf] + [+/- inf]*I, + nan, + nan + nan*I, + [+/- inf] + nan*I] + """ + return [self(1), self(0, -1./2), self(1, 1./3), self(-1./3, 1./4), + -self(1, 1)**(sage.rings.integer.Integer(2)**80), + self('inf'), self(1./3, 'inf'), self('inf', 'inf'), + self('nan'), self('nan', 'nan'), self('inf', 'nan')] cdef inline bint _do_sig(long prec): """ @@ -304,7 +596,7 @@ cdef inline bint _do_sig(long prec): TESTS:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField """ return (prec > 1000) @@ -312,21 +604,19 @@ cdef inline long prec(ComplexBall ball): return ball._parent._prec cdef inline Parent real_ball_field(ComplexBall ball): - return ball._parent.RealBallField + return ball._parent._base -cdef class ComplexBall(Element): +cdef class ComplexBall(RingElement): """ Hold one ``acb_t`` of the `Arb library `_ EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: a = ComplexBallField()(1, 1) # optional - arb - sage: a # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: a = ComplexBallField()(1, 1) + sage: a 1.000000000000000 + 1.000000000000000*I - sage: a._interval() # optional - arb - 1 + 1*I """ def __cinit__(self): """ @@ -334,8 +624,8 @@ cdef class ComplexBall(Element): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: ComplexBallField(2)(0) # optional - arb; indirect doctest + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: ComplexBallField(2)(0) # indirect doctest 0 """ acb_init(self.value) @@ -346,92 +636,87 @@ cdef class ComplexBall(Element): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: a = ComplexBallField(2)(0) # optional - arb; indirect doctest - sage: del a # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: a = ComplexBallField(2)(0) # indirect doctest + sage: del a """ acb_clear(self.value) def __init__(self, parent, x=None, y=None): """ - Initialize the :class:`ComplexBall` using `x` and `y`. + Initialize the :class:`ComplexBall`. INPUT: - ``parent`` -- a :class:`ComplexBallField`. - - ``x`` -- (default: ``None``) ``None`` or a - :class:`~sage.rings.complex_interval.ComplexIntervalFieldElement` or - a :class:`sage.rings.real_arb.RealBall`. - - - ``y`` -- (default: ``None``) ``None`` or a - :class:`sage.rings.real_arb.RealBall`. - - OUTPUT: + - ``x``, ``y`` (optional) -- either a complex number, interval or ball, + or two real ones. - None. + .. SEEALSO:: :meth:`ComplexBallField._element_constructor_` - EXAMPLES:: + TESTS:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: CBF(CIF(0, 1)) # optional - arb - 1.000000000000000*I - sage: CBF(1) # optional - arb - 1.000000000000000 - sage: CBF(1, 1) # optional - arb - 1.000000000000000 + 1.000000000000000*I - sage: CBF(x) # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField, ComplexBall + sage: from sage.rings.real_arb import RBF + sage: CBF53, CBF100 = ComplexBallField(53), ComplexBallField(100) + sage: ComplexBall(CBF100) + 0 + sage: ComplexBall(CBF100, ComplexBall(CBF53, ComplexBall(CBF100, 1/3))) + [0.333333333333333333333333333333 +/- 4.65e-31] + sage: ComplexBall(CBF100, RBF(pi)) + [3.141592653589793 +/- 5.61e-16] + sage: ComplexBall(CBF100, -3r) + -3.000000000000000000000000000000 + sage: ComplexBall(CBF100, 10^100) + 1.000000000000000000000000000000e+100 + sage: ComplexBall(CBF100, CIF(1, 2)) + 1.000000000000000000000000000000 + 2.000000000000000000000000000000*I + sage: ComplexBall(CBF100, RBF(1/3), RBF(1)) + [0.3333333333333333 +/- 7.04e-17] + 1.000000000000000000000000000000*I + sage: ComplexBall(CBF100, 1, 2) Traceback (most recent call last): ... - TypeError: unable to convert to a ComplexIntervalFieldElement - sage: RBF = RealBallField() # optional - arb - sage: CBF(RBF(1/3)) # optional - arb - [0.3333333333333333 +/- 7.04e-17] - sage: CBF(RBF(1/3), RBF(1/6)) # optional - arb - [0.3333333333333333 +/- 7.04e-17] + [0.1666666666666667 +/- 7.04e-17]*I - sage: CBF(1/3) # optional - arb - [0.333333333333333 +/- 3.99e-16] - sage: CBF(1/3, 1/6) # optional - arb - [0.3333333333333333 +/- 7.04e-17] + [0.1666666666666667 +/- 7.04e-17]*I - sage: ComplexBallField(106)(1/3, 1/6) # optional - arb - [0.33333333333333333333333333333333 +/- 6.94e-33] + [0.16666666666666666666666666666666 +/- 7.70e-33]*I + TypeError: unsupported initializer """ - Element.__init__(self, parent) + cdef fmpz_t tmpz + cdef fmpq_t tmpq + + RingElement.__init__(self, parent) if x is None: return - - if y is None: - # we assume x to be a complex number - if isinstance(x, RealBall): - arb_set(&self.value.real, ( x).value) - arb_set_ui(&self.value.imag, 0) - else: - if not isinstance(x, ComplexIntervalFieldElement): - try: - x = ComplexIntervalField(prec(self))(x) - except TypeError: - raise TypeError("unable to convert to a " - "ComplexIntervalFieldElement") + elif y is None: + if isinstance(x, ComplexBall): + acb_set(self.value, ( x).value) + elif isinstance(x, RealBall): + acb_set_arb(self.value, ( x).value) + elif isinstance(x, int): + acb_set_si(self.value, PyInt_AS_LONG(x)) + elif isinstance(x, sage.rings.integer.Integer): + if _do_sig(prec(self)): sig_on() + fmpz_init(tmpz) + fmpz_set_mpz(tmpz, ( x).value) + acb_set_fmpz(self.value, tmpz) + fmpz_clear(tmpz) + if _do_sig(prec(self)): sig_off() + elif isinstance(x, sage.rings.rational.Rational): + if _do_sig(prec(self)): sig_on() + fmpq_init(tmpq) + fmpq_set_mpq(tmpq, ( x).value) + acb_set_fmpq(self.value, tmpq, prec(self)) + fmpq_clear(tmpq) + if _do_sig(prec(self)): sig_off() + elif isinstance(x, ComplexIntervalFieldElement): ComplexIntervalFieldElement_to_acb(self.value, x) + else: + raise TypeError("unsupported initializer") + elif isinstance(x, RealBall) and isinstance(y, RealBall): + arb_set(acb_realref(self.value), ( x).value) + arb_set(acb_imagref(self.value), ( y).value) else: - if not isinstance(x, RealBall): - try: - x = real_ball_field(self)(x) - except TypeError: - raise TypeError("unable to convert to a " - "RealBall") - if not isinstance(y, RealBall): - try: - y = real_ball_field(self)(y) - except TypeError: - raise TypeError("unable to convert to a " - "RealBall") - arb_set(&self.value.real, ( x).value) - arb_set(&self.value.imag, ( y).value) + raise TypeError("unsupported initializer") cdef ComplexBall _new(self): """ @@ -442,6 +727,182 @@ cdef class ComplexBall(Element): x._parent = self._parent return x + def _repr_(self): + """ + Return a string representation of ``self``. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF(1/3) + [0.3333333333333333 +/- 7.04e-17] + sage: CBF(0, 1/3) + [0.3333333333333333 +/- 7.04e-17]*I + sage: CBF(1/3, 1/6) + [0.3333333333333333 +/- 7.04e-17] + [0.1666666666666667 +/- 7.04e-17]*I + + TESTS:: + + sage: CBF(1-I/2) + 1.000000000000000 - 0.5000000000000000*I + """ + cdef arb_t real = acb_realref(self.value) + cdef arb_t imag = acb_imagref(self.value) + if arb_is_zero(imag): + return self.real()._repr_() + elif arb_is_zero(real): + return "{}*I".format(self.imag()._repr_()) + elif arb_is_exact(imag) and arb_is_negative(imag): + return "{} - {}*I".format(self.real()._repr_(), + (-self.imag())._repr_()) + else: + return "{} + {}*I".format(self.real()._repr_(), + self.imag()._repr_()) + + # Conversions + + cpdef ComplexIntervalFieldElement _complex_mpfi_(self, parent): + """ + Return :class:`ComplexIntervalFieldElement` of the same value. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CIF(CBF(1/3, 1/3)) # indirect doctest + 0.3333333333333333? + 0.3333333333333333?*I + """ + cdef ComplexIntervalFieldElement res = parent.zero() + res = res._new() # FIXME after modernizing CIF + acb_to_ComplexIntervalFieldElement(res, self.value) + return res + + def _integer_(self, _): + """ + Check that this ball contains a single integer and return that integer. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: from sage.rings.real_arb import RBF + sage: ZZ(CBF(-42, RBF(.1, rad=.2))) # indirect doctest + -42 + sage: ZZ(CBF(i)) + Traceback (most recent call last): + ... + ValueError: 1.000000000000000*I does not contain a unique integer + """ + cdef sage.rings.integer.Integer res + cdef fmpz_t tmp + fmpz_init(tmp) + try: + if acb_get_unique_fmpz(tmp, self.value): + res = sage.rings.integer.Integer.__new__(sage.rings.integer.Integer) + fmpz_get_mpz(res.value, tmp) + else: + raise ValueError("{} does not contain a unique integer".format(self)) + finally: + fmpz_clear(tmp) + return res + + def _rational_(self): + """ + Check that this ball contains a single rational number and return that + number. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: QQ(CBF(12345/2^5)) + 12345/32 + sage: QQ(CBF(i)) + Traceback (most recent call last): + ... + ValueError: 1.000000000000000*I does not contain a unique rational number + """ + if acb_is_real(self.value) and acb_is_exact(self.value): + return self.real().mid().exact_rational() + else: + raise ValueError("{} does not contain a unique rational number".format(self)) + + def _complex_mpfr_field_(self, parent): + r""" + Convert this complex ball to a complex number. + + INPUT: + + - ``parent`` - :class:`~sage.rings.complex_field.ComplexField_class`, + target parent. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CC(CBF(1/3, 1/3)) + 0.333333333333333 + 0.333333333333333*I + sage: ComplexField(100)(CBF(1/3, 1/3)) + 0.33333333333333331482961625625 + 0.33333333333333331482961625625*I + """ + real_field = parent._base + return parent(real_field(self.real()), real_field(self.imag())) + + def _real_mpfi_(self, parent): + r""" + Try to convert this complex ball to a real interval. + + Fail if the imaginary part is not exactly zero. + + INPUT: + + - ``parent`` - :class:`~sage.rings.real_mpfi.RealIntervalField_class`, + target parent. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: from sage.rings.real_arb import RBF + sage: RIF(CBF(RBF(1/3, rad=1e-5))) + 0.3334? + sage: RIF(CBF(RBF(1/3, rad=1e-5), 1e-10)) + Traceback (most recent call last): + ... + ValueError: nonzero imaginary part + """ + if acb_is_real(self.value): + return parent(self.real()) + else: + raise ValueError("nonzero imaginary part") + + def _mpfr_(self, parent): + r""" + Try to convert this complex ball to a real number. + + Fail if the imaginary part is not exactly zero. + + INPUT: + + - ``parent`` - :class:`~sage.rings.real_mpfr.RealField_class`, + target parent. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: RR(CBF(1/3)) + 0.333333333333333 + sage: RR(CBF(1, 1/3) - CBF(0, 1/3)) + Traceback (most recent call last): + ... + ValueError: nonzero imaginary part + """ + if acb_is_real(self.value): + return parent(self.real()) + else: + raise ValueError("nonzero imaginary part") + + # Real and imaginary part, midpoint + cpdef RealBall real(self): """ Return the real part of this ball. @@ -452,15 +913,14 @@ cdef class ComplexBall(Element): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: a = CBF(1/3, 1/5) # optional - arb - sage: a.real() # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: CBF = ComplexBallField() + sage: a = CBF(1/3, 1/5) + sage: a.real() [0.3333333333333333 +/- 7.04e-17] """ - cdef RealBall r - r = real_ball_field(self)(0) - arb_set(r.value, &self.value.real) + cdef RealBall r = RealBall(real_ball_field(self)) + arb_set(r.value, acb_realref(self.value)) return r cpdef RealBall imag(self): @@ -473,63 +933,243 @@ cdef class ComplexBall(Element): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: a = CBF(1/3, 1/5) # optional - arb - sage: a.imag() # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: CBF = ComplexBallField() + sage: a = CBF(1/3, 1/5) + sage: a.imag() [0.2000000000000000 +/- 4.45e-17] """ - cdef RealBall r - r = real_ball_field(self)(0) - arb_set(r.value, &self.value.imag) + cdef RealBall r = RealBall(real_ball_field(self)) + arb_set(r.value, acb_imagref(self.value)) return r - def _repr_(self): + def abs(self): """ - Return a string representation of ``self``. + Return the absolute value of this complex ball. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF(1 + i).abs() + [1.414213562373095 +/- 2.99e-16] + sage: CBF(1 + i).abs().parent() + Real ball field with 53 bits precision + """ + cdef RealBall r = RealBall(real_ball_field(self)) + acb_abs(r.value, self.value, prec(self)) + return r + + def below_abs(self, test_zero=False): + """ + Return a lower bound for the absolute value of this complex ball. + + INPUT: + + - ``test_zero`` (boolean, default ``False``) -- if ``True``, + make sure that the returned lower bound is positive, raising + an error if the ball contains zero. + + .. SEEALSO:: :meth:`abs`, :meth:`above_abs` + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import ComplexBallField, CBF + sage: b = ComplexBallField(8)(1+i).below_abs() + sage: b + [1.4 +/- 0.0141] + sage: b.is_exact() + True + sage: QQ(b)*128 + 181 + sage: (CBF(1/3) - 1/3).below_abs() + 0 + sage: (CBF(1/3) - 1/3).below_abs(test_zero=True) + Traceback (most recent call last): + ... + ValueError: ball contains zero + """ + cdef RealBall res = RealBall(real_ball_field(self)) + acb_get_abs_lbound_arf(arb_midref(res.value), self.value, prec(self)) + if test_zero and arb_contains_zero(res.value): + assert acb_contains_zero(self.value) + raise ValueError("ball contains zero") + return res + + def above_abs(self): + """ + Return an upper bound for the absolute value of this complex ball. + + .. SEEALSO:: :meth:`abs`, :meth:`below_abs` + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: b = ComplexBallField(8)(1+i).above_abs() + sage: b + [1.4 +/- 0.0219] + sage: b.is_exact() + True + sage: QQ(b)*128 + 182 + """ + cdef RealBall res = RealBall(real_ball_field(self)) + acb_get_abs_ubound_arf(arb_midref(res.value), self.value, prec(self)) + return res + + def arg(self): + """ + Return the argument of this complex ball. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF(1 + i).arg() + [0.785398163397448 +/- 3.91e-16] + sage: CBF(-1).arg() + [3.141592653589793 +/- 5.61e-16] + sage: CBF(-1).arg().parent() + Real ball field with 53 bits precision + """ + cdef RealBall r = RealBall(real_ball_field(self)) + acb_arg(r.value, self.value, prec(self)) + return r + + def mid(self): + """ + Return the midpoint of this ball. OUTPUT: - A string. + :class:`~sage.rings.complex_number.ComplexNumber`, floating-point + complex number formed by the centers of the real and imaginary parts of + this ball. + + .. SEEALSO:: :meth:`squash` EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: CBF(1/3) # optional - arb - [0.333333333333333 +/- 3.99e-16] - sage: CBF(0, 1/3) # optional - arb - [0.3333333333333333 +/- 7.04e-17]*I - sage: ComplexBallField()(1/3, 1/6) # optional - arb - [0.3333333333333333 +/- 7.04e-17] + [0.1666666666666667 +/- 7.04e-17]*I + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF(1/3, 1).mid() + 0.333333333333333 + 1.00000000000000*I + sage: CBF(1/3, 1).mid().parent() + Complex Field with 53 bits of precision + sage: CBF('inf', 'nan').mid() + +infinity - NaN*I + sage: CBF('nan', 'inf').mid() + NaN + +infinity*I + sage: CBF('nan').mid() + NaN + sage: CBF('inf').mid() + +infinity + sage: CBF(0, 'inf').mid() + +infinity*I """ - if arb_is_zero(&self.value.imag): - return self.real()._repr_() - elif arb_is_zero(&self.value.real): - return "{}*I".format(self.imag()._repr_()) - else: - return "{} + {}*I".format(self.real()._repr_(), - self.imag()._repr_()) + re, im = self.real().mid(), self.imag().mid() + field = ComplexField(max(prec(self), re.prec(), im.prec())) + return field(re, im) - cpdef ComplexIntervalFieldElement _interval(self): + def squash(self): """ - Return :class:`ComplexIntervalFieldElement` of the same value. + Return an exact ball with the same midpoint as this ball. OUTPUT: - A :class:`ComplexIntervalFieldElement`. + A :class:`ComplexBall`. + + .. SEEALSO:: :meth:`mid` + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: mid = CBF(1/3, 1/10).squash() + sage: mid + [0.3333333333333333 +/- 1.49e-17] + [0.09999999999999999 +/- 1.68e-18]*I + sage: mid.parent() + Complex ball field with 53 bits precision + sage: mid.is_exact() + True + """ + cdef ComplexBall res = self._new() + arf_set(arb_midref(acb_realref(res.value)), arb_midref(acb_realref(self.value))) + arf_set(arb_midref(acb_imagref(res.value)), arb_midref(acb_imagref(self.value))) + mag_zero(arb_radref(acb_realref(res.value))) + mag_zero(arb_radref(acb_imagref(res.value))) + return res + + # Precision + + def round(self): + """ + Return a copy of this ball rounded to the precision of the parent. + + .. SEEALSO:: :meth:`trim` + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: b = CBF(exp(I*pi/3).n(100)) + sage: b.mid() + 0.50000000000000000000000000000 + 0.86602540378443864676372317075*I + sage: b.round().mid() + 0.500000000000000 + 0.866025403784439*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_set_round(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def accuracy(self): + """ + Return the effective relative accuracy of this ball measured in bits. + + This is computed as if calling + :meth:`~sage.rings.real_arb.RealBall.accuracy()` + on the real ball whose midpoint is the larger out of the real and + imaginary midpoints of this complex ball, and whose radius is the + larger out of the real and imaginary radii of this complex ball. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF(exp(I*pi/3)).accuracy() + 51 + sage: CBF(I/2).accuracy() == CBF.base().maximal_accuracy() + True + sage: CBF('nan', 'inf').accuracy() == -CBF.base().maximal_accuracy() + True + + .. seealso:: + + :meth:`~sage.rings.real_arb.RealBallField.maximal_accuracy` + """ + return acb_rel_accuracy_bits(self.value) + + def trim(self): + """ + Return a trimmed copy of this ball. + + Return a copy of this ball with both the real and imaginary parts + trimmed (see :meth:`~sage.rings.real_arb.RealBall.trim()`). + + .. SEEALSO:: :meth:`round` EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: a = CBF(CIF(2, 2)) # optional - arb - sage: a._interval() # optional - arb - 2 + 2*I + sage: from sage.rings.complex_ball_acb import CBF + sage: from sage.rings.real_arb import RBF + sage: b = CBF(1/3, RBF(1/3, rad=.01)) + sage: b.mid() + 0.333333333333333 + 0.333333333333333*I + sage: b.trim().mid() + 0.333333333333333 + 0.333333015441895*I + """ - cdef ComplexIntervalFieldElement target = ComplexIntervalField(prec(self))(0) - acb_to_ComplexIntervalFieldElement(target, self.value) - return target + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_trim(res.value, self.value) + if _do_sig(prec(self)): sig_off() + return res # Comparisons and predicates @@ -539,11 +1179,11 @@ cdef class ComplexBall(Element): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: CBF(0).is_zero() # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: CBF = ComplexBallField() + sage: CBF(0).is_zero() True - sage: CBF(RIF(-0.5, 0.5)).is_zero() # optional - arb + sage: CBF(RIF(-0.5, 0.5)).is_zero() False """ return acb_is_zero(self.value) @@ -555,15 +1195,15 @@ cdef class ComplexBall(Element): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: bool(CBF(pi, 1/3)) # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: CBF = ComplexBallField() + sage: bool(CBF(pi, 1/3)) True - sage: bool(CBF(RIF(-0.5, 0.5), 1/3)) # optional - arb + sage: bool(CBF(RIF(-0.5, 0.5), 1/3)) True - sage: bool(CBF(1/3, RIF(-0.5, 0.5))) #optional - arb + sage: bool(CBF(1/3, RIF(-0.5, 0.5))) True - sage: bool(CBF(RIF(-0.5, 0.5), RIF(-0.5, 0.5))) #optional - arb + sage: bool(CBF(RIF(-0.5, 0.5), RIF(-0.5, 0.5))) False """ return acb_is_nonzero(self.value) @@ -574,15 +1214,31 @@ cdef class ComplexBall(Element): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: CBF(1).is_exact() # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: CBF = ComplexBallField() + sage: CBF(1).is_exact() True - sage: CBF(1/3, 1/3).is_exact() # optional - arb + sage: CBF(1/3, 1/3).is_exact() False """ return acb_is_exact(self.value) + def is_real(self): + """ + Return ``True`` iff the imaginary part of this ball is exactly zero. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF(1/3, 0).is_real() + True + sage: (CBF(i/3) - CBF(1, 1/3)).is_real() + False + sage: CBF('inf').is_real() + True + """ + return acb_is_real(self.value) + cpdef _richcmp_(left, Element right, int op): """ Compare ``left`` and ``right``. @@ -591,94 +1247,94 @@ cdef class ComplexBall(Element): EXAMPLES:: - sage: from sage.rings.complex_ball_acb import ComplexBallField # optional - arb - sage: CBF = ComplexBallField() # optional - arb - sage: a = CBF(1) # optional - arb - sage: b = CBF(1) # optional - arb - sage: a is b # optional - arb + sage: from sage.rings.complex_ball_acb import ComplexBallField + sage: CBF = ComplexBallField() + sage: a = CBF(1) + sage: b = CBF(1) + sage: a is b False - sage: a == b # optional - arb + sage: a == b True - sage: a = CBF(1/3) # optional - arb - sage: a.is_exact() # optional - arb + sage: a = CBF(1/3) + sage: a.is_exact() False - sage: b = CBF(1/3) # optional - arb - sage: b.is_exact() # optional - arb + sage: b = CBF(1/3) + sage: b.is_exact() False - sage: a == b # optional - arb + sage: a == b False - sage: a = CBF(1, 2) # optional - arb - sage: b = CBF(1, 2) # optional - arb - sage: a is b # optional - arb + sage: a = CBF(1, 2) + sage: b = CBF(1, 2) + sage: a is b False - sage: a == b # optional - arb + sage: a == b True TESTS: Balls whose intersection consists of one point:: - sage: a = CBF(RIF(1, 2), RIF(1, 2)) # optional - arb - sage: b = CBF(RIF(2, 4), RIF(2, 4)) # optional - arb - sage: a < b # optional - arb + sage: a = CBF(RIF(1, 2), RIF(1, 2)) + sage: b = CBF(RIF(2, 4), RIF(2, 4)) + sage: a < b Traceback (most recent call last): ... TypeError: No order is defined for ComplexBalls. - sage: a > b # optional - arb + sage: a > b Traceback (most recent call last): ... TypeError: No order is defined for ComplexBalls. - sage: a <= b # optional - arb + sage: a <= b Traceback (most recent call last): ... TypeError: No order is defined for ComplexBalls. - sage: a >= b # optional - arb + sage: a >= b Traceback (most recent call last): ... TypeError: No order is defined for ComplexBalls. - sage: a == b # optional - arb + sage: a == b False - sage: a != b # optional - arb + sage: a != b False Balls with non-trivial intersection:: - sage: a = CBF(RIF(1, 4), RIF(1, 4)) # optional - arb - sage: a = CBF(RIF(2, 5), RIF(2, 5)) # optional - arb - sage: a == b # optional - arb + sage: a = CBF(RIF(1, 4), RIF(1, 4)) + sage: a = CBF(RIF(2, 5), RIF(2, 5)) + sage: a == b False - sage: a != b # optional - arb + sage: a != b False One ball contained in another:: - sage: a = CBF(RIF(1, 4), RIF(1, 4)) # optional - arb - sage: b = CBF(RIF(2, 3), RIF(2, 3)) # optional - arb - sage: a == b # optional - arb + sage: a = CBF(RIF(1, 4), RIF(1, 4)) + sage: b = CBF(RIF(2, 3), RIF(2, 3)) + sage: a == b False - sage: a != b # optional - arb + sage: a != b False Disjoint balls:: - sage: a = CBF(1/3, 1/3) # optional - arb - sage: b = CBF(1/5, 1/5) # optional - arb - sage: a == b # optional - arb + sage: a = CBF(1/3, 1/3) + sage: b = CBF(1/5, 1/5) + sage: a == b False - sage: a != b # optional - arb + sage: a != b True Exact elements:: - sage: a = CBF(2, 2) # optional - arb - sage: b = CBF(2, 2) # optional - arb - sage: a.is_exact() # optional - arb + sage: a = CBF(2, 2) + sage: b = CBF(2, 2) + sage: a.is_exact() True - sage: b.is_exact() # optional - arb + sage: b.is_exact() True - sage: a == b # optional - arb + sage: a == b True - sage: a != b # optional - arb + sage: a != b False """ cdef ComplexBall lt, rt @@ -697,3 +1353,271 @@ cdef class ComplexBall(Element): elif op == Py_GT or op == Py_GE or op == Py_LT or op == Py_LE: raise TypeError("No order is defined for ComplexBalls.") + + def identical(self, ComplexBall other): + """ + Return whether ``self`` and ``other`` represent the same ball. + + INPUT: + + - ``other`` -- a :class:`ComplexBall`. + + OUTPUT: + + Return True iff ``self`` and ``other`` are equal as sets, i.e. if their + real and imaginary parts each have the same midpoint and radius. + + Note that this is not the same thing as testing whether both ``self`` + and ``other`` certainly represent the complex real number, unless + either ``self`` or ``other`` is exact (and neither contains NaN). To + test whether both operands might represent the same mathematical + quantity, use :meth:`overlaps` or ``in``, depending on the + circumstance. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF(1, 1/3).identical(1 + CBF(0, 1)/3) + True + sage: CBF(1, 1).identical(1 + CBF(0, 1/3)*3) + False + """ + return acb_equal(self.value, other.value) + + def overlaps(self, ComplexBall other): + """ + Return True iff ``self`` and ``other`` have some point in common. + + INPUT: + + - ``other`` -- a :class:`ComplexBall`. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF(1, 1).overlaps(1 + CBF(0, 1/3)*3) + True + sage: CBF(1, 1).overlaps(CBF(1, 'nan')) + True + sage: CBF(1, 1).overlaps(CBF(0, 'nan')) + False + """ + return acb_overlaps(self.value, other.value) + + def contains_exact(self, other): + """ + Return ``True`` *iff* ``other`` is contained in ``self``. + + INPUT: + + - ``other`` -- :class:`ComplexBall`, + :class:`~sage.rings.integer.Integer`, + or :class:`~sage.rings.rational.Rational` + + .. SEEALSO:: + + Use ``other in self`` for a test that works for a wider range of + inputs but may return false negatives. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: from sage.rings.real_arb import RealBallField + sage: CBF(RealBallField(100)(1/3), 0).contains_exact(1/3) + True + sage: CBF(1).contains_exact(1) + True + sage: CBF(1).contains_exact(CBF(1)) + True + """ + cdef fmpz_t tmpz + cdef fmpq_t tmpq + if _do_sig(prec(self)): sig_on() + try: + if isinstance(other, ComplexBall): + res = acb_contains(self.value, ( other).value) + elif isinstance(other, sage.rings.integer.Integer): + fmpz_init(tmpz) + fmpz_set_mpz(tmpz, ( other).value) + res = acb_contains_fmpz(self.value, tmpz) + fmpz_clear(tmpz) + elif isinstance(other, sage.rings.rational.Rational): + fmpq_init(tmpq) + fmpq_set_mpq(tmpq, ( other).value) + res = acb_contains_fmpq(self.value, tmpq) + fmpq_clear(tmpq) + else: + raise TypeError + finally: + if _do_sig(prec(self)): sig_off() + return res + + def __contains__(self, other): + """ + Return True if ``other`` can be verified to be contained in ``self``. + + Depending on the type of ``other``, the test may use interval + arithmetic with a precision determined by the parent of ``self`` and + may return false negatives. + + .. SEEALSO:: :meth:`contains_exact` + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: 1/3*i in CBF(0, 1/3) + True + + A false negative:: + + sage: from sage.rings.real_arb import RealBallField + sage: RLF(1/3) in CBF(RealBallField(100)(1/3), 0) + False + """ + if not isinstance(other, ( + ComplexBall, + sage.rings.integer.Integer, + sage.rings.rational.Rational)): + other = self._parent(other) + return self.contains_exact(other) + + # Arithmetic + + def __neg__(self): + """ + Return the opposite of this ball. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: -CBF(1/3 + I) + [-0.3333333333333333 +/- 7.04e-17] - 1.000000000000000*I + """ + cdef ComplexBall res = self._new() + acb_neg(res.value, self.value) + return res + + def conjugate(self): + """ + Return the complex conjugate of this ball. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF(-2 + I/3).conjugate() + -2.000000000000000 + [-0.3333333333333333 +/- 7.04e-17]*I + """ + cdef ComplexBall res = self._new() + acb_conj(res.value, self.value) + return res + + cpdef ModuleElement _add_(self, ModuleElement other): + """ + Return the sum of two balls, rounded to the ambient field's precision. + + The resulting ball is guaranteed to contain the sums of any two points + of the respective input balls. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF(1) + CBF(I) + 1.000000000000000 + 1.000000000000000*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_add(res.value, self.value, ( other).value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + cpdef ModuleElement _sub_(self, ModuleElement other): + """ + Return the difference of two balls, rounded to the ambient field's + precision. + + The resulting ball is guaranteed to contain the differences of any two + points of the respective input balls. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF(1) - CBF(I) + 1.000000000000000 - 1.000000000000000*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_sub(res.value, self.value, ( other).value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + def __invert__(self): + """ + Return the inverse of this ball. + + The result is guaranteed to contain the inverse of any point of the + input ball. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: ~CBF(i/3) + [-3.00000000000000 +/- 9.44e-16]*I + sage: ~CBF(0) + [+/- inf] + sage: ~CBF(RIF(10,11)) + [0.1 +/- 9.53e-3] + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_inv(res.value, self.value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + cpdef RingElement _mul_(self, RingElement other): + """ + Return the product of two balls, rounded to the ambient field's + precision. + + The resulting ball is guaranteed to contain the products of any two + points of the respective input balls. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: CBF(-2, 1)*CBF(1, 1/3) + [-2.333333333333333 +/- 5.37e-16] + [0.333333333333333 +/- 4.82e-16]*I + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_mul(res.value, self.value, ( other).value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + + cpdef RingElement _div_(self, RingElement other): + """ + Return the quotient of two balls, rounded to the ambient field's + precision. + + The resulting ball is guaranteed to contain the quotients of any two + points of the respective input balls. + + EXAMPLES:: + + sage: from sage.rings.complex_ball_acb import CBF + sage: from sage.rings.real_arb import RBF + sage: CBF(-2, 1)/CBF(1, 1/3) + [-1.50000000000000 +/- 1.27e-15] + [1.500000000000000 +/- 8.94e-16]*I + sage: CBF(2+I)/CBF(0) + [+/- inf] + [+/- inf]*I + sage: CBF(1)/CBF(0) + [+/- inf] + sage: CBF(1)/CBF(RBF(0, 1.)) + nan + """ + cdef ComplexBall res = self._new() + if _do_sig(prec(self)): sig_on() + acb_div(res.value, self.value, ( other).value, prec(self)) + if _do_sig(prec(self)): sig_off() + return res + +CBF = ComplexBallField() diff --git a/src/sage/rings/complex_double.pyx b/src/sage/rings/complex_double.pyx index e181c7d8237..31c12bd710a 100644 --- a/src/sage/rings/complex_double.pyx +++ b/src/sage/rings/complex_double.pyx @@ -151,7 +151,7 @@ cdef class ComplexDoubleField_class(sage.rings.ring.Field): (-1.0, -1.0 + 1.2246...e-16*I, False) """ from sage.categories.fields import Fields - ParentWithGens.__init__(self, self, ('I',), normalize=False, category = Fields()) + ParentWithGens.__init__(self, self, ('I',), normalize=False, category=Fields().Metric().Complete()) self._populate_coercion_lists_() def __reduce__(self): diff --git a/src/sage/rings/complex_field.py b/src/sage/rings/complex_field.py index 5ac959ccce6..991635b8e6a 100644 --- a/src/sage/rings/complex_field.py +++ b/src/sage/rings/complex_field.py @@ -198,12 +198,12 @@ def __init__(self, prec=53): sage: C = ComplexField(200) sage: C.category() - Category of fields + Join of Category of fields and Category of complete metric spaces sage: TestSuite(C).run() """ self._prec = int(prec) from sage.categories.fields import Fields - ParentWithGens.__init__(self, self._real_field(), ('I',), False, category = Fields()) + ParentWithGens.__init__(self, self._real_field(), ('I',), False, category=Fields().Metric().Complete()) # self._populate_coercion_lists_() self._populate_coercion_lists_(coerce_list=[complex_number.RRtoCC(self._real_field(), self)]) @@ -744,3 +744,4 @@ def _factor_univariate_polynomial(self, f): from sage.structure.factorization import Factorization return Factorization([(R(g).monic(),e) for g,e in zip(*F)], f.leading_coefficient()) + diff --git a/src/sage/rings/complex_interval.pyx b/src/sage/rings/complex_interval.pyx index 4faeca9a017..936ed82c762 100644 --- a/src/sage/rings/complex_interval.pyx +++ b/src/sage/rings/complex_interval.pyx @@ -40,21 +40,20 @@ heavily modified: #***************************************************************************** -import math -import operator - include "sage/ext/interrupt.pxi" +from sage.libs.gmp.mpz cimport mpz_sgn, mpz_cmpabs_ui +from sage.libs.flint.fmpz cimport * from sage.structure.element cimport FieldElement, RingElement, Element, ModuleElement from complex_number cimport ComplexNumber import complex_interval_field from complex_field import ComplexField -import sage.misc.misc -cimport integer +from sage.rings.integer cimport Integer import infinity cimport real_mpfi cimport real_mpfr +from sage.libs.pari.gen cimport gen as pari_gen cdef double LOG_TEN_TWO_PLUS_EPSILON = 3.321928094887363 # a small overestimate of log(10,2) @@ -120,7 +119,7 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): real, imag = (real).real(), (real).imag() elif isinstance(real, ComplexIntervalFieldElement): real, imag = (real).real(), (real).imag() - elif isinstance(real, sage.libs.pari.all.pari_gen): + elif isinstance(real, pari_gen): real, imag = real.real(), real.imag() elif isinstance(real, list) or isinstance(real, tuple): re, imag = real @@ -475,6 +474,54 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): mpfi_union(x.__im, self.__im, other_intv.__im) return x + def magnitude(self): + """ + The largest absolute value of the elements of the interval, rounded + away from zero. + + OUTPUT: a real number with rounding mode ``RNDU`` + + EXAMPLES:: + + sage: CIF(RIF(-1,1), RIF(-1,1)).magnitude() + 1.41421356237310 + sage: CIF(RIF(1,2), RIF(3,4)).magnitude() + 4.47213595499958 + sage: parent(CIF(1).magnitude()) + Real Field with 53 bits of precision and rounding RNDU + """ + cdef real_mpfi.RealIntervalField_class RIF = self._parent._real_field() + cdef real_mpfr.RealNumber x = RIF.__upper_field._new() + cdef real_mpfr.RealNumber y = RIF.__upper_field._new() + mpfi_mag(x.value, self.__re) + mpfi_mag(y.value, self.__im) + mpfr_hypot(x.value, x.value, y.value, MPFR_RNDA) + return x + + def mignitude(self): + """ + The smallest absolute value of the elements of the interval, rounded + towards zero. + + OUTPUT: a real number with rounding mode ``RNDD`` + + EXAMPLES:: + + sage: CIF(RIF(-1,1), RIF(-1,1)).mignitude() + 0.000000000000000 + sage: CIF(RIF(1,2), RIF(3,4)).mignitude() + 3.16227766016837 + sage: parent(CIF(1).mignitude()) + Real Field with 53 bits of precision and rounding RNDD + """ + cdef real_mpfi.RealIntervalField_class RIF = self._parent._real_field() + cdef real_mpfr.RealNumber x = RIF.__lower_field._new() + cdef real_mpfr.RealNumber y = RIF.__lower_field._new() + mpfi_mig(x.value, self.__re) + mpfi_mig(y.value, self.__im) + mpfr_hypot(x.value, x.value, y.value, MPFR_RNDZ) + return x + def center(self): """ Returns the closest floating-point approximation to the center @@ -732,10 +779,109 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement): '[0.99109735947126309 .. 1.1179269966896264] + [1.4042388462787560 .. 1.4984624123369835]*I' sage: CIF(-7, -1) ^ CIF(0.3) 1.117926996689626? - 1.408500714575360?*I - """ - if isinstance(right, (int, long, integer.Integer)): - return RingElement.__pow__(self, right) - return (self.log() * self.parent()(right)).exp() + + Note that ``x^2`` is not the same as ``x*x``:: + + sage: a = CIF(RIF(-1,1)) + sage: print (a^2).str(style="brackets") + [0.00000000000000000 .. 1.0000000000000000] + sage: print (a*a).str(style="brackets") + [-1.0000000000000000 .. 1.0000000000000000] + sage: a = CIF(0, RIF(-1,1)) + sage: print (a^2).str(style="brackets") + [-1.0000000000000000 .. -0.00000000000000000] + sage: print (a*a).str(style="brackets") + [-1.0000000000000000 .. 1.0000000000000000] + sage: a = CIF(RIF(-1,1), RIF(-1,1)) + sage: print (a^2).str(style="brackets") + [-1.0000000000000000 .. 1.0000000000000000] + [-2.0000000000000000 .. 2.0000000000000000]*I + sage: print (a*a).str(style="brackets") + [-2.0000000000000000 .. 2.0000000000000000] + [-2.0000000000000000 .. 2.0000000000000000]*I + + We can take very high powers:: + + sage: RIF = RealIntervalField(27) + sage: CIF = ComplexIntervalField(27) + sage: s = RealField(27, rnd="RNDZ")(1/2)^(1/3) + sage: a = CIF(RIF(-s/2,s/2), RIF(-s, s)) + sage: r = a^(10^10000) + sage: print r.str(style="brackets") + [-2.107553304e1028 .. 2.107553304e1028] + [-2.107553304e1028 .. 2.107553304e1028]*I + + TESTS:: + + sage: CIF = ComplexIntervalField(7) + sage: [CIF(2) ^ RDF(i) for i in range(-5,6)] + [0.03125?, 0.06250?, 0.1250?, 0.2500?, 0.5000?, 1, 2, 4, 8, 16, 32] + sage: pow(CIF(1), CIF(1), CIF(1)) + Traceback (most recent call last): + ... + TypeError: pow() 3rd argument not allowed unless all arguments are integers + """ + if modulus is not None: + raise TypeError("pow() 3rd argument not allowed unless all arguments are integers") + + cdef ComplexIntervalFieldElement z, z2, t = None + z = self + + # Convert right to an integer + if not isinstance(right, Integer): + try: + right = Integer(right) + except TypeError: + # Exponent is really not an integer + return (z.log() * z._parent(right)).exp() + + cdef int s = mpz_sgn((right).value) + if s == 0: + return z._parent.one() + elif s < 0: + z = ~z + if not mpz_cmpabs_ui((right).value, 1): + return z + + # Convert exponent to fmpz_t + cdef fmpz_t e + fmpz_init(e) + fmpz_set_mpz(e, (right).value) + fmpz_abs(e, e) + + # Now we know that e >= 2. + # Use binary powering with special formula for squares. + + # Handle first bit more efficiently: + if fmpz_tstbit(e, 0): + res = z + else: + res = z._parent.one() + fmpz_tdiv_q_2exp(e, e, 1) # e >>= 1 + + # Allocate a temporary ComplexIntervalFieldElement + z2 = z._new() + + while True: + # Compute z2 = z^2 using the formula + # (a + bi)^2 = (a^2 - b^2) + 2abi + mpfi_sqr(z2.__re, z.__re) # a^2 + mpfi_sqr(z2.__im, z.__im) # b^2 + mpfi_sub(z2.__re, z2.__re, z2.__im) # a^2 - b^2 + mpfi_mul(z2.__im, z.__re, z.__im) # ab + mpfi_mul_2ui(z2.__im, z2.__im, 1) # 2ab + z = z2 + if fmpz_tstbit(e, 0): + res *= z + fmpz_tdiv_q_2exp(e, e, 1) # e >>= 1 + if fmpz_is_zero(e): + break + + # Swap temporary elements z2 and t (allocate t first if needed) + if t is not None: + z2 = t + else: + z2 = z2._new() + t = z + fmpz_clear(e) + return res def _magma_init_(self, magma): r""" diff --git a/src/sage/rings/continued_fraction.py b/src/sage/rings/continued_fraction.py index 13f17a7c667..fd86a4f6ebb 100644 --- a/src/sage/rings/continued_fraction.py +++ b/src/sage/rings/continued_fraction.py @@ -533,12 +533,8 @@ def __cmp__(self, other): def _mpfr_(self, R): r""" - Return a numerical approximation of ``self`` in the real mpfr ring ``R``. - - The output result is accurate: when the rounding mode of - ``R`` is 'RNDN' then the result is the nearest binary number of ``R`` to - ``self``. The other rounding mode are 'RNDD' (toward +infinity), 'RNDU' - (toward -infinity) and 'RNDZ' (toward zero). + Return a correctly-rounded numerical approximation of ``self`` + in the real mpfr ring ``R``. EXAMPLES:: @@ -589,43 +585,38 @@ def _mpfr_(self, R): sage: cf.n(digits=11) 8.9371541378 - TESTS:: + TESTS: - We check that the rounding works as expected, at least in the rational - case:: + Check that the rounding works as expected (at least in the + rational case):: - sage: for _ in xrange(100): - ....: a = QQ.random_element(num_bound=1<<64) + sage: fields = [] + sage: for prec in [17, 24, 53, 128, 256]: + ....: for rnd in ['RNDN', 'RNDD', 'RNDU', 'RNDZ']: + ....: fields.append(RealField(prec=prec, rnd=rnd)) + sage: for n in range(3000): # long time + ....: a = QQ.random_element(num_bound=2^(n%100)) ....: cf = continued_fraction(a) - ....: for prec in 17,24,53,128,256: - ....: for rnd in 'RNDN','RNDD','RNDU','RNDZ': - ....: R = RealField(prec=prec, rnd=rnd) - ....: assert R(cf) == R(a) + ....: for R in fields: + ....: assert R(cf) == R(a) """ # 1. integer case if self.quotient(1) is Infinity: return R(self.quotient(0)) - # 2. negative numbers - # TODO: it is possible to deal with negative values. The only problem is - # that we need to find the good value for N (which involves - # self.quotient(k) for k=0,1,2) + rnd = R.rounding_mode() + + # 2. negative numbers: reduce to the positive case if self.quotient(0) < 0: - rnd = R.rounding_mode() - if rnd == 'RNDN' or rnd == 'RNDZ': - return -R(-self) - elif rnd == 'RNDD': - r = R(-self) - s,m,e = r.sign_mantissa_exponent() - if e < 0: - return -(R(m+1) >> (-e)) - return -(R(m+1) << e) - else: - r = R(-self) - s,m,e = r.sign_mantissa_exponent() - if e < 0: - return -(R(m-1) >> (-e)) - return -(R(m-1) << e) + sgn = -1 + self = -self + # Adjust rounding for change in sign + if rnd == 'RNDD': + rnd = 'RNDA' + elif rnd == 'RNDU': + rnd = 'RNDZ' + else: + sgn = 1 # 3. positive non integer if self.quotient(0) == 0: # 0 <= self < 1 @@ -655,8 +646,8 @@ def _mpfr_(self, R): assert m_odd.nbits() == R.prec() or m_even.nbits() == R.prec() - if m_even == m_odd: # no need to worry (we have a decimal number) - return R(m_even) >> N + if m_even == m_odd: # no need to worry (we have an exact number) + return R(sgn * m_even) >> N # check ordering # m_even/2^N <= p_even/q_even <= self <= p_odd/q_odd <= m_odd/2^N @@ -665,30 +656,24 @@ def _mpfr_(self, R): assert p_even / q_even <= p_odd / q_odd assert p_odd / q_odd <= m_odd / (ZZ_1 << N) - rnd = R.rounding_mode() if rnd == 'RNDN': # round to the nearest # in order to find the nearest approximation we possibly need to # augment our precision on convergents. while True: assert not(p_odd << (N+1) <= (2*m_odd-1) * q_odd) or not(p_even << (N+1) >= (2*m_even+1) * q_even) if p_odd << (N+1) <= (2*m_odd-1) * q_odd: - return R(m_even) >> N + return R(sgn * m_even) >> N if p_even << (N+1) >= (2*m_even+1) * q_even: - return R(m_odd) >> N + return R(sgn * m_odd) >> N k += 1 p_even = self.numerator(2*k) p_odd = self.numerator(2*k+1) q_even = self.denominator(2*k) q_odd = self.denominator(2*k+1) - elif rnd == 'RNDU': # round up (toward +infinity) - return R(m_odd) >> N - elif rnd == 'RNDD': # round down (toward -infinity) - return R(m_even) >> N - elif rnd == 'RNDZ': # round toward zero - if m_even.sign() == 1: - return R(m_even) >> N - else: - return R(m_odd) >> N + elif rnd == 'RNDU' or rnd == 'RNDA': # round up + return R(sgn * m_odd) >> N + elif rnd == 'RNDD' or rnd == 'RNDZ': # round down + return R(sgn * m_even) >> N else: raise ValueError("%s unknown rounding mode" % rnd) @@ -1471,7 +1456,7 @@ def _rational_(self): return self.numerator(n-1) / self.denominator(n-1) def _latex_(self): - """ + r""" EXAMPLES:: sage: a = continued_fraction(-17/389) diff --git a/src/sage/rings/finite_rings/integer_mod_ring.py b/src/sage/rings/finite_rings/integer_mod_ring.py index 9388bec34c2..8ab0fa50415 100644 --- a/src/sage/rings/finite_rings/integer_mod_ring.py +++ b/src/sage/rings/finite_rings/integer_mod_ring.py @@ -50,20 +50,12 @@ """ #***************************************************************************** -# -# Sage: System for Algebra and Geometry Experimentation -# # Copyright (C) 2005 William Stein # -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# The full text of the GPL is available at: -# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** @@ -169,7 +161,7 @@ class IntegerModFactory(UniqueFactory): EXAMPLES:: - sage: R = IntegerModRing(15, is_field=True) + sage: R = IntegerModRing(33, is_field=True) sage: R in Fields() True sage: R.is_field() @@ -182,7 +174,7 @@ class IntegerModFactory(UniqueFactory): Traceback (most recent call last): ... ValueError: THIS SAGE SESSION MIGHT BE SERIOUSLY COMPROMISED! - The order 15 is not prime, but this ring has been put + The order 33 is not prime, but this ring has been put into the category of fields. This may already have consequences in other parts of Sage. Either it was a mistake of the user, or a probabilitstic primality test has failed. @@ -1568,17 +1560,6 @@ def degree(self): from sage.structure.sage_object import register_unpickle_override register_unpickle_override('sage.rings.integer_mod_ring', 'IntegerModRing_generic', IntegerModRing_generic) -## def GF(p): -## """ -## EXAMPLES: -## sage: F = GF(11) -## sage: F -## Finite field of size 11 -## """ -## if not arith.is_prime(p): -## raise NotImplementedError("only prime fields currently implemented") -## return IntegerModRing(p) - def crt(v): """ INPUT: diff --git a/src/sage/rings/fraction_field_FpT.pyx b/src/sage/rings/fraction_field_FpT.pyx index 4898d3c5760..ee3c10cf786 100644 --- a/src/sage/rings/fraction_field_FpT.pyx +++ b/src/sage/rings/fraction_field_FpT.pyx @@ -341,18 +341,6 @@ cdef class FpTElement(RingElement): else: return "\\frac{%s}{%s}" % (self.numer()._latex_(), self.denom()._latex_()) - def __richcmp__(left, right, int op): - """ - EXAMPLES:: - - sage: K = Frac(GF(5)['t']); t = K.gen() - sage: t == 1 - False - sage: t + 1 < t^2 - True - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element other) except -2: """ Compares this with another element. The ordering is arbitrary, @@ -387,6 +375,14 @@ cdef class FpTElement(RingElement): True sage: b < 1/a False + + :: + + sage: K = Frac(GF(5)['t']); t = K.gen() + sage: t == 1 + False + sage: t + 1 < t^2 + True """ # They are normalized. cdef int j = sage_cmp_nmod_poly_t(self._numer, (other)._numer) diff --git a/src/sage/rings/fraction_field_element.pyx b/src/sage/rings/fraction_field_element.pyx index 4796600bbdf..86d12adfb26 100644 --- a/src/sage/rings/fraction_field_element.pyx +++ b/src/sage/rings/fraction_field_element.pyx @@ -841,18 +841,6 @@ cdef class FractionFieldElement(FieldElement): """ return float(self.__numerator) / float(self.__denominator) - def __richcmp__(left, right, int op): - """ - EXAMPLES:: - - sage: K. = Frac(ZZ['x,y']) - sage: x > y - True - sage: 1 > y - False - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element other) except -2: """ EXAMPLES:: @@ -864,6 +852,14 @@ cdef class FractionFieldElement(FieldElement): True sage: t == t/5 False + + :: + + sage: K. = Frac(ZZ['x,y']) + sage: x > y + True + sage: 1 > y + False """ return cmp(self.__numerator * \ (other).__denominator, diff --git a/src/sage/rings/function_field/function_field_element.pyx b/src/sage/rings/function_field/function_field_element.pyx index c701e7f8e39..2ecaabd469d 100644 --- a/src/sage/rings/function_field/function_field_element.pyx +++ b/src/sage/rings/function_field/function_field_element.pyx @@ -361,6 +361,17 @@ cdef class FunctionFieldElement_polymod(FunctionFieldElement): """ return not not self._x + def __hash__(self): + """ + TESTS:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x*y + 4*x^3) + sage: len({hash(y^i+x^j) for i in [-2..2] for j in [-2..2]}) == 25 + True + """ + return hash(self._x) + cpdef int _cmp_(self, Element other) except -2: """ EXAMPLES:: @@ -563,6 +574,19 @@ cdef class FunctionFieldElement_rational(FunctionFieldElement): """ return not not self._x + def __hash__(self): + """ + TESTS: + + It would be nice if the following would produce a list of + 15 distinct hashes:: + + sage: K. = FunctionField(QQ) + sage: len({hash(t^i+t^j) for i in [-2..2] for j in [i..2]}) + 10 + """ + return hash(self._x) + cpdef int _cmp_(self, Element other) except -2: """ EXAMPLES:: diff --git a/src/sage/rings/ideal.py b/src/sage/rings/ideal.py index 93b9e106461..36932590f97 100644 --- a/src/sage/rings/ideal.py +++ b/src/sage/rings/ideal.py @@ -1229,6 +1229,22 @@ def __contains__(self, x): return x.is_zero() return self.gen().divides(x) + def __hash__(self): + r""" + Very stupid constant hash function! + + TESTS:: + + sage: P. = PolynomialRing(ZZ) + sage: I = P.ideal(x^2) + sage: J = [x, y^2 + x*y]*P + sage: hash(I) + 0 + sage: hash(J) + 0 + """ + return 0 + def __cmp__(self, other): """ Compare the two ideals. diff --git a/src/sage/rings/integer_ring.pyx b/src/sage/rings/integer_ring.pyx index f43931947ba..76adb61c7d6 100644 --- a/src/sage/rings/integer_ring.pyx +++ b/src/sage/rings/integer_ring.pyx @@ -122,7 +122,8 @@ cdef class IntegerRing_class(PrincipalIdealDomain): False sage: Z.category() Join of Category of euclidean domains - and Category of infinite enumerated sets + and Category of infinite enumerated sets + and Category of metric spaces sage: Z(2^(2^5) + 1) 4294967297 @@ -303,7 +304,7 @@ cdef class IntegerRing_class(PrincipalIdealDomain): True """ ParentWithGens.__init__(self, self, ('x',), normalize=False, - category=(EuclideanDomains(), InfiniteEnumeratedSets())) + category=(EuclideanDomains(), InfiniteEnumeratedSets().Metric())) self._populate_coercion_lists_(element_constructor=integer.Integer, init_no_parent=True, convert_method_name='_integer_') @@ -1245,9 +1246,9 @@ cdef class IntegerRing_class(PrincipalIdealDomain): elif n == 2: return sage.rings.integer.Integer(-1) elif n < 1: - raise ValueError, "n must be positive in zeta()" + raise ValueError("n must be positive in zeta()") else: - raise ValueError, "no nth root of unity in integer ring" + raise ValueError("no nth root of unity in integer ring") def parameter(self): r""" diff --git a/src/sage/rings/laurent_series_ring_element.pyx b/src/sage/rings/laurent_series_ring_element.pyx index f54ada8f8d5..77ac755c16e 100644 --- a/src/sage/rings/laurent_series_ring_element.pyx +++ b/src/sage/rings/laurent_series_ring_element.pyx @@ -641,8 +641,11 @@ cdef class LaurentSeries(AlgebraElement): """ if prec == infinity or prec >= self.prec(): return self + P = self._parent + if not self: + return LaurentSeries(P, P.power_series_ring()(0, prec=0), prec) u = self.__u.add_bigoh(prec - self.__n) - return LaurentSeries(self._parent, u, self.__n) + return LaurentSeries(P, u, self.__n) def degree(self): """ @@ -1228,10 +1231,22 @@ cdef class LaurentSeries(AlgebraElement): sage: f.power_series() Traceback (most recent call last): ... - ArithmeticError: self is a not a power series + TypeError: self is not a power series + + TESTS: + + Check whether a polynomial over a Laurent series ring is contained in the + polynomial ring over the power series ring (see :trac:`19459`): + + sage: L. = LaurentSeriesRing(GF(2)) + sage: R. = PolynomialRing(L) + sage: O = L.power_series_ring() + sage: S. = PolynomialRing(O) + sage: t**(-1)*x*y in S + False """ if self.__n < 0: - raise ArithmeticError, "self is a not a power series" + raise TypeError("self is not a power series") u = self.__u t = u.parent().gen() return t**(self.__n) * u diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 6c8a835fcc2..73e1ebee1ac 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -7177,7 +7177,7 @@ def _subfields_helper(self, degree=0, name=None, both_maps=True, optimize=False) sage: L2, _, _ = K.subfields(2)[0]; L2, CDF(L2.gen()) # indirect doctest (Number Field in a0 with defining polynomial x^2 - 23, -4.795831523312721) - Test for :trac: `7695`:: + Test for :trac:`7695`:: sage: F = CyclotomicField(7) sage: K = F.subfields(3)[0][0] diff --git a/src/sage/rings/padics/CA_template.pxi b/src/sage/rings/padics/CA_template.pxi index 9ca4a54a8c5..1f56e90b3af 100644 --- a/src/sage/rings/padics/CA_template.pxi +++ b/src/sage/rings/padics/CA_template.pxi @@ -160,22 +160,6 @@ cdef class CAElement(pAdicTemplateElement): """ return unpickle_cae_v2, (self.__class__, self.parent(), cpickle(self.value, self.prime_pow), self.absprec) - def __richcmp__(self, right, int op): - """ - Compare this element to ``right`` using the comparison operator ``op``. - - TESTS:: - - sage: R = ZpCA(5) - sage: a = R(17) - sage: b = R(21) - sage: a == b - False - sage: a < b - True - """ - return (self)._richcmp(right, op) - cpdef ModuleElement _neg_(self): """ Return the additive inverse of this element. diff --git a/src/sage/rings/padics/CR_template.pxi b/src/sage/rings/padics/CR_template.pxi index 45c70d9ccec..41f63a68b0e 100644 --- a/src/sage/rings/padics/CR_template.pxi +++ b/src/sage/rings/padics/CR_template.pxi @@ -270,22 +270,6 @@ cdef class CRElement(pAdicTemplateElement): """ return unpickle_cre_v2, (self.__class__, self.parent(), cpickle(self.unit, self.prime_pow), self.ordp, self.relprec) - def __richcmp__(self, right, int op): - """ - Compare this element to ``right`` using the comparison operator ``op``. - - TESTS:: - - sage: R = Zp(5) - sage: a = R(17) - sage: b = R(21) - sage: a == b - False - sage: a < b # indirect doctest - True - """ - return (self)._richcmp(right, op) - cpdef ModuleElement _neg_(self): """ Return the additive inverse of this element. diff --git a/src/sage/rings/padics/FM_template.pxi b/src/sage/rings/padics/FM_template.pxi index e9c21608613..f7c446439bb 100644 --- a/src/sage/rings/padics/FM_template.pxi +++ b/src/sage/rings/padics/FM_template.pxi @@ -155,22 +155,6 @@ cdef class FMElement(pAdicTemplateElement): """ return unpickle_fme_v2, (self.__class__, self.parent(), cpickle(self.value, self.prime_pow)) - def __richcmp__(self, right, int op): - """ - Compare this element to ``right`` using the comparison operator ``op``. - - TESTS:: - - sage: R = ZpFM(5) - sage: a = R(17) - sage: b = R(21) - sage: a == b - False - sage: a < b - True - """ - return (self)._richcmp(right, op) - cpdef ModuleElement _neg_(self): r""" Return the additive inverse of this element. diff --git a/src/sage/rings/padics/local_generic.py b/src/sage/rings/padics/local_generic.py index 48bdfb4f64c..8ee98a1b41d 100644 --- a/src/sage/rings/padics/local_generic.py +++ b/src/sage/rings/padics/local_generic.py @@ -55,6 +55,7 @@ def __init__(self, base, prec, names, element_class, category=None): category = CompleteDiscreteValuationFields() else: category = CompleteDiscreteValuationRings() + category = category.Metric().Complete() if default_category is not None: category = check_default_category(default_category, category) Parent.__init__(self, base, names=(names,), normalize=False, category=category, element_constructor=element_class) diff --git a/src/sage/rings/padics/morphism.pyx b/src/sage/rings/padics/morphism.pyx index 49bb5fb5ff1..20c6a31a720 100644 --- a/src/sage/rings/padics/morphism.pyx +++ b/src/sage/rings/padics/morphism.pyx @@ -1,12 +1,16 @@ -"Frobenius endomorphisms on padic fields" +""" +Frobenius endomorphisms on p-adic fields +""" -############################################################################# -# Copyright (C) 2013 Xavier Caruso -# -# Distributed under the terms of the GNU General Public License (GPL) +#***************************************************************************** +# Copyright (C) 2013 Xavier Caruso # +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. # http://www.gnu.org/licenses/ -#**************************************************************************** +#***************************************************************************** from sage.rings.integer cimport Integer from sage.rings.infinity import Infinity @@ -288,9 +292,6 @@ cdef class FrobeniusEndomorphism_padics(RingHomomorphism): codomain = self.codomain() return hash((domain,codomain,('Frob',self._power))) - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - cpdef int _cmp_(left, Element right) except -2: """ Compare left and right diff --git a/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx b/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx index 377f2f6713b..317b69f56dc 100644 --- a/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx +++ b/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx @@ -445,25 +445,6 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): ans.prime_pow = self.prime_pow return ans - def __richcmp__(left, right, op): - """ - Compares ``left`` and ``right`` under the operation ``op``. - - EXAMPLES:: - - sage: R = ZpFM(5,5) - sage: S. = R[] - sage: f = x^5 + 75*x^3 - 15*x^2 +125*x - 5 - sage: W. = R.ext(f) - sage: w == 1 # indirect doctest - False - sage: y = 1 + w - sage: z = 1 + w + w^27 - sage: y == z - True - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(left, Element right) except -2: """ First compare valuations, then compare the values. diff --git a/src/sage/rings/padics/padic_base_leaves.py b/src/sage/rings/padics/padic_base_leaves.py index a43eb7ef9af..55de7f5c625 100644 --- a/src/sage/rings/padics/padic_base_leaves.py +++ b/src/sage/rings/padics/padic_base_leaves.py @@ -231,7 +231,7 @@ def __init__(self, p, prec, print_mode, names): sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^3)]) sage: R = ZpCR(3, 2) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) + sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) # long time sage: R = ZpCR(next_prime(10^60)) sage: TestSuite(R).run() @@ -320,7 +320,7 @@ def __init__(self, p, prec, print_mode, names): sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^3)]) sage: R = ZpCA(3, 2) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) + sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) # long time sage: R = ZpCA(next_prime(10^60)) sage: TestSuite(R).run() @@ -411,7 +411,7 @@ def __init__(self, p, prec, print_mode, names): sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^3)]) sage: R = ZpFM(3, 2) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) + sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) # long time sage: R = ZpFM(next_prime(10^60)) sage: TestSuite(R).run() @@ -525,10 +525,12 @@ def __init__(self, p, prec, print_mode, names): sage: TestSuite(R).run(elements = [R.random_element() for i in range(2^10)], max_runs = 2^12) # long time sage: R = Qp(3, 1) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) + sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^6)]) # long time sage: R = Qp(3, 2) - sage: TestSuite(R).run(elements = [R.random_element() for i in range(3^9)]) + sage: TestSuite(R).run(elements=[R.random_element() for i in range(3^9)], + ....: skip="_test_metric") # Skip because too long + sage: R._test_metric(elements=[R.random_element() for i in range(3^3)]) sage: R = Qp(next_prime(10^60)) sage: TestSuite(R).run() diff --git a/src/sage/rings/padics/padic_generic.py b/src/sage/rings/padics/padic_generic.py index 1fba8833650..6cccca5d9e8 100644 --- a/src/sage/rings/padics/padic_generic.py +++ b/src/sage/rings/padics/padic_generic.py @@ -24,6 +24,10 @@ # http://www.gnu.org/licenses/ #***************************************************************************** + +from sage.misc.prandom import sample +from sage.misc.misc import some_tuples + from sage.categories.principal_ideal_domains import PrincipalIdealDomains from sage.categories.fields import Fields from sage.rings.infinity import infinity @@ -34,6 +38,7 @@ from sage.rings.padics.precision_error import PrecisionError from sage.misc.cachefunc import cached_method + class pAdicGeneric(PrincipalIdealDomain, LocalGeneric): def __init__(self, base, p, prec, print_mode, names, element_class, category=None): """ @@ -56,6 +61,7 @@ def __init__(self, base, p, prec, print_mode, names, element_class, category=Non category = Fields() else: category = PrincipalIdealDomains() + category = category.Metric().Complete() LocalGeneric.__init__(self, base, prec, names, element_class, category) self._printer = pAdicPrinter(self, print_mode) @@ -518,12 +524,7 @@ def _test_add(self, **options): tester.assertEqual(y.precision_absolute(),x.precision_absolute()) tester.assertEqual(y.precision_relative(),x.precision_relative()) - from sage.combinat.cartesian_product import CartesianProduct - elements = CartesianProduct(elements, elements) - if len(elements) > tester._max_runs: - from random import sample - elements = sample(elements, tester._max_runs) - for x,y in elements: + for x,y in some_tuples(elements, 2, tester._max_runs): z = x + y tester.assertIs(z.parent(), self) tester.assertEqual(z.precision_absolute(), min(x.precision_absolute(), y.precision_absolute())) @@ -552,19 +553,14 @@ def _test_sub(self, **options): """ tester = self._tester(**options) - elements = tester.some_elements() + elements = list(tester.some_elements()) for x in elements: y = x - self.zero() tester.assertEqual(y, x) tester.assertEqual(y.precision_absolute(), x.precision_absolute()) tester.assertEqual(y.precision_relative(), x.precision_relative()) - from sage.combinat.cartesian_product import CartesianProduct - elements = CartesianProduct(elements, elements) - if len(elements) > tester._max_runs: - from random import sample - elements = sample(elements, tester._max_runs) - for x,y in elements: + for x,y in some_tuples(elements, 2, tester._max_runs): z = x - y tester.assertIs(z.parent(), self) tester.assertEqual(z.precision_absolute(), min(x.precision_absolute(), y.precision_absolute())) @@ -628,12 +624,8 @@ def _test_mul(self, **options): """ tester = self._tester(**options) - from sage.combinat.cartesian_product import CartesianProduct - elements = CartesianProduct(tester.some_elements(), tester.some_elements()) - if len(elements) > tester._max_runs: - from random import sample - elements = sample(elements, tester._max_runs) - for x,y in elements: + elements = list(tester.some_elements()) + for x,y in some_tuples(elements, 2, tester._max_runs): z = x * y tester.assertIs(z.parent(), self) tester.assertLessEqual(z.precision_relative(), min(x.precision_relative(), y.precision_relative())) @@ -659,12 +651,8 @@ def _test_div(self, **options): """ tester = self._tester(**options) - from sage.combinat.cartesian_product import CartesianProduct - elements = CartesianProduct(tester.some_elements(), tester.some_elements()) - if len(elements) > tester._max_runs: - from random import sample - elements = sample(elements, tester._max_runs) - for x,y in elements: + elements = list(tester.some_elements()) + for x,y in some_tuples(elements, 2, tester._max_runs): try: z = x / y except (ZeroDivisionError, PrecisionError, ValueError): diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index 7d9fd5dacd4..1a334b28f17 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -76,6 +76,42 @@ cdef class pAdicGenericElement(LocalGenericElement): 3 + O(19^5) sage: a < b True + + :: + + sage: R = Zp(5); a = R(5, 6); b = R(5 + 5^6, 8) + sage: a == b #indirect doctest + True + + :: + + sage: R = Zp(5) + sage: a = R(17) + sage: b = R(21) + sage: a == b + False + sage: a < b + True + + :: + + sage: R = ZpCA(5) + sage: a = R(17) + sage: b = R(21) + sage: a == b + False + sage: a < b + True + + :: + + sage: R = ZpFM(5) + sage: a = R(17) + sage: b = R(21) + sage: a == b + False + sage: a < b + True """ m = min(left.precision_absolute(), right.precision_absolute()) x_ordp = left.valuation() diff --git a/src/sage/rings/polynomial/ideal.py b/src/sage/rings/polynomial/ideal.py index a23f3e8567a..508496c75d4 100644 --- a/src/sage/rings/polynomial/ideal.py +++ b/src/sage/rings/polynomial/ideal.py @@ -64,4 +64,3 @@ def residue_field(self, names=None, check=True): from sage.rings.finite_rings.residue_field import ResidueField return ResidueField(self, names, check=False) - diff --git a/src/sage/rings/polynomial/laurent_polynomial.pyx b/src/sage/rings/polynomial/laurent_polynomial.pyx index 0ef044296ef..956c0c09ee5 100644 --- a/src/sage/rings/polynomial/laurent_polynomial.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial.pyx @@ -844,30 +844,6 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial_generic): rl = LaurentPolynomial_univariate(self._parent, r, 0) return (ql, rl) - def __richcmp__(left, right, int op): - """ - Return the rich comparison of ``left`` and ``right`` defined by ``op``. - - EXAMPLES:: - - sage: R. = LaurentPolynomialRing(QQ) - sage: f = x^(-1) + 1 + x - sage: g = x^(-1) + 2 - sage: f == g - False - sage: f != g - True - sage: f < g - True - sage: f <= g - True - sage: f > g - False - sage: f >= g - False - """ - return (left)._richcmp(right, op) - cpdef int _cmp_(self, Element right_r) except -2: r""" Comparison of ``self`` and ``right_r``. @@ -886,10 +862,16 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial_generic): sage: g = x^(-1) + 2 sage: f == g False + sage: f != g + True sage: f < g True + sage: f <= g + True sage: f > g False + sage: f >= g + False :: @@ -1341,18 +1323,16 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial_generic): def __hash__(self): r""" - TESTS:: + TESTS: + + Test that the hash is non-constant (the hash does not need to be + deterministic so we leave some slack for collisions):: sage: L. = LaurentPolynomialRing(QQ) - sage: f = L({(-1,-1):1}) - sage: hash(f) - 1 - sage: f = L({(1,1):1}) - sage: hash(f) - -2021162459040316190 # 64-bit - -1148451614 # 32-bit + sage: len({hash(w^i*z^j) for i in [-2..2] for j in [-2..2]}) > 20 + True """ - return hash(self._poly) + return hash(self._poly) ^ hash(self._mon) cdef _new_c(self): """ diff --git a/src/sage/rings/polynomial/multi_polynomial.pyx b/src/sage/rings/polynomial/multi_polynomial.pyx index 0c56d646477..6abf48db402 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pyx +++ b/src/sage/rings/polynomial/multi_polynomial.pyx @@ -4,7 +4,7 @@ Base class for elements of multivariate polynomial rings from sage.rings.integer cimport Integer from sage.rings.integer_ring import ZZ - +from sage.structure.element cimport coercion_model from sage.misc.derivative import multi_derivative from sage.rings.infinity import infinity @@ -1207,8 +1207,6 @@ cdef class MPolynomial(CommutativeRingElement): from sage.matrix.constructor import matrix if self.parent() != right.parent(): - from sage.structure.element import get_coercion_model - coercion_model = get_coercion_model() a, b = coercion_model.canonical_coercion(self,right) if variable: variable = a.parent()(variable) diff --git a/src/sage/rings/polynomial/multi_polynomial_ideal.py b/src/sage/rings/polynomial/multi_polynomial_ideal.py index da64b370cdb..460b2ddb4a6 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ideal.py +++ b/src/sage/rings/polynomial/multi_polynomial_ideal.py @@ -2205,7 +2205,6 @@ def variety(self, ring=None): This is due to precision error, which causes the computation of an intermediate Groebner basis to fail. - If the ground field's characteristic is too large for Singular, we resort to a toy implementation:: @@ -2217,6 +2216,22 @@ def variety(self, ring=None): verbose 0 (...: multi_polynomial_ideal.py, variety) Warning: falling back to very slow toy implementation. [{y: 0, x: 0}] + The dictionary expressing the variety will be indexed by generators + of the polynomial ring after changing to the target field. + But the mapping will also accept generators of the original ring, + or even generator names as strings, when provided as keys:: + + sage: K. = QQ[] + sage: I = ideal([x^2+2*y-5,x+y+3]) + sage: v = I.variety(AA)[0]; v + {x: 4.464101615137755?, y: -7.464101615137755?} + sage: v.keys()[0].parent() + Multivariate Polynomial Ring in x, y over Algebraic Real Field + sage: v[x] + 4.464101615137755? + sage: v["y"] + -7.464101615137755? + TESTS:: sage: K. = GF(27) @@ -2246,27 +2261,27 @@ def variety(self, ring=None): x26^2 + x26, x27^2 + x27, x28^2 + x28, x29^2 + x29, x30^2 + x30]) sage: I.basis_is_groebner() True - sage: for V in I.variety(): print V # long time (6s on sage.math, 2011) - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 0, x19: 0, x18: 1, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 0, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 1, x19: 0, x18: 1, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 0, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 0, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 0, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 0, x19: 0, x18: 0, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 1, x4: 1, x19: 0, x18: 0, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 1, x25: 1, x9: 0, x8: 0, x20: 0, x17: 1, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 0, x19: 0, x18: 1, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 0, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} - {x14: 0, x24: 0, x16: 1, x1: 1, x3: 1, x2: 0, x5: 0, x4: 1, x19: 0, x18: 1, x7: 1, x6: 0, x10: 1, x30: 0, x28: 1, x29: 1, x13: 0, x27: 1, x11: 0, x25: 1, x9: 0, x8: 0, x20: 0, x17: 0, x23: 0, x26: 0, x15: 0, x21: 1, x12: 0, x22: 0} + sage: sorted("".join(str(V[g]) for g in R.gens()) for V in I.variety()) # long time (6s on sage.math, 2011) + ['101000100000000110001000100110', + '101000100000000110001000101110', + '101000100100000101001000100110', + '101000100100000101001000101110', + '101010100000000110001000100110', + '101010100000000110001000101110', + '101010100010000110001000100110', + '101010100010000110001000101110', + '101010100110000110001000100110', + '101010100110000110001000101110', + '101100100000000110001000100110', + '101100100000000110001000101110', + '101100100100000101001000100110', + '101100100100000101001000101110', + '101110100000000110001000100110', + '101110100000000110001000101110', + '101110100010000110001000100110', + '101110100010000110001000101110', + '101110100110000110001000100110', + '101110100110000110001000101110'] Check that the issue at :trac:`7425` is fixed:: @@ -2353,15 +2368,16 @@ def _variety(T, V, v=None): else: raise TypeError("Local/unknown orderings not supported by 'toy_buchberger' implementation.") + from sage.misc.converting_dict import KeyConvertingDict V = [] for t in T: Vbar = _variety([P(f) for f in t], []) #Vbar = _variety(list(t.gens()),[]) for v in Vbar: - V.append(dict([(P(var),val) for var,val in v.iteritems()])) + V.append(KeyConvertingDict(P, v)) V.sort() - return Sequence(V) + return V @require_field def hilbert_polynomial(self): @@ -3013,6 +3029,18 @@ def __init__(self, ring, gens, coerce=True): Ideal_generic.__init__(self, ring, gens, coerce=coerce) self._gb_by_ordering = dict() + def __hash__(self): + r""" + Stupid constant hash function! + + TESTS:: + + sage: R. = PolynomialRing(IntegerRing(), 2, order='lex') + sage: hash(R.ideal([x, y])) + 0 + """ + return 0 + @cached_method def gens(self): """ @@ -4418,8 +4446,9 @@ def weil_restriction(self): Ring in x0, x1, x2, x3, x4, y0, y1, y2, y3, y4, z0, z1, z2, z3, z4 over Finite Field of size 3 sage: J += sage.rings.ideal.FieldIdeal(J.ring()) # ensure radical ideal - sage: J.variety() - [{y1: 0, y4: 0, x4: 0, y2: 0, y3: 0, y0: 0, x2: 0, z4: 0, z3: 0, z2: 0, x1: 0, z1: 0, z0: 0, x0: 1, x3: 0}] + sage: from sage.doctest.fixtures import reproducible_repr + sage: print(reproducible_repr(J.variety())) + [{x0: 1, x1: 0, x2: 0, x3: 0, x4: 0, y0: 0, y1: 0, y2: 0, y3: 0, y4: 0, z0: 0, z1: 0, z2: 0, z3: 0, z4: 0}] Weil restrictions are often used to study elliptic curves over diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index e50037ea73a..f96796667b1 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -219,7 +219,7 @@ from sage.rings.finite_rings.integer_mod_ring import is_IntegerModRing from sage.rings.number_field.number_field_base cimport NumberField from sage.rings.arith import gcd -from sage.structure.element import coerce_binop, get_coercion_model +from sage.structure.element import coerce_binop from sage.structure.parent cimport Parent from sage.structure.parent_base cimport ParentWithBase @@ -231,6 +231,7 @@ from sage.structure.element cimport RingElement from sage.structure.element cimport ModuleElement from sage.structure.element cimport Element from sage.structure.element cimport CommutativeRingElement +from sage.structure.element cimport coercion_model from sage.structure.factorization import Factorization from sage.structure.sequence import Sequence @@ -2114,7 +2115,7 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn cdef poly *res # ownership will be transferred to us in the next line singular_polynomial_call(&res, self._poly, _ring, coerced_x, MPolynomial_libsingular_get_element) - res_parent = get_coercion_model().common_parent(parent._base, *x) + res_parent = coercion_model.common_parent(parent._base, *x) if res == NULL: return res_parent(0) @@ -2128,10 +2129,6 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn sage_res = res_parent(sage_res) return sage_res - # you may have to replicate this boilerplate code in derived classes if you override - # __richcmp__. The python documentation at http://docs.python.org/api/type-structs.html - # explains how __richcmp__, __hash__, and __cmp__ are tied together. - def __hash__(self): """ This hash incorporates the variable name in an effort to @@ -2155,7 +2152,7 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn """ return self._hash_c() - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ Compare left and right and return -1, 0, and 1 for <,==, and > respectively. @@ -2207,9 +2204,6 @@ cdef class MPolynomial_libsingular(sage.rings.polynomial.multi_polynomial.MPolyn sage: (66*x^2 + 23) > (66*x^2 + 2) True """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: if left is right: return 0 cdef poly *p = (left)._poly diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx index 773cdebd99b..0991db3ff1e 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_generic.pyx @@ -640,8 +640,8 @@ cdef class MPolynomialRing_generic(sage.rings.ring.CommutativeRing): We do not check if the provided index/rank is within the allowed range. If it is not an infinite loop will occur. """ - from sage.combinat import choose_nk - comb = choose_nk.from_rank(i, n+d-1, n-1) + from sage.combinat import combination + comb = combination.from_rank(i, n+d-1, n-1) if comb == []: return (d,) monomial = [ comb[0] ] diff --git a/src/sage/rings/polynomial/multi_polynomial_sequence.py b/src/sage/rings/polynomial/multi_polynomial_sequence.py index 8a45334782e..3cdca4a8535 100644 --- a/src/sage/rings/polynomial/multi_polynomial_sequence.py +++ b/src/sage/rings/polynomial/multi_polynomial_sequence.py @@ -157,6 +157,7 @@ from sage.misc.cachefunc import cached_method from types import GeneratorType +from sage.misc.converting_dict import KeyConvertingDict from sage.misc.package import is_package_installed from sage.structure.sequence import Sequence, Sequence_generic @@ -1346,10 +1347,12 @@ def solve(self, algorithm='polybori', n=1, eliminate_linear_variables=True, ver Without argument, a single arbitrary solution is returned:: + sage: from sage.doctest.fixtures import reproducible_repr sage: R. = BooleanPolynomialRing() sage: S = Sequence([x*y+z, y*z+x, x+y+z+1]) - sage: sol = S.solve(); sol # random - [{y: 1, z: 0, x: 0}] + sage: sol = S.solve() + sage: print(reproducible_repr(sol)) + [{x: 0, y: 1, z: 0}] We check that it is actually a solution:: @@ -1358,7 +1361,8 @@ def solve(self, algorithm='polybori', n=1, eliminate_linear_variables=True, ver We obtain all solutions:: - sage: sols = S.solve(n=Infinity); sols # random + sage: sols = S.solve(n=Infinity) + sage: print(reproducible_repr(sols)) [{x: 0, y: 1, z: 0}, {x: 1, y: 1, z: 1}] sage: map( lambda x: S.subs(x), sols) [[0, 0, 0], [0, 0, 0]] @@ -1366,15 +1370,17 @@ def solve(self, algorithm='polybori', n=1, eliminate_linear_variables=True, ver We can force the use of exhaustive search if the optional package ``FES`` is present:: - sage: sol = S.solve(algorithm='exhaustive_search'); sol # random, optional - FES + sage: sol = S.solve(algorithm='exhaustive_search') # optional - FES + sage: print(reproducible_repr(sol)) # optional - FES [{x: 1, y: 1, z: 1}] sage: S.subs( sol[0] ) [0, 0, 0] And we may use SAT-solvers if they are available:: - sage: sol = S.solve(algorithm='sat'); sol # random, optional - cryptominisat - [{y: 1, z: 0, x: 0}] + sage: sol = S.solve(algorithm='sat'); sol # optional - cryptominisat + sage: print(reproducible_repr(sol)) # optional - cryptominisat + [{x: 0, y: 1, z: 0}] sage: S.subs( sol[0] ) [0, 0, 0] @@ -1451,15 +1457,19 @@ def solve(self, algorithm='polybori', n=1, eliminate_linear_variables=True, ver eliminated_variables = { f.lex_lead() for f in reductors } leftover_variables = { x.lm() for x in R_origin.gens() } - solved_variables - eliminated_variables + key_convert = lambda x: R_origin(x).lm() if leftover_variables != set(): partial_solutions = solutions solutions = [] for sol in partial_solutions: for v in VectorSpace( GF(2), len(leftover_variables) ): - new_solution = sol.copy() + new_solution = KeyConvertingDict(key_convert, sol) for var,val in zip(leftover_variables, v): new_solution[ var ] = val solutions.append( new_solution ) + else: + solutions = [ KeyConvertingDict(key_convert, sol) + for sol in solutions ] for r in reductors: for sol in solutions: diff --git a/src/sage/rings/polynomial/pbori.pyx b/src/sage/rings/polynomial/pbori.pyx index e789f33ce02..7f7954c841c 100644 --- a/src/sage/rings/polynomial/pbori.pyx +++ b/src/sage/rings/polynomial/pbori.pyx @@ -1260,7 +1260,7 @@ cdef class BooleanPolynomialRing(MPolynomialRing_generic): [x*y, x*y, x, x, x, x*y, x, y, x*y, 1] """ from sage.rings.integer_ring import ZZ - from sage.combinat.choose_nk import from_rank + from sage.combinat.combination import from_rank t = ZZ.random_element(0,monom_counts[-1]) if t == 0: @@ -2200,9 +2200,6 @@ cdef class BooleanMonomial(MonoidElement): self._ring = parent._ring self._pbmonom = PBMonom_Constructor((self._ring)._pbring) - def __dealloc__(self): - pass # destruction by C++ object's destructor - def __reduce__(self): """ Pickling @@ -2219,7 +2216,7 @@ cdef class BooleanMonomial(MonoidElement): gens = self._parent.gens() return self._parent, (tuple([gens.index(x) for x in self.variables()]),) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ Compare BooleanMonomial objects. @@ -2243,10 +2240,6 @@ cdef class BooleanMonomial(MonoidElement): sage: M(x) >= M(x) True """ - # boilerplate code from sage.structure.parent - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: cdef int res res = left._pbmonom.compare((right)._pbmonom) return res @@ -5110,10 +5103,11 @@ class BooleanPolynomialIdeal(MPolynomialIdeal): A Simple example:: + sage: from sage.doctest.fixtures import reproducible_repr sage: R. = BooleanPolynomialRing() sage: I = ideal( [ x*y*z + x*z + y + 1, x+y+z+1 ] ) - sage: I.variety() - [{z: 0, y: 1, x: 0}, {z: 1, y: 1, x: 1}] + sage: print(reproducible_repr(I.variety())) + [{x: 0, y: 1, z: 0}, {x: 1, y: 1, z: 1}] TESTS: @@ -5130,14 +5124,14 @@ class BooleanPolynomialIdeal(MPolynomialIdeal): x1*x2 + x1*x4 + x1*x5 + x1*x6 + x2*x3 + x2*x4 + x2*x5 + x3*x5 + x5*x6 + x5 + x6, \ x1*x2 + x1*x6 + x2*x4 + x2*x5 + x2*x6 + x3*x6 + x4*x6 + x5*x6 + x5] sage: I = R.ideal( polys ) - sage: I.variety() - [{x6: 0, x5: 0, x4: 0, x3: 0, x2: 0, x1: 0}, - {x6: 1, x5: 0, x4: 0, x3: 1, x2: 1, x1: 1}] + sage: print(reproducible_repr(I.variety())) + [{x1: 0, x2: 0, x3: 0, x4: 0, x5: 0, x6: 0}, {x1: 1, x2: 1, x3: 1, x4: 0, x5: 0, x6: 1}] sage: R = PolynomialRing(GF(2), 6, ['x%d'%(i+1) for i in range(6)], order='lex') sage: I = R.ideal( polys ) - sage: (I + sage.rings.ideal.FieldIdeal(R)).variety() - [{x2: 0, x5: 0, x4: 0, x1: 0, x6: 0, x3: 0}, {x2: 1, x5: 0, x4: 0, x1: 1, x6: 1, x3: 1}] + sage: v = (I + sage.rings.ideal.FieldIdeal(R)).variety() + sage: print(reproducible_repr(v)) + [{x1: 0, x2: 0, x3: 0, x4: 0, x5: 0, x6: 0}, {x1: 1, x2: 1, x3: 1, x4: 0, x5: 0, x6: 1}] Check that :trac:`13976` is fixed:: @@ -5148,13 +5142,21 @@ class BooleanPolynomialIdeal(MPolynomialIdeal): sage: sols[0][y] 1 + Make sure the result is a key converting dict, as discussed in + :trac:`9788` and consistent with + :meth:`sage.rings.polynomial.multi_polynomial_ideal.MPolynomialIdeal_singular_repr.variety`:: + + sage: sols[0]["y"] + 1 + """ + from sage.misc.converting_dict import KeyConvertingDict R_bool = self.ring() R = R_bool.cover_ring() I = R.ideal( [ R( f ) for f in self.groebner_basis() ] ) J = FieldIdeal(R) solutions = (I+J).variety(**kwds) - return [ { R_bool(var):val for var,val in s.iteritems() } for s in solutions ] + return [ KeyConvertingDict(R_bool, s) for s in solutions ] def reduce(self, f): diff --git a/src/sage/rings/polynomial/plural.pyx b/src/sage/rings/polynomial/plural.pyx index 56a572dc4fb..650918aa013 100644 --- a/src/sage/rings/polynomial/plural.pyx +++ b/src/sage/rings/polynomial/plural.pyx @@ -1385,9 +1385,6 @@ cdef class NCPolynomial_plural(RingElement): """ return unpickle_NCPolynomial_plural, (self._parent, self.dict()) - # you may have to replicate this boilerplate code in derived classes if you override - # __richcmp__. The python documentation at http://docs.python.org/api/type-structs.html - # explains how __richcmp__, __hash__, and __cmp__ are tied together. def __hash__(self): """ This hash incorporates the variable name in an effort to @@ -1411,7 +1408,7 @@ cdef class NCPolynomial_plural(RingElement): """ return self._hash_c() - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ Compare left and right and return -1, 0, and 1 for <,==, and > respectively. @@ -1431,9 +1428,6 @@ cdef class NCPolynomial_plural(RingElement): sage: y^2 > x False -## sage: (2/3*x^2 + 1/2*y + 3) > (2/3*x^2 + 1/4*y + 10) -# True - TESTS:: sage: A. = FreeAlgebra(QQ, 3) @@ -1458,22 +1452,7 @@ cdef class NCPolynomial_plural(RingElement): sage: (x+1) > x True - -# sage: f = 3/4*x^2*y + 1/2*x + 2/7 -# sage: f > f -# False -# sage: f < f -# False -# sage: f == f -# True - -# sage: P. = PolynomialRing(GF(127), order='degrevlex') -# sage: (66*x^2 + 23) > (66*x^2 + 2) -# True """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: if left is right: return 0 cdef poly *p = (left)._poly diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index ebe25afc471..0ec9ccc1560 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -41,7 +41,6 @@ TESTS:: cdef is_FractionField, is_RealField, is_ComplexField -cdef coerce_binop, generic_power, parent cdef ZZ, QQ, RR, CC, RDF, CDF import operator, copy, re @@ -73,8 +72,10 @@ from sage.rings.real_double import is_RealDoubleField, RDF from sage.rings.complex_double import is_ComplexDoubleField, CDF from sage.rings.real_mpfi import is_RealIntervalField -from sage.structure.element import RingElement, generic_power, parent -from sage.structure.element cimport Element, RingElement, ModuleElement, MonoidElement +from sage.structure.element import generic_power +from sage.structure.element cimport parent_c as parent +from sage.structure.element cimport (Element, RingElement, + ModuleElement, MonoidElement, coercion_model) from sage.rings.rational_field import QQ, is_RationalField from sage.rings.integer_ring import ZZ, is_IntegerRing @@ -924,9 +925,6 @@ cdef class Polynomial(CommutativeAlgebraElement): if c: return c return 0 - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - def __nonzero__(self): """ EXAMPLES:: @@ -971,9 +969,6 @@ cdef class Polynomial(CommutativeAlgebraElement): """ return (self.parent(), tuple(self)) - # you may have to replicate this boilerplate code in derived classes if you override - # __richcmp__. The python documentation at http://docs.python.org/api/type-structs.html - # explains how __richcmp__, __hash__, and __cmp__ are tied together. def __hash__(self): return self._hash_c() @@ -3235,7 +3230,7 @@ cdef class Polynomial(CommutativeAlgebraElement): # calling the coercion model bin_op is much more accurate than using the # true division (which is bypassed by polynomials). But it does not work # in all cases!! - cm = sage.structure.element.get_coercion_model() + cm = coercion_model try: S = cm.bin_op(R.one(), ZZ.one(), operator.div).parent() Q = S.base_ring() @@ -4576,7 +4571,6 @@ cdef class Polynomial(CommutativeAlgebraElement): # sylvester_matrix() in multi_polynomial.pyx. if self.parent() != right.parent(): - coercion_model = sage.structure.element.get_coercion_model() a, b = coercion_model.canonical_coercion(self,right) variable = a.parent()(self.variables()[0]) #We add the variable to cover the case that right is a multivariate @@ -7932,15 +7926,9 @@ cdef class Polynomial_generic_dense(Polynomial): del x[n] n -= 1 - # you may have to replicate this boilerplate code in derived classes if you override - # __richcmp__. The python documentation at http://docs.python.org/api/type-structs.html - # explains how __richcmp__, __hash__, and __cmp__ are tied together. def __hash__(self): return self._hash_c() - def __richcmp__(left, right, int op): - return (left)._richcmp(right, op) - def __getitem__(self, n): """ EXAMPLES:: diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index e0d2d4f4ff6..9c469c9eb46 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -268,7 +268,7 @@ def __init__(self, base_ring, name=None, sparse=False, element_class=None, categ sage: category(ZZ['x']) Join of Category of unique factorization domains and Category of commutative algebras over - (euclidean domains and infinite enumerated sets) + (euclidean domains and infinite enumerated sets and metric spaces) sage: category(GF(7)['x']) Join of Category of euclidean domains and Category of commutative algebras over (finite fields and diff --git a/src/sage/rings/polynomial/polynomial_template.pxi b/src/sage/rings/polynomial/polynomial_template.pxi index d2aee68f334..57f4550b27d 100644 --- a/src/sage/rings/polynomial/polynomial_template.pxi +++ b/src/sage/rings/polynomial/polynomial_template.pxi @@ -529,7 +529,7 @@ cdef class Polynomial_template(Polynomial): """ return not celement_is_zero(&self.x, (self)._cparent) - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ EXAMPLE:: @@ -541,14 +541,6 @@ cdef class Polynomial_template(Polynomial): sage: x > 1 True """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - EXAMPLE:: - - sage: P. = GF(2)[] - """ return celement_cmp(&(left).x, &(right).x, (left)._cparent) def __hash__(self): diff --git a/src/sage/rings/power_series_ring.py b/src/sage/rings/power_series_ring.py index e11fdf7f779..3dc1e66c7a7 100644 --- a/src/sage/rings/power_series_ring.py +++ b/src/sage/rings/power_series_ring.py @@ -499,6 +499,15 @@ def __init__(self, base_ring, name=None, default_prec=None, sparse=False, sage: R.category() Category of complete discrete valuation rings sage: TestSuite(R).run() + + It is checked that the default precision is non-negative + (see :trac:`19409`):: + + sage: PowerSeriesRing(ZZ, 'x', default_prec=-5) + Traceback (most recent call last): + ... + ValueError: default_prec (= -5) must be non-negative + """ R = PolynomialRing(base_ring, name, sparse=sparse) self.__poly_ring = R @@ -506,6 +515,9 @@ def __init__(self, base_ring, name=None, default_prec=None, sparse=False, if default_prec is None: from sage.misc.defaults import series_precision default_prec = series_precision() + elif default_prec < 0: + raise ValueError("default_prec (= %s) must be non-negative" + % default_prec) self.__params = (base_ring, name, default_prec, sparse) if use_lazy_mpoly_ring and (is_MPolynomialRing(base_ring) or \ @@ -708,9 +720,21 @@ def _element_constructor_(self, f, prec=infinity, check=True): sage: P(1/q) Traceback (most recent call last): ... - ArithmeticError: self is a not a power series + TypeError: self is not a power series + + It is checked that the precision is non-negative + (see :trac:`19409`):: + + sage: PowerSeriesRing(ZZ, 'x')(1, prec=-5) + Traceback (most recent call last): + ... + ValueError: prec (= -5) must be non-negative """ + if prec is not infinity: + prec = integer.Integer(prec) + if prec < 0: + raise ValueError("prec (= %s) must be non-negative" % prec) if isinstance(f, power_series_ring_element.PowerSeries) and f.parent() is self: if prec >= f.prec(): return f diff --git a/src/sage/rings/power_series_ring_element.pyx b/src/sage/rings/power_series_ring_element.pyx index ae25cd36fea..a991d433559 100644 --- a/src/sage/rings/power_series_ring_element.pyx +++ b/src/sage/rings/power_series_ring_element.pyx @@ -165,8 +165,6 @@ cdef class PowerSeries(AlgebraElement): """ AlgebraElement.__init__(self, parent) self.__is_gen = is_gen - if not (prec is infinity): - prec = int(prec) self._prec = prec def __hash__(self): @@ -1025,6 +1023,12 @@ cdef class PowerSeries(AlgebraElement): ... ZeroDivisionError: leading coefficient must be a unit + A test for the case where the precision is 0:: + + sage: R. = PowerSeriesRing(ZZ, default_prec=0) + sage: ~(1+x) + O(x^0) + AUTHORS: - David Harvey (2006-09-09): changed to use Newton's method @@ -1050,6 +1054,8 @@ cdef class PowerSeries(AlgebraElement): if prec is infinity: return self._parent(first_coeff, prec=prec) + elif not prec: + return self._parent(0, prec=0) A = self.truncate() R = A.parent() # R is the corresponding polynomial ring diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 8fc07afb441..cd3a99f14f9 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -449,7 +449,7 @@ ....: def convert_test(v): ....: try: ....: return ty(v) - ....: except ValueError: + ....: except (TypeError, ValueError): ....: return None ....: return [convert_test(_) for _ in all_vals] sage: convert_test_all(float) @@ -3807,7 +3807,8 @@ def degree(self): def interval_fast(self, field): r""" - Given a ``RealIntervalField``, compute the value of this number + Given a :class:`RealIntervalField` or + :class:`ComplexIntervalField`, compute the value of this number using interval arithmetic of at least the precision of the field, and return the value in that field. (More precision may be used in the computation.) The returned interval may be arbitrarily @@ -3827,15 +3828,11 @@ def interval_fast(self, field): sage: x.interval_fast(RIF) Traceback (most recent call last): ... - TypeError: Unable to convert number to real interval. + TypeError: unable to convert 0.7071067811865475244? + 0.7071067811865475244?*I to real interval """ - if field.prec() == self._value.prec(): - return field(self._value) - elif field.prec() > self._value.prec(): + while self._value.prec() < field.prec(): self._more_precision() - return self.interval_fast(field) - else: - return field(self._value) + return field(self._value) def interval_diameter(self, diam): """ @@ -3887,11 +3884,22 @@ def interval(self, field): 0.8412535328311811689? + 0.540640817455597582?*I sage: x.interval(CIF64) 0.8412535328311811689? + 0.5406408174555975822?*I + + The following implicitly use this method:: + + sage: RIF(AA(5).sqrt()) + 2.236067977499790? + sage: AA(-5).sqrt().interval(RIF) + Traceback (most recent call last): + ... + TypeError: unable to convert 2.236067977499789697?*I to real interval """ target = RR(1.0) >> field.prec() val = self.interval_diameter(target) return field(val) + _real_mpfi_ = interval + def radical_expression(self): r""" Attempt to obtain a symbolic expression using radicals. If no diff --git a/src/sage/rings/quotient_ring.py b/src/sage/rings/quotient_ring.py index f268c22cf0b..343b72fe3a9 100644 --- a/src/sage/rings/quotient_ring.py +++ b/src/sage/rings/quotient_ring.py @@ -29,15 +29,16 @@ ``x - I.reduce(x) in I``). Here is a toy example:: sage: from sage.rings.noncommutative_ideals import Ideal_nc + sage: from itertools import product sage: class PowerIdeal(Ideal_nc): - ... def __init__(self, R, n): - ... self._power = n - ... self._power = n - ... Ideal_nc.__init__(self,R,[R.prod(m) for m in CartesianProduct(*[R.gens()]*n)]) - ... def reduce(self,x): - ... R = self.ring() - ... return add([c*R(m) for m,c in x if len(m) = FreeAlgebra(QQ, 3) sage: I3 = PowerIdeal(F,3); I3 Twosided Ideal (x^3, x^2*y, x^2*z, x*y*x, x*y^2, x*y*z, x*z*x, x*z*y, @@ -83,15 +84,16 @@ letterplace wrapper allows to provide the above toy example more easily:: + sage: from itertools import product sage: F. = FreeAlgebra(QQ, implementation='letterplace') - sage: Q3 = F.quo(F*[F.prod(m) for m in CartesianProduct(*[F.gens()]*3)]*F) + sage: Q3 = F.quo(F*[F.prod(m) for m in product(F.gens(), repeat=3)]*F) sage: Q3 Quotient of Free Associative Unital Algebra on 3 generators (x, y, z) over Rational Field by the ideal (x*x*x, x*x*y, x*x*z, x*y*x, x*y*y, x*y*z, x*z*x, x*z*y, x*z*z, y*x*x, y*x*y, y*x*z, y*y*x, y*y*y, y*y*z, y*z*x, y*z*y, y*z*z, z*x*x, z*x*y, z*x*z, z*y*x, z*y*y, z*y*z, z*z*x, z*z*y, z*z*z) sage: Q3.0*Q3.1-Q3.1*Q3.0 xbar*ybar - ybar*xbar sage: Q3.0*(Q3.1*Q3.2)-(Q3.1*Q3.2)*Q3.0 0 - sage: Q2 = F.quo(F*[F.prod(m) for m in CartesianProduct(*[F.gens()]*2)]*F) + sage: Q2 = F.quo(F*[F.prod(m) for m in product(F.gens(), repeat=2)]*F) sage: Q2.is_commutative() True diff --git a/src/sage/rings/quotient_ring_element.py b/src/sage/rings/quotient_ring_element.py index 9f681331d55..853d267a924 100644 --- a/src/sage/rings/quotient_ring_element.py +++ b/src/sage/rings/quotient_ring_element.py @@ -590,6 +590,18 @@ def __float__(self): """ return float(self.lift()) + def __hash__(self): + r""" + TESTS:: + + sage: R. = QQ[] + sage: S. = R.quo(x^2 + y^2) + sage: hash(a) + 15360174650385711 # 64-bit + 1505322287 # 32-bit + """ + return hash(self.__rep) + def __cmp__(self, other): """ EXAMPLES:: diff --git a/src/sage/rings/rational_field.py b/src/sage/rings/rational_field.py index 59a757424eb..4a041a848bb 100644 --- a/src/sage/rings/rational_field.py +++ b/src/sage/rings/rational_field.py @@ -154,7 +154,7 @@ def __init__(self): sage: Q.is_field() True sage: Q.category() - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces sage: Q.zeta() -1 @@ -215,7 +215,7 @@ def __init__(self): ('x',) """ from sage.categories.basic import QuotientFields - ParentWithGens.__init__(self, self, category = QuotientFields()) + ParentWithGens.__init__(self, self, category=QuotientFields().Metric()) self._assign_names(('x',),normalize=False) # ??? self._populate_coercion_lists_(element_constructor=rational.Rational, init_no_parent=True) diff --git a/src/sage/rings/real_arb.pyx b/src/sage/rings/real_arb.pyx index 7c7722dae9e..9558479b335 100644 --- a/src/sage/rings/real_arb.pyx +++ b/src/sage/rings/real_arb.pyx @@ -5,11 +5,9 @@ AUTHORS: - Clemens Heuberger (2014-10-21): Initial version. -This is a binding to the optional `Arb library `_; it +This is a binding to the `Arb library `_; it may be useful to refer to its documentation for more details. -You may have to run ``sage -i arb`` to use the arb library. - Parts of the documentation for this module are copied or adapted from Arb's own documentation, licenced under the GNU General Public License version 2, or later. @@ -28,75 +26,75 @@ Comparison Two elements are equal if and only if they are the same object or if both are exact and equal:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: a = RBF(1) # optional - arb - sage: b = RBF(1) # optional - arb - sage: a is b # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RBF = RealBallField() + sage: a = RBF(1) + sage: b = RBF(1) + sage: a is b False - sage: a == b # optional - arb + sage: a == b True - sage: a = RBF(1/3) # optional - arb - sage: b = RBF(1/3) # optional - arb - sage: a.is_exact() # optional - arb + sage: a = RBF(1/3) + sage: b = RBF(1/3) + sage: a.is_exact() False - sage: b.is_exact() # optional - arb + sage: b.is_exact() False - sage: a is b # optional - arb + sage: a is b False - sage: a == b # optional - arb + sage: a == b False A ball is non-zero if and only if it does not contain zero. :: - sage: a = RBF(RIF(-0.5, 0.5)) # optional - arb - sage: bool(a) # optional - arb + sage: a = RBF(RIF(-0.5, 0.5)) + sage: bool(a) False - sage: a != 0 # optional - arb + sage: a != 0 False - sage: b = RBF(1/3) # optional - arb - sage: bool(b) # optional - arb + sage: b = RBF(1/3) + sage: bool(b) True - sage: b != 0 # optional - arb + sage: b != 0 True A ball ``left`` is less than a ball ``right`` if all elements of ``left`` are less than all elements of ``right``. :: - sage: a = RBF(RIF(1, 2)) # optional - arb - sage: b = RBF(RIF(3, 4)) # optional - arb - sage: a < b # optional - arb + sage: a = RBF(RIF(1, 2)) + sage: b = RBF(RIF(3, 4)) + sage: a < b True - sage: a <= b # optional - arb + sage: a <= b True - sage: a > b # optional - arb + sage: a > b False - sage: a >= b # optional - arb + sage: a >= b False - sage: a = RBF(RIF(1, 3)) # optional - arb - sage: b = RBF(RIF(2, 4)) # optional - arb - sage: a < b # optional - arb + sage: a = RBF(RIF(1, 3)) + sage: b = RBF(RIF(2, 4)) + sage: a < b False - sage: a <= b # optional - arb + sage: a <= b False - sage: a > b # optional - arb + sage: a > b False - sage: a >= b # optional - arb + sage: a >= b False Comparisons with Sage symbolic infinities work with some limitations:: - sage: -infinity < RBF(1) < +infinity # optional - arb + sage: -infinity < RBF(1) < +infinity True - sage: -infinity < RBF(infinity) # optional - arb + sage: -infinity < RBF(infinity) True - sage: RBF(infinity) < infinity # optional - arb + sage: RBF(infinity) < infinity False - sage: RBF(NaN) < infinity # optional - arb + sage: RBF(NaN) < infinity Traceback (most recent call last): ... ValueError: infinite but not with +/- phase - sage: 1/RBF(0) <= infinity # optional - arb + sage: 1/RBF(0) <= infinity Traceback (most recent call last): ... ValueError: infinite but not with +/- phase @@ -104,15 +102,15 @@ Comparisons with Sage symbolic infinities work with some limitations:: Comparisons between elements of real ball fields, however, support special values and should be preferred:: - sage: RBF(NaN) < RBF(infinity) # optional - arb + sage: RBF(NaN) < RBF(infinity) False - sage: 1/RBF(0) <= RBF(infinity) # optional - arb + sage: 1/RBF(0) <= RBF(infinity) True TESTS:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: (RBF(pi) * identity_matrix(QQ, 3)).parent() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: (RBF(pi) * identity_matrix(QQ, 3)).parent() Full MatrixSpace of 3 by 3 dense matrices over Real ball field with 53 bits precision Classes and Methods @@ -145,7 +143,7 @@ cimport sage.structure.element from sage.libs.arb.arb cimport * from sage.libs.arb.arf cimport arf_t, arf_init, arf_get_mpfr, arf_set_mpfr, arf_clear, arf_set_mag, arf_set -from sage.libs.arb.arf cimport arf_equal, arf_is_nan, arf_is_neg_inf, arf_is_pos_inf, arf_get_mag +from sage.libs.arb.arf cimport arf_equal, arf_is_nan, arf_is_neg_inf, arf_is_pos_inf, arf_get_mag, ARF_PREC_EXACT from sage.libs.arb.mag cimport mag_t, mag_init, mag_clear, mag_add, mag_set_d, MAG_BITS, mag_is_inf, mag_is_finite, mag_zero from sage.libs.flint.flint cimport flint_free from sage.libs.flint.fmpz cimport fmpz_t, fmpz_init, fmpz_get_mpz, fmpz_set_mpz, fmpz_clear @@ -203,8 +201,8 @@ cdef int arb_to_mpfi(mpfi_t target, arb_t source, const long precision) except - EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RIF(RBF(2)**(2**100)) # optional - arb, indirect doctest + sage: from sage.rings.real_arb import RBF + sage: RIF(RBF(2)**(2**100)) # indirect doctest Traceback (most recent call last): ... ArithmeticError: Error converting arb to mpfi. Overflow? @@ -237,38 +235,38 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb; indirect doctest - sage: RBF(1) # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RBF = RealBallField() # indirect doctest + sage: RBF(1) 1.000000000000000 :: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: (1/2*RBF(1)) + AA(sqrt(2)) - 1 + polygen(QQ, x) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: (1/2*RBF(1)) + AA(sqrt(2)) - 1 + polygen(QQ, x) x + [0.914213562373095 +/- 4.10e-16] TESTS:: - sage: RBF.bracket(RBF(1/2), RBF(1/3)) # optional - arb + sage: RBF.bracket(RBF(1/2), RBF(1/3)) [+/- 5.56e-17] - sage: RBF.cardinality() # optional - arb + sage: RBF.cardinality() +Infinity - sage: RBF.cartesian_product(QQ).an_element()**2 # optional - arb + sage: RBF.cartesian_product(QQ).an_element()**2 ([1.440000000000000 +/- 4.98e-16], 1/4) - sage: RBF.coerce_embedding() is None # optional - arb + sage: RBF.coerce_embedding() is None True - sage: loads(dumps(RBF)) is RBF # optional - arb + sage: loads(dumps(RBF)) is RBF True - sage: RBF['x'].gens_dict_recursive() # optional - arb + sage: RBF['x'].gens_dict_recursive() {'x': x} - sage: RBF.is_finite() # optional - arb + sage: RBF.is_finite() False - sage: RBF.is_zero() # optional - arb + sage: RBF.is_zero() False - sage: RBF.one() # optional - arb + sage: RBF.one() 1.000000000000000 - sage: RBF.zero() # optional - arb + sage: RBF.zero() 0 """ Element = RealBall @@ -280,8 +278,8 @@ class RealBallField(UniqueRepresentation, Parent): TESTS:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField(53) is RealBallField() # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RealBallField(53) is RealBallField() True """ return super(RealBallField, cls).__classcall__(cls, precision, category) @@ -296,15 +294,15 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: RBF(1) # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RBF = RealBallField() + sage: RBF(1) 1.000000000000000 - sage: RealBallField(0) # optional - arb + sage: RealBallField(0) Traceback (most recent call last): ... ValueError: Precision must be at least 2. - sage: RealBallField(1) # optional - arb + sage: RealBallField(1) Traceback (most recent call last): ... ValueError: Precision must be at least 2. @@ -316,6 +314,9 @@ class RealBallField(UniqueRepresentation, Parent): # FIXME: RBF is not even associative, but CompletionFunctor only works with rings. category=category or sage.categories.rings.Rings().Infinite()) self._prec = precision + from sage.rings.qqbar import AA + from sage.rings.real_lazy import RLF + self._populate_coercion_lists_([ZZ, QQ, AA, RLF]) def _repr_(self): r""" @@ -323,10 +324,10 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField() # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RealBallField() Real ball field with 53 bits precision - sage: RealBallField(106) # optional - arb + sage: RealBallField(106) Real ball field with 106 bits precision """ return "Real ball field with {} bits precision".format(self._prec) @@ -342,24 +343,20 @@ class RealBallField(UniqueRepresentation, Parent): TESTS:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField().has_coerce_map_from(RealBallField(54)) # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RealBallField().has_coerce_map_from(RealBallField(54)) True - sage: RealBallField().has_coerce_map_from(RealBallField(52)) # optional - arb + sage: RealBallField().has_coerce_map_from(RealBallField(52)) False - sage: RealBallField().has_coerce_map_from(RIF) # optional - arb + sage: RealBallField().has_coerce_map_from(RIF) False - sage: RealBallField().has_coerce_map_from(SR) # optional - arb + sage: RealBallField().has_coerce_map_from(SR) False - sage: RealBallField().has_coerce_map_from(RR) # optional - arb + sage: RealBallField().has_coerce_map_from(RR) False """ - from sage.rings.qqbar import AA - from sage.rings.real_lazy import RLF if isinstance(other, RealBallField): return (other._prec >= self._prec) - elif (other is ZZ) or (other is QQ) or (other is AA) or (other is RLF): - return True else: return False @@ -368,53 +365,51 @@ class RealBallField(UniqueRepresentation, Parent): Convert ``mid`` to an element of this real ball field, perhaps non-canonically. - In addition to the inputs supported by - :meth:`ElementConstructor.__init__`, + In addition to the inputs supported by :meth:`RealBall.__init__`, anything that is convertible to a real interval can also be used to construct a real ball:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(RIF(0, 1)) # optional - arb; indirect doctest + sage: from sage.rings.real_arb import RBF + sage: RBF(RIF(0, 1)) # indirect doctest [+/- 1.01] - sage: RBF(1) # optional - arb + sage: RBF(1) 1.000000000000000 - sage: RBF(x) # optional - arb + sage: RBF(x) Traceback (most recent call last): ... - TypeError: unable to convert x to a RealIntervalFieldElement + TypeError: unable to convert x to a RealBall Various symbolic constants can be converted without going through real intervals. (This is faster and yields tighter error bounds.) :: - sage: RBF(e) # optional - arb + sage: RBF(e) [2.718281828459045 +/- 5.35e-16] - sage: RBF(pi) # optional - arb + sage: RBF(pi) [3.141592653589793 +/- 5.61e-16] """ try: return self.element_class(self, mid, rad) except TypeError: pass - try: return self.element_class(self, mid.pyobject(), rad) except (AttributeError, TypeError): pass - try: mid = RealIntervalField(self._prec)(mid) + return self.element_class(self, mid, rad) except TypeError: - raise TypeError("unable to convert {} to a RealIntervalFieldElement".format(mid)) - return self.element_class(self, mid, rad) + pass + raise TypeError("unable to convert {} to a RealBall".format(mid)) def gens(self): r""" EXAMPLE:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.gens() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF.gens() (1.000000000000000,) - sage: RBF.gens_dict() # optional - arb + sage: RBF.gens_dict() {'1.000000000000000': 1.000000000000000} """ return (self.one(),) @@ -425,8 +420,8 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLE:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.base() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF.base() Real ball field with 53 bits precision """ return self @@ -437,8 +432,8 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLE:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.base_ring() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF.base_ring() Real ball field with 53 bits precision """ return self @@ -450,12 +445,12 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField(42) # optional - arb - sage: functor, base = RBF.construction() # optional - arb - sage: functor, base # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RBF = RealBallField(42) + sage: functor, base = RBF.construction() + sage: functor, base (Completion[+Infinity], Rational Field) - sage: functor(base) is RBF # optional - arb + sage: functor(base) is RBF True """ from sage.categories.pushout import CompletionFunctor @@ -464,14 +459,35 @@ class RealBallField(UniqueRepresentation, Parent): {'type': 'Ball'}) return functor, QQ + def complex_field(self): + """ + Return the complex ball field with the same precision. + + EXAMPLES:: + + sage: from sage.rings.real_arb import RBF, RealBallField + sage: from sage.rings.complex_ball_acb import ComplexBallField + doctest:...: FutureWarning: This class/method/function is marked as experimental. + It, its functionality or its interface might change without a formal deprecation. + See http://trac.sagemath.org/17218 for details. + sage: RBF.complex_field() + Complex ball field with 53 bits precision + sage: RealBallField(3).algebraic_closure() + Complex ball field with 3 bits precision + """ + from sage.rings.complex_ball_acb import ComplexBallField + return ComplexBallField(self._prec) + + algebraic_closure = complex_field + def precision(self): """ Return the bit precision used for operations on elements of this field. EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField().precision() # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RealBallField().precision() 53 """ return self._prec @@ -482,8 +498,8 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField().is_exact() # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RealBallField().is_exact() False """ return False @@ -494,8 +510,8 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField().characteristic() # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RealBallField().characteristic() 0 """ return 0 @@ -507,8 +523,8 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.some_elements() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF.some_elements() [1.000000000000000, [0.3333333333333333 +/- 7.04e-17], [-4.733045976388941e+363922934236666733021124 +/- 3.46e+363922934236666733021108], @@ -534,17 +550,17 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.sinpi(1) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF.sinpi(1) 0 - sage: RBF.sinpi(1/3) # optional - arb + sage: RBF.sinpi(1/3) [0.866025403784439 +/- 5.15e-16] - sage: RBF.sinpi(1 + 2^(-100)) # optional - arb + sage: RBF.sinpi(1 + 2^(-100)) [-2.478279624546525e-30 +/- 5.90e-46] TESTS:: - sage: RBF.sinpi(RLF(sqrt(2))) # optional - arb + sage: RBF.sinpi(RLF(sqrt(2))) [-0.96390253284988 +/- 4.11e-15] """ cdef RealBall res, x_as_ball @@ -581,15 +597,15 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.cospi(1) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF.cospi(1) -1.000000000000000 - sage: RBF.cospi(1/3) # optional - arb + sage: RBF.cospi(1/3) 0.5000000000000000 TESTS:: - sage: RBF.cospi(RLF(sqrt(2))) # optional - arb + sage: RBF.cospi(RLF(sqrt(2))) [-0.26625534204142 +/- 5.38e-15] """ cdef RealBall res, x_as_ball @@ -626,19 +642,19 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.gamma(5) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF.gamma(5) 24.00000000000000 - sage: RBF.gamma(10**20) # optional - arb + sage: RBF.gamma(10**20) [+/- 5.92e+1956570551809674821757] - sage: RBF.gamma(1/3) # optional - arb + sage: RBF.gamma(1/3) [2.678938534707747 +/- 8.99e-16] - sage: RBF.gamma(-5) # optional - arb + sage: RBF.gamma(-5) nan TESTS:: - sage: RBF.gamma(RLF(pi)) # optional - arb + sage: RBF.gamma(RLF(pi)) [2.2880377953400 +/- 4.29e-14] """ cdef RealBall res @@ -686,12 +702,12 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF.zeta(3) # abs tol 5e-16, optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF.zeta(3) # abs tol 5e-16 [1.202056903159594 +/- 2.87e-16] - sage: RBF.zeta(1) # optional - arb + sage: RBF.zeta(1) nan - sage: RBF.zeta(1/2) # optional - arb + sage: RBF.zeta(1/2) [-1.460354508809587 +/- 1.94e-16] """ cdef RealBall res @@ -714,25 +730,25 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: [RBF.bernoulli(n) for n in range(4)] # optional - arb + sage: from sage.rings.real_arb import RBF + sage: [RBF.bernoulli(n) for n in range(4)] [1.000000000000000, -0.5000000000000000, [0.1666666666666667 +/- 7.04e-17], 0] - sage: RBF.bernoulli(2**20) # optional - arb + sage: RBF.bernoulli(2**20) [-1.823002872104961e+5020717 +/- 7.16e+5020701] - sage: RBF.bernoulli(2**1000) # optional - arb + sage: RBF.bernoulli(2**1000) Traceback (most recent call last): ... ValueError: argument too large TESTS:: - sage: RBF.bernoulli(2r) # optional - arb + sage: RBF.bernoulli(2r) [0.1666666666666667 +/- 7.04e-17] - sage: RBF.bernoulli(2/3) # optional - arb + sage: RBF.bernoulli(2/3) Traceback (most recent call last): ... TypeError: no canonical coercion from Rational Field to Integer Ring - sage: RBF.bernoulli(-1) # optional - arb + sage: RBF.bernoulli(-1) Traceback (most recent call last): ... ValueError: expected a nonnegative index @@ -757,8 +773,8 @@ class RealBallField(UniqueRepresentation, Parent): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: [RBF.fibonacci(n) for n in xrange(7)] # optional - arb + sage: from sage.rings.real_arb import RBF + sage: [RBF.fibonacci(n) for n in xrange(7)] [0, 1.000000000000000, 1.000000000000000, @@ -766,9 +782,9 @@ class RealBallField(UniqueRepresentation, Parent): 3.000000000000000, 5.000000000000000, 8.000000000000000] - sage: RBF.fibonacci(-2) # optional - arb + sage: RBF.fibonacci(-2) -1.000000000000000 - sage: RBF.fibonacci(10**20) # optional - arb + sage: RBF.fibonacci(10**20) [3.78202087472056e+20898764024997873376 +/- 4.01e+20898764024997873361] """ cdef fmpz_t tmpz @@ -784,6 +800,27 @@ class RealBallField(UniqueRepresentation, Parent): fmpz_clear(tmpz) return res + def maximal_accuracy(self): + r""" + Return the relative accuracy of exact elements measured in bits. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: from sage.rings.real_arb import RBF + sage: RBF.maximal_accuracy() + 9223372036854775807 # 64-bit + 2147483647 # 32-bit + + .. seealso:: + + :meth:`RealBall.accuracy` + """ + return ARF_PREC_EXACT + cdef inline bint _do_sig(long prec): """ @@ -791,9 +828,9 @@ cdef inline bint _do_sig(long prec): TESTS:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: _ = RealBallField()(1).psi() # optional - arb; indirect doctest - sage: _ = RealBallField(1500)(1).psi() # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: _ = RealBallField()(1).psi() # indirect doctest + sage: _ = RealBallField(1500)(1).psi() """ return (prec > 1000) @@ -807,12 +844,12 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: a = RealBallField()(RIF(1)) # optional - arb; indirect doctest - sage: b = a.psi() # optional - arb - sage: b # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: a = RealBallField()(RIF(1)) # indirect doctest + sage: b = a.psi() + sage: b [-0.577215664901533 +/- 3.85e-16] - sage: RIF(b) # optional - arb + sage: RIF(b) -0.577215664901533? """ @@ -822,8 +859,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField()(RIF(1)) # optional - arb; indirect doctest + sage: from sage.rings.real_arb import RealBallField + sage: RealBallField()(RIF(1)) # indirect doctest 1.000000000000000 """ arb_init(self.value) @@ -834,9 +871,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: a = RealBallField()(RIF(1)) # optional - arb; indirect doctest - sage: del a # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: a = RealBallField()(RIF(1)) # indirect doctest + sage: del a """ arb_clear(self.value) @@ -856,38 +893,40 @@ cdef class RealBall(RingElement): floating-point, the radius is adjusted to account for the roundoff error. + .. SEEALSO:: :meth:`RealBallField._element_constructor_` + EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: RBF() # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RBF = RealBallField() + sage: RBF() 0 One can create exact real balls using elements of various exact parents, or using floating-point numbers:: - sage: RBF(3) # optional - arb + sage: RBF(3) 3.000000000000000 - sage: RBF(3r) # optional - arb + sage: RBF(3r) 3.000000000000000 - sage: RBF(1/3) # optional - arb + sage: RBF(1/3) [0.3333333333333333 +/- 7.04e-17] - sage: RBF(3.14) # optional - arb + sage: RBF(3.14) [3.140000000000000 +/- 1.25e-16] :: - sage: RBF(3, 0.125) # optional - arb + sage: RBF(3, 0.125) [3e+0 +/- 0.126] - sage: RBF(pi, 0.125r) # optional - arb + sage: RBF(pi, 0.125r) [3e+0 +/- 0.267] Note that integers and floating-point numbers are ''not'' rounded to the parent's precision:: - sage: b = RBF(11111111111111111111111111111111111111111111111); b # optional - arb + sage: b = RBF(11111111111111111111111111111111111111111111111); b [1.111111111111111e+46 +/- 1.12e+30] - sage: b.mid().exact_rational() # optional - arb + sage: b.mid().exact_rational() 11111111111111111111111111111111111111111111111 Similarly, converting a real ball from one real ball field to another @@ -895,35 +934,35 @@ cdef class RealBall(RingElement): the precision of operations involving it, not the actual representation of its center:: - sage: RBF100 = RealBallField(100) # optional - arb - sage: b100 = RBF100(1/3); b100 # optional - arb + sage: RBF100 = RealBallField(100) + sage: b100 = RBF100(1/3); b100 [0.333333333333333333333333333333 +/- 4.65e-31] - sage: b53 = RBF(b100); b53 # optional - arb + sage: b53 = RBF(b100); b53 [0.3333333333333333 +/- 3.34e-17] - sage: RBF100(b53) # optional - arb + sage: RBF100(b53) [0.333333333333333333333333333333 +/- 4.65e-31] Special values are supported:: - sage: RBF(oo).mid(), RBF(-oo).mid(), RBF(unsigned_infinity).mid() # optional - arb + sage: RBF(oo).mid(), RBF(-oo).mid(), RBF(unsigned_infinity).mid() (+infinity, -infinity, 0.000000000000000) - sage: RBF(NaN) # optional - arb + sage: RBF(NaN) nan TESTS:: - sage: from sage.rings.real_arb import RealBall # optional - arb - sage: RealBall(RBF, sage.symbolic.constants.Pi()) # abs tol 1e-16, optional - arb + sage: from sage.rings.real_arb import RealBall + sage: RealBall(RBF, sage.symbolic.constants.Pi()) # abs tol 1e-16 [3.141592653589793 +/- 5.62e-16] - sage: RealBall(RBF, sage.symbolic.constants.Log2()) # abs tol 1e-16, optional - arb + sage: RealBall(RBF, sage.symbolic.constants.Log2()) # abs tol 1e-16 [0.693147180559945 +/- 4.06e-16] - sage: RealBall(RBF, sage.symbolic.constants.Catalan()) # optional - arb + sage: RealBall(RBF, sage.symbolic.constants.Catalan()) [0.915965594177219 +/- 1.23e-16] - sage: RealBall(RBF, sage.symbolic.constants.Khinchin()) # optional - arb + sage: RealBall(RBF, sage.symbolic.constants.Khinchin()) [2.685452001065306 +/- 6.82e-16] - sage: RealBall(RBF, sage.symbolic.constants.Glaisher()) # optional - arb + sage: RealBall(RBF, sage.symbolic.constants.Glaisher()) [1.282427129100623 +/- 6.02e-16] - sage: RealBall(RBF, sage.symbolic.constants.e) # optional - arb + sage: RealBall(RBF, sage.symbolic.constants.e) [2.718281828459045 +/- 5.35e-16] """ cdef fmpz_t tmpz @@ -1018,8 +1057,8 @@ cdef class RealBall(RingElement): TESTS:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField()(2)**2 # indirect doctest, optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RealBallField()(2)**2 # indirect doctest 4.000000000000000 """ @@ -1038,8 +1077,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField()(RIF(1.9, 2)) # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RealBallField()(RIF(1.9, 2)) [2e+0 +/- 0.101] """ cdef char* c_result @@ -1065,9 +1104,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: a = RealBallField()(RIF(2)) # optional - arb - sage: RIF(a) # optional - arb, indirect doctest + sage: from sage.rings.real_arb import RealBallField + sage: a = RealBallField()(RIF(2)) + sage: RIF(a) # indirect doctest 2 """ cdef RealIntervalFieldElement result @@ -1081,14 +1120,14 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: ZZ(RBF(1, rad=0.1r)) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: ZZ(RBF(1, rad=0.1r)) 1 - sage: ZZ(RBF(1, rad=1.0r)) # optional - arb + sage: ZZ(RBF(1, rad=1.0r)) Traceback (most recent call last): ... ValueError: [+/- 2.01] does not contain a unique integer - sage: ZZ(RBF(pi)) # optional - arb + sage: ZZ(RBF(pi)) Traceback (most recent call last): ... ValueError: [3.141592653589793 +/- 5.61e-16] does not contain a unique integer @@ -1107,6 +1146,26 @@ cdef class RealBall(RingElement): fmpz_clear(tmp) return res + def _rational_(self): + """ + Check that this ball contains a single rational number and return that + number. + + EXAMPLES:: + + sage: from sage.rings.real_arb import RBF + sage: QQ(RBF(123456/2^12)) + 1929/64 + sage: QQ(RBF(1/3)) + Traceback (most recent call last): + ... + ValueError: [0.3333333333333333 +/- 7.04e-17] does not contain a unique rational number + """ + if arb_is_exact(self.value): + return self.mid().exact_rational() + else: + raise ValueError("{} does not contain a unique rational number".format(self)) + def _mpfr_(self, RealField_class field): """ Convert this real ball to a real number. @@ -1116,31 +1175,31 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: mypi = RBF(pi) # optional - arb - sage: RR(mypi) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: mypi = RBF(pi) + sage: RR(mypi) 3.14159265358979 - sage: Reals(rnd='RNDU')(mypi) # optional - arb + sage: Reals(rnd='RNDU')(mypi) 3.14159265358980 - sage: Reals(rnd='RNDD')(mypi) # optional - arb + sage: Reals(rnd='RNDD')(mypi) 3.14159265358979 - sage: Reals(rnd='RNDZ')(mypi) # optional - arb + sage: Reals(rnd='RNDZ')(mypi) 3.14159265358979 - sage: Reals(rnd='RNDZ')(-mypi) # optional - arb + sage: Reals(rnd='RNDZ')(-mypi) -3.14159265358979 - sage: Reals(rnd='RNDU')(-mypi) # optional - arb + sage: Reals(rnd='RNDU')(-mypi) -3.14159265358979 :: - sage: b = RBF(RIF(-1/2, 1)) # optional - arb - sage: RR(b) # optional - arb + sage: b = RBF(RIF(-1/2, 1)) + sage: RR(b) 0.250000000000000 - sage: Reals(rnd='RNDU')(b) # optional - arb + sage: Reals(rnd='RNDU')(b) 1.00000000093133 - sage: Reals(rnd='RNDD')(b) # optional - arb + sage: Reals(rnd='RNDD')(b) -0.500000000931323 - sage: Reals(rnd='RNDZ')(b) # optional - arb + sage: Reals(rnd='RNDZ')(b) 0.250000000000000 """ cdef RealNumber left, mid, right @@ -1173,7 +1232,7 @@ cdef class RealBall(RingElement): return field(0) raise ValueError("unknown rounding mode") - # Center and radius + # Center and radius, absolute value def mid(self): """ @@ -1183,21 +1242,25 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField, RBF # optional - arb - sage: RealBallField(16)(1/3).mid() # optional - arb + sage: from sage.rings.real_arb import RealBallField, RBF + sage: RealBallField(16)(1/3).mid() 0.3333 - sage: RealBallField(16)(1/3).mid().parent() # optional - arb - Real Field with 15 bits of precision + sage: RealBallField(16)(1/3).mid().parent() + Real Field with 16 bits of precision + sage: RealBallField(16)(RBF(1/3)).mid().parent() + Real Field with 53 bits of precision + sage: RBF('inf').mid() + +infinity :: - sage: b = RBF(2)^(2^1000) # optional - arb - sage: b.mid() # optional - arb + sage: b = RBF(2)^(2^1000) + sage: b.mid() Traceback (most recent call last): ... RuntimeError: unable to convert to MPFR (exponent out of range?) """ - cdef long mid_prec = arb_bits(self.value) or prec(self) + cdef long mid_prec = max(arb_bits(self.value), prec(self)) if mid_prec < MPFR_PREC_MIN: mid_prec = MPFR_PREC_MIN cdef RealField_class mid_field = RealField(mid_prec) @@ -1211,10 +1274,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RealBallField()(1/3).rad() # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RealBallField()(1/3).rad() 5.5511151e-17 - sage: RealBallField()(1/3).rad().parent() # optional - arb + sage: RealBallField()(1/3).rad().parent() Real Field with 30 bits of precision """ # Should we return a real number with rounding towards +∞ (or away from @@ -1232,19 +1295,19 @@ cdef class RealBall(RingElement): def squash(self): """ - Return an exact ball with the same center of this ball. + Return an exact ball with the same center as this ball. .. SEEALSO:: :meth:`mid`, :meth:`rad_as_ball` EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: mid = RealBallField(16)(1/3).squash() # optional - arb - sage: mid # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: mid = RealBallField(16)(1/3).squash() + sage: mid [0.3333 +/- 2.83e-5] - sage: mid.is_exact() # optional - arb + sage: mid.is_exact() True - sage: mid.parent() # optional - arb + sage: mid.parent() Real ball field with 16 bits precision """ cdef RealBall res = self._new() @@ -1260,13 +1323,13 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: rad = RBF(1/3).rad_as_ball() # optional - arb - sage: rad # optional - arb + sage: from sage.rings.real_arb import RBF + sage: rad = RBF(1/3).rad_as_ball() + sage: rad [5.55111512e-17 +/- 3.13e-26] - sage: rad.is_exact() # optional - arb + sage: rad.is_exact() True - sage: rad.parent() # optional - arb + sage: rad.parent() Real ball field with 30 bits precision """ cdef RealBall res = self._parent.element_class(RealBallField(MAG_BITS)) @@ -1274,6 +1337,80 @@ cdef class RealBall(RingElement): mag_zero(arb_radref(res.value)) return res + def abs(self): + """ + Return the absolute value of this ball. + + EXAMPLES:: + + sage: from sage.rings.real_arb import RBF + sage: RBF(-1/3).abs() + [0.3333333333333333 +/- 7.04e-17] + """ + cdef RealBall r = self._new() + arb_abs(r.value, self.value) + return r + + def below_abs(self, test_zero=False): + """ + Return a lower bound for the absolute value of this ball. + + INPUT: + + - ``test_zero`` (boolean, default ``False``) -- if ``True``, + make sure that the returned lower bound is positive, raising + an error if the ball contains zero. + + .. SEEALSO:: :meth:`abs`, :meth:`above_abs` + + EXAMPLES:: + + sage: from sage.rings.real_arb import RealBallField, RBF + sage: RealBallField(8)(1/3).below_abs() + [0.33 +/- 7.82e-5] + sage: b = RealBallField(8)(1/3).below_abs() + sage: b + [0.33 +/- 7.82e-5] + sage: b.is_exact() + True + sage: QQ(b) + 169/512 + + sage: RBF(0).below_abs() + 0 + sage: RBF(0).below_abs(test_zero=True) + Traceback (most recent call last): + ... + ValueError: ball contains zero + """ + cdef RealBall res = self._new() + arb_get_abs_lbound_arf(arb_midref(res.value), self.value, prec(self)) + if test_zero and arb_contains_zero(res.value): + assert arb_contains_zero(self.value) + raise ValueError("ball contains zero") + return res + + def above_abs(self): + """ + Return an upper bound for the absolute value of this ball. + + .. SEEALSO:: :meth:`abs`, :meth:`below_abs` + + EXAMPLES:: + + sage: from sage.rings.real_arb import RealBallField + sage: b = RealBallField(8)(1/3).above_abs() + sage: b + [0.33 +/- 3.99e-3] + sage: b.is_exact() + True + sage: QQ(b) + 171/512 + """ + cdef RealBall res = self._new() + arb_get_abs_ubound_arf(arb_midref(res.value), self.value, prec(self)) + return res + # Precision def round(self): @@ -1285,12 +1422,12 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: b = RBF(pi.n(100)) # optional - arb - sage: b.mid() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: b = RBF(pi.n(100)) + sage: b.mid() 3.141592653589793238462643383 - sage: b.round().mid() # optional - arb - 3.1415926535898 + sage: b.round().mid() + 3.14159265358979 """ cdef RealBall res = self._new() if _do_sig(prec(self)): sig_on() @@ -1303,18 +1440,23 @@ cdef class RealBall(RingElement): Return the effective relative accuracy of this ball measured in bits. The accuracy is defined as the difference between the position of the - top bit in the midpoint and the top bit in the radius and , minus one. - The result is clamped between plus/minus ``ARF_PREC_EXACT``. + top bit in the midpoint and the top bit in the radius, minus one. + The result is clamped between plus/minus + :meth:`~RealBallField.maximal_accuracy`. EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(pi).accuracy() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(pi).accuracy() 51 - sage: RBF(1).accuracy() # optional - arb - 9223372036854775807 - sage: RBF(NaN).accuracy() # optional - arb - -9223372036854775807 + sage: RBF(1).accuracy() == RBF.maximal_accuracy() + True + sage: RBF(NaN).accuracy() == -RBF.maximal_accuracy() + True + + .. seealso:: + + :meth:`~RealBallField.maximal_accuracy` """ return arb_rel_accuracy_bits(self.value) @@ -1331,12 +1473,12 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: b = RBF(RIF(3.1415,3.1416)) # optional - arb - sage: b.mid() # optional - arb - 3.14155000000000 - sage: b.trim().mid() # optional - arb - 3.14155000 + sage: from sage.rings.real_arb import RBF + sage: b = RBF(0.11111111111111, rad=.001) + sage: b.mid() + 0.111111111111110 + sage: b.trim().mid() + 0.111111104488373 """ cdef RealBall res = self._new() if _do_sig(prec(self)): sig_on() @@ -1352,11 +1494,11 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: RBF(0).is_zero() # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RBF = RealBallField() + sage: RBF(0).is_zero() True - sage: RBF(RIF(-0.5, 0.5)).is_zero() # optional - arb + sage: RBF(RIF(-0.5, 0.5)).is_zero() False """ return arb_is_zero(self.value) @@ -1368,11 +1510,11 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: bool(RBF(pi)) # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RBF = RealBallField() + sage: bool(RBF(pi)) True - sage: bool(RBF(RIF(-0.5, 0.5))) # optional - arb + sage: bool(RBF(RIF(-0.5, 0.5))) False """ return arb_is_nonzero(self.value) @@ -1383,11 +1525,11 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: RBF(1).is_exact() # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RBF = RealBallField() + sage: RBF(1).is_exact() True - sage: RBF(RIF(0.1, 0.2)).is_exact() # optional - arb + sage: RBF(RIF(0.1, 0.2)).is_exact() False """ return arb_is_exact(self.value) @@ -1400,171 +1542,171 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RealBallField # optional - arb - sage: RBF = RealBallField() # optional - arb - sage: a = RBF(1) # optional - arb - sage: b = RBF(1) # optional - arb - sage: a is b # optional - arb + sage: from sage.rings.real_arb import RealBallField + sage: RBF = RealBallField() + sage: a = RBF(1) + sage: b = RBF(1) + sage: a is b False - sage: a == b # optional - arb + sage: a == b True - sage: a = RBF(1/3) # optional - arb - sage: a.is_exact() # optional - arb + sage: a = RBF(1/3) + sage: a.is_exact() False - sage: b = RBF(1/3) # optional - arb - sage: b.is_exact() # optional - arb + sage: b = RBF(1/3) + sage: b.is_exact() False - sage: a == b # optional - arb + sage: a == b False TESTS: Balls whose intersection consists of one point:: - sage: a = RBF(RIF(1, 2)) # optional - arb - sage: b = RBF(RIF(2, 4)) # optional - arb - sage: a < b # optional - arb + sage: a = RBF(RIF(1, 2)) + sage: b = RBF(RIF(2, 4)) + sage: a < b False - sage: a > b # optional - arb + sage: a > b False - sage: a <= b # optional - arb + sage: a <= b False - sage: a >= b # optional - arb + sage: a >= b False - sage: a == b # optional - arb + sage: a == b False - sage: a != b # optional - arb + sage: a != b False Balls with non-trivial intersection:: - sage: a = RBF(RIF(1, 4)) # optional - arb - sage: a = RBF(RIF(2, 5)) # optional - arb - sage: a < b # optional - arb + sage: a = RBF(RIF(1, 4)) + sage: a = RBF(RIF(2, 5)) + sage: a < b False - sage: a <= b # optional - arb + sage: a <= b False - sage: a > b # optional - arb + sage: a > b False - sage: a >= b # optional - arb + sage: a >= b False - sage: a == b # optional - arb + sage: a == b False - sage: a != b # optional - arb + sage: a != b False One ball contained in another:: - sage: a = RBF(RIF(1, 4)) # optional - arb - sage: b = RBF(RIF(2, 3)) # optional - arb - sage: a < b # optional - arb + sage: a = RBF(RIF(1, 4)) + sage: b = RBF(RIF(2, 3)) + sage: a < b False - sage: a <= b # optional - arb + sage: a <= b False - sage: a > b # optional - arb + sage: a > b False - sage: a >= b # optional - arb + sage: a >= b False - sage: a == b # optional - arb + sage: a == b False - sage: a != b # optional - arb + sage: a != b False Disjoint balls:: - sage: a = RBF(1/3) # optional - arb - sage: b = RBF(1/2) # optional - arb - sage: a < b # optional - arb + sage: a = RBF(1/3) + sage: b = RBF(1/2) + sage: a < b True - sage: a <= b # optional - arb + sage: a <= b True - sage: a > b # optional - arb + sage: a > b False - sage: a >= b # optional - arb + sage: a >= b False - sage: a == b # optional - arb + sage: a == b False - sage: a != b # optional - arb + sage: a != b True Exact elements:: - sage: a = RBF(2) # optional - arb - sage: b = RBF(2) # optional - arb - sage: a.is_exact() # optional - arb + sage: a = RBF(2) + sage: b = RBF(2) + sage: a.is_exact() True - sage: b.is_exact() # optional - arb + sage: b.is_exact() True - sage: a < b # optional - arb + sage: a < b False - sage: a <= b # optional - arb + sage: a <= b True - sage: a > b # optional - arb + sage: a > b False - sage: a >= b # optional - arb + sage: a >= b True - sage: a == b # optional - arb + sage: a == b True - sage: a != b # optional - arb + sage: a != b False Special values:: - sage: inf = RBF(+infinity) # optional - arb - sage: other_inf = RBF(+infinity, 42.r) # optional - arb - sage: neg_inf = RBF(-infinity) # optional - arb - sage: extended_line = 1/RBF(0) # optional - arb - sage: exact_nan = inf - inf # optional - arb - sage: exact_nan.mid(), exact_nan.rad() # optional - arb + sage: inf = RBF(+infinity) + sage: other_inf = RBF(+infinity, 42.r) + sage: neg_inf = RBF(-infinity) + sage: extended_line = 1/RBF(0) + sage: exact_nan = inf - inf + sage: exact_nan.mid(), exact_nan.rad() (NaN, 0.00000000) - sage: other_exact_nan = inf - inf # optional - arb + sage: other_exact_nan = inf - inf :: - sage: exact_nan == exact_nan, exact_nan <= exact_nan, exact_nan >= exact_nan # optional - arb + sage: exact_nan == exact_nan, exact_nan <= exact_nan, exact_nan >= exact_nan (False, False, False) - sage: exact_nan != exact_nan, exact_nan < exact_nan, exact_nan > exact_nan # optional - arb + sage: exact_nan != exact_nan, exact_nan < exact_nan, exact_nan > exact_nan (False, False, False) - sage: from operator import eq, ne, le, lt, ge, gt # optional - arb - sage: ops = [eq, ne, le, lt, ge, gt] # optional - arb - sage: any(op(exact_nan, other_exact_nan) for op in ops) # optional - arb + sage: from operator import eq, ne, le, lt, ge, gt + sage: ops = [eq, ne, le, lt, ge, gt] + sage: any(op(exact_nan, other_exact_nan) for op in ops) False - sage: any(op(exact_nan, b) for op in ops for b in [RBF(1), extended_line, inf, neg_inf]) # optional - arb + sage: any(op(exact_nan, b) for op in ops for b in [RBF(1), extended_line, inf, neg_inf]) False :: - sage: neg_inf < a < inf and inf > a > neg_inf # optional - arb + sage: neg_inf < a < inf and inf > a > neg_inf True - sage: neg_inf <= b <= inf and inf >= b >= neg_inf # optional - arb + sage: neg_inf <= b <= inf and inf >= b >= neg_inf True - sage: neg_inf <= extended_line <= inf and inf >= extended_line >= neg_inf # optional - arb + sage: neg_inf <= extended_line <= inf and inf >= extended_line >= neg_inf True - sage: neg_inf < extended_line or extended_line < inf # optional - arb + sage: neg_inf < extended_line or extended_line < inf False - sage: inf > extended_line or extended_line > neg_inf # optional - arb + sage: inf > extended_line or extended_line > neg_inf False :: - sage: all(b <= b == b >= b and not (b < b or b != b or b > b) # optional - arb + sage: all(b <= b == b >= b and not (b < b or b != b or b > b) ....: for b in [inf, neg_inf, other_inf]) True - sage: any(b1 == b2 for b1 in [inf, neg_inf, a, extended_line] # optional - arb + sage: any(b1 == b2 for b1 in [inf, neg_inf, a, extended_line] ....: for b2 in [inf, neg_inf, a, extended_line] ....: if not b1 is b2) False - sage: all(b1 != b2 and not b1 == b2 # optional - arb + sage: all(b1 != b2 and not b1 == b2 ....: for b1 in [inf, neg_inf, a] ....: for b2 in [inf, neg_inf, a] ....: if not b1 is b2) True - sage: neg_inf <= -other_inf == neg_inf == -other_inf < other_inf == inf <= other_inf # optional - arb + sage: neg_inf <= -other_inf == neg_inf == -other_inf < other_inf == inf <= other_inf True - sage: any(inf < b or b > inf # optional - arb + sage: any(inf < b or b > inf ....: for b in [inf, other_inf, a, extended_line]) False - sage: any(inf <= b or b >= inf for b in [a, extended_line]) # optional - arb + sage: any(inf <= b or b >= inf for b in [a, extended_line]) False """ cdef RealBall lt, rt @@ -1634,10 +1776,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: (RBF(2)^(2^1000)).is_finite() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: (RBF(2)^(2^1000)).is_finite() True - sage: RBF(oo).is_finite() # optional - arb + sage: RBF(oo).is_finite() False """ return arb_is_finite(self.value) @@ -1656,12 +1798,12 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).identical(RBF(3)-RBF(2)) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1).identical(RBF(3)-RBF(2)) True - sage: RBF(1, rad=0.25r).identical(RBF(1, rad=0.25r)) # optional - arb + sage: RBF(1, rad=0.25r).identical(RBF(1, rad=0.25r)) True - sage: RBF(1).identical(RBF(1, rad=0.25r)) # optional - arb + sage: RBF(1).identical(RBF(1, rad=0.25r)) False """ return arb_equal(self.value, other.value) @@ -1676,10 +1818,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(pi).overlaps(RBF(pi) + 2**(-100)) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(pi).overlaps(RBF(pi) + 2**(-100)) True - sage: RBF(pi).overlaps(RBF(3)) # optional - arb + sage: RBF(pi).overlaps(RBF(3)) False """ return arb_overlaps(self.value, other.value) @@ -1699,29 +1841,29 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: b = RBF(1) # optional - arb - sage: b.contains_exact(1) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: b = RBF(1) + sage: b.contains_exact(1) True - sage: b.contains_exact(QQ(1)) # optional - arb + sage: b.contains_exact(QQ(1)) True - sage: b.contains_exact(1.) # optional - arb + sage: b.contains_exact(1.) True - sage: b.contains_exact(b) # optional - arb + sage: b.contains_exact(b) True :: - sage: RBF(1/3).contains_exact(1/3) # optional - arb + sage: RBF(1/3).contains_exact(1/3) True - sage: RBF(sqrt(2)).contains_exact(sqrt(2)) # optional - arb + sage: RBF(sqrt(2)).contains_exact(sqrt(2)) Traceback (most recent call last): ... TypeError TESTS:: - sage: b.contains_exact(1r) # optional - arb + sage: b.contains_exact(1r) True """ @@ -1762,13 +1904,13 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF, RealBallField # optional - arb - sage: sqrt(2) in RBF(sqrt(2)) # optional - arb + sage: from sage.rings.real_arb import RBF, RealBallField + sage: sqrt(2) in RBF(sqrt(2)) True A false negative:: - sage: sqrt(2) in RBF(RealBallField(100)(sqrt(2))) # optional - arb + sage: sqrt(2) in RBF(RealBallField(100)(sqrt(2))) False """ return self.contains_exact(self._parent(other)) @@ -1779,8 +1921,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(-infinity).is_negative_infinity() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(-infinity).is_negative_infinity() True """ return (arf_is_neg_inf(arb_midref(self.value)) @@ -1792,8 +1934,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(infinity).is_positive_infinity() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(infinity).is_positive_infinity() True """ return (arf_is_pos_inf(arb_midref(self.value)) @@ -1816,16 +1958,16 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(infinity).is_infinity() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(infinity).is_infinity() True - sage: RBF(-infinity).is_infinity() # optional - arb + sage: RBF(-infinity).is_infinity() True - sage: RBF(NaN).is_infinity() # optional - arb + sage: RBF(NaN).is_infinity() True - sage: (~RBF(0)).is_infinity() # optional - arb + sage: (~RBF(0)).is_infinity() True - sage: RBF(42, rad=1.r).is_infinity() # optional - arb + sage: RBF(42, rad=1.r).is_infinity() False """ return not self.is_finite() @@ -1838,8 +1980,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: -RBF(1/3) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: -RBF(1/3) [-0.3333333333333333 +/- 7.04e-17] """ cdef RealBall res = self._new() @@ -1855,12 +1997,12 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: ~RBF(5) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: ~RBF(5) [0.2000000000000000 +/- 4.45e-17] - sage: ~RBF(0) # optional - arb + sage: ~RBF(0) [+/- inf] - sage: RBF(RIF(-0.1,0.1)) # optional - arb + sage: RBF(RIF(-0.1,0.1)) [+/- 0.101] """ @@ -1879,8 +2021,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1) + RBF(1/3) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1) + RBF(1/3) [1.333333333333333 +/- 5.37e-16] """ cdef RealBall res = self._new() @@ -1899,8 +2041,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1) - RBF(1/3) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1) - RBF(1/3) [0.666666666666667 +/- 5.37e-16] """ cdef RealBall res = self._new() @@ -1919,8 +2061,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(-2) * RBF(1/3) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(-2) * RBF(1/3) [-0.666666666666667 +/- 4.82e-16] """ cdef RealBall res = self._new() @@ -1939,10 +2081,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(pi)/RBF(e) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(pi)/RBF(e) [1.155727349790922 +/- 8.43e-16] - sage: RBF(2)/RBF(0) # optional - arb + sage: RBF(2)/RBF(0) [+/- inf] """ cdef RealBall res = self._new() @@ -1955,30 +2097,30 @@ cdef class RealBall(RingElement): """ EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(e)^17 # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(e)^17 [24154952.7535753 +/- 9.30e-8] - sage: RBF(e)^(-1) # optional - arb + sage: RBF(e)^(-1) [0.367879441171442 +/- 4.50e-16] - sage: RBF(e)^(1/2) # optional - arb + sage: RBF(e)^(1/2) [1.648721270700128 +/- 4.96e-16] - sage: RBF(e)^RBF(pi) # optional - arb + sage: RBF(e)^RBF(pi) [23.1406926327793 +/- 9.16e-14] :: - sage: RBF(-1)^(1/3) # optional - arb + sage: RBF(-1)^(1/3) nan - sage: RBF(0)^(-1) # optional - arb + sage: RBF(0)^(-1) [+/- inf] - sage: RBF(-e)**RBF(pi) # optional - arb + sage: RBF(-e)**RBF(pi) nan TESTS:: - sage: RBF(e)**(2r) # optional - arb + sage: RBF(e)**(2r) [7.38905609893065 +/- 4.68e-15] - sage: RBF(e)**(-1r) # optional - arb + sage: RBF(e)**(-1r) [0.367879441171442 +/- 4.50e-16] """ cdef fmpz_t tmpz @@ -2011,10 +2153,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(2).sqrt() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(2).sqrt() [1.414213562373095 +/- 2.99e-16] - sage: RBF(-1/3).sqrt() # optional - arb + sage: RBF(-1/3).sqrt() nan """ cdef RealBall res = self._new() @@ -2032,12 +2174,12 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(2).sqrtpos() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(2).sqrtpos() [1.414213562373095 +/- 2.99e-16] - sage: RBF(-1/3).sqrtpos() # optional - arb + sage: RBF(-1/3).sqrtpos() 0 - sage: RBF(0, rad=2.r).sqrtpos() # optional - arb + sage: RBF(0, rad=2.r).sqrtpos() [+/- 1.42] """ cdef RealBall res = self._new() @@ -2054,10 +2196,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(2).rsqrt() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(2).rsqrt() [0.707106781186547 +/- 5.73e-16] - sage: RBF(0).rsqrt() # optional - arb + sage: RBF(0).rsqrt() nan """ cdef RealBall res = self._new() @@ -2073,11 +2215,11 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: eps = RBF(10^(-20)) # optional - arb - sage: (1 + eps).sqrt() - 1 # optional - arb + sage: from sage.rings.real_arb import RBF + sage: eps = RBF(10^(-20)) + sage: (1 + eps).sqrt() - 1 [+/- 1.12e-16] - sage: eps.sqrt1pm1() # optional - arb + sage: eps.sqrt1pm1() [5.00000000000000e-21 +/- 2.54e-36] """ cdef RealBall res = self._new() @@ -2094,8 +2236,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1000+1/3, rad=1.r).floor() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1000+1/3, rad=1.r).floor() [1.00e+3 +/- 1.01] """ cdef RealBall res = self._new() @@ -2110,8 +2252,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1000+1/3, rad=1.r).ceil() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1000+1/3, rad=1.r).ceil() [1.00e+3 +/- 2.01] """ cdef RealBall res = self._new() @@ -2128,10 +2270,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(3).log() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(3).log() [1.098612288668110 +/- 6.63e-16] - sage: RBF(-1/3).log() # optional - arb + sage: RBF(-1/3).log() nan """ cdef RealBall res = self._new() @@ -2147,11 +2289,11 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: eps = RBF(1e-30) # optional - arb - sage: (1 + eps).log() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: eps = RBF(1e-30) + sage: (1 + eps).log() [+/- 2.23e-16] - sage: eps.log1p() # optional - arb + sage: eps.log1p() [1.00000000000000e-30 +/- 2.68e-46] """ cdef RealBall res = self._new() @@ -2166,8 +2308,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).exp() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1).exp() [2.718281828459045 +/- 5.41e-16] """ cdef RealBall res = self._new() @@ -2183,11 +2325,11 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: eps = RBF(1e-30) # optional - arb - sage: exp(eps) - 1 # optional - arb + sage: from sage.rings.real_arb import RBF + sage: eps = RBF(1e-30) + sage: exp(eps) - 1 [+/- 3.16e-30] - sage: eps.expm1() # optional - arb + sage: eps.expm1() [1.000000000000000e-30 +/- 8.34e-47] """ cdef RealBall res = self._new() @@ -2204,8 +2346,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(pi).sin() # abs tol 1e-16, optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(pi).sin() # abs tol 1e-16 [+/- 5.69e-16] """ cdef RealBall res = self._new() @@ -2222,8 +2364,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(pi).cos() # abs tol 1e-16, optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(pi).cos() # abs tol 1e-16 [-1.00000000000000 +/- 6.69e-16] """ cdef RealBall res = self._new() @@ -2238,10 +2380,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).tan() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1).tan() [1.557407724654902 +/- 3.26e-16] - sage: RBF(pi/2).tan() # optional - arb + sage: RBF(pi/2).tan() [+/- inf] """ cdef RealBall res = self._new() @@ -2256,10 +2398,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).cot() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1).cot() [0.642092615934331 +/- 4.79e-16] - sage: RBF(pi).cot() # optional - arb + sage: RBF(pi).cot() [+/- inf] """ cdef RealBall res = self._new() @@ -2274,10 +2416,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).arcsin() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1).arcsin() [1.570796326794897 +/- 6.65e-16] - sage: RBF(1, rad=.125r).arcsin() # optional - arb + sage: RBF(1, rad=.125r).arcsin() nan """ cdef RealBall res = self._new() @@ -2292,10 +2434,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).arccos() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1).arccos() 0 - sage: RBF(1, rad=.125r).arccos() # optional - arb + sage: RBF(1, rad=.125r).arccos() nan """ cdef RealBall res = self._new() @@ -2310,8 +2452,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).arctan() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1).arctan() [0.785398163397448 +/- 3.91e-16] """ cdef RealBall res = self._new() @@ -2326,8 +2468,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).sinh() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1).sinh() [1.175201193643801 +/- 6.18e-16] """ cdef RealBall res = self._new() @@ -2342,8 +2484,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).cosh() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1).cosh() [1.543080634815244 +/- 5.28e-16] """ cdef RealBall res = self._new() @@ -2358,8 +2500,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).tanh() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1).tanh() [0.761594155955765 +/- 2.81e-16] """ cdef RealBall res = self._new() @@ -2374,10 +2516,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).coth() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1).coth() [1.313035285499331 +/- 4.97e-16] - sage: RBF(0).coth() # optional - arb + sage: RBF(0).coth() [+/- inf] """ cdef RealBall res = self._new() @@ -2392,10 +2534,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).arcsinh() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1).arcsinh() [0.881373587019543 +/- 1.87e-16] - sage: RBF(0).arcsinh() # optional - arb + sage: RBF(0).arcsinh() 0 """ cdef RealBall res = self._new() @@ -2410,12 +2552,12 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(2).arccosh() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(2).arccosh() [1.316957896924817 +/- 6.61e-16] - sage: RBF(1).arccosh() # optional - arb + sage: RBF(1).arccosh() 0 - sage: RBF(0).arccosh() # optional - arb + sage: RBF(0).arccosh() nan """ cdef RealBall res = self._new() @@ -2430,12 +2572,12 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(0).arctanh() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(0).arctanh() 0 - sage: RBF(1/2).arctanh() # optional - arb + sage: RBF(1/2).arctanh() [0.549306144334055 +/- 3.32e-16] - sage: RBF(1).arctanh() # optional - arb + sage: RBF(1).arctanh() nan """ cdef RealBall res = self._new() @@ -2455,8 +2597,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1/2).gamma() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1/2).gamma() [1.772453850905516 +/- 3.41e-16] """ cdef RealBall res = self._new() @@ -2469,13 +2611,13 @@ cdef class RealBall(RingElement): """ Return the image of this ball by the logarithmic Gamma function. - The complex branch structure is assumed, so if ``self`` ≤ 0, the result + The complex branch structure is assumed, so if ``self`` <= 0, the result is an indeterminate interval. EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1/2).log_gamma() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1/2).log_gamma() [0.572364942924700 +/- 4.87e-16] """ cdef RealBall res = self._new() @@ -2491,10 +2633,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(-1).rgamma() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(-1).rgamma() 0 - sage: RBF(3).rgamma() # optional - arb + sage: RBF(3).rgamma() 0.5000000000000000 """ cdef RealBall res = self._new() @@ -2509,8 +2651,8 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).psi() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1).psi() [-0.577215664901533 +/- 3.85e-16] """ @@ -2531,12 +2673,12 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(-1).zeta() # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(-1).zeta() [-0.0833333333333333 +/- 4.36e-17] - sage: RBF(-1).zeta(1) # optional - arb + sage: RBF(-1).zeta(1) [-0.0833333333333333 +/- 6.81e-17] - sage: RBF(-1).zeta(2) # abs tol 1e-16, optional - arb + sage: RBF(-1).zeta(2) # abs tol 1e-16 [-1.083333333333333 +/- 4.09e-16] """ cdef RealBall a_ball @@ -2558,23 +2700,23 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: polylog(0, -1) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: polylog(0, -1) -1/2 - sage: RBF(-1).polylog(0) # optional - arb + sage: RBF(-1).polylog(0) [-0.50000000000000 +/- 1.29e-15] - sage: polylog(1, 1/2) # optional - arb + sage: polylog(1, 1/2) -log(1/2) - sage: RBF(1/2).polylog(1) # optional - arb + sage: RBF(1/2).polylog(1) [0.6931471805599 +/- 5.02e-14] - sage: RBF(1/3).polylog(1/2) # optional - arb + sage: RBF(1/3).polylog(1/2) [0.44210883528067 +/- 6.75e-15] - sage: RBF(1/3).polylog(RLF(pi)) # optional - arb + sage: RBF(1/3).polylog(RLF(pi)) [0.34728895057225 +/- 5.51e-15] TESTS:: - sage: RBF(1/3).polylog(2r) # optional - arb + sage: RBF(1/3).polylog(2r) [0.36621322997706 +/- 4.62e-15] """ cdef RealBall s_as_ball @@ -2602,16 +2744,16 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(pi).chebyshev_T(0) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(pi).chebyshev_T(0) 1.000000000000000 - sage: RBF(pi).chebyshev_T(1) # abs tol 1e-16, optional - arb + sage: RBF(pi).chebyshev_T(1) # abs tol 1e-16 [3.141592653589793 +/- 5.62e-16] - sage: RBF(pi).chebyshev_T(10**20) # optional - arb + sage: RBF(pi).chebyshev_T(10**20) Traceback (most recent call last): ... ValueError: index too large - sage: RBF(pi).chebyshev_T(-1) # optional - arb + sage: RBF(pi).chebyshev_T(-1) Traceback (most recent call last): ... ValueError: expected a nonnegative index @@ -2635,16 +2777,16 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(pi).chebyshev_U(0) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(pi).chebyshev_U(0) 1.000000000000000 - sage: RBF(pi).chebyshev_U(1) # optional - arb + sage: RBF(pi).chebyshev_U(1) [6.28318530717959 +/- 4.66e-15] - sage: RBF(pi).chebyshev_U(10**20) # optional - arb + sage: RBF(pi).chebyshev_U(10**20) Traceback (most recent call last): ... ValueError: index too large - sage: RBF(pi).chebyshev_U(-1) # optional - arb + sage: RBF(pi).chebyshev_U(-1) Traceback (most recent call last): ... ValueError: expected a nonnegative index @@ -2667,10 +2809,10 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: from sage.rings.real_arb import RBF # optional - arb - sage: RBF(1).agm(1) # optional - arb + sage: from sage.rings.real_arb import RBF + sage: RBF(1).agm(1) 1.000000000000000 - sage: RBF(sqrt(2)).agm(1)^(-1) # optional - arb + sage: RBF(sqrt(2)).agm(1)^(-1) [0.83462684167407 +/- 4.31e-15] """ cdef RealBall other_as_ball diff --git a/src/sage/rings/real_double.pyx b/src/sage/rings/real_double.pyx index e6ec3dfc30c..a0a22c4e7d3 100644 --- a/src/sage/rings/real_double.pyx +++ b/src/sage/rings/real_double.pyx @@ -140,7 +140,7 @@ cdef class RealDoubleField_class(Field): sage: TestSuite(R).run() """ from sage.categories.fields import Fields - Field.__init__(self, self, category = Fields()) + Field.__init__(self, self, category=Fields().Metric().Complete()) self._populate_coercion_lists_(element_constructor=RealDoubleElement, init_no_parent=True, convert_method_name='_real_double_') diff --git a/src/sage/rings/real_mpfi.pxd b/src/sage/rings/real_mpfi.pxd index d90d471627e..865381d25a6 100644 --- a/src/sage/rings/real_mpfi.pxd +++ b/src/sage/rings/real_mpfi.pxd @@ -30,14 +30,17 @@ cdef class RealIntervalField_class(sage.rings.ring.Field): cdef real_mpfr.RealField_class __lower_field cdef real_mpfr.RealField_class __middle_field cdef real_mpfr.RealField_class __upper_field - cdef RealIntervalFieldElement _new(self) + cdef inline RealIntervalFieldElement _new(self): + """Return a new real interval with parent ``self``.""" + return RealIntervalFieldElement.__new__(RealIntervalFieldElement, self) cdef class RealIntervalFieldElement(RingElement): cdef mpfi_t value - cdef char init - cdef RealIntervalFieldElement _new(self) + cdef inline RealIntervalFieldElement _new(self): + """Return a new real interval with same parent as ``self``.""" + return RealIntervalFieldElement.__new__(RealIntervalFieldElement, self._parent) cdef RealIntervalFieldElement abs(RealIntervalFieldElement self) cdef Rational _simplest_rational_helper(self) cpdef _str_question_style(self, int base, int error_digits, e, bint prefer_sci) diff --git a/src/sage/rings/real_mpfi.pyx b/src/sage/rings/real_mpfi.pyx index 398915c3dd3..7f2c6d1c296 100644 --- a/src/sage/rings/real_mpfi.pyx +++ b/src/sage/rings/real_mpfi.pyx @@ -564,17 +564,6 @@ cdef class RealIntervalField_class(sage.rings.ring.Field): else: return RealField(self.__prec, self.sci_not, "RNDZ") - cdef RealIntervalFieldElement _new(self): - """ - Return a new real number with parent ``self``. - """ - cdef RealIntervalFieldElement x - x = RealIntervalFieldElement.__new__(RealIntervalFieldElement) - x._parent = self - mpfi_init2(x.value, self.__prec) - x.init = 1 - return x - def _repr_(self): """ Return a string representation of ``self``. @@ -667,7 +656,7 @@ cdef class RealIntervalField_class(sage.rings.ring.Field): sage: R('2', base=2) Traceback (most recent call last): ... - TypeError: Unable to convert number to real interval. + TypeError: unable to convert '2' to real interval sage: a = R('1.1001', base=2); a 1.5625000? sage: a.str(2) @@ -1115,21 +1104,33 @@ cdef class RealIntervalFieldElement(RingElement): """ A real number interval. """ - cdef RealIntervalFieldElement _new(self): + def __cinit__(self, parent, x=None, base=None): """ - Return a new real interval with same parent as ``self``. + Initialize the parent of this element and allocate memory + + TESTS:: + + sage: from sage.rings.real_mpfi import RealIntervalFieldElement + sage: RealIntervalFieldElement.__new__(RealIntervalFieldElement, None) + Traceback (most recent call last): + ... + TypeError: Cannot convert NoneType to sage.rings.real_mpfi.RealIntervalField_class + sage: RealIntervalFieldElement.__new__(RealIntervalFieldElement, ZZ) + Traceback (most recent call last): + ... + TypeError: Cannot convert sage.rings.integer_ring.IntegerRing_class to sage.rings.real_mpfi.RealIntervalField_class + sage: RealIntervalFieldElement.__new__(RealIntervalFieldElement, RIF) + [.. NaN ..] """ - cdef RealIntervalFieldElement x - x = RealIntervalFieldElement.__new__(RealIntervalFieldElement) - x._parent = self._parent - mpfi_init2(x.value, (self._parent).__prec) - x.init = 1 - return x + cdef RealIntervalField_class p = parent + mpfi_init2(self.value, p.__prec) + self._parent = p - def __init__(self, RealIntervalField_class parent, x=0, int base=10): + def __init__(self, parent, x=0, int base=10): """ - Create a real interval element. Should be called by first creating - a :class:`RealIntervalField`, as illustrated in the examples. + Initialize a real interval element. Should be called by first + creating a :class:`RealIntervalField`, as illustrated in the + examples. EXAMPLES:: @@ -1140,7 +1141,17 @@ cdef class RealIntervalFieldElement(RingElement): sage: R('1.2456').str(style='brackets') '[1.0 .. 1.3]' - EXAMPLES: + :: + + sage: RIF = RealIntervalField(53) + sage: RIF(RR.pi()) + 3.1415926535897932? + sage: RIF(RDF.pi()) + 3.1415926535897932? + sage: RIF(math.pi) + 3.1415926535897932? + sage: RIF.pi() + 3.141592653589794? Rounding:: @@ -1157,94 +1168,60 @@ cdef class RealIntervalFieldElement(RingElement): Type: ``RealIntervalField?`` for many more examples. """ - import sage.rings.qqbar - - self.init = 0 - if parent is None: - raise TypeError - self._parent = parent - mpfi_init2(self.value, parent.__prec) - self.init = 1 - if x is None: return - cdef RealIntervalFieldElement _x, n, d - cdef RealNumber rn, rn1 - cdef Rational rat, rat1 - cdef Integer integ, integ1 - cdef RealDoubleElement dx, dx1 - cdef int ix, ix1 + if x is None: + return + + cdef RealNumber ra, rb + cdef RealIntervalFieldElement d + if isinstance(x, RealIntervalFieldElement): - _x = x # so we can get at x.value - mpfi_set(self.value, _x.value) + mpfi_set(self.value, (x).value) elif isinstance(x, RealNumber): - rn = x - mpfi_set_fr(self.value, rn.value) + mpfi_set_fr(self.value, (x).value) elif isinstance(x, Rational): - rat = x - mpfi_set_q(self.value, rat.value) + mpfi_set_q(self.value, (x).value) elif isinstance(x, Integer): - integ = x - mpfi_set_z(self.value, integ.value) + mpfi_set_z(self.value, (x).value) + elif isinstance(x, RealDoubleElement): + mpfi_set_d(self.value, (x)._value) elif isinstance(x, int): - ix = x - mpfi_set_si(self.value, ix) + mpfi_set_si(self.value, x) + elif isinstance(x, float): + mpfi_set_d(self.value, x) + elif hasattr(x, '_real_mpfi_'): + d = x._real_mpfi_(self._parent) + mpfi_set(self.value, d.value) elif isinstance(x, tuple): try: a, b = x except ValueError: raise TypeError("tuple defining an interval must have length 2") if isinstance(a, RealNumber) and isinstance(b, RealNumber): - rn = a - rn1 = b - mpfi_interv_fr(self.value, rn.value, rn1.value) + mpfi_interv_fr(self.value, (a).value, (b).value) elif isinstance(a, RealDoubleElement) and isinstance(b, RealDoubleElement): - dx = a - dx1 = b - mpfi_interv_d(self.value, dx._value, dx1._value) + mpfi_interv_d(self.value, (a)._value, (b)._value) elif isinstance(a, Rational) and isinstance(b, Rational): - rat = a - rat1 = b - mpfi_interv_q(self.value, rat.value, rat1.value) + mpfi_interv_q(self.value, (a).value, (b).value) elif isinstance(a, Integer) and isinstance(b, Integer): - integ = a - integ1 = b - mpfi_interv_z(self.value, integ.value, integ1.value) + mpfi_interv_z(self.value, (a).value, (b).value) elif isinstance(a, int) and isinstance(b, int): - ix = a - ix1 = b - mpfi_interv_si(self.value, ix, ix1) + mpfi_interv_si(self.value, a, b) else: # generic fallback - rn = self._parent(a).lower() - rn1 = self._parent(b).upper() - mpfi_interv_fr(self.value, rn.value, rn1.value) - - elif isinstance(x, sage.rings.qqbar.AlgebraicReal): - d = x.interval(self._parent) - mpfi_set(self.value, d.value) - - elif hasattr(x, '_real_mpfi_'): - d = x._real_mpfi_(self._parent) - mpfi_set(self.value, d.value) - + ra = self._parent(a).lower() + rb = self._parent(b).upper() + mpfi_interv_fr(self.value, ra.value, rb.value) + elif isinstance(x, basestring): + s = str(x).replace('..', ',').replace(' ','').replace('+infinity', '@inf@').replace('-infinity','-@inf@') + if mpfi_set_str(self.value, s, base): + raise TypeError("unable to convert {!r} to real interval".format(x)) else: - from sage.symbolic.expression import Expression - - if isinstance(x, Expression): - d = x._real_mpfi_(self._parent) - mpfi_set(self.value, d.value) - - elif isinstance(x, str): - # string - s = str(x).replace('..', ',').replace(' ','').replace('+infinity', '@inf@').replace('-infinity','-@inf@') - if mpfi_set_str(self.value, s, base): - raise TypeError("Unable to convert number to real interval.") - else: - # try coercing to real - try: - rn = self._parent._lower_field()(x) - rn1 = self._parent._upper_field()(x) - except TypeError: - raise TypeError("Unable to convert number to real interval.") - mpfi_interv_fr(self.value, rn.value, rn1.value) + # try coercing to real + try: + ra = self._parent._lower_field()(x) + rb = self._parent._upper_field()(x) + except TypeError: + raise TypeError("unable to convert {!r} to real interval".format(x)) + mpfi_interv_fr(self.value, ra.value, rb.value) def __reduce__(self): """ @@ -1292,7 +1269,7 @@ cdef class RealIntervalFieldElement(RingElement): sage: R = RealIntervalField() sage: del R # indirect doctest """ - if self.init: + if self._parent is not None: mpfi_clear(self.value) def __repr__(self): @@ -2433,15 +2410,19 @@ cdef class RealIntervalFieldElement(RingElement): """ The largest absolute value of the elements of the interval. + OUTPUT: a real number with rounding mode ``RNDU`` + EXAMPLES:: sage: RIF(-2, 1).magnitude() 2.00000000000000 sage: RIF(-1, 2).magnitude() 2.00000000000000 + sage: parent(RIF(1).magnitude()) + Real Field with 53 bits of precision and rounding RNDU """ cdef RealNumber x - x = (self._parent).__middle_field._new() + x = (self._parent).__upper_field._new() mpfi_mag(x.value, self.value) return x @@ -2449,6 +2430,8 @@ cdef class RealIntervalFieldElement(RingElement): """ The smallest absolute value of the elements of the interval. + OUTPUT: a real number with rounding mode ``RNDD`` + EXAMPLES:: sage: RIF(-2, 1).mignitude() @@ -2457,9 +2440,11 @@ cdef class RealIntervalFieldElement(RingElement): 1.00000000000000 sage: RIF(3, 4).mignitude() 3.00000000000000 + sage: parent(RIF(1).mignitude()) + Real Field with 53 bits of precision and rounding RNDD """ cdef RealNumber x - x = (self._parent).__middle_field._new() + x = (self._parent).__lower_field._new() mpfi_mig(x.value, self.value) return x @@ -3925,7 +3910,7 @@ cdef class RealIntervalFieldElement(RingElement): sage: a.min('x') Traceback (most recent call last): ... - TypeError: Unable to convert number to real interval. + TypeError: unable to convert 'x' to real interval """ cdef RealIntervalFieldElement constructed cdef RealIntervalFieldElement result @@ -4029,7 +4014,7 @@ cdef class RealIntervalFieldElement(RingElement): sage: a.max('x') Traceback (most recent call last): ... - TypeError: Unable to convert number to real interval. + TypeError: unable to convert 'x' to real interval """ cdef RealIntervalFieldElement constructed cdef RealIntervalFieldElement result @@ -5011,22 +4996,15 @@ cdef class RealIntervalFieldElement(RingElement): A :class:`RealIntervalFieldElement`. - This uses the optional `arb library `_. - You may have to install it via ``sage -i arb``. - EXAMPLES:: - sage: psi_1 = RIF(1).psi() # optional - arb - sage: psi_1 # optional - arb + sage: psi_1 = RIF(1).psi() + sage: psi_1 -0.577215664901533? - sage: psi_1.overlaps(-RIF.euler_constant()) # optional - arb + sage: psi_1.overlaps(-RIF.euler_constant()) True """ - try: - from sage.rings.real_arb import RealBallField - except ImportError: - raise TypeError("The optional arb package is not installed. " - "Consider installing it via 'sage -i arb'") + from sage.rings.real_arb import RealBallField return RealBallField(self.precision())(self).psi()._real_mpfi_(self._parent) # MPFI does not have: agm, erf, gamma, zeta diff --git a/src/sage/rings/real_mpfr.pxd b/src/sage/rings/real_mpfr.pxd index 4b2b1465cf8..cef3184333c 100644 --- a/src/sage/rings/real_mpfr.pxd +++ b/src/sage/rings/real_mpfr.pxd @@ -2,9 +2,7 @@ from sage.libs.mpfr cimport * cimport sage.rings.ring cimport sage.structure.element - -cdef extern from "pari/pari.h": - ctypedef long* GEN +from sage.libs.pari.types cimport GEN cdef class RealNumber(sage.structure.element.RingElement) # forward decl @@ -14,13 +12,15 @@ cdef class RealField_class(sage.rings.ring.Field): cdef bint sci_not cdef mpfr_rnd_t rnd cdef object rnd_str - cdef RealNumber _new(self) - + cdef inline RealNumber _new(self): + """Return a new real number with parent ``self``.""" + return (RealNumber.__new__(RealNumber, self)) cdef class RealNumber(sage.structure.element.RingElement): cdef mpfr_t value - cdef char init - cdef RealNumber _new(self) + cdef inline RealNumber _new(self): + """Return a new real number with same parent as ``self``.""" + return (RealNumber.__new__(RealNumber, self._parent)) cdef _set(self, x, int base) cdef _set_from_GEN_REAL(self, GEN g) cdef RealNumber abs(RealNumber self) diff --git a/src/sage/rings/real_mpfr.pyx b/src/sage/rings/real_mpfr.pyx index ac9e423689f..c41d68d6e81 100644 --- a/src/sage/rings/real_mpfr.pyx +++ b/src/sage/rings/real_mpfr.pyx @@ -154,8 +154,6 @@ import sage.rings.infinity from sage.structure.parent_gens cimport ParentWithGens -cdef class RealNumber(sage.structure.element.RingElement) - #***************************************************************************** # # Implementation @@ -437,7 +435,6 @@ cdef class RealField_class(sage.rings.ring.Field): Real Field with 17 bits of precision and rounding RNDD """ global MY_MPFR_PREC_MAX - cdef RealNumber rn if prec < MPFR_PREC_MIN or prec > MY_MPFR_PREC_MAX: raise ValueError, "prec (=%s) must be >= %s and <= %s"%( prec, MPFR_PREC_MIN, MY_MPFR_PREC_MAX) @@ -451,36 +448,20 @@ cdef class RealField_class(sage.rings.ring.Field): self.rnd = n self.rnd_str = rnd from sage.categories.fields import Fields - ParentWithGens.__init__(self, self, tuple([]), False, category = Fields()) - - # hack, we cannot call the constructor here - rn = RealNumber.__new__(RealNumber) - rn._parent = self - mpfr_init2(rn.value, self.__prec) - rn.init = 1 - mpfr_set_d(rn.value, 0.0, self.rnd) + ParentWithGens.__init__(self, self, tuple([]), False, category=Fields().Metric().Complete()) + + # Initialize zero and one + cdef RealNumber rn + rn = self._new() + mpfr_set_zero(rn.value, 1) self._zero_element = rn - rn = RealNumber.__new__(RealNumber) - rn._parent = self - mpfr_init2(rn.value, self.__prec) - rn.init = 1 - mpfr_set_d(rn.value, 1.0, self.rnd) + rn = self._new() + mpfr_set_ui(rn.value, 1, MPFR_RNDZ) self._one_element = rn self._populate_coercion_lists_(convert_method_name='_mpfr_') - cdef RealNumber _new(self): - """ - Return a new real number with parent ``self``. - """ - cdef RealNumber x - x = RealNumber.__new__(RealNumber) - x._parent = self - mpfr_init2(x.value, self.__prec) - x.init = 1 - return x - def _repr_(self): """ Return a string representation of ``self``. @@ -969,7 +950,6 @@ cdef class RealField_class(sage.rings.ring.Field): else: return RealField(prec, self.sci_not, _rounding_modes[self.rnd]) - # int mpfr_const_pi (mpfr_t rop, mp_rnd_t rnd) def pi(self): r""" Return `\pi` to the precision of this field. @@ -999,8 +979,6 @@ cdef class RealField_class(sage.rings.ring.Field): if self.__prec > SIG_PREC_THRESHOLD: sig_off() return x - - # int mpfr_const_euler (mpfr_t rop, mp_rnd_t rnd) def euler_constant(self): """ Returns Euler's gamma constant to the precision of this field. @@ -1017,7 +995,6 @@ cdef class RealField_class(sage.rings.ring.Field): sig_off() return x - # int mpfr_const_catalan (mpfr_t rop, mp_rnd_t rnd) def catalan_constant(self): """ Returns Catalan's constant to the precision of this field. @@ -1034,7 +1011,6 @@ cdef class RealField_class(sage.rings.ring.Field): if self.__prec > SIG_PREC_THRESHOLD: sig_off() return x - # int mpfr_const_log2 (mpfr_t rop, mp_rnd_t rnd) def log2(self): r""" Return `\log(2)` (i.e., the natural log of 2) to the precision @@ -1252,8 +1228,6 @@ cdef class RealField_class(sage.rings.ring.Field): # # RealNumber -- element of Real Field # -# -# #***************************************************************************** cdef class RealLiteral(RealNumber) @@ -1270,18 +1244,29 @@ cdef class RealNumber(sage.structure.element.RingElement): internal precision, in order to avoid confusing roundoff issues that occur because numbers are stored internally in binary. """ - cdef RealNumber _new(self): + def __cinit__(self, parent, x=None, base=None): """ - Return a new real number with same parent as self. + Initialize the parent of this element and allocate memory + + TESTS:: + + sage: from sage.rings.real_mpfr import RealNumber + sage: RealNumber.__new__(RealNumber, None) + Traceback (most recent call last): + ... + TypeError: Cannot convert NoneType to sage.rings.real_mpfr.RealField_class + sage: RealNumber.__new__(RealNumber, ZZ) + Traceback (most recent call last): + ... + TypeError: Cannot convert sage.rings.integer_ring.IntegerRing_class to sage.rings.real_mpfr.RealField_class + sage: RealNumber.__new__(RealNumber, RR) + NaN """ - cdef RealNumber x - x = RealNumber.__new__(RealNumber) - x._parent = self._parent - mpfr_init2(x.value, (self._parent).__prec) - x.init = 1 - return x + cdef RealField_class p = parent + mpfr_init2(self.value, p.__prec) + self._parent = p - def __init__(self, RealField_class parent, x=0, int base=10): + def __init__(self, parent, x=0, int base=10): """ Create a real number. Should be called by first creating a RealField, as illustrated in the examples. @@ -1326,16 +1311,8 @@ cdef class RealNumber(sage.structure.element.RingElement): sage: TestSuite(R).run() """ - self.init = 0 - if parent is None: - raise TypeError - self._parent = parent - mpfr_init2(self.value, parent.__prec) - self.init = 1 - if x is None: - return - - self._set(x, base) + if x is not None: + self._set(x, base) def _magma_init_(self, magma): r""" @@ -1493,7 +1470,7 @@ cdef class RealNumber(sage.structure.element.RingElement): return (__create__RealNumber_version0, (self._parent, s, 32)) def __dealloc__(self): - if self.init: + if self._parent is not None: mpfr_clear(self.value) def __repr__(self): @@ -3764,10 +3741,11 @@ cdef class RealNumber(sage.structure.element.RingElement): """ return mpfr_sgn(self.value) != 0 - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ - Return the Cython rich comparison operator (see the Cython - documentation for details). + Return ``-1`` if exactly one of the numbers is ``NaN``. Return ``-1`` + if ``left`` is less than ``right``, ``0`` if ``left`` and ``right`` + are equal, and ``1`` if ``left`` is greater than ``right``. EXAMPLES:: @@ -3797,27 +3775,6 @@ cdef class RealNumber(sage.structure.element.RingElement): False sage: RR('1')>=RR('1') True - """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - Return ``-1`` if exactly one of the numbers is ``NaN``. Return ``-1`` - if ``left`` is less than ``right``, ``0`` if ``left`` and ``right`` - are equal, and ``1`` if ``left`` is greater than ``right``. - - EXAMPLES:: - - sage: RR('1')<=RR('1') - True - sage: RR('1')RR('1') - False - sage: RR('1')>=RR('1') - True - sage: RR('nan')==R('nan') - False sage: RR('inf')==RR('inf') True sage: RR('inf')==RR('-inf') @@ -4374,15 +4331,6 @@ cdef class RealNumber(sage.structure.element.RingElement): sig_off() return x - ########################################################## - # it would be nice to get zero back here: - # sage: R(-1).acos().sin() - # _57 = -0.50165576126683320234e-19 - # i think this could be "fixed" by using MPFI. (put on to-do list.) - # - # this seems to work ok: - # sage: R(-1).acos().cos() - # _58 = -0.10000000000000000000e1 def sin(self): """ Return the sine of ``self``. @@ -4437,9 +4385,6 @@ cdef class RealNumber(sage.structure.element.RingElement): sig_off() return x,y - - # int mpfr_sin_cos (mpfr_t rop, mpfr_t op, mpfr_t, mp_rnd_t rnd) - def arccos(self): """ Return the inverse cosine of ``self``. @@ -4493,10 +4438,6 @@ cdef class RealNumber(sage.structure.element.RingElement): sig_off() return x - #int mpfr_acos _PROTO ((mpfr_ptr, mpfr_srcptr, mp_rnd_t)); - #int mpfr_asin _PROTO ((mpfr_ptr, mpfr_srcptr, mp_rnd_t)); - #int mpfr_atan _PROTO ((mpfr_ptr, mpfr_srcptr, mp_rnd_t)); - def cosh(self): """ Return the hyperbolic cosine of ``self``. @@ -5651,41 +5592,6 @@ def __create__RealNumber_version0(parent, x, base=10): return RealNumber(parent, x, base=base) -cdef inline RealNumber empty_RealNumber(RealField_class parent): - """ - Create and return an empty initialized real number. - - EXAMPLES: - - These are indirect tests of this function:: - - sage: from sage.rings.real_mpfr import RRtoRR - sage: R10 = RealField(10) - sage: R100 = RealField(100) - sage: f = RRtoRR(R100, R10) - sage: a = R100(1.2) - sage: f(a) - 1.2 - sage: g = f.section() - sage: g - Generic map: - From: Real Field with 10 bits of precision - To: Real Field with 100 bits of precision - sage: g(f(a)) # indirect doctest - 1.1992187500000000000000000000 - sage: b = R10(2).sqrt() - sage: f(g(b)) - 1.4 - sage: f(g(b)) == b - True - """ - - cdef RealNumber y = RealNumber.__new__(RealNumber) - y._parent = parent - mpfr_init2(y.value, parent.__prec) - y.init = 1 - return y - cdef class RRtoRR(Map): cpdef Element _call_(self, x): """ @@ -5712,7 +5618,7 @@ cdef class RRtoRR(Map): True """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() if type(x) is RealLiteral: mpfr_set_str(y.value, (x).literal, (x).base, parent.rnd) else: @@ -5745,7 +5651,7 @@ cdef class ZZtoRR(Map): 1.2346e8 """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() mpfr_set_z(y.value, (x).value, parent.rnd) return y @@ -5760,7 +5666,7 @@ cdef class QQtoRR(Map): -0.33333333333333333333333333333333333333333333333333333333333 """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() mpfr_set_q(y.value, (x).value, parent.rnd) return y @@ -5780,7 +5686,7 @@ cdef class double_toRR(Map): 3.1415926535897931159979634685441851615905761718750000000000 """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() mpfr_set_d(y.value, x, parent.rnd) return y @@ -5808,6 +5714,6 @@ cdef class int_toRR(Map): 1.00000000000000 """ cdef RealField_class parent = self._codomain - cdef RealNumber y = empty_RealNumber(parent) + cdef RealNumber y = parent._new() mpfr_set_si(y.value, x, parent.rnd) return y diff --git a/src/sage/rings/ring.pyx b/src/sage/rings/ring.pyx index 7d84ca3bab1..b62886b03c9 100644 --- a/src/sage/rings/ring.pyx +++ b/src/sage/rings/ring.pyx @@ -68,7 +68,7 @@ AUTHORS: from sage.misc.cachefunc import cached_method -from sage.structure.element import get_coercion_model +from sage.structure.element cimport coercion_model from sage.structure.parent_gens cimport ParentWithGens from sage.structure.parent cimport Parent from sage.structure.category_object import check_default_category @@ -134,11 +134,14 @@ cdef class Ring(ParentWithGens): Test agaings another bug fixed in :trac:`9944`:: sage: QQ['x'].category() - Join of Category of euclidean domains and Category of commutative algebras over quotient fields + Join of Category of euclidean domains + and Category of commutative algebras over (quotient fields and metric spaces) sage: QQ['x','y'].category() - Join of Category of unique factorization domains and Category of commutative algebras over quotient fields + Join of Category of unique factorization domains + and Category of commutative algebras over (quotient fields and metric spaces) sage: PolynomialRing(MatrixSpace(QQ,2),'x').category() - Category of algebras over (algebras over quotient fields and infinite sets) + Category of algebras over (algebras over + (quotient fields and metric spaces) and infinite sets) sage: PolynomialRing(SteenrodAlgebra(2),'x').category() Category of algebras over graded hopf algebras with basis over Finite Field of size 2 @@ -1308,7 +1311,7 @@ cdef class CommutativeRing(Ring): try: return self.fraction_field() except (NotImplementedError,TypeError): - return get_coercion_model().division_parent(self) + return coercion_model.division_parent(self) def __pow__(self, n, _): """ diff --git a/src/sage/rings/semirings/tropical_semiring.pyx b/src/sage/rings/semirings/tropical_semiring.pyx index cc4d1104ab8..fa06154e1e6 100644 --- a/src/sage/rings/semirings/tropical_semiring.pyx +++ b/src/sage/rings/semirings/tropical_semiring.pyx @@ -134,10 +134,11 @@ cdef class TropicalSemiringElement(RingElement): return hash(self._val) # Comparisons - - def __richcmp__(left, right, int op): + cpdef int _cmp_(left, Element right) except -2: """ - Rich comparisons. + Return ``-1`` if ``left`` is less than ``right``, ``0`` if + ``left`` and ``right`` are equal, and ``1`` if ``left`` is + greater than ``right``. EXAMPLES:: @@ -170,30 +171,6 @@ cdef class TropicalSemiringElement(RingElement): False sage: T.infinity() >= T.infinity() True - """ - return (left)._richcmp(right, op) - - cpdef int _cmp_(left, Element right) except -2: - """ - Return ``-1`` if ``left`` is less than ``right``, ``0`` if - ``left`` and ``right`` are equal, and ``1`` if ``left`` is - greater than ``right``. - - EXAMPLES:: - - sage: T = TropicalSemiring(QQ) - sage: T(2) == T(2) - True - sage: T(2) != T(4) - True - sage: T(2) < T(4) - True - sage: T(2) > T(4) - False - sage: T.infinity() == T.infinity() - True - sage: T(4) <= T.infinity() - True Using the `\max` definition:: diff --git a/src/sage/rings/universal_cyclotomic_field.py b/src/sage/rings/universal_cyclotomic_field.py index 8373d8e7dde..d4751177d3b 100644 --- a/src/sage/rings/universal_cyclotomic_field.py +++ b/src/sage/rings/universal_cyclotomic_field.py @@ -533,15 +533,24 @@ def __hash__(self): sage: UCF = UniversalCyclotomicField() sage: hash(UCF.zero()) # indirect doctest - 1302034650 # 32-bit - 3713081631936575706 # 64-bit + 0 sage: hash(UCF.gen(3,2)) 313156239 # 32-bit 1524600308199219855 # 64-bit + + TESTS: + + See :trac:`19514`:: + + sage: hash(UCF.one()) + 1 """ k = self._obj.Conductor().sage() coeffs = self._obj.CoeffsCyc(k).sage() - return hash((k,) + tuple(coeffs)) + if k == 1: + return hash(coeffs[0]) + else: + return hash((k,) + tuple(coeffs)) def _algebraic_(self, R): r""" diff --git a/src/sage/schemes/elliptic_curves/ell_local_data.py b/src/sage/schemes/elliptic_curves/ell_local_data.py index 5dd59f12ab2..da434fe9c83 100644 --- a/src/sage/schemes/elliptic_curves/ell_local_data.py +++ b/src/sage/schemes/elliptic_curves/ell_local_data.py @@ -705,7 +705,7 @@ def _tate(self, proof = None, globally = False): sage: E.tamagawa_number(K.ideal(2)) 4 - This is to show that the bug :trac: `11630` is fixed. (The computation of the class group would produce a warning):: + This is to show that the bug :trac:`11630` is fixed. (The computation of the class group would produce a warning):: sage: K. = NumberField(x^7-2*x+177) sage: E = EllipticCurve([0,1,0,t,t]) diff --git a/src/sage/schemes/elliptic_curves/ell_number_field.py b/src/sage/schemes/elliptic_curves/ell_number_field.py index f4577fa0007..9867d3e3144 100644 --- a/src/sage/schemes/elliptic_curves/ell_number_field.py +++ b/src/sage/schemes/elliptic_curves/ell_number_field.py @@ -109,6 +109,8 @@ import gal_reps_number_field +from six import reraise as raise_ + class EllipticCurve_number_field(EllipticCurve_field): r""" Elliptic curve over a number field. @@ -893,7 +895,7 @@ def _reduce_model(self): (a1, a2, a3, a4, a6) = [ZK(a) for a in self.a_invariants()] except TypeError: import sys - raise TypeError, "_reduce_model() requires an integral model.", sys.exc_info()[2] + raise_(TypeError, "_reduce_model() requires an integral model.", sys.exc_info()[2]) # N.B. Must define s, r, t in the right order. if ZK.absolute_degree() == 1: diff --git a/src/sage/schemes/elliptic_curves/padic_lseries.py b/src/sage/schemes/elliptic_curves/padic_lseries.py index 1edef217bd7..406681d297f 100644 --- a/src/sage/schemes/elliptic_curves/padic_lseries.py +++ b/src/sage/schemes/elliptic_curves/padic_lseries.py @@ -827,7 +827,7 @@ def series(self, n=2, quadratic_twist=+1, prec=5, eta=0): sage: L.series(3) O(3^5) + O(3^2)*T + (2 + 2*3 + O(3^2))*T^2 + (2 + O(3))*T^3 + (1 + O(3))*T^4 + O(T^5) - Checks if the precision can be changed (:trac: `5846`):: + Checks if the precision can be changed (:trac:`5846`):: sage: L.series(3,prec=4) O(3^5) + O(3^2)*T + (2 + 2*3 + O(3^2))*T^2 + (2 + O(3))*T^3 + O(T^4) @@ -1098,7 +1098,7 @@ def series(self, n=3, quadratic_twist = +1, prec=5, eta = 0): Univariate Quotient Polynomial Ring in alpha over 3-adic Field with capped relative precision 2 with modulus (1 + O(3^2))*x^2 + (3 + O(3^3))*x + (3 + O(3^3)) - An example where we only compute the leading term (:trac: `15737`):: + An example where we only compute the leading term (:trac:`15737`):: sage: E = EllipticCurve("17a1") sage: L = E.padic_lseries(3) diff --git a/src/sage/schemes/elliptic_curves/sha_tate.py b/src/sage/schemes/elliptic_curves/sha_tate.py index ec1bd73eb02..ab5e46e2a73 100644 --- a/src/sage/schemes/elliptic_curves/sha_tate.py +++ b/src/sage/schemes/elliptic_curves/sha_tate.py @@ -522,10 +522,10 @@ def an_padic(self, p, prec=0, use_twists=True): 4 + O(5) sage: EllipticCurve('448c5').sha().an_padic(7,prec=4, use_twists=False) # long time (2s on sage.math, 2011) 2 + 7 + O(7^6) - sage: EllipticCurve([-19,34]).sha().an_padic(5) # see :trac: `6455`, long time (4s on sage.math, 2011) + sage: EllipticCurve([-19,34]).sha().an_padic(5) # see :trac:`6455`, long time (4s on sage.math, 2011) 1 + O(5) - Test for :trac: `15737`:: + Test for :trac:`15737`:: sage: E = EllipticCurve([-100,0]) sage: s = E.sha() diff --git a/src/sage/schemes/generic/morphism.py b/src/sage/schemes/generic/morphism.py index 1de864415ed..7d540df7ec5 100644 --- a/src/sage/schemes/generic/morphism.py +++ b/src/sage/schemes/generic/morphism.py @@ -1339,7 +1339,7 @@ def change_ring(self,R, **kwds): Defn: Defined on coordinates by sending (x : y) to (x : y) - Check that :trac:'16834' is fixed:: + Check that :trac:`16834` is fixed:: sage: A. = AffineSpace(RR,3) sage: h = Hom(A,A) diff --git a/src/sage/schemes/toric/divisor.py b/src/sage/schemes/toric/divisor.py index 1eed28a1390..92bfa8ba654 100644 --- a/src/sage/schemes/toric/divisor.py +++ b/src/sage/schemes/toric/divisor.py @@ -1168,16 +1168,16 @@ def is_ample(self): Example 6.1.3, 6.1.11, 6.1.17 of [CLS]_:: + sage: from itertools import product sage: fan = Fan(cones=[(0,1), (1,2), (2,3), (3,0)], - ... rays=[(-1,2), (0,1), (1,0), (0,-1)]) + ....: rays=[(-1,2), (0,1), (1,0), (0,-1)]) sage: F2 = ToricVariety(fan,'u1, u2, u3, u4') sage: def D(a,b): return a*F2.divisor(2) + b*F2.divisor(3) - ... - sage: [ (a,b) for a,b in CartesianProduct(range(-3,3),range(-3,3)) - ... if D(a,b).is_ample() ] + sage: [ (a,b) for a,b in product(range(-3,3), repeat=2) + ....: if D(a,b).is_ample() ] [(1, 1), (1, 2), (2, 1), (2, 2)] - sage: [ (a,b) for a,b in CartesianProduct(range(-3,3),range(-3,3)) - ... if D(a,b).is_nef() ] + sage: [ (a,b) for a,b in product(range(-3,3), repeat=2) + ....: if D(a,b).is_nef() ] [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] @@ -1246,16 +1246,16 @@ def is_nef(self): Example 6.1.3, 6.1.11, 6.1.17 of [CLS]_:: + sage: from itertools import product sage: fan = Fan(cones=[(0,1), (1,2), (2,3), (3,0)], - ... rays=[(-1,2), (0,1), (1,0), (0,-1)]) + ....: rays=[(-1,2), (0,1), (1,0), (0,-1)]) sage: F2 = ToricVariety(fan,'u1, u2, u3, u4') sage: def D(a,b): return a*F2.divisor(2) + b*F2.divisor(3) - ... - sage: [ (a,b) for a,b in CartesianProduct(range(-3,3),range(-3,3)) - ... if D(a,b).is_ample() ] + sage: [ (a,b) for a,b in product(range(-3,3), repeat=2) + ....: if D(a,b).is_ample() ] [(1, 1), (1, 2), (2, 1), (2, 2)] - sage: [ (a,b) for a,b in CartesianProduct(range(-3,3),range(-3,3)) - ... if D(a,b).is_nef() ] + sage: [ (a,b) for a,b in product(range(-3,3), repeat=2) + ....: if D(a,b).is_nef() ] [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] """ diff --git a/src/sage/schemes/toric/points.py b/src/sage/schemes/toric/points.py index ca902bf97eb..aedb22ac2db 100644 --- a/src/sage/schemes/toric/points.py +++ b/src/sage/schemes/toric/points.py @@ -35,10 +35,9 @@ #***************************************************************************** - +import itertools from copy import copy -from sage.combinat.cartesian_product import CartesianProduct from sage.misc.all import powerset, prod from sage.misc.cachefunc import cached_method from sage.rings.arith import gcd @@ -220,7 +219,7 @@ def _Chow_group_free(self): units = self.units() result = [] ker = self.rays().matrix().integer_kernel().matrix() - for phases in CartesianProduct(*([units] * ker.nrows())): + for phases in itertools.product(units, repeat=ker.nrows()): phases = tuple(prod(mu**exponent for mu, exponent in zip(phases, column)) for column in ker.columns()) result.append(phases) @@ -288,9 +287,10 @@ def rescalings(self): if len(tors) == 0: # optimization for smooth fans return free result = set(free) - for f, t in CartesianProduct(free, tors): - phases = tuple(x*y for x, y in zip(f, t)) - result.add(phases) + for f in free: + for t in tors: + phases = tuple(x*y for x, y in zip(f, t)) + result.add(phases) return tuple(sorted(result)) def orbit(self, point): @@ -390,7 +390,7 @@ def coordinate_iter(self): patch = copy(big_torus) for i in cone.ambient_ray_indices(): patch[i] = [zero] - for p in CartesianProduct(*patch): + for p in itertools.product(*patch): yield tuple(p) def __iter__(self): @@ -869,7 +869,7 @@ def solutions_serial(self, inhomogeneous_equations, log_range): OUTPUT: All solutions (as tuple of log inhomogeneous coordinates) in - the Cartesian product of the ranges. + the cartesian product of the ranges. EXAMPLES:: @@ -881,10 +881,10 @@ def solutions_serial(self, inhomogeneous_equations, log_range): sage: ffe.solutions_serial([s^2-1, s^6-s^2], [range(6)]) sage: list(_) - [[0], [3]] + [(0,), (3,)] """ - from sage.combinat.cartesian_product import CartesianProduct - for log_t in CartesianProduct(*log_range): + from itertools import product + for log_t in product(*log_range): t = self.ambient.exp(log_t) if all(poly(t) == 0 for poly in inhomogeneous_equations): yield log_t @@ -909,14 +909,14 @@ def solutions(self, inhomogeneous_equations, log_range): sage: ffe.solutions([s^2-1, s^6-s^2], [range(6)]) sage: sorted(_) - [[0], [3]] + [(0,), (3,)] """ # Do simple cases in one process (this includes most doctests) if len(log_range) <= 2: for log_t in self.solutions_serial(inhomogeneous_equations, log_range): yield log_t raise StopIteration - # Parallelize the outermost loop of the Cartesian product + # Parallelize the outermost loop of the cartesian product work = [([[r]] + log_range[1:],) for r in log_range[0]] from sage.parallel.decorate import Parallel parallel = Parallel() diff --git a/src/sage/sets/family.py b/src/sage/sets/family.py index 90f3a4f437b..b76b81f2ddd 100644 --- a/src/sage/sets/family.py +++ b/src/sage/sets/family.py @@ -1009,7 +1009,7 @@ def cardinality(self): Check that :trac:`15195` is fixed:: - sage: C = CartesianProduct(PositiveIntegers(), [1,2,3]) + sage: C = cartesian_product([PositiveIntegers(), [1,2,3]]) sage: C.cardinality() +Infinity sage: F = Family(C, lambda x: x) diff --git a/src/sage/sets/finite_set_maps.py b/src/sage/sets/finite_set_maps.py index daec1e077fd..a79e5e14cdb 100644 --- a/src/sage/sets/finite_set_maps.py +++ b/src/sage/sets/finite_set_maps.py @@ -18,6 +18,8 @@ #***************************************************************************** +import itertools + from sage.structure.parent import Parent from sage.rings.integer import Integer from sage.structure.unique_representation import UniqueRepresentation @@ -25,7 +27,6 @@ from sage.categories.monoids import Monoids from sage.categories.enumerated_sets import EnumeratedSets from sage.sets.finite_enumerated_set import FiniteEnumeratedSet -from sage.combinat.cartesian_product import CartesianProduct from sage.sets.integer_range import IntegerRange from sage.sets.finite_set_map_cy import ( FiniteSetMap_MN, FiniteSetMap_Set, @@ -338,7 +339,7 @@ def __iter__(self): sage: FiniteSetMaps(1,1).list() [[0]] """ - for v in CartesianProduct(*([range(self._n)]*self._m)): + for v in itertools.product(range(self._n), repeat=self._m): yield self._from_list_(v) def _from_list_(self, v): diff --git a/src/sage/sets/set.py b/src/sage/sets/set.py index 83d8bdfd5e0..12ef7a8667e 100644 --- a/src/sage/sets/set.py +++ b/src/sage/sets/set.py @@ -36,14 +36,19 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.structure.element import Element -from sage.structure.parent import Parent, Set_generic + from sage.misc.latex import latex -import sage.rings.infinity +from sage.misc.prandom import choice from sage.misc.misc import is_iterator + +from sage.structure.element import Element +from sage.structure.parent import Parent, Set_generic + from sage.categories.sets_cat import Sets from sage.categories.enumerated_sets import EnumeratedSets +import sage.rings.infinity + def Set(X=frozenset()): r""" Create the underlying set of ``X``. @@ -224,7 +229,12 @@ def __init__(self, X): if isinstance(X, (int,long)) or is_Integer(X): # The coercion model will try to call Set_object(0) raise ValueError('underlying object cannot be an integer') - Parent.__init__(self, category=Sets()) + + category = Sets() + if X in Sets().Finite() or isinstance(X, (tuple,list,set,frozenset)): + category = Sets().Finite() + + Parent.__init__(self, category=category) self.__object = X def __hash__(self): @@ -685,6 +695,21 @@ def __init__(self, X): """ Set_object.__init__(self, X) + def random_element(self): + r""" + Return a random element in this set. + + EXAMPLES:: + + sage: Set([1,2,3]).random_element() # random + 2 + """ + try: + return self.object().random_element() + except AttributeError: + # TODO: this very slow! + return choice(self.list()) + def is_finite(self): r""" Return ``True`` as this is a finite set. diff --git a/src/sage/sets/set_from_iterator.py b/src/sage/sets/set_from_iterator.py index 3ab4303cf7f..3546b11f6a3 100644 --- a/src/sage/sets/set_from_iterator.py +++ b/src/sage/sets/set_from_iterator.py @@ -180,6 +180,24 @@ def __init__(self, f, args=None, kwds=None, name=None, category=None, cache=Fals *getattr(self, '_args', ()), **getattr(self, '_kwds', {})))) + def __hash__(self): + r""" + A simple hash using the first elements of the set. + + EXAMPLES:: + + sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator + sage: E = EnumeratedSetFromIterator(xsrange, (1,200)) + sage: hash(E) + 4600916458883504074 # 64-bit + -2063607862 # 32-bit + """ + try: + return hash(self._cache[:13]) + except AttributeError: + from itertools import islice + return hash(tuple(islice(self, 13))) + def __reduce__(self): r""" Support for pickle. diff --git a/src/sage/structure/category_object.pyx b/src/sage/structure/category_object.pyx index be78efcf49a..405ff0d9d4d 100644 --- a/src/sage/structure/category_object.pyx +++ b/src/sage/structure/category_object.pyx @@ -235,7 +235,8 @@ cdef class CategoryObject(sage_object.SageObject): sage: ZZ.categories() [Join of Category of euclidean domains - and Category of infinite enumerated sets, + and Category of infinite enumerated sets + and Category of metric spaces, Category of euclidean domains, Category of principal ideal domains, Category of unique factorization domains, diff --git a/src/sage/structure/coerce_actions.pyx b/src/sage/structure/coerce_actions.pyx index 30da0d9a117..18f19a13d95 100644 --- a/src/sage/structure/coerce_actions.pyx +++ b/src/sage/structure/coerce_actions.pyx @@ -18,14 +18,13 @@ import operator include "sage/ext/interrupt.pxi" from cpython.int cimport * from cpython.number cimport * -from sage.structure.element cimport parent_c +from sage.structure.element cimport parent_c, coercion_model from sage.categories.action import InverseAction, PrecomposedAction from coerce_exceptions import CoercionException cdef _record_exception(): - from element import get_coercion_model - get_coercion_model()._record_exception() + coercion_model._record_exception() cdef inline an_element(R): if isinstance(R, Parent): diff --git a/src/sage/structure/element.pxd b/src/sage/structure/element.pxd index c2317e745ae..66e6047cb52 100644 --- a/src/sage/structure/element.pxd +++ b/src/sage/structure/element.pxd @@ -148,5 +148,6 @@ cdef class CoercionModel: cpdef canonical_coercion(self, x, y) cpdef bin_op(self, x, y, op) +cdef CoercionModel coercion_model cdef generic_power_c(a, nn, one) diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index b0d93cb1035..a76e61926ec 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -526,9 +526,6 @@ cdef class Element(SageObject): pass return res - def __hash__(self): - return hash(str(self)) - def _im_gens_(self, codomain, im_gens): """ Return the image of ``self`` in codomain under the map that sends @@ -964,6 +961,19 @@ cdef class Element(SageObject): return 1 raise + def _cache_key(self): + """ + Provide a hashable key for an element if it is not hashable + + EXAMPLES:: + + sage: a=sage.structure.element.Element(ZZ) + sage: a._cache_key() + (Integer Ring, 'Generic element of a structure') + """ + + return(self.parent(),str(self)) + cdef _richcmp(self, other, int op): """ Compare ``self`` and ``other`` using the coercion framework, diff --git a/src/sage/structure/indexed_generators.py b/src/sage/structure/indexed_generators.py index 2f445492466..5a079f3a7e9 100644 --- a/src/sage/structure/indexed_generators.py +++ b/src/sage/structure/indexed_generators.py @@ -74,6 +74,9 @@ class IndexedGenerators(object): - ``generator_cmp`` -- a comparison function (default: ``cmp``), to use for sorting elements in the output of elements + - ``string_quotes`` -- bool (default: ``True``), if ``True`` then + display string indices with quotes + .. NOTE:: These print options may also be accessed and modified using the @@ -130,7 +133,8 @@ def __init__(self, indices, prefix="x", **kwds): 'scalar_mult': "*", 'latex_scalar_mult': None, 'tensor_symbol': None, - 'generator_cmp': cmp} + 'generator_cmp': cmp, + 'string_quotes': True} # 'bracket': its default value here is None, meaning that # the value of self._repr_option_bracket is used; the default # value of that attribute is True -- see immediately before @@ -186,8 +190,9 @@ def print_options(self, **kwds): - ``latex_scalar_mult`` - ``tensor_symbol`` - ``generator_cmp`` + - ``string_quotes`` - See the documentation for :class:`CombinatorialFreeModule` for + See the documentation for :class:`IndexedGenerators` for descriptions of the effects of setting each of these options. OUTPUT: if the user provides any input, set the appropriate @@ -209,7 +214,8 @@ def print_options(self, **kwds): [('bracket', '('), ('generator_cmp', ), ('latex_bracket', False), ('latex_prefix', None), ('latex_scalar_mult', None), ('prefix', 'x'), - ('scalar_mult', '*'), ('tensor_symbol', None)] + ('scalar_mult', '*'), ('string_quotes', True), + ('tensor_symbol', None)] sage: F.print_options(bracket='[') # reset """ # don't just use kwds.get(...) because I want to distinguish @@ -220,7 +226,7 @@ def print_options(self, **kwds): # TODO: make this into a set and put it in a global variable? if option in ['prefix', 'latex_prefix', 'bracket', 'latex_bracket', 'scalar_mult', 'latex_scalar_mult', 'tensor_symbol', - 'generator_cmp' + 'generator_cmp', 'string_quotes' ]: self._print_options[option] = kwds[option] else: @@ -262,6 +268,9 @@ def _repr_generator(self, m): sage: e = F.basis() sage: e['a'] + 2*e['b'] # indirect doctest F['a'] + 2*F['b'] + sage: F.print_options(string_quotes=False) + sage: e['a'] + 2*e['b'] + F[a] + 2*F[b] sage: QS3 = CombinatorialFreeModule(QQ, Permutations(3), prefix="") sage: original_print_options = QS3.print_options() @@ -309,6 +318,9 @@ def _repr_generator(self, m): else: left = bracket right = bracket + quotes = self._print_options.get('string_quotes', True) + if not quotes and isinstance(m, str): + return self.prefix() + left + m + right return self.prefix() + left + repr(m) + right # mind the (m), to accept a tuple for m def _ascii_art_generator(self, m): diff --git a/src/sage/structure/parent.pyx b/src/sage/structure/parent.pyx index 1936d2b69b9..90ff244043c 100644 --- a/src/sage/structure/parent.pyx +++ b/src/sage/structure/parent.pyx @@ -95,7 +95,7 @@ This came up in some subtle bug once:: """ from types import MethodType -from element cimport parent_c +from .element cimport parent_c, coercion_model cimport sage.categories.morphism as morphism cimport sage.categories.map as map from sage.structure.debug_options import debug @@ -118,8 +118,7 @@ dummy_attribute_error = AttributeError(dummy_error_message) cdef _record_exception(): - from element import get_coercion_model - get_coercion_model()._record_exception() + coercion_model._record_exception() cdef object _Integer cdef bint is_Integer(x): @@ -805,6 +804,7 @@ cdef class Parent(category_object.CategoryObject): running ._test_eq() . . . pass running ._test_euclidean_degree() . . . pass running ._test_gcd_vs_xgcd() . . . pass + running ._test_metric() . . . pass running ._test_not_implemented_methods() . . . pass running ._test_one() . . . pass running ._test_pickling() . . . pass @@ -875,6 +875,7 @@ cdef class Parent(category_object.CategoryObject): _test_eq _test_euclidean_degree _test_gcd_vs_xgcd + _test_metric _test_not_implemented_methods _test_one _test_pickling @@ -1117,7 +1118,7 @@ cdef class Parent(category_object.CategoryObject): it is a ring, from the point of view of categories:: sage: MS.category() - Category of infinite algebras over quotient fields + Category of infinite algebras over (quotient fields and metric spaces) sage: MS in Rings() True @@ -1808,7 +1809,6 @@ cdef class Parent(category_object.CategoryObject): if embedding is not None: self.register_embedding(embedding) - def _unset_coercions_used(self): r""" Pretend that this parent has never been interrogated by the coercion @@ -1820,8 +1820,7 @@ cdef class Parent(category_object.CategoryObject): For internal use only! """ self._coercions_used = False - import sage.structure.element - sage.structure.element.get_coercion_model().reset_cache() + coercion_model.reset_cache() def _unset_embedding(self): r""" @@ -2082,10 +2081,10 @@ cdef class Parent(category_object.CategoryObject): [ 0 1] [-272118 0] - sage: a.matrix() * b + sage: a.matrix() * b.matrix() [-272118 0] [ 0 -462] - sage: a * b.matrix() + sage: a.matrix() * b.matrix() [-272118 0] [ 0 -462] """ diff --git a/src/sage/symbolic/expression_conversions.py b/src/sage/symbolic/expression_conversions.py index 64d9ae6768f..63dfce65952 100644 --- a/src/sage/symbolic/expression_conversions.py +++ b/src/sage/symbolic/expression_conversions.py @@ -587,7 +587,7 @@ def derivative(self, ex, operator): # symbolic variable, yet we would like to treat it like # one. So, we replace the argument `1` with a temporary # variable e.g. `t0` and then evaluate the derivative - # f'(t0) symbolically at t0=1. See trac #12796. + # f'(t0) symbolically at t0=1. See :trac:`12796`. temp_args = [SR.var("t%s"%i) for i in range(len(args))] f = operator.function()(*temp_args) params = operator.parameter_set() @@ -668,7 +668,7 @@ class SympyConverter(Converter): TESTS: - Make sure we can convert I (trac #6424):: + Make sure we can convert I (:trac:`6424`):: sage: bool(I._sympy_() == I) True @@ -1196,7 +1196,7 @@ def __init__(self, ex, *vars): sage: ff(1.0,2.0,3.0) 4.1780638977... - Using _fast_float_ without specifying the variable names is + Using ``_fast_float_`` without specifying the variable names is deprecated:: sage: f = x._fast_float_() @@ -1208,8 +1208,8 @@ def __init__(self, ex, *vars): sage: f(1.2) 1.2 - Using _fast_float_ on a function which is the identity is - now supported (see Trac 10246):: + Using ``_fast_float_`` on a function which is the identity is + now supported (see :trac:`10246`):: sage: f = symbolic_expression(x).function(x) sage: f._fast_float_(x) diff --git a/src/sage/symbolic/function.pyx b/src/sage/symbolic/function.pyx index ba2a3b64a26..08d51cfe37d 100644 --- a/src/sage/symbolic/function.pyx +++ b/src/sage/symbolic/function.pyx @@ -22,7 +22,7 @@ from expression cimport new_Expression_from_GEx, Expression from ring import SR from sage.structure.coerce cimport py_scalar_to_element, is_numpy_type -from sage.structure.element import get_coercion_model +from sage.structure.element cimport coercion_model # we keep a database of symbolic functions initialized in a session # this also makes the .operator() method of symbolic expressions work @@ -255,7 +255,7 @@ cdef class Function(SageObject): evalf = self._evalf_ # catch AttributeError early if any(self._is_numerical(x) for x in args): if not any(isinstance(x, Expression) for x in args): - p = get_coercion_model().common_parent(*args) + p = coercion_model.common_parent(*args) return evalf(*args, parent=p) except Exception: pass diff --git a/src/sage/symbolic/random_tests.py b/src/sage/symbolic/random_tests.py index a2164f69bda..6b6e5944dbe 100644 --- a/src/sage/symbolic/random_tests.py +++ b/src/sage/symbolic/random_tests.py @@ -333,42 +333,43 @@ def assert_strict_weak_order(a,b,c, cmp_func): sage: x = [SR(unsigned_infinity), SR(oo), -SR(oo)] sage: cmp = matrix(3,3) - sage: indices = list(CartesianProduct(range(0,3),range(0,3))) - sage: for i,j in CartesianProduct(range(0,3),range(0,3)): - ... cmp[i,j] = x[i].__cmp__(x[j]) + sage: for i in range(3): + ....: for j in range(3): + ....: cmp[i,j] = x[i].__cmp__(x[j]) sage: cmp [ 0 -1 -1] [ 1 0 -1] [ 1 1 0] """ from sage.matrix.constructor import matrix - from sage.combinat.cartesian_product import CartesianProduct from sage.combinat.permutation import Permutations x = (a,b,c) cmp = matrix(3,3) - indices = list(CartesianProduct(range(0,3),range(0,3))) - for i,j in indices: - cmp[i,j] = (cmp_func(x[i], x[j]) == 1) # or -1, doesn't matter + for i in range(3): + for j in range(3): + cmp[i,j] = (cmp_func(x[i], x[j]) == 1) # or -1, doesn't matter msg = 'The binary relation failed to be a strict weak order on the elements\n' msg += ' a = '+str(a)+'\n' msg += ' b = '+str(b)+'\n' msg += ' c = '+str(c)+'\n' msg += str(cmp) - for i in range(0,3): # irreflexivity + for i in range(3): + # irreflexivity if cmp[i,i]: raise ValueError(msg) - for i,j in indices: # asymmetric - if i==j: continue - #if x[i] == x[j]: continue - if cmp[i,j] and cmp[j,i]: raise ValueError(msg) + # asymmetric + for j in range(i): + if cmp[i,j] and cmp[j,i]: raise ValueError(msg) - for i,j,k in Permutations([0,1,2]): # transitivity + def incomparable(i,j): + return not (cmp[i,j] or cmp[j,i]) + + for i,j,k in Permutations([0,1,2]): + # transitivity if cmp[i,j] and cmp[j,k] and not cmp[i,k]: raise ValueError(msg) - def incomparable(i,j): - return (not cmp[i,j]) and (not cmp[j,i]) - for i,j,k in Permutations([0,1,2]): # transitivity of equivalence + # transitivity of equivalence if incomparable(i,j) and incomparable(j,k) and not incomparable(i,k): raise ValueError(msg) def test_symbolic_expression_order(repetitions=100): diff --git a/src/sage/symbolic/relation.py b/src/sage/symbolic/relation.py index 45a42df68b7..7fa8459d955 100644 --- a/src/sage/symbolic/relation.py +++ b/src/sage/symbolic/relation.py @@ -764,7 +764,7 @@ def solve(f, *args, **kwds): sage: solve((x==1,x==-1),x,solution_dict=1) [] - This inequality holds for any real ``x`` (trac #8078):: + This inequality holds for any real ``x`` (:trac:`8078`):: sage: solve(x^4+2>0,x) [x < +Infinity] diff --git a/src/sage/symbolic/ring.pyx b/src/sage/symbolic/ring.pyx index 439d02de32c..a7c4bb57f4f 100644 --- a/src/sage/symbolic/ring.pyx +++ b/src/sage/symbolic/ring.pyx @@ -263,6 +263,22 @@ cdef class SymbolicRing(CommutativeRing): sage: SR(factor(x^2*y^3 + x^2*y^2*z - x*y^3 - x*y^2*z - 2*x*y*z - 2*x*z^2 + 2*y*z + 2*z^2)) (x*y^2 - 2*z)*(x - 1)*(y + z) + Asymptotic expansions:: + + sage: A. = AsymptoticRing(growth_group='x^ZZ * y^QQ * log(y)^ZZ', coefficient_ring=ZZ) + doctest:...: FutureWarning: This class/method/function is + marked as experimental. + ... + See http://trac.sagemath.org/17601 for details. + sage: s = SR(3*x^5 * log(y) + 4*y^(3/7) + O(x*log(y))); s + 3*x^5*log(y) + 4*y^(3/7) + Order(x*log(y)) + sage: s.operator(), s.operands() + (, + [3*x^5*log(y), 4*y^(3/7), Order(x*log(y))]) + sage: t = s.operands()[0]; t + 3*x^5*log(y) + sage: t.operator(), t.operands() + (, [x^5, log(y), 3]) """ cdef GEx exp diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 8fbe8419514..15a1fea98e2 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -243,7 +243,8 @@ class :class:`~sage.modules.free_module.FreeModule_generic` sage: M.category() Category of modules over Integer Ring sage: N.category() - Category of modules with basis over (euclidean domains and infinite enumerated sets) + Category of finite dimensional modules with basis over + (euclidean domains and infinite enumerated sets and metric spaces) In other words, the module created by ``FreeModule`` is actually `\ZZ^3`, while, in the absence of any distinguished basis, no *canonical* isomorphism @@ -367,7 +368,8 @@ class :class:`~sage.modules.free_module.FreeModule_generic` sage: V = VectorSpace(QQ,3) ; V Vector space of dimension 3 over Rational Field sage: V.category() - Category of vector spaces with basis over quotient fields + Category of finite dimensional vector spaces with basis + over (quotient fields and metric spaces) sage: V is QQ^3 True sage: V.basis() diff --git a/src/sage/tests/book_schilling_zabrocki_kschur_primer.py b/src/sage/tests/book_schilling_zabrocki_kschur_primer.py index 116e4a37784..15581a2cd6e 100644 --- a/src/sage/tests/book_schilling_zabrocki_kschur_primer.py +++ b/src/sage/tests/book_schilling_zabrocki_kschur_primer.py @@ -475,7 +475,7 @@ Sage example in ./kschurnotes/notes-mike-anne.tex, line 2810:: sage: c = Partition([3,2,1]).to_core(3) - sage: for p in f.support(): + sage: for p in sorted(f.support()): # Sorted for consistant doctest ordering ....: print p, SkewPartition([p.to_core(3).to_partition(),c.to_partition()]) ....: [3, 1, 1, 1, 1] [[5, 2, 1, 1, 1], [5, 2, 1]] diff --git a/src/sage/tests/french_book/domaines_doctest.py b/src/sage/tests/french_book/domaines_doctest.py index f8a15b85660..4628ffd4740 100644 --- a/src/sage/tests/french_book/domaines_doctest.py +++ b/src/sage/tests/french_book/domaines_doctest.py @@ -174,7 +174,7 @@ Sage example in ./domaines.tex, line 415:: sage: QQ.category() - Category of quotient fields + Join of Category of quotient fields and Category of metric spaces Sage example in ./domaines.tex, line 421:: diff --git a/src/sage/tests/french_book/nonlinear_doctest.py b/src/sage/tests/french_book/nonlinear_doctest.py index cb175085117..e51096a073e 100644 --- a/src/sage/tests/french_book/nonlinear_doctest.py +++ b/src/sage/tests/french_book/nonlinear_doctest.py @@ -37,10 +37,11 @@ Sage example in ./nonlinear.tex, line 231:: + sage: from itertools import product sage: def build_complex_roots(degree): ....: R. = PolynomialRing(CDF, 'x') ....: v = [] - ....: for c in CartesianProduct(*[[-1, 1]] * (degree + 1)): + ....: for c in product([-1, 1], repeat=degree+1): ....: v.extend(R(c).roots(multiplicities=False)) ....: return v sage: data = build_complex_roots(12) # long time diff --git a/src/sage/version.py b/src/sage/version.py index 13ac1cb1b05..f9d17c83c11 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,4 +1,4 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '6.10.beta0' -date = '2015-10-15' +version = '6.10.beta3' +date = '2015-11-05' diff --git a/src/setup.py b/src/setup.py index 45694c063ec..cf0013689f4 100755 --- a/src/setup.py +++ b/src/setup.py @@ -324,6 +324,7 @@ def plural(n,noun): ######################################################################## from distutils.command.build_ext import build_ext +from distutils.command.install import install from distutils.dep_util import newer_group from distutils import log @@ -633,6 +634,22 @@ def run_cythonize(): print('Finished cleaning, time: %.2f seconds.' % (time.time() - t)) +######################################################### +### Install also Jupyter kernel spec +######################################################### + +# We cannot just add the installation of the kernel spec to data_files +# since the file is generated, not copied. +class sage_install(install): + def run(self): + install.run(self) + self.install_kernel_spec() + + def install_kernel_spec(self): + from sage.repl.ipython_kernel.install import SageKernelSpec + SageKernelSpec.update() + + ######################################################### ### Distutils ######################################################### @@ -647,6 +664,5 @@ def run_cythonize(): packages = python_packages, data_files = python_data_files, scripts = [], - cmdclass = { 'build_ext': sage_build_ext }, + cmdclass = dict(build_ext=sage_build_ext, install=sage_install), ext_modules = ext_modules) -