diff --git a/VERSION.txt b/VERSION.txt index 3d8643ab2e7..bf5329c460f 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -Sage version 6.3.beta2, released 2014-05-24 +Sage version 6.3.beta3, released 2014-06-04 diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 16e280a5591..184d956cbaf 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=56399592db642ce25cd331edba8ceeecfa211393 -md5=030561c77e141c8e0c7c045b1c301964 -cksum=4180963490 +sha1=94c4a9dc954e0d5653907a5187f61059835c713b +md5=9316e8ffe91a749ea737da0f57f3f1ef +cksum=66239435 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index e85087affde..bb95160cb6e 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -31 +33 diff --git a/build/pkgs/database_cremona_ellcurve/checksums.ini b/build/pkgs/database_cremona_ellcurve/checksums.ini index 635bb7d6e2e..8841af98022 100644 --- a/build/pkgs/database_cremona_ellcurve/checksums.ini +++ b/build/pkgs/database_cremona_ellcurve/checksums.ini @@ -1,4 +1,4 @@ tarball=database_cremona_ellcurve-VERSION.tar.bz2 -sha1=25f256d78bf5c4454d85bb9a22df2d396e4962ba -md5=14de1b11227fdd66e1a87e8101940988 -cksum=2753267872 +sha1=ab96f509574419c03d9f1c8820c4d96042a17a04 +md5=56956829063534142f9f12bc1274c01c +cksum=2400753722 diff --git a/build/pkgs/database_cremona_ellcurve/package-version.txt b/build/pkgs/database_cremona_ellcurve/package-version.txt index 83ada668757..576d8616a6d 100644 --- a/build/pkgs/database_cremona_ellcurve/package-version.txt +++ b/build/pkgs/database_cremona_ellcurve/package-version.txt @@ -1 +1 @@ -20140325 +20140512 diff --git a/build/pkgs/gap_packages/checksums.ini b/build/pkgs/gap_packages/checksums.ini index 8b4c9b21681..c5495eb543b 100644 --- a/build/pkgs/gap_packages/checksums.ini +++ b/build/pkgs/gap_packages/checksums.ini @@ -1,4 +1,4 @@ tarball=gap_packages-VERSION.tar.bz2 -sha1=8fbd475be3aec265e36a600fe9cf3b00bd131d88 -md5=8d3c0fa85af6528bf2b9faf491da4180 -cksum=1526641510 +sha1=9ce61e1947bc5aa0f761f97fb9777e97ab6d3eda +md5=57657e75f9b80f5afc36dcdd3538eada +cksum=2009528425 diff --git a/build/pkgs/gap_packages/package-version.txt b/build/pkgs/gap_packages/package-version.txt index b48b2de9d96..48991896991 100644 --- a/build/pkgs/gap_packages/package-version.txt +++ b/build/pkgs/gap_packages/package-version.txt @@ -1 +1 @@ -4.7.4 +4.7.4.20140531 diff --git a/build/pkgs/gap_packages/spkg-install b/build/pkgs/gap_packages/spkg-install index 97611fe3ea3..f68e33784f4 100755 --- a/build/pkgs/gap_packages/spkg-install +++ b/build/pkgs/gap_packages/spkg-install @@ -31,7 +31,7 @@ cd src/ for p in \ crime ctbllib design factint grape \ guava-3.12 Hap1.10 HAPcryst laguna polymaking \ - sonata toric1.8 polycyclic-2.11 autpgrp Alnuth-3.0.0 + sonata toric1.8 polycyclic-2.11 autpgrp Alnuth-3.0.0 atlasrep do echo "Copying package $p" $CP -pr $p "$PKG_DIR" diff --git a/build/pkgs/maxima/SPKG.txt b/build/pkgs/maxima/SPKG.txt index bfefe8e063a..365c18fe643 100644 --- a/build/pkgs/maxima/SPKG.txt +++ b/build/pkgs/maxima/SPKG.txt @@ -29,43 +29,55 @@ COPYING. * The Maxima mailing list - see http://maxima.sourceforge.net/maximalist.html +== Dependencies == + + * ECL (Embedded Common Lisp) + == Special Update/Build Instructions == - * Make sure the patches still apply cleanly. +1. Go to http://sourceforge.net/projects/maxima/files/Maxima-source/ + and download the source tarball maxima-x.y.z.tar.gz. + +2. Extract the tarball. + +3. In the directory maxima-x.y.z, run the spkg-src script. + +4. Compress the directory into a new file maxima-x.y.z.tar.bz2 and + place it in the upstream/ directory. - * Run the `spkg-src` script after updating to a newer upstream - release. It removes a large amount of unused documentation and - disables the associated Makefiles, reducing the size of the SPKG - greatly. +5. Make sure the patches still apply cleanly, and update them if + necessary. -============================================================= +6. Test the resulting package. -How to make a new version of the Maxima spkg. +Here's what spkg-src does: - 1. Go to http://sourceforge.net/project/showfiles.php?group_id=4933 - and download the source tar.gz ball. That's near the bottom - of that page (i.e., hard to find if you don't scroll past - all the big red adds). + * Removes the unneeded PDF figures from the documentation. - 2. Extract the tarball to replace the src/ subdirectory of this package. + * Removes the foreign language versions of the info files. - 3. Create an spkg from this directory, with - sage -pkg maxima-x.y.z +The following patches are applied: - 4. Test the resulting maxima-??.spkg. + * 0001-taylor2-Avoid-blowing-the-stack-when-diff-expand-isn.patch: + Fix for Maxima bug #2520 (abs_integrate fails on abs(sin(x)) and + abs(cos(x))). Introduced in Trac #13364 (Upgrade Maxima to + 5.29.1). -============================================================= + * build-fasl.patch: Build a fasl library for ecl in addition to an + executable program. Introduced in Trac #16178 (Build maxima fasl + without asdf). -Here's what spkg-dist does: + * infodir.patch: Correct the path to the Info directory. -* Removes the foreign language versions of the info files, - since they add several MEGS (and they weren't in previous - versions of Maxima). This means making empty directories - and changing the Makefile.in's. + * matrixexp.patch: Fix matrixexp(matrix([%i*%pi])), which broke after + Maxima 5.29.1. Introduced in Trac #13973. -* In the past, before doing step 3 above, one would also type + * maxima_bug_2526.patch: Fix for Maxima bug #2526 (abs_integrate + fails on integrate(sqrt(x + sqrt(x)), x)). Introduced in Trac + #13364 (Upgrade Maxima to 5.29.1). - ./spkg-dist + * maxima.system.patch: Set c::*compile-in-constants* to t. - However, this caused problems with calling automake because - of the changed .in files. + * undoing_true_false_printing_patch.patch: Revert an upstream change + causing '?' to be printed around some words. Introduced in Trac + #13364 (Upgrade Maxima to 5.29.1). diff --git a/build/pkgs/maxima/checksums.ini b/build/pkgs/maxima/checksums.ini index c4ed65c05a9..a2e67610120 100644 --- a/build/pkgs/maxima/checksums.ini +++ b/build/pkgs/maxima/checksums.ini @@ -1,4 +1,4 @@ tarball=maxima-VERSION.tar.bz2 -sha1=9314e23a4166a3d455b37a18d846474aa310e6c4 -md5=40b01a718ddba1c8364d76a1e5d35d8f -cksum=2999621606 +sha1=8c1a10d0cfb10bb10448fd18b021d73e25fc744a +md5=55140d4f9f7f212fb1cf8479b895862e +cksum=1303222960 diff --git a/build/pkgs/maxima/package-version.txt b/build/pkgs/maxima/package-version.txt index e92eb951fb5..31208a27d1e 100644 --- a/build/pkgs/maxima/package-version.txt +++ b/build/pkgs/maxima/package-version.txt @@ -1 +1 @@ -5.29.1.p5 +5.33.0.p0 diff --git a/build/pkgs/maxima/patches/0001-taylor2-Avoid-blowing-the-stack-when-diff-expand-isn.patch b/build/pkgs/maxima/patches/0001-taylor2-Avoid-blowing-the-stack-when-diff-expand-isn.patch index 3e2156cb838..9a632255b57 100644 --- a/build/pkgs/maxima/patches/0001-taylor2-Avoid-blowing-the-stack-when-diff-expand-isn.patch +++ b/build/pkgs/maxima/patches/0001-taylor2-Avoid-blowing-the-stack-when-diff-expand-isn.patch @@ -87,10 +87,10 @@ diff --git a/tests/rtest_taylor.mac b/tests/rtest_taylor.mac index 1b26258..d2132c7 100644 --- a/tests/rtest_taylor.mac +++ b/tests/rtest_taylor.mac -@@ -517,3 +517,9 @@ diff('(f(taylor(x,x,0,6))),x); - 'diff(f(taylor(x,x,0,6)),x,1); - ev(%,nouns); - 'diff(f(+x),x,1); +@@ -539,3 +539,9 @@ + + 2856700692480*x^2 + 3370143559680*x + 2386516803584)], + pade (t, 5, 5), is (equal (%%, foo))); + true; + +/* Avoid blowing the stack if diff-expand doesn't return anything + helpful. Example comes from Bug ID: 2520 diff --git a/build/pkgs/maxima/patches/matrixexp.patch b/build/pkgs/maxima/patches/matrixexp.patch new file mode 100644 index 00000000000..794ca861cc1 --- /dev/null +++ b/build/pkgs/maxima/patches/matrixexp.patch @@ -0,0 +1,13 @@ +--- a/share/linearalgebra/matrixexp.lisp 2013-10-07 04:37:12.000000000 +0100 ++++ b/share/linearalgebra/matrixexp.lisp 2014-05-16 02:16:09.112011893 +0100 +@@ -138,8 +138,8 @@ + (print `(ratvars = ,$ratvars gcd = '$gcd algebraic = ,$algebraic)) + (print `(ratfac = ,$ratfac)) + (merror "Unable to find the spectrum"))) +- +- (setq res ($fullratsimp (ncpower (sub (mult z ($ident n)) mat) -1) z)) ++ ++ (setq res ($fullratsimp ($invert (sub (mult z ($ident n)) mat) '$crering) z)) + (setq m (length sp)) + (dotimes (i m) + (setq zi (nth i sp)) diff --git a/build/pkgs/maxima/patches/maxima_bug_2526.patch b/build/pkgs/maxima/patches/maxima_bug_2526.patch index 5db31f7b9df..cd0fc8b8932 100644 --- a/build/pkgs/maxima/patches/maxima_bug_2526.patch +++ b/build/pkgs/maxima/patches/maxima_bug_2526.patch @@ -11,7 +11,7 @@ q : almost_everywhere_simp(q), --- a/share/contrib/integration/rtest_abs_integrate.mac +++ b/share/contrib/integration/rtest_abs_integrate.mac -@@ -511,16 +511,24 @@ integrate(sqrt(max(5,x)),x); +@@ -510,12 +510,19 @@ integrate(sqrt(max(5,x)),x); /* abs_integrate causes stack overflow - ID: 3533723 */ block([extra_integration_methods : ['intfudu, 'intfugudu, 'signum_int, 'abs_integrate_use_if, 'floor_int, 'if_int]], integrate(log(sin(x)),x)); @@ -35,10 +35,5 @@ +integrate(sqrt(x + sqrt(x)),x,0,1); +(3*log(sqrt(2)+1)-3*log(sqrt(2)-1)+7*2^(3/2))/24$ - (print("time = ", elapsed_real_time () - start), is(elapsed_real_time () - start < 70)); - true$ + /* SF bug #2557: "abs_integrate leaks assumptions into enclosing context" */ -+ - (remvalue(e,i,di,f1,start), remove(x,noninteger), remfunction(convolution, unit_box),0); - 0$ - diff --git a/build/pkgs/maxima/spkg-src b/build/pkgs/maxima/spkg-src index 6a5257bf1b6..ed02af06283 100755 --- a/build/pkgs/maxima/spkg-src +++ b/build/pkgs/maxima/spkg-src @@ -8,8 +8,8 @@ def cmd(x): if os.system(x): print "(Failed.)" -cmd('rm src/doc/info/figures/*.pdf') +cmd('rm doc/info/figures/*.pdf') for X in ['de', 'de.utf8', 'es', 'es.utf8', 'pt', 'pt.utf8', 'pt_BR', 'pt_BR.utf8']: - cmd('rm -r src/doc/info/%s/*'%X) - open('src/doc/info/%s/Makefile.in'%X,'w').write('all:\n\tls\n\n') + cmd('rm -r doc/info/%s/*'%X) + open('doc/info/%s/Makefile.in'%X,'w').write('all:\n\tls\n\n') diff --git a/build/pkgs/mcqd/SPKG.txt b/build/pkgs/mcqd/SPKG.txt new file mode 100644 index 00000000000..e6379e83828 --- /dev/null +++ b/build/pkgs/mcqd/SPKG.txt @@ -0,0 +1,29 @@ += MCQD 1.0 = + +== Description == + +MaxCliqueDyn is a fast exact algorithm for finding a maximum clique in an +undirected graph. + +== License == + +GPL 3 + +== SPKG Maintainers == + +Jernej Azarija (azi.stdout@gmail.com) +Nathann Cohen (nathann.cohen@gmail.com) + +== Upstream Contact == + +MCQD is currently being maintained by Janez Konc. +http://www.sicmm.org/~konc/ + +== Dependencies == + +None + +== Changelog == + +=== mcqd-1.0 (Jernej Azarija, Nathann Cohen, 19 march 2014) == + * Initial version diff --git a/build/pkgs/mcqd/checksums.ini b/build/pkgs/mcqd/checksums.ini new file mode 100644 index 00000000000..631b2445e05 --- /dev/null +++ b/build/pkgs/mcqd/checksums.ini @@ -0,0 +1,4 @@ +tarball=mcqd-VERSION.tar.bz2 +sha1=9c55da46815aa3903f0be2d1ffbdff9c17d4aa31 +md5=831538634b7b7efe15b5e5ad3d9c59c2 +cksum=2853560422 diff --git a/build/pkgs/mcqd/package-version.txt b/build/pkgs/mcqd/package-version.txt new file mode 100644 index 00000000000..d3827e75a5c --- /dev/null +++ b/build/pkgs/mcqd/package-version.txt @@ -0,0 +1 @@ +1.0 diff --git a/build/pkgs/mcqd/spkg-install b/build/pkgs/mcqd/spkg-install new file mode 100755 index 00000000000..0d623044935 --- /dev/null +++ b/build/pkgs/mcqd/spkg-install @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +if [ "$SAGE_LOCAL" = "" ]; then + echo "SAGE_LOCAL undefined ... exiting"; + echo "Maybe run 'sage -sh'?" + exit 1 +fi + +cd src +grep -v "std..cout.*current max" mcqd.h > mcqd2.h && +mv mcqd2.h mcqd.h && +g++ -O3 -c mcqd.cpp -o $SAGE_LOCAL/lib/libmcqd.so && +cp mcqd.h $SAGE_LOCAL/include/mcqd.h + +if [ $? -ne 0 ]; then + echo "An error occurred whilst building MCQD" + exit 1 +fi \ No newline at end of file diff --git a/build/pkgs/pexpect/SPKG.txt b/build/pkgs/pexpect/SPKG.txt index 9e6a575328d..e51db676922 100644 --- a/build/pkgs/pexpect/SPKG.txt +++ b/build/pkgs/pexpect/SPKG.txt @@ -37,6 +37,9 @@ Patches: == Changelog == +=== pexpect-2.0.p6 (Bradly Schlenker, 10 May 2014) === + * Trac #15178: Fixed pexpect.py-isdir_bug_fix.patch. (mislabeled variable) + === pexpect-2.0.p5 (Jeroen Demeyer, 13 January 2012) === * Trac #12221: add env.patch to add env parameter to spawn.__init__() * Restore upstream sources, use patch for patching diff --git a/build/pkgs/pexpect/package-version.txt b/build/pkgs/pexpect/package-version.txt index 796e9d6b31b..9e95227c8eb 100644 --- a/build/pkgs/pexpect/package-version.txt +++ b/build/pkgs/pexpect/package-version.txt @@ -1 +1 @@ -2.0.p5 +2.0.p6 diff --git a/build/pkgs/pexpect/patches/pexpect.py-isdir_bug_fix.patch b/build/pkgs/pexpect/patches/pexpect.py-isdir_bug_fix.patch index a4e5ea297d9..b778f7a62e6 100644 --- a/build/pkgs/pexpect/patches/pexpect.py-isdir_bug_fix.patch +++ b/build/pkgs/pexpect/patches/pexpect.py-isdir_bug_fix.patch @@ -5,7 +5,7 @@ # Special case where filename already contains a path. if os.path.dirname(filename) != '': - if os.access (filename, os.X_OK): -+ if os.access (filename, os.X_OK) and not os.path.isdir(f): ++ if os.access (filename, os.X_OK) and not os.path.isdir(filename): return filename if not os.environ.has_key('PATH') or os.environ['PATH'] == '': diff --git a/build/pkgs/sage_mode/SPKG.txt b/build/pkgs/sage_mode/SPKG.txt new file mode 100644 index 00000000000..a605f0025ad --- /dev/null +++ b/build/pkgs/sage_mode/SPKG.txt @@ -0,0 +1,175 @@ += sage-mode = + +== Description == + +sage-mode provides Emacs Lisp that helps you use Sage in GNU Emacs. +Inspiration was provided by ipython.el originally. + +== License == + +Copyright (C) 2007, 2008, 2009, 2010, 2011 Nick Alexander + +Author: Nick Alexander + +sage-mode 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, or (at your option) +any later version. + +sage-mode 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. + +You should have received a copy of the GNU General Public License +along with sage-mode; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. + +== SPKG Maintainers == + + * Ivan Andrus + +== Upstream Contact == + + * Maintainer: Ivan Andrus + * Bitbucket mercurial repository: [[http://bitbucket.org/gvol/sage-mode]] + +== Dependencies == + + * sage + * GNU Emacs (with python.el, not python-mode.el) + + +== Special Update/Build Instructions == + +;; Start .emacs + + +;; After installation of the spkg, you must add something like the +;; following to your .emacs: + +(add-to-list 'load-path "$INSTALL_DIR") +(require 'sage "sage") +(setq sage-command "$SAGE_ROOT/sage") + +;; If you want sage-view to typeset all your output and display plot() +;; commands inline, uncomment the following line and configure sage-view: +;; (add-hook 'sage-startup-after-prompt-hook 'sage-view) +;; In particular customize the variable sage-view-default-commands. +;; Using sage-view to typeset output requires a working LaTeX +;; installation with the preview package. + +;; Also consider running (customize-group 'sage) to see more options. + +;; End .emacs + + +== Changelog == + +=== sage-mode-0.9.1 (Ivan Andrus, 2013-03-10) === +* Fixed some build issues + +=== sage-mode-0.9 (Ivan Andrus, 2013-03-10) === +* Fixed completion with new ipython +* Fixed interaction with new python.el +* Fixed recognition of sage-view output +* Added sage-attach-this-file +* Make sage-view prettier + +=== sage-mode-0.8 (Ivan Andrus, 2012-09-12) === +* Many improvements and new maintainer. + +=== sage-mode-0.7 (Nick Alexander, 2011-10-07) === +* Fix a bug reported by Ivan Andrus regarding font-lock and +triple-quoted strings. + +* sage-mode is now hosted on bitbucket for easy viewing and cloning: +browse to [[http://bitbucket.org/gvol/sage-mode]]. + +=== sage-mode-0.6 (Nick Alexander, 2009-05-11) === +* `sage-run' no longer waits for a sage: prompt by default. This can cause +problems if `sage-startup-after-prompt-hook' is non-trivial, but doesn't lock +up emacs busy waiting for the prompt. + +* `sage-send-doctest' now works with multiline tests and one line function +definitions. + +* `sage-rerun' now exits from the debugger if it sees the debugger prompt, and +`sage-build' with a prefix argument (build and run sage) should no longer hang. + +* `pyrex-mode' is now autoload-ed and the default for pyx, pxi, and pxd files. + +* `next-error' now jumps between top-level errors, avoiding lower level source +files. Lower level source files are still hyperlinked. + +* `sage-test' with a prefix argument now offers to test without rebuilding. + +* Now `sage-send-doctest' and `sage-send-region' are bound in *Help* buffer. + +* `sage-send-{buffer, region doctest}' now either automatically +(`sage-quit-debugger-automatically') or after a prompt quit the debugger +before sending input to the sage slave. + +* In the debugger, very short history items are not remembered. (No more +looping through u/d in the history.) + +* *sage-test* buffers now search for test status rather than relying on the +exit codes that incorrectly say tests failed all the time. + +* SPKG.txt and spkg-install now print paths in the install instructions. + +* C-u RET no longer sends input to the sage slave with output to +*sage-output*. Use `sage-move-output-to-buffer' instead, or +`comint-write-output' to save to a file. + +=== sage-mode-0.5.4 (Nick Alexander, 2009-03-13) === +* Fixed errors in `sage-build' and `sage-test' autoloads; made sage-mode.el +provide 'sage-mode. + +=== sage-mode-0.5.3 (Nick Alexander, 2009-03-12) === +* Updated `sage-view' to new version. + +* Completely reworked keymaps and added preliminary menus. + +* Completely reworked customize interface and added autoload interface +through `sage.el' and autogenerated `sage-load.el'. + +* Added some documentation and additional customization options. + +=== sage-mode-0.5.2 (Nick Alexander, 2009-02-19) === +* Made individual parts of `sage-view' toggleable, fixed bugs with plots and +multiple outputs. + +=== sage-mode-0.5.1 (Nick Alexander, 2009-02-11) === +* Added `sage-startup-hook' and made `sage-view' actually usable. + +=== sage-mode-0.5 (Nick Alexander, 2009-01-31) === +* Incorporated Matthias Meulien's sage-view.el. To enable, try sage-view or + (add-hook 'inferior-sage-mode-hook 'sage-view). + +* Added sage-rerun to restart sage and made C-u C-c C-b (sage-build with a + prefix argument) build and restart sage. + +=== sage-mode-0.4 (Nick Alexander, 2008-06-16) === +I honestly can't remember. + +=== sage-mode-0.3 (Nick Alexander, 2008-06-15) === +Add sage-build command. Make sage-default-test-* more robust, and add +`sage-send-all-doctest-lines-in-file'. + +* sage-build.el: new module for building (sage-build) and running +(sage-build with prefix argument) sage. + +* sage-test.el (sage-send-all-doctest-lines): new function. Runs all +sage: doctest lines in a file in sequence. Use with pdb to reproduce +errors found only after sage -t. + +* sage-test.el (sage-default-test-command, +sage-default-test-new-command): cleaned up to use sage-build. + +=== sage-mode-0.2 (Nick Alexander, 2008-06-14) === +First alpha version, for Mike Hansen to use and test. + +=== sage-mode-0.1 (Nick Alexander) === +First internal version. diff --git a/build/pkgs/sage_mode/checksums.ini b/build/pkgs/sage_mode/checksums.ini new file mode 100644 index 00000000000..b62262435c6 --- /dev/null +++ b/build/pkgs/sage_mode/checksums.ini @@ -0,0 +1,4 @@ +tarball=sage_mode-VERSION.tar.bz2 +sha1=83abb4de8724543bf67dba05c37d8e8400d811e6 +md5=c1c9fad76db2314b7576765f10ae254c +cksum=409763902 diff --git a/build/pkgs/sage_mode/package-version.txt b/build/pkgs/sage_mode/package-version.txt new file mode 100644 index 00000000000..51176c7c891 --- /dev/null +++ b/build/pkgs/sage_mode/package-version.txt @@ -0,0 +1 @@ +0.11 diff --git a/build/pkgs/sage_mode/spkg-install b/build/pkgs/sage_mode/spkg-install new file mode 100755 index 00000000000..c12fa6018b3 --- /dev/null +++ b/build/pkgs/sage_mode/spkg-install @@ -0,0 +1,58 @@ +#!/bin/sh + +cd src +CUR=`pwd` + +# Build and install Python support files +cd python/ +python setup.py build + +if [ $? -ne 0 ]; then + echo "Failure to build sage-mode Python support files" + exit 1 +fi + +python setup.py install + +if [ $? -ne 0 ]; then + echo "Failure to install sage-mode Python support files" + exit 1 +fi + +# Copy emacs lisp to SAGE_DATA, and print helpful instructions +cd "$CUR" +if [ "x$SAGE_DATA" = x ]; then + INSTALL_DIR=$SAGE_LOCAL/share/emacs/site-lisp/sage-mode + # Create intermediate directories + mkdir -p $INSTALL_DIR +else + INSTALL_DIR=$SAGE_DATA/emacs +fi +rm -rf $INSTALL_DIR +cp -r emacs $INSTALL_DIR + +# Remove old elc files if any. +# Then, even if byte compiling fails, they won't taint the setup. +rm -f $INSTALL_DIR/*.elc + +# Byte compile -- this can fail since we don't require emacs for Sage +EMACS=${EMACS-emacs} +$EMACS -batch 2> /dev/null > /dev/null +if [ $? -ne 0 ]; then + echo + echo WARNING: Could not find emacs at "'$EMACS'" + echo "Set the EMACS environment variable or ignore this if you don't have emacs installed" + +else + + echo Byte compiling sage-mode with "'$EMACS'" + echo Set the EMACS environment variable to compile with a different emacs. + $EMACS -batch -L $INSTALL_DIR/ -f batch-byte-compile $INSTALL_DIR/*.el + + # Trick the shell into expanding $INSTALL_DIR and $SAGE_ROOT + export INSTALL_DIR + sh -c "cat < + + + + + + + + + + + diff --git a/src/mac-app/AppController.h b/src/mac-app/AppController.h index 1b3b6f47579..47c4c56fe54 100644 --- a/src/mac-app/AppController.h +++ b/src/mac-app/AppController.h @@ -30,6 +30,7 @@ NSUserDefaults *defaults; NSTask *theTask; + NSTask *launchTask; NSPipe *taskPipe; int port; diff --git a/src/mac-app/AppController.m b/src/mac-app/AppController.m index 5705bbdad16..df3244a1005 100644 --- a/src/mac-app/AppController.m +++ b/src/mac-app/AppController.m @@ -105,9 +105,17 @@ -(IBAction)startServer:(id)sender{ } // Create a task to start the server - [NSTask launchedTaskWithLaunchPath:scriptPath - arguments:[NSArray arrayWithObjects:sageBinary, logPath, nil]]; - // We now forget about the task. I hope that's okay... + + // Get any default options they might have for this session + [defaults synchronize]; + NSString *defArgs = [[defaults dictionaryForKey:@"DefaultArguments"] + objectForKey:@"notebook"]; + launchTask = [[NSTask launchedTaskWithLaunchPath:scriptPath + arguments:[NSArray arrayWithObjects:sageBinary, + logPath, + defArgs, // May be nil, but that's okay + nil]] + retain]; // Open loading page since it can take a while to start [self browseRemoteURL:[[NSBundle mainBundle] pathForResource:@"loading-page" ofType:@"html"]]; @@ -161,7 +169,9 @@ - (void)taskTerminated:(NSNotification *)aNotification { [self serverStartedWithPort:p]; } else { // We failed, so tell the user - if (haveStatusItem) [statusItem setImage:statusImageGrey]; + if (haveStatusItem) { + [statusItem setImage:statusImageGrey]; + } port = 0; } // Reset for next time. @@ -169,6 +179,34 @@ - (void)taskTerminated:(NSNotification *)aNotification { theTask = nil; [taskPipe release]; taskPipe = nil; + } else if (theObject == launchTask ) { + + const int status = [theObject terminationStatus]; + if (status != 0) { + // We failed, so tell the user + if (haveStatusItem) { + [statusItem setImage:statusImageGrey]; + } + port = 0; + NSAlert *alert = [NSAlert alertWithMessageText:@"Sage Server failed to start" + defaultButton:@"View Log" + alternateButton:@"Cancel" + otherButton:nil + informativeTextWithFormat:@"For some reason the Sage server failed to start. " + "Please check the log for clues, and have that information handy when asking for help."]; + [alert setAlertStyle:NSWarningAlertStyle]; + NSModalResponse resp = [alert runModal]; + if (resp == NSModalResponseOK) { + // View Log + [self viewSageLog:self]; + } else { + // Cancel + } + } + // Reset for next time. + [launchTask release]; + launchTask = nil; + } else { // NSLog(@"Got called for a different task."); } diff --git a/src/mac-app/start-sage.sh b/src/mac-app/start-sage.sh index 166889a0d0a..e314e354f62 100755 --- a/src/mac-app/start-sage.sh +++ b/src/mac-app/start-sage.sh @@ -8,7 +8,7 @@ # Ensure we have enough arguments if [ $# -lt 2 ]; then - echo "usage: $0 SAGE_EXECUTABLE LOG" + echo "usage: $0 SAGE_EXECUTABLE LOG [ARGS_FOR_NOTEBOOK]" exit 1; fi @@ -52,7 +52,9 @@ echo Checking install location >> "$SAGE_LOG" echo Checking existence of notebook directory >> "$SAGE_LOG" if [ -d $DOT_SAGE/sage_notebook.sagenb ]; then echo Starting Notebook >> "$SAGE_LOG" - ./sage --notebook >> "$SAGE_LOG" 2>> "$SAGE_LOG" + # $3 is not quoted because it comes as one argument from the app, + # so we need the shell to parse it here. + ./sage --notebook $3 >> "$SAGE_LOG" 2>> "$SAGE_LOG" else # if Terminal.app is not running before it is activated by # osascript, then it inherits the environment from osascript. @@ -67,5 +69,7 @@ else -e ' activate' \ -e " do script \"'$SAGE_ROOT'/sage --notebook\"" \ -e 'end' + # We don't include $3 here since this should only happen the first time + # they run it, and then we don't have to worry about quoting it. fi -exit 0 +exit $? diff --git a/src/module_list.py b/src/module_list.py index 6a2502aab6e..692772c0697 100755 --- a/src/module_list.py +++ b/src/module_list.py @@ -1904,6 +1904,11 @@ def uname_specific(name, value, alternative): flint_depends, libraries = ['flint', 'gmp', 'ratpoints']), + + Extension('sage.schemes.elliptic_curves.period_lattice_region', + sources = ['sage/schemes/elliptic_curves/period_lattice_region.pyx'], + include_dirs = numpy_include_dirs), + Extension('sage.schemes.hyperelliptic_curves.hypellfrob', sources = ['sage/schemes/hyperelliptic_curves/hypellfrob.pyx', 'sage/schemes/hyperelliptic_curves/hypellfrob/hypellfrob.cpp', @@ -2151,6 +2156,14 @@ def uname_specific(name, value, alternative): libraries = ['cryptominisat', 'z']) ]) +if is_package_installed('mcqd'): + ext_modules.append( + Extension("sage.graphs.mcqd", + ["sage/graphs/mcqd.pyx"], + language = "c++")) +# libraries = ["mcqd"])) + + # Only include darwin_utilities on OS_X >= 10.5 UNAME = os.uname() if UNAME[0] == "Darwin" and not UNAME[2].startswith('8.'): 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 cc0bb8d537d..f59e67985a7 100644 --- a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra.py +++ b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra.py @@ -23,6 +23,7 @@ from sage.rings.ring import Algebra from sage.misc.cachefunc import cached_method +from functools import reduce class FiniteDimensionalAlgebra(Algebra): 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 7a5b91bd26b..4361661657c 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 @@ -21,6 +21,7 @@ from sage.structure.sage_object import SageObject from sage.misc.cachefunc import cached_method +from functools import reduce class FiniteDimensionalAlgebraIdeal(Ideal_generic): diff --git a/src/sage/algebras/hall_algebra.py b/src/sage/algebras/hall_algebra.py index c0f908e8431..e4b4af8a342 100644 --- a/src/sage/algebras/hall_algebra.py +++ b/src/sage/algebras/hall_algebra.py @@ -22,6 +22,7 @@ from sage.combinat.hall_polynomial import hall_polynomial from sage.combinat.sf.sf import SymmetricFunctions from sage.rings.all import ZZ +from functools import reduce def transpose_cmp(x, y): r""" diff --git a/src/sage/algebras/steenrod/steenrod_algebra_bases.py b/src/sage/algebras/steenrod/steenrod_algebra_bases.py index d2f0af32e8e..184664a601b 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra_bases.py +++ b/src/sage/algebras/steenrod/steenrod_algebra_bases.py @@ -718,7 +718,7 @@ def serre_cartan_basis(n, p=2, bound=1): # elements from serre_cartan_basis (n - last, bound=2 * last). # This means that 2 last <= n - last, or 3 last <= n. result = [(n,)] - for last in range(bound, 1+n/3): + for last in range(bound, 1+n//3): for vec in serre_cartan_basis(n - last, bound = 2*last): new = vec + (last,) result.append(new) @@ -731,7 +731,7 @@ def serre_cartan_basis(n, p=2, bound=1): result = [] # 2 cases: append P^{last}, or append P^{last} beta # case 1: append P^{last} - for last in range(bound, 1+n/(2*(p - 1))): + for last in range(bound, 1+n//(2*(p - 1))): if n - 2*(p-1)*last > 0: for vec in serre_cartan_basis(n - 2*(p-1)*last, p, p*last): @@ -739,7 +739,7 @@ def serre_cartan_basis(n, p=2, bound=1): # case 2: append P^{last} beta if bound == 1: bound = 0 - for last in range(bound+1, 1+n/(2*(p - 1))): + for last in range(bound+1, 1+n//(2*(p - 1))): basis = serre_cartan_basis(n - 2*(p-1)*last - 1, p, p*last) for vec in basis: @@ -970,8 +970,8 @@ def arnonC_basis(n,bound=1): # first also must be divisible by 2**(len(old-basis-elt)) # This means that 3 first <= 2 n. result = [(n,)] - for first in range(bound,1+2*n/3): - for vec in arnonC_basis(n - first, max(first/2,1)): + for first in range(bound, 1+2*n//3): + for vec in arnonC_basis(n - first, max(first//2,1)): if first % 2**len(vec) == 0: result.append((first,) + vec) return tuple(result) @@ -1057,7 +1057,7 @@ def sorting_pair(s,t,basis): # pair used for sorting the basis sorting_pair(y[0][0], y[0][1], basis))) deg = n - 2*dim*(p-1) q_degrees = [1+2*(p-1)*d for d in - xi_degrees(int((deg - 1)/(2*(p-1))), p)] + [1] + xi_degrees((deg - 1)//(2*(p-1)), p)] + [1] q_degrees_decrease = q_degrees q_degrees.reverse() if deg % (2*(p-1)) <= len(q_degrees): diff --git a/src/sage/algebras/steenrod/steenrod_algebra_mult.py b/src/sage/algebras/steenrod/steenrod_algebra_mult.py index 73a4ff6b234..a3666c99bbf 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra_mult.py +++ b/src/sage/algebras/steenrod/steenrod_algebra_mult.py @@ -784,7 +784,7 @@ def adem(a, b, c=0, p=2): elif a == 0: return {(b,): 1} elif a >= 2*b: return {(a,b): 1} result = {} - for c in range(1+a/2): + for c in range(1 + a//2): if binomial_mod2(b-c-1, a-2*c) == 1: if c == 0: result[(a+b,)] = 1 @@ -810,7 +810,7 @@ def adem(a, b, c=0, p=2): if A >= p*B: # admissible return {(0,A,0,B,0): 1} result = {} - for j in range(1 + int(a/p)): + for j in range(1 + a//p): coeff = (-1)**(A+j) * binomial_modp((B-j) * (p-1) - 1, A - p*j, p) if coeff % p != 0: if j == 0: @@ -821,14 +821,14 @@ def adem(a, b, c=0, p=2): if A >= p*B + 1: # admissible return {(0,A,1,B,0): 1} result = {} - for j in range(1 + int(a/p)): + for j in range(1 + a//p): coeff = (-1)**(A+j) * binomial_modp((B-j) * (p-1), A - p*j, p) if coeff % p != 0: if j == 0: result[(1,A+B,0)] = coeff else: result[(1,A+B-j,0,j,0)] = coeff - for j in range(1 + int((a-1)/p)): + for j in range(1 + (a-1)//p): coeff = (-1)**(A+j-1) * binomial_modp((B-j) * (p-1) - 1, A - p*j - 1, p) if coeff % p != 0: if j == 0: diff --git a/src/sage/calculus/calculus.py b/src/sage/calculus/calculus.py index bb6660aa127..42429c906e3 100644 --- a/src/sage/calculus/calculus.py +++ b/src/sage/calculus/calculus.py @@ -1050,7 +1050,7 @@ def limit(ex, dir=None, taylor=False, algorithm='maxima', **argv): ValueError: Computation failed since Maxima requested additional constraints; using the 'assume' command before limit evaluation *may* help (see `assume?` for more details) - Is a positive, negative, or zero? + Is a positive, negative or zero? With this example, Maxima is looking for a LOT of information:: @@ -1122,7 +1122,7 @@ def limit(ex, dir=None, taylor=False, algorithm='maxima', **argv): ValueError: dir must be one of None, 'plus', '+', 'right', 'minus', '-', 'left' - We check that Trac ticket 3718 is fixed, so that + We check that :trac:`3718` is fixed, so that Maxima gives correct limits for the floor function:: sage: limit(floor(x), x=0, dir='-') @@ -1133,7 +1133,7 @@ def limit(ex, dir=None, taylor=False, algorithm='maxima', **argv): und Maxima gives the right answer here, too, showing - that Trac 4142 is fixed:: + that :trac:`4142` is fixed:: sage: f = sqrt(1-x^2) sage: g = diff(f, x); g @@ -1150,7 +1150,7 @@ def limit(ex, dir=None, taylor=False, algorithm='maxima', **argv): sage: limit(1/x, x=0, dir='-') -Infinity - Check that Trac 8942 is fixed:: + Check that :trac:`8942` is fixed:: sage: f(x) = (cos(pi/4-x) - tan(x)) / (1 - sin(pi/4+x)) sage: limit(f(x), x = pi/4, dir='minus') @@ -1160,7 +1160,8 @@ def limit(ex, dir=None, taylor=False, algorithm='maxima', **argv): sage: limit(f(x), x = pi/4) Infinity - Check that we give deprecation warnings for 'above' and 'below' #9200:: + Check that we give deprecation warnings for 'above' and 'below', + :trac:`9200`:: sage: limit(1/x, x=0, dir='above') doctest:...: DeprecationWarning: the keyword @@ -1177,6 +1178,14 @@ def limit(ex, dir=None, taylor=False, algorithm='maxima', **argv): sage: limit(tanh(x),x=0) 0 + + Check that :trac:`15386` is fixed:: + + sage: n = var('n') + sage: assume(n>0) + sage: sequence = -(3*n^2 + 1)*(-1)^n/sqrt(n^5 + 8*n^3 + 8) + sage: limit(sequence, n=infinity) + 0 """ if not isinstance(ex, Expression): ex = SR(ex) diff --git a/src/sage/calculus/functional.py b/src/sage/calculus/functional.py index 48d75aac9c0..e2c06b36c99 100644 --- a/src/sage/calculus/functional.py +++ b/src/sage/calculus/functional.py @@ -246,7 +246,7 @@ def integral(f, *args, **kwds): constraints; using the 'assume' command before integral evaluation *may* help (example of legal syntax is 'assume(a>0)', see `assume?` for more details) - Is a positive, negative, or zero? + Is a positive, negative or zero? sage: assume(a>0) sage: integral(abs(x)*x, x, 0, a) 1/3*a^3 diff --git a/src/sage/categories/additive_groups.py b/src/sage/categories/additive_groups.py index 43b7d1d4aa0..7e59bfa50f8 100644 --- a/src/sage/categories/additive_groups.py +++ b/src/sage/categories/additive_groups.py @@ -53,43 +53,3 @@ class AdditiveGroups(CategoryWithAxiom_singleton): _base_category_class_and_axiom = (AdditiveMonoids, "AdditiveInverse") AdditiveCommutative = LazyImport('sage.categories.commutative_additive_groups', 'CommutativeAdditiveGroups', at_startup=True) - - class ElementMethods: - ##def -x, -(x,y): - def __sub__(left, right): - """ - Top-level subtraction operator. - See extensive documentation at the top of ``element.pyx``. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b']) - sage: a,b = F.basis() - sage: a - b - B['a'] - B['b'] - """ - if have_same_parent(left, right) and hasattr(left, "_sub_"): - return left._sub_(right) - from sage.structure.element import get_coercion_model - import operator - return get_coercion_model().bin_op(left, right, operator.sub) - - ################################################## - # Negation - ################################################## - - def __neg__(self): - """ - Top-level negation operator for elements of abelian - monoids, which may choose to implement ``_neg_`` rather than - ``__neg__`` for consistancy. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, ['a','b']) - sage: a,b = F.basis() - sage: - b - -B['b'] - """ - return self._neg_() - diff --git a/src/sage/categories/additive_magmas.py b/src/sage/categories/additive_magmas.py index 4e4355fa510..b35515de5b4 100644 --- a/src/sage/categories/additive_magmas.py +++ b/src/sage/categories/additive_magmas.py @@ -14,6 +14,7 @@ from sage.categories.category_with_axiom import CategoryWithAxiom from sage.categories.category_singleton import Category_singleton from sage.categories.algebra_functor import AlgebrasCategory +from sage.categories.cartesian_product import CartesianProductsCategory from sage.categories.with_realizations import WithRealizationsCategory from sage.categories.sets_cat import Sets from sage.structure.sage_object import have_same_parent @@ -477,6 +478,45 @@ def _add_parent(self, other): """ return self.parent().summation(self, other) + class CartesianProducts(CartesianProductsCategory): + def extra_super_categories(self): + """ + Implement the fact that a cartesian product of additive magmas is + an additive magma. + + EXAMPLES:: + + sage: C = AdditiveMagmas().CartesianProducts() + sage: C.extra_super_categories() + [Category of additive magmas] + sage: C.super_categories() + [Category of additive magmas, Category of Cartesian products of sets] + sage: C.axioms() + frozenset([]) + """ + return [AdditiveMagmas()] + + class ElementMethods: + def _add_(self, right): + r""" + EXAMPLES:: + + sage: G5=GF(5); G8=GF(4,'x'); GG = G5.cartesian_product(G8) + sage: e = GG((G5(1),G8.primitive_element())); e + (1, x) + sage: e+e + (2, 0) + sage: e=groups.misc.AdditiveCyclic(8) + sage: x=e.cartesian_product(e)((e(1),e(2))) + sage: x + (1, 2) + sage: 4*x + (4, 0) + """ + return self.parent()._cartesian_product_of_elements( + x+y for x,y in zip(self.cartesian_factors(), + right.cartesian_factors())) + class Algebras(AlgebrasCategory): def extra_super_categories(self): @@ -534,9 +574,28 @@ def product_on_basis(self, g1, g2): return self.monomial(g1 + g2) class AdditiveCommutative(CategoryWithAxiom): + class CartesianProducts(CartesianProductsCategory): + def extra_super_categories(self): + """ + Implement the fact that a cartesian product of commutative + additive magmas is a commutative additive magma. + + EXAMPLES:: + + sage: C = AdditiveMagmas().AdditiveCommutative().CartesianProducts() + sage: C.extra_super_categories(); + [Category of additive commutative additive magmas] + sage: C.axioms() + frozenset(['AdditiveCommutative']) + """ + return [AdditiveMagmas().AdditiveCommutative()] + class Algebras(AlgebrasCategory): def extra_super_categories(self): """ + Implement the fact that the algebra of a commutative additive + magmas is commutative. + EXAMPLES:: sage: AdditiveMagmas().AdditiveCommutative().Algebras(QQ).extra_super_categories() @@ -585,9 +644,6 @@ def AdditiveInverse(self): """ return self._with_axiom("AdditiveInverse") - class AdditiveInverse(CategoryWithAxiom): - pass - class ParentMethods: def _test_zero(self, **options): @@ -665,7 +721,6 @@ def zero_element(self): return self.zero() class ElementMethods: - # TODO: merge with the implementation in Element which currently # overrides this one, and further requires self.parent()(0) to work. # @@ -720,6 +775,152 @@ def _test_nonzero_equal(self, **options): tester.assertEqual(bool(self), self != self.parent().zero()) tester.assertEqual(not self, self == self.parent().zero()) + def __sub__(left, right): + """ + Return the difference between ``left`` and ``right``, if it exists. + + This top-level implementation delegates the work to + the ``_sub_`` method or to coercion. See the extensive + documentation at the top of :ref:`sage.structure.element`. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b']) + sage: a,b = F.basis() + sage: a - b + B['a'] - B['b'] + """ + if have_same_parent(left, right) and hasattr(left, "_sub_"): + return left._sub_(right) + from sage.structure.element import get_coercion_model + import operator + return get_coercion_model().bin_op(left, right, operator.sub) + + def __neg__(self): + """ + Return the negation of ``self``, if it exists. + + This top-level implementation delegates the job to + ``_neg_``, for those additive unital magmas which may + choose to implement it instead of ``__neg__`` for + consistency. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b']) + sage: a,b = F.basis() + sage: - b + -B['b'] + + TESTS:: + + sage: b.__neg__.__module__ + 'sage.categories.additive_magmas' + sage: b._neg_.__module__ + 'sage.combinat.free_module' + """ + return self._neg_() + + class AdditiveInverse(CategoryWithAxiom): + class CartesianProducts(CartesianProductsCategory): + def extra_super_categories(self): + """ + Implement the fact that a cartesian product of additive magmas + with inverses is an additive magma with inverse. + + EXAMPLES:: + + sage: C = AdditiveMagmas().AdditiveUnital().AdditiveInverse().CartesianProducts() + sage: C.extra_super_categories(); + [Category of additive inverse additive unital additive magmas] + sage: sorted(C.axioms()) + ['AdditiveInverse', 'AdditiveUnital'] + """ + return [AdditiveMagmas().AdditiveUnital().AdditiveInverse()] + + class CartesianProducts(CartesianProductsCategory): + def extra_super_categories(self): + """ + Implement the fact that a cartesian product of unital additive + magmas is a unital additive magma. + + EXAMPLES:: + + sage: C = AdditiveMagmas().AdditiveUnital().CartesianProducts() + sage: C.extra_super_categories(); + [Category of additive unital additive magmas] + sage: C.axioms() + frozenset(['AdditiveUnital']) + """ + return [AdditiveMagmas().AdditiveUnital()] + + class ParentMethods: + def zero(self): + r""" + Returns the zero of this group + + EXAMPLE:: + + sage: GF(8,'x').cartesian_product(GF(5)).zero() + (0, 0) + """ + return self._cartesian_product_of_elements( + _.zero() for _ in self.cartesian_factors()) + + class ElementMethods: + def __neg__(self): + r""" + Return the negation of ``self``, if it exists. + + The inverse is computed by negating each cartesian + factor and attempting to convert the result back + to the original parent. + + For example, if one of the cartesian factor is an + element ``x`` of `\NN`, the result of ``-x`` is in + `\ZZ`. So we need to convert it back to `\NN`. As + a side effect, this checks that ``x`` indeed has a + negation in `\NN`. + + If needed an optimized version without this + conversion could be implemented in + :class:`AdditiveMagmas.AdditiveUnital.AdditiveInverse.ElementMethods`. + + EXAMPLES:: + + sage: G=GF(5); GG = G.cartesian_product(G) + sage: oneone = GG([GF(5)(1),GF(5)(1)]) + sage: -oneone + (4, 4) + + sage: NNSemiring = NonNegativeIntegers(category=Semirings() & InfiniteEnumeratedSets()) + sage: C = cartesian_product([ZZ,NNSemiring,RR]) + sage: -C([2,0,.4]) + (-2, 0, -0.400000000000000) + + sage: c = C.an_element(); c + (1, 42, 1.00000000000000) + sage: -c + Traceback (most recent call last): + ... + ValueError: Value -42 in not in Non negative integers. + + .. TODO:: + + Use plain ``NN`` above once it is a semiring. + See :trac:`16406`. There is a further issue + with ``NN`` being lazy imported which breaks + the assertion that the inputs are parents in + ``cartesian_product``:: + + sage: cartesian_product([ZZ, NN, RR]) + Traceback (most recent call last): + ... + AssertionError + """ + return self.parent()( + -x for x in self.cartesian_factors()) + class Algebras(AlgebrasCategory): def extra_super_categories(self): diff --git a/src/sage/categories/additive_semigroups.py b/src/sage/categories/additive_semigroups.py index acd9f78bc79..f2d5ae27db9 100644 --- a/src/sage/categories/additive_semigroups.py +++ b/src/sage/categories/additive_semigroups.py @@ -11,6 +11,7 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_import import LazyImport from sage.categories.category_with_axiom import CategoryWithAxiom_singleton +from sage.categories.cartesian_product import CartesianProductsCategory from sage.categories.algebra_functor import AlgebrasCategory from sage.categories.additive_magmas import AdditiveMagmas @@ -82,6 +83,24 @@ def _test_additive_associativity(self, **options): for x,y,z in tester.some_elements(CartesianProduct(S,S,S)): tester.assert_((x + y) + z == x + (y + z)) + class CartesianProducts(CartesianProductsCategory): + + def extra_super_categories(self): + """ + Implement the fact that a cartesian product of additive semigroups + is an additive semigroup. + + EXAMPLES:: + + sage: from sage.categories.additive_semigroups import AdditiveSemigroups + sage: C = AdditiveSemigroups().CartesianProducts() + sage: C.extra_super_categories() + [Category of additive semigroups] + sage: C.axioms() + frozenset(['AdditiveAssociative']) + """ + return [AdditiveSemigroups()] + class Algebras(AlgebrasCategory): def extra_super_categories(self): diff --git a/src/sage/categories/algebras.py b/src/sage/categories/algebras.py index 41068a5992d..00bd5597642 100644 --- a/src/sage/categories/algebras.py +++ b/src/sage/categories/algebras.py @@ -137,7 +137,9 @@ def extra_super_categories(self): [Category of algebras over Rational Field] sage: sorted(C.super_categories(), key=str) [Category of Cartesian products of commutative additive groups, - Category of Cartesian products of monoids, + Category of Cartesian products of distributive magmas and additive magmas, + Category of Cartesian products of semigroups, + Category of Cartesian products of unital magmas, Category of algebras over Rational Field] """ return [self.base_category()] diff --git a/src/sage/categories/cartesian_product.py b/src/sage/categories/cartesian_product.py index 17f52a56ea0..9b035c305eb 100644 --- a/src/sage/categories/cartesian_product.py +++ b/src/sage/categories/cartesian_product.py @@ -51,10 +51,14 @@ class CartesianProductFunctor(CovariantFunctorialConstruction): sage: C.an_element()^2 ('abcdabcd', 1, 1/4) sage: C.category() - Category of Cartesian products of monoids + Join of Category of monoids + and Category of Cartesian products of semigroups + and Category of Cartesian products of unital magmas sage: Monoids().CartesianProducts() - Category of Cartesian products of monoids + Join of Category of monoids + and Category of Cartesian products of semigroups + and Category of Cartesian products of unital magmas The Cartesian product functor is covariant: if ``A`` is a subcategory of ``B``, then ``A.CartesianProducts()`` is a @@ -62,6 +66,17 @@ class CartesianProductFunctor(CovariantFunctorialConstruction): :class:`~sage.categories.covariant_functorial_construction.CovariantFunctorialConstruction`):: sage: C.categories() + [Join of ..., + Category of monoids, + Category of Cartesian products of semigroups, + Category of semigroups, + Category of Cartesian products of unital magmas, + Category of Cartesian products of magmas, + Category of unital magmas, + Category of magmas, + Category of Cartesian products of sets, + Category of sets, ...] + [Category of Cartesian products of monoids, Category of monoids, Category of Cartesian products of semigroups, diff --git a/src/sage/categories/commutative_additive_groups.py b/src/sage/categories/commutative_additive_groups.py index 7b2a16f750a..bb0f7486f39 100644 --- a/src/sage/categories/commutative_additive_groups.py +++ b/src/sage/categories/commutative_additive_groups.py @@ -10,8 +10,8 @@ from sage.categories.category_types import AbelianCategory from sage.categories.category_with_axiom import CategoryWithAxiom -from sage.categories.cartesian_product import CartesianProductsCategory from sage.categories.algebra_functor import AlgebrasCategory +from sage.categories.cartesian_product import CartesianProductsCategory from sage.categories.additive_groups import AdditiveGroups class CommutativeAdditiveGroups(CategoryWithAxiom, AbelianCategory): @@ -41,71 +41,24 @@ class CommutativeAdditiveGroups(CategoryWithAxiom, AbelianCategory): TESTS:: sage: TestSuite(CommutativeAdditiveGroups()).run() - """ - _base_category_class_and_axiom = (AdditiveGroups, "AdditiveCommutative") - - class Algebras(AlgebrasCategory): - pass - - class CartesianProducts(CartesianProductsCategory): - - def extra_super_categories(self): - """ - EXAMPLES:: - - sage: from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups - sage: CommutativeAdditiveGroups().CartesianProducts().extra_super_categories(); - [Category of commutative additive groups] - sage: CommutativeAdditiveGroups().CartesianProducts().super_categories() - [Category of commutative additive groups, Category of Cartesian products of sets] - """ - return [CommutativeAdditiveGroups()] - - class ElementMethods: - # TODO: move to AdditiveMagmas.CartesianProducts.ElementMethods - def _add_(self, right): - r""" - EXAMPLES:: - - sage: G5=GF(5); G8=GF(4,'x'); GG = G5.cartesian_product(G8) - sage: e = GG((G5(1),G8.primitive_element())); e - (1, x) - sage: e+e - (2, 0) - sage: e=groups.misc.AdditiveCyclic(8) - sage: x=e.cartesian_product(e)((e(1),e(2))) - sage: x - (1, 2) - sage: 4*x - (4, 0) - """ - return self.parent()._cartesian_product_of_elements( - x+y for x,y in zip(self.cartesian_factors(), - right.cartesian_factors())) + sage: sorted(CommutativeAdditiveGroups().CartesianProducts().axioms()) + ['AdditiveAssociative', 'AdditiveCommutative', 'AdditiveInverse', 'AdditiveUnital'] - # TODO: move to AdditiveGroups.CartesianProducts.ElementMethods - def _neg_(self): - r""" - EXAMPLES:: + The empty covariant functorial construction category classes + ``CartesianProducts`` and ``Algebras`` are left here for the sake + of nicer output since this is a commonly used category:: - sage: G=GF(5); GG = G.cartesian_product(G) - sage: oneone = GG([GF(5)(1),GF(5)(1)]) - sage: -oneone - (4, 4) - """ - return self.parent()._cartesian_product_of_elements( - -x for x in self.cartesian_factors()) + sage: CommutativeAdditiveGroups().CartesianProducts() + Category of Cartesian products of commutative additive groups + sage: CommutativeAdditiveGroups().Algebras(QQ) + Category of commutative additive group algebras over Rational Field - class ParentMethods: - # TODO: move to AdditiveMagmas.AdditiveUnital.CartesianProducts.ElementMethods - def zero(self): - r""" - Returns the zero of this group + Also, it's likely that some code will end up there at some point. + """ + _base_category_class_and_axiom = (AdditiveGroups, "AdditiveCommutative") - EXAMPLE:: + class CartesianProducts(CartesianProductsCategory): + pass - sage: GF(8,'x').cartesian_product(GF(5)).zero() - (0, 0) - """ - return self._cartesian_product_of_elements( - _.zero() for _ in self._sets) + class Algebras(AlgebrasCategory): + pass diff --git a/src/sage/categories/covariant_functorial_construction.py b/src/sage/categories/covariant_functorial_construction.py index 24ab7d1478c..c8b3dc4f8e7 100644 --- a/src/sage/categories/covariant_functorial_construction.py +++ b/src/sage/categories/covariant_functorial_construction.py @@ -160,11 +160,14 @@ def category_from_categories(self, categories): sage: Cat1 = Rings() sage: Cat2 = Groups() sage: cartesian_product.category_from_categories((Cat1, Cat1, Cat1)) - Join of Category of Cartesian products of monoids and - Category of Cartesian products of commutative additive groups + Join of Category of rings and ... + and Category of Cartesian products of semigroups and ... + and Category of Cartesian products of commutative additive groups sage: cartesian_product.category_from_categories((Cat1, Cat2)) - Category of Cartesian products of monoids + Join of Category of monoids + and Category of Cartesian products of semigroups + and Category of Cartesian products of unital magmas """ assert(len(categories) > 0) return self.category_from_category(Category.meet(categories)) diff --git a/src/sage/categories/distributive_magmas_and_additive_magmas.py b/src/sage/categories/distributive_magmas_and_additive_magmas.py index f13d711bf40..65036f86b1c 100644 --- a/src/sage/categories/distributive_magmas_and_additive_magmas.py +++ b/src/sage/categories/distributive_magmas_and_additive_magmas.py @@ -10,6 +10,7 @@ from sage.misc.lazy_import import LazyImport from sage.categories.category_with_axiom import CategoryWithAxiom +from sage.categories.cartesian_product import CartesianProductsCategory class DistributiveMagmasAndAdditiveMagmas(CategoryWithAxiom): """ @@ -79,3 +80,19 @@ def _test_distributivity(self, **options): # right distributivity tester.assert_((x + y) * z == (x * z) + (y * z)) + class CartesianProducts(CartesianProductsCategory): + def extra_super_categories(self): + """ + Implement the fact that a cartesian product of magmas distributing + over additive magmas is a magma distributing over an + additive magma. + + EXAMPLES:: + + sage: C = (Magmas() & AdditiveMagmas()).Distributive().CartesianProducts() + sage: C.extra_super_categories(); + [Category of distributive magmas and additive magmas] + sage: C.axioms() + frozenset(['Distributive']) + """ + return [DistributiveMagmasAndAdditiveMagmas()] diff --git a/src/sage/categories/magmas.py b/src/sage/categories/magmas.py index 2dd72c02eaa..b2edc4fda39 100644 --- a/src/sage/categories/magmas.py +++ b/src/sage/categories/magmas.py @@ -279,6 +279,66 @@ def extra_super_categories(self): class Unital(CategoryWithAxiom): + class ParentMethods: + @cached_method + def one(self): + r""" + Return the unit of the monoid, that is the unique neutral + element for `*`. + + .. NOTE:: + + The default implementation is to coerce `1` into ``self``. + It is recommended to override this method because the + coercion from the integers: + + - is not always meaningful (except for `1`); + - often uses ``self.one()``. + + EXAMPLES:: + + sage: M = Monoids().example(); M + An example of a monoid: the free monoid generated by ('a', 'b', 'c', 'd') + sage: M.one() + '' + """ + return self(1) + + def _test_one(self, **options): + r""" + Test that ``self.one()`` is an element of ``self`` and is + neutral for the operation ``*``. + + INPUT: + + - ``options`` -- any keyword arguments accepted by :meth:`_tester` + + EXAMPLES: + + By default, this method tests only the elements returned by + ``self.some_elements()``:: + + sage: S = Monoids().example() + sage: S._test_one() + + However, the elements tested can be customized with the + ``elements`` keyword argument:: + + sage: S._test_one(elements = (S('a'), S('b'))) + + See the documentation for :class:`TestSuite` for more information. + """ + tester = self._tester(**options) + one = self.one() + tester.assert_(self.is_parent_of(one)) + 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__()) + + class SubcategoryMethods: @cached_method @@ -311,7 +371,102 @@ def Inverse(self): return self._with_axiom("Inverse") class Inverse(CategoryWithAxiom): - pass + class CartesianProducts(CartesianProductsCategory): + def extra_super_categories(self): + """ + Implement the fact that a cartesian product of magmas with + inverses is a magma with inverse. + + EXAMPLES:: + + sage: C = Magmas().Unital().Inverse().CartesianProducts() + sage: C.extra_super_categories(); + [Category of inverse unital magmas] + sage: sorted(C.axioms()) + ['Inverse', 'Unital'] + """ + return [Magmas().Unital().Inverse()] + + class CartesianProducts(CartesianProductsCategory): + def extra_super_categories(self): + """ + Implement the fact that a cartesian product of unital magmas is + a unital magma + + EXAMPLES:: + + sage: C = Magmas().Unital().CartesianProducts() + sage: C.extra_super_categories(); + [Category of unital magmas] + sage: C.axioms() + frozenset(['Unital']) + + sage: Monoids().CartesianProducts().is_subcategory(Monoids()) + True + """ + return [Magmas().Unital()] + + class ParentMethods: + + @cached_method + def one(self): + """ + Return the unit of this cartesian product. + + It is built from the units for the cartesian factors of ``self``. + + EXAMPLES:: + + sage: cartesian_product([QQ, ZZ, RR]).one() + (1, 1, 1.00000000000000) + """ + return self._cartesian_product_of_elements( + _.one() for _ in self.cartesian_factors()) + + class ElementMethods: + def __invert__(self): + r""" + Return the inverse of ``self``, if it exists. + + The inverse is computed by inverting each + cartesian factor and attempting to convert the + result back to the original parent. + + For example, if one of the cartesian factor is an + element ``x`` of `\ZZ`, the result of ``~x`` is in + `\QQ`. So we need to convert it back to `\ZZ`. As + a side effect, this checks that ``x`` is indeed + invertible in `\ZZ`. + + If needed an optimized version without this + conversion could be implemented in + :class:`Magmas.Unital.Inverse.ElementMethods`. + + EXAMPLES:: + + sage: C = cartesian_product([QQ, ZZ, RR, GF(5)]) + sage: c = C([2,-1,2,2]); c + (2, -1, 2.00000000000000, 2) + sage: ~c + (1/2, -1, 0.500000000000000, 3) + + This fails as soon as one of the entries is not + invertible:: + + sage: ~C([0,2,2,2]) + Traceback (most recent call last): + ... + ZeroDivisionError: rational division by zero + + sage: ~C([2,2,2,2]) + Traceback (most recent call last): + ... + TypeError: no conversion of this rational to integer + """ + # variant without coercion: + # return self.parent()._cartesian_product_of_elements( + return self.parent()( + ~x for x in self.cartesian_factors()) class Algebras(AlgebrasCategory): @@ -701,8 +856,10 @@ def example(self): sage: C = Magmas().CartesianProducts().example(); C The cartesian product of (Rational Field, Integer Ring, Integer Ring) sage: C.category() - Join of Category of Cartesian products of monoids - and Category of Cartesian products of commutative additive groups + Join of Category of rings ... + sage: sorted(C.category().axioms()) + ['AdditiveAssociative', 'AdditiveCommutative', 'AdditiveInverse', 'AdditiveUnital', 'Associative', 'Distributive', 'Unital'] + sage: TestSuite(C).run() """ from cartesian_product import cartesian_product diff --git a/src/sage/categories/monoids.py b/src/sage/categories/monoids.py index edec3a791c3..dfe4f4ce828 100644 --- a/src/sage/categories/monoids.py +++ b/src/sage/categories/monoids.py @@ -66,29 +66,6 @@ class Monoids(CategoryWithAxiom): Inverse = LazyImport('sage.categories.groups', 'Groups', at_startup=True) class ParentMethods: - @cached_method - def one(self): - r""" - Return the unit of the monoid, that is the unique neutral - element for `*`. - - .. NOTE:: - - The default implementation is to coerce `1` into ``self``. - It is recommended to override this method because the - coercion from the integers: - - - is not always meaningful (except for `1`); - - often uses ``self.one()``. - - EXAMPLES:: - - sage: M = Monoids().example(); M - An example of a monoid: the free monoid generated by ('a', 'b', 'c', 'd') - sage: M.one() - '' - """ - return self(1) def one_element(self): r""" @@ -103,40 +80,6 @@ def one_element(self): """ return self.one() - def _test_one(self, **options): - r""" - Test that ``self.one()`` is an element of ``self`` and is - neutral for the operation ``*``. - - INPUT: - - - ``options`` -- any keyword arguments accepted by :meth:`_tester` - - EXAMPLES: - - By default, this method tests only the elements returned by - ``self.some_elements()``:: - - sage: S = Monoids().example() - sage: S._test_one() - - However, the elements tested can be customized with the - ``elements`` keyword argument:: - - sage: S._test_one(elements = (S('a'), S('b'))) - - See the documentation for :class:`TestSuite` for more information. - """ - tester = self._tester(**options) - one = self.one() - tester.assert_(self.is_parent_of(one)) - 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__()) - def prod(self, args): r""" n-ary product of elements of ``self``. @@ -296,36 +239,6 @@ def one(self): """ return self.retract(self.ambient().one()) - class CartesianProducts(CartesianProductsCategory): - """ - The category of monoids constructed as cartesian products of monoids - """ - def extra_super_categories(self): - """ - A cartesian product of monoids is endowed with a natural - monoid structure. - - EXAMPLES:: - - sage: Monoids().CartesianProducts().extra_super_categories() - [Category of monoids] - sage: Monoids().CartesianProducts().super_categories() - [Category of monoids, Category of Cartesian products of semigroups] - """ - return [Monoids()] - - class ParentMethods: - - @cached_method - def one(self): - """ - EXAMPLES:: - - sage: cartesian_product([QQ, ZZ, RR]).one() - (1, 1, 1.00000000000000) - """ - return cartesian_product([set.one() for set in self._sets]) - class Algebras(AlgebrasCategory): def extra_super_categories(self): diff --git a/src/sage/categories/primer.py b/src/sage/categories/primer.py index fdacef90d77..48d52f0f27a 100644 --- a/src/sage/categories/primer.py +++ b/src/sage/categories/primer.py @@ -1123,13 +1123,13 @@ class naming and introspection. Sage currently works around the sage: C.categories() [Category of Cartesian products of algebras with basis over Rational Field, ... - Category of Cartesian products of monoids, Category of monoids, - Category of Cartesian products of semigroups, Category of semigroups, + Category of Cartesian products of semigroups, Category of semigroups, ... Category of Cartesian products of magmas, ..., Category of magmas, ... + Category of Cartesian products of additive magmas, ..., Category of additive magmas, Category of Cartesian products of sets, Category of sets, ...] This reveals the parallel hierarchy of categories for cartesian -products of monoids, semigroups, ... We are thus glad that Sage uses +products of semigroups magmas, ... We are thus glad that Sage uses its knowledge that a monoid is a semigroup to automatically deduce that a cartesian product of monoids is a cartesian product of semigroups, and build the hierarchy of classes for parents and diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index d1f30b592e2..fd12410bfb3 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -16,6 +16,7 @@ from sage.categories.category_with_axiom import CategoryWithAxiom from sage.categories.rngs import Rngs from category import HomCategory +from functools import reduce class Rings(CategoryWithAxiom): """ diff --git a/src/sage/categories/semigroups.py b/src/sage/categories/semigroups.py index e3f119bd26d..5cc1aa90cdc 100644 --- a/src/sage/categories/semigroups.py +++ b/src/sage/categories/semigroups.py @@ -418,6 +418,9 @@ class CartesianProducts(CartesianProductsCategory): def extra_super_categories(self): """ + Implement the fact that a cartesian product of semigroups is a + semigroup. + EXAMPLES:: sage: Semigroups().CartesianProducts().extra_super_categories() diff --git a/src/sage/categories/sets_cat.py b/src/sage/categories/sets_cat.py index 38133eb461d..5954bd5e212 100644 --- a/src/sage/categories/sets_cat.py +++ b/src/sage/categories/sets_cat.py @@ -298,8 +298,7 @@ def CartesianProducts(self): sage: Semigroups().CartesianProducts() Category of Cartesian products of semigroups sage: EuclideanDomains().CartesianProducts() - Join of Category of Cartesian products of monoids - and Category of Cartesian products of commutative additive groups + Join of Category of rings and Category of Cartesian products of ... """ return CartesianProductsCategory.category_of(self) @@ -671,8 +670,8 @@ def Algebras(self, base_ring): sage: Groups().Algebras(QQ) Category of group algebras over Rational Field - sage: CommutativeAdditiveGroups().Algebras(QQ) - Category of commutative additive group algebras over Rational Field + sage: AdditiveMagmas().AdditiveAssociative().Algebras(QQ) + Category of additive semigroup algebras over Rational Field sage: Monoids().Algebras(Rings()) Category of monoid algebras over Category of rings @@ -1346,7 +1345,8 @@ def cartesian_product(*parents): sage: type(C) sage: C.category() - Join of Category of Cartesian products of monoids and Category of Cartesian products of commutative additive groups + Join of Category of rings and ... + and Category of Cartesian products of commutative additive groups """ return parents[0].CartesianProduct( parents, diff --git a/src/sage/coding/code_bounds.py b/src/sage/coding/code_bounds.py index 67b7cf09fa3..1dbad5844d6 100644 --- a/src/sage/coding/code_bounds.py +++ b/src/sage/coding/code_bounds.py @@ -392,7 +392,7 @@ def get_list(n,d,q): for i in range(1,int(r*n)+1): if i**2-2*r*n*i+r*n*d>0: I.append(i) - return I + return I I = get_list(n,d,q) bnd = min([ff(n,d,w,q) for w in I]) return int(bnd) diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index 8e3e660c7de..20839f9c92c 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -1246,9 +1246,9 @@ def chinen_polynomial(self): # an easy thing to do. Some tricky gymnastics are used to # make Sage deal with objects over QQ(sqrt(q)) nicely. if is_even(n): - Pd = q**(k-n/2)*RT(Cd.zeta_polynomial())*T**(dperp - d) - if not(is_even(n)): - Pd = s*q**(k-(n+1)/2)*RT(Cd.zeta_polynomial())*T**(dperp - d) + Pd = q**(k-n//2) * RT(Cd.zeta_polynomial()) * T**(dperp - d) + else: + Pd = s * q**(k-(n+1)//2) * RT(Cd.zeta_polynomial()) * T**(dperp - d) CP = P+Pd f = CP/CP(1,s) return f(t,sqrt(q)) @@ -2549,16 +2549,16 @@ def sd_duursma_data(C, i): n = C.length() d = C.minimum_distance() if i == 1: - v = (n-4*d)/2 + 4 + v = (n-4*d)//2 + 4 m = d-3 - if i == 2: - v = (n-6*d)/8 + 3 + elif i == 2: + v = (n-6*d)//8 + 3 m = d-5 - if i == 3: - v = (n-4*d)/4 + 3 + elif i == 3: + v = (n-4*d)//4 + 3 m = d-4 - if i == 4: - v = (n-3*d)/2 + 3 + elif i == 4: + v = (n-3*d)//2 + 3 m = d-3 return [v,m] diff --git a/src/sage/combinat/abstract_tree.py b/src/sage/combinat/abstract_tree.py index a899d53b8c1..b0c360f7c00 100644 --- a/src/sage/combinat/abstract_tree.py +++ b/src/sage/combinat/abstract_tree.py @@ -66,6 +66,7 @@ from sage.structure.list_clone import ClonableArray from sage.rings.integer import Integer from sage.misc.misc_c import prod +from functools import reduce # Unfortunately Cython forbids multiple inheritance. Therefore, we do not @@ -1302,7 +1303,7 @@ def pair_nodes_tree(self, nodes, edges, matrix): # build all subtree matrices. node, name = create_node(self) edge = [name] - split = int(len(self) / 2) + split = len(self) // 2 # the left part for i in range(split): tmp(self[i], edge, nodes, edges, matrix) @@ -1391,7 +1392,7 @@ def odd_nodes_tree(self, nodes, edges, matrix): # build all subtree matrices. node, name = create_node(self) edge = [name] - split = int(len(self) / 2) + split = len(self) // 2 # the left part for i in range(split): tmp(self[i], edge, nodes, edges, matrix) diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index 012950d6d44..4c27445c5fb 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -110,10 +110,13 @@ from non_decreasing_parking_function import NonDecreasingParkingFunctions, NonDecreasingParkingFunction from parking_functions import ParkingFunctions, ParkingFunction +# Trees and Tamari interval posets +from sage.misc.lazy_import import lazy_import from ordered_tree import (OrderedTree, OrderedTrees, LabelledOrderedTree, LabelledOrderedTrees) from binary_tree import (BinaryTree, BinaryTrees, LabelledBinaryTree, LabelledBinaryTrees) +lazy_import('sage.combinat.interval_posets', ['TamariIntervalPoset', 'TamariIntervalPosets']) from combination import Combinations from cartesian_product import CartesianProduct @@ -181,7 +184,6 @@ from gelfand_tsetlin_patterns import GelfandTsetlinPattern, GelfandTsetlinPatterns # Finite State Machines (Automaton, Transducer) -from sage.misc.lazy_import import lazy_import lazy_import('sage.combinat.finite_state_machine', ['Automaton', 'Transducer', 'FiniteStateMachine']) lazy_import('sage.combinat.finite_state_machine_generators', diff --git a/src/sage/combinat/binary_tree.py b/src/sage/combinat/binary_tree.py index af5b64df875..309ff23e5a5 100644 --- a/src/sage/combinat/binary_tree.py +++ b/src/sage/combinat/binary_tree.py @@ -690,7 +690,7 @@ def _to_dyck_word_rec(self, usemap="1L0R"): else: return [] - @combinatorial_map(name = "to the Tamari corresponding Dyck path") + @combinatorial_map(name="to the Tamari corresponding Dyck path") def to_dyck_word_tamari(self): r""" Return the Dyck word associated with ``self`` in consistency with @@ -698,10 +698,10 @@ def to_dyck_word_tamari(self): The bijection is defined recursively as follows: - - a leaf is associated with an empty Dyck word + - a leaf is associated with an empty Dyck word; - a tree with children `l,r` is associated with the Dyck word - `T(l) 1 T(r) 0` + `T(l) 1 T(r) 0`. EXAMPLES:: @@ -716,6 +716,65 @@ def to_dyck_word_tamari(self): """ return self.to_dyck_word("L1R0") + def tamari_interval(self, other): + r""" + Return the Tamari interval between ``self`` and ``other`` as a + :class:`~sage.combinat.interval_posets.TamariIntervalPoset`. + + A "Tamari interval" is an interval in the Tamari poset. + See :meth:`tamari_lequal` for the definition of the Tamari poset. + + INPUT: + + - ``other`` -- a binary tree greater or equal to ``self`` + in the Tamari order + + EXAMPLES:: + + sage: bt = BinaryTree([[None, [[], None]], None]) + sage: ip = bt.tamari_interval(BinaryTree([None, [[None, []], None]])); ip + The tamari interval of size 4 induced by relations [(2, 4), (3, 4), (3, 1), (2, 1)] + sage: ip.lower_binary_tree() + [[., [[., .], .]], .] + sage: ip.upper_binary_tree() + [., [[., [., .]], .]] + sage: ip.interval_cardinality() + 4 + sage: ip.number_of_tamari_inversions() + 2 + sage: list(ip.binary_trees()) + [[., [[., [., .]], .]], + [[., [., [., .]]], .], + [., [[[., .], .], .]], + [[., [[., .], .]], .]] + sage: bt.tamari_interval(BinaryTree([[None,[]],[]])) + Traceback (most recent call last): + ... + ValueError: The two binary trees are not comparable on the Tamari lattice. + + TESTS: + + Setting ``other`` equal to ``bt`` gives an interval consisting of + just one element:: + + sage: ip = bt.tamari_interval(bt) + sage: ip + The tamari interval of size 4 induced by relations [(1, 4), (2, 3), (3, 4), (3, 1), (2, 1)] + sage: list(ip.binary_trees()) + [[[., [[., .], .]], .]] + + Empty trees work well:: + + sage: bt = BinaryTree() + sage: ip = bt.tamari_interval(bt) + sage: ip + The tamari interval of size 0 induced by relations [] + sage: list(ip.binary_trees()) + [.] + """ + from sage.combinat.interval_posets import TamariIntervalPosets + return TamariIntervalPosets.from_binary_trees(self, other) + @combinatorial_map(name="to Dyck paths: up step, left tree, down step, right tree") def to_dyck_word(self, usemap="1L0R"): r""" @@ -1409,12 +1468,10 @@ def in_order_traversal(self, node_action=None, leaf_action=None): else: node_action(node) - def tamari_greater(self): + def tamari_lequal(self, t2): r""" - The list of all trees greater or equal to ``self`` in the Tamari - order. - - This is the order filter of the Tamari order generated by ``self``. + Return ``True`` if ``self`` is less or equal to another binary + tree ``t2`` (of the same size as ``self``) in the Tamari order. The Tamari order on binary trees of size `n` is the partial order on the set of all binary trees of size `n` generated by the @@ -1423,8 +1480,88 @@ def tamari_greater(self): then `T < T'`. This not only is a well-defined partial order, but actually is a lattice structure on the set of binary trees of size `n`, and - is a quotient of the weak order on the `n`-th symmetric group. - See [CP12]_. + is a quotient of the weak order on the `n`-th symmetric group + (also known as the right permutohedron order, see + :meth:`~sage.combinat.permutation.Permutation.permutohedron_lequal`). + See [CP12]_. The set of binary trees of size `n` equipped with + the Tamari order is called the `n`-th Tamari poset. + + The Tamari order can equivalently be defined as follows: + + If `T` and `S` are two binary trees of size `n`, then the + following four statements are equivalent: + + - We have `T \leq S` in the Tamari order. + + - There exist elements `t` and `s` of the Sylvester classes + (:meth:`sylvester_class`) of `T` and `S`, respectively, + such that `t \leq s` in the weak order on the symmetric + group. + + - The 132-avoiding permutation corresponding to `T` (see + :meth:`to_132_avoiding_permutation`) is `\leq` to the + 132-avoiding permutation corresponding to `S` in the weak + order on the symmetric group. + + - The 312-avoiding permutation corresponding to `T` (see + :meth:`to_312_avoiding_permutation`) is `\leq` to the + 312-avoiding permutation corresponding to `S` in the weak + order on the symmetric group. + + .. SEEALSO:: + + :meth:`tamari_smaller`, :meth:`tamari_greater`, + :meth:`tamari_pred`, :meth:`tamari_succ`, + :meth:`tamari_interval` + + EXAMPLES: + + This tree:: + + | o | + | / \ | + | o o | + | / | + | o | + | / \ | + | o o | + + is Tamari-`\leq` to the following tree:: + + | _o_ | + | / \ | + | o o | + | / \ \ | + | o o o | + + Checking this:: + + sage: b = BinaryTree([[[[], []], None], []]) + sage: c = BinaryTree([[[],[]],[None,[]]]) + sage: b.tamari_lequal(c) + True + + TESTS:: + + sage: for T in BinaryTrees(4): + ....: for S in T.tamari_smaller(): + ....: if S != T and T.tamari_lequal(S): + ....: print "FAILURE" + ....: if not S.tamari_lequal(T): + ....: print "FAILURE" + """ + self_perm = self.to_312_avoiding_permutation() + t2_perm = t2.to_312_avoiding_permutation() + return self_perm.permutohedron_lequal(t2_perm) + + def tamari_greater(self): + r""" + The list of all trees greater or equal to ``self`` in the Tamari + order. + + This is the order filter of the Tamari order generated by ``self``. + + See :meth:`tamari_lequal` for the definition of the Tamari poset. .. SEEALSO:: @@ -1498,6 +1635,8 @@ def tamari_pred(self): This list is computed by performing all left rotates possible on its nodes. + See :meth:`tamari_lequal` for the definition of the Tamari poset. + EXAMPLES: For this tree:: @@ -1547,15 +1686,7 @@ def tamari_smaller(self): This is the order ideal of the Tamari order generated by ``self``. - The Tamari order on binary trees of size `n` is the partial order - on the set of all binary trees of size `n` generated by the - following requirement: If a binary tree `T'` is obtained by - right rotation (see :meth:`right_rotate`) from a binary tree `T`, - then `T < T'`. - This not only is a well-defined partial order, but actually is - a lattice structure on the set of binary trees of size `n`, and - is a quotient of the weak order on the `n`-th symmetric group. - See [CP12]_. + See :meth:`tamari_lequal` for the definition of the Tamari poset. .. SEEALSO:: @@ -1613,6 +1744,8 @@ def tamari_succ(self): This is the list of all trees obtained by a right rotate of one of its nodes. + See :meth:`tamari_lequal` for the definition of the Tamari poset. + EXAMPLES: The list of successors of:: @@ -2215,12 +2348,13 @@ def sylvester_class(self, left_to_right=False): ``self``. The sylvester class of a tree `T` is the set of permutations - `\sigma` whose binary search tree (a notion defined in [HNT05]_, - Definition 7) is `T` after forgetting the labels. This is an - equivalence class of the sylvester congruence (the congruence on - words which holds two words `uacvbw` and `ucavbw` congruent - whenever `a`, `b`, `c` are letters satisfying `a \leq b < c`, and - extends by transitivity) on the symmetric group. + `\sigma` whose right-to-left binary search tree (a notion defined + in [HNT05]_, Definition 7) is `T` after forgetting the labels. + This is an equivalence class of the sylvester congruence (the + congruence on words which holds two words `uacvbw` and `ucavbw` + congruent whenever `a`, `b`, `c` are letters satisfying + `a \leq b < c`, and extends by transitivity) on the symmetric + group. For example the following tree's sylvester class consists of the permutations `(1,3,2)` and `(3,1,2)`:: @@ -2231,15 +2365,88 @@ def sylvester_class(self, left_to_right=False): (only the nodes are drawn here). - The binary search tree of a word is constructed by an RSK-like - insertion algorithm which proceeds as follows: Start with an - empty labelled binary tree, and read the word from left to right. - Each time a letter is read from the word, insert this letter in - the existing tree using binary search tree insertion + The right-to-left binary search tree of a word is constructed by + an RSK-like insertion algorithm which proceeds as follows: Start + with an empty labelled binary tree, and read the word from right + to left. Each time a letter is read from the word, insert this + letter in the existing tree using binary search tree insertion (:meth:`~sage.combinat.binary_tree.LabelledBinaryTree.binary_search_insert`). - If a left-to-right reading is to be employed instead, the - ``left_to_right`` optional keyword variable should be set to - ``True``. + This is what the + :meth:`~sage.combinat.permutation.Permutation.binary_search_tree` + method computes if it is given the keyword + ``left_to_right=False``. + + Here are two more descriptions of the sylvester class of a binary + search tree: + + - The sylvester class of a binary search tree `T` is the set of + all linear extensions of the poset corresponding to `T` (that + is, of the poset whose Hasse diagram is `T`, with the root on + top), provided that the nodes of `T` are labelled with + `1, 2, \ldots, n` in a binary-search-tree way (i.e., every left + descendant of a node has a label smaller than that of the node, + and every right descendant of a node has a label higher than + that of the node). + + - The sylvester class of a binary search tree `T` (with vertex + labels `1, 2, \ldots, n`) is the interval `[u, v]` in the right + permutohedron order + (:meth:`~sage.combinat.permutation.Permutation.permutohedron_lequal`), + where `u` is the 312-avoiding permutation corresponding to `T` + (:meth:`to_312_avoiding_permutation`), and where `v` is the + 132-avoiding permutation corresponding to `T` + (:meth:`to_132_avoiding_permutation`). + + If the optional keyword variable ``left_to_right`` is set to + ``True``, then the *left* sylvester class of ``self`` is + returned instead. This is the set of permutations `\sigma` whose + left-to-right binary search tree (that is, the result of the + :meth:`~sage.combinat.permutation.Permutation.binary_search_tree` + with ``left_to_right`` set to ``True``) is ``self``. It is an + equivalence class of the left sylvester congruence. + + .. WARNING:: + + This method yields the elements of the sylvester class as + raw lists, not as permutations! + + EXAMPLES: + + Verifying the claim that the right-to-left binary search trees of + the permutations in the sylvester class of a tree `t` all equal + `t`:: + + sage: def test_bst_of_sc(n, left_to_right): + ....: for t in BinaryTrees(n): + ....: for p in t.sylvester_class(left_to_right=left_to_right): + ....: p_per = Permutation(p) + ....: tree = p_per.binary_search_tree(left_to_right=left_to_right) + ....: if not BinaryTree(tree) == t: + ....: return False + ....: return True + sage: test_bst_of_sc(4, False) + True + sage: test_bst_of_sc(5, False) # long time + True + sage: test_bst_of_sc(6, False) # long time + True + + The same with the left-to-right version of binary search:: + + sage: test_bst_of_sc(4, True) + True + sage: test_bst_of_sc(5, True) # long time + True + sage: test_bst_of_sc(6, True) # long time + True + + Checking that the sylvester class is the set of linear extensions + of the poset of the tree:: + + sage: all( sorted(t.canonical_labelling().sylvester_class()) + ....: == sorted(list(v) for v in t.canonical_labelling().to_poset().linear_extensions()) + ....: for t in BinaryTrees(4) ) + True TESTS:: @@ -2291,8 +2498,8 @@ def sylvester_class(self, left_to_right=False): builder = lambda i, p: list(p) + [i] shift = self[0].node_number() + 1 - for l, r in product(self[0].sylvester_class(), - self[1].sylvester_class()): + for l, r in product(self[0].sylvester_class(left_to_right=left_to_right), + self[1].sylvester_class(left_to_right=left_to_right)): for p in shuffle(W(l), W([shift + ri for ri in r])): yield builder(shift, p) diff --git a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py index eeecc7a37f2..6865641c43d 100644 --- a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py +++ b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py @@ -2129,7 +2129,7 @@ def SetToPath(T): sage: SetToPath(PathSubset(4,4)) [1, 0, 3, 2, 5, 4, 7, 6] """ - n = (max(T)+1)/2 + n = (max(T)+1) // 2 ans = [1] for i in range(n-1): if 2*i in T: diff --git a/src/sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py b/src/sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py index 6feae7b2836..d8026324968 100644 --- a/src/sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +++ b/src/sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py @@ -1614,7 +1614,7 @@ def class_size(self): n = self._rank a = binomial( 2*(n+1), n+1 ) / (n+2) if n % 2 == 1: - a += binomial( n+1, (n+1)/2 ) + a += binomial( n+1, (n+1)//2 ) if n % 3 == 0: a += 2 * binomial( 2*n/3, n/3 ) return a / (n+3) @@ -1966,7 +1966,7 @@ def _construct_classical_mutation_classes(n): # finite A data[ ('A',n) ] = ClusterQuiver(['A',n]).mutation_class(data_type='dig6') # affine A - for j in range(1, n/2+1): + for j in range(1, n//2+1): data[ ('A',(n-j,j),1) ] = ClusterQuiver(['A',[n-j,j],1]).mutation_class(data_type='dig6') # finite B if n > 1: diff --git a/src/sage/combinat/combinatorial_map.py b/src/sage/combinat/combinatorial_map.py index 79ada34ee8b..d208dc8c483 100644 --- a/src/sage/combinat/combinatorial_map.py +++ b/src/sage/combinat/combinatorial_map.py @@ -1,5 +1,52 @@ """ Combinatorial maps + +This module provides a decorator that can be used to add semantic to a +Python method by marking it as implementing a *combinatorial map*, +that is a map between two :class:`enumerated sets `:: + + sage: from sage.combinat.combinatorial_map import combinatorial_map + sage: class MyPermutation(object): + ....: + ....: @combinatorial_map() + ....: def reverse(self): + ....: ''' + ....: Reverse the permutation + ....: ''' + ....: # ... code ... + +By default, this decorator is a no-op: it returns the decorated method +as is:: + + sage: MyPermutation.reverse + + +See :func:`combinatorial_map_wrapper` for the various options this +decorator can take. + +Projects built on top of Sage are welcome to customize locally this +hook to instrument the Sage code and exploit this semantic +information. Typically, the decorator could be used to populate a +database of maps. For a real-life application, see the project +`FindStat `. As a basic example, a variant of +the decorator is provided as :func:`combinatorial_map_wrapper`; it +wraps the decorated method, so that one can later use +:func:`combinatorial_maps_in_class` to query an object, or class +thereof, for all the combinatorial maps that apply to it. + +.. NOTE:: + + Since decorators are evaluated upon loading Python modules, + customizing :obj:`combinatorial map` needs to be done before the + modules using it are loaded. In the examples below, where we + illustrate the customized ``combinatorial_map`` decorator on the + :mod:`sage.combinat.permutation` module, we resort to force a + reload of this module after dynamically changing + ``sage.combinat.combinatorial_map.combinatorial_map``. This is + good enough for those doctests, but remains fragile. + + For real use cases it's probably best to just edit this source + file statically (see below). """ #***************************************************************************** # Copyright (C) 2011 Christian Stump @@ -8,64 +55,108 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -def combinatorial_map(f=None, order=None, name=None): +def combinatorial_map_trivial(f=None, order=None, name=None): r""" - Combinatorial maps + Combinatorial map decorator - We call a method a *combinatorial map* if it is a map between two - combinatorial sets. + See :ref:`sage.combinat.combinatorial_map` for a description of + this decorator and its purpose. This default implementation does + nothing. INPUT: + - ``f`` -- (default: ``None``, if combinatorial_map is used as a decorator) a function - ``name`` -- (default: ``None``) the name for nicer outputs on combinatorial maps - ``order`` -- (default: ``None``) the order of the combinatorial map, if it is known. Is not used, but might be helpful later OUTPUT: - - A combinatorial map. This is an instance of the :class:`CombinatorialMap` - - The decorator :obj:`combinatorial_map` can be used to declare - methods as combinatorial maps. + - ``f`` unchanged EXAMPLES:: - sage: p = Permutation([1,3,2,4]) - sage: p.left_tableau - Combinatorial map: Robinson-Schensted insertion tableau + sage: from sage.combinat.combinatorial_map import combinatorial_map_trivial as combinatorial_map + sage: class MyPermutation(object): + ....: + ....: @combinatorial_map + ....: def reverse(self): + ....: ''' + ....: Reverse the permutation + ....: ''' + ....: # ... code ... + ....: + ....: @combinatorial_map(name='descent set of permutation') + ....: def descent_set(self): + ....: ''' + ....: The descent set of the permutation + ....: ''' + ....: # ... code ... + + sage: MyPermutation.reverse + + sage: MyPermutation.descent_set + + """ + if f is None: + return lambda f: f + else: + return f - We define a class illustrating the use of the decorator - :obj:`combinatorial_map` with the various arguments:: +def combinatorial_map_wrapper(f=None, order=None, name=None): + r""" + Combinatorial map decorator (basic example). + + See :ref:`sage.combinat.combinatorial_map` for a description of + the ``combinatorial_map`` decorator and its purpose. This + implementation, together with :func:`combinatorial_maps_in_class` + illustrates how to use this decorator as a hook to instrument the + Sage code. - sage: from sage.combinat.combinatorial_map import combinatorial_map + INPUT: + + - ``f`` -- (default: ``None``, if combinatorial_map is used as a decorator) a function + - ``name`` -- (default: ``None``) the name for nicer outputs on combinatorial maps + - ``order`` -- (default: ``None``) the order of the combinatorial map, if it is known. Is not used, but might be helpful later + + OUTPUT: + + - A combinatorial map. This is an instance of the :class:`CombinatorialMap`. + + EXAMPLES: + + We define a class illustrating the use of this implementation of + the :obj:`combinatorial_map` decorator with its various arguments:: + + sage: from sage.combinat.combinatorial_map import combinatorial_map_wrapper as combinatorial_map sage: class MyPermutation(object): - ... - ... @combinatorial_map() - ... def reverse(self): - ... ''' - ... Reverse the permutation - ... ''' - ... pass - ... - ... @combinatorial_map(order=2) - ... def inverse(self): - ... ''' - ... The inverse of the permutation - ... ''' - ... pass - ... - ... @combinatorial_map(name='descent set of permutation') - ... def descent_set(self): - ... ''' - ... The descent set of the permutation - ... ''' - ... pass - ... - ... def major_index(self): - ... ''' - ... The major index of the permutation - ... ''' - ... pass + ....: + ....: @combinatorial_map() + ....: def reverse(self): + ....: ''' + ....: Reverse the permutation + ....: ''' + ....: pass + ....: + ....: @combinatorial_map(order=2) + ....: def inverse(self): + ....: ''' + ....: The inverse of the permutation + ....: ''' + ....: pass + ....: + ....: @combinatorial_map(name='descent set of permutation') + ....: def descent_set(self): + ....: ''' + ....: The descent set of the permutation + ....: ''' + ....: pass + ....: + ....: def major_index(self): + ....: ''' + ....: The major index of the permutation + ....: ''' + ....: pass sage: MyPermutation.reverse Combinatorial map: reverse sage: MyPermutation.descent_set @@ -73,8 +164,8 @@ def combinatorial_map(f=None, order=None, name=None): sage: MyPermutation.inverse Combinatorial map: inverse - One can determine all the combinatorial maps associated with a given object - as follows:: + One can now determine all the combinatorial maps associated with a + given object as follows:: sage: from sage.combinat.combinatorial_map import combinatorial_maps_in_class sage: X = combinatorial_maps_in_class(MyPermutation); X # random @@ -90,8 +181,8 @@ def combinatorial_map(f=None, order=None, name=None): But one can define a function that turns ``major_index`` into a combinatorial map:: sage: def major_index(p): - ... return p.major_index() - ... + ....: return p.major_index() + ....: sage: major_index sage: combinatorial_map(major_index) @@ -103,11 +194,19 @@ def combinatorial_map(f=None, order=None, name=None): else: return CombinatorialMap(f, order=order, name=name) +############################################################################## +# Edit here to customize the combinatorial_map hook +############################################################################## +combinatorial_map = combinatorial_map_trivial +#combinatorial_map = combinatorial_map_wrapper + class CombinatorialMap(object): r""" This is a wrapper class for methods that are *combinatorial maps*. - For further details and doctests, see :func:`combinatorial_map`. + For further details and doctests, see + :ref:`sage.combinat.combinatorial_map` and + :func:`combinatorial_map_wrapper`. """ def __init__(self, f, order=None, name=None): """ @@ -115,11 +214,11 @@ def __init__(self, f, order=None, name=None): EXAMPLES:: - sage: from sage.combinat.combinatorial_map import combinatorial_map + sage: from sage.combinat.combinatorial_map import combinatorial_map_wrapper as combinatorial_map sage: def f(x): - ... "doc of f" - ... return x - ... + ....: "doc of f" + ....: return x + ....: sage: x = combinatorial_map(f); x Combinatorial map: f sage: x.__doc__ @@ -148,6 +247,8 @@ def __repr__(self): """ EXAMPLES:: + sage: sage.combinat.combinatorial_map.combinatorial_map = sage.combinat.combinatorial_map.combinatorial_map_wrapper + sage: _ = reload(sage.combinat.permutation); sage: p = Permutation([1,3,2,4]) sage: p.left_tableau.__repr__() 'Combinatorial map: Robinson-Schensted insertion tableau' @@ -160,6 +261,8 @@ def _sage_src_lines_(self): EXAMPLES:: + sage: sage.combinat.combinatorial_map.combinatorial_map = sage.combinat.combinatorial_map.combinatorial_map_wrapper + sage: _ = reload(sage.combinat.permutation); sage: p = Permutation([1,3,2,4]) sage: cm = p.left_tableau; cm Combinatorial map: Robinson-Schensted insertion tableau @@ -178,6 +281,8 @@ def __get__(self, inst, cls=None): EXAMPLES:: + sage: sage.combinat.combinatorial_map.combinatorial_map = sage.combinat.combinatorial_map.combinatorial_map_wrapper + sage: _ = reload(sage.combinat.permutation); sage: p = Permutation([1,3,2,4]) sage: p.left_tableau #indirect doctest Combinatorial map: Robinson-Schensted insertion tableau @@ -191,6 +296,8 @@ def __call__(self, *args, **kwds): EXAMPLES:: + sage: sage.combinat.combinatorial_map.combinatorial_map = sage.combinat.combinatorial_map.combinatorial_map_wrapper + sage: _ = reload(sage.combinat.permutation); sage: p = Permutation([1,3,2,4]) sage: cm = type(p).left_tableau; cm Combinatorial map: Robinson-Schensted insertion tableau @@ -214,6 +321,8 @@ def unbounded_map(self): EXAMPLES:: + sage: sage.combinat.combinatorial_map.combinatorial_map = sage.combinat.combinatorial_map.combinatorial_map_wrapper + sage: _ = reload(sage.combinat.permutation); sage: from sage.combinat.permutation import Permutation sage: pi = Permutation([1,3,2]) sage: f = pi.reverse @@ -231,10 +340,10 @@ def order(self): sage: from sage.combinat.combinatorial_map import combinatorial_map sage: class CombinatorialClass: - ... @combinatorial_map(order=2) - ... def to_self_1(): pass - ... @combinatorial_map() - ... def to_self_2(): pass + ....: @combinatorial_map(order=2) + ....: def to_self_1(): pass + ....: @combinatorial_map() + ....: def to_self_2(): pass sage: CombinatorialClass.to_self_1.order() 2 sage: CombinatorialClass.to_self_2.order() is None @@ -251,10 +360,10 @@ def name(self): sage: from sage.combinat.combinatorial_map import combinatorial_map sage: class CombinatorialClass: - ... @combinatorial_map(name='map1') - ... def to_self_1(): pass - ... @combinatorial_map() - ... def to_self_2(): pass + ....: @combinatorial_map(name='map1') + ....: def to_self_1(): pass + ....: @combinatorial_map() + ....: def to_self_2(): pass sage: CombinatorialClass.to_self_1.name() 'map1' sage: CombinatorialClass.to_self_2.name() @@ -267,10 +376,16 @@ def name(self): def combinatorial_maps_in_class(cls): """ - Returns the combinatorial maps of the class as a list of combinatorial maps. + Return the combinatorial maps of the class as a list of combinatorial maps. + + For further details and doctests, see + :ref:`sage.combinat.combinatorial_map` and + :func:`combinatorial_map_wrapper`. EXAMPLES:: + sage: sage.combinat.combinatorial_map.combinatorial_map = sage.combinat.combinatorial_map.combinatorial_map_wrapper + sage: _ = reload(sage.combinat.permutation); sage: from sage.combinat.combinatorial_map import combinatorial_maps_in_class sage: p = Permutation([1,3,2,4]) sage: cmaps = combinatorial_maps_in_class(p) diff --git a/src/sage/combinat/crystals/kirillov_reshetikhin.py b/src/sage/combinat/crystals/kirillov_reshetikhin.py index 84ffced0b2c..41dd3138d03 100644 --- a/src/sage/combinat/crystals/kirillov_reshetikhin.py +++ b/src/sage/combinat/crystals/kirillov_reshetikhin.py @@ -2303,7 +2303,7 @@ def epsilon0(self): 1 """ b = self.parent().to_ambient_crystal()(self) - return b.epsilon(0)/2 + return b.epsilon(0) // 2 def phi0(self): r""" @@ -2318,7 +2318,7 @@ def phi0(self): 0 """ b = self.parent().to_ambient_crystal()(self) - return b.phi(0)/2 + return b.phi(0) // 2 KR_type_Bn.Element = KR_type_BnElement @@ -2639,9 +2639,9 @@ def from_pm_diagram_to_highest_weight_vector(self, pm): plus = pm.heights_of_addable_plus() minus = pm.heights_of_minus() l = len([i for i in plus if i==rank-1]) - a = (len(plus) + l)/2 + a = (len(plus) + l) // 2 list += sum(([i]*a for i in range(1,rank+1)),[]) - a = (len(minus)-l)/2 + a = (len(minus)-l) // 2 list += (range(1,rank+1)+[rank])*a for i in reversed(list): u = u.f(i) @@ -3170,7 +3170,7 @@ def outer_shape(self): """ t = [] ll = self._list - for i in range((self.n)/2): + for i in range(self.n // 2): t.append(sum(ll[0:4*i+4])) t.append(sum(ll[0:4*i+4])) if is_even(self.n+1): diff --git a/src/sage/combinat/designs/bibd.py b/src/sage/combinat/designs/bibd.py index 1c63feb5bbd..97f4de26d7e 100644 --- a/src/sage/combinat/designs/bibd.py +++ b/src/sage/combinat/designs/bibd.py @@ -30,9 +30,23 @@ can always be transformed into a `((k-1)v+1,4,1)`-BIBD, which covers all possible cases of `(v,4,1)`-BIBD. +`K_5`-decompositions of `K_v` +----------------------------- + +Decompositions of `K_v` into `K_4` (i.e. `(v,4,1)`-BIBD) are built following +Clayton Smith's construction [ClaytonSmith]_. + +.. [ClaytonSmith] On the existence of `(v,5,1)`-BIBD. + http://www.argilo.net/files/bibd.pdf + Clayton Smith + + Functions --------- """ + +from sage.categories.sets_cat import EmptySetError +from sage.misc.unknown import Unknown from design_catalog import transversal_design from block_design import BlockDesign from sage.rings.arith import binomial @@ -73,12 +87,12 @@ def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): .. SEEALSO:: - * :meth:`steiner_triple_system` - * :meth:`v_4_1_BIBD` + * :func:`steiner_triple_system` + * :func:`v_4_1_BIBD` + * :func:`v_5_1_BIBD` TODO: - * Implement `(v,5,1)`-BIBD using `this text `_. * Implement other constructions from the Handbook of Combinatorial Designs. @@ -101,6 +115,10 @@ def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): Traceback (most recent call last): ... ValueError: No such design exists ! + sage: designs.BalancedIncompleteBlockDesign(16,6) + Traceback (most recent call last): + ... + NotImplementedError: I don't know how to build this design. TESTS:: @@ -112,21 +130,43 @@ def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): sage: _ = designs.BalancedIncompleteBlockDesign(21,5) - A trivial BIBD:: + Some trivial BIBD:: sage: designs.BalancedIncompleteBlockDesign(10,10) Incidence structure with 10 points and 1 blocks + sage: designs.BalancedIncompleteBlockDesign(1,10) + Incidence structure with 1 points and 0 blocks + + Existence of BIBD with `k=3,4,5`:: + + sage: [v for v in xrange(50) if designs.BalancedIncompleteBlockDesign(v,3,existence=True)] + [1, 3, 7, 9, 13, 15, 19, 21, 25, 27, 31, 33, 37, 39, 43, 45, 49] + sage: [v for v in xrange(100) if designs.BalancedIncompleteBlockDesign(v,4,existence=True)] + [1, 4, 13, 16, 25, 28, 37, 40, 49, 52, 61, 64, 73, 76, 85, 88, 97] + sage: [v for v in xrange(150) if designs.BalancedIncompleteBlockDesign(v,5,existence=True)] + [1, 5, 21, 25, 41, 45, 61, 65, 81, 85, 101, 105, 121, 125, 141, 145] + + For `k > 5` there are currently very few constructions:: + + sage: [v for v in xrange(150) if designs.BalancedIncompleteBlockDesign(v,6,existence=True) is True] + [1, 6, 31] + sage: [v for v in xrange(150) if designs.BalancedIncompleteBlockDesign(v,6,existence=True) is Unknown] + [16, 21, 36, 46, 51, 61, 66, 76, 81, 91, 96, 106, 111, 121, 126, 136, 141] """ - if ((binomial(v,2)%binomial(k,2) != 0) or - (v-1)%(k-1) != 0): + if v == 1: if existence: - return False - raise ValueError("No such design exists !") + return True + return BlockDesign(v, [], test=False) if k == v: if existence: return True - return BlockDesign(v,[range(v)], test=False) + return BlockDesign(v, [range(v)], test=False) + + if v < k or k < 2 or (v-1) % (k-1) != 0 or (v*(v-1)) % (k*(k-1)) != 0: + if existence: + return False + raise EmptySetError("No such design exists !") if k == 2: if existence: @@ -135,12 +175,17 @@ def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): return BlockDesign(v, combinations(range(v),2), test = False) if k == 3: if existence: - return bool((n%6) in [1,3]) + return v%6 == 1 or v%6 == 3 return steiner_triple_system(v) if k == 4: if existence: - return bool((n%12) in [1,4]) + return v%12 == 1 or v%12 == 4 return BlockDesign(v, v_4_1_BIBD(v), test = False) + if k == 5: + if existence: + return v%20 == 1 or v%20 == 5 + return BlockDesign(v, v_5_1_BIBD(v), test = False) + if BIBD_from_TD(v,k,existence=True): if existence: return True @@ -159,7 +204,7 @@ def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): if B.low_bd() > expected_n_of_blocks: if existence: return False - raise ValueError("No such design exists !") + raise EmptySetError("No such design exists !") B = B.incidence_structure() if len(B.blcks) == expected_n_of_blocks: if existence: @@ -168,10 +213,9 @@ def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): return B if existence: - from sage.misc.unknown import Unknown return Unknown else: - raise ValueError("I don't know how to build this design.") + raise NotImplementedError("I don't know how to build this design.") def steiner_triple_system(n): r""" @@ -214,7 +258,7 @@ def steiner_triple_system(n): sage: designs.steiner_triple_system(10) Traceback (most recent call last): ... - ValueError: Steiner triple systems only exist for n = 1 mod 6 or n = 3 mod 6 + EmptySetError: Steiner triple systems only exist for n = 1 mod 6 or n = 3 mod 6 REFERENCE: @@ -227,7 +271,7 @@ def steiner_triple_system(n): name = "Steiner Triple System on "+str(n)+" elements" if n%6 == 3: - t = (n-3)/6 + t = (n-3) // 6 Z = range(2*t+1) T = lambda (x,y) : x + (2*t+1)*y @@ -237,19 +281,19 @@ def steiner_triple_system(n): elif n%6 == 1: - t = (n-1)/6 + t = (n-1) // 6 N = range(2*t) T = lambda (x,y) : x+y*t*2 if (x,y) != (-1,-1) else n-1 - L1 = lambda i,j : (i+j) % (int((n-1)/3)) - L = lambda i,j : L1(i,j)/2 if L1(i,j)%2 == 0 else t+(L1(i,j)-1)/2 + L1 = lambda i,j : (i+j) % ((n-1)//3) + L = lambda i,j : L1(i,j)//2 if L1(i,j)%2 == 0 else t+(L1(i,j)-1)//2 sts = [[(i,0),(i,1),(i,2)] for i in range(t)] + \ [[(-1,-1),(i,k),(i-t,(k+1) % 3)] for i in range(t,2*t) for k in [0,1,2]] + \ [[(i,k),(j,k),(L(i,j),(k+1) % 3)] for k in [0,1,2] for i in N for j in N if i < j] else: - raise ValueError("Steiner triple systems only exist for n = 1 mod 6 or n = 3 mod 6") + raise EmptySetError("Steiner triple systems only exist for n = 1 mod 6 or n = 3 mod 6") from sage.sets.set import Set sts = Set(map(lambda x: Set(map(T,x)),sts)) @@ -402,6 +446,73 @@ def BIBD_from_TD(v,k,existence=False): return BIBD +def BIBD_from_difference_family(G, D, check=True): + r""" + Return the BIBD associated to the difference family ``D`` on the group ``G``. + + Let `G` be a finite Abelian group. A *simple `(G,k)`-difference family* (or + a *`(G,k,1)`-difference family*) is a family `B = \{B_1,B_2,\ldots,B_b\}` of + `k`-subsets of `G` such that for each element of `G \backslash \{0\}` there + exists a unique `s \in \{1,\ldots,b\}` and a unique pair of distinct + elements `x,y \in B_s` such that `x - y = g`. + + If `\{B_1, B_2, \ldots, B_b\}` is a simple `(G,k)`-difference family then + its set of translates `\{B_i + g; i \in \{1,\ldots,b\}, g \in G\}` is a + `(v,k,1)`-BIBD where `v` is the cardinality of `G`. + + INPUT:: + + - ``G`` - a finite Abelian group + + - ``D`` - a difference family on ``G``. + + - ``check`` - whether or not we check the output (default: ``True``) + + EXAMPLES:: + + sage: G = Zmod(21) + sage: D = [[0,1,4,14,16]] + sage: print sorted(G(x-y) for x in D[0] for y in D[0] if x != y) + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] + + sage: from sage.combinat.designs.bibd import BIBD_from_difference_family + sage: BIBD_from_difference_family(G, D) + [[0, 1, 4, 14, 16], + [1, 2, 5, 15, 17], + [2, 3, 6, 16, 18], + [3, 4, 7, 17, 19], + [4, 5, 8, 18, 20], + [5, 6, 9, 19, 0], + [6, 7, 10, 20, 1], + [7, 8, 11, 0, 2], + [8, 9, 12, 1, 3], + [9, 10, 13, 2, 4], + [10, 11, 14, 3, 5], + [11, 12, 15, 4, 6], + [12, 13, 16, 5, 7], + [13, 14, 17, 6, 8], + [14, 15, 18, 7, 9], + [15, 16, 19, 8, 10], + [16, 17, 20, 9, 11], + [17, 18, 0, 10, 12], + [18, 19, 1, 11, 13], + [19, 20, 2, 12, 14], + [20, 0, 3, 13, 15]] + """ + r = {e:i for i,e in enumerate(G)} + bibd = [[r[G(x)+g] for x in d] for d in D for g in r] + if check: + assert _check_pbd(bibd, G.cardinality(), [len(D[0])]) + return bibd + + + + + +################ +# (v,4,1)-BIBD # +################ + def v_4_1_BIBD(v, check=True): r""" Returns a `(v,4,1)`-BIBD. @@ -438,7 +549,7 @@ def v_4_1_BIBD(v, check=True): if v == 0: return [] if v <= 12 or v%12 not in [1,4]: - raise ValueError("A K_4-decomposition of K_v exists iif v=2,4 mod 12, v>12 or v==0") + raise EmptySetError("A K_4-decomposition of K_v exists iif v=2,4 mod 12, v>12 or v==0") # Step 1. Base cases. if v == 13: @@ -547,7 +658,7 @@ def BIBD_from_PBD(PBD,v,k,check=True,base_cases={}): sage: PBD = PBD_4_5_8_9_12(17) sage: bibd = _check_pbd(BIBD_from_PBD(PBD,52,4),52,[4]) """ - r = (v-1)/(k-1) + r = (v-1) // (k-1) bibd = [] for X in PBD: n = len(X) @@ -805,3 +916,205 @@ def _get_t_u(v): t = 12*s+d['t'] u = d['u'] return t,u + +################ +# (v,5,1)-BIBD # +################ + + +def v_5_1_BIBD(v, check=True): + r""" + Returns a `(v,5,1)`-BIBD. + + This method follows the constuction from [ClaytonSmith]_. + + INPUT: + + - ``v`` (integer) + + .. SEEALSO:: + + * :meth:`BalancedIncompleteBlockDesign` + + EXAMPLES:: + + sage: from sage.combinat.designs.bibd import v_5_1_BIBD + sage: i = 0 + sage: while i<200: + ....: i += 20 + ....: _ = v_5_1_BIBD(i+1) + ....: _ = v_5_1_BIBD(i+5) + """ + v = int(v) + + assert (v > 1) + assert (v%20 == 5 or v%20 == 1) # note: equivalent to (v-1)%4 == 0 and (v*(v-1))%20 == 0 + + # Lemma 27 + if v%5 == 0 and (v//5)%4 == 1 and is_prime_power(v//5): + bibd = BIBD_5q_5_for_q_prime_power(v//5) + # Lemma 28 + elif v == 21: + from sage.rings.finite_rings.integer_mod_ring import Zmod + bibd = BIBD_from_difference_family(Zmod(21), [[0,1,4,14,16]], check=False) + elif v == 41: + from sage.rings.finite_rings.integer_mod_ring import Zmod + bibd = BIBD_from_difference_family(Zmod(41), [[0,1,4,11,29],[0,2,8,17,22]], check=False) + elif v == 61: + from sage.rings.finite_rings.integer_mod_ring import Zmod + bibd = BIBD_from_difference_family(Zmod(61), [[0,1,3,13,34],[0,4,9,23,45],[0,6,17,24,32]], check=False) + elif v == 81: + from sage.groups.additive_abelian.additive_abelian_group import AdditiveAbelianGroup + D = [[(0, 0, 0, 1), (2, 0, 0, 1), (0, 0, 2, 1), (1, 2, 0, 2), (0, 1, 1, 1)], + [(0, 0, 1, 0), (1, 1, 0, 2), (0, 2, 1, 0), (1, 2, 0, 1), (1, 1, 1, 0)], + [(2, 2, 1, 1), (1, 2, 2, 2), (2, 0, 1, 2), (0, 1, 2, 1), (1, 1, 0, 0)], + [(0, 2, 0, 2), (1, 1, 0, 1), (1, 2, 1, 2), (1, 2, 1, 0), (0, 2, 1, 1)]] + bibd = BIBD_from_difference_family(AdditiveAbelianGroup([3]*4), D, check=False) + elif v == 161: + # VI.16.16 of the Handbook of Combinatorial Designs, Second Edition + D = [(0, 19, 34, 73, 80), (0, 16, 44, 71, 79), (0, 12, 33, 74, 78), (0, 13, 30, 72, 77), (0, 11, 36, 67, 76), (0, 18, 32, 69, 75), (0, 10, 48, 68, 70), (0, 3, 29, 52, 53)] + from sage.rings.finite_rings.integer_mod_ring import Zmod + bibd = BIBD_from_difference_family(Zmod(161), D, check=False) + elif v == 281: + from sage.rings.finite_rings.integer_mod_ring import Zmod + D = [[3**(2*a+56*b) for b in range(5)] for a in range(14)] + bibd = BIBD_from_difference_family(Zmod(281), D, check=False) + # Lemma 29 + elif v == 165: + bibd = BIBD_from_PBD(v_5_1_BIBD(41,check=False),165,5,check=False) + elif v == 181: + bibd = BIBD_from_PBD(v_5_1_BIBD(45,check=False),181,5,check=False) + elif v in (201,285,301,401,421,425): + # Call directly the BIBD_from_TD function + bibd = BIBD_from_TD(v,5) + # Lemma 30 + elif v == 141: + # VI.16.16 of the Handbook of Combinatorial Designs, Second Edition + from sage.rings.finite_rings.integer_mod_ring import Zmod + D = [(0, 33, 60, 92, 97), (0, 3, 45, 88, 110), (0, 18, 39, 68, 139), (0, 12, 67, 75, 113), (0, 1, 15, 84, 94), (0, 7, 11, 24, 30), (0, 36, 90, 116, 125)] + bibd = BIBD_from_difference_family(Zmod(141), D, check=False) + + # Theorem 31.2 + elif (v-1)//4 in [80, 81, 85, 86, 90, 91, 95, 96, 110, 111, 115, 116, 120, 121, 250, 251, 255, 256, 260, 261, 265, 266, 270, 271]: + r = (v-1)//4 + if r <= 96: + k,t,u = 5, 16, r-80 + elif r <= 121: + k,t,u = 10, 11, r-110 + else: + k,t,u = 10, 25, r-250 + bibd = BIBD_from_PBD(PBD_from_TD(k,t,u),v,5,check=False) + + else: + r,s,t,u = _get_r_s_t_u(v) + bibd = BIBD_from_PBD(PBD_from_TD(5,t,u),v,5,check=False) + + if check: + _check_pbd(bibd,v,[5]) + + return bibd + +def _get_r_s_t_u(v): + r""" + Implements the table from [ClaytonSmith]_ + + Returns the parameters ``r,s,t,u`` associated with an integer ``v``. + + INPUT: + + - ``v`` (integer) + + EXAMPLES:: + + sage: from sage.combinat.designs.bibd import _get_r_s_t_u + sage: _get_r_s_t_u(25) + (6, 0, 1, 1) + """ + r = int((v-1)/4) + s = r//150 + x = r%150 + + if x == 0: t,u = 30*s-5, 25 + elif x == 1: t,u = 30*s-5, 26 + elif x <= 21: t,u = 30*s+1, x-5 + elif x == 25: t,u = 30*s+5, 0 + elif x == 26: t,u = 30*s+5, 1 + elif x == 30: t,u = 30*s+5, 5 + elif x <= 51: t,u = 30*s+5, x-25 + elif x <= 66: t,u = 30*s+11, x-55 + elif x <= 96: t,u = 30*s+11, x-55 + elif x <= 121: t,u = 30*s+11, x-55 + elif x <= 146: t,u = 30*s+25, x-125 + + return r,s,t,u + +def PBD_from_TD(k,t,u): + r""" + Returns a `(kt,\{k,t\})`-PBD if `u=0` and a `(kt+u,\{k,k+1,t,u\})`-PBD otherwise. + + This is theorem 23 from [ClaytonSmith]_. The PBD is obtained from the blocks + a truncated `TD(k+1,t)`, to which are added the blocks corresponding to the + groups of the TD. When `u=0`, a `TD(k,t)` is used instead. + + INPUT: + + - ``k,t,u`` -- integers such that `0\leq u \leq t`. + + EXAMPLES:: + + sage: from sage.combinat.designs.bibd import PBD_from_TD + sage: from sage.combinat.designs.bibd import _check_pbd + sage: PBD = PBD_from_TD(2,2,1); PBD + [[0, 2, 4], [0, 3], [1, 2], [1, 3, 4], [0, 1], [2, 3]] + sage: _check_pbd(PBD,2*2+1,[2,3]) + [[0, 2, 4], [0, 3], [1, 2], [1, 3, 4], [0, 1], [2, 3]] + + """ + from orthogonal_arrays import transversal_design + TD = transversal_design(k+bool(u),t, check=False) + TD = [[x for x in X if x=2: + TD.append(range(k*t,k*t+u)) + return TD + +def BIBD_5q_5_for_q_prime_power(q): + r""" + Returns a `(5q,5,1)`-BIBD with `q\equiv 1\pmod 4` a prime power. + + See Theorem 24 [ClaytonSmith]_. + + INPUT: + + - ``q`` (integer) -- a prime power such that `q\equiv 1\pmod 4`. + + EXAMPLES:: + + sage: from sage.combinat.designs.bibd import BIBD_5q_5_for_q_prime_power + sage: for q in [25, 45, 65, 85, 125, 145, 185, 205, 305, 405, 605]: # long time + ....: _ = BIBD_5q_5_for_q_prime_power(q/5) # long time + """ + from sage.rings.arith import is_prime_power + from sage.rings.finite_rings.constructor import FiniteField + + if q%4 != 1 or not is_prime_power(q): + raise ValueError("q is not a prime power or q%4!=1.") + + d = (q-1)/4 + B = [] + F = FiniteField(q,'x') + a = F.primitive_element() + L = {b:i for i,b in enumerate(F)} + for b in L: + B.append([i*q + L[b] for i in range(5)]) + for i in range(5): + for j in range(d): + B.append([ i*q + L[b ], + ((i+1)%5)*q + L[ a**j+b ], + ((i+1)%5)*q + L[-a**j+b ], + ((i+4)%5)*q + L[ a**(j+d)+b], + ((i+4)%5)*q + L[-a**(j+d)+b], + ]) + + return B diff --git a/src/sage/combinat/designs/incidence_structures.py b/src/sage/combinat/designs/incidence_structures.py index e42a0382b89..d9a99df92a9 100644 --- a/src/sage/combinat/designs/incidence_structures.py +++ b/src/sage/combinat/designs/incidence_structures.py @@ -217,31 +217,31 @@ def automorphism_group(self): EXAMPLES:: - sage: from sage.combinat.designs.block_design import BlockDesign - sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) - sage: G = BD.automorphism_group(); G - Permutation Group with generators [(4,5)(6,7), (4,6)(5,7), (2,3)(6,7), (2,4)(3,5), (1,2)(5,6)] - sage: BD = BlockDesign(4,[[0],[0,1],[1,2],[3,3]],test=False) - sage: G = BD.automorphism_group(); G - Permutation Group with generators [()] - sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) - sage: G = BD.automorphism_group(); G - Permutation Group with generators [(4,5)(6,7), (4,6)(5,7), (2,3)(6,7), (2,4)(3,5), (1,2)(5,6)] + sage: P = designs.DesarguesianProjectivePlaneDesign(2); P + Incidence structure with 7 points and 7 blocks + sage: G = P.automorphism_group() + sage: G.is_isomorphic(PGL(3,2)) + True + sage: G + Permutation Group with generators [(2,3)(4,5), (2,4)(3,5), (1,2)(4,6), (0,1)(4,5)] + + A non self-dual example:: + + sage: from sage.combinat.designs.incidence_structures import IncidenceStructure + sage: IS = IncidenceStructure(range(4), [[0,1,2,3],[1,2,3]]) + sage: IS.automorphism_group().cardinality() + 6 + sage: IS.dual_design().automorphism_group().cardinality() + 1 """ from sage.groups.perm_gps.partn_ref.refinement_matrices import MatrixStruct from sage.groups.perm_gps.permgroup import PermutationGroup from sage.groups.perm_gps.permgroup_named import SymmetricGroup - M1 = self.incidence_matrix() + M1 = self.incidence_matrix().transpose() M2 = MatrixStruct(M1) M2.run() gens = M2.automorphism_group()[0] - v = len(self.points()) - G = SymmetricGroup(v) - gns = [] - for g in gens: - L = [j+1 for j in g] - gns.append(G(L)) - return PermutationGroup(gns) + return PermutationGroup(gens, domain=range(self.v)) def block_design_checker(self, t, v, k, lmbda, type=None): """ @@ -517,12 +517,17 @@ def incidence_graph(self): A = self.incidence_matrix() return BipartiteGraph(A) - def is_block_design(self): + def is_block_design(self, verbose=False): """ Returns a pair ``True, pars`` if the incidence structure is a `t`-design, for some `t`, where ``pars`` is the list of parameters `(t, v, k, lmbda)`. The largest possible `t` is returned, provided `t=10`. + INPUT: + + - ``verbose`` (boolean) -- prints useful information when the answer is + negative. + EXAMPLES:: sage: BD = IncidenceStructure(range(7),[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) @@ -552,19 +557,22 @@ def is_block_design(self): r_list = [0]*v for block in self.blcks: if len(block) != k: + if verbose: + print "All blocks do not have the same size" return False for x in block: r_list[x] += 1 r = r_list[0] if any(x!=r for x in r_list): + if verbose: + print "All points do not have the same degree" return False # Definition and consistency of 'l' (lambda) and 't' t_found_yet = False for t in range(2,min(v,k+1)): - # Is lambda an integer ? if (b*binomial(k,t)) % binomial(v,t) == 0: l = (b*binomial(k,t))/binomial(v,t) diff --git a/src/sage/combinat/designs/steiner_quadruple_systems.py b/src/sage/combinat/designs/steiner_quadruple_systems.py index bf324f6b6d6..070a9179cd3 100644 --- a/src/sage/combinat/designs/steiner_quadruple_systems.py +++ b/src/sage/combinat/designs/steiner_quadruple_systems.py @@ -219,7 +219,7 @@ def three_n_minus_eight(n, B): # Line 4. - k = (n-14)/12 + k = (n-14) // 12 for i in xrange(3): for b in xrange(n-4): for bb in xrange(n-4): @@ -281,7 +281,7 @@ def three_n_minus_four(n, B): Y.append((r(0,aa),r(1,aaa), r(2,aaaa),3*(n-2)+a)) # Line 4. - k = (n-10)/12 + k = (n-10) // 12 for i in xrange(3): for b in xrange(n-2): for bb in xrange(n-2): @@ -338,7 +338,7 @@ def four_n_minus_six(n, B): Y.append(tuple(r(i,ii,x) if x<= n-3 else x+3*(n-2) for x in s )) # Line 2/3/4/5 - k = f/2 + k = f // 2 for l in xrange(2): for eps in xrange(2): for c in xrange(k): @@ -407,7 +407,7 @@ def twelve_n_minus_ten(n, B): B14 = steiner_quadruple_system(14) r = lambda i,x : i%(n-1)+(x%12)*(n-1) - k = n/2 + k = n // 2 # Line 1. Y = [] @@ -543,10 +543,10 @@ def P(alpha, m): if m%2==0: if alpha < m: if alpha%2 == 0: - b = alpha/2 + b = alpha // 2 return [(2*a, (2*a + 2*b + 1)%(2*m)) for a in xrange(m)] else: - b = (alpha-1)/2 + b = (alpha-1) // 2 return [(2*a, (2*a - 2*b - 1)%(2*m)) for a in xrange(m)] else: y = alpha - m @@ -557,10 +557,10 @@ def P(alpha, m): else: if alpha < m-1: if alpha % 2 == 0: - b = alpha/2 + b = alpha // 2 return [(2*a,(2*a+2*b+1)%(2*m)) for a in xrange(m)] else: - b = (alpha-1)/2 + b = (alpha-1) // 2 return [(2*a,(2*a-2*b-1)%(2*m)) for a in xrange(m)] else: y = alpha-m+1 @@ -628,7 +628,7 @@ def barP_system(m): # The first (shorter) collections of pairs, obtained from P by removing # pairs. Those are added to 'last', a new list of pairs last = [] - for n in xrange(1,(m-2)/2+1): + for n in xrange(1, (m-2)//2+1): pairs.append([p for p in P(2*n,m) if not isequal(p,(2*n,(4*n+1)%(2*m)))]) last.append((2*n,(4*n+1)%(2*m))) pairs.append([p for p in P(2*n-1,m) if not isequal(p,(2*m-2-2*n,2*m-1-4*n))]) @@ -655,7 +655,7 @@ def barP_system(m): # Now the points must be relabeled relabel = {} - for n in xrange(1,(m-2)/2+1): + for n in xrange(1, (m-2)//2+1): relabel[2*n] = (4*n)%(2*m) relabel[4*n+1] = (4*n+1)%(2*m) relabel[2*m-2-2*n] = (4*n-2)%(2*m) @@ -671,7 +671,7 @@ def barP_system(m): # pairs. Those are added to 'last', a new list of pairs last = [] - for n in xrange(0,(m-3)/2+1): + for n in xrange(0, (m-3)//2+1): pairs.append([p for p in P(2*n,m) if not isequal(p,(2*n,(4*n+1)%(2*m)))]) last.append((2*n,(4*n+1)%(2*m))) pairs.append([p for p in P(2*n+1,m) if not isequal(p,(2*m-2-2*n,2*m-3-4*n))]) @@ -693,7 +693,7 @@ def barP_system(m): # Now the points must be relabeled relabel = {} - for n in xrange(0,(m-3)/2+1): + for n in xrange(0, (m-3)//2+1): relabel[2*n] = (4*n)%(2*m) relabel[4*n+1] = (4*n+1)%(2*m) relabel[2*m-2-2*n] = (4*n+2)%(2*m) @@ -751,22 +751,22 @@ def steiner_quadruple_system(n, check = False): elif n == 38: return _SQS38() elif (n%12) in [4,8]: - nn = n/2 + nn = n // 2 sqs = two_n(nn,steiner_quadruple_system(nn, check = False)) elif (n%18) in [4,10]: - nn = (n+2)/3 + nn = (n+2) // 3 sqs = three_n_minus_two(nn,steiner_quadruple_system(nn, check = False)) elif (n%36) == 34: - nn = (n+8)/3 + nn = (n+8) // 3 sqs = three_n_minus_eight(nn,steiner_quadruple_system(nn, check = False)) elif (n%36) == 26 : - nn = (n+4)/3 + nn = (n+4) // 3 sqs = three_n_minus_four(nn,steiner_quadruple_system(nn, check = False)) elif (n%24) in [2,10]: - nn = (n+6)/4 + nn = (n+6) // 4 sqs = four_n_minus_six(nn,steiner_quadruple_system(nn, check = False)) elif (n%72) in [14,38]: - nn = (n+10)/12 + nn = (n+10) // 12 sqs = twelve_n_minus_ten(nn, steiner_quadruple_system(nn, check = False)) else: raise ValueError("This shouldn't happen !") diff --git a/src/sage/combinat/dyck_word.py b/src/sage/combinat/dyck_word.py index 4cbf4454e0b..c8940d01918 100644 --- a/src/sage/combinat/dyck_word.py +++ b/src/sage/combinat/dyck_word.py @@ -3,7 +3,7 @@ A class of an object enumerated by the :func:`Catalan numbers`, -see [Sta1999]_, [Sta]_ for details. +see [Sta-EC2]_, [StaCat98]_ for details. AUTHORS: @@ -27,14 +27,18 @@ REFERENCES: -.. [Sta1999] R. Stanley, Enumerative Combinatorics, Volume 2. +.. [Sta-EC2] Richard P. Stanley. + *Enumerative Combinatorics*, Volume 2. Cambridge University Press, 2001. -.. [Sta] R. Stanley, electronic document containing the list - of Catalan objects at http://www-math.mit.edu/~rstan/ec/catalan.pdf +.. [StaCat98] Richard Stanley. *Exercises on Catalan and Related Numbers + excerpted from Enumerative Combinatorics, vol. 2 (CUP 1999)*, + version of 23 June 1998. + http://www-math.mit.edu/~rstan/ec/catalan.pdf -.. [Hag2008] The `q,t` -- Catalan Numbers and the Space of Diagonal Harmonics: - With an Appendix on the Combinatorics of Macdonald Polynomials, James Haglund, +.. [Hag2008] James Haglund. *The* `q,t` -- *Catalan Numbers and the + Space of Diagonal Harmonics: + With an Appendix on the Combinatorics of Macdonald Polynomials*. University of Pennsylvania, Philadelphia -- AMS, 2008, 167 pp. """ @@ -658,9 +662,10 @@ def pretty_print(self, type=None, labelling=None, underpath=True): Display a DyckWord as a lattice path in the `\ZZ^2` grid. If the ``type`` is "N-E", then the a cell below the diagonal is - indicated by a period, a cell below the path, but above the - diagonal are indicated by an x. If a list of labels is included, - they are displayed along the vertical edges of the Dyck path. + indicated by a period, whereas a cell below the path but above + the diagonal is indicated by an x. If a list of labels is + included, they are displayed along the vertical edges of the + Dyck path. If the ``type`` is "NE-SE", then the path is simply printed as up steps and down steps. @@ -678,7 +683,7 @@ def pretty_print(self, type=None, labelling=None, underpath=True): the up steps in ``self``. - ``underpath`` -- (if type is "N-E", default:``True``) If ``True``, - the labelling is shown under the path otherwise, it is shown to + the labelling is shown under the path; otherwise, it is shown to the right of the path. EXAMPLES:: @@ -1095,7 +1100,9 @@ def min_from_heights(cls, heights): def associated_parenthesis(self, pos): r""" Report the position for the parenthesis in ``self`` that matches the - one at position ``pos`` . + one at position ``pos``. + + The positions in ``self`` are counted from `0`. INPUT: @@ -1138,7 +1145,7 @@ def associated_parenthesis(self, pos): d -= 1 height -= 1 else: - raise ValueError("unknown symbol %s" % self[pos - 1]) + raise ValueError("unknown symbol %s" % self[pos]) while height != 0: pos += d @@ -1495,7 +1502,8 @@ def to_standard_tableau(self): def to_binary_tree(self, usemap="1L0R"): r""" Return a binary tree recursively constructed from the Dyck path - by the sent ``usemap``. The default usemap is ``1L0R`` which means: + ``self`` by the map ``usemap``. The default ``usemap`` is ``'1L0R'`` + which means: - an empty Dyck word is a leaf, @@ -1504,11 +1512,13 @@ def to_binary_tree(self, usemap="1L0R"): INPUT: - - ``usemap`` -- a string, either ``1L0R``, ``1R0L``, ``L1R0``, - ``R1L0``. + - ``usemap`` -- a string, either ``'1L0R'``, ``'1R0L'``, ``'L1R0'``, + ``'R1L0'`` - Other valid ``usemap`` are ``1R0L``, ``L1R0``, and ``R1L0`` and all - corresponding to different recursive definitions of Dyck paths. + Other valid ``usemap`` are ``'1R0L'``, ``'L1R0'``, and ``'R1L0'``. + These correspond to different maps from Dyck paths to binary + trees, whose recursive definitions are hopefully clear from the + names. EXAMPLES:: @@ -1564,8 +1574,11 @@ def to_binary_tree(self, usemap="1L0R"): @combinatorial_map(name="to the Tamari corresponding Binary tree") def to_binary_tree_tamari(self): r""" - Return the binary tree with consistency with the Tamari order - of ``self``. + Return the binary tree corresponding to ``self`` in a way which + is consistent with the Tamari orders on the set of Dyck paths and + on the set of binary trees. + + This is the ``'L1R0'`` map documented in :meth:`to_binary_tree`. EXAMPLES:: @@ -1578,6 +1591,51 @@ def to_binary_tree_tamari(self): """ return self.to_binary_tree("L1R0") + def tamari_interval(self, other): + r""" + Return the Tamari interval between ``self`` and ``other`` as a + :class:`~sage.combinat.interval_posets.TamariIntervalPoset`. + + A "Tamari interval" means an interval in the Tamari order. The + Tamari order on the set of Dyck words of size `n` is the + partial order obtained from the Tamari order on the set of + binary trees of size `n` (see + :meth:`~sage.combinat.binary_tree.BinaryTree.tamari_lequal`) + by means of the Tamari bijection between Dyck words and binary + trees + (:meth:`~sage.combinat.binary_tree.BinaryTree.to_dyck_word_tamari`). + + INPUT: + + - ``other`` -- a Dyck word greater or equal to ``self`` in the + Tamari order + + EXAMPLES:: + + sage: dw = DyckWord([1, 1, 0, 1, 0, 0, 1, 0]) + sage: ip = dw.tamari_interval(DyckWord([1, 1, 1, 0, 0, 1, 0, 0])); ip + The tamari interval of size 4 induced by relations [(2, 4), (3, 4), (3, 1), (2, 1)] + sage: ip.lower_dyck_word() + [1, 1, 0, 1, 0, 0, 1, 0] + sage: ip.upper_dyck_word() + [1, 1, 1, 0, 0, 1, 0, 0] + sage: ip.interval_cardinality() + 4 + sage: ip.number_of_tamari_inversions() + 2 + sage: list(ip.dyck_words()) + [[1, 1, 1, 0, 0, 1, 0, 0], + [1, 1, 1, 0, 0, 0, 1, 0], + [1, 1, 0, 1, 0, 1, 0, 0], + [1, 1, 0, 1, 0, 0, 1, 0]] + sage: dw.tamari_interval(DyckWord([1,1,0,0,1,1,0,0])) + Traceback (most recent call last): + ... + ValueError: The two Dyck words are not comparable on the Tamari lattice. + """ + from sage.combinat.interval_posets import TamariIntervalPosets + return TamariIntervalPosets.from_dyck_words(self, other) + def to_area_sequence(self): r""" Return the area sequence of the Dyck word ``self``. @@ -2188,15 +2246,24 @@ def to_noncrossing_partition(self, bijection=None): def to_Catalan_code(self): r""" - Return the Catalan code associated to ``self`` . The Catalan code is a - sequence of non--negative integers `a_i` such that if `i < j` and - `a_i > 0` and `a_j > 0` and `a_{i+1} = a_{i+2} = \cdots = a_{j-1} = 0` - then `a_i - a_j < j-i`. + Return the Catalan code associated to ``self``. + + A Catalan code of length `n` is a sequence + `(a_1, a_2, \ldots, a_n)` of `n` integers `a_i` such that: + + - `0 \leq a_i \leq n-i` for every `i`; + + - if `i < j` and `a_i > 0` and `a_j > 0` and + `a_{i+1} = a_{i+2} = \cdots = a_{j-1} = 0`, + then `a_i - a_j < j-i`. + + It turns out that the Catalan codes of length `n` are in + bijection with Dyck words. The Catalan code of a Dyck word is example (x) in Richard Stanley's exercises on combinatorial interpretations for Catalan objects. The code in this example is the reverse of the description provided - there. See [Sta1999]_ and [Sta]_. + there. See [Sta-EC2]_ and [StaCat98]_. EXAMPLES:: @@ -3483,14 +3550,22 @@ def from_Catalan_code(self, code): Return the Dyck word associated to the given Catalan code ``code``. - A Catalan code is a sequence of non--negative integers `a_i` such - that if `i < j` and `a_i > 0` and `a_j > 0` and `a_{i+1} = a_{i+2} - = \cdots = a_{j-1} = 0` then `a_i - a_j < j-i`. + A Catalan code of length `n` is a sequence + `(a_1, a_2, \ldots, a_n)` of `n` integers `a_i` such that: + + - `0 \leq a_i \leq n-i` for every `i`; + + - if `i < j` and `a_i > 0` and `a_j > 0` and + `a_{i+1} = a_{i+2} = \cdots = a_{j-1} = 0`, + then `a_i - a_j < j-i`. + + It turns out that the Catalan codes of length `n` are in + bijection with Dyck words. The Catalan code of a Dyck word is example (x) in Richard Stanley's exercises on combinatorial interpretations for Catalan objects. The code in this example is the reverse of the description provided - there. See [Sta1999]_ and [Sta]_. + there. See [Sta-EC2]_ and [StaCat98]_. EXAMPLES:: diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 30b80ea6661..fcbb38b2d31 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -2394,7 +2394,15 @@ def __deepcopy__(self, memo): relabel_iter = itertools.count(0) for state in self.iter_states(): if relabel: - state._deepcopy_relabel_ = relabel_iter.next() + if self._deepcopy_labels_ is None: + state._deepcopy_relabel_ = relabel_iter.next() + elif hasattr(self._deepcopy_labels_, '__call__'): + state._deepcopy_relabel_ = self._deepcopy_labels_(state.label()) + elif hasattr(self._deepcopy_labels_, '__getitem__'): + state._deepcopy_relabel_ = self._deepcopy_labels_[state.label()] + else: + raise TypeError("labels must be None, a callable " + "or a dictionary.") s = deepcopy(state, memo) if relabel: del state._deepcopy_relabel_ @@ -2426,16 +2434,20 @@ def deepcopy(self, memo=None): return deepcopy(self, memo) - def relabeled(self, memo=None): + def relabeled(self, memo=None, labels=None): """ Returns a deep copy of the finite state machine, but the - states are relabeled by integers starting with 0. + states are relabeled. INPUT: - ``memo`` -- (default: ``None``) a dictionary storing already processed elements. + - ``labels`` -- (default: ``None``) a dictionary or callable + mapping old labels to new labels. If ``None``, then the new + labels are integers starting with 0. + OUTPUT: A new finite state machine. @@ -2448,10 +2460,25 @@ def relabeled(self, memo=None): sage: FSM2 = FSM1.relabeled() sage: FSM2.states() [0, 1, 2] + sage: FSM3 = FSM1.relabeled(labels={'A': 'a', 'B': 'b', 'C': 'c'}) + sage: FSM3.states() + ['a', 'b', 'c'] + sage: FSM4 = FSM2.relabeled(labels=lambda x: 2*x) + sage: FSM4.states() + [0, 2, 4] + + TESTS:: + + sage: FSM2.relabeled(labels=1) + Traceback (most recent call last): + ... + TypeError: labels must be None, a callable or a dictionary. """ self._deepcopy_relabel_ = True + self._deepcopy_labels_ = labels new = deepcopy(self, memo) del self._deepcopy_relabel_ + del self._deepcopy_labels_ return new diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index 925dcb40fa3..a784bdf90a9 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -2814,8 +2814,9 @@ def __init__(self, modules, **options): sage: F = CombinatorialFreeModule(ZZ, [2,4,5]) sage: G = CombinatorialFreeModule(ZZ, [2,4,7]) - sage: cartesian_product([F, G]) + sage: C = cartesian_product([F, G]) ; C Free module generated by {2, 4, 5} over Integer Ring (+) Free module generated by {2, 4, 7} over Integer Ring + sage: TestSuite(C).run() """ assert(len(modules) > 0) # TODO: generalize to a family or tuple R = modules[0].base_ring() @@ -2937,6 +2938,21 @@ def _cartesian_product_of_elements(self, elements): """ return self.sum(self.summand_embedding(i)(elements[i]) for i in self._sets_keys()) + def cartesian_factors(self): + """ + Return the factors of the cartesian product. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.__custom_name = "F" + sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.__custom_name = "G" + sage: S = cartesian_product([F, G]) + sage: S.cartesian_factors() + (F, G) + + """ + return self._sets + class Element(CombinatorialFreeModule.Element): # TODO: get rid of this inheritance pass diff --git a/src/sage/combinat/integer_vector_weighted.py b/src/sage/combinat/integer_vector_weighted.py index 04bb584a819..e9c5b56cf4f 100644 --- a/src/sage/combinat/integer_vector_weighted.py +++ b/src/sage/combinat/integer_vector_weighted.py @@ -256,13 +256,13 @@ def _recfun(self, n, l): w = l[-1] l = l[:-1] if l == []: - d = int(n) / int(w) + d = int(n) // int(w) if n % w == 0: yield [d] # Otherwise: bad branch return - for d in range(int(n)/int(w), -1, -1): + for d in range(int(n)//int(w), -1, -1): for x in self._recfun(n-d*w, l): yield x + [d] diff --git a/src/sage/combinat/interval_posets.py b/src/sage/combinat/interval_posets.py new file mode 100644 index 00000000000..125a35e26ef --- /dev/null +++ b/src/sage/combinat/interval_posets.py @@ -0,0 +1,2737 @@ +r""" +Tamari Interval-posets + +This module implements Tamari interval-posets: combinatorial objects which +represent intervals of the Tamari order. They have been introduced in +[PCh2013]_ and allow for many combinatorial operations on Tamari intervals. +In particular, they are linked to :class:`DyckWords` and :class:`BinaryTrees`. +An introduction into Tamari interval-posets is given in Chapter 7 +of [Pons2013]_. + +The Tamari lattice can be defined as a lattice structure on either of several +classes of Catalan objects, especially binary trees and Dyck paths +[TamBrack1962]_ [HuangTamari1972]_ [Sta-EC2]_. An interval can be seen as +a pair of comparable elements. The number of intervals has been given in +[ChapTamari08]_. + +REFERENCES: + +.. [PCh2013] Gregory Chatel and Viviane Pons. + *Counting smaller trees in the Tamari order*. + FPSAC. (2013). :arxiv:`1212.0751v1`. + +.. [Pons2013] Viviane Pons, + *Combinatoire algebrique liee aux ordres sur les permutations*. + PhD Thesis. (2013). :arxiv:`1310.1805v1`. + +.. [TamBrack1962] Dov Tamari. + *The algebra of bracketings and their enumeration*. + Nieuw Arch. Wisk. (1962). + +.. [HuangTamari1972] Samuel Huang and Dov Tamari. + *Problems of associativity: A simple proof for the lattice property + of systems ordered by a semi-associative law*. + J. Combinatorial Theory Ser. A. (1972). + http://www.sciencedirect.com/science/article/pii/0097316572900039 . + +.. [ChapTamari08] Frederic Chapoton. + *Sur le nombre d'intervalles dans les treillis de Tamari*. + Sem. Lothar. Combin. (2008). + :arxiv:`math/0602368v1`. + +AUTHORS: + +- Viviane Pons 2014: initial implementation +- Frederic Chapoton 2014: review +- Darij Grinberg 2014: review +- Travis Scrimshaw 2014: review +""" +#***************************************************************************** +# Copyright (C) 2013 Viviane Pons , +# +# 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.enumerated_sets import EnumeratedSets +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.categories.posets import Posets +from sage.combinat.posets.posets import Poset, FinitePoset +from sage.categories.finite_posets import FinitePosets +from sage.combinat.binary_tree import BinaryTrees +from sage.combinat.binary_tree import LabelledBinaryTrees +from sage.combinat.dyck_word import DyckWords +from sage.combinat.permutation import Permutation +from sage.misc.classcall_metaclass import ClasscallMetaclass +from sage.misc.cachefunc import cached_method +from sage.misc.latex import latex +from sage.misc.lazy_attribute import lazy_attribute +from sage.rings.integer import Integer +from sage.rings.all import NN +from sage.sets.non_negative_integers import NonNegativeIntegers +from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets +from sage.sets.family import Family +from sage.structure.element import Element +from sage.structure.global_options import GlobalOptions +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation + +TamariIntervalPosetOptions = GlobalOptions(name="Tamari Interval-posets", + doc=r""" + Set and display the global options for Tamari interval-posets. If no + parameters are set, then the function returns a copy of the options + dictionary. + + The ``options`` to Tamari interval-posets can be accessed as the method + :obj:`TamariIntervalPosets.global_options` of :class:`TamariIntervalPosets` + and related parent classes. + """, + end_doc=r""" + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(2,4),(3,4),(2,1),(3,1)]) + sage: ip.latex_options()["color_decreasing"] + 'red' + sage: TamariIntervalPosets.global_options(latex_color_decreasing='green') + sage: ip.latex_options()["color_decreasing"] + 'green' + sage: TamariIntervalPosets.global_options.reset() + sage: ip.latex_options()["color_decreasing"] + 'red' + """, + latex_tikz_scale=dict(default=1, + description='the default value for the tikz scale when latexed', + checker=lambda x: True), # More trouble than it's worth to check + latex_line_width_scalar=dict(default=0.5, + description='the default value for the line width as a' + 'multiple of the tikz scale when latexed', + checker=lambda x: True), # More trouble than it's worth to check + latex_color_decreasing=dict(default="red", + description='the default color of decreasing relations when latexed', + checker=lambda x: True), # More trouble than it's worth to check + latex_color_increasing=dict(default="blue", + description='the default color of increasing relations when latexed', + checker=lambda x: True), # More trouble than it's worth to check + latex_hspace=dict(default=1, + description='the default difference between horizontal' + ' coordinates of vertices when latexed', + checker=lambda x: True), # More trouble than it's worth to check + latex_vspace=dict(default=1, + description='the default difference between vertical' + ' coordinates of vertices when latexed', + checker=lambda x: True) # More trouble than it's worth to check +) + + +class TamariIntervalPoset(Element): + r""" + The class of Tamari interval-posets. + + An interval-poset is a labelled poset of size `n`, with labels + `1, 2, \ldots, n`, satisfying the following conditions: + + - if `a < c` (as integers) and `a` precedes `c` in the poset, then, + for all `b` such that `a < b < c`, `b` precedes `c`, + + - if `a < c` (as integers) and `c` precedes `a` in the poset, then, + for all `b` such that `a < b < c`, `b` precedes `a`. + + We use the word "precedes" here to distinguish the poset order and + the natural order on numbers. "Precedes" means "is smaller than + with respect to the poset structure"; this does not imply a + covering relation. + + Interval-posets of size `n` are in bijection with intervals of + the Tamari lattice of binary trees of size `n`. Specifically, if + `P` is an interval-poset of size `n`, then the set of linear + extensions of `P` (as permutations in `S_n`) is an interval in the + right weak order (see + :meth:`~sage.combinat.permutation.Permutation.permutohedron_lequal`), + and is in fact the preimage of an interval in the Tamari lattice (of + binary trees of size `n`) under the operation which sends a + permutation to its right-to-left binary search tree + (:meth:`~sage.combinat.permutation.Permutation.binary_search_tree` + with the ``left_to_right`` variable set to ``False``) + without its labelling. + + INPUT: + + - ``size`` -- an integer, the size of the interval-posets (number of + vertices) + + - ``relations`` -- a list (or tuple) of pairs ``(a,b)`` (themselves + lists or tuples), each representing a relation of the form + '`a` precedes `b`' in the poset. + + - ``check`` -- (default: ``True``) whether to check the interval-poset + condition or not. + + .. WARNING:: + + The ``relations`` input can be a list or tuple, but not an + iterator (nor should its entries be iterators). + + NOTATION: + + Here and in the following, the signs `<` and `>` always refer to + the natural ordering on integers, whereas the word "precedes" refers + to the order of the interval-poset. "Minimal" and "maximal" refer + to the natural ordering on integers. + + The *increasing relations* of an interval-poset `P` mean the pairs + `(a, b)` of elements of `P` such that `a < b` as integers and `a` + precedes `b` in `P`. The *initial forest* of `P` is the poset + obtained by imposing (only) the increasing relations on the ground + set of `P`. It is a sub-interval poset of `P`, and is a forest with + its roots on top. This forest is usually given the structure of a + planar forest by ordering brother nodes by their labels; it then has + the property that if its nodes are traversed in post-order + (see :meth:~sage.combinat.abstract_tree.AbstractTree.post_order_traversal`, + and traverse the trees of the forest from left to right as well), + then the labels encountered are `1, 2, \ldots, n` in this order. + + The *decreasing relations* of an interval-poset `P` mean the pairs + `(a, b)` of elements of `P` such that `b < a` as integers and `a` + precedes `b` in `P`. The *final forest* of `P` is the poset + obtained by imposing (only) the decreasing relations on the ground + set of `P`. It is a sub-interval poset of `P`, and is a forest with + its roots on top. This forest is usually given the structure of a + planar forest by ordering brother nodes by their labels; it then has + the property that if its nodes are traversed in pre-order + (see :meth:`~sage.combinat.abstract_tree.AbstractTree.pre_order_traversal`, + and traverse the trees of the forest from left to right as well), + then the labels encountered are `1, 2, \ldots, n` in this order. + + EXAMPLES:: + + sage: TamariIntervalPoset(0,[]) + The tamari interval of size 0 induced by relations [] + sage: TamariIntervalPoset(3,[]) + The tamari interval of size 3 induced by relations [] + sage: TamariIntervalPoset(3,[(1,2)]) + The tamari interval of size 3 induced by relations [(1, 2)] + sage: TamariIntervalPoset(3,[(1,2),(2,3)]) + The tamari interval of size 3 induced by relations [(1, 2), (2, 3)] + sage: TamariIntervalPoset(3,[(1,2),(2,3),(1,3)]) + The tamari interval of size 3 induced by relations [(1, 2), (2, 3)] + sage: TamariIntervalPoset(3,[(1,2),(3,2)]) + The tamari interval of size 3 induced by relations [(1, 2), (3, 2)] + sage: TamariIntervalPoset(3,[[1,2],[2,3]]) + The tamari interval of size 3 induced by relations [(1, 2), (2, 3)] + sage: TamariIntervalPoset(3,[[1,2],[2,3],[1,2],[1,3]]) + The tamari interval of size 3 induced by relations [(1, 2), (2, 3)] + + sage: TamariIntervalPoset(3,[(3,4)]) + Traceback (most recent call last): + ... + ValueError: The relations do not correspond to the size of the poset. + + sage: TamariIntervalPoset(2,[(2,1),(1,2)]) + Traceback (most recent call last): + ... + ValueError: Hasse diagram contains cycles. + + sage: TamariIntervalPoset(3,[(1,3)]) + Traceback (most recent call last): + ... + ValueError: This does not satisfy the Tamari interval-poset condition. + + It is also possible to transform a poset directly into an interval-poset:: + + sage: TIP = TamariIntervalPosets() + sage: p = Poset( ([1,2,3], [(1,2)])) + sage: TIP(p) + The tamari interval of size 3 induced by relations [(1, 2)] + sage: TIP(Poset({1: []})) + The tamari interval of size 1 induced by relations [] + sage: TIP(Poset({})) + The tamari interval of size 0 induced by relations [] + """ + __metaclass__ = ClasscallMetaclass + + @staticmethod + def __classcall_private__(cls, *args, **opts): + r""" + Ensure that interval-posets created by the enumerated sets and + directly are the same and that they are instances of + :class:`TamariIntervalPoset`. + + TESTS:: + + sage: ip = TamariIntervalPoset(4,[(2,4),(3,4),(2,1),(3,1)]) + sage: ip.parent() + Interval-posets + sage: type(ip) + + + sage: ip2 = TamariIntervalPosets()(4,[(2,4),(3,4),(2,1),(3,1)]) + sage: ip2.parent() is ip.parent() + True + sage: type(ip) is type(ip2) + True + + sage: ip3 = TamariIntervalPosets(4)([(2,4),(3,4),(2,1),(3,1)]) + sage: ip3.parent() is ip.parent() + False + sage: type(ip3) is type(ip) + True + """ + P = TamariIntervalPosets_all() + return P.element_class(P, *args, **opts) + + def __init__(self, parent, size, relations, check=True): + r""" + TESTS:: + + sage: TamariIntervalPoset(3,[(1,2),(3,2)]).parent() + Interval-posets + """ + self._size = size + self._poset = Poset((range(1, size + 1), relations)) + if self._poset.cardinality() != size: + # This can happen as the Poset constructor automatically adds + # in elements from the relations. + raise ValueError("The relations do not correspond to the size of the poset.") + + if check and not TamariIntervalPosets.check_poset(self._poset): + raise ValueError("This does not satisfy the Tamari interval-poset condition.") + + Element.__init__(self, parent) + + self._cover_relations = tuple(self._poset.cover_relations()) + self._latex_options = dict() + + def set_latex_options(self, D): + r""" + Set the latex options for use in the ``_latex_`` function. The + default values are set in the ``__init__`` function. + + - ``tikz_scale`` -- (default: 1) scale for use with the tikz package + + - ``line_width`` -- (default: 1*``tikz_scale``) value representing the + line width + + - ``color_decreasing`` -- (default: red) the color for decreasing + relations + + - ``color_increasing`` -- (default: blue) the color for increasing + relations + + - ``hspace`` -- (default: 1) the difference between horizontal + coordinates of adjacent vertices + + - ``vspace`` -- (default: 1) the difference between vertical + coordinates of adjacent vertices + + INPUT: + + - ``D`` -- a dictionary with a list of latex parameters to change + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(2,4),(3,4),(2,1),(3,1)]) + sage: ip.latex_options()["color_decreasing"] + 'red' + sage: ip.set_latex_options({"color_decreasing":'green'}) + sage: ip.latex_options()["color_decreasing"] + 'green' + sage: ip.set_latex_options({"color_increasing":'black'}) + sage: ip.latex_options()["color_increasing"] + 'black' + + To change the default options for all interval-posets, use the + parent's global latex options:: + + sage: ip = TamariIntervalPoset(4,[(2,4),(3,4),(2,1),(3,1)]) + sage: ip2 = TamariIntervalPoset(4,[(1,2),(2,3)]) + sage: ip.latex_options()["color_decreasing"] + 'red' + sage: ip2.latex_options()["color_decreasing"] + 'red' + sage: TamariIntervalPosets.global_options(latex_color_decreasing='green') + sage: ip.latex_options()["color_decreasing"] + 'green' + sage: ip2.latex_options()["color_decreasing"] + 'green' + + Next we set a local latex option and show the global does not + override it:: + + sage: ip.set_latex_options({"color_decreasing": 'black'}) + sage: ip.latex_options()["color_decreasing"] + 'black' + sage: TamariIntervalPosets.global_options(latex_color_decreasing='blue') + sage: ip.latex_options()["color_decreasing"] + 'black' + sage: ip2.latex_options()["color_decreasing"] + 'blue' + sage: TamariIntervalPosets.global_options.reset() + """ + for opt in D: + self._latex_options[opt] = D[opt] + + def latex_options(self): + r""" + Return the latex options for use in the ``_latex_`` function as a + dictionary. The default values are set using the global options. + + - ``tikz_scale`` -- (default: 1) scale for use with the tikz package + + - ``line_width`` -- (default: 1) value representing the line width + (additionally scaled by ``tikz_scale``) + + - ``color_decreasing`` -- (default: ``'red'``) the color for + decreasing relations + + - ``color_increasing`` -- (default: ``'blue'``) the color for + increasing relations + + - ``hspace`` -- (default: 1) the difference between horizontal + coordinates of adjacent vertices + + - ``vspace`` -- (default: 1) the difference between vertical + coordinates of adjacent vertices + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(2,4),(3,4),(2,1),(3,1)]) + sage: ip.latex_options()['color_decreasing'] + 'red' + sage: ip.latex_options()['hspace'] + 1 + """ + d = self._latex_options.copy() + if "tikz_scale" not in d: + d["tikz_scale"] = self.parent().global_options["latex_tikz_scale"] + if "line_width" not in d: + d["line_width"] = self.parent().global_options["latex_line_width_scalar"] * d["tikz_scale"] + if "color_decreasing" not in d: + d["color_decreasing"] = self.parent().global_options["latex_color_decreasing"] + if "color_increasing" not in d: + d["color_increasing"] = self.parent().global_options["latex_color_increasing"] + if "hspace" not in d: + d["hspace"] = self.parent().global_options["latex_hspace"] + if "vspace" not in d: + d["vspace"] = self.parent().global_options["latex_vspace"] + return d + + def _latex_(self): + r""" + A latex representation of ``self`` using the tikzpicture package. + + If `x` precedes `y`, then `y` will always be placed on top of `x` + and/or to the right of `x`. + Decreasing relations tend to be drawn vertically and increasing + relations horizontally. + The algorithm tries to avoid superposition but on big + interval-posets, it might happen. + + You can use ``self.set_latex_options()`` to change default latex + options. Or you can use the parent's global options. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(2,4),(3,4),(2,1),(3,1)]) + sage: print ip._latex_() + \begin{tikzpicture}[scale=1] + \node(T2) at (0,-1) {2}; + \node(T3) at (1,-2) {3}; + \node(T1) at (1,0) {1}; + \node(T4) at (2,-1) {4}; + \draw[line width = 0.5, color=blue] (T2) -- (T4); + \draw[line width = 0.5, color=red] (T2) -- (T1); + \draw[line width = 0.5, color=blue] (T3) -- (T4); + \draw[line width = 0.5, color=red] (T3) -- (T1); + \end{tikzpicture} + """ + latex.add_package_to_preamble_if_available("tikz") + latex_options = self.latex_options() + start = "\\begin{tikzpicture}[scale=" + str(latex_options['tikz_scale']) + "]\n" + end = "\\end{tikzpicture}" + + def draw_node(j, x, y): + r""" + Internal method to draw vertices + """ + return "\\node(T" + str(j) + ") at (" + str(x) + "," + str(y) + ") {" + str(j) + "};\n" + + def draw_increasing(i, j): + r""" + Internal method to draw increasing relations + """ + return "\\draw[line width = " + str(latex_options["line_width"]) + ", color=" + latex_options["color_increasing"] + "] (T" + str(i) + ") -- (T" + str(j) + ");\n" + + def draw_decreasing(i, j): + r""" + Internal method to draw decreasing relations + """ + return "\\draw[line width = " + str(latex_options["line_width"]) + ", color=" + latex_options["color_decreasing"] + "] (T" + str(i) + ") -- (T" + str(j) + ");\n" + if self.size() == 0: + nodes = "\\node(T0) at (0,0){$\emptyset$};" + relations = "" + else: + nodes = "" # latex for node decraltions + relations = "" # latex for drawing relations + to_draw = [] + to_draw.append((1, 0)) # a pilo of nodes to be drawn with their y position + + current_parent = [self.increasing_parent(1)] # a pilo for the current increasing parents + parenty = [0] # a pilo for the current parent y positions + if current_parent[-1] is not None: + relations += draw_increasing(1, current_parent[-1]) + vspace = latex_options["vspace"] + hspace = latex_options["hspace"] + x = 0 + y = 0 + + # the idea is that we draw the nodes from left to right and save their y position + for i in xrange(2, self.size() + 1): + # at each, we draw all possible nodes and add the current node to the to_draw pilo + decreasing_parent = self.decreasing_parent(i) + increasing_parent = self.increasing_parent(i) + while len(to_draw) > 0 and (decreasing_parent is None or decreasing_parent < to_draw[-1][0]): + # we draw all the nodes which can be placed at x + # we know these nodes won't have any more decreasing children (so their horizontal position is fixed) + n = to_draw.pop() + nodes += draw_node(n[0], x, n[1]) + if i != current_parent[-1]: + #i is not the current increasing parent + if (not self.le(i, i - 1) and decreasing_parent is not None): + # there is no decreasing relation between i and i-1 + #they share a decreasing parent and are placed alongside horizontally + x += hspace + if current_parent[-1] is not None: + y -= vspace + else: + #otherwise, they are placed alongside vertically + y -= vspace + if increasing_parent != current_parent[-1]: + current_parent.append(increasing_parent) + parenty.append(y) + nodey = y + else: + # i is the current increasing parent so it takes the current vertical position + current_parent.pop() + x += hspace + nodey = parenty.pop() + if len(current_parent) == 0 or increasing_parent != current_parent[-1]: + current_parent.append(increasing_parent) + parenty.append(nodey) + to_draw.append((i, nodey)) + if increasing_parent is not None: + relations += draw_increasing(i, increasing_parent) + if decreasing_parent is not None: + relations += draw_decreasing(i, decreasing_parent) + for n in to_draw: + # we draw all remaining nodes + nodes += draw_node(n[0], x, n[1]) + return start + nodes + relations + end + + def poset(self): + r""" + Return ``self`` as a labelled poset. + + An interval-poset is indeed constructed from a labelled poset which + is stored internally. This method allows to access the poset and + all the associated methods. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(1,2),(3,2),(2,4),(3,4)]) + sage: pos = ip.poset(); pos + Finite poset containing 4 elements + sage: pos.maximal_chains() + [[3, 2, 4], [1, 2, 4]] + sage: pos.maximal_elements() + [4] + sage: pos.is_lattice() + False + """ + return self._poset + + @cached_method + def increasing_cover_relations(self): + r""" + Return the cover relations of the initial forest of ``self`` + (the poset formed by keeping only the relations of the form + `a` precedes `b` with `a < b`). + + The initial forest of ``self`` is a forest with its roots + being on top. It is also called the increasing poset of ``self``. + + .. WARNING:: + + This method computes the cover relations of the initial + forest. This is not identical with the cover relations of + ``self`` which happen to be increasing! + + .. SEEALSO:: + + :meth:`initial_forest` + + EXAMPLES:: + + sage: TamariIntervalPoset(4,[(1,2),(3,2),(2,4),(3,4)]).increasing_cover_relations() + [(1, 2), (2, 4), (3, 4)] + sage: TamariIntervalPoset(3,[(1,2),(1,3),(2,3)]).increasing_cover_relations() + [(1, 2), (2, 3)] + """ + relations = [] + size = self.size() + for i in xrange(1, size): + for j in xrange(i + 1, size + 1): + if self.le(i, j): + relations.append((i, j)) + break + return relations + + def increasing_roots(self): + r""" + Return the root vertices of the initial forest of ``self``, + i.e., the vertices `a` of ``self`` such that there is no + `b > a` with `a` precedes `b`. + + OUTPUT: + + The list of all roots of the initial forest of ``self``, in + decreasing order. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(3,5),(4,5)]); ip + The tamari interval of size 6 induced by relations [(1, 2), (3, 5), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + sage: ip.increasing_roots() + [6, 5, 2] + sage: ip.initial_forest().increasing_roots() + [6, 5, 2] + """ + size = self.size() + if size == 0: + return [] + roots = [size] + root = size + for i in xrange(size - 1, 0, -1): + if not self.le(i, root): + roots.append(i) + root = i + return roots + + def increasing_children(self, v): + r""" + Return the children of ``v`` in the initial forest of ``self``. + + INPUT: + + - ``v`` -- an integer representing a vertex of ``self`` + (between 1 and ``size``) + + OUTPUT: + + The list of all children of ``v`` in the initial forest of + ``self``, in decreasing order. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(3,5),(4,5)]); ip + The tamari interval of size 6 induced by relations [(1, 2), (3, 5), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + sage: ip.increasing_children(2) + [1] + sage: ip.increasing_children(5) + [4, 3] + sage: ip.increasing_children(1) + [] + """ + children = [] + root = None + for i in xrange(v - 1, 0, -1): + if not self.le(i, v): + break + if root is None or not self.le(i, root): + children.append(i) + root = i + return children + + def increasing_parent(self, v): + r""" + Return the vertex parent of ``v`` in the initial forest of ``self``. + + This is the lowest (as integer!) vertex `b > v` such that `v` + precedes `b`. If there is no such vertex (that is, `v` is an + increasing root), then ``None`` is returned. + + INPUT: + + - ``v`` -- an integer representing a vertex of ``self`` + (between 1 and ``size``) + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(3,5),(4,5)]); ip + The tamari interval of size 6 induced by relations [(1, 2), (3, 5), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + sage: ip.increasing_parent(1) + 2 + sage: ip.increasing_parent(3) + 5 + sage: ip.increasing_parent(4) + 5 + sage: ip.increasing_parent(5) is None + True + """ + parent = None + for i in xrange(self.size(), v, -1): + if self.le(v, i): + parent = i + return parent + + @cached_method + def decreasing_cover_relations(self): + r""" + Return the cover relations of the final forest of ``self`` + (the poset formed by keeping only the relations of the form + `a` precedes `b` with `a > b`). + + The final forest of ``self`` is a forest with its roots + being on top. It is also called the decreasing poset of ``self``. + + .. WARNING:: + + This method computes the cover relations of the final + forest. This is not identical with the cover relations of + ``self`` which happen to be decreasing! + + .. SEEALSO:: + + :meth:`final_forest` + + EXAMPLES:: + + sage: TamariIntervalPoset(4,[(2,1),(3,2),(3,4),(4,2)]).decreasing_cover_relations() + [(4, 2), (3, 2), (2, 1)] + sage: TamariIntervalPoset(4,[(2,1),(4,3),(2,3)]).decreasing_cover_relations() + [(4, 3), (2, 1)] + sage: TamariIntervalPoset(3,[(2,1),(3,1),(3,2)]).decreasing_cover_relations() + [(3, 2), (2, 1)] + """ + relations = [] + for i in xrange(self.size(), 1, -1): + for j in xrange(i - 1, 0, -1): + if self.le(i, j): + relations.append((i, j)) + break + return relations + + def decreasing_roots(self): + r""" + Return the root vertices of the final forest of ``self``, + i.e., the vertices `b` such that there is no `a < b` with `b` + preceding `a`. + + OUTPUT: + + The list of all roots of the final forest of ``self``, in + increasing order. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(3,5),(4,5)]); ip + The tamari interval of size 6 induced by relations [(1, 2), (3, 5), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + sage: ip.decreasing_roots() + [1, 2] + sage: ip.final_forest().decreasing_roots() + [1, 2] + """ + if self.size() == 0: + return [] + roots = [1] + root = 1 + for i in xrange(2, self.size() + 1): + if not self.le(i, root): + roots.append(i) + root = i + return roots + + def decreasing_children(self, v): + r""" + Return the children of ``v`` in the final forest of ``self``. + + INPUT: + + - ``v`` -- an integer representing a vertex of ``self`` + (between 1 and ``size``) + + OUTPUT: + + The list of all children of ``v`` in the final forest of ``self``, + in increasing order. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(3,5),(4,5)]); ip + The tamari interval of size 6 induced by relations [(1, 2), (3, 5), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + sage: ip.decreasing_children(2) + [3, 5] + sage: ip.decreasing_children(3) + [4] + sage: ip.decreasing_children(1) + [] + """ + children = [] + root = None + for i in xrange(v + 1, self.size() + 1): + if not self.le(i, v): + break + if root is None or not self.le(i, root): + children.append(i) + root = i + return children + + def decreasing_parent(self, v): + r""" + Return the vertex parent of ``v`` in the final forest of ``self``. + This is the highest (as integer!) vertex `a < v` such that ``v`` + precedes ``a``. If there is no such vertex (that is, `v` is a + decreasing root), then ``None`` is returned. + + INPUT: + + - ``v`` -- an integer representing a vertex of ``self`` (between + 1 and ``size``) + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(3,5),(4,5)]); ip + The tamari interval of size 6 induced by relations [(1, 2), (3, 5), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + sage: ip.decreasing_parent(4) + 3 + sage: ip.decreasing_parent(3) + 2 + sage: ip.decreasing_parent(5) + 2 + sage: ip.decreasing_parent(2) is None + True + """ + parent = None + for i in xrange(1, v): + if self.le(v, i): + parent = i + return parent + + def le(self, e1, e2): + r""" + Return whether ``e1`` precedes or equals ``e2`` in ``self``. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(1,2),(2,3)]) + sage: ip.le(1,2) + True + sage: ip.le(1,3) + True + sage: ip.le(2,3) + True + sage: ip.le(3,4) + False + sage: ip.le(1,1) + True + """ + return self._poset.le(e1, e2) + + def lt(self, e1, e2): + r""" + Return whether ``e1`` strictly precedes ``e2`` in ``self``. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(1,2),(2,3)]) + sage: ip.lt(1,2) + True + sage: ip.lt(1,3) + True + sage: ip.lt(2,3) + True + sage: ip.lt(3,4) + False + sage: ip.lt(1,1) + False + """ + return self._poset.lt(e1, e2) + + def ge(self, e1, e2): + r""" + Return whether ``e2`` precedes or equals ``e1`` in ``self``. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(1,2),(2,3)]) + sage: ip.ge(2,1) + True + sage: ip.ge(3,1) + True + sage: ip.ge(3,2) + True + sage: ip.ge(4,3) + False + sage: ip.ge(1,1) + True + """ + return self._poset.ge(e1, e2) + + def gt(self, e1, e2): + r""" + Return whether ``e2`` strictly precedes ``e1`` in ``self``. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(1,2),(2,3)]) + sage: ip.gt(2,1) + True + sage: ip.gt(3,1) + True + sage: ip.gt(3,2) + True + sage: ip.gt(4,3) + False + sage: ip.gt(1,1) + False + """ + return self._poset.gt(e1, e2) + + def size(self): + r""" + Return the size (number of vertices) of the interval-poset. + + EXAMPLES:: + + sage: TamariIntervalPoset(3,[(2,1),(3,1)]).size() + 3 + """ + return self._size + + def complement(self): + r""" + Return the complement of the interval-poset ``self``. + + If `P` is a Tamari interval-poset of size `n`, then the + *complement* of `P` is defined as the interval-poset `Q` whose + base set is `[n] = \{1, 2, \ldots, n\}` (just as for `P`), but + whose order relation has `a` precede `b` if and only if + `n + 1 - a` precedes `n + 1 - b` in `P`. + + In terms of the Tamari lattice, the *complement* is the symmetric + of ``self``. It is formed from the left-right symmeterized of + the binary trees of the interval (switching left and right + subtrees, see + :meth:`~sage.combinat.binary_tree.BinaryTree.left_right_symmetry`). + In particular, initial intervals are sent to final intervals and + vice-versa. + + EXAMPLES:: + + sage: TamariIntervalPoset(3, [(2, 1), (3, 1)]).complement() + The tamari interval of size 3 induced by relations [(1, 3), (2, 3)] + sage: TamariIntervalPoset(0, []).complement() + The tamari interval of size 0 induced by relations [] + sage: ip = TamariIntervalPoset(4, [(1, 2), (2, 4), (3, 4)]) + sage: ip.complement() == TamariIntervalPoset(4, [(2, 1), (3, 1), (4, 3)]) + True + sage: ip.lower_binary_tree() == ip.complement().upper_binary_tree().left_right_symmetry() + True + sage: ip.upper_binary_tree() == ip.complement().lower_binary_tree().left_right_symmetry() + True + sage: ip.is_initial_interval() + True + sage: ip.complement().is_final_interval() + True + """ + N = self._size + 1 + new_covers = [[N - i[0], N - i[1]] for i in self._poset.cover_relations_iterator()] + return TamariIntervalPoset(N - 1, new_covers) + + def insertion(self, i): + """ + Return the Tamari insertion of an integer `i` into the + interval-poset ``self``. + + If `P` is a Tamari interval-poset of size `n` and `i` is an + integer with `1 \leq i \leq n+1`, then the Tamari insertion of + `i` into `P` is defined as the Tamari interval-poset of size + `n+1` which corresponds to the interval `[C_1, C_2]` on the + Tamari lattice, where the binary trees `C_1` and `C_2` are + defined as follows: We write the interval-poset `P` as + `[B_1, B_2]` for two binary trees `B_1` and `B_2`. We label + the vertices of each of these two trees with the integers + `1, 2, \ldots, i-1, i+1, i+2, \ldots, n+1` in such a way that + the trees are binary search trees (this labelling is unique). + Then, we insert `i` into each of these trees (in the way as + explained in + :meth:`~sage.combinat.binary_tree.LabelledBinaryTree.binary_search_insert`). + The shapes of the resulting two trees are denoted `C_1` and + `C_2`. + + An alternative way to construct the insertion of `i` into + `P` is by relabeling each vertex `u` of `P` satisfying + `u \geq i` (as integers) as `u+1`, and then adding a vertex + `i` which should precede `i-1` and `i+1`. + + .. TODO:: + + To study this, it would be more natural to define + interval-posets on arbitrary ordered sets rather than just + on `\{1, 2, \ldots, n\}`. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4, [(2, 3), (4, 3)]); ip + The tamari interval of size 4 induced by relations [(2, 3), (4, 3)] + sage: ip.insertion(1) + The tamari interval of size 5 induced by relations [(1, 2), (3, 4), (5, 4)] + sage: ip.insertion(2) + The tamari interval of size 5 induced by relations [(2, 3), (3, 4), (5, 4), (2, 1)] + sage: ip.insertion(3) + The tamari interval of size 5 induced by relations [(2, 4), (3, 4), (5, 4), (3, 2)] + sage: ip.insertion(4) + The tamari interval of size 5 induced by relations [(2, 3), (4, 5), (5, 3), (4, 3)] + sage: ip.insertion(5) + The tamari interval of size 5 induced by relations [(2, 3), (5, 4), (4, 3)] + + sage: ip = TamariIntervalPoset(0, []) + sage: ip.insertion(1) + The tamari interval of size 1 induced by relations [] + + sage: ip = TamariIntervalPoset(1, []) + sage: ip.insertion(1) + The tamari interval of size 2 induced by relations [(1, 2)] + sage: ip.insertion(2) + The tamari interval of size 2 induced by relations [(2, 1)] + + TESTS: + + Verifying that the two ways of computing insertion are + equivalent:: + + sage: def insert_alternative(T, i): + ....: # Just another way to compute the insertion of i into T. + ....: from sage.combinat.binary_tree import LabelledBinaryTree + ....: B1 = T.lower_binary_tree().canonical_labelling() + ....: B2 = T.upper_binary_tree().canonical_labelling() + ....: # We should relabel the trees to "make space" for a label i, + ....: # but we don't, because it doesn't make a difference: The + ....: # binary search insertion will go precisely the same, because + ....: # an integer equal to the label of the root gets sent onto + ....: # the left branch. + ....: C1 = B1.binary_search_insert(i) + ....: C2 = B2.binary_search_insert(i) + ....: return TamariIntervalPosets.from_binary_trees(C1, C2) + sage: def test_equivalence(n): + ....: for T in TamariIntervalPosets(n): + ....: for i in range(1, n + 2): + ....: if not (insert_alternative(T, i) == T.insertion(i)): + ....: print T, i + ....: return False + ....: return True + sage: test_equivalence(3) + True + """ + n = self._size + if not 0 < i <= n + 1: + raise ValueError("integer to be inserted not in the appropriate interval") + def add1(u): + if u >= i: + return u + 1 + return u + rels = [(add1(a), add1(b)) for (a, b) in self.decreasing_cover_relations()] \ + + [(add1(a), add1(b)) for (a, b) in self.increasing_cover_relations()] \ + + [(k, k - 1) for k in [i] if i > 1] \ + + [(k, k + 1) for k in [i] if i <= n] + return TamariIntervalPoset(n + 1, rels) + + def _repr_(self): + r""" + TESTS:: + + sage: TamariIntervalPoset(3,[(2,1),(3,1)]) + The tamari interval of size 3 induced by relations [(3, 1), (2, 1)] + sage: TamariIntervalPoset(3,[(3,1),(2,1)]) + The tamari interval of size 3 induced by relations [(3, 1), (2, 1)] + sage: TamariIntervalPoset(3,[(2,3),(2,1)]) + The tamari interval of size 3 induced by relations [(2, 3), (2, 1)] + """ + return "The tamari interval of size {} induced by relations {}".format(self.size(), + self.increasing_cover_relations() + self.decreasing_cover_relations()) + + def __eq__(self, other): + r""" + TESTS:: + + sage: TamariIntervalPoset(0,[]) == TamariIntervalPoset(0,[]) + True + sage: TamariIntervalPoset(1,[]) == TamariIntervalPoset(0,[]) + False + sage: TamariIntervalPoset(3,[(1,2),(3,2)]) == TamariIntervalPoset(3,[(3,2),(1,2)]) + True + sage: TamariIntervalPoset(3,[(1,2),(3,2)]) == TamariIntervalPoset(3,[(1,2)]) + False + """ + if (not isinstance(other, TamariIntervalPoset)): + return False + return self.size() == other.size() and self._cover_relations == other._cover_relations + + def __ne__(self, other): + r""" + TESTS:: + + sage: TamariIntervalPoset(0,[]) != TamariIntervalPoset(0,[]) + False + sage: TamariIntervalPoset(1,[]) != TamariIntervalPoset(0,[]) + True + sage: TamariIntervalPoset(3,[(1,2),(3,2)]) != TamariIntervalPoset(3,[(3,2),(1,2)]) + False + sage: TamariIntervalPoset(3,[(1,2),(3,2)]) != TamariIntervalPoset(3,[(1,2)]) + True + """ + return not (self == other) + + def __le__(self, el2): + r""" + TESTS:: + + sage: ip1 = TamariIntervalPoset(4,[(1,2),(2,3),(4,3)]) + sage: ip2 = TamariIntervalPoset(4,[(1,2),(2,3)]) + sage: ip1 <= ip2 + True + sage: ip1 <= ip1 + True + sage: ip2 <= ip1 + False + """ + return self.parent().le(self, el2) + + def __lt__(self, el2): + r""" + TESTS:: + + sage: ip1 = TamariIntervalPoset(4,[(1,2),(2,3),(4,3)]) + sage: ip2 = TamariIntervalPoset(4,[(1,2),(2,3)]) + sage: ip1 < ip2 + True + sage: ip1 < ip1 + False + sage: ip2 < ip1 + False + """ + return self.parent().lt(self, el2) + + def __ge__(self, el2): + r""" + TESTS:: + + sage: ip1 = TamariIntervalPoset(4,[(1,2),(2,3),(4,3)]) + sage: ip2 = TamariIntervalPoset(4,[(1,2),(2,3)]) + sage: ip1 >= ip2 + False + sage: ip1 >= ip1 + True + sage: ip2 >= ip1 + True + """ + return self.parent().ge(self, el2) + + def __gt__(self, el2): + r""" + TESTS:: + + sage: ip1 = TamariIntervalPoset(4,[(1,2),(2,3),(4,3)]) + sage: ip2 = TamariIntervalPoset(4,[(1,2),(2,3)]) + sage: ip1 > ip2 + False + sage: ip1 > ip1 + False + sage: ip2 > ip1 + True + """ + return self.parent().gt(self, el2) + + def __iter__(self): + r""" + Iterate through the vertices of ``self``. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(1,2),(3,2)]) + sage: [i for i in ip] + [1, 2, 3, 4] + """ + return xrange(1,self.size()+1).__iter__() + + + def contains_interval(self, other): + r""" + Return whether the interval represented by ``other`` is contained + in ``self`` as an interval of the Tamari lattice. + + In terms of interval-posets, it means that all relations of ``self`` + are relations of ``other``. + + INPUT: + + - ``other`` -- an interval-poset + + EXAMPLES:: + + sage: ip1 = TamariIntervalPoset(4,[(1,2),(2,3),(4,3)]) + sage: ip2 = TamariIntervalPoset(4,[(2,3)]) + sage: ip2.contains_interval(ip1) + True + sage: ip3 = TamariIntervalPoset(4,[(2,1)]) + sage: ip2.contains_interval(ip3) + False + sage: ip4 = TamariIntervalPoset(3,[(2,3)]) + sage: ip2.contains_interval(ip4) + False + """ + if other.size() != self.size(): + return False + for (i, j) in self._cover_relations: + if not other.le(i, j): + return False + return True + + def lower_contains_interval(self, other): + r""" + Return whether the interval represented by ``other`` is contained + in ``self`` as an interval of the Tamari lattice and if they share + the same lower bound. + + As interval-posets, it means that ``other`` contains the relations + of ``self`` plus some extra increasing relations. + + INPUT: + + - ``other`` -- an interval-poset + + EXAMPLES:: + + sage: ip1 = TamariIntervalPoset(4,[(1,2),(2,3),(4,3)]); + sage: ip2 = TamariIntervalPoset(4,[(4,3)]) + sage: ip2.lower_contains_interval(ip1) + True + sage: ip2.contains_interval(ip1) and ip2.lower_binary_tree() == ip1.lower_binary_tree() + True + sage: ip3 = TamariIntervalPoset(4,[(4,3),(2,1)]) + sage: ip2.contains_interval(ip3) + True + sage: ip2.lower_binary_tree() == ip3.lower_binary_tree() + False + sage: ip2.lower_contains_interval(ip3) + False + """ + if not self.contains_interval(other): + return False + for (i, j) in other.decreasing_cover_relations(): + if not self.le(i, j): + return False + return True + + def upper_contains_interval(self, other): + r""" + Return whether the interval represented by ``other`` is contained + in ``self`` as an interval of the Tamari lattice and if they share + the same upper bound. + + As interval-posets, it means that ``other`` contains the relations + of ``self`` plus some extra decreasing relations. + + INPUT: + + - ``other`` -- an interval-poset + + EXAMPLES:: + + sage: ip1 = TamariIntervalPoset(4,[(1,2),(2,3),(4,3)]) + sage: ip2 = TamariIntervalPoset(4,[(1,2),(2,3)]) + sage: ip2.upper_contains_interval(ip1) + True + sage: ip2.contains_interval(ip1) and ip2.upper_binary_tree() == ip1.upper_binary_tree() + True + sage: ip3 = TamariIntervalPoset(4,[(1,2),(2,3),(3,4)]) + sage: ip2.upper_contains_interval(ip3) + False + sage: ip2.contains_interval(ip3) + True + sage: ip2.upper_binary_tree() == ip3.upper_binary_tree() + False + """ + if not self.contains_interval(other): + return False + for (i, j) in other.increasing_cover_relations(): + if not self.le(i, j): + return False + return True + + def is_linear_extension(self, perm): + r""" + Return whether the permutation ``perm`` is a linear extension + of ``self``. + + INPUT: + + - ``perm`` -- a permutation of the size of ``self`` + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(1,2),(2,3),(4,3)]) + sage: ip.is_linear_extension([1,4,2,3]) + True + sage: ip.is_linear_extension(Permutation([1,4,2,3])) + True + sage: ip.is_linear_extension(Permutation([1,4,3,2])) + False + """ + return self._poset.is_linear_extension(perm) + + def contains_binary_tree(self, binary_tree): + r""" + Return whether the interval represented by ``self`` contains + the binary tree ``binary_tree``. + + INPUT: + + - ``binary_tree`` -- a binary tree + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(2,4),(3,4),(2,1),(3,1)]) + sage: ip.contains_binary_tree(BinaryTree([[None,[None,[]]],None])) + True + sage: ip.contains_binary_tree(BinaryTree([None,[[[],None],None]])) + True + sage: ip.contains_binary_tree(BinaryTree([[],[[],None]])) + False + sage: ip.contains_binary_tree(ip.lower_binary_tree()) + True + sage: ip.contains_binary_tree(ip.upper_binary_tree()) + True + sage: all([ip.contains_binary_tree(bt) for bt in ip.binary_trees()]) + True + + """ + return self.is_linear_extension(binary_tree.to_132_avoiding_permutation()) + + def contains_dyck_word(self, dyck_word): + r""" + Return whether the interval represented by ``self`` contains + the Dyck word ``dyck_word``. + + INPUT: + + - ``dyck_word`` -- a Dyck word + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(2,4),(3,4),(2,1),(3,1)]) + sage: ip.contains_dyck_word(DyckWord([1,1,1,0,0,0,1,0])) + True + sage: ip.contains_dyck_word(DyckWord([1,1,0,1,0,1,0,0])) + True + sage: ip.contains_dyck_word(DyckWord([1,0,1,1,0,1,0,0])) + False + sage: ip.contains_dyck_word(ip.lower_dyck_word()) + True + sage: ip.contains_dyck_word(ip.upper_dyck_word()) + True + sage: all([ip.contains_dyck_word(bt) for bt in ip.dyck_words()]) + True + """ + return self.contains_binary_tree(dyck_word.to_binary_tree_tamari()) + + def intersection(self, other): + r""" + Return the interval-poset formed by combining the relations from + both ``self`` and ``other``. It corresponds to the intersection + of the two corresponding intervals of the Tamari lattice. + + INPUT: + + - ``other`` -- an interval-poset of the same size as ``self`` + + EXAMPLES:: + + sage: ip1 = TamariIntervalPoset(4,[(1,2),(2,3)]) + sage: ip2 = TamariIntervalPoset(4,[(4,3)]) + sage: ip1.intersection(ip2) + The tamari interval of size 4 induced by relations [(1, 2), (2, 3), (4, 3)] + sage: ip3 = TamariIntervalPoset(4,[(2,1)]) + sage: ip1.intersection(ip3) + Traceback (most recent call last): + ... + ValueError: This intersection is empty, it does not correspond to an interval-poset. + sage: ip4 = TamariIntervalPoset(3,[(2,3)]) + sage: ip2.intersection(ip4) + Traceback (most recent call last): + ... + ValueError: Intersections are only possible on interval-posets of the same size. + """ + if other.size() != self.size(): + raise ValueError("Intersections are only possible on interval-posets of the same size.") + try: + return TamariIntervalPoset(self.size(), self._cover_relations + other._cover_relations) + except ValueError: + raise ValueError("This intersection is empty, it does not correspond to an interval-poset.") + + def initial_forest(self): + r""" + Return the initial forest of ``self``, i.e., the interval-poset + formed from only the increasing relations of ``self``. + + EXAMPLES:: + + sage: TamariIntervalPoset(4,[(1,2),(3,2),(2,4),(3,4)]).initial_forest() + The tamari interval of size 4 induced by relations [(1, 2), (2, 4), (3, 4)] + sage: ip = TamariIntervalPoset(4,[(1,2),(2,3)]) + sage: ip.initial_forest() == ip + True + """ + return TamariIntervalPoset(self.size(), self.increasing_cover_relations()) + + def final_forest(self): + r""" + Return the final forest of ``self``, i.e., the interval-poset + formed with only the decreasing relations of ``self``. + + EXAMPLES:: + + sage: TamariIntervalPoset(4,[(2,1),(3,2),(3,4),(4,2)]).final_forest() + The tamari interval of size 4 induced by relations [(4, 2), (3, 2), (2, 1)] + sage: ip = TamariIntervalPoset(3,[(2,1),(3,1)]) + sage: ip.final_forest() == ip + True + """ + return TamariIntervalPoset(self.size(), self.decreasing_cover_relations()) + + def is_initial_interval(self): + r""" + Return if ``self`` corresponds to an initial interval of the Tamari + lattice, i.e. if its lower end is the smallest element of the lattice. + It consists of checking that ``self`` does not contain any decreasing + relations. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4, [(1, 2), (2, 4), (3, 4)]) + sage: ip.is_initial_interval() + True + sage: ip.lower_dyck_word() + [1, 0, 1, 0, 1, 0, 1, 0] + sage: ip = TamariIntervalPoset(4, [(1, 2), (2, 4), (3, 4), (3, 2)]) + sage: ip.is_initial_interval() + False + sage: ip.lower_dyck_word() + [1, 0, 1, 1, 0, 0, 1, 0] + sage: all([ DyckWord([1,0,1,0,1,0]).tamari_interval(dw).is_initial_interval() for dw in DyckWords(3)]) + True + """ + return self.decreasing_cover_relations() == [] + + def is_final_interval(self): + r""" + Return if ``self`` corresponds to a final interval of the Tamari + lattice, i.e. if its upper end is the largest element of the lattice. + It consists of checking that ``self`` does not contain any increasing + relations. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4, [(4, 3), (3, 1), (2, 1)]) + sage: ip.is_final_interval() + True + sage: ip.upper_dyck_word() + [1, 1, 1, 1, 0, 0, 0, 0] + sage: ip = TamariIntervalPoset(4, [(4, 3), (3, 1), (2, 1), (2, 3)]) + sage: ip.is_final_interval() + False + sage: ip.upper_dyck_word() + [1, 1, 0, 1, 1, 0, 0, 0] + sage: all([ dw.tamari_interval(DyckWord([1, 1, 1, 0, 0, 0])).is_final_interval() for dw in DyckWords(3)]) + True + """ + return self.increasing_cover_relations() == [] + + def lower_binary_tree(self): + r""" + Return the lowest binary tree in the interval of the Tamari + lattice represented by ``self``. + + This is a binary tree. It is the shape of the unique binary + search tree whose left-branch ordered forest (i.e., the result + of applying + :meth:`~sage.combinat.binary_tree.BinaryTree.to_ordered_tree_left_branch` + and cutting off the root) is the final forest of ``self``. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(4,5)]); ip + The tamari interval of size 6 induced by relations [(1, 2), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + sage: ip.lower_binary_tree() + [[., .], [[., [., .]], [., .]]] + sage: TamariIntervalPosets.final_forest(ip.lower_binary_tree()) == ip.final_forest() + True + sage: ip == TamariIntervalPosets.from_binary_trees(ip.lower_binary_tree(),ip.upper_binary_tree()) + True + """ + return self.min_linear_extension().binary_search_tree_shape(left_to_right=False) + + def lower_dyck_word(self): + r""" + Return the lowest Dyck word in the interval of the Tamari lattice + represented by ``self``. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(4,5)]); ip + The tamari interval of size 6 induced by relations [(1, 2), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + sage: ip.lower_dyck_word() + [1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0] + sage: TamariIntervalPosets.final_forest(ip.lower_dyck_word()) == ip.final_forest() + True + sage: ip == TamariIntervalPosets.from_dyck_words(ip.lower_dyck_word(),ip.upper_dyck_word()) + True + """ + return self.lower_binary_tree().to_dyck_word_tamari() + + def upper_binary_tree(self): + r""" + Return the highest binary tree in the interval of the Tamari + lattice represented by ``self``. + + This is a binary tree. It is the shape of the unique binary + search tree whose right-branch ordered forest (i.e., the result + of applying + :meth:`~sage.combinat.binary_tree.BinaryTree.to_ordered_tree_right_branch` + and cutting off the root) is the initial forest of ``self``. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(4,5)]); ip + The tamari interval of size 6 induced by relations [(1, 2), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + sage: ip.upper_binary_tree() + [[., .], [., [[., .], [., .]]]] + sage: TamariIntervalPosets.initial_forest(ip.upper_binary_tree()) == ip.initial_forest() + True + sage: ip == TamariIntervalPosets.from_binary_trees(ip.lower_binary_tree(),ip.upper_binary_tree()) + True + """ + return self.max_linear_extension().binary_search_tree_shape(left_to_right=False) + + def upper_dyck_word(self): + r""" + Return the highest Dyck word in the interval of the Tamari lattice + represented by ``self``. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(4,5)]); ip + The tamari interval of size 6 induced by relations [(1, 2), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + sage: ip.upper_dyck_word() + [1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0] + sage: TamariIntervalPosets.initial_forest(ip.upper_dyck_word()) == ip.initial_forest() + True + sage: ip == TamariIntervalPosets.from_dyck_words(ip.lower_dyck_word(),ip.upper_dyck_word()) + True + """ + return self.upper_binary_tree().to_dyck_word_tamari() + + def sub_poset(self, start, end): + r""" + Return the renormalized sub-poset of ``self`` consisting solely + of integers from ``start`` (inclusive) to ``end`` (not inclusive). + + "Renormalized" means that these integers are relabelled + `1,2,\ldots,k` in the obvious way (i.e., by subtracting + ``start - 1``). + + INPUT: + + - ``start`` -- an integer, the starting vertex (inclusive) + - ``end`` -- an integer, the ending vertex (not inclusive) + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(3,5),(4,5)]); ip + The tamari interval of size 6 induced by relations [(1, 2), (3, 5), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + sage: ip.sub_poset(1,3) + The tamari interval of size 2 induced by relations [(1, 2)] + sage: ip.sub_poset(1,4) + The tamari interval of size 3 induced by relations [(1, 2), (3, 2)] + sage: ip.sub_poset(1,5) + The tamari interval of size 4 induced by relations [(1, 2), (4, 3), (3, 2)] + sage: ip.sub_poset(1,7) == ip + True + sage: ip.sub_poset(1,1) + The tamari interval of size 0 induced by relations [] + """ + if start < 1 or start > end or end > self.size() + 1: + raise ValueError("Invalid starting or ending value, accepted: 1 <= start <= end <= size+1") + if start == end: + return TamariIntervalPoset(0, []) + relations = [(i - start + 1, j - start + 1) for (i, j) in self.increasing_cover_relations() if i >= start and j < end] + relations.extend([(j - start + 1, i - start + 1) for (j, i) in self.decreasing_cover_relations() if i >= start and j < end]) + return TamariIntervalPoset(end - start, relations) + + def min_linear_extension(self): + r""" + Return the minimal permutation for the right weak order which is + a linear extension of ``self``. + + This is also the minimal permutation in the sylvester + class of ``self.lower_binary_tree()`` and is a 312-avoiding + permutation. + + The right weak order is also known as the right permutohedron + order. See + :meth:`~sage.combinat.permutation.Permutation.permutohedron_lequal` + for its definition. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(1,2),(2,3),(4,3)]) + sage: ip.min_linear_extension() + [1, 2, 4, 3] + sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(4,5)]) + sage: ip.min_linear_extension() + [1, 4, 3, 6, 5, 2] + sage: ip = TamariIntervalPoset(0,[]) + sage: ip.min_linear_extension() + [] + sage: ip = TamariIntervalPoset(5, [(1, 4), (2, 4), (3, 4), (5, 4)]); ip + The tamari interval of size 5 induced by relations [(1, 4), (2, 4), (3, 4), (5, 4)] + sage: ip.min_linear_extension() + [1, 2, 3, 5, 4] + + """ + # The min linear extension is build by postfix-reading the + # final forest of ``self``. + def add(perm, i): + r""" + Internal recursive method to compute the min linear extension. + """ + for j in self.decreasing_children(i): + add(perm, j) + perm.append(i) + perm = [] + for i in self.decreasing_roots(): + add(perm, i) + return Permutation(perm) + + def max_linear_extension(self): + r""" + Return the maximal permutation for the right weak order which is + a linear extension of ``self``. + + This is also the maximal permutation in the sylvester + class of ``self.upper_binary_tree()`` and is a 132-avoiding + permutation. + + The right weak order is also known as the right permutohedron + order. See + :meth:`~sage.combinat.permutation.Permutation.permutohedron_lequal` + for its definition. + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(1,2),(2,3),(4,3)]) + sage: ip.max_linear_extension() + [4, 1, 2, 3] + sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(4,5)]); ip + The tamari interval of size 6 induced by relations [(1, 2), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + sage: ip.max_linear_extension() + [6, 4, 5, 3, 1, 2] + sage: ip = TamariIntervalPoset(0,[]); ip + The tamari interval of size 0 induced by relations [] + sage: ip.max_linear_extension() + [] + sage: ip = TamariIntervalPoset(5, [(1, 4), (2, 4), (3, 4), (5, 4)]); ip + The tamari interval of size 5 induced by relations [(1, 4), (2, 4), (3, 4), (5, 4)] + sage: ip.max_linear_extension() + [5, 3, 2, 1, 4] + """ + # The max linear extension is build by right-to-left + # postfix-reading the initial forest of ``self``. The + # right-to-leftness here is ensured by the fact that + # :meth:`increasing_children` and :meth:`increasing_roots` + # output their results in decreasing order. + def add(perm, i): + r""" + Internal recursive method to compute the max linear extension. + """ + for j in self.increasing_children(i): + add(perm, j) + perm.append(i) + perm = [] + for i in self.increasing_roots(): + add(perm, i) + return Permutation(perm) + + def linear_extensions(self): + r""" + Return an iterator on the permutations which are linear + extensions of ``self``. + + They form an interval of the right weak order (also called the + right permutohedron order -- see + :meth:`~sage.combinat.permutation.Permutation.permutohedron_lequal` + for a definition). + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(3,[(1,2),(3,2)]) + sage: list(ip.linear_extensions()) + [[3, 1, 2], [1, 3, 2]] + sage: ip = TamariIntervalPoset(4,[(1,2),(2,3),(4,3)]) + sage: list(ip.linear_extensions()) + [[4, 1, 2, 3], [1, 4, 2, 3], [1, 2, 4, 3]] + """ + for ext in self._poset.linear_extensions(): + yield Permutation(ext) + + def lower_contained_intervals(self): + r""" + If ``self`` represents the interval `[t_1, t_2]` of the Tamari + lattice, return an iterator on all intervals `[t_1,t]` with + `t \leq t_2` for the Tamari lattice. + + In terms of interval-posets, it corresponds to adding all possible + relations of the form `n` precedes `m` with `n 2 + 2 -> 3 + 1 -> 3 + 3 -> 4 + 2 -> 4 + 1 -> 4 + ... + ("Link" means "relation of the poset".) + + One useful feature of interval-posets is that if you add a single + new relation -- say, `x` precedes `y` -- to an existing + interval-poset and take the transitive closure, and if the axioms + of an interval-poset are still satisfied for `(a,c) = (x,y)` and + for `(a,c) = (y,x)`, then the transitive closure is an + interval-poset (i.e., roughly speaking, the other new relations + forced by `x` preceding `y` under transitive closure cannot + invalidate the axioms). This is helpful when extending + interval-posets, and is the reason why this and other iterators + don't yield invalid interval-posets. + """ + def add_relations(poset, n, m): + r""" + Internal recursive method to generate all possible intervals. + At every step during the iteration, we have n < m and every + i satisfying n < i < m satisfies that i precedes m in the + poset ``poset`` (except when m > size). + """ + if n <= 0: + #if n<=0, then we go to the next m + n = m + m += 1 + if m > size: + #if m>size, it's finished + return + + if poset.le(n, m): + #there is already a link n->m, so we go to the next n + for pos in add_relations(poset, n - 1, m): + yield pos + elif poset.le(m, n): + #there is an inverse link m->n, we know we won't be able + #to create a link i->m with i<=n, so we go to the next m + for pos in add_relations(poset, m, m + 1): + yield pos + else: + #there is no link n->m + #first option : we don't create the link and go to the next m + #(since the lack of a link n->m forbids any links i->m + #with im already exist for all + #nj should + elif stop: + return False + return True + + @staticmethod + def final_forest(element): + r""" + Return the final forest of a binary tree, an interval-poset or a + Dyck word. + + A final forest is an interval-poset corresponding to a final + interval of the Tamari lattice, i.e., containing only decreasing + relations. + + It can be constructed from a binary tree by its binary + search tree labeling with the rule: `b` precedes + `a` in the final forest iff `b` is in the right subtree of `a` + in the binary search tree. + + INPUT: + + - ``element`` -- a binary tree, a Dyck word or an interval-poset + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(1,2),(2,3),(4,3)]) + sage: TamariIntervalPosets.final_forest(ip) + The tamari interval of size 4 induced by relations [(1, 2), (2, 3)] + + From binary trees:: + + sage: bt = BinaryTree(); bt + . + sage: TamariIntervalPosets.final_forest(bt) + The tamari interval of size 0 induced by relations [] + sage: bt = BinaryTree([]); bt + [., .] + sage: TamariIntervalPosets.final_forest(bt) + The tamari interval of size 1 induced by relations [] + sage: bt = BinaryTree([[],None]); bt + [[., .], .] + sage: TamariIntervalPosets.final_forest(bt) + The tamari interval of size 2 induced by relations [] + sage: bt = BinaryTree([None,[]]); bt + [., [., .]] + sage: TamariIntervalPosets.final_forest(bt) + The tamari interval of size 2 induced by relations [(2, 1)] + sage: bt = BinaryTree([[],[]]); bt + [[., .], [., .]] + sage: TamariIntervalPosets.final_forest(bt) + The tamari interval of size 3 induced by relations [(3, 2)] + sage: bt = BinaryTree([[None,[[],None]],[]]); bt + [[., [[., .], .]], [., .]] + sage: TamariIntervalPosets.final_forest(bt) + The tamari interval of size 5 induced by relations [(5, 4), (3, 1), (2, 1)] + + From Dyck words:: + + sage: dw = DyckWord([1,0]) + sage: TamariIntervalPosets.final_forest(dw) + The tamari interval of size 1 induced by relations [] + sage: dw = DyckWord([1,1,0,1,0,0,1,1,0,0]) + sage: TamariIntervalPosets.final_forest(dw) + The tamari interval of size 5 induced by relations [(5, 4), (3, 1), (2, 1)] + """ + if isinstance(element, TamariIntervalPoset): + return element.initial_forest() + elif element in DyckWords(): + binary_tree = element.to_binary_tree_tamari() + elif element in BinaryTrees() or element in LabelledBinaryTrees(): + binary_tree = element + else: + raise ValueError("Do not know how to construct the initial forest of {}".format(element)) + + def get_relations(bt, start=1): + r""" + Recursive method to get the binary tree final forest relations + with only one recursive reading of the tree. + + The vertices are being labelled with integers starting with + ``start``. + + OUTPUT: + + - the indexes of the nodes on the left border of the tree + (these become the roots of the forest) + - the relations of the final forest (as a list of tuples) + - the next available index for a node (size of tree + + ``start``) + """ + if not bt: + return [], [], start # leaf + roots, relations, index = get_relations(bt[0], start=start) + rroots, rrelations, rindex = get_relations(bt[1], start=index + 1) + roots.append(index) + relations.extend(rrelations) + relations.extend([(j, index) for j in rroots]) + return roots, relations, rindex + + roots, relations, index = get_relations(binary_tree) + return TamariIntervalPoset(index - 1, relations) + + @staticmethod + def initial_forest(element): + r""" + Return the inital forest of a binary tree, an interval-poset or + a Dyck word. + + An initial forest is an interval-poset corresponding to an initial + interval of the Tamari lattice, i.e., containing only increasing + relations. + + It can be constructed from a binary tree by its binary + search tree labeling with the rule: `a` precedes `b` in the + initial forest iff `a` is in the left subtree of `b` in the + binary search tree. + + INPUT: + + - ``element`` -- a binary tree, a Dyck word or an interval-poset + + EXAMPLES:: + + sage: ip = TamariIntervalPoset(4,[(1,2),(2,3),(4,3)]) + sage: TamariIntervalPosets.initial_forest(ip) + The tamari interval of size 4 induced by relations [(1, 2), (2, 3)] + + with binary trees:: + + sage: bt = BinaryTree(); bt + . + sage: TamariIntervalPosets.initial_forest(bt) + The tamari interval of size 0 induced by relations [] + sage: bt = BinaryTree([]); bt + [., .] + sage: TamariIntervalPosets.initial_forest(bt) + The tamari interval of size 1 induced by relations [] + sage: bt = BinaryTree([[],None]); bt + [[., .], .] + sage: TamariIntervalPosets.initial_forest(bt) + The tamari interval of size 2 induced by relations [(1, 2)] + sage: bt = BinaryTree([None,[]]); bt + [., [., .]] + sage: TamariIntervalPosets.initial_forest(bt) + The tamari interval of size 2 induced by relations [] + sage: bt = BinaryTree([[],[]]); bt + [[., .], [., .]] + sage: TamariIntervalPosets.initial_forest(bt) + The tamari interval of size 3 induced by relations [(1, 2)] + sage: bt = BinaryTree([[None,[[],None]],[]]); bt + [[., [[., .], .]], [., .]] + sage: TamariIntervalPosets.initial_forest(bt) + The tamari interval of size 5 induced by relations [(1, 4), (2, 3), (3, 4)] + + from Dyck words:: + + sage: dw = DyckWord([1,0]) + sage: TamariIntervalPosets.initial_forest(dw) + The tamari interval of size 1 induced by relations [] + sage: dw = DyckWord([1,1,0,1,0,0,1,1,0,0]) + sage: TamariIntervalPosets.initial_forest(dw) + The tamari interval of size 5 induced by relations [(1, 4), (2, 3), (3, 4)] + """ + if isinstance(element, TamariIntervalPoset): + return element.initial_forest() + elif element in DyckWords(): + binary_tree = element.to_binary_tree_tamari() + elif element in BinaryTrees() or element in LabelledBinaryTrees(): + binary_tree = element + else: + raise ValueError("Do not know how to construct the initial forest of {}".format(element)) + + def get_relations(bt, start=1): + r""" + Recursive method to get the binary tree initial forest + relations with only one recursive reading of the tree. + + The vertices are being labelled with integers starting with + ``start``. + + OUTPUT: + + - the indexes of the nodes on the right border of the tree + (these become the roots of the forest) + - the relations of the initial forest (as a list of tuples) + - the next available index for a node (size of tree + + ``start``) + """ + if not bt: + return [], [], start # leaf + lroots, lrelations, index = get_relations(bt[0], start=start) + roots, relations, rindex = get_relations(bt[1], start=index + 1) + roots.append(index) + relations.extend(lrelations) + relations.extend([(j, index) for j in lroots]) + return roots, relations, rindex + + roots, relations, index = get_relations(binary_tree) + return TamariIntervalPoset(index - 1, relations) + + @staticmethod + def from_binary_trees(tree1, tree2): + r""" + Return the interval-poset corresponding to the interval + [``tree1``,``tree2``] of the Tamari lattice. Raise an exception if + ``tree1`` is not `\leq` ``tree2`` in the Tamari lattice. + + INPUT: + + - ``tree1`` -- a binary tree + - ``tree2`` -- a binary tree greater or equal than ``tree1`` for + the Tamari lattice + + EXAMPLES:: + + sage: tree1 = BinaryTree([[],None]) + sage: tree2 = BinaryTree([None,[]]) + sage: TamariIntervalPosets.from_binary_trees(tree1,tree2) + The tamari interval of size 2 induced by relations [] + sage: TamariIntervalPosets.from_binary_trees(tree1,tree1) + The tamari interval of size 2 induced by relations [(1, 2)] + sage: TamariIntervalPosets.from_binary_trees(tree2,tree2) + The tamari interval of size 2 induced by relations [(2, 1)] + + sage: tree1 = BinaryTree([[],[[None,[]],[]]]) + sage: tree2 = BinaryTree([None,[None,[None,[[],[]]]]]) + sage: TamariIntervalPosets.from_binary_trees(tree1,tree2) + The tamari interval of size 6 induced by relations [(4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + + sage: tree3 = BinaryTree([None,[None,[[],[None,[]]]]]) + sage: TamariIntervalPosets.from_binary_trees(tree1,tree3) + Traceback (most recent call last): + ... + ValueError: The two binary trees are not comparable on the Tamari lattice. + sage: TamariIntervalPosets.from_binary_trees(tree1,BinaryTree()) + Traceback (most recent call last): + ... + ValueError: The two binary trees are not comparable on the Tamari lattice. + """ + initial_forest = TamariIntervalPosets.initial_forest(tree2) + final_forest = TamariIntervalPosets.final_forest(tree1) + try: + return initial_forest.intersection(final_forest) + except: + raise ValueError("The two binary trees are not comparable on the Tamari lattice.") + + @staticmethod + def from_dyck_words(dw1, dw2): + r""" + Return the interval-poset corresponding to the interval + [``dw1``,``dw2``] of the Tamari lattice. Raise an exception if the + two Dyck words ``dw1`` and ``dw2`` do not satisfy + ``dw1`` `\leq` ``dw2`` in the Tamari lattice. + + INPUT: + + - ``dw1`` -- a Dyck word + - ``dw2`` -- a Dyck word greater or equal than ``dw1`` for + the Tamari lattice + + EXAMPLES:: + + sage: dw1 = DyckWord([1,0,1,0]) + sage: dw2 = DyckWord([1,1,0,0]) + sage: TamariIntervalPosets.from_dyck_words(dw1,dw2) + The tamari interval of size 2 induced by relations [] + sage: TamariIntervalPosets.from_dyck_words(dw1,dw1) + The tamari interval of size 2 induced by relations [(1, 2)] + sage: TamariIntervalPosets.from_dyck_words(dw2,dw2) + The tamari interval of size 2 induced by relations [(2, 1)] + + sage: dw1 = DyckWord([1,0,1,1,1,0,0,1,1,0,0,0]) + sage: dw2 = DyckWord([1,1,1,1,0,1,1,0,0,0,0,0]) + sage: TamariIntervalPosets.from_dyck_words(dw1,dw2) + The tamari interval of size 6 induced by relations [(4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + + sage: dw3 = DyckWord([1,1,1,0,1,1,1,0,0,0,0,0]) + sage: TamariIntervalPosets.from_dyck_words(dw1,dw3) + Traceback (most recent call last): + ... + ValueError: The two Dyck words are not comparable on the Tamari lattice. + sage: TamariIntervalPosets.from_dyck_words(dw1,DyckWord([1,0])) + Traceback (most recent call last): + ... + ValueError: The two Dyck words are not comparable on the Tamari lattice. + """ + tree1 = dw1.to_binary_tree_tamari() + tree2 = dw2.to_binary_tree_tamari() + try: + return TamariIntervalPosets.from_binary_trees(tree1, tree2) + except: + raise ValueError("The two Dyck words are not comparable on the Tamari lattice.") + + def __call__(self, *args, **keywords): + r""" + Allows for a poset to be directly transformed into an interval-poset. + + It is some kind of coercion but cannot be made through the coercion + system because posets do not have parents. + + EXAMPLES:: + + sage: TIP = TamariIntervalPosets() + sage: p = Poset( ([1,2,3], [(1,2)])) + sage: TIP(p) + The tamari interval of size 3 induced by relations [(1, 2)] + sage: TIP(TIP(p)) + The tamari interval of size 3 induced by relations [(1, 2)] + sage: TIP(3,[(1,2)]) + The tamari interval of size 3 induced by relations [(1, 2)] + sage: p = Poset(([1,2,3],[(1,3)])) + sage: TIP(p) + Traceback (most recent call last): + ... + ValueError: This does not satisfy the Tamari interval-poset condition. + """ + if isinstance(args[0], TamariIntervalPoset): + return args[0] + if len(args) == 1 and isinstance(args[0], FinitePoset): + return self.element_class(self, args[0].cardinality(), args[0].cover_relations()) + + return super(TamariIntervalPosets, self).__call__(*args, **keywords) + + def le(self, el1, el2): + r""" + Poset stucture on the set of interval-posets through interval + containment. + + Return whether the interval represented by ``el1`` is contained in + the interval represented by ``el2``. + + INPUT: + + - ``el1`` -- an interval-poset + - ``el2`` -- an interval-poset + + EXAMPLES:: + + sage: ip1 = TamariIntervalPoset(4,[(1,2),(2,3),(4,3)]) + sage: ip2 = TamariIntervalPoset(4,[(1,2),(2,3)]) + sage: TamariIntervalPosets().le(ip1,ip2) + True + sage: TamariIntervalPosets().le(ip2,ip1) + False + """ + return el2.contains_interval(el1) + + global_options = TamariIntervalPosetOptions + + +################################################################# +# Enumerated set of all Tamari Interval-posets +################################################################# +class TamariIntervalPosets_all(DisjointUnionEnumeratedSets, TamariIntervalPosets): + r""" + The enumerated set of all Tamari interval-posets. + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.combinat.interval_posets import TamariIntervalPosets_all + sage: S = TamariIntervalPosets_all() + sage: S.cardinality() + +Infinity + + sage: it = iter(S) + sage: [it.next() for i in xrange(5)] + [The tamari interval of size 0 induced by relations [], + The tamari interval of size 1 induced by relations [], + The tamari interval of size 2 induced by relations [], + The tamari interval of size 2 induced by relations [(2, 1)], + The tamari interval of size 2 induced by relations [(1, 2)]] + sage: it.next().parent() + Interval-posets + sage: S(0,[]) + The tamari interval of size 0 induced by relations [] + + sage: S is TamariIntervalPosets_all() + True + sage: TestSuite(S).run() + """ + DisjointUnionEnumeratedSets.__init__( + self, Family(NonNegativeIntegers(), TamariIntervalPosets_size), + facade=True, keepkey=False, category=(Posets(), EnumeratedSets())) + + def _repr_(self): + r""" + TEST:: + + sage: TamariIntervalPosets() + Interval-posets + """ + return "Interval-posets" + + def _element_constructor_(self, size, relations): + r""" + EXAMPLES:: + + sage: TIP = TamariIntervalPosets() + sage: TIP(3,[(1,2)]) + The tamari interval of size 3 induced by relations [(1, 2)] + """ + return self.element_class(self, size, relations) + + def __contains__(self, x): + r""" + TESTS:: + + sage: S = TamariIntervalPosets() + sage: 1 in S + False + sage: S(0,[]) in S + True + """ + return isinstance(x, self.element_class) + + Element = TamariIntervalPoset + + +################################################################# +# Enumerated set of Tamari interval-posets of a given size +################################################################# +class TamariIntervalPosets_size(TamariIntervalPosets): + r""" + The enumerated set of interval-posets of a given size. + + TESTS:: + + sage: from sage.combinat.interval_posets import TamariIntervalPosets_size + sage: for i in xrange(6): TestSuite(TamariIntervalPosets_size(i)).run() + """ + def __init__(self, size): + r""" + TESTS:: + + sage: S = TamariIntervalPosets(3) + sage: TestSuite(S).run() + + sage: S is TamariIntervalPosets(3) + True + """ + # there is a natural order on interval-posets through inclusions + # that is why we use the FinitePosets category + super(TamariIntervalPosets_size, self).__init__(category=(FinitePosets(), FiniteEnumeratedSets())) + + self._size = size + + def _repr_(self): + r""" + TESTS:: + + sage: TamariIntervalPosets(3) + Interval-posets of size 3 + """ + return "Interval-posets of size {}".format(self._size) + + def __contains__(self, x): + r""" + TESTS:: + + sage: S = TamariIntervalPosets(3) + sage: 1 in S + False + sage: S([]) in S + True + """ + return isinstance(x, self.element_class) and x.size() == self._size + + def cardinality(self): + r""" + The cardinality of ``self``. That is, the number of + interval-posets of size `n`. + + The formula was given in [ChapTamari08]_: + + .. MATH:: + + \frac{2(4n+1)!}{(n+1)!(3n+2)!} + = \frac{2}{n(n+1)} \binom{4n+1}{n-1}. + + EXAMPLES:: + + sage: [TamariIntervalPosets(i).cardinality() for i in range(6)] + [1, 1, 3, 13, 68, 399] + """ + from sage.rings.arith import binomial + n = self._size + if n == 0: + return Integer(1) + return (2 * binomial(4 * n + 1, n - 1)) // (n * (n + 1)) + # return Integer(2 * factorial(4*n+1)/(factorial(n+1)*factorial(3*n+2))) + + def __iter__(self): + r""" + Recursive generation: we iterate through all interval-posets of + size ``size - 1`` and add all possible relations to the last + vertex. + + This works thanks to the fact that the restriction of an + interval-poset of size `n` to the subset `\{1, 2, \ldots, k\}` for + a fixed `k \leq n` is an interval-poset. + + TESTS:: + + sage: TIP1 = TamariIntervalPosets(1) + sage: list(TIP1) + [The tamari interval of size 1 induced by relations []] + sage: TIP2 = TamariIntervalPosets(2) + sage: list(TIP2) + [The tamari interval of size 2 induced by relations [], + The tamari interval of size 2 induced by relations [(2, 1)], + The tamari interval of size 2 induced by relations [(1, 2)]] + sage: TIP3 = TamariIntervalPosets(3) + sage: list(TIP3) + [The tamari interval of size 3 induced by relations [], + The tamari interval of size 3 induced by relations [(3, 2)], + The tamari interval of size 3 induced by relations [(2, 3)], + The tamari interval of size 3 induced by relations [(1, 3), (2, 3)], + The tamari interval of size 3 induced by relations [(2, 1)], + The tamari interval of size 3 induced by relations [(3, 2), (2, 1)], + The tamari interval of size 3 induced by relations [(3, 1), (2, 1)], + The tamari interval of size 3 induced by relations [(2, 3), (2, 1)], + The tamari interval of size 3 induced by relations [(2, 3), (3, 1), (2, 1)], + The tamari interval of size 3 induced by relations [(1, 3), (2, 3), (2, 1)], + The tamari interval of size 3 induced by relations [(1, 2)], + The tamari interval of size 3 induced by relations [(1, 2), (3, 2)], + The tamari interval of size 3 induced by relations [(1, 2), (2, 3)]] + sage: all([len(list(TamariIntervalPosets(i)))==TamariIntervalPosets(i).cardinality() for i in xrange(6)]) + True + """ + n = self._size + if n <=1: + yield TamariIntervalPoset(n, []) + return + + for tip in TamariIntervalPosets(n - 1): + new_tip = TamariIntervalPoset(n, tip._cover_relations) + yield new_tip # we have added an extra vertex but no relations + + # adding a decreasing relation n>>m2 with m2>n + if not new_tip.le(m, n): + new_tip = TamariIntervalPoset(n, new_tip._cover_relations + ((m, n),)) + yield new_tip + else: + continue + + # further adding a decreasing relation n>>m2 with m2 + sage: S.first().__class__ == TamariIntervalPosets().first().__class__ + True + """ + return self._parent_for.element_class + + def _element_constructor_(self, relations): + r""" + EXAMPLES:: + + sage: TIP3 = TamariIntervalPosets(3) + sage: TIP3([(1,2)]) + The tamari interval of size 3 induced by relations [(1, 2)] + sage: TIP3([(3,4)]) + Traceback (most recent call last): + ... + ValueError: The relations do not correspond to the size of the poset. + """ + return self.element_class(self, self._size, relations) + diff --git a/src/sage/combinat/matrices/latin.py b/src/sage/combinat/matrices/latin.py index 69e28c272dc..d6607d957c0 100644 --- a/src/sage/combinat/matrices/latin.py +++ b/src/sage/combinat/matrices/latin.py @@ -148,6 +148,7 @@ #load "dancing_links.sage" from dlxcpp import DLXCPP +from functools import reduce class LatinSquare: def __init__(self, *args): @@ -1173,7 +1174,7 @@ def genus(T1, T2): """ cells_map, t1, t2, t3 = tau123(T1, T2) - return (len(t1.to_cycles()) + len(t2.to_cycles()) + len(t3.to_cycles()) - T1.nr_filled_cells() - 2)/(-2) + return (len(t1.to_cycles()) + len(t2.to_cycles()) + len(t3.to_cycles()) - T1.nr_filled_cells() - 2) // (-2) def tau123(T1, T2): """ @@ -1815,7 +1816,7 @@ def elementary_abelian_2group(s): L_prev = elementary_abelian_2group(s-1) L = LatinSquare(2**s, 2**s) - offset = L.nrows()/2 + offset = L.nrows() // 2 for r in range(L_prev.nrows()): for c in range(L_prev.ncols()): diff --git a/src/sage/combinat/ncsf_qsym/qsym.py b/src/sage/combinat/ncsf_qsym/qsym.py index 05e5e9b8ac7..d631c2080f9 100644 --- a/src/sage/combinat/ncsf_qsym/qsym.py +++ b/src/sage/combinat/ncsf_qsym/qsym.py @@ -149,7 +149,7 @@ class QuasiSymmetricFunctions(UniqueRepresentation, Parent): Chapter 5 of [Reiner2013]_ and Section 11 of [HazWitt1]_ are devoted to quasi-symmetric functions, as are Malvenuto's thesis [Mal1993]_ - and part of Chapter 7 of [Sta1999]_. + and part of Chapter 7 of [Sta-EC2]_. .. rubric:: The implementation of the quasi-symmetric function Hopf algebra diff --git a/src/sage/combinat/ncsym/ncsym.py b/src/sage/combinat/ncsym/ncsym.py index ad3a904afa1..cccdb196628 100644 --- a/src/sage/combinat/ncsym/ncsym.py +++ b/src/sage/combinat/ncsym/ncsym.py @@ -27,6 +27,7 @@ from sage.combinat.posets.posets import Poset from sage.combinat.sf.sf import SymmetricFunctions from sage.sets.set import Set +from functools import reduce def matchings(A, B): """ diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 0a44f498200..0e773aaec1c 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -3521,7 +3521,7 @@ def zero_one_sequence(self): partitions). These are also known as **path sequences**, **Maya diagrams**, - **plus-minus diagrams**, **Comet code** [Sta1999]_, among others. + **plus-minus diagrams**, **Comet code** [Sta-EC2]_, among others. OUTPUT: @@ -3672,7 +3672,7 @@ def quotient(self, length): tmp = [] for i in reversed(range(len(part))): if part[i] % length == e: - tmp.append(ZZ((part[i]-k)/length)) + tmp.append(ZZ((part[i]-k)//length)) k += length a = [i for i in tmp if i != 0] diff --git a/src/sage/combinat/partition_algebra.py b/src/sage/combinat/partition_algebra.py index 166f51088b0..3973381a7aa 100644 --- a/src/sage/combinat/partition_algebra.py +++ b/src/sage/combinat/partition_algebra.py @@ -675,7 +675,7 @@ def __iter__(self): sage: all( [ bk.cardinality() == len(bk.list()) for bk in bks] ) True """ - for sp in SetPartitions(self._set, [2]*(len(self._set)/2)): + for sp in SetPartitions(self._set, [2]*(len(self._set)//2)): yield self.element_class(self, sp) class SetPartitionsBkhalf_k(SetPartitionsAkhalf_k): @@ -747,7 +747,7 @@ def __iter__(self): {{1, 2}, {-3, -1}, {4, -4}, {3, -2}}] """ set = range(1,self.k+1) + map(lambda x: -1*x, range(1,self.k+1)) - for sp in SetPartitions(set, [2]*(len(set)/2) ): + for sp in SetPartitions(set, [2]*(len(set)//2) ): yield self.element_class(self, Set(list(sp)) + Set([Set([self.k+1, -self.k -1])])) ##### diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index db74410661d..2fb3e2fedfb 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -112,6 +112,10 @@ :meth:`~sage.combinat.permutation.Permutation.robinson_schensted` | Returns the pair of standard tableaux obtained by running the Robinson-Schensted Algorithm on ``self``. :meth:`~sage.combinat.permutation.Permutation.left_tableau` | Returns the left standard tableau after performing the RSK algorithm. :meth:`~sage.combinat.permutation.Permutation.right_tableau` | Returns the right standard tableau after performing the RSK algorithm. + :meth:`~sage.combinat.permutation.Permutation.increasing_tree` | Returns the increasing tree of ``self``. + :meth:`~sage.combinat.permutation.Permutation.increasing_tree_shape` | Returns the shape of the increasing tree of ``self``. + :meth:`~sage.combinat.permutation.Permutation.binary_search_tree` | Returns the binary search tree of ``self``. + :meth:`~sage.combinat.permutation.Permutation.sylvester_class` | Iterates over the equivalence class of ``self`` under sylvester congruence :meth:`~sage.combinat.permutation.Permutation.RS_partition` | Returns the shape of the tableaux obtained by the RSK algorithm. :meth:`~sage.combinat.permutation.Permutation.remove_extra_fixed_points` | Returns the permutation obtained by removing any fixed points at the end of ``self``. :meth:`~sage.combinat.permutation.Permutation.retract_plain` | Returns the plain retract of ``self`` to a smaller symmetric group `S_m`. @@ -3889,6 +3893,21 @@ def binary_search_tree(self, left_to_right=True): """ Return the binary search tree associated to ``self``. + If `w` is a word, then the binary search tree associated to `w` + is defined as the result of starting with an empty binary tree, + and then inserting the letters of `w` one by one into this tree. + Here, the insertion is being done according to the method + :meth:`~sage.combinat.binary_tree.LabelledBinaryTree.binary_search_insert`, + and the word `w` is being traversed from left to right. + + A permutation is regarded as a word (using one-line notation), + and thus a binary search tree associated to a permutation is + defined. + + If the optional keyword variable ``left_to_right`` is set to + ``False``, the word `w` is being traversed from right to left + instead. + EXAMPLES:: sage: Permutation([1,4,3,2]).binary_search_tree() @@ -3942,6 +3961,108 @@ def binary_search_tree_shape(self, left_to_right=True): """ return self.binary_search_tree(left_to_right).shape() + def sylvester_class(self, left_to_right=False): + """ + Iterate over the equivalence class of the permutation ``self`` + under sylvester congruence. + + Sylvester congruence is an equivalence relation on the set `S_n` + of all permutations of `n`. It is defined as the smallest + equivalence relation such that every permutation of the form + `uacvbw` with `u`, `v` and `w` being words and `a`, `b` and `c` + being letters satisfying `a \leq b < c` is equivalent to the + permutation `ucavbw`. (Here, permutations are regarded as words + by way of one-line notation.) This definition comes from [HNT05]_, + Definition 8, where it is more generally applied to arbitrary + words. + + The equivalence class of a permutation `p \in S_n` under sylvester + congruence is called the *sylvester class* of `p`. It is an + interval in the right permutohedron order (see + :meth:`permutohedron_lequal`) on `S_n`. + + This is related to the + :meth:`~sage.combinat.binary_tree.LabelledBinaryTree.sylvester_class` + method in that the equivalence class of a permutation `\pi` under + sylvester congruence is the sylvester class of the right-to-left + binary search tree of `\pi`. However, the present method + yields permutations, while the method on labelled binary trees + yields plain lists. + + If the variable ``left_to_right`` is set to ``True``, the method + instead iterates over the equivalence class of ``self`` with + respect to the *left* sylvester congruence. The left sylvester + congruence is easiest to define by saying that two permutations + are equivalent under it if and only if their reverses + (:meth:`reverse`) are equivalent under (standard) sylvester + congruence. + + EXAMPLES: + + The sylvester class of a permutation in `S_5`:: + + sage: p = Permutation([3, 5, 1, 2, 4]) + sage: sorted(p.sylvester_class()) + [[1, 3, 2, 5, 4], + [1, 3, 5, 2, 4], + [1, 5, 3, 2, 4], + [3, 1, 2, 5, 4], + [3, 1, 5, 2, 4], + [3, 5, 1, 2, 4], + [5, 1, 3, 2, 4], + [5, 3, 1, 2, 4]] + + The sylvester class of a permutation `p` contains `p`:: + + sage: all( p in p.sylvester_class() for p in Permutations(4) ) + True + + Small cases:: + + sage: list(Permutation([]).sylvester_class()) + [[]] + + sage: list(Permutation([1]).sylvester_class()) + [[1]] + + The sylvester classes in `S_3`:: + + sage: [sorted(p.sylvester_class()) for p in Permutations(3)] + [[[1, 2, 3]], + [[1, 3, 2], [3, 1, 2]], + [[2, 1, 3]], + [[2, 3, 1]], + [[1, 3, 2], [3, 1, 2]], + [[3, 2, 1]]] + + The left sylvester classes in `S_3`:: + + sage: [sorted(p.sylvester_class(left_to_right=True)) for p in Permutations(3)] + [[[1, 2, 3]], + [[1, 3, 2]], + [[2, 1, 3], [2, 3, 1]], + [[2, 1, 3], [2, 3, 1]], + [[3, 1, 2]], + [[3, 2, 1]]] + + A left sylvester class in `S_5`:: + + sage: p = Permutation([4, 2, 1, 5, 3]) + sage: sorted(p.sylvester_class(left_to_right=True)) + [[4, 2, 1, 3, 5], + [4, 2, 1, 5, 3], + [4, 2, 3, 1, 5], + [4, 2, 3, 5, 1], + [4, 2, 5, 1, 3], + [4, 2, 5, 3, 1], + [4, 5, 2, 1, 3], + [4, 5, 2, 3, 1]] + """ + parself = self.parent() + t = self.binary_search_tree(left_to_right=left_to_right) + for u in t.sylvester_class(left_to_right=left_to_right): + yield parself(u) + @combinatorial_map(name='Robinson-Schensted tableau shape') def RS_partition(self): """ @@ -5579,7 +5700,7 @@ def from_rank(n, rank): factoradic = [None] * n for j in range(1,n+1): factoradic[n-j] = Integer(rank % j) - rank = int(rank) / int(j) + rank = int(rank) // j return from_lehmer_code(factoradic) diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 2a27a03ee77..3a328f372e9 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -1117,9 +1117,9 @@ def _latex_(self): sage: print P._latex_() #optional - dot2tex graphviz \begin{tikzpicture}[>=latex,line join=bevel,] %% - \node (node_1) at (6bp,57bp) [draw,draw=none] {$2$}; - \node (node_0) at (6bp,7bp) [draw,draw=none] {$1$}; - \draw [black,<-] (node_1) ..controls (6bp,31.269bp) and (6bp,20.287bp) .. (node_0); + \node (node_1) at (6.0...bp,57.0...bp) [draw,draw=none] {$2$}; + \node (node_0) at (6.0...bp,7.0...bp) [draw,draw=none] {$1$}; + \draw [black,<-] (node_1) ..controls (6.0...bp,31.269...bp) and (6.0...bp,20.287...bp) .. (node_0); % \end{tikzpicture} """ diff --git a/src/sage/combinat/rigged_configurations/kleber_tree.py b/src/sage/combinat/rigged_configurations/kleber_tree.py index d8edd3c5531..4070e04c9d5 100644 --- a/src/sage/combinat/rigged_configurations/kleber_tree.py +++ b/src/sage/combinat/rigged_configurations/kleber_tree.py @@ -137,7 +137,7 @@ def _draw_tree(tree_node, node_label=True, style_point=None, style_node='fill=wh node_place_str = ".center" nb_children = len(tree_node.children) - half = int(nb_children/2) + half = nb_children // 2 children_str = '' pos = [start[0],start[1]] start[1] += vspace diff --git a/src/sage/combinat/rigged_configurations/kr_tableaux.py b/src/sage/combinat/rigged_configurations/kr_tableaux.py index 75997231429..296e9a71f11 100644 --- a/src/sage/combinat/rigged_configurations/kr_tableaux.py +++ b/src/sage/combinat/rigged_configurations/kr_tableaux.py @@ -619,7 +619,7 @@ def _fill(self, weight): # Step 3 - Add the final column if c > -1: - val = (self._r + x - 1) / 2 + val = (self._r + x - 1) // 2 temp_list = [-x - j for j in range(self._r - val)] for j in range(val): temp_list.append(val - j) @@ -1366,7 +1366,7 @@ def epsilon(self, i): """ if i == 0: # Don't need to half it since we pull to the KR crystal return KirillovReshetikhinTableauxElement.epsilon(self, i) - return KirillovReshetikhinTableauxElement.epsilon(self, i) / 2 + return KirillovReshetikhinTableauxElement.epsilon(self, i) // 2 def phi(self, i): r""" @@ -1382,7 +1382,7 @@ def phi(self, i): """ if i == 0: # Don't need to half it since we pull to the KR crystal return KirillovReshetikhinTableauxElement.phi(self, i) - return KirillovReshetikhinTableauxElement.phi(self, i) / 2 + return KirillovReshetikhinTableauxElement.phi(self, i) // 2 @cached_method def to_array(self, rows=True): diff --git a/src/sage/combinat/root_system/cartan_matrix.py b/src/sage/combinat/root_system/cartan_matrix.py index 0ef6e7ce79d..11b8dbd650b 100644 --- a/src/sage/combinat/root_system/cartan_matrix.py +++ b/src/sage/combinat/root_system/cartan_matrix.py @@ -391,6 +391,25 @@ def symmetrizer(self): scalar = LCM(map(lambda x: QQ(x).denominator(), sym)) return Family( {iset[i]: ZZ(val*scalar) for i, val in enumerate(sym)} ) + @cached_method + def symmetrized_matrix(self): + """ + Return the symmetrized matrix of ``self`` if symmetrizable. + + EXAMPLES:: + + sage: cm = CartanMatrix(['B',4,1]) + sage: cm.symmetrized_matrix() + [ 4 0 -2 0 0] + [ 0 4 -2 0 0] + [-2 -2 4 -2 0] + [ 0 0 -2 4 -2] + [ 0 0 0 -2 2] + """ + M = matrix.diagonal(list(self.symmetrizer())) * self + M.set_immutable() + return M + ########################################################################## # Cartan type methods diff --git a/src/sage/combinat/root_system/cartan_type.py b/src/sage/combinat/root_system/cartan_type.py index 1930fddb8ae..076b4ac0846 100644 --- a/src/sage/combinat/root_system/cartan_type.py +++ b/src/sage/combinat/root_system/cartan_type.py @@ -105,8 +105,6 @@ sage: T.is_finite(), T.is_simply_laced(), T.is_affine(), T.is_crystallographic() (True, True, False, True) -It will eventually include Coxeter numbers, etc. - In particular, a Sage Cartan type is endowed with a fixed choice of labels for the nodes of the Dynkin diagram. This choice follows the conventions of Nicolas Bourbaki, Lie Groups and Lie Algebras: Chapter 4-6, @@ -1788,6 +1786,43 @@ def classical(self): """ + @abstract_method + def basic_untwisted(self): + r""" + Return the basic untwisted Cartan type associated with this affine + Cartan type. + + Given an affine type `X_n^{(r)}`, the basic untwisted type is `X_n`. + In other words, it is the classical Cartan type that is twisted to + obtain ``self``. + + EXAMPLES:: + + sage: CartanType(['A', 1, 1]).basic_untwisted() + ['A', 1] + sage: CartanType(['A', 3, 1]).basic_untwisted() + ['A', 3] + sage: CartanType(['B', 3, 1]).basic_untwisted() + ['B', 3] + sage: CartanType(['E', 6, 1]).basic_untwisted() + ['E', 6] + sage: CartanType(['G', 2, 1]).basic_untwisted() + ['G', 2] + + sage: CartanType(['A', 2, 2]).basic_untwisted() + ['A', 2] + sage: CartanType(['A', 4, 2]).basic_untwisted() + ['A', 4] + sage: CartanType(['A', 11, 2]).basic_untwisted() + ['A', 11] + sage: CartanType(['D', 5, 2]).basic_untwisted() + ['D', 5] + sage: CartanType(['E', 6, 2]).basic_untwisted() + ['E', 6] + sage: CartanType(['D', 4, 3]).basic_untwisted() + ['D', 4] + """ + def row_annihilator(self, m = None): r""" Return the unique minimal non trivial annihilating linear @@ -2290,6 +2325,42 @@ def affine(self): """ return CartanType([self.letter, self.n, 1]) + def coxeter_number(self): + """ + Return the Coxeter number associated with ``self``. + + The Coxeter number is the order of a Coxeter element of the + corresponding Weyl group. + + See Bourbaki, Lie Groups and Lie Algebras V.6.1 or + :wikipedia:`Coxeter_element` for more information. + + EXAMPLES:: + + sage: CartanType(['A',4]).coxeter_number() + 5 + sage: CartanType(['B',4]).coxeter_number() + 8 + sage: CartanType(['C',4]).coxeter_number() + 8 + """ + return sum(self.affine().a()) + + def dual_coxeter_number(self): + """ + Return the Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['A',4]).dual_coxeter_number() + 5 + sage: CartanType(['B',4]).dual_coxeter_number() + 7 + sage: CartanType(['C',4]).dual_coxeter_number() + 5 + """ + return sum(self.affine().acheck()) + def type(self): """ Returns the type of ``self``. @@ -2511,6 +2582,30 @@ def classical(self): """ return CartanType([self.letter,self.n]) + def basic_untwisted(self): + r""" + Return the basic_untwisted Cartan type associated with this affine + Cartan type. + + Given an affine type `X_n^{(r)}`, the basic_untwisted type is `X_n`. In + other words, it is the classical Cartan type that is twisted to + obtain ``self``. + + EXAMPLES:: + + sage: CartanType(['A', 1, 1]).basic_untwisted() + ['A', 1] + sage: CartanType(['A', 3, 1]).basic_untwisted() + ['A', 3] + sage: CartanType(['B', 3, 1]).basic_untwisted() + ['B', 3] + sage: CartanType(['E', 6, 1]).basic_untwisted() + ['E', 6] + sage: CartanType(['G', 2, 1]).basic_untwisted() + ['G', 2] + """ + return self.classical() + def is_untwisted_affine(self): """ Implement :meth:`CartanType_affine.is_untwisted_affine` by diff --git a/src/sage/combinat/root_system/root_lattice_realizations.py b/src/sage/combinat/root_system/root_lattice_realizations.py index c6f9c255413..37aa8e2eb76 100644 --- a/src/sage/combinat/root_system/root_lattice_realizations.py +++ b/src/sage/combinat/root_system/root_lattice_realizations.py @@ -24,6 +24,7 @@ from sage.structure.element import Element from sage.sets.family import Family from sage.rings.all import ZZ, QQ +from sage.matrix.constructor import matrix from sage.modules.free_module_element import vector from sage.combinat.backtrack import TransitiveIdeal, TransitiveIdealGraded from sage.combinat.root_system.plot import PlotOptions, barycentric_projection_matrix @@ -482,6 +483,33 @@ def alpha(self): else: return self.simple_roots() + @cached_method + def basic_imaginary_roots(self): + r""" + Return the basic imaginary roots of ``self``. + + The basic imaginary roots `\delta` are the set of imaginary roots + in `-C^{\vee}` where `C` is the dominant chamger (i.e., + `\langle \beta, \alpha_i^{\vee} \rangle \leq 0` for all `i \in I`). + All imaginary roots are `W`-conjugate to a simple imaginary root. + + EXAMPLES:: + + sage: RootSystem(['A', 2]).root_lattice().basic_imaginary_roots() + () + sage: Q = RootSystem(['A', 2, 1]).root_lattice() + sage: Q.basic_imaginary_roots() + (alpha[0] + alpha[1] + alpha[2],) + sage: delta = Q.basic_imaginary_roots()[0] + sage: all(delta.scalar(Q.simple_coroot(i)) <= 0 for i in Q.index_set()) + True + """ + if self.cartan_type().is_finite(): + return () + if self.cartan_type().is_affine(): + return (self.null_root(),) + raise ValueError("only implemented for finite and affine types") + @cached_method def simple_roots_tilde(self): r""" @@ -533,15 +561,14 @@ def simple_roots_tilde(self): def roots(self): """ - Returns the roots of self. + Return the roots of ``self``. EXAMPLES:: sage: RootSystem(['A',2]).ambient_lattice().roots() [(1, -1, 0), (1, 0, -1), (0, 1, -1), (-1, 1, 0), (-1, 0, 1), (0, -1, 1)] - - This matches with http://en.wikipedia.org/wiki/Root_systems:: + This matches with :wikipedia:`Root_systems`:: sage: for T in CartanType.samples(finite = True, crystallographic = True): ... print "%s %3s %3s"%(T, len(RootSystem(T).root_lattice().roots()), len(RootSystem(T).weight_lattice().roots())) @@ -560,36 +587,110 @@ def roots(self): ['F', 4] 48 48 ['G', 2] 12 12 - .. todo:: the result should be an enumerated set, and handle infinite root systems + .. TODO:: + + The result should be an enumerated set, and handle + infinite root systems. """ + if not self.cartan_type().is_finite(): + from sage.sets.disjoint_union_enumerated_sets \ + import DisjointUnionEnumeratedSets + D = DisjointUnionEnumeratedSets([self.positive_roots(), + self.negative_roots()]) + D.rename("All roots of type {}".format(self.cartan_type())) + return D + return list(self.positive_roots()) + list(self.negative_roots()) + def short_roots(self): + """ + Return a list of the short roots of ``self``. + + EXAMPLES:: + + sage: L = RootSystem(['B',3]).root_lattice() + sage: sorted(L.short_roots()) + [-alpha[1] - alpha[2] - alpha[3], + alpha[1] + alpha[2] + alpha[3], + -alpha[2] - alpha[3], + alpha[2] + alpha[3], + -alpha[3], + alpha[3]] + """ + if not self.cartan_type().is_finite(): + raise NotImplementedError("only implemented for finite Cartan types") + return filter(lambda x: x.is_short_root(), self.roots()) + + def long_roots(self): + """ + Return a list of the long roots of ``self``. + + EXAMPLES:: + + sage: L = RootSystem(['B',3]).root_lattice() + sage: sorted(L.long_roots()) + [-alpha[1], -alpha[1] - 2*alpha[2] - 2*alpha[3], + -alpha[1] - alpha[2], -alpha[1] - alpha[2] - 2*alpha[3], + alpha[1], alpha[1] + alpha[2], + alpha[1] + alpha[2] + 2*alpha[3], + alpha[1] + 2*alpha[2] + 2*alpha[3], -alpha[2], + -alpha[2] - 2*alpha[3], alpha[2], alpha[2] + 2*alpha[3]] + """ + if not self.cartan_type().is_finite(): + raise NotImplementedError("only implemented for finite Cartan types") + return filter(lambda x: x.is_long_root(), self.roots()) + @cached_method - def positive_roots(self, index_set = None): + def positive_roots(self, index_set=None): r""" Return the positive roots of ``self``. - If ``index_set`` is not None, returns the positive roots of the parabolic subsystem - with simple roots in ``index_set``. + If ``index_set`` is not ``None``, returns the positive roots of + the parabolic subsystem with simple roots in ``index_set``. + + Algorithm for finite type: generate them from the simple roots by + applying successive reflections toward the positive chamber. EXAMPLES:: sage: L = RootSystem(['A',3]).root_lattice() sage: sorted(L.positive_roots()) - [alpha[1], alpha[1] + alpha[2], alpha[1] + alpha[2] + alpha[3], alpha[2], alpha[2] + alpha[3], alpha[3]] + [alpha[1], alpha[1] + alpha[2], + alpha[1] + alpha[2] + alpha[3], alpha[2], + alpha[2] + alpha[3], alpha[3]] sage: sorted(L.positive_roots((1,2))) [alpha[1], alpha[1] + alpha[2], alpha[2]] sage: sorted(L.positive_roots(())) [] - Algorithm: generate them from the simple roots by applying - successive reflections toward the positive chamber. + sage: L = RootSystem(['A',3,1]).root_lattice() + sage: PR = L.positive_roots(); PR + Disjoint union of Family (Positive real roots of type ['A', 3, 1], + Positive imaginary roots of type ['A', 3, 1]) + sage: [PR.unrank(i) for i in range(10)] + [alpha[1], + alpha[2], + alpha[3], + alpha[1] + alpha[2], + alpha[2] + alpha[3], + alpha[1] + alpha[2] + alpha[3], + alpha[0] + 2*alpha[1] + alpha[2] + alpha[3], + alpha[0] + alpha[1] + 2*alpha[2] + alpha[3], + alpha[0] + alpha[1] + alpha[2] + 2*alpha[3], + alpha[0] + 2*alpha[1] + 2*alpha[2] + alpha[3]] """ + if self.cartan_type().is_affine(): + from sage.sets.disjoint_union_enumerated_sets \ + import DisjointUnionEnumeratedSets + return DisjointUnionEnumeratedSets([self.positive_real_roots(), + self.positive_imaginary_roots()]) if not self.cartan_type().is_finite(): - raise NotImplementedError("Only implemented for finite Cartan type") + raise NotImplementedError("Only implemented for finite and" + " affine Cartan types") if index_set is None: index_set = tuple(self.cartan_type().index_set()) - return TransitiveIdealGraded(attrcall('pred', index_set=index_set), [self.simple_root(i) for i in index_set]) + return TransitiveIdealGraded(attrcall('pred', index_set=index_set), + [self.simple_root(i) for i in index_set]) @cached_method def nonparabolic_positive_roots(self, index_set = None): @@ -639,6 +740,129 @@ def nonparabolic_positive_root_sum(self, index_set=None): """ return self.sum(self.nonparabolic_positive_roots(index_set)) + def positive_real_roots(self): + """ + Return the positive real roots of ``self``. + + EXAMPLES:: + + sage: L = RootSystem(['A',3]).root_lattice() + sage: sorted(L.positive_real_roots()) + [alpha[1], alpha[1] + alpha[2], alpha[1] + alpha[2] + alpha[3], + alpha[2], alpha[2] + alpha[3], alpha[3]] + + sage: L = RootSystem(['A',3,1]).root_lattice() + sage: PRR = L.positive_real_roots(); PRR + Positive real roots of type ['A', 3, 1] + sage: [PRR.unrank(i) for i in range(10)] + [alpha[1], + alpha[2], + alpha[3], + alpha[1] + alpha[2], + alpha[2] + alpha[3], + alpha[1] + alpha[2] + alpha[3], + alpha[0] + 2*alpha[1] + alpha[2] + alpha[3], + alpha[0] + alpha[1] + 2*alpha[2] + alpha[3], + alpha[0] + alpha[1] + alpha[2] + 2*alpha[3], + alpha[0] + 2*alpha[1] + 2*alpha[2] + alpha[3]] + + sage: Q = RootSystem(['A',4,2]).root_lattice() + sage: PR = Q.positive_roots() + sage: [PR.unrank(i) for i in range(5)] + [alpha[1], + alpha[2], + 2*alpha[1] + alpha[2], + alpha[1] + alpha[2], + alpha[0] + alpha[1] + alpha[2]] + + sage: Q = RootSystem(['D',3,2]).root_lattice() + sage: PR = Q.positive_roots() + sage: [PR.unrank(i) for i in range(5)] + [alpha[1], + alpha[2], + alpha[1] + 2*alpha[2], + alpha[1] + alpha[2], + alpha[0] + alpha[1] + 2*alpha[2]] + """ + if self.cartan_type().is_finite(): + return tuple(TransitiveIdealGraded(attrcall('pred'), self.simple_roots())) + 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.combinat.root_system.root_system import RootSystem + from sage.sets.positive_integers import PositiveIntegers + from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets + + Q = RootSystem(self.cartan_type().classical()).root_space(self.base_ring()) + + # Start with the classical positive roots + alpha = self.simple_roots() + def lift(x): + """ + Lift up the classical element into ``self``. + """ + return self.sum(c*alpha[i] for i,c in x) + P = Family(Q.positive_real_roots(), lift) + + # Add all of the delta shifts + delta = self.null_root() + if self.cartan_type().is_untwisted_affine(): + C = CartesianProduct(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()) + 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()) + Fs = Family(Cs, lambda x: lift(x[1]) + x[0]*delta) + if self.cartan_type().dual() == 'G': # D_4^3 + k = 3 + else: + k = 2 + Fl = Family(Cl, lambda x: lift(x[1]) + x[0]*k*delta) + D = DisjointUnionEnumeratedSets([P, Fs, Fl]) + + # Return the final union + D.rename("Positive real roots of type {}".format(self.cartan_type())) + return D + + def positive_imaginary_roots(self): + """ + Return the positive imaginary roots of ``self``. + + EXAMPLES:: + + sage: L = RootSystem(['A',3]).root_lattice() + sage: L.positive_imaginary_roots() + () + + sage: L = RootSystem(['A',3,1]).root_lattice() + sage: PIR = L.positive_imaginary_roots(); PIR + Positive imaginary roots of type ['A', 3, 1] + sage: [PIR.unrank(i) for i in range(5)] + [alpha[0] + alpha[1] + alpha[2] + alpha[3], + 2*alpha[0] + 2*alpha[1] + 2*alpha[2] + 2*alpha[3], + 3*alpha[0] + 3*alpha[1] + 3*alpha[2] + 3*alpha[3], + 4*alpha[0] + 4*alpha[1] + 4*alpha[2] + 4*alpha[3], + 5*alpha[0] + 5*alpha[1] + 5*alpha[2] + 5*alpha[3]] + """ + if self.cartan_type().is_finite(): + return () + if not self.cartan_type().is_affine(): + raise NotImplementedError("only implemented for finite and affine Cartan types") + from sage.sets.positive_integers import PositiveIntegers + delta = self.null_root() + F = Family(PositiveIntegers(), lambda x: x*delta) + F.rename("Positive imaginary roots of type {}".format(self.cartan_type())) + return F + @cached_method def positive_roots_by_height(self, increasing = True): r""" @@ -695,7 +919,7 @@ def positive_roots_parabolic(self, index_set = None): sage: sorted(lattice.positive_roots_parabolic(), key=str) [alpha[1], alpha[1] + alpha[2], alpha[1] + alpha[2] + alpha[3], alpha[2], alpha[2] + alpha[3], alpha[3]] - .. warning:: + .. WARNING:: This returns an error if the Cartan type is not finite. """ @@ -731,7 +955,7 @@ def positive_roots_nonparabolic(self, index_set = None): sage: lattice.positive_roots_nonparabolic((1,2,3)) [] - .. warning:: + .. WARNING:: This returns an error if the Cartan type is not finite. @@ -765,7 +989,7 @@ def positive_roots_nonparabolic_sum(self, index_set = None): sage: lattice.positive_roots_nonparabolic_sum((1,2,3)) 0 - .. warning:: + .. WARNING:: This returns an error if the Cartan type is not finite. @@ -2343,8 +2567,11 @@ def plot_alcoves(self, alcoves=True, alcove_labels=False, wireframe=False, **opt Line defined by 2 points: [(1.0, -1.0), (0.0, -1.0)] Line defined by 2 points: [(1.0, 0.0), (0.0, 0.0)] Line defined by 2 points: [(1.0, 0.0), (1.0, -1.0)] - sage: [(line.options()['rgbcolor'], line.options()['thickness']) for line in p] - [('black', 2), ('blue', 1), ('red', 1), ('black', 2), ('black', 2), ('blue', 1), ('black', 2), ('red', 1), ('black', 2), ('red', 1), ('black', 2), ('blue', 1)] + sage: sorted((line.options()['rgbcolor'], line.options()['thickness']) for line in p) + [('black', 2), ('black', 2), ('black', 2), + ('black', 2), ('black', 2), ('black', 2), + ('blue', 1), ('blue', 1), ('blue', 1), + ('red', 1), ('red', 1), ('red', 1)] """ plot_options = self.plot_parse_options(**options) if not hasattr(self, "fundamental_weights"): @@ -2662,6 +2889,82 @@ def scalar(self, lambdacheck): NotImplementedError: """ + def symmetric_form(self, alpha): + r""" + Return the symmetric form of ``self`` with ``alpha``. + + Consider the simple roots `\alpha_i` and let `(b_{ij})_{ij}` + denote the symmetrized Cartan matrix `(a_{ij})_{ij}`, we have + + .. MATH:: + + (\alpha_i | \alpha_j) = b_{ij} + + and extended bilinearly. See Chapter 6 in Kac, Infinite + Dimensional Lie Algebras for more details. + + EXAMPLES:: + + sage: Q = RootSystem(['B',2,1]).root_lattice() + sage: alpha = Q.simple_roots() + sage: alpha[1].symmetric_form(alpha[0]) + 0 + sage: alpha[1].symmetric_form(alpha[1]) + 4 + sage: elt = alpha[0] - 3*alpha[1] + alpha[2] + sage: elt.symmetric_form(alpha[1]) + -14 + sage: elt.symmetric_form(alpha[0]+2*alpha[2]) + 14 + sage: Q = RootSystem(CartanType(['A',4,2]).dual()).root_lattice() + sage: Qc = RootSystem(['A',4,2]).coroot_lattice() + sage: alpha = Q.simple_roots() + sage: alphac = Qc.simple_roots() + sage: elt = alpha[0] + 2*alpha[1] + 2*alpha[2] + sage: eltc = alphac[0] + 2*alphac[1] + 2*alphac[2] + sage: elt.symmetric_form(alpha[1]) + 0 + sage: eltc.symmetric_form(alphac[1]) + 0 + """ + cm = self.parent().dynkin_diagram().cartan_matrix() + sym = cm.symmetrized_matrix() + iset = self.parent().index_set() + return sum(cl*sym[iset.index(ml),iset.index(mr)]*cr + for ml,cl in self for mr,cr in alpha) + + def norm_squared(self): + """ + Return the norm squared of ``self`` with respect to the + symmetric form. + + EXAMPLES:: + + sage: Q = RootSystem(['B',2,1]).root_lattice() + sage: alpha = Q.simple_roots() + sage: alpha[1].norm_squared() + 4 + sage: alpha[2].norm_squared() + 2 + sage: elt = alpha[0] - 3*alpha[1] + alpha[2] + sage: elt.norm_squared() + 50 + sage: elt = alpha[0] + alpha[1] + 2*alpha[2] + sage: elt.norm_squared() + 0 + sage: Q = RootSystem(CartanType(['A',4,2]).dual()).root_lattice() + sage: Qc = RootSystem(['A',4,2]).coroot_lattice() + sage: alpha = Q.simple_roots() + sage: alphac = Qc.simple_roots() + sage: elt = alpha[0] + 2*alpha[1] + 2*alpha[2] + sage: eltc = alphac[0] + 2*alphac[1] + 2*alphac[2] + sage: elt.norm_squared() + 0 + sage: eltc.norm_squared() + 0 + """ + return self.symmetric_form(self) + ########################################################################## # Action and orbits w.r.t. the Weyl group ########################################################################## @@ -3419,7 +3722,7 @@ def is_parabolic_root(self, index_set): def is_short_root(self): r""" - Is ``self`` a short root? + Return ``True`` if ``self`` is a short (real) root. Returns False unless the parent is an irreducible root system of finite type having two root lengths and ``self`` is of the shorter length. @@ -3439,12 +3742,23 @@ def is_short_root(self): sage: RootSystem(['A',2]).root_lattice().simple_root(1).is_short_root() False + An example in affine type:: + + sage: Q = RootSystem(['B',2,1]).root_lattice() + sage: alpha = Q.simple_roots() + sage: alpha[0].is_short_root() + False + sage: alpha[1].is_short_root() + False + sage: alpha[2].is_short_root() + True """ ct = self.parent().cartan_type() if not ct.is_irreducible(): raise ValueError("Cartan type needs to be irreducible!") if not ct.is_finite(): - raise NotImplementedError("Implemented only for irreducible finite root systems") + return self.norm_squared() == min(alpha.norm_squared() + for alpha in self.parent().simple_roots()) L = self.parent().root_system.ambient_space() # uses peculiarities of ambient embedding ls = L(self) return ls.scalar(ls) < L._maximum_root_length() @@ -3462,3 +3776,62 @@ def is_short_root(self): #if ct.type() == 'C' or ct.type() == 'G': # return True #return False + + def is_long_root(self): + """ + Return ``True`` if ``self`` is a long (real) root. + + EXAMPLES:: + + sage: Q = RootSystem(['B',2,1]).root_lattice() + sage: alpha = Q.simple_roots() + sage: alpha[0].is_long_root() + True + sage: alpha[1].is_long_root() + True + sage: alpha[2].is_long_root() + False + """ + alpha = self.parent().simple_roots() + norm_sq = self.norm_squared() + return max(sroot.norm_squared() for sroot in alpha) == norm_sq \ + and all(c * alpha[i].norm_squared() / norm_sq in ZZ for i,c in self) + + def is_imaginary_root(self): + r""" + Return ``True`` if ``self`` is an imaginary root. + + A root `\alpha` is imaginary if it is not `W` conjugate + to a simple root where `W` is the corresponding Weyl group. + + EXAMPLES:: + + sage: Q = RootSystem(['B',2,1]).root_lattice() + sage: alpha = Q.simple_roots() + sage: alpha[0].is_imaginary_root() + False + sage: elt = alpha[0] + alpha[1] + 2*alpha[2] + sage: elt.is_imaginary_root() + True + """ + return self.norm_squared() <= 0 + + def is_real_root(self): + r""" + Return ``True`` if ``self`` is a real root. + + A root `\alpha` is real if it is `W` conjugate to a simple + root where `W` is the corresponding Weyl group. + + EXAMPLES:: + + sage: Q = RootSystem(['B',2,1]).root_lattice() + sage: alpha = Q.simple_roots() + sage: alpha[0].is_real_root() + True + sage: elt = alpha[0] + alpha[1] + 2*alpha[2] + sage: elt.is_real_root() + False + """ + return self.norm_squared() > 0 + diff --git a/src/sage/combinat/root_system/type_A.py b/src/sage/combinat/root_system/type_A.py index 5ffbc800663..b9acbcf5ac0 100644 --- a/src/sage/combinat/root_system/type_A.py +++ b/src/sage/combinat/root_system/type_A.py @@ -219,6 +219,28 @@ def _latex_(self): AmbientSpace = AmbientSpace + def coxeter_number(self): + """ + Return the Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['A',4]).coxeter_number() + 5 + """ + return self.n + 1 + + def dual_coxeter_number(self): + """ + Return the dual Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['A',4]).dual_coxeter_number() + 5 + """ + return self.n + 1 + def dynkin_diagram(self): """ Returns the Dynkin diagram of type A. diff --git a/src/sage/combinat/root_system/type_B.py b/src/sage/combinat/root_system/type_B.py index d4d76252c7e..f3a8fc2ac87 100644 --- a/src/sage/combinat/root_system/type_B.py +++ b/src/sage/combinat/root_system/type_B.py @@ -178,6 +178,28 @@ def _latex_(self): AmbientSpace = AmbientSpace + def coxeter_number(self): + """ + Return the Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['B',4]).coxeter_number() + 8 + """ + return 2*self.n + + def dual_coxeter_number(self): + """ + Return the dual Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['B',4]).dual_coxeter_number() + 7 + """ + return 2*self.n - 1 + def dual(self): """ Types B and C are in duality: diff --git a/src/sage/combinat/root_system/type_BC_affine.py b/src/sage/combinat/root_system/type_BC_affine.py index 30b76459a10..63f41b3949b 100644 --- a/src/sage/combinat/root_system/type_BC_affine.py +++ b/src/sage/combinat/root_system/type_BC_affine.py @@ -249,6 +249,27 @@ def classical(self): import cartan_type return cartan_type.CartanType(["C", self.n]) + def basic_untwisted(self): + r""" + Return the basic untwisted Cartan type associated with this affine + Cartan type. + + Given an affine type `X_n^{(r)}`, the basic untwisted type is `X_n`. + In other words, it is the classical Cartan type that is twisted to + obtain ``self``. + + EXAMPLES:: + + sage: CartanType(['A', 2, 2]).basic_untwisted() + ['A', 2] + sage: CartanType(['A', 4, 2]).basic_untwisted() + ['A', 4] + sage: CartanType(['BC', 4, 2]).basic_untwisted() + ['A', 8] + """ + import cartan_type + return cartan_type.CartanType(["A", 2*self.n]) + def _default_folded_cartan_type(self): """ Return the default folded Cartan type. diff --git a/src/sage/combinat/root_system/type_C.py b/src/sage/combinat/root_system/type_C.py index de09df26a38..d4f436b01cb 100644 --- a/src/sage/combinat/root_system/type_C.py +++ b/src/sage/combinat/root_system/type_C.py @@ -172,6 +172,28 @@ def _latex_(self): AmbientSpace = AmbientSpace + def coxeter_number(self): + """ + Return the Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['C',4]).coxeter_number() + 8 + """ + return 2*self.n + + def dual_coxeter_number(self): + """ + Return the dual Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['C',4]).dual_coxeter_number() + 5 + """ + return self.n + 1 + def dual(self): """ Types B and C are in duality: diff --git a/src/sage/combinat/root_system/type_D.py b/src/sage/combinat/root_system/type_D.py index 9ec3c98aaff..176c41c7930 100644 --- a/src/sage/combinat/root_system/type_D.py +++ b/src/sage/combinat/root_system/type_D.py @@ -195,6 +195,28 @@ def is_atomic(self): """ return True + def coxeter_number(self): + """ + Return the Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['D',4]).coxeter_number() + 6 + """ + return 2*self.n - 2 + + def dual_coxeter_number(self): + """ + Return the dual Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['D',4]).dual_coxeter_number() + 6 + """ + return 2*self.n - 2 + @cached_method def dynkin_diagram(self): """ diff --git a/src/sage/combinat/root_system/type_E.py b/src/sage/combinat/root_system/type_E.py index aff9e975fd9..f400f06f52f 100644 --- a/src/sage/combinat/root_system/type_E.py +++ b/src/sage/combinat/root_system/type_E.py @@ -479,6 +479,46 @@ def _latex_(self): AmbientSpace = AmbientSpace + def coxeter_number(self): + """ + Return the Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['E',6]).coxeter_number() + 12 + sage: CartanType(['E',7]).coxeter_number() + 18 + sage: CartanType(['E',8]).coxeter_number() + 30 + """ + if self.n == 6: + return 12 + if self.n == 7: + return 18 + # n == 8 + return 30 + + def dual_coxeter_number(self): + """ + Return the dual Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['E',6]).dual_coxeter_number() + 12 + sage: CartanType(['E',7]).dual_coxeter_number() + 18 + sage: CartanType(['E',8]).dual_coxeter_number() + 30 + """ + if self.n == 6: + return 12 + if self.n == 7: + return 18 + # n == 8 + return 30 + def dynkin_diagram(self): """ Returns a Dynkin diagram for type E. diff --git a/src/sage/combinat/root_system/type_F.py b/src/sage/combinat/root_system/type_F.py index c6ba0520681..e122115c7e3 100644 --- a/src/sage/combinat/root_system/type_F.py +++ b/src/sage/combinat/root_system/type_F.py @@ -243,6 +243,28 @@ def _latex_(self): AmbientSpace = AmbientSpace + def coxeter_number(self): + """ + Return the Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['F',4]).coxeter_number() + 12 + """ + return 12 + + def dual_coxeter_number(self): + """ + Return the dual Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['F',4]).dual_coxeter_number() + 9 + """ + return 9 + def dynkin_diagram(self): """ Returns a Dynkin diagram for type F. diff --git a/src/sage/combinat/root_system/type_G.py b/src/sage/combinat/root_system/type_G.py index 681d10dfc8b..4556a2d5d07 100644 --- a/src/sage/combinat/root_system/type_G.py +++ b/src/sage/combinat/root_system/type_G.py @@ -152,6 +152,28 @@ def _latex_(self): AmbientSpace = AmbientSpace + def coxeter_number(self): + """ + Return the Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['G',2]).coxeter_number() + 6 + """ + return 6 + + def dual_coxeter_number(self): + """ + Return the dual Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['G',2]).dual_coxeter_number() + 4 + """ + return 4 + def dynkin_diagram(self): """ Returns a Dynkin diagram for type G. diff --git a/src/sage/combinat/root_system/type_H.py b/src/sage/combinat/root_system/type_H.py index c1ba2c98a3a..7df084830b5 100644 --- a/src/sage/combinat/root_system/type_H.py +++ b/src/sage/combinat/root_system/type_H.py @@ -74,3 +74,19 @@ def coxeter_diagram(self): g.add_edge(i, i+1, 3) g.set_edge_label(n-1, n, 5) return g + + def coxeter_number(self): + """ + Return the Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['H',3]).coxeter_number() + 10 + sage: CartanType(['H',4]).coxeter_number() + 30 + """ + if self.n == 3: + return 10 + return 30 + diff --git a/src/sage/combinat/root_system/type_I.py b/src/sage/combinat/root_system/type_I.py index 586e5baa597..ea8a7cdeb6f 100644 --- a/src/sage/combinat/root_system/type_I.py +++ b/src/sage/combinat/root_system/type_I.py @@ -79,3 +79,16 @@ def coxeter_diagram(self): """ from sage.graphs.graph import Graph return Graph([[1,2,self.n]], multiedges=False) + + def coxeter_number(self): + """ + Return the Coxeter number associated with ``self``. + + EXAMPLES:: + + sage: CartanType(['I',3]).coxeter_number() + 3 + sage: CartanType(['I',12]).coxeter_number() + 12 + """ + return self.n diff --git a/src/sage/combinat/root_system/type_dual.py b/src/sage/combinat/root_system/type_dual.py index a6c40ac88e6..05f0ade2b98 100644 --- a/src/sage/combinat/root_system/type_dual.py +++ b/src/sage/combinat/root_system/type_dual.py @@ -525,6 +525,36 @@ def classical(self): """ return self.dual().classical().dual() + def basic_untwisted(self): + r""" + Return the basic untwisted Cartan type associated with this affine + Cartan type. + + Given an affine type `X_n^{(r)}`, the basic untwisted type is `X_n`. + In other words, it is the classical Cartan type that is twisted to + obtain ``self``. + + EXAMPLES:: + + sage: CartanType(['A', 7, 2]).basic_untwisted() + ['A', 7] + sage: CartanType(['E', 6, 2]).basic_untwisted() + ['E', 6] + sage: CartanType(['D', 4, 3]).basic_untwisted() + ['D', 4] + """ + import cartan_type + if self.dual().type() == 'B': + return cartan_type.CartanType(['A', self.classical().rank()*2-1]) + elif self.dual().type() == 'BC': + return cartan_type.CartanType(['A', self.classical().rank()*2]) + elif self.dual().type() == 'C': + return cartan_type.CartanType(['D', self.classical().rank()+1]) + elif self.dual().type() == 'F': + return cartan_type.CartanType(['E', 6]) + elif self.dual().type() == 'G': + return cartan_type.CartanType(['D', 4]) + def special_node(self): """ Implement :meth:`CartanType_affine.special_node` diff --git a/src/sage/combinat/root_system/type_relabel.py b/src/sage/combinat/root_system/type_relabel.py index e8c50e8b798..e7e2da78ebc 100644 --- a/src/sage/combinat/root_system/type_relabel.py +++ b/src/sage/combinat/root_system/type_relabel.py @@ -681,6 +681,23 @@ def classical(self): """ return self._type.classical().relabel(self._relabelling) + def basic_untwisted(self): + r""" + Return the basic untwisted Cartan type associated with this affine + Cartan type. + + Given an affine type `X_n^{(r)}`, the basic untwisted type is `X_n`. + In other words, it is the classical Cartan type that is twisted to + obtain ``self``. + + EXAMPLES:: + + sage: ct = CartanType(['A', 5, 2]).relabel({0:1, 1:0, 2:2, 3:3}) + sage: ct.basic_untwisted() + ['A', 5] + """ + return self._type.basic_untwisted() + def special_node(self): r""" Returns a special node of the Dynkin diagram diff --git a/src/sage/combinat/root_system/weight_lattice_realizations.py b/src/sage/combinat/root_system/weight_lattice_realizations.py index 56ac10c8677..19f9a99bbd0 100644 --- a/src/sage/combinat/root_system/weight_lattice_realizations.py +++ b/src/sage/combinat/root_system/weight_lattice_realizations.py @@ -20,6 +20,7 @@ from sage.misc.abstract_method import abstract_method from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute from sage.misc.misc import prod from sage.categories.category_types import Category_over_base_ring from sage.combinat.family import Family @@ -870,3 +871,161 @@ def weyl_dimension(self, highest_weight): d = prod([ rho.dot_product(x) for x in self.positive_roots()]) from sage.rings.integer import Integer return Integer(n/d) + + @lazy_attribute + def _symmetric_form_matrix(self): + r""" + Return the matrix for the symmetric form `( | )` in + the weight lattice basis. + + Let `A` be a symmetrizable Cartan matrix with symmetrizer `D`,. + This returns the matrix `M^t DA M`, where `M` is dependent upon + the type given below. + + In finite types, `M` is the inverse of the Cartan matrix. + + In affine types, `M` takes the basis + `(\Lambda_0, \Lambda_1, \ldots, \Lambda_r, \delta)` to + `(\alpha_0, \ldots, \alpha_r, \Lambda_0)` where `r` is the + rank of ``self``. + + This is used in computing the symmetric form for affine + root systems. + + EXAMPLES:: + + sage: P = RootSystem(['C',2]).weight_lattice() + sage: P._symmetric_form_matrix + [1 1] + [1 2] + + sage: P = RootSystem(['C',2,1]).weight_lattice() + sage: P._symmetric_form_matrix + [0 0 0 1] + [0 1 1 1] + [0 1 2 1] + [1 1 1 0] + + sage: P = RootSystem(['A',4,2]).weight_lattice() + sage: P._symmetric_form_matrix + [ 0 0 0 1/2] + [ 0 2 2 1] + [ 0 2 4 1] + [1/2 1 1 0] + """ + from sage.matrix.constructor import matrix + ct = self.cartan_type() + cm = ct.cartan_matrix() + if cm.det() != 0: + cm_inv = cm.inverse() + diag = cm.is_symmetrizable(True) + return cm_inv.transpose() * matrix.diagonal(diag) + + if not ct.is_affine(): + raise ValueError("only implemented for affine types when the" + " Cartan matrix is singular") + + r = ct.rank() + a = ct.a() + # Determine the change of basis matrix + # La[0], ..., La[r], delta -> al[0], ..., al[r], La[0] + M = cm.stack( matrix([1] + [0]*(r-1)) ) + M = matrix.block([[ M, matrix([[1]] + [[0]]*r) ]]) + M = M.inverse() + + if a[0] != 1: + from sage.rings.all import QQ + S = matrix([~a[0]]+[0]*(r-1)) + A = cm.symmetrized_matrix().change_ring(QQ).stack(S) + else: + A = cm.symmetrized_matrix().stack(matrix([1]+[0]*(r-1))) + A = matrix.block([[A, matrix([[~a[0]]] + [[0]]*r)]]) + return M.transpose() * A * M + + class ElementMethods: + def symmetric_form(self, la): + r""" + Return the symmetric form of ``self`` with ``la``. + + Return the pairing `( | )` on the weight lattice. See Chapter 6 + in Kac, Infinite Dimensional Lie Algebras for more details. + + .. WARNING:: + + For affine root systems, if you are not working in the + extended weight lattice/space, this may return incorrect + results. + + EXAMPLES:: + + sage: P = RootSystem(['C',2]).weight_lattice() + sage: al = P.simple_roots() + sage: al[1].symmetric_form(al[1]) + 2 + sage: al[1].symmetric_form(al[2]) + -2 + sage: al[2].symmetric_form(al[1]) + -2 + sage: Q = RootSystem(['C',2]).root_lattice() + sage: alQ = Q.simple_roots() + sage: all(al[i].symmetric_form(al[j]) == alQ[i].symmetric_form(alQ[j]) + ....: for i in P.index_set() for j in P.index_set()) + True + + sage: P = RootSystem(['C',2,1]).weight_lattice(extended=True) + sage: al = P.simple_roots() + sage: al[1].symmetric_form(al[1]) + 2 + sage: al[1].symmetric_form(al[2]) + -2 + sage: al[1].symmetric_form(al[0]) + -2 + sage: al[0].symmetric_form(al[1]) + -2 + sage: Q = RootSystem(['C',2,1]).root_lattice() + sage: alQ = Q.simple_roots() + sage: all(al[i].symmetric_form(al[j]) == alQ[i].symmetric_form(alQ[j]) + ....: for i in P.index_set() for j in P.index_set()) + True + sage: La = P.basis() + sage: [La['delta'].symmetric_form(al) for al in P.simple_roots()] + [0, 0, 0] + sage: [La[0].symmetric_form(al) for al in P.simple_roots()] + [1, 0, 0] + + sage: P = RootSystem(['C',2,1]).weight_lattice() + sage: Q = RootSystem(['C',2,1]).root_lattice() + sage: al = P.simple_roots() + sage: alQ = Q.simple_roots() + sage: all(al[i].symmetric_form(al[j]) == alQ[i].symmetric_form(alQ[j]) + ....: for i in P.index_set() for j in P.index_set()) + True + + The result of `(\Lambda_0 | \alpha_0)` should be `1`, however we + get `0` because we are not working in the extended weight + lattice:: + + sage: La = P.basis() + sage: [La[0].symmetric_form(al) for al in P.simple_roots()] + [0, 0, 0] + + TESTS: + + We check that `A_{2n}^{(2)}` has 3 different root lengths:: + + sage: P = RootSystem(['A',4,2]).weight_lattice() + sage: al = P.simple_roots() + sage: [al[i].symmetric_form(al[i]) for i in P.index_set()] + [2, 4, 8] + """ + P = self.parent() + ct = P.cartan_type() + sym = P._symmetric_form_matrix + + if ct.is_finite(): + iset = P.index_set() + else: + iset = P.index_set() + ('delta',) + + return sum(cl*sym[iset.index(ml),iset.index(mr)]*cr + for ml,cl in self for mr,cr in la) diff --git a/src/sage/combinat/rsk.py b/src/sage/combinat/rsk.py index 6a26d227597..46373fa9e14 100644 --- a/src/sage/combinat/rsk.py +++ b/src/sage/combinat/rsk.py @@ -129,7 +129,7 @@ def RSK(obj1=None, obj2=None, insertion='RSK', check_standard=False, **options): This correspondence has been introduced in [Knu1970]_, where it has been referred to as "Construction A". - For more information, see Chapter 7 in [Sta1999]_. + For more information, see Chapter 7 in [Sta-EC2]_. We also note that integer matrices are in bijection with generalized permutations. In addition, we can also convert any word `w` (and any diff --git a/src/sage/combinat/set_partition.py b/src/sage/combinat/set_partition.py index 12acf7dd4e8..19734a34bdd 100644 --- a/src/sage/combinat/set_partition.py +++ b/src/sage/combinat/set_partition.py @@ -46,6 +46,7 @@ from sage.combinat.set_partition_ordered import OrderedSetPartitions from sage.combinat.combinat import bell_number, stirling_number2 from sage.combinat.permutation import Permutation +from functools import reduce class SetPartition(ClonableArray): """ diff --git a/src/sage/combinat/set_partition_ordered.py b/src/sage/combinat/set_partition_ordered.py index 1c9bd40ae8c..0f0dbd63a91 100644 --- a/src/sage/combinat/set_partition_ordered.py +++ b/src/sage/combinat/set_partition_ordered.py @@ -39,6 +39,7 @@ from sage.combinat.composition import Composition, Compositions from sage.combinat.words.word import Word import sage.combinat.permutation as permutation +from functools import reduce class OrderedSetPartition(ClonableArray): """ diff --git a/src/sage/combinat/sf/jack.py b/src/sage/combinat/sf/jack.py index efa807c0294..440acbdc816 100644 --- a/src/sage/combinat/sf/jack.py +++ b/src/sage/combinat/sf/jack.py @@ -478,8 +478,8 @@ def normalize_coefficients(self, c): b = gcd([i.numerator() for i in numer.coeffs()]) l = Integer(a).gcd(Integer(b)) - denom = denom / l - numer = numer / l + denom = denom // l + numer = numer // l return c.parent()(numer, denom) else: diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 9deec1f8610..bcae7864d57 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -222,6 +222,7 @@ from sage.matrix.constructor import matrix from sage.misc.misc import prod, uniq from copy import copy +from functools import reduce def SymmetricFunctionAlgebra(R, basis="schur"): diff --git a/src/sage/combinat/similarity_class_type.py b/src/sage/combinat/similarity_class_type.py index aa3c8d3144e..ab7e2fed0f2 100644 --- a/src/sage/combinat/similarity_class_type.py +++ b/src/sage/combinat/similarity_class_type.py @@ -191,6 +191,7 @@ class type, it is also possible to compute the number of classes of that type 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 @cached_function def fq(n, q = None): diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index c9e59e6519f..769cc7b3565 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -1571,7 +1571,7 @@ def cardinality(self): """ Return the number of standard skew tableaux with shape of the skew partition ``skp``. This uses a formula due to Aitken - (see Cor. 7.16.3 of [Sta1999]_). + (see Cor. 7.16.3 of [Sta-EC2]_). EXAMPLES:: diff --git a/src/sage/combinat/sloane_functions.py b/src/sage/combinat/sloane_functions.py index c6ff19accae..a8b47f12906 100644 --- a/src/sage/combinat/sloane_functions.py +++ b/src/sage/combinat/sloane_functions.py @@ -9426,7 +9426,7 @@ def _eval(self, n): else: for d in srange(3,n,2): if n % d == 0: - return min(d, 2*n/d) + return min(d, 2*n//d) class ExponentialNumbers(SloaneSequence): diff --git a/src/sage/combinat/species/generating_series.py b/src/sage/combinat/species/generating_series.py index 305e0194dc6..f523c95aefe 100644 --- a/src/sage/combinat/species/generating_series.py +++ b/src/sage/combinat/species/generating_series.py @@ -41,9 +41,9 @@ :: sage: def g(): - ... for i in _integers_from(0): - ... yield p([2])^i - ... yield p(0) + ....: for i in _integers_from(0): + ....: yield p([2])^i + ....: yield p(0) sage: geo1 = CIS((p([1])^i for i in _integers_from(0))) sage: geo2 = CIS(g()) sage: s = geo1 * geo2 @@ -80,11 +80,14 @@ from sage.combinat.sf.sf import SymmetricFunctions from sage.misc.cachefunc import cached_function + @cached_function def OrdinaryGeneratingSeriesRing(R): """ - Returns the ring of ordinary generating series. Note that is is - just a LazyPowerSeriesRing whose elements have some extra methods. + Returns the ring of ordinary generating series. + + Note that is is just a LazyPowerSeriesRing whose elements have + some extra methods. EXAMPLES:: @@ -105,6 +108,7 @@ def OrdinaryGeneratingSeriesRing(R): """ return OrdinaryGeneratingSeriesRing_class(R) + class OrdinaryGeneratingSeriesRing_class(LazyPowerSeriesRing): def __init__(self, R): """ @@ -117,6 +121,7 @@ def __init__(self, R): """ LazyPowerSeriesRing.__init__(self, R, OrdinaryGeneratingSeries) + class OrdinaryGeneratingSeries(LazyPowerSeries): def count(self, n): """ @@ -173,6 +178,7 @@ def ExponentialGeneratingSeriesRing(R): """ return ExponentialGeneratingSeriesRing_class(R) + class ExponentialGeneratingSeriesRing_class(LazyPowerSeriesRing): def __init__(self, R): """ @@ -229,11 +235,9 @@ def functorial_composition(self, y): f \Box g = \sum_{n=0}^{\infty} f_{g_n} \frac{x^n}{n!} - - REFERENCES: - - Section 2.2 of BLL. + - Section 2.2 of [BLL]_. EXAMPLES:: @@ -313,6 +317,7 @@ def CycleIndexSeriesRing(R): """ return CycleIndexSeriesRing_class(R) + class CycleIndexSeriesRing_class(LazyPowerSeriesRing): def __init__(self, R): """ @@ -538,6 +543,7 @@ def _egs_gen(self, ao): def __invert__(self): """ Return the multiplicative inverse of self. + This algorithm is derived from [BLL]_. EXAMPLES:: @@ -890,8 +896,9 @@ def weighted_composition(self, y_species): """ Returns the composition of this cycle index series with the cycle index series of y_species where y_species is a weighted species. + Note that this is basically the same algorithm as composition - except we can't use the optimization that the powering of cycle + except we can not use the optimization that the powering of cycle index series commutes with 'stretching'. EXAMPLES:: @@ -969,5 +976,69 @@ def _weighted_compose_term(self, p, y_species): return parent.sum(res) + def compositional_inverse(self): + r""" + Return the compositional inverse of ``self`` if possible. + + (Specifically, if ``self`` is of the form `0 + p_{1} + \dots`.) + + The compositional inverse is the inverse with respect to + plethystic substitution. This is the operation on cycle index + series which corresponds to substitution, a.k.a. partitional + composition, on the level of species. See Section 2.2 of + [BLL]_ for a definition of this operation. + + EXAMPLES:: + + sage: Eplus = species.SetSpecies(min=1).cycle_index_series() + sage: Eplus(Eplus.compositional_inverse()).coefficients(8) + [0, p[1], 0, 0, 0, 0, 0, 0] + + TESTS:: + + sage: Eplus = species.SetSpecies(min=2).cycle_index_series() + sage: Eplus.compositional_inverse() + Traceback (most recent call last): + ... + ValueError: not an invertible series + + ALGORITHM: + + Let `F` be a species satisfying `F = 0 + X + F_2 + F_3 + \dots` for `X` the species of singletons. + (Equivalently, `\lvert F[\varnothing] \rvert = 0` and `\lvert F[\{1\}] \rvert = 1`.) + Then there exists a (virtual) species `G` satisfying `F \circ G = G \circ F = X`. + + It follows that `(F - X) \circ G = F \circ G - X \circ G = X - G`. + Rearranging, we obtain the recursive equation `G = X - (F - X) \circ G`, which can be + solved using iterative methods. + + .. WARNING:: + + This algorithm is functional but can be very slow. + Use with caution! + + .. SEEALSO:: + + The compositional inverse `\Omega` of the species `E_{+}` + of nonempty sets can be handled much more efficiently + using specialized methods. These are implemented in + :class:`~sage.combinat.species.combinatorial_logarithm.CombinatorialLogarithmSeries`. + + AUTHORS: + + - Andrew Gainer-Dewar + """ + cisr = self.parent() + sfa = cisr._base + + X = cisr([0, sfa([1]), 0]) + + if self.coefficients(2) != X.coefficients(2): + raise ValueError('not an invertible series') + + res = cisr() + res.define(X - (self - X).compose(res)) + + return res diff --git a/src/sage/combinat/species/partition_species.py b/src/sage/combinat/species/partition_species.py index eced77bc240..a7cada1fef4 100644 --- a/src/sage/combinat/species/partition_species.py +++ b/src/sage/combinat/species/partition_species.py @@ -25,6 +25,7 @@ from sage.rings.all import ZZ from sage.misc.cachefunc import cached_function from sage.combinat.species.misc import accept_size +from functools import reduce class PartitionSpeciesStructure(GenericSpeciesStructure): def __init__(self, parent, labels, list): diff --git a/src/sage/combinat/species/species.py b/src/sage/combinat/species/species.py index 808e3a82051..5402f3af291 100644 --- a/src/sage/combinat/species/species.py +++ b/src/sage/combinat/species/species.py @@ -56,6 +56,7 @@ from sage.misc.cachefunc import cached_method from sage.combinat.species.misc import accept_size from sage.combinat.species.structure import StructuresWrapper, IsotypesWrapper +from functools import reduce class GenericCombinatorialSpecies(SageObject): def __init__(self, min=None, max=None, weight=None): diff --git a/src/sage/combinat/words/finite_word.py b/src/sage/combinat/words/finite_word.py index 64ac7c3ad41..922a2227bb2 100644 --- a/src/sage/combinat/words/finite_word.py +++ b/src/sage/combinat/words/finite_word.py @@ -2088,14 +2088,14 @@ def is_palindrome(self, f=None): """ l = self.length() if f is None: - return self[:l/2] == self[l/2 + l%2:].reversal() + return self[:l//2] == self[l//2 + l%2:].reversal() else: from sage.combinat.words.morphism import WordMorphism if not isinstance(f, WordMorphism): f = WordMorphism(f) if not f.is_involution(): raise ValueError("f must be an involution") - return self[:l/2 + l%2] == f(self[l/2:].reversal()) + return self[:l//2 + l%2] == f(self[l//2:].reversal()) def lps(self, f=None, l=None): r""" @@ -2990,7 +2990,7 @@ def is_overlap(self): """ if self.length() == 0: return False - return self.length_border() > self.length()/2 + return self.length_border() > self.length()//2 def primitive_length(self): r""" @@ -3060,7 +3060,7 @@ def exponent(self): """ if self.length() == 0: return 0 - return self.length() / self.primitive_length() + return self.length() // self.primitive_length() def has_period(self, p): r""" @@ -6088,7 +6088,7 @@ def is_square(self): if self.length() % 2 != 0: return False else: - l = self.length() / 2 + l = self.length() // 2 return self[:l] == self[l:] def is_square_free(self): @@ -6142,7 +6142,7 @@ def is_cube(self): """ if self.length() % 3 != 0: return False - l = self.length() / 3 + l = self.length() // 3 return self[:l] == self[l:2*l] == self[2*l:] def is_cube_free(self): diff --git a/src/sage/combinat/words/paths.py b/src/sage/combinat/words/paths.py index d539b732f46..171140626b0 100644 --- a/src/sage/combinat/words/paths.py +++ b/src/sage/combinat/words/paths.py @@ -2097,7 +2097,7 @@ def _area_vh(path, x=0, y=0): elif move == A: area -= y x -= 1 - return area/2 + return area // 2 def is_simple(self): r""" diff --git a/src/sage/crypto/block_cipher/miniaes.py b/src/sage/crypto/block_cipher/miniaes.py index b563e695740..ebb682cdfe4 100644 --- a/src/sage/crypto/block_cipher/miniaes.py +++ b/src/sage/crypto/block_cipher/miniaes.py @@ -362,7 +362,7 @@ def __call__(self, B, key, algorithm="encrypt"): if len(key) != self._key_size: raise ValueError("secret key must be a 16-bit binary string") - N = len(B) / self._key_size # the number of 16-bit blocks + N = len(B) // self._key_size # the number of 16-bit blocks MS = MatrixSpace(FiniteField(self._key_size, "x"), 2, 2) bin = BinaryStrings() S = "" @@ -1885,7 +1885,7 @@ def binary_to_GF(self, B): raise ValueError("the number of bits in the binary string B must be positive and a multiple of 4") # a string with number of bits that is a multiple of 4 if Mod(len(b), 4).lift() == 0: - M = len(b) / 4 # the number of nibbles + M = len(b) // 4 # the number of nibbles return [self._bin_to_GF[b[i*4 : (i+1)*4]] for i in xrange(M)] else: raise ValueError("the number of bits in the binary string B must be positive and a multiple of 4") @@ -1955,7 +1955,7 @@ def binary_to_integer(self, B): raise ValueError("the number of bits in the binary string B must be positive and a multiple of 4") # a string with number of bits that is a multiple of 4 if Mod(len(b), 4).lift() == 0: - M = len(b) / 4 # the number of nibbles + M = len(b) // 4 # the number of nibbles return [self._bin_to_int[b[i*4 : (i+1)*4]] for i in xrange(M)] else: raise ValueError("the number of bits in the binary string B must be positive and a multiple of 4") diff --git a/src/sage/crypto/block_cipher/sdes.py b/src/sage/crypto/block_cipher/sdes.py index dd431dcedea..7389171ae85 100644 --- a/src/sage/crypto/block_cipher/sdes.py +++ b/src/sage/crypto/block_cipher/sdes.py @@ -210,7 +210,7 @@ def __call__(self, B, K, algorithm="encrypt"): if len(K) != self._key_size: raise ValueError("secret key must be a 10-bit binary string") - N = len(B) / Blength # the number of 8-bit blocks + N = len(B) // Blength # the number of 8-bit blocks S = "" bin = BinaryStrings() # encrypt each 8-bit block in succession diff --git a/src/sage/crypto/lattice.py b/src/sage/crypto/lattice.py index 1884b79e553..ac8d47b2fce 100644 --- a/src/sage/crypto/lattice.py +++ b/src/sage/crypto/lattice.py @@ -11,8 +11,8 @@ * Michael Schneider """ -def gen_lattice(type='modular', n=4, m=8, q=11, seed=None, \ - quotient=None, dual=False, ntl=False): +def gen_lattice(type='modular', n=4, m=8, q=11, seed=None, + quotient=None, dual=False, ntl=False, lattice=False): """ This function generates different types of integral lattice bases of row vectors relevant in cryptography. @@ -44,6 +44,9 @@ def gen_lattice(type='modular', n=4, m=8, q=11, seed=None, \ for Regev's LWE bases [R05]_. * ``ntl`` - Set this flag if you want the lattice basis in NTL readable format. + * ``lattice`` - Set this flag if you want a + :class:`FreeModule_submodule_with_basis_integer` object instead + of an integer matrix representing the basis. OUTPUT: ``B`` a unique size-reduced triangular (primal: lower_left, dual: lower_right) basis of row vectors for the lattice in question. @@ -125,6 +128,50 @@ def gen_lattice(type='modular', n=4, m=8, q=11, seed=None, \ sage: B_dual_alt.hermite_form() == B_dual.hermite_form() True + TESTS: + + We are testing output format choices:: + + sage: sage.crypto.gen_lattice(m=10, q=11, seed=42) + [11 0 0 0 0 0 0 0 0 0] + [ 0 11 0 0 0 0 0 0 0 0] + [ 0 0 11 0 0 0 0 0 0 0] + [ 0 0 0 11 0 0 0 0 0 0] + [ 2 4 3 5 1 0 0 0 0 0] + [ 1 -5 -4 2 0 1 0 0 0 0] + [-4 3 -1 1 0 0 1 0 0 0] + [-2 -3 -4 -1 0 0 0 1 0 0] + [-5 -5 3 3 0 0 0 0 1 0] + [-4 -3 2 -5 0 0 0 0 0 1] + + sage: sage.crypto.gen_lattice(m=10, q=11, seed=42, ntl=True) + [ + [11 0 0 0 0 0 0 0 0 0] + [0 11 0 0 0 0 0 0 0 0] + [0 0 11 0 0 0 0 0 0 0] + [0 0 0 11 0 0 0 0 0 0] + [2 4 3 5 1 0 0 0 0 0] + [1 -5 -4 2 0 1 0 0 0 0] + [-4 3 -1 1 0 0 1 0 0 0] + [-2 -3 -4 -1 0 0 0 1 0 0] + [-5 -5 3 3 0 0 0 0 1 0] + [-4 -3 2 -5 0 0 0 0 0 1] + ] + + sage: sage.crypto.gen_lattice(m=10, q=11, seed=42, lattice=True) + Free module of degree 10 and rank 10 over Integer Ring + User basis matrix: + [ 0 0 1 1 0 -1 -1 -1 1 0] + [-1 1 0 1 0 1 1 0 1 1] + [-1 0 0 0 -1 1 1 -2 0 0] + [-1 -1 0 1 1 0 0 1 1 -1] + [ 1 0 -1 0 0 0 -2 -2 0 0] + [ 2 -1 0 0 1 0 1 0 0 -1] + [-1 1 -1 0 1 -1 1 0 -1 -2] + [ 0 0 -1 3 0 0 0 -1 -1 -1] + [ 0 -1 0 -1 2 0 -1 0 0 2] + [ 0 1 1 0 1 1 -2 1 -1 -2] + REFERENCES: .. [A96] Miklos Ajtai. @@ -206,7 +253,14 @@ def minrep(a): ZZ(q)]], subdivide=False) for i in range(m//2): B.swap_rows(i,m-i-1) - if not ntl: - return B - else: + if ntl and lattice: + raise ValueError("Cannot specify ntl=True and lattice=True " + "at the same time") + + if ntl: return B._ntl_() + elif lattice: + from sage.modules.free_module_integer import IntegerLattice + return IntegerLattice(B) + else: + return B diff --git a/src/sage/crypto/public_key/blum_goldwasser.py b/src/sage/crypto/public_key/blum_goldwasser.py index fdb32a2da86..18853e63496 100644 --- a/src/sage/crypto/public_key/blum_goldwasser.py +++ b/src/sage/crypto/public_key/blum_goldwasser.py @@ -354,8 +354,8 @@ def decrypt(self, C, K): if (not is_blum_prime(p)) or (not is_blum_prime(q)): raise ValueError("p and q must be distinct Blum primes.") # prepare to decrypt - d1 = power_mod((p + 1) / 4, t + 1, p - 1) - d2 = power_mod((q + 1) / 4, t + 1, q - 1) + d1 = power_mod((p + 1) // 4, t + 1, p - 1) + d2 = power_mod((q + 1) // 4, t + 1, q - 1) u = power_mod(xt1, d1, p) v = power_mod(xt1, d2, q) x0 = mod(v*a*p + u*b*q, n).lift() @@ -512,23 +512,23 @@ def encrypt(self, P, K, seed=None): # sub-blocks of length h = 16 if mod(len(M), 16) == 0: h = 16 - t = len(M) / h + t = len(M) // h # sub-blocks of length h = 8 elif mod(len(M), 8) == 0: h = 8 - t = len(M) / h + t = len(M) // h # sub-blocks of length h = 4 elif mod(len(M), 4) == 0: h = 4 - t = len(M) / h + t = len(M) // h # sub-blocks of length h = 2 elif mod(len(M), 2) == 0: h = 2 - t = len(M) / h + t = len(M) // h # sub-blocks of length h = 1 else: h = 1 - t = len(M) / h + t = len(M) // h # If no seed is provided, select a random seed. x0 = seed if seed is None: diff --git a/src/sage/crypto/util.py b/src/sage/crypto/util.py index c07818715b4..434383bc2c5 100644 --- a/src/sage/crypto/util.py +++ b/src/sage/crypto/util.py @@ -257,7 +257,7 @@ def bin_to_ascii(B): b = map(lambda x: int(str(x)), list(B)) A = [] # the number of 8-bit blocks - k = n / 8 + k = n // 8 for i in xrange(k): # Convert from 8-bit string to ASCII integer. Then convert the # ASCII integer to the corresponding ASCII character. diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 806f5912c97..55ddf867dc2 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -25,6 +25,7 @@ import doctest import collections from sage.misc.preparser import preparse, strip_string_literals +from functools import reduce float_regex = re.compile('([+-]?((\d*\.?\d+)|(\d+\.?))([eE][+-]?\d+)?)') optional_regex = re.compile(r'(long time|not implemented|not tested|known bug)|([^ a-z]\s*optional\s*[:-]*((\s|\w)*))') diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index 2a637bc6a41..ce91290292d 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -673,6 +673,7 @@ def _test_enough_doctests(self, check_extras=True, verbose=True): ....: FDS = FileDocTestSource(filename, DocTestDefaults(long=True,optional=True)) ....: FDS._test_enough_doctests(verbose=False) There are 7 tests in sage/combinat/dyck_word.py that are not being run + There are 6 tests in sage/combinat/interval_posets.py that are not being run There are 18 tests in sage/combinat/partition.py that are not being run There are 15 tests in sage/combinat/permutation.py that are not being run There are 14 tests in sage/combinat/skew_partition.py that are not being run diff --git a/src/sage/dynamics/flat_surfaces/strata.py b/src/sage/dynamics/flat_surfaces/strata.py index f5a9ab4b3b3..402779fb9f6 100644 --- a/src/sage/dynamics/flat_surfaces/strata.py +++ b/src/sage/dynamics/flat_surfaces/strata.py @@ -1689,7 +1689,7 @@ def representative(self, reduced=True, alphabet=None): 3 2 5 4 6 8 7 10 9 1 0 """ zeroes = filter(lambda x: x > 0, self._parent._zeroes) - zeroes = map(lambda x: x/2, zeroes) + zeroes = map(lambda x: x//2, zeroes) n = self._parent._zeroes.count(0) g = self._parent._genus diff --git a/src/sage/dynamics/interval_exchanges/template.py b/src/sage/dynamics/interval_exchanges/template.py index 05cad4e7495..ae48b8a29f8 100644 --- a/src/sage/dynamics/interval_exchanges/template.py +++ b/src/sage/dynamics/interval_exchanges/template.py @@ -953,7 +953,7 @@ def attached_out_degree(self): left_corner = ((self[1][0], self[0][0]), 'L') for s in self.separatrix_diagram(side=True): if left_corner in s: - return len(s)/2 - 1 + return len(s)//2 - 1 def attached_in_degree(self): r""" @@ -977,7 +977,7 @@ def attached_in_degree(self): for s in self.separatrix_diagram(side=True): if right_corner in s: - return len(s)/2 - 1 + return len(s)//2 - 1 def attached_type(self): r""" @@ -1026,11 +1026,11 @@ def attached_type(self): if left_corner in s and right_corner in s: i1 = s.index(left_corner) i2 = s.index(right_corner) - return ([len(s)/2-1], ((i2-i1+1)/2) % 2) + return ([len(s)//2 - 1], ((i2-i1+1)//2) % 2) elif left_corner in s: - left_degree = len(s)/2-1 + left_degree = len(s)//2 - 1 elif right_corner in s: - right_degree = len(s)/2-1 + right_degree = len(s)//2 - 1 return ([left_degree,right_degree], 0) @@ -1182,7 +1182,7 @@ def stratum(self, marked_separatrix='no'): if len(self) == 1: return AbelianStratum([]) - singularities = [len(x)/2 - 1 for x in self.separatrix_diagram()] + singularities = [len(x)//2 - 1 for x in self.separatrix_diagram()] return AbelianStratum(singularities,marked_separatrix=marked_separatrix) diff --git a/src/sage/functions/bessel.py b/src/sage/functions/bessel.py index 9e6e872cf7b..c3bb57416bd 100644 --- a/src/sage/functions/bessel.py +++ b/src/sage/functions/bessel.py @@ -309,7 +309,7 @@ def __call__(self, *args, **kwds): sage: bessel_J(0, 1.0, "maxima", 53) doctest:1: DeprecationWarning: precision argument is deprecated; algorithm argument is currently deprecated, but will be available as a named keyword in the future See http://trac.sagemath.org/4102 for details. - .7651976865579666 + 0.7651976865579666 """ if len(args) > 2 or len(kwds) > 0: from sage.misc.superseded import deprecation @@ -1485,8 +1485,8 @@ def __init__(self, nu, typ = "J", algorithm = None, prec = 53): bessel_j(6,pi) sage: b.n(53) 0.0145459669825056 - sage: _Bessel(6, typ='I', algorithm="maxima")(pi) - 0.0294619840059568 + sage: _Bessel(6, typ='I', algorithm="maxima")(pi) # rel tol 5e-13 + 0.02946198400594384 sage: _Bessel(6, typ='Y', algorithm="maxima")(pi) -4.33932818939038 diff --git a/src/sage/functions/spike_function.py b/src/sage/functions/spike_function.py index 2ebccad7ce0..eff28733dc3 100644 --- a/src/sage/functions/spike_function.py +++ b/src/sage/functions/spike_function.py @@ -161,7 +161,7 @@ def plot_fft_abs(self, samples=2**12, xmin=None, xmax=None, **kwds): w = self.vector(samples = samples, xmin=xmin, xmax=xmax) xmin, xmax = self._ranges(xmin, xmax) z = w.fft() - k = vector(RDF, [abs(z[i]) for i in range(int(len(z)/2))]) + k = vector(RDF, [abs(z[i]) for i in range(len(z)//2)]) return k.plot(xmin=0, xmax=1, **kwds) def plot_fft_arg(self, samples=2**12, xmin=None, xmax=None, **kwds): @@ -180,7 +180,7 @@ def plot_fft_arg(self, samples=2**12, xmin=None, xmax=None, **kwds): w = self.vector(samples = samples, xmin=xmin, xmax=xmax) xmin, xmax = self._ranges(xmin, xmax) z = w.fft() - k = vector(RDF, [(z[i]).arg() for i in range(int(len(z)/2))]) + k = vector(RDF, [(z[i]).arg() for i in range(len(z)//2)]) return k.plot(xmin=0, xmax=1, **kwds) def vector(self, samples=2**16, xmin=None, xmax=None): diff --git a/src/sage/geometry/fan_morphism.py b/src/sage/geometry/fan_morphism.py index 35f22bc4c03..d6278a9bcd3 100644 --- a/src/sage/geometry/fan_morphism.py +++ b/src/sage/geometry/fan_morphism.py @@ -88,6 +88,7 @@ is_FreeModuleMorphism) from sage.rings.all import Infinity, ZZ from sage.rings.infinity import is_Infinite +from functools import reduce class FanMorphism(FreeModuleMorphism): r""" diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index f431144dab0..3eb74a7bd11 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -128,6 +128,7 @@ import os import subprocess import StringIO +from functools import reduce data_location = os.path.join(SAGE_SHARE,'reflexive_polytopes') diff --git a/src/sage/graphs/generators/families.py b/src/sage/graphs/generators/families.py index 2f91b241e54..53563824df4 100644 --- a/src/sage/graphs/generators/families.py +++ b/src/sage/graphs/generators/families.py @@ -1033,16 +1033,16 @@ def HararyGraph( k, n ): raise ValueError("Number of vertices n should be greater than k.") if k%2 == 0: - G = CirculantGraph( n, range(1,k/2+1) ) + G = CirculantGraph( n, range(1,k//2+1) ) else: if n%2 == 0: - G = CirculantGraph( n, range(1,(k-1)/2+1) ) + G = CirculantGraph( n, range(1,(k-1)//2+1) ) for i in range(n): - G.add_edge( i, (i+n/2)%n ) + G.add_edge( i, (i + n//2)%n ) else: G = HararyGraph( k-1, n ) - for i in range((n-1)/2+1): - G.add_edge( i, (i+(n-1)/2)%n ) + for i in range((n-1)//2 + 1): + G.add_edge( i, (i + (n-1)//2)%n ) G.name('Harary graph {0}, {1}'.format(k,n)) return G diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 07d04f02545..ebba7cb9efd 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -593,8 +593,8 @@ def _bit_vector(self): total_length = n*n bit = lambda x,y : x*n + y else: - total_length = int(n*(n - 1))/int(2) - n_ch_2 = lambda b : int(b*(b-1))/int(2) + total_length = (n*(n - 1))//2 + n_ch_2 = lambda b : int(b*(b-1))//2 bit = lambda x,y : n_ch_2(max([x,y])) + min([x,y]) bit_vector = set() for u,v,_ in self.edge_iterator(): @@ -1345,14 +1345,14 @@ def adjacency_matrix(self, sparse=None, boundary_first=False): Full MatrixSpace of 256 by 256 dense matrices over Integer Ring sage: graphs.CubeGraph(9).adjacency_matrix().parent() Full MatrixSpace of 512 by 512 sparse matrices over Integer Ring + sage: Graph([(i,i+1) for i in range(500)]+[(0,1),], multiedges=True).adjacency_matrix().parent() + Full MatrixSpace of 501 by 501 dense matrices over Integer Ring """ n = self.order() if sparse is None: - if n <= 256 or self.density() > 0.05: + sparse=True + if self.has_multiple_edges() or n <= 256 or self.density() > 0.05: sparse=False - else: - sparse=True - verts = self.vertices(boundary_first=boundary_first) new_indices = dict((v,i) for i,v in enumerate(verts)) D = {} @@ -2711,10 +2711,10 @@ def is_eulerian(self, path=False): return False else: # if there was another vertex with the same sign of difference... - if uv[(diff+1)/2] is not None: + if uv[(diff+1)//2] is not None: return False # ... the graph is not semi-eulerian else: - uv[(diff+1)/2] = v + uv[(diff+1)//2] = v else: return False else: @@ -4054,10 +4054,10 @@ def genus(self, set_embedding=True, on_embedding=None, minimal=True, maximal=Fal faces = len(self.faces(self._embedding)) except AttributeError: raise AttributeError('graph must have attribute _embedding set to compute current (embedded) genus') - return (2-verts+edges-faces)/2 + return (2-verts+edges-faces) // 2 else: # compute genus on the provided dict faces = len(self.faces(on_embedding)) - return (2-verts+edges-faces)/2 + return (2-verts+edges-faces) // 2 else: # then compute either maximal or minimal genus of all embeddings import genus @@ -12855,7 +12855,7 @@ def triangles_count(self, algorithm='iter'): """ if self.is_directed(): from sage.graphs.digraph_generators import digraphs - return self.subgraph_search_count(digraphs.Circuit(3))/3 + return self.subgraph_search_count(digraphs.Circuit(3)) // 3 else: if algorithm=='iter': @@ -12864,10 +12864,10 @@ def triangles_count(self, algorithm='iter'): ggnx = self.networkx_graph() for u in ggnx.nodes_iter(): tr += sum(ggnx.has_edge(v,w) for v,w in Combinations(ggnx.neighbors(u),2)) - return tr/3 + return tr//3 elif algorithm=='matrix': - return (self.adjacency_matrix()**3).trace()/6 + return (self.adjacency_matrix()**3).trace() // 6 else: raise ValueError("Algorithm '%s' not yet implemented. Please contribute." %(algorithm)) @@ -15520,20 +15520,37 @@ def plot(self, **options): """ return self.graphplot(**options).plot() - def show(self, **kwds): + def show(self, method = "matplotlib", **kwds): """ Shows the (di)graph. INPUT: - This method accepts any option understood by + - ``method`` -- + + - If ``method="matplotlib"`` (default) then graph is drawn as a + picture file, then displayed. In this situation, the method + accepts any other option understood by + :meth:`~sage.graphs.generic_graph.plot` (graph-specific) or by + :meth:`sage.plot.graphics.Graphics.show`. + + - If ``method="js"`` the graph is displayed using the `d3.js + `_ library in a browser. In this situation, the + method accepts any other option understood by + :meth:`sage.graphs.graph_plot_js.gen_html_code`. + + + This method accepts any other option understood by :meth:`~sage.graphs.generic_graph.GenericGraph.plot` (graph-specific) or by :meth:`sage.plot.graphics.Graphics.show`. .. NOTE:: - See the documentation of the :mod:`sage.graphs.graph_plot` module - for information on default arguments of this method. + - See the documentation of the :mod:`sage.graphs.graph_plot` module + for information on default arguments of this method. + + - For the javascript counterpart, refer to + :mod:`sage.graphs.graph_plot_js`. EXAMPLES:: @@ -15541,6 +15558,18 @@ def show(self, **kwds): sage: P = C.plot(vertex_labels=False, vertex_size=0, graph_border=True) sage: P.show() # long time (3s on sage.math, 2011) """ + if method == "js": + from sage.graphs.graph_plot_js import gen_html_code + from sage.doctest import DOCTEST_MODE + filename = gen_html_code(self, **kwds) + + if DOCTEST_MODE: + return + from sage.misc.viewer import browser + import os + os.system('%s %s 2>/dev/null 1>/dev/null &'% (browser(), filename)) + return + from graph_plot import graphplot_options # This dictionary only contains the options that graphplot diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 01038ef84e8..634c6b94c1a 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -1753,7 +1753,7 @@ def sparse6_string(self): # split into groups of 6, and convert numbers to decimal, adding 63 six_bits = '' - for i in range(len(s)/6): + for i in range(len(s)//6): six_bits += chr( int( s[6*i:6*(i+1)], 2) + 63 ) return ':' + generic_graph_pyx.N(n) + six_bits @@ -5069,6 +5069,10 @@ def clique_maximum(self, algorithm="Cliquer"): (see :class:`~sage.numerical.mip.MixedIntegerLinearProgram`) + - If ``algorithm = "mcqd"`` - Uses the MCQD solver + (``_). Note that the MCQD + package must be installed. + .. NOTE:: Currently only implemented for undirected graphs. Use to_undirected @@ -5101,7 +5105,8 @@ def clique_maximum(self, algorithm="Cliquer"): sage: C.clique_maximum(algorithm = "BFS") Traceback (most recent call last): ... - NotImplementedError: Only 'MILP' and 'Cliquer' are supported. + NotImplementedError: Only 'MILP', 'Cliquer' and 'mcqd' are supported. + """ self._scream_if_not_simple(allow_multiple_edges=True) if algorithm=="Cliquer": @@ -5109,8 +5114,14 @@ def clique_maximum(self, algorithm="Cliquer"): return max_clique(self) elif algorithm == "MILP": return self.complement().independent_set(algorithm = algorithm) + elif algorithm == "mcqd": + try: + from sage.graphs.mcqd import mcqd + except ImportError: + raise ImportError("Please install the mcqd package") + return mcqd(self) else: - raise NotImplementedError("Only 'MILP' and 'Cliquer' are supported.") + raise NotImplementedError("Only 'MILP', 'Cliquer' and 'mcqd' are supported.") def clique_number(self, algorithm="Cliquer", cliques=None): r""" @@ -5126,16 +5137,22 @@ def clique_number(self, algorithm="Cliquer", cliques=None): - ``algorithm`` -- the algorithm to be used : - - If ``algorithm = "Cliquer"`` - This wraps the C program Cliquer [NisOst2003]_. + - If ``algorithm = "Cliquer"`` - This wraps the C program Cliquer + [NisOst2003]_. - - If ``algorithm = "networkx"`` - This function is based on NetworkX's implementation - of the Bron and Kerbosch Algorithm [BroKer1973]_. + - If ``algorithm = "networkx"`` - This function is based on + NetworkX's implementation of the Bron and Kerbosch Algorithm + [BroKer1973]_. - If ``algorithm = "MILP"``, the problem is solved through a Mixed Integer Linear Program. (see :class:`~sage.numerical.mip.MixedIntegerLinearProgram`) + - If ``algorithm = "mcqd"`` - Uses the MCQD solver + (``_). Note that the MCQD + package must be installed. + - ``cliques`` - an optional list of cliques that can be input if already computed. Ignored unless ``algorithm=="networkx"``. @@ -5173,6 +5190,10 @@ def clique_number(self, algorithm="Cliquer", cliques=None): sage: g = graphs.PetersenGraph() sage: g.clique_number(algorithm="MILP") 2 + sage: for i in range(10): # optional - mcqd + ... g = graphs.RandomGNP(15,.5) # optional - mcqd + ... if g.clique_number() != g.clique_number(algorithm="mcqd"): # optional - mcqd + ... print "This is dead wrong !" # optional - mcqd """ self._scream_if_not_simple(allow_loops=False) if algorithm=="Cliquer": @@ -5183,8 +5204,14 @@ def clique_number(self, algorithm="Cliquer", cliques=None): return networkx.graph_clique_number(self.networkx_graph(copy=False),cliques) elif algorithm == "MILP": return len(self.complement().independent_set(algorithm = algorithm)) + elif algorithm == "mcqd": + try: + from sage.graphs.mcqd import mcqd + except ImportError: + raise ImportError("Please install the mcqd package") + return len(mcqd(self)) else: - raise NotImplementedError("Only 'networkx' 'MILP' and 'Cliquer' are supported.") + raise NotImplementedError("Only 'networkx' 'MILP' 'Cliquer' and 'mcqd' are supported.") def cliques_number_of(self, vertices=None, cliques=None): """ @@ -5323,6 +5350,10 @@ def independent_set(self, algorithm = "Cliquer", value_only = False, reduction_r (see :class:`~sage.numerical.mip.MixedIntegerLinearProgram`) + * If ``algorithm = "mcqd"`` - Uses the MCQD solver + (``_). Note that the MCQD + package must be installed. + - ``value_only`` -- boolean (default: ``False``). If set to ``True``, only the size of a maximum independent set is returned. Otherwise, a maximum independent set is returned as a list of vertices. @@ -5349,9 +5380,9 @@ def independent_set(self, algorithm = "Cliquer", value_only = False, reduction_r .. NOTE:: - While Cliquer is usually (and by far) the most efficient of the two - implementations, the Mixed Integer Linear Program formulation - sometimes proves faster on very "symmetrical" graphs. + While Cliquer/MCAD are usually (and by far) the most efficient + implementations, the MILP formulation sometimes proves faster on + very "symmetrical" graphs. EXAMPLES: @@ -5406,6 +5437,10 @@ def vertex_cover(self, algorithm = "Cliquer", value_only = False, - ``"MILP"`` will compute a minimum vertex cover through a mixed integer linear program. + - If ``algorithm = "mcqd"`` - Uses the MCQD solver + (``_). Note that the MCQD + package must be installed. + - ``value_only`` -- boolean (default: ``False``). If set to ``True``, only the size of a minimum vertex cover is returned. Otherwise, a minimum vertex cover is returned as a list of vertices. @@ -5480,13 +5515,17 @@ def vertex_cover(self, algorithm = "Cliquer", value_only = False, ... if g.size() != 0: ... print "This thing is not a vertex cover !" + Testing mcqd:: + + sage: graphs.PetersenGraph().vertex_cover(algorithm="mcqd",value_only=True) # optional - mcqd + 6 Given a wrong algorithm:: sage: graphs.PetersenGraph().vertex_cover(algorithm = "guess") Traceback (most recent call last): ... - ValueError: The algorithm must be either "Cliquer" or "MILP". + ValueError: The algorithm must be "Cliquer" "MILP" or "mcqd". REFERENCE: @@ -5595,9 +5634,8 @@ def vertex_cover(self, algorithm = "Cliquer", value_only = False, size_cover_g = 0 cover_g = [] - elif algorithm == "Cliquer": - from sage.graphs.cliquer import max_clique - independent = max_clique(g.complement()) + elif algorithm == "Cliquer" or algorithm == "mcqd": + independent = g.complement().clique_maximum(algorithm=algorithm) if value_only: size_cover_g = g.order() - len(independent) else: @@ -5623,7 +5661,7 @@ def vertex_cover(self, algorithm = "Cliquer", value_only = False, b = p.get_values(b) cover_g = [v for v in g.vertices() if b[v] == 1] else: - raise ValueError("The algorithm must be either \"Cliquer\" or \"MILP\".") + raise ValueError("The algorithm must be \"Cliquer\" \"MILP\" or \"mcqd\".") ######################### # Returning the results # diff --git a/src/sage/graphs/graph_latex.py b/src/sage/graphs/graph_latex.py index ed1213e9b56..2efbde6f93d 100644 --- a/src/sage/graphs/graph_latex.py +++ b/src/sage/graphs/graph_latex.py @@ -1346,9 +1346,9 @@ def dot2tex_picture(self): sage: print G.latex_options().dot2tex_picture() # optional - dot2tex graphviz \begin{tikzpicture}[>=latex,line join=bevel,] %% - \node (3333) at (...bp,...bp) [draw,draw=none] {$3333$}; - \node (88) at (...bp,...bp) [draw,draw=none] {$88$}; - \draw [black,->] (3333) ..controls (...bp,...bp) and (...bp,...bp) .. (88); + \node (node_1) at (...bp,...bp) [draw,draw=none] {$3333$}; + \node (node_0) at (...bp,...bp) [draw,draw=none] {$88$}; + \draw [black,->] (node_1) ..controls (...bp,...bp) and (...bp,...bp) .. (node_0); \definecolor{strokecol}{rgb}{0.0,0.0,0.0}; \pgfsetstrokecolor{strokecol} \draw (...bp,...bp) node {$\text{\texttt{my{\char`\_}label}}$}; diff --git a/src/sage/graphs/graph_list.py b/src/sage/graphs/graph_list.py index dea89eb2b66..f28856dc0db 100644 --- a/src/sage/graphs/graph_list.py +++ b/src/sage/graphs/graph_list.py @@ -309,7 +309,7 @@ def to_graphics_arrays(list, **kwds): else: plist.append(list[i].plot(pos=pos, vertex_size=50, vertex_labels=False, graph_border=True)) else: raise TypeError('Param list must be a list of Sage (di)graphs.') - num_arrays = len(plist)/20 + num_arrays = len(plist) // 20 if ( len(plist)%20 > 0 ): num_arrays += 1 rows = 5 cols = 4 diff --git a/src/sage/graphs/graph_plot.py b/src/sage/graphs/graph_plot.py index 00021bec092..f287a77f252 100644 --- a/src/sage/graphs/graph_plot.py +++ b/src/sage/graphs/graph_plot.py @@ -610,7 +610,7 @@ def set_edges(self, **edge_options): distance = dist if len(local_labels)*dist > max_dist: distance = float(max_dist)/len(local_labels) - for i in range(len(local_labels)/2): + for i in range(len(local_labels)//2): k = (i+1.0)*distance if self._arcdigraph: odd_start = self._polar_hack_for_multidigraph(p1, [odd_x(k),odd_y(k)], self._vertex_radius)[0] diff --git a/src/sage/graphs/graph_plot_js.py b/src/sage/graphs/graph_plot_js.py new file mode 100644 index 00000000000..ff851431af1 --- /dev/null +++ b/src/sage/graphs/graph_plot_js.py @@ -0,0 +1,278 @@ +r""" +Graph plotting in Javascript with d3.js + +This module implements everything that can be used to draw graphs with `d3.js +`_ in Sage. + +On Python's side, this is mainly done by wrapping a graph's edges and vertices +in a structure that can then be used in the javascript code. This javascript +code is then inserted into a .html file to be opened by a browser. + +What Sage feeds javascript with is a "graph" object with the following content: + +- ``vertices`` -- each vertex is a dictionay defining : + + - ``name`` -- The vertex's label + + - ``group`` -- the vertex' color (integer) + +The ID of a vertex is its index in the vertex list. + +- ``edges`` -- each edge is a dictionary defining : + + - ``source`` -- the ID (int) of the edge's source + + - ``target`` -- the ID (int) of the edge's destination + + - ``color`` -- the edge's color (integer) + + - ``value`` -- thickness of the edge + + - ``strength`` -- the edge's strength in the automatic layout + + - ``color`` -- color (hexadecimal code) + + - ``curve`` -- distance from the barycenter of the two endpoints and the + center of the edge. It defines the curve of the edge, which can be useful + for multigraphs. + +- ``pos`` -- a list whose `i` th element is a dictionary defining the position of + the `i` th vertex. + +It also contains the definition of some numerical/boolean variables whose +definition can be found in the documentation of +:meth:`~sage.graphs.generic_graph.GenericGraph.show` : ``directed``, ``charge``, +``link_distance``, ``link_strength``, ``gravity``, ``vertex_size``, +``edge_thickness``. + +.. TODO:: + + - Add tooltip like in ``_. + + - Add a zoom through scrolling (``_). + +Authors: + +- Nathann Cohen, Brice Onfroy -- July 2013 -- + Initial version of the Sage code, + Javascript code, using examples from `d3.js `_. + +Functions +--------- +""" +from sage.misc.temporary_file import tmp_filename +from sage.plot.colors import rainbow + +#***************************************************************************** +# Copyright (C) 2013 Nathann Cohen +# Brice Onfroy +# +# 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/ +#***************************************************************************** + + +def gen_html_code(G, + vertex_labels=False, + edge_labels=False, + vertex_partition=[], + edge_partition=[], + force_spring_layout=False, + charge=-120, + link_distance=30, + link_strength=2, + gravity=.04, + vertex_size=7, + edge_thickness=4): + r""" + Creates a .html file showing the graph using `d3.js `_. + + This function returns the name of the .html file. If you want to + visualize the actual graph use + :meth:`~sage.graphs.generic_graph.GenericGraph.show`. + + INPUT: + + - ``G`` -- the graph + + - ``vertex_labels`` (boolean) -- Whether to display vertex labels (set to + ``False`` by default). + + - ``edge_labels`` (boolean) -- Whether to display edge labels (set to + ``False`` by default). + + - ``vertex_partition`` -- a list of lists representing a partition of the + vertex set. Vertices are then colored in the graph according to the + partition. Set to ``[]`` by default. + + - ``edge_partition`` -- same as ``vertex_partition``, with edges + instead. Set to ``[]`` by default. + + - ``force_spring_layout`` -- whether to take sage's position into account if + there is one (see :meth:`~sage.graphs.generic_graph.GenericGraph.` and + :meth:`~sage.graphs.generic_graph.GenericGraph.`), or to compute a spring + layout. Set to ``False`` by default. + + - ``vertex_size`` -- The size of a vertex' circle. Set to `7` by default. + + - ``edge_thickness`` -- Thickness of an edge. Set to ``4`` by default. + + - ``charge`` -- the vertices' charge. Defines how they repulse each + other. See ``_ for more + information. Set to ``-120`` by default. + + - ``link_distance`` -- See + ``_ for more + information. Set to ``30`` by default. + + - ``link_strength`` -- See + ``_ for more + information. Set to ``2`` by default. + + - ``gravity`` -- See + ``_ for more + information. Set to ``0.04`` by default. + + EXAMPLES:: + + sage: graphs.RandomTree(50).show(method="js") # optional -- internet + + sage: g = graphs.PetersenGraph() + sage: g.show(method = "js", vertex_partition=g.coloring()) # optional -- internet + + sage: graphs.DodecahedralGraph().show(method="js", force_spring_layout=True) # optional -- internet + + sage: graphs.DodecahedralGraph().show(method="js") # optional -- internet + + sage: g = digraphs.DeBruijn(2,2) + sage: g.allow_multiple_edges(True) + sage: g.add_edge("10","10","a") + sage: g.add_edge("10","10","b") + sage: g.add_edge("10","10","c") + sage: g.add_edge("10","10","d") + sage: g.add_edge("01","11","1") + sage: g.show(method="js", vertex_labels=True,edge_labels=True, + ....: link_distance=200,gravity=.05,charge=-500, + ....: edge_partition=[[("11","12","2"),("21","21","a")]], + ....: edge_thickness=4) # optional -- internet + + TESTS:: + + sage: from sage.graphs.graph_plot_js import gen_html_code + sage: filename = gen_html_code(graphs.PetersenGraph()) + """ + directed = G.is_directed() + multiple_edges = G.has_multiple_edges() + + # Associated an integer to each vertex + v_to_id = {v: i for i, v in enumerate(G.vertices())} + + # Vertex colors + color = {i: len(vertex_partition) for i in range(G.order())} + for i, l in enumerate(vertex_partition): + for v in l: + color[v_to_id[v]] = i + + # Vertex list + nodes = [] + for v in G.vertices(): + nodes.append({"name": str(v), "group": str(color[v_to_id[v]])}) + + # Edge colors. + edge_color_default = "#aaa" + color_list = rainbow(len(edge_partition)) + edge_color = {} + for i, l in enumerate(edge_partition): + for e in l: + u, v, label = e if len(e) == 3 else e+(None,) + edge_color[u, v, label] = color_list[i] + if not directed: + edge_color[v, u, label] = color_list[i] + + # Edge list + edges = [] + seen = {} # How many times has this edge been seen ? + + for u, v, l in G.edges(): + + # Edge color + color = edge_color.get((u, v, l), edge_color_default) + + # Computes the curve of the edge + curve = 0 + + # Loop ? + if u == v: + seen[u, v] = seen.get((u, v), 0)+1 + curve = seen[u, v]*10+10 + + # For directed graphs, one also has to take into accounts + # edges in the opposite direction + elif directed: + if G.has_edge(v, u): + seen[u, v] = seen.get((u, v), 0)+1 + curve = seen[u, v]*15 + else: + if multiple_edges and len(G.edge_label(u, v)) != 1: + # Multiple edges. The first one has curve 15, then + # -15, then 30, then -30, ... + seen[u, v] = seen.get((u, v), 0) + 1 + curve = (1 if seen[u, v] % 2 else -1)*(seen[u, v]//2)*15 + + elif not directed and multiple_edges: + # Same formula as above for multiple edges + if len(G.edge_label(u, v)) != 1: + seen[u, v] = seen.get((u, v), 0) + 1 + curve = (1 if seen[u, v] % 2 else -1)*(seen[u, v]//2)*15 + + # Adding the edge to the list + edges.append({"source": v_to_id[u], + "target": v_to_id[v], + "strength": 0, + "color": color, + "curve": curve, + "name": str(l) if edge_labels else ""}) + + loops = [e for e in edges if e["source"] == e["target"]] + edges = [e for e in edges if e["source"] != e["target"]] + + # Defines the vertices' layout if possible + Gpos = G.get_pos() + pos = [] + if Gpos is not None and force_spring_layout is False: + charge = 0 + link_strength = 0 + gravity = 0 + + for v in G.vertices(): + x, y = Gpos[v] + pos.append([x, -y]) + + # Encodes the data as a JSON string + from json import JSONEncoder + string = JSONEncoder().encode({"nodes": nodes, + "links": edges, "loops": loops, "pos": pos, + "directed": G.is_directed(), + "charge": int(charge), + "link_distance": int(link_distance), + "link_strength": int(link_strength), + "gravity": float(gravity), + "vertex_labels": bool(vertex_labels), + "edge_labels": bool(edge_labels), + "vertex_size": int(vertex_size), + "edge_thickness": int(edge_thickness)}) + + from sage.env import SAGE_EXTCODE + js_code_file = open(SAGE_EXTCODE+"/graphs/graph_plot_js.html", 'r') + js_code = js_code_file.read().replace("// HEREEEEEEEEEEE", string) + js_code_file.close() + + # Writes the temporary .html file + filename = tmp_filename(ext='.html') + f = open(filename, 'w') + f.write(js_code) + f.close() + + return filename diff --git a/src/sage/graphs/matchpoly.pyx b/src/sage/graphs/matchpoly.pyx index a20a271d0c9..a8e7db241c4 100644 --- a/src/sage/graphs/matchpoly.pyx +++ b/src/sage/graphs/matchpoly.pyx @@ -196,9 +196,19 @@ def matching_polynomial(G, complement=True, name=None): x^12 - 66*x^10 + 1485*x^8 - 13860*x^6 + 51975*x^4 - 62370*x^2 + 10395 sage: matching_polynomial(graphs.CompleteGraph(13), complement=False) x^13 - 78*x^11 + 2145*x^9 - 25740*x^7 + 135135*x^5 - 270270*x^3 + 135135*x + + TESTS: + + Non-integer labels should work, (:trac:`15545`):: + + sage: G = Graph(10); + sage: G.add_vertex((0,1)) + sage: G.add_vertex('X') + sage: G.matching_polynomial() + x^12 """ - cdef int nverts, nedges, i, j, v, cur + cdef int nverts, nedges, i, j, cur cdef int *edges1, *edges2, *edges_mem, **edges cdef fmpz_poly_t pol diff --git a/src/sage/graphs/mcqd.pxd b/src/sage/graphs/mcqd.pxd new file mode 100644 index 00000000000..d8d8b01bc72 --- /dev/null +++ b/src/sage/graphs/mcqd.pxd @@ -0,0 +1,8 @@ +from libcpp cimport bool + +cdef extern from "mcqd.h": + cdef cppclass Maxclique: + Maxclique() + Maxclique(bool **, int n) + void mcqdyn(int * maxclique, int& size) + diff --git a/src/sage/graphs/mcqd.pyx b/src/sage/graphs/mcqd.pyx new file mode 100644 index 00000000000..3e7e759af50 --- /dev/null +++ b/src/sage/graphs/mcqd.pyx @@ -0,0 +1,66 @@ +include "sage/ext/interrupt.pxi" +include 'sage/ext/stdsage.pxi' +include 'sage/ext/cdefs.pxi' + +def mcqd(G): + """ + Computes the max clique using MCQD + + INPUT: + + - ``G`` -- A graph + + TESTS:: + + sage: from sage.graphs.mcqd import mcqd # optional - mcqd + sage: for i in range(10): # optional - mcqd + ... g = graphs.RandomGNP(15,.5) # optional - mcqd + ... if g.clique_number() != len(mcqd(g)): # optional - mcqd + ... print "This is dead wrong !" # optional - mcqd + """ + cdef int n = G.order() + + # - c0 is the adjacency matrix + # - c points toward each row of the matrix + # - qmax stores the max clique + cdef bool ** c = sage_malloc(n*sizeof(bool *)) + cdef bool * c0 = sage_calloc(n*n,sizeof(bool)) + cdef int * qmax = sage_malloc(n*sizeof(int)) + sage_free(NULL) + if c == NULL or c0 == NULL or qmax == NULL: + sage_free(c) + sage_free(c0) + sage_free(qmax) + raise MemoryError("Allocation Failed") + + c[0] = c0 + + # Defines c + cdef int i,ui,vi + for i in range(1,n): + c[i] = c[i-1] + n + + # Defines the adjacency matrix + cdef list vertices = G.vertices() + cdef dict vertex_to_id = {v:i for i,v in enumerate(vertices)} + + for u in G: + ui = vertex_to_id[u] + for v in G.neighbors(u): + vi = vertex_to_id[v] + c[ui][vi] = 1 + c[vi][ui] = 1 + + # Calls the solver + cdef int clique_number + cdef Maxclique * C = new Maxclique(c,n) + C.mcqdyn(qmax, clique_number) + + # Returns the answer + cdef list answer = [vertices[qmax[i]] for i in range(clique_number)] + sage_free(c[0]) + sage_free(c) + sage_free(qmax) + del C + + return answer diff --git a/src/sage/groups/abelian_gps/dual_abelian_group_element.py b/src/sage/groups/abelian_gps/dual_abelian_group_element.py index 0484856d11f..a44dfbc7d57 100644 --- a/src/sage/groups/abelian_gps/dual_abelian_group_element.py +++ b/src/sage/groups/abelian_gps/dual_abelian_group_element.py @@ -60,6 +60,7 @@ from sage.misc.functional import exp from sage.rings.complex_field import is_ComplexField from sage.groups.abelian_gps.element_base import AbelianGroupElementBase +from functools import reduce def add_strings(x, z=0): """ diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index d85ff218721..55362b6e546 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -998,7 +998,7 @@ def as_permutation_group(self, limit=4096000): from sage.combinat.permutation import Permutation from sage.groups.perm_gps.permgroup import PermutationGroup return PermutationGroup([ - Permutation(coset_table[2*i]) for i in range(len(coset_table)/2)]) + Permutation(coset_table[2*i]) for i in range(len(coset_table)//2)]) def direct_product(self, H, reduced=False, new_names=True): r""" diff --git a/src/sage/groups/perm_gps/cubegroup.py b/src/sage/groups/perm_gps/cubegroup.py index 8d57352fc53..16c03cc4b9e 100644 --- a/src/sage/groups/perm_gps/cubegroup.py +++ b/src/sage/groups/perm_gps/cubegroup.py @@ -364,30 +364,30 @@ def color_of_square(facet, colors=['lpurple', 'yellow', 'red', 'green', 'orange' cubie_center_list = { # centers of the cubies on the F,U, R faces - 1: [1/2,1/2,5/2], # ulb - 2: [1/2,3/2,5/2], # ub - 3: [1/2,5/2,5/2], # ubr - 4: [3/2,1/2,5/2], # ul - 5: [3/2,5/2,5/2], # ur - 6: [5/2,1/2,5/2], # ufl - 7: [5/2,3/2,5/2], # uf - 8: [5/2,5/2,5/2], # urf - 17: [5/2,1/2,5/2], # flu - 18: [5/2,3/2,5/2], # fu - 19: [5/2,5/2,5/2], # fur - 20: [5/2,1/2,3/2], # fl - 21: [5/2,5/2,3/2], # fr - 22: [5/2,1/2,1/2], # fdl - 23: [5/2,3/2,1/2], # fd - 24: [5/2,5/2,1/2], # frd - 25: [5/2,5/2,5/2], # rfu - 26: [3/2,5/2,5/2], # ru - 27: [1/2,5/2,5/2], # rub - 28: [5/2,5/2,3/2], # rf - 29: [1/2,5/2,3/2], # rb - 30: [5/2,5/2,1/2], # rdf - 31: [3/2,5/2,1/2], # rd - 32: [1/2,5/2,1/2], # rbd + 1: [1//2, 1//2, 5//2], # ulb + 2: [1//2, 3//2, 5//2], # ub + 3: [1//2, 5//2, 5//2], # ubr + 4: [3//2, 1//2, 5//2], # ul + 5: [3//2, 5//2, 5//2], # ur + 6: [5//2, 1//2, 5//2], # ufl + 7: [5//2, 3//2, 5//2], # uf + 8: [5//2, 5//2, 5//2], # urf + 17: [5//2, 1//2, 5//2], # flu + 18: [5//2, 3//2, 5//2], # fu + 19: [5//2, 5//2, 5//2], # fur + 20: [5//2, 1//2, 3//2], # fl + 21: [5//2, 5//2, 3//2], # fr + 22: [5//2, 1//2, 1//2], # fdl + 23: [5//2, 3//2, 1//2], # fd + 24: [5//2, 5//2, 1//2], # frd + 25: [5//2, 5//2, 5//2], # rfu + 26: [3//2, 5//2, 5//2], # ru + 27: [1//2, 5//2, 5//2], # rub + 28: [5//2, 5//2, 3//2], # rf + 29: [1//2, 5//2, 3//2], # rb + 30: [5//2, 5//2, 1//2], # rdf + 31: [3//2, 5//2, 1//2], # rd + 32: [1//2, 5//2, 1//2], # rbd } def cubie_centers(label): diff --git a/src/sage/homology/chain_complex.py b/src/sage/homology/chain_complex.py index bdebe02f55c..b00b301807b 100644 --- a/src/sage/homology/chain_complex.py +++ b/src/sage/homology/chain_complex.py @@ -428,7 +428,7 @@ def vector_art(d): if v.degree() == 0: return AsciiArt(['0']) v = str(v.column()).splitlines() - return AsciiArt(v, baseline=len(v)/2) + return AsciiArt(v, baseline=len(v)//2) result = [] chain_complex = self.parent() diff --git a/src/sage/homology/simplicial_complex.py b/src/sage/homology/simplicial_complex.py index cfa8e6e09b2..5f710846c75 100644 --- a/src/sage/homology/simplicial_complex.py +++ b/src/sage/homology/simplicial_complex.py @@ -168,6 +168,7 @@ 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') def lattice_paths(t1, t2, length=None): diff --git a/src/sage/interfaces/ecm.py b/src/sage/interfaces/ecm.py index 0d4868392a6..247b177eda2 100644 --- a/src/sage/interfaces/ecm.py +++ b/src/sage/interfaces/ecm.py @@ -772,7 +772,7 @@ def time(self, n, factor_digits, verbose=False): '35', '40', '45', '50', '55', '60', '65', '70', '75', '80'] h_min = 35 h_max = 80 - offset = (self._B1_table_value(factor_digits, h_min, h_max) - h_min) / 5 + offset = (self._B1_table_value(factor_digits, h_min, h_max) - h_min) // 5 print('offset', offset) curve_count = curve_count_table.split()[offset] time = time_table.split()[offset] diff --git a/src/sage/interfaces/gap.py b/src/sage/interfaces/gap.py index d2736531611..075addc1ed1 100644 --- a/src/sage/interfaces/gap.py +++ b/src/sage/interfaces/gap.py @@ -262,10 +262,10 @@ def get_gap_memory_pool_size(): return gap_memory_pool_size from sage.misc.memory_info import MemoryInfo mem = MemoryInfo() - suggested_size = max(int(mem.available_swap() / 10), - int(mem.available_ram() / 50)) + suggested_size = max(mem.available_swap() // 10, + mem.available_ram() // 50) # Don't eat all address space if the user set ulimit -v - suggested_size = min(suggested_size, int(mem.virtual_memory_limit()/10)) + suggested_size = min(suggested_size, mem.virtual_memory_limit()//10) # ~220MB is the minimum for long doctests suggested_size = max(suggested_size, 250 * 1024**2) return suggested_size diff --git a/src/sage/interfaces/matlab.py b/src/sage/interfaces/matlab.py index cd216db08a3..b9564531eb6 100644 --- a/src/sage/interfaces/matlab.py +++ b/src/sage/interfaces/matlab.py @@ -12,13 +12,13 @@ EXAMPLES:: - sage: matlab.eval('2+2') # optional + sage: matlab.eval('2+2') # optional - matlab '\nans =\n\n 4\n' :: - sage: a = matlab(10) # optional - sage: a**10 # optional + sage: a = matlab(10) # optional - matlab + sage: a**10 # optional - matlab 1.0000e+10 AUTHORS: @@ -30,61 +30,60 @@ EXAMPLES:: - sage: matlab('4+10') # optional + sage: matlab('4+10') # optional - matlab 14 - sage: matlab('date') # optional; random output + sage: matlab('date') # optional - matlab; random output 18-Oct-2006 - sage: matlab('5*10 + 6') # optional + sage: matlab('5*10 + 6') # optional - matlab 56 - sage: matlab('(6+6)/3') # optional + sage: matlab('(6+6)/3') # optional - matlab 4 - sage: matlab('9')^2 # optional + sage: matlab('9')^2 # optional - matlab 81 - sage: a = matlab(10); b = matlab(20); c = matlab(30) # optional - sage: avg = (a+b+c)/3 # optional - sage: avg # optional + sage: a = matlab(10); b = matlab(20); c = matlab(30) # optional - matlab + sage: avg = (a+b+c)/3 ; avg # optional - matlab 20 - sage: parent(avg) # optional + sage: parent(avg) # optional - matlab Matlab :: - sage: my_scalar = matlab('3.1415') # optional - sage: my_scalar # optional + sage: my_scalar = matlab('3.1415') # optional - matlab + sage: my_scalar # optional - matlab 3.1415 - sage: my_vector1 = matlab('[1,5,7]') # optional - sage: my_vector1 # optional + sage: my_vector1 = matlab('[1,5,7]') # optional - matlab + sage: my_vector1 # optional - matlab 1 5 7 - sage: my_vector2 = matlab('[1;5;7]') # optional - sage: my_vector2 # optional + sage: my_vector2 = matlab('[1;5;7]') # optional - matlab + sage: my_vector2 # optional - matlab 1 5 7 - sage: my_vector1 * my_vector2 # optional + sage: my_vector1 * my_vector2 # optional - matlab 75 :: - sage: row_vector1 = matlab('[1 2 3]') # optional - sage: row_vector2 = matlab('[3 2 1]') # optional - sage: matrix_from_row_vec = matlab('[%s; %s]'%(row_vector1.name(), row_vector2.name())) # optional - sage: matrix_from_row_vec # optional + sage: row_vector1 = matlab('[1 2 3]') # optional - matlab + sage: row_vector2 = matlab('[3 2 1]') # optional - matlab + sage: matrix_from_row_vec = matlab('[%s; %s]'%(row_vector1.name(), row_vector2.name())) # optional - matlab + sage: matrix_from_row_vec # optional - matlab 1 2 3 3 2 1 :: - sage: column_vector1 = matlab('[1;3]') # optional - sage: column_vector2 = matlab('[2;8]') # optional - sage: matrix_from_col_vec = matlab('[%s %s]'%(column_vector1.name(), column_vector2.name())) # optional - sage: matrix_from_col_vec # optional + sage: column_vector1 = matlab('[1;3]') # optional - matlab + sage: column_vector2 = matlab('[2;8]') # optional - matlab + sage: matrix_from_col_vec = matlab('[%s %s]'%(column_vector1.name(), column_vector2.name())) # optional - matlab + sage: matrix_from_col_vec # optional - matlab 1 2 3 8 :: - sage: my_matrix = matlab('[8, 12, 19; 7, 3, 2; 12, 4, 23; 8, 1, 1]') # optional - sage: my_matrix # optional + sage: my_matrix = matlab('[8, 12, 19; 7, 3, 2; 12, 4, 23; 8, 1, 1]') # optional - matlab + sage: my_matrix # optional - matlab 8 12 19 7 3 2 12 4 23 @@ -92,8 +91,8 @@ :: - sage: combined_matrix = matlab('[%s, %s]'%(my_matrix.name(), my_matrix.name())) # optional - sage: combined_matrix # optional + sage: combined_matrix = matlab('[%s, %s]'%(my_matrix.name(), my_matrix.name())) # optional - matlab + sage: combined_matrix # optional - matlab 8 12 19 8 12 19 7 3 2 7 3 2 12 4 23 12 4 23 @@ -101,32 +100,32 @@ :: - sage: tm = matlab('0.5:2:10') # optional - sage: tm # optional + sage: tm = matlab('0.5:2:10') # optional - matlab + sage: tm # optional - matlab 0.5000 2.5000 4.5000 6.5000 8.5000 :: - sage: my_vector1 = matlab('[1,5,7]') # optional - sage: my_vector1(1) # optional + sage: my_vector1 = matlab('[1,5,7]') # optional - matlab + sage: my_vector1(1) # optional - matlab 1 - sage: my_vector1(2) # optional + sage: my_vector1(2) # optional - matlab 5 - sage: my_vector1(3) # optional + sage: my_vector1(3) # optional - matlab 7 Matrix indexing works as follows:: - sage: my_matrix = matlab('[8, 12, 19; 7, 3, 2; 12, 4, 23; 8, 1, 1]') # optional - sage: my_matrix(3,2) # optional + sage: my_matrix = matlab('[8, 12, 19; 7, 3, 2; 12, 4, 23; 8, 1, 1]') # optional - matlab + sage: my_matrix(3,2) # optional - matlab 4 Setting using parenthesis cannot work (because of how the Python language works). Use square brackets or the set function:: - sage: my_matrix = matlab('[8, 12, 19; 7, 3, 2; 12, 4, 23; 8, 1, 1]') # optional - sage: my_matrix.set(2,3, 1999) # optional - sage: my_matrix # optional + sage: my_matrix = matlab('[8, 12, 19; 7, 3, 2; 12, 4, 23; 8, 1, 1]') # optional - matlab + sage: my_matrix.set(2,3, 1999) # optional - matlab + sage: my_matrix # optional - matlab 8 12 19 7 3 1999 12 4 23 @@ -161,10 +160,10 @@ class Matlab(Expect): EXAMPLES:: - sage: a = matlab('[ 1, 1, 2; 3, 5, 8; 13, 21, 33 ]') # optional - sage: b = matlab('[ 1; 3; 13]') # optional - sage: c = a * b # optional - sage: print c # optional + sage: a = matlab('[ 1, 1, 2; 3, 5, 8; 13, 21, 33 ]') # optional - matlab + sage: b = matlab('[ 1; 3; 13]') # optional - matlab + sage: c = a * b # optional - matlab + sage: print c # optional - matlab 30 122 505 @@ -201,9 +200,9 @@ def _read_in_file_command(self, filename): sage: m = identity_matrix(ZZ, 10) sage: sm = matlab.sage2matlab_matrix_string(m) - sage: m = matlab(sm) #optional - matlab + sage: m = matlab(sm) # optional - matlab """ - return "eval(fileread('%s'));"%filename + return "eval(fileread('{0}'));".format(filename) def _quit_string(self): return 'quit;' @@ -245,10 +244,10 @@ def set(self, var, value): """ Set the variable var to the given value. """ - cmd = '%s=%s;'%(var,value) + cmd = '{0}={1};'.format(var, value) out = self.eval(cmd) if out.find("error") != -1: - raise TypeError("Error executing code in Matlab\nCODE:\n\t%s\nMatlab ERROR:\n\t%s"%(cmd, out)) + raise TypeError("Error executing code in Matlab\nCODE:\n\t{0}\nMatlab ERROR:\n\t{1}".format(cmd, out)) def get(self, var): """ @@ -257,10 +256,10 @@ def get(self, var): EXAMPLES:: sage: s = matlab.eval('a = 2') # optional - matlab - sage: matlab.get('a') #optional + sage: matlab.get('a') # optional - matlab ' 2' """ - s = self.eval('%s'%var) + s = self.eval('{0}'.format(var)) return self.strip_answer(s) def strip_answer(self, s): @@ -294,7 +293,7 @@ def chdir(self, directory): / """ - self.eval("cd('%s')"%directory) + self.eval("cd('{0}')".format(directory)) def sage2matlab_matrix_string(self, A): """ @@ -332,11 +331,11 @@ def _matrix_(self, R): EXAMPLES:: sage: A = matlab('[1,2;3,4]') # optional - matlab - sage: matrix(ZZ, A) # optional + sage: matrix(ZZ, A) # optional - matlab [1 2] [3 4] sage: A = matlab('[1,2;3,4.5]') # optional - matlab - sage: matrix(RR, A) # optional + sage: matrix(RR, A) # optional - matlab [1.00000000000000 2.00000000000000] [3.00000000000000 4.50000000000000] @@ -347,7 +346,7 @@ def _matrix_(self, R): """ from sage.matrix.all import matrix matlab = self.parent() - entries = matlab.strip_answer(matlab.eval("mat2str(%s)"%self.name())) + entries = matlab.strip_answer(matlab.eval("mat2str({0})".format(self.name()))) entries = entries.strip()[1:-1].replace(';', ' ') entries = map(R, entries.split(' ')) nrows, ncols = map(int, str(self.size()).strip().split()) @@ -357,7 +356,7 @@ def _matrix_(self, R): def set(self, i, j, x): P = self._check_valid() z = P(x) - P.eval('%s(%s,%s) = %s'%(self.name(), i, j, z.name())) + P.eval('{0}({1},{2}) = {3}'.format(self.name(), i, j, z.name())) # An instance matlab = Matlab(script_subdirectory='user') @@ -374,7 +373,7 @@ def matlab_console(): EXAMPLES:: - sage: matlab_console() # optional; not tested + sage: matlab_console() # optional - matlab; not tested < M A T L A B > Copyright 1984-2006 The MathWorks, Inc. ... @@ -403,4 +402,3 @@ def matlab_version(): '7.2.0.283 (R2006a)' """ return str(matlab('version')).strip() - diff --git a/src/sage/interfaces/maxima.py b/src/sage/interfaces/maxima.py index 7603881f6a7..91ff4469347 100644 --- a/src/sage/interfaces/maxima.py +++ b/src/sage/interfaces/maxima.py @@ -556,8 +556,9 @@ def __init__(self, script_subdirectory=None, logfile=None, server=None, self._display_prompt = '' # See #15440 for the importance of the trailing space self._output_prompt_re = re.compile('\(\%o[0-9]+\) ') - self._ask = ['zero or nonzero?', 'an integer?', 'positive, negative, or zero?', - 'positive or negative?', 'positive or zero?'] + self._ask = ['zero or nonzero\\?', 'an integer\\?', + 'positive, negative or zero\\?', 'positive or negative\\?', + 'positive or zero\\?', 'equal to .*\\?'] self._prompt_wait = [self._prompt] + [re.compile(x) for x in self._ask] + \ ['Break [0-9]+'] #note that you might need to change _expect_expr if you #change this @@ -642,7 +643,7 @@ def _expect_expr(self, expr=None, timeout=None): TypeError: Computation failed since Maxima requested additional constraints (try the command "maxima.assume('a>0')" before integral or limit evaluation, for example): - Is a positive or negative? + Is a positive or negative? sage: maxima.assume('a>0') [a>0] sage: maxima('integrate(1/(x^3*(a+b*x)^(1/3)),x)') @@ -651,9 +652,9 @@ def _expect_expr(self, expr=None, timeout=None): Traceback (most recent call last): ... TypeError: Computation failed since Maxima requested additional - constraints (try the command "maxima.assume('n+1>0')" before + constraints (try the command "maxima.assume('n>0')" before integral or limit evaluation, for example): - Is n+1 zero or nonzero? + Is n equal to -1? sage: maxima.assume('n+1>0') [n>-1] sage: maxima('integrate(x^n,x)') @@ -670,7 +671,7 @@ def _expect_expr(self, expr=None, timeout=None): TypeError: Computation failed since Maxima requested additional constraints (try the command "maxima.assume('a>0')" before integral or limit evaluation, for example): - Is a positive, negative, or zero? + Is a positive, negative or zero? """ if expr is None: expr = self._prompt_wait @@ -692,8 +693,8 @@ def _expect_expr(self, expr=None, timeout=None): j = v.find('Is ') v = v[j:] - k = v.find(' ',4) - msg = """Computation failed since Maxima requested additional constraints (try the command "maxima.assume('""" + v[4:k] +""">0')" before integral or limit evaluation, for example):\n""" + v + self._ask[i-1] + k = v.find(' ', 3) + msg = """Computation failed since Maxima requested additional constraints (try the command "maxima.assume('""" + v[3:k] + """>0')" before integral or limit evaluation, for example):\n""" + v + self._expect.after self._sendline(";") self._expect_expr() raise ValueError(msg) diff --git a/src/sage/interfaces/maxima_abstract.py b/src/sage/interfaces/maxima_abstract.py index 9ac7a11f740..db5d412c5c1 100644 --- a/src/sage/interfaces/maxima_abstract.py +++ b/src/sage/interfaces/maxima_abstract.py @@ -270,7 +270,7 @@ def completions(self, s, verbose=True): EXAMPLES:: sage: sorted(maxima.completions('gc', verbose=False)) - ['gcd', 'gcdex', 'gcfactor', 'gcprint', 'gctime'] + ['gcd', 'gcdex', 'gcfactor', 'gctime'] """ if verbose: print s, @@ -420,7 +420,7 @@ def version(self): EXAMPLES:: sage: maxima.version() - '5.29.1' + '5.33.0' """ return maxima_version() @@ -601,11 +601,11 @@ def function(self, args, defn, rep=None, latex=None): EXAMPLES:: sage: f = maxima.function('x', 'sin(x)') - sage: f(3.2) - -.058374143427580... + sage: f(3.2) # abs tol 2e-16 + -0.058374143427579909 sage: f = maxima.function('x,y', 'sin(x)+cos(y)') - sage: f(2,3.5) - sin(2)-.9364566872907963 + sage: f(2, 3.5) # abs tol 2e-16 + sin(2)-0.9364566872907963 sage: f sin(x)+cos(y) @@ -1513,7 +1513,7 @@ def nintegral(self, var='x', a=0, b=1, EXAMPLES:: sage: maxima('exp(-sqrt(x))').nintegral('x',0,1) - (.5284822353142306, 4.16331413788384...e-11, 231, 0) + (0.5284822353142306, 4.16331413788384...e-11, 231, 0) Note that GP also does numerical integration, and can do so to very high precision very quickly:: @@ -2352,7 +2352,7 @@ def maxima_version(): sage: from sage.interfaces.maxima_abstract import maxima_version sage: maxima_version() - '5.29.1' + '5.33.0' """ return os.popen('maxima --version').read().split()[-1] diff --git a/src/sage/interfaces/maxima_lib.py b/src/sage/interfaces/maxima_lib.py index 066cf1dff6e..7d90915ca29 100644 --- a/src/sage/interfaces/maxima_lib.py +++ b/src/sage/interfaces/maxima_lib.py @@ -654,7 +654,7 @@ def sr_integral(self,*args): constraints; using the 'assume' command before integral evaluation *may* help (example of legal syntax is 'assume(a>0)', see `assume?` for more details) - Is a positive or negative? + Is a positive or negative? sage: assume(a>0) sage: integrate(1/(x^3 *(a+b*x)^(1/3)),x) 2/9*sqrt(3)*b^2*arctan(1/3*sqrt(3)*(2*(b*x + a)^(1/3) + a^(1/3))/a^(1/3))/a^(7/3) - 1/9*b^2*log((b*x + a)^(2/3) + (b*x + a)^(1/3)*a^(1/3) + a^(2/3))/a^(7/3) + 2/9*b^2*log((b*x + a)^(1/3) - a^(1/3))/a^(7/3) + 1/6*(4*(b*x + a)^(5/3)*b^2 - 7*(b*x + a)^(2/3)*a*b^2)/((b*x + a)^2*a^2 - 2*(b*x + a)*a^3 + a^4) @@ -665,9 +665,9 @@ def sr_integral(self,*args): ... ValueError: Computation failed since Maxima requested additional constraints; using the 'assume' command before integral evaluation - *may* help (example of legal syntax is 'assume(n+1>0)', + *may* help (example of legal syntax is 'assume(n>0)', see `assume?` for more details) - Is n+1 zero or nonzero? + Is n equal to -1? sage: assume(n+1>0) sage: integral(x^n,x) x^(n + 1)/(n + 1) @@ -700,7 +700,16 @@ def sr_integral(self,*args): :: sage: integrate(cos(x + abs(x)), x) - -1/2*x*sgn(x) + 1/4*(sgn(x) + 1)*sin(2*x) + 1/2*x + -1/4*(2*x - sin(2*x))*real_part(sgn(x)) + 1/2*x + 1/4*sin(2*x) + + Note that the last example yielded the same answer in a + simpler form in earlier versions of Maxima (<= 5.29.1), namely + ``-1/2*x*sgn(x) + 1/4*(sgn(x) + 1)*sin(2*x) + 1/2*x``. This + is because Maxima no longer simplifies ``realpart(signum(x))`` + to ``signum(x)``:: + + sage: maxima("realpart(signum(x))") + 'realpart(signum(x)) An example from sage-support thread e641001f8b8d1129:: @@ -730,6 +739,24 @@ def sr_integral(self,*args): sage: integrate(f, (x, -infinity, infinity)) 1/3*pi^2 + Sometimes one needs different simplification settings, such as + ``radexpand``, to compute an integral (see :trac:`10955`):: + + sage: f = sqrt(x + 1/x^2) + sage: maxima = sage.calculus.calculus.maxima + sage: maxima('radexpand') + true + sage: integrate(f, x) + integrate(sqrt(x + 1/x^2), x) + sage: maxima('radexpand: all') + all + sage: g = integrate(f, x); g + 2/3*sqrt(x^3 + 1) - 1/3*log(sqrt(x^3 + 1) + 1) + 1/3*log(sqrt(x^3 + 1) - 1) + sage: (f - g.diff(x)).simplify_radical() + 0 + sage: maxima('radexpand: true') + true + """ try: return max_to_sr(maxima_eval(([max_integrate],[sr_to_max(SR(a)) for a in args]))) @@ -742,8 +769,8 @@ def sr_integral(self,*args): elif "Is" in s: # Maxima asked for a condition j = s.find('Is ') s = s[j:] - k = s.find(' ',4) - raise ValueError("Computation failed since Maxima requested additional constraints; using the 'assume' command before integral evaluation *may* help (example of legal syntax is 'assume(" + s[4:k] +">0)', see `assume?` for more details)\n" + s) + k = s.find(' ', 3) + raise ValueError("Computation failed since Maxima requested additional constraints; using the 'assume' command before integral evaluation *may* help (example of legal syntax is 'assume(" + s[3:k] + ">0)', see `assume?` for more details)\n" + s) else: raise @@ -772,7 +799,7 @@ def sr_sum(self,*args): constraints; using the 'assume' command before summation *may* help (example of legal syntax is 'assume(abs(q)-1>0)', see `assume?` for more details) - Is abs(q)-1 positive, negative, or zero? + Is abs(q)-1 positive, negative or zero? sage: assume(q > 1) sage: sum(a*q^k, k, 0, oo) Traceback (most recent call last): @@ -795,6 +822,22 @@ def sr_sum(self,*args): Traceback (most recent call last): ... ValueError: Sum is divergent. + + An error with an infinite sum in Maxima (before 5.30.0, + see :trac:`13712`):: + + sage: n = var('n') + sage: sum(1/((2*n-1)^2*(2*n+1)^2*(2*n+3)^2), n, 0, oo) + 3/256*pi^2 + + Maxima correctly detects division by zero in a symbolic sum + (see :trac:`11894`):: + + sage: sum(1/(m^4 + 2*m^3 + 3*m^2 + 2*m)^2, m, 0, infinity) + Traceback (most recent call last): + ... + RuntimeError: ECL says: Error executing code in Maxima: Zero to negative power computed. + """ try: return max_to_sr(maxima_eval([[max_ratsimp],[[max_simplify_sum],([max_sum],[sr_to_max(SR(a)) for a in args])]])); @@ -808,8 +851,8 @@ def sr_sum(self,*args): elif "Is" in s: # Maxima asked for a condition j = s.find('Is ') s = s[j:] - k = s.find(' ',4) - raise ValueError("Computation failed since Maxima requested additional constraints; using the 'assume' command before summation *may* help (example of legal syntax is 'assume(" + s[4:k] +">0)', see `assume?` for more details)\n" + s) + k = s.find(' ', 3) + raise ValueError("Computation failed since Maxima requested additional constraints; using the 'assume' command before summation *may* help (example of legal syntax is 'assume(" + s[3:k] + ">0)', see `assume?` for more details)\n" + s) else: raise @@ -834,7 +877,7 @@ def sr_limit(self,expr,v,a,dir=None): ValueError: Computation failed since Maxima requested additional constraints; using the 'assume' command before limit evaluation *may* help (see `assume?` for more details) - Is a positive, negative, or zero? + Is a positive, negative or zero? sage: assume(a>0) sage: limit(x^a,x=0) Traceback (most recent call last): @@ -852,7 +895,7 @@ def sr_limit(self,expr,v,a,dir=None): [] The second limit below was computed incorrectly prior to - maxima-5.24 (:trac:`10868`):: + Maxima 5.24 (:trac:`10868`):: sage: f(n) = 2 + 1/factorial(n) sage: limit(f(n), n=infinity) @@ -860,6 +903,14 @@ def sr_limit(self,expr,v,a,dir=None): sage: limit(1/f(n), n=infinity) 1/2 + The limit below was computed incorrectly prior to Maxima 5.30 + (see :trac:`13526`):: + + sage: n = var('n') + sage: l = (3^n + (-2)^n) / (3^(n+1) + (-2)^(n+1)) + sage: l.limit(n=oo) + 1/3 + """ try: L=[sr_to_max(SR(a)) for a in [expr,v,a]] @@ -966,7 +1017,7 @@ def to_poly_solve(self,vars,options=""): sage: from sage.interfaces.maxima_lib import maxima_lib sage: sol = maxima_lib(sin(x) == 0).to_poly_solve(x) sage: sol.sage() - [[x == pi + 2*pi*z60], [x == 2*pi*z62]] + [[x == pi*z54]] """ if options.find("use_grobner=true") != -1: cmd=EclObject([[max_to_poly_solve], self.ecl(), sr_to_max(vars), diff --git a/src/sage/interfaces/singular.py b/src/sage/interfaces/singular.py index 386da753a6c..576fcc73e3d 100644 --- a/src/sage/interfaces/singular.py +++ b/src/sage/interfaces/singular.py @@ -1653,7 +1653,7 @@ def sage_poly(self, R=None, kcache=None): if singular_poly_list == ['1','0'] : return R(0) - coeff_start = int(len(singular_poly_list)/2) + coeff_start = len(singular_poly_list) // 2 if isinstance(R,(MPolynomialRing_polydict,QuotientRing_generic)) and (ring_is_fine or can_convert_to_singular(R)): # we need to lookup the index of a given variable represented diff --git a/src/sage/libs/ecl.pyx b/src/sage/libs/ecl.pyx index 31e16f354f5..e20d5f46f62 100644 --- a/src/sage/libs/ecl.pyx +++ b/src/sage/libs/ecl.pyx @@ -560,15 +560,15 @@ cdef class EclObject: Floats in Python are IEEE double, which LISP has as well. However, the printing of floating point types in LISP depends on settings:: - sage: a = EclObject(float(10**40)) + sage: a = EclObject(float(10^40)) sage: ecl_eval("(setf *read-default-float-format* 'single-float)") sage: a - + sage: ecl_eval("(setf *read-default-float-format* 'double-float)") sage: a - + Tuples are translated to dotted lists:: diff --git a/src/sage/libs/fplll/fplll.pxd b/src/sage/libs/fplll/fplll.pxd index a8d0c7f515c..924855f614b 100644 --- a/src/sage/libs/fplll/fplll.pxd +++ b/src/sage/libs/fplll/fplll.pxd @@ -2,7 +2,5 @@ include "sage/ext/cdefs.pxi" include "fplll.pxi" cdef class FP_LLL: - cdef ZZ_mat *_lattice - cdef int _check_precision(self, int precision) except -1 - cdef int _check_eta(self, float eta) except -1 - cdef int _check_delta(self, float delta) except -1 + cdef object fp_map + cdef ZZ_mat[mpz_t] *_lattice diff --git a/src/sage/libs/fplll/fplll.pxi b/src/sage/libs/fplll/fplll.pxi index dd7ac4a5861..659e88da09e 100644 --- a/src/sage/libs/fplll/fplll.pxi +++ b/src/sage/libs/fplll/fplll.pxi @@ -2,47 +2,127 @@ # general include # -cdef extern from "fplll/fplll.h": - pass +from libcpp.vector cimport vector # # integers # -cdef extern from "fplll/nr.h": - ctypedef struct Z_NR "fplll::Z_NR": - mpz_t (*GetData)() - void (*set_mpz_t "set")(mpz_t d) - - Z_NR *Z_NR_new "new fplll::Z_NR"() - void Z_NR_delete "delete "(Z_NR *mem) - - Z_NR *Z_NR_construct "Construct< fplll::Z_NR >"(void *mem) - void Z_NR_destruct "Destruct< fplll::Z_NR >"(Z_NR *mem) +cdef extern from "fplll/nr.h" namespace "fplll": + cdef cppclass Z_NR[T]: + T& getData() + void set(mpz_t d) # # matrices over the integers # -cdef extern from "fplll/matrix.h": - ctypedef struct ZZ_mat "fplll::ZZ_mat": - int (*GetNumCols)() - int (*GetNumRows)() +cdef extern from "fplll/matrix.h" namespace "fplll": + cdef cppclass MatrixRow[T]: + Z_NR[T]& operator[](int i) + + cdef cppclass ZZ_mat[T]: + + ZZ_mat() + ZZ_mat(int r, int c) + + int getRows() + int getCols() + + T& operator()(int i, int j) + MatrixRow[T] operator[](int i) + + void gen_intrel(int bits) + void gen_simdioph(int bits, int bits2) + void gen_uniform(int bits) + void gen_ntrulike(int bits, int q) + void gen_ntrulike2(int bits, int q) + void gen_ajtai(double alpha) + + +cdef extern from "fplll/defs.h" namespace "fplll": + + cdef enum LLLFlags: + LLL_VERBOSE + LLL_EARLY_RED + LLL_SIEGEL + LLL_DEFAULT + + cdef enum BKZFlags: + BKZ_DEFAULT + BKZ_VERBOSE + BKZ_NO_LLL + BKZ_MAX_LOOPS + BKZ_MAX_TIME + BKZ_BOUNDED_LLL + BKZ_AUTO_ABORT + + cdef enum LLLMethod: + LM_WRAPPER + LM_PROVED + LM_HEURISTIC + LM_FAST - Z_NR (*Get)(int i, int j) - void (*Set)(int i, int j, Z_NR d) + cdef enum IntType: + ZT_MPZ + ZT_LONG + ZT_DOUBLE - void (*print_c "print")() + cdef enum FloatType: + FT_DEFAULT + FT_DOUBLE + FT_LONG_DOUBLE + FT_DPE + FT_MPFR - void (*gen_intrel)(int bits) - void (*gen_simdioph)(int bits,int bits2) - void (*gen_uniform)(int bits) - void (*gen_ntrulike)(int bits,int q) - void (*gen_ntrulike2)(int bits,int q) - void (*gen_ajtai)(double alpha) + cdef enum SVPMethod: + SVPM_FAST + SVPM_PROVED - ZZ_mat *ZZ_mat_new "new fplll::ZZ_mat"(int r, int c) - void ZZ_mat_delete "delete "(ZZ_mat *mem) + cdef double LLL_DEF_DELTA + cdef double LLL_DEF_ETA + +cdef extern from "fplll/fplll.h" namespace "fplll": + + int lllReduction(ZZ_mat[mpz_t] b, double delta, double eta, + LLLMethod method, FloatType floatType, + int precision, int flags) + int lllReduction(ZZ_mat[mpz_t] b, ZZ_mat[mpz_t] u, + double delta, double eta, + LLLMethod method, FloatType floatType, + int precision, int flags) + + cdef struct BKZParam: + BKZParam() + ZZ_mat[mpz_t]* b + ZZ_mat[mpz_t]* u + int blockSize + double delta + FloatType floatType + int precision + int flags + int maxLoops + double maxTime + vector[double] pruning + + int bkzReduction(BKZParam ¶m) + int bkzReduction(ZZ_mat[mpz_t] b, int blockSize) + + int hkzReduction(ZZ_mat[mpz_t] b) + int shortestVector(ZZ_mat[mpz_t] b, + vector[Z_NR[mpz_t]] &solCoord, + SVPMethod method) + const char* getRedStatusStr (int status) + +cdef extern from "fplll/util.h" namespace "fplll": + void vectMatrixProduct(vector[Z_NR[mpz_t]] &result, + vector[Z_NR[mpz_t]] &x, + const ZZ_mat[mpz_t] &m) + + +# +# fpLLL 3.x interface +# # # fastest LLL @@ -53,7 +133,7 @@ cdef extern from "fplll/fplllv31.h": int (*LLL)() ZZ_mat* (*GetBase)() - fast_double *fast_double_new "new fplll::fast"(ZZ_mat *B,int precision, double eta, double delta) + fast_double *fast_double_new "new fplll::fast"(ZZ_mat[mpz_t] *B,int precision, double eta, double delta) void fast_double_delete "delete "(fast_double *mem) # @@ -65,7 +145,7 @@ cdef extern from "fplll/fplllv31.h": int (*LLL)() ZZ_mat* (*GetBase)() - fast_early_red_double *fast_early_red_double_new "new fplll::fast_early_red"(ZZ_mat *B,int precision, double eta, double delta) + fast_early_red_double *fast_early_red_double_new "new fplll::fast_early_red"(ZZ_mat[mpz_t] *B,int precision, double eta, double delta) void fast_early_red_double_delete "delete "(fast_early_red_double *mem) # @@ -77,21 +157,21 @@ cdef extern from "fplll/fplllv31.h": int (*LLL)() ZZ_mat* (*GetBase)() - heuristic_mpfr *heuristic_mpfr_new "new fplll::heuristic"(ZZ_mat *B,int precision, double eta, double delta) + heuristic_mpfr *heuristic_mpfr_new "new fplll::heuristic"(ZZ_mat[mpz_t] *B,int precision, double eta, double delta) void heuristic_mpfr_delete "delete "(heuristic_mpfr *mem) ctypedef struct heuristic_dpe "fplll::heuristic": int (*LLL)() ZZ_mat* (*GetBase)() - heuristic_dpe *heuristic_dpe_new "new fplll::heuristic"(ZZ_mat *B,int precision, double eta, double delta) + heuristic_dpe *heuristic_dpe_new "new fplll::heuristic"(ZZ_mat[mpz_t] *B,int precision, double eta, double delta) void heuristic_dpe_delete "delete "(heuristic_dpe *mem) ctypedef struct heuristic_double "fplll::heuristic": int (*LLL)() ZZ_mat* (*GetBase)() - heuristic_double *heuristic_double_new "new fplll::heuristic"(ZZ_mat *B,int precision, double eta, double delta) + heuristic_double *heuristic_double_new "new fplll::heuristic"(ZZ_mat[mpz_t] *B,int precision, double eta, double delta) void heuristic_double_delete "delete "(heuristic_double *mem) # @@ -103,21 +183,21 @@ cdef extern from "fplll/fplllv31.h": int (*LLL)() ZZ_mat* (*GetBase)() - heuristic_early_red_mpfr *heuristic_early_red_mpfr_new "new fplll::heuristic_early_red"(ZZ_mat *B,int precision, double eta, double delta) + heuristic_early_red_mpfr *heuristic_early_red_mpfr_new "new fplll::heuristic_early_red"(ZZ_mat[mpz_t] *B,int precision, double eta, double delta) void heuristic_early_red_mpfr_delete "delete "(heuristic_early_red_mpfr *mem) ctypedef struct heuristic_early_red_dpe "fplll::heuristic_early_red": int (*LLL)() ZZ_mat* (*GetBase)() - heuristic_early_red_dpe *heuristic_early_red_dpe_new "new fplll::heuristic_early_red"(ZZ_mat *B,int precision, double eta, double delta) + heuristic_early_red_dpe *heuristic_early_red_dpe_new "new fplll::heuristic_early_red"(ZZ_mat[mpz_t] *B,int precision, double eta, double delta) void heuristic_early_red_dpe_delete "delete "(heuristic_early_red_dpe *mem) ctypedef struct heuristic_early_red_double "fplll::heuristic_early_red": int (*LLL)() ZZ_mat* (*GetBase)() - heuristic_early_red_double *heuristic_early_red_double_new "new fplll::heuristic_early_red"(ZZ_mat *B,int precision, double eta, double delta) + heuristic_early_red_double *heuristic_early_red_double_new "new fplll::heuristic_early_red"(ZZ_mat[mpz_t] *B,int precision, double eta, double delta) void heuristic_early_red_double_delete "delete "(heuristic_early_red_double *mem) @@ -131,21 +211,24 @@ cdef extern from "fplll/fplllv31.h": int (*LLL)() ZZ_mat* (*GetBase)() - proved_mpfr *proved_mpfr_new "new fplll::proved"(ZZ_mat *B,int precision, double eta, double delta) + proved_mpfr *proved_mpfr_new "new fplll::proved"( + ZZ_mat[mpz_t] *B,int precision, double eta, double delta) void proved_mpfr_delete "delete "(proved_mpfr *mem) ctypedef struct proved_dpe "fplll::proved": int (*LLL)() ZZ_mat* (*GetBase)() - proved_dpe *proved_dpe_new "new fplll::proved"(ZZ_mat *B,int precision, double eta, double delta) + proved_dpe *proved_dpe_new "new fplll::proved"( + ZZ_mat[mpz_t] *B,int precision, double eta, double delta) void proved_dpe_delete "delete "(proved_dpe *mem) ctypedef struct proved_double "fplll::proved": int (*LLL)() ZZ_mat* (*GetBase)() - proved_double *proved_double_new "new fplll::proved"(ZZ_mat *B,int precision, double eta, double delta) + proved_double *proved_double_new "new fplll::proved"( + ZZ_mat[mpz_t] *B,int precision, double eta, double delta) void proved_double_delete "delete "(proved_double *mem) # @@ -157,5 +240,6 @@ cdef extern from "fplll/fplllv31.h": int (*LLL)() ZZ_mat* (*GetBase)() - wrapper *wrapper_new "new fplll::wrapper"(ZZ_mat *B,int precision, double eta, double delta) + wrapper *wrapper_new "new fplll::wrapper"( + ZZ_mat[mpz_t] *B, int precision, double eta, double delta) void wrapper_delete "delete "(wrapper *mem) diff --git a/src/sage/libs/fplll/fplll.pyx b/src/sage/libs/fplll/fplll.pyx index 59cd53e5796..ea2fec79bc7 100644 --- a/src/sage/libs/fplll/fplll.pyx +++ b/src/sage/libs/fplll/fplll.pyx @@ -1,16 +1,17 @@ -""" -Wrapper for the fpLLL library by Damien Stehle and David Cade found at +r""" +fpLLL library - http://perso.ens-lyon.fr/damien.stehle/english.html +Wrapper for the fpLLL library by Damien Stehle, Xavier Pujol and David Cade +found at http://perso.ens-lyon.fr/damien.stehle/fplll/. -This wrapper provides access to all fpLLL LLL implementations except -for integer matrices over C ints as opposed to integer matrices over -multi-precision integers. If that feature is required, please let the -authors of this know. +This wrapper provides access to fpLLL's LLL, BKZ and enumeration +implementations. AUTHORS: - -- 2007-10 Martin Albrecht - initial release + +- Martin Albrecht (2007-10) initial release +- Martin Albrecht (2014-03) update to fpLLL 4.0 interface + """ #***************************************************************************** @@ -18,6 +19,7 @@ AUTHORS: # Sage: System for Algebra and Geometry Experimentation # # Copyright (C) 2007 Martin Albrecht +# Copyright (C) 2014 Martin Albrecht # # Distributed under the terms of the GNU General Public License (GPL) # @@ -36,23 +38,87 @@ include "sage/ext/interrupt.pxi" from sage.matrix.matrix_integer_dense cimport Matrix_integer_dense from sage.rings.integer_ring import ZZ from sage.matrix.constructor import matrix +from sage.modules.vector_integer_dense cimport Vector_integer_dense +from sage.misc.superseded import deprecation + +cdef inline int _check_precision(int precision) except -1: + """ + Check whether the provided precision is within valid bounds. If not raise + a ``TypeError``. + + INPUT: + + - ``precision`` -- an integer + + """ + if precision < 0: + raise TypeError("precision must be >= 0") + +cdef inline int _check_eta(float eta) except -1: + r""" + Check whether the provided parameter `\eta` is within valid bounds. If + not raise a ``TypeError``. + + INPUT: + + - ``eta`` -- a floating point number + + """ + if eta < 0.5: + raise TypeError("eta must be >= 0.5") + +cdef inline int _check_delta(float delta) except -1: + """ + Check whether the provided parameter `\delta` is within valid bounds. If + not raise a ``TypeError``. + + INPUT: + + - ``delta`` -- a floating point number + + """ + if delta <= 0.25: + raise TypeError("delta must be > 0.25") + elif delta > 1.0: + raise TypeError("delta must be <= 1.0") + +cdef inline FloatType check_float_type(object float_type): + cdef FloatType float_type_ + + if float_type == "default" or float_type is None: + float_type_= FT_DEFAULT + elif float_type == "double": + float_type_ = FT_DOUBLE + elif float_type == "long double": + float_type_ = FT_LONG_DOUBLE + elif float_type == "dpe": + float_type_ = FT_DPE + elif float_type == "mpfr": + float_type_ = FT_MPFR + else: + raise ValueError("Float type '%s' unknown" % float_type) + return float_type_ cdef class FP_LLL: """ - A basic wrapper class to support conversion to/from Sage integer - matrices and executing the LLL computation. + A basic wrapper class to support conversion to/from Sage integer matrices + and executing LLL/BKZ computations. .. note: - Usually you don't want to create this object yourself but use - the LLL method of the integer matrices. + Usually you don't want to create this object yourself but use the LLL + method of the integer matrices/lattices. + """ + + def __cinit__(self, Matrix_integer_dense A): """ - Construct a new fpLLL wrapper for the matrix A. + Construct a new fpLLL wrapper for the matrix ``A``. INPUT: - A -- a matrix over the integers + + - ``A`` -- a matrix over the integers EXAMPLE:: @@ -63,31 +129,35 @@ cdef class FP_LLL: sage: A = matrix(ZZ, 2, 0) sage: FP_LLL(A).fast() + doctest:...: DeprecationWarning: You can just call LLL() instead + See http://trac.sagemath.org/15976 for details. + sage: A = matrix(ZZ, 0, 2) sage: FP_LLL(A) Traceback (most recent call last): ... ValueError: fpLLL cannot handle matrices with zero rows. + """ cdef int i,j - if A._nrows==0: + + cdef Z_NR[mpz_t] t + + if A._nrows == 0: raise ValueError('fpLLL cannot handle matrices with zero rows.') - self._lattice = ZZ_mat_new(A._nrows,A._ncols) - cdef Z_NR *t + self._lattice = new ZZ_mat[mpz_t](A._nrows,A._ncols) - for i from 0 <= i < A._nrows: - for j from 0 <= j < A._ncols: - t = Z_NR_new() - t.set_mpz_t(A._matrix[i][j]) - self._lattice.Set(i,j,t[0]) - Z_NR_delete(t) + for i in range(A._nrows): + for j in range(A._ncols): + t.set(A._matrix[i][j]) + self._lattice[0][i][j] = t def __dealloc__(self): """ Destroy internal data. """ - ZZ_mat_delete(self._lattice) + del self._lattice def __repr__(self): """ @@ -98,11 +168,16 @@ cdef class FP_LLL: sage: FP_LLL(A) # indirect doctest fpLLL wrapper acting on a 10 x 10 matrix """ - return "fpLLL wrapper acting on a %d x %d matrix"%(self._lattice.GetNumRows(),self._lattice.GetNumCols()) + return "fpLLL wrapper acting on a %d x %d matrix" % ( + self._lattice.getRows(), self._lattice.getCols()) def _sage_(self): """ - Return a Sage representation of self's matrix. + Return a Sage representation of this matrix. + + OUTPUT: + + A Sage representation of this matrix. EXAMPLE:: @@ -114,56 +189,458 @@ cdef class FP_LLL: """ return to_sage(self._lattice) - cdef int _check_precision(self, int precision): + def LLL(self, float delta=LLL_DEF_DELTA, float eta=LLL_DEF_ETA, + method=None, float_type=None, int precision=0, + verbose=False, siegel=False, early_red=False): + r""" + `(\delta,\eta)`-LLL reduce this lattice. + + INPUT: + + - ``delta`` -- (default: ``0.99``) parameter `0.25 < \delta < 1.0` + - ``eta `` -- (default: ``0.51``) parameter `0.5 \leq \eta < + \sqrt{\delta}` + - ``method`` -- (default: ``None``) can be one of the following: + + * ``'wrapper'`` (``None``) + * ``'proved'`` + * ``'fast'`` + * ``'heuristic'`` + + - ``float_type`` -- (default: ``None``) can be one of the following: + + * ``None`` - for automatic choice + * ``'double'`` + * ``'long double'`` + * ``'dpe'`` + * ``'mpfr'`` + + - ``precision`` -- (default: ``0`` for automatic choice) precision + to use + - ``verbose`` -- (default: ``False``) be verbose + - ``siegel`` -- (default: ``False``) use Siegel conditioning + - ``early_red`` -- (default: ``False``) use early reduction + + OUTPUT: + + Nothing is returned but the internal state is modified. + + EXAMPLES:: + + sage: from sage.libs.fplll.fplll import FP_LLL + sage: A = random_matrix(ZZ,10,10); A + [ -8 2 0 0 1 -1 2 1 -95 -1] + [ -2 -12 0 0 1 -1 1 -1 -2 -1] + [ 4 -4 -6 5 0 0 -2 0 1 -4] + [ -6 1 -1 1 1 -1 1 -1 -3 1] + [ 1 0 0 -3 2 -2 0 -2 1 0] + [ -1 1 0 0 1 -1 4 -1 1 -1] + [ 14 1 -5 4 -1 0 2 4 1 1] + [ -2 -1 0 4 -3 1 -5 0 -2 -1] + [ -9 -1 -1 3 2 1 -1 1 -2 1] + [ -1 2 -7 1 0 2 3 -1955 -22 -1] + + sage: F = FP_LLL(A) + sage: F.LLL(method="wrapper") + sage: L = F._sage_(); L + [ 1 0 0 -3 2 -2 0 -2 1 0] + [ -1 1 0 0 1 -1 4 -1 1 -1] + [ -2 0 0 1 0 -2 -1 -3 0 -2] + [ -2 -2 0 -1 3 0 -2 0 2 0] + [ 1 1 1 2 3 -2 -2 0 3 1] + [ -4 1 -1 0 1 1 2 2 -3 3] + [ 1 -3 -7 2 3 -1 0 0 -1 -1] + [ 1 -9 1 3 1 -3 1 -1 -1 0] + [ 8 5 19 3 27 6 -3 8 -25 -22] + [ 172 -25 57 248 261 793 76 -839 -41 376] + sage: L.is_LLL_reduced() + True + sage: L.hermite_form() == A.hermite_form() + True + + sage: set_random_seed(0) + sage: A = random_matrix(ZZ,10,10); A + [ -8 2 0 0 1 -1 2 1 -95 -1] + [ -2 -12 0 0 1 -1 1 -1 -2 -1] + [ 4 -4 -6 5 0 0 -2 0 1 -4] + [ -6 1 -1 1 1 -1 1 -1 -3 1] + [ 1 0 0 -3 2 -2 0 -2 1 0] + [ -1 1 0 0 1 -1 4 -1 1 -1] + [ 14 1 -5 4 -1 0 2 4 1 1] + [ -2 -1 0 4 -3 1 -5 0 -2 -1] + [ -9 -1 -1 3 2 1 -1 1 -2 1] + [ -1 2 -7 1 0 2 3 -1955 -22 -1] + + sage: F = FP_LLL(A) + sage: F.LLL(method="proved") + sage: L = F._sage_(); L + [ 1 0 0 -3 2 -2 0 -2 1 0] + [ -1 1 0 0 1 -1 4 -1 1 -1] + [ -2 0 0 1 0 -2 -1 -3 0 -2] + [ -2 -2 0 -1 3 0 -2 0 2 0] + [ 1 1 1 2 3 -2 -2 0 3 1] + [ -4 1 -1 0 1 1 2 2 -3 3] + [ 1 -3 -7 2 3 -1 0 0 -1 -1] + [ 1 -9 1 3 1 -3 1 -1 -1 0] + [ 8 5 19 3 27 6 -3 8 -25 -22] + [ 172 -25 57 248 261 793 76 -839 -41 376] + + sage: L.is_LLL_reduced() + True + sage: L.hermite_form() == A.hermite_form() + True + + sage: A = random_matrix(ZZ,10,10,x=-(10^5),y=10^5) + sage: f = FP_LLL(A) + sage: f.LLL(method="fast") + sage: L = f._sage_() + sage: L.is_LLL_reduced(eta=0.51,delta=0.99) + True + sage: L.hermite_form() == A.hermite_form() + True + + sage: set_random_seed(0) + sage: A = random_matrix(ZZ,10,10); A + [ -8 2 0 0 1 -1 2 1 -95 -1] + [ -2 -12 0 0 1 -1 1 -1 -2 -1] + [ 4 -4 -6 5 0 0 -2 0 1 -4] + [ -6 1 -1 1 1 -1 1 -1 -3 1] + [ 1 0 0 -3 2 -2 0 -2 1 0] + [ -1 1 0 0 1 -1 4 -1 1 -1] + [ 14 1 -5 4 -1 0 2 4 1 1] + [ -2 -1 0 4 -3 1 -5 0 -2 -1] + [ -9 -1 -1 3 2 1 -1 1 -2 1] + [ -1 2 -7 1 0 2 3 -1955 -22 -1] + + sage: F = FP_LLL(A) + sage: F.LLL(method="fast", early_red=True) + sage: L = F._sage_(); L + [ 1 0 0 -3 2 -2 0 -2 1 0] + [ -1 1 0 0 1 -1 4 -1 1 -1] + [ -2 0 0 1 0 -2 -1 -3 0 -2] + [ -2 -2 0 -1 3 0 -2 0 2 0] + [ 1 1 1 2 3 -2 -2 0 3 1] + [ -4 1 -1 0 1 1 2 2 -3 3] + [ 1 -3 -7 2 3 -1 0 0 -1 -1] + [ 1 -9 1 3 1 -3 1 -1 -1 0] + [ 8 5 19 3 27 6 -3 8 -25 -22] + [ 172 -25 57 248 261 793 76 -839 -41 376] + + sage: L.is_LLL_reduced(eta=0.51,delta=0.99) + True + sage: L.hermite_form() == A.hermite_form() + True + + sage: set_random_seed(0) + sage: A = random_matrix(ZZ,10,10); A + [ -8 2 0 0 1 -1 2 1 -95 -1] + [ -2 -12 0 0 1 -1 1 -1 -2 -1] + [ 4 -4 -6 5 0 0 -2 0 1 -4] + [ -6 1 -1 1 1 -1 1 -1 -3 1] + [ 1 0 0 -3 2 -2 0 -2 1 0] + [ -1 1 0 0 1 -1 4 -1 1 -1] + [ 14 1 -5 4 -1 0 2 4 1 1] + [ -2 -1 0 4 -3 1 -5 0 -2 -1] + [ -9 -1 -1 3 2 1 -1 1 -2 1] + [ -1 2 -7 1 0 2 3 -1955 -22 -1] + + sage: F = FP_LLL(A) + sage: F.LLL(method="heuristic") + sage: L = F._sage_(); L + [ 1 0 0 -3 2 -2 0 -2 1 0] + [ -1 1 0 0 1 -1 4 -1 1 -1] + [ -2 0 0 1 0 -2 -1 -3 0 -2] + [ -2 -2 0 -1 3 0 -2 0 2 0] + [ 1 1 1 2 3 -2 -2 0 3 1] + [ -4 1 -1 0 1 1 2 2 -3 3] + [ 1 -3 -7 2 3 -1 0 0 -1 -1] + [ 1 -9 1 3 1 -3 1 -1 -1 0] + [ 8 5 19 3 27 6 -3 8 -25 -22] + [ 172 -25 57 248 261 793 76 -839 -41 376] + + sage: L.is_LLL_reduced(eta=0.51,delta=0.99) + True + sage: L.hermite_form() == A.hermite_form() + True + + sage: set_random_seed(0) + sage: A = random_matrix(ZZ,10,10); A + [ -8 2 0 0 1 -1 2 1 -95 -1] + [ -2 -12 0 0 1 -1 1 -1 -2 -1] + [ 4 -4 -6 5 0 0 -2 0 1 -4] + [ -6 1 -1 1 1 -1 1 -1 -3 1] + [ 1 0 0 -3 2 -2 0 -2 1 0] + [ -1 1 0 0 1 -1 4 -1 1 -1] + [ 14 1 -5 4 -1 0 2 4 1 1] + [ -2 -1 0 4 -3 1 -5 0 -2 -1] + [ -9 -1 -1 3 2 1 -1 1 -2 1] + [ -1 2 -7 1 0 2 3 -1955 -22 -1] + + sage: F = FP_LLL(A) + sage: F.LLL(method="heuristic", early_red=True) + sage: L = F._sage_(); L + [ 1 0 0 -3 2 -2 0 -2 1 0] + [ -1 1 0 0 1 -1 4 -1 1 -1] + [ -2 0 0 1 0 -2 -1 -3 0 -2] + [ -2 -2 0 -1 3 0 -2 0 2 0] + [ 1 1 1 2 3 -2 -2 0 3 1] + [ -4 1 -1 0 1 1 2 2 -3 3] + [ 1 -3 -7 2 3 -1 0 0 -1 -1] + [ 1 -9 1 3 1 -3 1 -1 -1 0] + [ 8 5 19 3 27 6 -3 8 -25 -22] + [ 172 -25 57 248 261 793 76 -839 -41 376] + + sage: L.is_LLL_reduced(eta=0.51,delta=0.99) + True + sage: L.hermite_form() == A.hermite_form() + True """ - Check whether the provided precision is within valid - bounds. If not raise a TypeError. + _check_delta(delta) + _check_eta(eta) + _check_precision(precision) + + cdef LLLMethod method_ + if method == "wrapper" or method is None: + method_ = LM_WRAPPER + elif method == "proved": + method_ = LM_PROVED + elif method == "heuristic": + method_ = LM_HEURISTIC + elif method == "fast": + method_ = LM_FAST + else: + raise ValueError("method '{}' unknown".format(method)) + + cdef int flags = LLL_DEFAULT + + if verbose: + flags |= LLL_VERBOSE + if early_red: + flags |= LLL_EARLY_RED + if siegel: + flags |= LLL_SIEGEL + + if float_type is None and method_ == LM_FAST: + float_type = "double" + + if method_ == LM_WRAPPER and check_float_type(float_type) != FT_DEFAULT: + raise ValueError("fpLLL's LLL wrapper function requires " + "float type None") + if method_ == LM_FAST and \ + check_float_type(float_type) not in (FT_DOUBLE, FT_LONG_DOUBLE): + raise ValueError("fpLLL's LLL fast function requires " + "float type 'double' or 'long double'") + + sig_on() + cdef int r = lllReduction(self._lattice[0], delta, eta, method_, + check_float_type(float_type), precision, flags) + sig_off() + if r: + raise RuntimeError( str(getRedStatusStr(r)) ) + + def BKZ(self, int block_size, double delta=LLL_DEF_DELTA, + float_type=None, int precision=0, int max_loops=0, int max_time=0, + verbose=False, no_lll=False, bounded_lll=False, auto_abort=False): + r""" + Run BKZ reduction. INPUT: - precision -- an integer + + - ``block_size`` -- an integer from 1 to ``nrows`` + - ``delta`` -- (default: ``0.99``) LLL parameter `0.25 < \delta < 1.0` + - ``float_type`` -- (default: ``None``) can be one of the following: + + * ``None`` - for automatic choice + * ``'double'`` + * ``'long double'`` + * ``'dpe'`` + * ``'mpfr'`` + + - ``verbose`` -- (default: ``False``) be verbose + - ``no_lll`` -- (default: ``False``) to use LLL + - ``bounded_lll`` -- (default: ``False``) bounded LLL + - ``precision`` -- (default: ``0`` for automatic choice) bit + precision to use if ``fp`` is ``'rr'`` + - ``max_loops`` -- (default: ``0`` for no restriction) maximum + number of full loops + - ``max_time`` -- (default: ``0`` for no restricion) stop after + time seconds (up to loop completion) + - ``auto_abort`` -- (default: ``False``) heuristic, stop when the + average slope of `\log(\lVert b_i^* \rVert)` does not decrease + fast enough + + OUTPUT: + + Nothing is returned but the internal state is modified. + + EXAMPLES:: + + sage: from sage.libs.fplll.fplll import FP_LLL + sage: A = sage.crypto.gen_lattice(type='random', n=1, m=60, q=2^90, seed=42) + sage: F = FP_LLL(A) + + sage: F.LLL() + sage: F._sage_()[0].norm().n() + 7.810... + + sage: F.BKZ(10) + sage: F._sage_()[0].norm().n() + 6.164... + """ - if precision < 0: - raise TypeError, "precision must be >= 0" + if block_size <= 0: + raise ValueError("block size must be > 0") + if max_loops < 0: + raise ValueError("maximum number of loops must be >= 0") + if max_time < 0: + raise ValueError("maximum time must be >= 0") + + _check_delta(delta) + _check_precision(precision) + + cdef BKZParam o = BKZParam() + + o.b = self._lattice + o.delta = delta + o.blockSize = block_size + o.floatType = check_float_type(float_type) + o.precision = precision + o.flags = BKZ_DEFAULT + + if verbose: + o.flags |= BKZ_VERBOSE + if no_lll: + o.flags |= BKZ_NO_LLL + if bounded_lll: + o.flags |= BKZ_BOUNDED_LLL + if auto_abort: + o.flags |= BKZ_AUTO_ABORT + if max_loops: + o.flags |= BKZ_MAX_LOOPS + o.maxLoops = max_loops + if max_time: + o.flags |= BKZ_MAX_TIME + o.maxTime = max_time - cdef int _check_eta(self, float eta): + sig_on() + cdef int r = bkzReduction(o) + sig_off() + if r: + raise RuntimeError( str(getRedStatusStr(r)) ) + + def HKZ(self): """ - Check whether the provided parameter $\eta$ is within valid - bounds. If not raise a TypeError. + Run HKZ reduction. + + OUTPUT: + + Nothing is returned but the internal state is modified. + + EXAMPLE:: + + sage: from sage.libs.fplll.fplll import FP_LLL + sage: A = sage.crypto.gen_lattice(type='random', n=1, m=10, q=2^60, seed=42) + sage: F = FP_LLL(A) + sage: F.HKZ() + sage: F._sage_() + [ -8 27 7 19 10 -5 14 34 4 -18] + [-22 23 3 -14 11 30 -12 26 17 26] + [-20 6 -18 33 -26 16 8 -15 -14 -26] + [ -2 30 9 -30 -28 -19 -7 -28 12 -15] + [-16 1 25 -23 -11 -21 -39 4 -34 -13] + [-27 -2 -24 -67 32 -13 -6 0 15 -4] + [ 9 -12 7 31 22 -7 -63 11 27 36] + [ 14 -4 0 -21 -17 -7 -9 35 79 -22] + [-17 -16 54 21 0 -17 28 -45 -6 12] + [ 43 16 6 30 24 17 -39 -46 -18 -22] - INPUT: - eta -- a floating point number """ - if eta < 0.5: - raise TypeError, "eta must be >= 0.5" + sig_on() + cdef int r = hkzReduction(self._lattice[0]) + sig_off() + if r: + raise RuntimeError( str(getRedStatusStr(r)) ) - cdef int _check_delta(self, float delta): + def shortest_vector(self, method=None): """ - Check whether the provided parameter $\delta$ is within valid - bounds. If not raise a TypeError. + Return a shortest vector. INPUT: - delta -- a floating point number + + - ``method`` - (default: ``"proved"``) ``"proved"`` or ``"fast"`` + + OUTPUT: + + A shortest non-zero vector for this lattice. + + EXAMPLE:: + + sage: from sage.libs.fplll.fplll import FP_LLL + sage: A = sage.crypto.gen_lattice(type='random', n=1, m=40, q=2^60, seed=42) + sage: F = FP_LLL(A) + sage: F.shortest_vector('proved') == F.shortest_vector('fast') + True + """ - if delta <= 0.25: - raise TypeError, "delta must be > 0.25" - if delta > 1.0: - raise TypeError, "delta must be <= 1.0" + cdef SVPMethod method_ + if method == "proved" or method is None: + method_ = SVPM_PROVED + elif method == "fast": + method_ = SVPM_FAST + else: + raise ValueError("method '{}' unknown".format(method)) + + cdef int r + + cdef ZZ_mat[mpz_t] u + + r = lllReduction(self._lattice[0], u, LLL_DEF_DELTA, LLL_DEF_ETA, + LM_WRAPPER, FT_DEFAULT, 0, LLL_DEFAULT) + if r: + raise RuntimeError( str(getRedStatusStr(r)) ) + + cdef vector[Z_NR[mpz_t]] solCoord + cdef vector[Z_NR[mpz_t]] solution + + sig_on() + r = shortestVector(self._lattice[0], solCoord, method_) + sig_off() + if r: + raise RuntimeError("fpLLL's SVP solver returned an error ({:d})".format(r)) + + vectMatrixProduct(solution, solCoord, self._lattice[0]) + + VS = ZZ**self._lattice.getCols() + cdef Vector_integer_dense v = Vector_integer_dense(VS, 0) + + for i in range(len(v)): + mpz_set(v._entries[i], solution[i].getData()) + return v + + # + # Deprecated interfaces + # def wrapper(self, int precision=0, float eta=0.51, float delta=0.99): - """ + r""" Perform LLL reduction using fpLLL's \code{wrapper} implementation. This implementation invokes a sequence of floating point LLL computations such that - * the computation is reasonably fast (based on an heuristic model) - * the result is proven to be LLL reduced. + + * the computation is reasonably fast (based on an heuristic model) + * the result is proven to be LLL reduced. INPUT: - precision -- internal precision (default: auto) - eta -- LLL parameter eta with 1/2 <= eta < sqrt(delta) (default: 0.51) - delta -- LLL parameter delta with 1/4 < delta <= 1 (default: 0.99) + + - ``precision`` -- (default: auto) internal precision + - ``eta`` -- (default: ``0.51``) LLL parameter `\eta` with + `1/2 \leq \eta < \sqrt{\delta}` + - ``delta`` -- (default: ``0.99``) LLL parameter `\delta` with + `1/4 < \delta \leq 1` OUTPUT: - nothing is returned but the internal state modified. + + Nothing is returned but the internal state is modified. EXAMPLE:: @@ -195,12 +672,14 @@ cdef class FP_LLL: [ 172 -25 57 248 261 793 76 -839 -41 376] sage: L.is_LLL_reduced() True - sage: L.echelon_form() == A.echelon_form() + sage: L.hermite_form() == A.hermite_form() True """ - self._check_precision(precision) - self._check_eta(eta) - self._check_delta(delta) + deprecation(15976, 'You can just call LLL() instead') + + _check_precision(precision) + _check_eta(eta) + _check_delta(delta) cdef int ret = 0 cdef wrapper *w = wrapper_new(self._lattice, 0, eta, delta) @@ -209,26 +688,34 @@ cdef class FP_LLL: sig_off() wrapper_delete(w) if ret != 0: - raise RuntimeError, "fpLLL returned %d != 0"%ret + raise RuntimeError("fpLLL returned {:d} != 0".format(ret)) def proved(self, int precision=0, float eta=0.51, float delta=0.99, implementation=None): """ - Perform LLL reduction using fpLLL's \code{proved} + Perform LLL reduction using fpLLL's ``proved`` implementation. This implementation is the only provable correct floating point implementation in the free software - world. Provability is only guaranteed if the 'mpfr' + world. Provability is only guaranteed if the ``'mpfr'`` implementation is chosen. INPUT: - precision -- internal precision (default: auto) - eta -- LLL parameter eta with 1/2 <= eta < sqrt(delta) (default: 0.51) - delta -- LLL parameter delta with 1/4 < delta <= 1 (default: 0.99) - implementation -- floating point implementation in ('double', 'dpe', 'mpfr') - (default: 'mpfr') + + - ``precision`` -- (default: auto) internal precision + - ``eta`` -- (default: ``0.51``) LLL parameter `\eta` with + `1/2 \leq \eta < \sqrt{δ}` + - ``delta`` -- (default: ``0.99``) LLL parameter `\delta` with + `1/4 < \delta \leq 1` + - ``implementation`` -- (default: ``"mpfr"``) which floating point + implementation to use, can be one of the following: + + * ``"double"`` + * ``"dpe"`` + * ``"mpfr"`` OUTPUT: - nothing is returned but the internal state modified. + + Nothing is returned but the internal state modified. EXAMPLE:: @@ -261,12 +748,14 @@ cdef class FP_LLL: sage: L.is_LLL_reduced() True - sage: L.echelon_form() == A.echelon_form() + sage: L.hermite_form() == A.hermite_form() True """ - self._check_precision(precision) - self._check_eta(eta) - self._check_delta(delta) + deprecation(15976, 'You can just call LLL() instead') + + _check_precision(precision) + _check_eta(eta) + _check_delta(delta) cdef proved_double *pdouble cdef proved_mpfr *pmpfr @@ -296,22 +785,26 @@ cdef class FP_LLL: proved_mpfr_delete(pmpfr) if ret != 0: - raise RuntimeError, "fpLLL returned %d != 0"%ret + raise RuntimeError("fpLLL returned {:d} != 0".format(ret)) def fast(self, int precision=0, float eta=0.51, float delta=0.99, implementation=None): - """ - Perform LLL reduction using fpLLL's \code{fast} - implementation. This implementation is the fastest floating - point implementation available in the free software world. + r""" + Perform LLL reduction using fpLLL's fast + implementation. This implementation is the fastest floating + point implementation currently available in the free software world. INPUT: - precision -- internal precision (default: auto) - eta -- LLL parameter eta with 1/2 <= eta < sqrt(delta) (default: 0.51) - delta -- LLL parameter delta with 1/4 < delta <= 1 (default: 0.99) - implementation -- ignored + + - ``precision`` -- (default: auto) internal precision + - ``eta`` -- (default: ``0.51``) LLL parameter `\eta` with + `1/2 \leq \eta < \sqrt{\delta}` + - ``delta`` -- (default: ``0.99``) LLL parameter `\delta` with + `1/4 < \delta \leq 1` + - ``implementation`` -- ignored OUTPUT: - nothing is returned but the internal state modified. + + Nothing is returned but the internal is state modified. EXAMPLE:: @@ -322,12 +815,14 @@ cdef class FP_LLL: sage: L = f._sage_() sage: L.is_LLL_reduced(eta=0.51,delta=0.99) True - sage: L.echelon_form() == A.echelon_form() + sage: L.hermite_form() == A.hermite_form() True """ - self._check_precision(precision) - self._check_eta(eta) - self._check_delta(delta) + deprecation(15976, 'You can just call LLL() instead') + + _check_precision(precision) + _check_eta(eta) + _check_delta(delta) cdef fast_double *pdouble cdef int ret = 0 @@ -339,8 +834,8 @@ cdef class FP_LLL: fast_double_delete(pdouble) def fast_early_red(self, int precision=0, float eta=0.51, float delta=0.99, implementation=None): - """ - Perform LLL reduction using fpLLL's \code{fast} + r""" + Perform LLL reduction using fpLLL's fast implementation with early reduction. This implementation inserts some early reduction steps inside @@ -350,13 +845,22 @@ cdef class FP_LLL: integer relation detection problems. INPUT: - precision -- internal precision (default: auto) - eta -- LLL parameter eta with 1/2 <= eta < sqrt(delta) (default: 0.51) - delta -- LLL parameter delta with 1/4 < delta <= 1 (default: 0.99) - implementation -- ignored + + - ``precision`` -- (default: auto) internal precision + - ``eta`` -- (default: ``0.51``) LLL parameter `\eta` with + `1/2 \leq \eta < \sqrt{\delta}` + - ``delta`` -- (default: ``0.99``) LLL parameter `\delta` with + `1/4 < \delta \leq 1` + - ``implementation`` -- (default: ``"mpfr"``) which floating point + implementation to use, can be one of the following: + + * ``"double"`` + * ``"dpe"`` + * ``"mpfr"`` OUTPUT: - nothing is returned but the internal state modified. + + Nothing is returned but the internal state modified. EXAMPLE:: @@ -389,12 +893,14 @@ cdef class FP_LLL: sage: L.is_LLL_reduced(eta=0.51,delta=0.99) True - sage: L.echelon_form() == A.echelon_form() + sage: L.hermite_form() == A.hermite_form() True """ - self._check_precision(precision) - self._check_eta(eta) - self._check_delta(delta) + deprecation(15976, 'You can just call LLL() instead') + + _check_precision(precision) + _check_eta(eta) + _check_delta(delta) cdef int ret = 0 cdef fast_early_red_double *pdouble @@ -406,19 +912,27 @@ cdef class FP_LLL: fast_early_red_double_delete(pdouble) def heuristic(self, int precision=0, float eta=0.51, float delta=0.99, implementation=None): - """ - Perform LLL reduction using fpLLL's \code{heuristic} + r""" + Perform LLL reduction using fpLLL's heuristic implementation. INPUT: - precision -- internal precision (default: auto) - eta -- LLL parameter eta with 1/2 <= eta < sqrt(delta) (default: 0.51) - delta -- LLL parameter delta with 1/4 < delta <= 1 (default: 0.99) - implementation -- floating point implementation in ('double', 'dpe', 'mpfr') - (default: 'mpfr') + + - ``precision`` -- (default: auto) internal precision + - ``eta`` -- (default: ``0.51``) LLL parameter `\eta` with + `1/2 \leq \eta < \sqrt{\delta}` + - ``delta`` -- (default: ``0.99``) LLL parameter `\delta` with + `1/4 < \delta \leq 1` + - ``implementation`` -- (default: ``"mpfr"``) which floating point + implementation to use, can be one of the following: + + * ``"double"`` + * ``"dpe"`` + * ``"mpfr"`` OUTPUT: - nothing is returned but the internal state modified. + + Nothing is returned but the internal state modified. EXAMPLE:: @@ -451,12 +965,14 @@ cdef class FP_LLL: sage: L.is_LLL_reduced(eta=0.51,delta=0.99) True - sage: L.echelon_form() == A.echelon_form() + sage: L.hermite_form() == A.hermite_form() True """ - self._check_precision(precision) - self._check_eta(eta) - self._check_delta(delta) + deprecation(15976, 'You can just call LLL() instead') + + _check_precision(precision) + _check_eta(eta) + _check_delta(delta) cdef heuristic_double *pdouble cdef heuristic_mpfr *pmpfr @@ -486,8 +1002,8 @@ cdef class FP_LLL: heuristic_mpfr_delete(pmpfr) def heuristic_early_red(self, int precision=0, float eta=0.51, float delta=0.99, implementation=None): - """ - Perform LLL reduction using fpLLL's \code{heuristic} + r""" + Perform LLL reduction using fpLLL's heuristic implementation with early reduction. This implementation inserts some early reduction steps inside @@ -497,14 +1013,22 @@ cdef class FP_LLL: or integer relation detection problems. INPUT: - precision -- internal precision (default: auto) - eta -- LLL parameter eta with 1/2 <= eta < sqrt(delta) (default: 0.51) - delta -- LLL parameter delta with 1/4 < delta <= 1 (default: 0.99) - implementation -- floating point implementation in ('double', 'dpe', 'mpfr') - (default: 'mpfr') + + - ``precision`` -- (default: auto) internal precision + - ``eta`` -- (default: ``0.51``) LLL parameter `\eta` with + `1/2 \leq \eta < \sqrt{\delta}` + - ``delta`` -- (default: ``0.99``) LLL parameter `\delta` with + `1/4 < \delta \leq 1` + - ``implementation`` -- (default: ``"mpfr"``) which floating point + implementation to use, can be one of the following: + + * ``"double"`` + * ``"dpe"`` + * ``"mpfr"`` OUTPUT: - nothing is returned but the internal state modified. + + Nothing is returned but the internal state modified. EXAMPLE:: @@ -537,12 +1061,14 @@ cdef class FP_LLL: sage: L.is_LLL_reduced(eta=0.51,delta=0.99) True - sage: L.echelon_form() == A.echelon_form() + sage: L.hermite_form() == A.hermite_form() True """ - self._check_precision(precision) - self._check_eta(eta) - self._check_delta(delta) + deprecation(15976, 'You can just call LLL() instead') + + _check_precision(precision) + _check_eta(eta) + _check_delta(delta) cdef heuristic_early_red_double *pdouble cdef heuristic_early_red_mpfr *pmpfr @@ -571,14 +1097,23 @@ cdef class FP_LLL: sig_off() heuristic_early_red_mpfr_delete(pmpfr) +# +# Lattice basis generators +# + def gen_intrel(int d, int b): r""" - Return a $(d+1 x d)$-dimensional knapsack-type random lattice, - where the $x_i$s are random $b$ bits integers. + Return a `(d+1 \times d)`-dimensional knapsack-type random lattice, + where the `x_i`'s are random ``b`` bits integers. INPUT: - d -- dimension - b -- bitsize of entries + + - ``d`` -- dimension + - ``b`` -- bitsize of entries + + OUTPUT: + + An integer lattice. EXAMPLE:: @@ -608,25 +1143,31 @@ def gen_intrel(int d, int b): [-1 -1 0 0 1 0 2 0 0 0 -2] sage: L.is_LLL_reduced() True - sage: L.echelon_form() == A.echelon_form() + sage: L.hermite_form() == A.hermite_form() True + """ - cdef ZZ_mat *A = ZZ_mat_new(d,d+1) + cdef ZZ_mat[mpz_t] *A = new ZZ_mat[mpz_t](d,d+1) A.gen_intrel(b) B = to_sage(A) - ZZ_mat_delete(A) + del A return B def gen_simdioph(int d, int b, int b2): - r""" - Return a $d$-dimensional simultaneous diophantine approximation - random lattice, where the $d$ $x_i$s are random $b$ bits integers. + """ + Return a ``d``-dimensional simultaneous diophantine approximation random + lattice, where the ``d`` `x_i`'s are random ``b`` bits integers. INPUT: - d -- dimension - b -- bitsize of entries - b2 -- bitsize of entries + + - ``d`` -- dimension + - ``b`` -- bitsize of entries + - ``b2`` -- bitsize of entries + + OUTPUT: + + An integer lattice. EXAMPLE:: @@ -656,25 +1197,31 @@ def gen_simdioph(int d, int b, int b2): [-192 760 152 -272 8 -272 48 264 -104 8] sage: L.is_LLL_reduced() True - sage: L.echelon_form() == A.echelon_form() + sage: L.hermite_form() == A.hermite_form() True + """ - cdef ZZ_mat *A = ZZ_mat_new(d,d) + cdef ZZ_mat[mpz_t] *A = new ZZ_mat[mpz_t](d,d) A.gen_simdioph(b, b2) B = to_sage(A) - ZZ_mat_delete(A) + del A return B def gen_uniform(int nr, int nc, int b): r""" - Return a (nr x nc) matrix where the entries are random $b$ bits - integers. + Return a `(nr \times nc)` matrix where the entries are random ``b`` + bits integers. INPUT: - nr -- row dimension - nc -- column dimension - b-- bitsize of entries + + - ``nr`` -- row dimension + - ``nc`` -- column dimension + - ``b`` -- bitsize of entries + + OUTPUT: + + An integer lattice. EXAMPLE:: @@ -704,37 +1251,40 @@ def gen_uniform(int nr, int nc, int b): [ 713 -1115 1887 -563 733 2443 816 972 876 -2074] sage: L.is_LLL_reduced() True - sage: L.echelon_form() == A.echelon_form() + sage: L.hermite_form() == A.hermite_form() True + """ - cdef ZZ_mat *A = ZZ_mat_new(nr,nc) + cdef ZZ_mat[mpz_t] *A = new ZZ_mat[mpz_t](nr,nc) A.gen_uniform(b) B = to_sage(A) - ZZ_mat_delete(A) + del A return B def gen_ntrulike(int d, int b, int q): - """ - Generate a ntru-like lattice of dimension (2*d x 2*d), with the - coefficients $h_i$ chosen as random b bits integers and parameter - q: - - \begin{verbatim} - [[ 1 0 ... 0 h0 h1 ... h_{d-1} ] - [ 0 1 ... 0 h1 h2 ... h0 ] - [ ................................ ] - [ 0 0 ... 1 h_{d-1} h0 ... h_{d-1} ] - [ 0 0 ... 0 q 0 ... 0 ] - [ 0 0 ... 0 0 q ... 0 ] - [ ................................ ] - [ 0 0 ... 0 0 0 ... q ]] - \end{verbatim} + r""" + Generate a NTRU-like lattice of dimension `(2d \times 2d)`, with the + coefficients `h_i` chosen as random `b` bits integers and parameter `q`:: + + [[ 1 0 ... 0 h0 h1 ... h_{d-1} ] + [ 0 1 ... 0 h1 h2 ... h0 ] + [ ................................ ] + [ 0 0 ... 1 h_{d-1} h0 ... h_{d-1} ] + [ 0 0 ... 0 q 0 ... 0 ] + [ 0 0 ... 0 0 q ... 0 ] + [ ................................ ] + [ 0 0 ... 0 0 0 ... q ]] INPUT: - d -- dimension - b -- bitsize of entries - q -- see above + + - ``d`` -- dimension + - ``b`` -- bitsize of entries + - ``q`` -- the `q` above + + OUTPUT: + + An integer lattice. EXAMPLE:: @@ -764,24 +1314,30 @@ def gen_ntrulike(int d, int b, int q): [ 1 -2 -1 -2 1 0 1 1 0 1] sage: L.is_LLL_reduced() True - sage: L.echelon_form() == A.echelon_form() + sage: L.hermite_form() == A.hermite_form() True + """ - cdef ZZ_mat *A = ZZ_mat_new(2*d,2*d) + cdef ZZ_mat[mpz_t] *A = new ZZ_mat[mpz_t](2*d,2*d) A.gen_ntrulike(b, q) B = to_sage(A) - ZZ_mat_delete(A) + del A return B def gen_ntrulike2(int d, int b, int q): - r""" - Like \code{gen_ntrulike} but with the $q$ vectors coming first. + """ + Like :func:`gen_ntrulike` but with the `q` vectors coming first. INPUT: - d -- dimension - b -- bitsize of entries - q -- see gen_ntrulike + + - ``d`` -- dimension + - ``b`` -- bitsize of entries + - ``q`` -- see :func:`gen_ntrulike` + + OUTPUT: + + An integer lattice. EXAMPLE:: @@ -811,25 +1367,31 @@ def gen_ntrulike2(int d, int b, int q): [-2 1 1 0 1 -3 -2 -1 0 1] sage: L.is_LLL_reduced() True - sage: L.echelon_form() == A.echelon_form() + sage: L.hermite_form() == A.hermite_form() True + """ - cdef ZZ_mat *A = ZZ_mat_new(2*d,2*d) + cdef ZZ_mat[mpz_t] *A = new ZZ_mat[mpz_t](2*d,2*d) A.gen_ntrulike2(b,q) B = to_sage(A) - ZZ_mat_delete(A) + del A return B def gen_ajtai(int d, float alpha): r""" - Return Ajtai-like $(d x d)$-matrix of floating point parameter - $\alpha$. The matrix is lower-triangular, $B_{imi}$ is - $~2^{(d-i+1)^\alpha}$ and $B_{i,j}$ is $~B_{j,j}/2$ for $jmatrix(ZZ, - A.GetNumRows(), - A.GetNumCols()) + cdef Matrix_integer_dense B = matrix(ZZ, A.getRows(), A.getCols()) - for i from 0 <= i < A.GetNumRows(): - for j from 0 <= j < A.GetNumCols(): + for i from 0 <= i < A.getRows(): + for j from 0 <= j < A.getCols(): t = &B._matrix[i][j] - mpz_set(t[0], A.Get(i,j).GetData()) + mpz_set(t[0], A[0][i][j].getData()) return B diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index 5c9696a8ee3..38daf8e2244 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1203,13 +1203,19 @@ cdef class GapElement_Integer(GapElement): 1361129467683753853853498429727072845824 sage: large.sage() 1361129467683753853853498429727072845824 + + sage: huge = libgap.eval('10^9999'); huge # gap abbreviates very long ints + + sage: huge.sage().ndigits() + 10000 """ if ring is None: ring = ZZ if self.is_C_int(): return ring(libGAP_INT_INTOBJ(self.value)) else: - return ring(str(self)) + string = self.String().sage() + return ring(string) ############################################################################ diff --git a/src/sage/libs/gmp/types.pxd b/src/sage/libs/gmp/types.pxd index 5c51c4b49ef..b0ac7f608b7 100644 --- a/src/sage/libs/gmp/types.pxd +++ b/src/sage/libs/gmp/types.pxd @@ -4,6 +4,7 @@ cdef extern from "gmp.h": # Underlying typedefs ctypedef unsigned long mp_limb_t + ctypedef unsigned long mp_bitcnt_t ctypedef long mp_size_t ctypedef long mp_exp_t diff --git a/src/sage/libs/lcalc/lcalc_Lfunction.pyx b/src/sage/libs/lcalc/lcalc_Lfunction.pyx index 3475e202450..10bfb1f84ac 100644 --- a/src/sage/libs/lcalc/lcalc_Lfunction.pyx +++ b/src/sage/libs/lcalc/lcalc_Lfunction.pyx @@ -900,7 +900,7 @@ def Lfunction_from_character(chi, type="complex"): raise TypeError("Dirichlet character is not primitive") modulus=chi.modulus() - if (chi(-1) == 1): + if chi.is_even(): a=0 else: a=1 diff --git a/src/sage/libs/mpfr.pxd b/src/sage/libs/mpfr.pxd index a515157c1ba..47514855be4 100644 --- a/src/sage/libs/mpfr.pxd +++ b/src/sage/libs/mpfr.pxd @@ -246,6 +246,12 @@ cdef extern from "mpfr.h": long MPFR_VERSION_NUM (major, minor, patchlevel) char * mpfr_get_patches () + # Printf-Like Functions + int mpfr_printf (const char*, ...) + int mpfr_asprintf (char**, const char*, ...) + int mpfr_sprintf (char**, const char*, ...) + int mpfr_snprintf (char*, size_t, const char*, ...) + # Rounding Mode Related Functions void mpfr_set_default_rounding_mode (mp_rnd_t rnd) mp_rnd_t mpfr_get_default_rounding_mode () diff --git a/src/sage/logic/booleval.py b/src/sage/logic/booleval.py index ae05fc248e0..9252ec8342b 100644 --- a/src/sage/logic/booleval.py +++ b/src/sage/logic/booleval.py @@ -1,5 +1,5 @@ r""" -Module used to evaluate boolean formulas +Evaluation of Boolean Formulas AUTHORS: @@ -46,19 +46,17 @@ def eval_formula(tree, vdict): INPUT: - ``tree`` -- a list of three elements corresponding to a branch of a - parse tree. + parse tree - - ``vdict`` -- a dictionary containing variable keys and boolean values. + - ``vdict`` -- a dictionary containing variable keys and boolean values OUTPUT: - The result of the evaluation as a boolean value + The result of the evaluation as a boolean value. EXAMPLES: - This example illustrates evaluating a boolean formula. - - :: + This example illustrates evaluating a boolean formula:: sage: import sage.logic.booleval as booleval sage: t = ['|', ['&', 'a', 'b'], ['&', 'a', 'c']] @@ -79,34 +77,28 @@ def eval_formula(tree, vdict): def eval_f(tree): r""" - Evaluate the tree + Evaluate the tree. INPUT: - ``tree`` -- a list of three elements corresponding to a branch of a - parse tree. + parse tree OUTPUT: - The result of the evaluation as a boolean value + The result of the evaluation as a boolean value. EXAMPLES: - This example illustrates how to evaluate a parse tree. - - :: + This example illustrates how to evaluate a parse tree:: sage: import sage.logic.booleval as booleval sage: booleval.eval_f(['&', True, False]) False - :: - sage: booleval.eval_f(['^', True, True]) False - :: - sage: booleval.eval_f(['|', False, True]) True """ @@ -118,33 +110,27 @@ def eval_op(op, lv, rv): INPUT: - - ``op`` -- a string or character representing a boolean operator. + - ``op`` -- a string or character representing a boolean operator - - ``lv`` -- a boolean or variable. + - ``lv`` -- a boolean or variable - - ``rv`` -- a boolean or variable. + - ``rv`` -- a boolean or variable OUTPUT: - The evaluation of ``lv op rv`` as a boolean value + The evaluation of ``lv op rv`` as a boolean value. EXAMPLES: - We can evaluate an operator given the values on either side. - - :: + We can evaluate an operator given the values on either side:: sage: import sage.logic.booleval as booleval sage: booleval.eval_op('&', True, False) False - :: - sage: booleval.eval_op('^', True, True) False - :: - sage: booleval.eval_op('|', False, True) True """ diff --git a/src/sage/logic/boolformula.py b/src/sage/logic/boolformula.py index 3bea9e4b9e3..26058cd8823 100644 --- a/src/sage/logic/boolformula.py +++ b/src/sage/logic/boolformula.py @@ -1,5 +1,5 @@ r""" -Module that creates boolean formulas as instances of the BooleanFormula class. +Boolean Formulas Formulas consist of the operators ``&``, ``|``, ``~``, ``^``, ``->``, ``<->``, corresponding to ``and``, ``or``, ``not``, ``xor``, ``if...then``, ``if and @@ -9,7 +9,8 @@ EXAMPLES: -Create boolean formulas and combine them with ifthen() method:: +Create boolean formulas and combine them with +:meth:`~sage.logic.boolformula.BooleanFormula.ifthen()` method:: sage: import sage.logic.propcalc as propcalc sage: f = propcalc.formula("a&((b|c)^a->c)<->b") @@ -149,35 +150,32 @@ class BooleanFormula(object): - __expression = "" - __tree = [] - __vars_order = [] + """ + Boolean formulas. - def __init__(self, exp, tree, vo): - r""" - Initialize the data fields + INPUT: - INPUT: + - ``self`` -- calling object - - ``self`` -- calling object + - ``exp`` -- a string; this contains the boolean expression + to be manipulated - - ``exp`` -- a string. This contains the boolean expression - to be manipulated + - ``tree`` -- a list; this contains the parse tree of the expression. - - ``tree`` -- a list. This contains the parse tree of the expression. - - - ``vo`` -- a list. This contains the variables in the expression, in the - order that they appear. Each variable only occurs once in the list. - - OUTPUT: + - ``vo`` -- a list; this contains the variables in the expression, in the + order that they appear; each variable only occurs once in the list + """ + __expression = "" + __tree = [] + __vars_order = [] - None + def __init__(self, exp, tree, vo): + r""" + Initialize the data fields. EXAMPLES: - This example illustrates the creation of a statement. - - :: + This example illustrates the creation of a statement:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a&b|~(c|a)") @@ -192,19 +190,11 @@ def __repr__(self): r""" Return a string representation of this statement. - INPUT: - - - ``self`` -- calling object - OUTPUT: A string representation of calling statement - EXAMPLES: - - This example illustrates how a statement is represented with __repr__. - - :: + EXAMPLES:: sage: import sage.logic.propcalc as propcalc sage: propcalc.formula("man->monkey&human") @@ -216,27 +206,17 @@ def _latex_(self): r""" Return a LaTeX representation of this statement. - INPUT: - - - ``self`` -- calling object - OUTPUT: A string containing the latex code for the statement - EXAMPLES: - - This example shows how to get the latex code for a boolean formula. - - :: + EXAMPLES:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("man->monkey&human") sage: latex(s) man\rightarrow monkey\wedge human - :: - sage: f = propcalc.formula("a & ((~b | c) ^ a -> c) <-> ~b") sage: latex(f) a\wedge ((\neg b\vee c)\oplus a\rightarrow c)\leftrightarrow \neg b @@ -248,11 +228,7 @@ def _latex_(self): def polish_notation(self): r""" - Convert the calling boolean formula into polish notation - - INPUT: - - - ``self`` -- calling object + Convert the calling boolean formula into polish notation. OUTPUT: @@ -260,17 +236,13 @@ def polish_notation(self): EXAMPLES: - This example illustrates converting a formula to polish notation. - - :: + This example illustrates converting a formula to polish notation:: sage: import sage.logic.propcalc as propcalc sage: f = propcalc.formula("~~a|(c->b)") sage: f.polish_notation() '|~~a->cb' - :: - sage: g = propcalc.formula("(a|~b)->c") sage: g.polish_notation() '->|a~bc' @@ -285,19 +257,14 @@ def tree(self): r""" Return the parse tree of this boolean expression. - INPUT: - - - ``self`` -- calling object - OUTPUT: The parse tree as a nested list EXAMPLES: - This example illustrates how to find the parse tree of a boolean formula. - - :: + This example illustrates how to find the parse tree of a boolean + formula:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("man -> monkey & human") @@ -323,33 +290,24 @@ def full_tree(self): r""" Return a full syntax parse tree of the calling formula. - INPUT: - - - ``self`` -- calling object. This is a boolean formula. - OUTPUT: The full syntax parse tree as a nested list EXAMPLES: - This example shows how to find the full syntax parse tree of a formula. - - :: + This example shows how to find the full syntax parse tree + of a formula:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a->(b&c)") sage: s.full_tree() ['->', 'a', ['&', 'b', 'c']] - :: - sage: t = propcalc.formula("a & ((~b | c) ^ a -> c) <-> ~b") sage: t.full_tree() ['<->', ['&', 'a', ['->', ['^', ['|', ['~', 'b'], 'c'], 'a'], 'c']], ['~', 'b']] - :: - sage: f = propcalc.formula("~~(a&~b)") sage: f.full_tree() ['~', ['~', ['&', 'a', ['~', 'b']]]] @@ -367,27 +325,20 @@ def full_tree(self): def __or__(self, other): r""" - Overload the | operator to 'or' two statements together. + Overload the ``|`` operator to 'or' two statements together. INPUT: - - ``self`` -- calling object. This is the statement on - the left side of the operator. - - - ``other`` -- a boolean formula. This is the statement - on the right side of the operator. + - ``other`` -- a boolean formula; this is the statement + on the right side of the operator OUTPUT: - A boolean formula of the following form: - - ``self`` | ``other`` + A boolean formula of the form ``self | other``. EXAMPLES: - This example illustrates combining two formulas with '|'. - - :: + This example illustrates combining two formulas with ``|``:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a&b") @@ -399,27 +350,20 @@ def __or__(self, other): def __and__(self, other): r""" - Overload the & operator to 'and' two statements together. + Overload the ``&`` operator to 'and' two statements together. INPUT: - - ``self`` -- calling object. This is the formula on the - left side of the operator. - - - ``other`` -- a boolean formula. This is the formula on - the right side of the operator. + - ``other`` -- a boolean formula; this is the formula on + the right side of the operator OUTPUT: - A boolean formula of the following form: - - ``self`` & ``other`` + A boolean formula of the form ``self & other``. EXAMPLES: - This example shows how to combine two formulas with '&'. - - :: + This example shows how to combine two formulas with ``&``:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a&b") @@ -431,27 +375,20 @@ def __and__(self, other): def __xor__(self, other): r""" - Overload the ^ operator to xor two statements together. + Overload the ``^`` operator to 'xor' two statements together. INPUT: - - ``self`` -- calling object. This is the formula on the - left side of the operator. - - - ``other`` -- a boolean formula. This is the formula on - the right side of the operator. + - ``other`` -- a boolean formula; this is the formula on + the right side of the operator OUTPUT: - A boolean formula of the following form: - - ``self`` ^ ``other`` + A boolean formula of the form ``self ^ other``. EXAMPLES: - This example illustrates how to combine two formulas with '^'. - - :: + This example illustrates how to combine two formulas with ``^``:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a&b") @@ -463,27 +400,20 @@ def __xor__(self, other): def __pow__(self, other): r""" - Overload the ^ operator to xor two statements together. + Overload the ``^`` operator to 'xor' two statements together. INPUT: - - ``self`` -- calling object. This is the formula on the - left side of the operator. - - - ``other`` -- a boolean formula. This is the formula on - the right side of the operator. + - ``other`` -- a boolean formula; this is the formula on + the right side of the operator OUTPUT: - A boolean formula of the following form: - - ``self`` ^ ``other`` + A boolean formula of the form ``self ^ other``. EXAMPLES: - This example shows how to combine two formulas with '^'. - - :: + This example shows how to combine two formulas with ``^``:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a&b") @@ -493,33 +423,24 @@ def __pow__(self, other): .. TODO:: - This function seems to be identical to __xor__. - Thus, this function should be replaced with __xor__ everywhere - that it appears in the logic module. Then it can be deleted + This function seems to be identical to ``__xor__``. + Thus, this function should be replaced with ``__xor__`` everywhere + that it appears in the logic module. Then it can be deleted altogether. """ return self.add_statement(other, '^') def __invert__(self): r""" - Overload the ~ operator to not a statement. - - INPUT: - - - ``self`` -- calling object. This is the formula on the - right side of the operator. + Overload the ``~`` operator to 'not' a statement. OUTPUT: - A boolean formula of the following form: - - ~``self`` + A boolean formula of the form ``~self``. EXAMPLES: - This example shows how to negate a boolean formula. - - :: + This example shows how to negate a boolean formula:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a&b") @@ -532,59 +453,45 @@ def __invert__(self): def ifthen(self, other): r""" - Combine two formulas with the -> operator. + Combine two formulas with the ``->`` operator. INPUT: - - ``self`` -- calling object. This is the formula on - the left side of the operator. - - - ``other`` -- a boolean formula. This is the formula - on the right side of the operator. + - ``other`` -- a boolean formula; this is the formula + on the right side of the operator OUTPUT: - A boolean formula of the following form: - - ``self`` -> ``other`` + A boolean formula of the form ``self -> other``. - EXAMPLES: - - This example illustrates how to combine two formulas with '->'. + EXAMPLES: - :: + This example illustrates how to combine two formulas with '->':: - sage: import sage.logic.propcalc as propcalc - sage: s = propcalc.formula("a&b") - sage: f = propcalc.formula("c^d") - sage: s.ifthen(f) - (a&b)->(c^d) + sage: import sage.logic.propcalc as propcalc + sage: s = propcalc.formula("a&b") + sage: f = propcalc.formula("c^d") + sage: s.ifthen(f) + (a&b)->(c^d) """ return self.add_statement(other, '->') def iff(self, other): r""" - Combine two formulas with the <-> operator. + Combine two formulas with the ``<->`` operator. INPUT: - - ``self`` -- calling object. This is the formula - on the left side of the operator. - - - ``other`` -- a boolean formula. This is the formula - on the right side of the operator. + - ``other`` -- a boolean formula; this is the formula + on the right side of the operator OUTPUT: - A boolean formula of the following form: - - ``self`` <-> ``other`` + A boolean formula of the form ``self <-> other``. EXAMPLES: - This example illustrates how to combine two formulas with '<->'. - - :: + This example illustrates how to combine two formulas with '<->':: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a&b") @@ -596,29 +503,24 @@ def iff(self, other): def __eq__(self, other): r""" - Overload the == operator to deterine logical equivalence. + Overload the ``==`` operator to deterine logical equivalence. INPUT: - - ``self`` -- calling object. This is the formula on - the left side of the comparator. - - - ``other`` -- a boolean formula. This is the formula - on the right side of the comparator. + - ``other`` -- a boolean formula; this is the formula + on the right side of the comparator OUTPUT: A boolean value to be determined as follows: - True - if ``self`` and ``other`` are logically equivalent + - ``True`` if ``self`` and ``other`` are logically equivalent - False - if ``self`` and ``other`` are not logically equivalent + - ``False`` if ``self`` and ``other`` are not logically equivalent EXAMPLES: - This example shows how to determine logical equivalence. - - :: + This example shows how to determine logical equivalence:: sage: import sage.logic.propcalc as propcalc sage: f = propcalc.formula("(a|b)&c") @@ -640,13 +542,11 @@ def truthtable(self, start=0, end=-1): INPUT: - - ``self`` -- calling object - - - ``start`` -- (default: 0) an integer. This is the first - row of the truth table to be created. + - ``start`` -- (default: 0) an integer; this is the first + row of the truth table to be created - - ``end`` -- (default: -1) an integer. This is the laste - row of the truth table to be created. + - ``end`` -- (default: -1) an integer; this is the laste + row of the truth table to be created OUTPUT: @@ -654,9 +554,7 @@ def truthtable(self, start=0, end=-1): EXAMPLES: - This example illustrates the creation of a truth table. - - :: + This example illustrates the creation of a truth table:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a&b|~(c|a)") @@ -671,9 +569,7 @@ def truthtable(self, start=0, end=-1): True True False True True True True True - We can now create a truthtable of rows 1 to 4, inclusive. - - :: + We can now create a truthtable of rows 1 to 4, inclusive:: sage: s.truthtable(1, 5) a b c value @@ -688,12 +584,12 @@ def truthtable(self, start=0, end=-1): each variable associated to a column of the number, and taking on a true value if that column has a value of 1. Please see the logictable module for details. The function returns a table that - start inclusive and end exclusive so truthtable(0, 2) will include - row 0, but not row 2. + start inclusive and end exclusive so ``truthtable(0, 2)`` will + include row 0, but not row 2. When sent with no start or end parameters, this is an - exponential time function requiring O(2**n) time, where - n is the number of variables in the expression. + exponential time function requiring `O(2^n)` time, where + `n` is the number of variables in the expression. """ max = 2 ** len(self.__vars_order) if end < 0: @@ -730,9 +626,7 @@ def evaluate(self, var_values): INPUT: - - ``self`` -- calling object - - - ``var_values`` -- a dictionary. This contains the + - ``var_values`` -- a dictionary; this contains the pairs of variables and their boolean values. OUTPUT: @@ -741,17 +635,12 @@ def evaluate(self, var_values): EXAMPLES: - This example illustrates the evaluation of a boolean formula. - - :: + This example illustrates the evaluation of a boolean formula:: sage: import sage.logic.propcalc as propcalc sage: f = propcalc.formula("a&b|c") sage: f.evaluate({'a':False, 'b':False, 'c':True}) True - - :: - sage: f.evaluate({'a':True, 'b':False, 'c':False}) False """ @@ -759,33 +648,27 @@ def evaluate(self, var_values): def is_satisfiable(self): r""" - Determine if the formula is True for some assignment of values. - - INPUT: - - - ``self`` -- calling object + Determine if the formula is ``True`` for some assignment of values. OUTPUT: A boolean value to be determined as follows: - True - if there is an assignment of values that makes the formula True + - ``True`` if there is an assignment of values that makes the + formula ``True``. - False - if the formula cannot be made True by any assignment of values + - ``False`` if the formula cannot be made ``True`` by any assignment + of values. EXAMPLES: - This example illustrates how to check a formula for satisfiability. - - :: + This example illustrates how to check a formula for satisfiability:: sage: import sage.logic.propcalc as propcalc sage: f = propcalc.formula("a|b") sage: f.is_satisfiable() True - :: - sage: g = f & (~f) sage: g.is_satisfiable() False @@ -798,61 +681,46 @@ def is_satisfiable(self): def is_tautology(self): r""" - Determine if the formula is always True. - - INPUT: - - - ``self`` -- calling object + Determine if the formula is always ``True``. OUTPUT: A boolean value to be determined as follows: - True - if the formula is a tautology + - ``True`` if the formula is a tautology. - False - if the formula is not a tautology + - ``False`` if the formula is not a tautology. EXAMPLES: - This example illustrates how to check if a formula is a tautology. - - :: + This example illustrates how to check if a formula is a tautology:: sage: import sage.logic.propcalc as propcalc sage: f = propcalc.formula("a|~a") sage: f.is_tautology() True - :: - sage: f = propcalc.formula("a&~a") sage: f.is_tautology() False - :: - sage: f = propcalc.formula("a&b") sage: f.is_tautology() False - """ return not (~self).is_satisfiable() def is_contradiction(self): r""" - Determine if the formula is always False. - - INPUT: - - - ``self`` -- calling object + Determine if the formula is always ``False``. OUTPUT: A boolean value to be determined as follows: - True - if the formula is a contradiction + - ``True`` if the formula is a contradiction. - False - if the formula is not a contradiction + - ``False`` if the formula is not a contradiction. EXAMPLES: @@ -865,14 +733,10 @@ def is_contradiction(self): sage: f.is_contradiction() True - :: - sage: f = propcalc.formula("a|~a") sage: f.is_contradiction() False - :: - sage: f = propcalc.formula("a|b") sage: f.is_contradiction() False @@ -945,9 +809,7 @@ def equivalent(self, other): EXAMPLES: - This example shows how to check for logical equivalence. - - :: + This example shows how to check for logical equivalence:: sage: import sage.logic.propcalc as propcalc sage: f = propcalc.formula("(a|b)&c") @@ -955,8 +817,6 @@ def equivalent(self, other): sage: f.equivalent(g) True - :: - sage: g = propcalc.formula("a|b&c") sage: f.equivalent(g) False @@ -967,19 +827,13 @@ def convert_cnf_table(self): r""" Convert boolean formula to conjunctive normal form. - INPUT: - - - ``self`` -- calling object - OUTPUT: - An instance of :class:`BooleanFormula` in conjunctive normal form + An instance of :class:`BooleanFormula` in conjunctive normal form. EXAMPLES: - This example illustrates how to convert a formula to cnf. - - :: + This example illustrates how to convert a formula to cnf:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a ^ b <-> c") @@ -987,9 +841,8 @@ def convert_cnf_table(self): sage: s (a|b|~c)&(a|~b|c)&(~a|b|c)&(~a|~b|~c) - We now show that :meth:`convert_cnf` and :meth:`convert_cnf_table` are aliases. - - :: + We now show that :meth:`convert_cnf` and :meth:`convert_cnf_table` + are aliases:: sage: t = propcalc.formula("a ^ b <-> c") sage: t.convert_cnf_table(); t @@ -1028,19 +881,13 @@ def convert_cnf_recur(self): r""" Convert boolean formula to conjunctive normal form. - INPUT: - - - ``self`` -- calling object - OUTPUT: - An instance of :class:`BooleanFormula` in conjunctive normal form + An instance of :class:`BooleanFormula` in conjunctive normal form. EXAMPLES: - This example hows how to convert a formula to conjunctive normal form. - - :: + This example hows how to convert a formula to conjunctive normal form:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a^b<->c") @@ -1052,13 +899,13 @@ def convert_cnf_recur(self): This function works by applying a set of rules that are guaranteed to convert the formula. Worst case the converted - expression has an O(2^n) increase in size (and time as well), but if - the formula is already in CNF (or close to) it is only O(n). + expression has an `O(2^n)` increase in size (and time as well), but + if the formula is already in CNF (or close to) it is only `O(n)`. This function can require an exponential blow up in space from the - original expression. This in turn can require large amounts of time. - Unless a formula is already in (or close to) being in cnf convert_cnf() - is typically preferred, but results can vary. + original expression. This in turn can require large amounts of + time. Unless a formula is already in (or close to) being in cnf + :meth:`convert_cnf()` is typically preferred, but results can vary. """ self.__tree = logicparser.apply_func(self.__tree, self.reduce_op) self.__tree = logicparser.apply_func(self.__tree, self.dist_not) @@ -1069,19 +916,13 @@ def satformat(self): r""" Return the satformat representation of a boolean formula. - INPUT: - - - ``self`` -- calling object - OUTPUT: - The satformat of the formula as a string + The satformat of the formula as a string. EXAMPLES: - This example illustrates how to find the satformat of a formula. - - :: + This example illustrates how to find the satformat of a formula:: sage: import sage.logic.propcalc as propcalc sage: f = propcalc.formula("a&((b|c)^a->c)<->b") @@ -1097,9 +938,11 @@ def satformat(self): description of satformat. If the instance of boolean formula has not been converted to - CNF form by a call to convert_cnf() or convert_cnf_recur() - satformat() will call convert_cnf(). Please see the notes for - convert_cnf() and convert_cnf_recur() for performance issues. + CNF form by a call to :meth:`convert_cnf()` or + :meth:`convert_cnf_recur()`, then :meth:`satformat()` will call + :meth:`convert_cnf()`. Please see the notes for + :meth:`convert_cnf()` and :meth:`convert_cnf_recur()` for + performance issues. """ self.convert_cnf_table() s = '' @@ -1172,7 +1015,8 @@ def satformat(self): # True True False True # True True True True # -# NOTES: +# .. NOTES:: +# # If the instance of boolean formula has not been converted to # cnf form by a call to convert_cnf() or convert_cnf_recur() # satformat() will call convert_cnf(). Please see the notes for @@ -1204,26 +1048,22 @@ def satformat(self): def convert_opt(self, tree): r""" - Convert a parse tree to the tuple form used by bool_opt. + Convert a parse tree to the tuple form used by :meth:`bool_opt()`. INPUT: - - ``self`` -- calling object - - - ``tree`` -- a list. This is a branch of a + - ``tree`` -- a list; this is a branch of a parse tree and can only contain the '&', '|' - and '~' operators along with variables. + and '~' operators along with variables OUTPUT: - A 3-tuple + A 3-tuple. EXAMPLES: This example illustrates the conversion of a formula into its - corresponding tuple. - - :: + corresponding tuple:: sage: import sage.logic.propcalc as propcalc, sage.logic.logicparser as logicparser sage: s = propcalc.formula("a&(b|~c)") @@ -1235,7 +1075,9 @@ def convert_opt(self, tree): This function only works on one branch of the parse tree. To apply the function to every branch of a parse tree, pass the - function as an argument in :func:`apply_func` in logicparser.py. + function as an argument in + :func:`~sage.logic.logicparser.apply_func()` in + :mod:`~sage.logic.logicparser`. """ if not isinstance(tree[1], TupleType) and not (tree[1] is None): lval = ('prop', tree[1]) @@ -1259,24 +1101,19 @@ def add_statement(self, other, op): INPUT: - - ``self`` -- calling object. This is the formula on - the left side of the operator. - - - ``other`` -- instance of BooleanFormula class. This - is the formula on the right of the operator. + - ``other`` -- instance of :class:`BooleanFormula`; this + is the formula on the right of the operator - - ``op`` -- a string. This is the operator used to - combine the two formulas. + - ``op`` -- a string; this is the operator used to + combine the two formulas OUTPUT: - The result as an instance of :class:`BooleanFormula` + The result as an instance of :class:`BooleanFormula`. EXAMPLES: - This example shows how to create a new formula from two others. - - :: + This example shows how to create a new formula from two others:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a&b") @@ -1284,8 +1121,6 @@ def add_statement(self, other, op): sage: s.add_statement(f, '|') (a&b)|(c^d) - :: - sage: s.add_statement(f, '->') (a&b)->(c^d) """ @@ -1295,31 +1130,27 @@ def add_statement(self, other, op): def get_bit(self, x, c): r""" - Determine if bit c of the number x is 1. + Determine if bit ``c`` of the number ``x`` is 1. INPUT: - - ``self`` -- calling object - - - ``x`` -- an integer. This is the number from - which to take the bit. + - ``x`` -- an integer; this is the number from + which to take the bit - - ``c`` -- an integer. This is the but number to - be taken, where 0 is the low order bit. + - ``c`` -- an integer; this is the but number to + be taken, where 0 is the low order bit OUTPUT: A boolean to be determined as follows: - True - if bit c of x is 1 + - ``True`` if bit ``c`` of ``x`` is 1. - False - if bit c of x is not 1 + - ``False`` if bit c of x is not 1. EXAMPLES: - This example illustrates the use of :meth:`get_bit`. - - :: + This example illustrates the use of :meth:`get_bit`:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a&b") @@ -1328,16 +1159,12 @@ def get_bit(self, x, c): sage: s.get_bit(8, 0) False - It is not an error to have a bit out of range. - - :: + It is not an error to have a bit out of range:: sage: s.get_bit(64, 7) False - Nor is it an error to use a negative number. - - :: + Nor is it an error to use a negative number:: sage: s.get_bit(-1, 3) False @@ -1349,8 +1176,8 @@ def get_bit(self, x, c): .. NOTE:: The 0 bit is the low order bit. Errors should be handled - gracefully by a return of false, and negative numbers x - always return false while a negative c will index from the + gracefully by a return of ``False``, and negative numbers ``x`` + always return ``False`` while a negative ``c`` will index from the high order bit. """ bits = [] @@ -1373,20 +1200,17 @@ def reduce_op(self, tree): INPUT: - - ``self`` -- calling object - - - ``tree`` -- a list. This represents a branch - of a parse tree. + - ``tree`` -- a list; this represents a branch + of a parse tree OUTPUT: - A new list with no ^, ->, or <-> as first element of list. + A new list with no '^', '->', or '<->' as first element of list. EXAMPLES: - This example illustrates the use of :meth:`reduce_op` with :func:`apply_func`. - - :: + This example illustrates the use of :meth:`reduce_op` with + :func:`apply_func`:: sage: import sage.logic.propcalc as propcalc, sage.logic.logicparser as logicparser sage: s = propcalc.formula("a->b^c") @@ -1398,7 +1222,8 @@ def reduce_op(self, tree): This function only operates on a single branch of a parse tree. To apply the function to an entire parse tree, pass the function - as an argument to :func:`apply_func` in logicparser.py. + as an argument to :func:`~sage.logic.logicparser.apply_func()` + in :mod:`~sage.logic.logicparser`. """ if tree[0] == '<->': # parse tree for (~tree[1]|tree[2])&(~tree[2]|tree[1]) @@ -1417,24 +1242,20 @@ def reduce_op(self, tree): def dist_not(self, tree): r""" - Distribute ~ operators over & and | operators. + Distribute '~' operators over '&' and '|' operators. INPUT: - - ``self`` calling object - - - ``tree`` a list. This represents a branch - of a parse tree. + - ``tree`` a list; this represents a branch + of a parse tree OUTPUT: - A new list + A new list. EXAMPLES: - This example illustrates the distribution of '~' over '&'. - - :: + This example illustrates the distribution of '~' over '&':: sage: import sage.logic.propcalc as propcalc, sage.logic.logicparser as logicparser sage: s = propcalc.formula("~(a&b)") @@ -1446,7 +1267,8 @@ def dist_not(self, tree): This function only operates on a single branch of a parse tree. To apply the function to an entire parse tree, pass the function - as an argument to :func:`apply_func` in logicparser.py. + as an argument to :func:`~sage.logic.logicparser.apply_func()` + in :mod:`~sage.logic.logicparser`. """ if tree[0] == '~' and isinstance(tree[1], ListType): op = tree[1][0] @@ -1465,24 +1287,20 @@ def dist_not(self, tree): def dist_ors(self, tree): r""" - Distribute | over &. + Distribute '|' over '&'. INPUT: - - ``self`` -- calling object - - - ``tree`` -- a list. This represents a branch of - a parse tree. + - ``tree`` -- a list; this represents a branch of + a parse tree OUTPUT: - A new list + A new list. EXAMPLES: - This example illustrates the distribution of '|' over '&'. - - :: + This example illustrates the distribution of '|' over '&':: sage: import sage.logic.propcalc as propcalc, sage.logic.logicparser as logicparser sage: s = propcalc.formula("(a&b)|(a&c)") @@ -1494,7 +1312,8 @@ def dist_ors(self, tree): This function only operates on a single branch of a parse tree. To apply the function to an entire parse tree, pass the function - as an argument to :func:`apply_func` in logicparser.py. + as an argument to :func:`~sage.logic.logicparser.apply_func()` + in :mod:`~sage.logic.logicparser`. """ if tree[0] == '|' and isinstance(tree[2], ListType) and tree[2][0] == '&': new_tree = ['&', ['|', tree[1], tree[2][1]], @@ -1512,20 +1331,17 @@ def to_infix(self, tree): INPUT: - - ``self`` -- calling object - - - ``tree`` -- a list. This represents a branch - of a parse tree. + - ``tree`` -- a list; this represents a branch + of a parse tree OUTPUT: - A new list + A new list. EXAMPLES: - This example shows how to convert a parse tree from prefix to infix form. - - :: + This example shows how to convert a parse tree from prefix to + infix form:: sage: import sage.logic.propcalc as propcalc, sage.logic.logicparser as logicparser sage: s = propcalc.formula("(a&b)|(a&c)") @@ -1537,7 +1353,8 @@ def to_infix(self, tree): This function only operates on a single branch of a parse tree. To apply the function to an entire parse tree, pass the function - as an argument to :func:`apply_func` in logicparser.py. + as an argument to :func:`~sage.logic.logicparser.apply_func()` + in :mod:`~sage.logic.logicparser`. """ if tree[0] != '~': return [tree[1], tree[0], tree[2]] @@ -1545,26 +1362,15 @@ def to_infix(self, tree): def convert_expression(self): r""" - Convert the string representation of a formula to conjunctive normal form. - - INPUT: - - - ``self`` -- calling object - - OUTPUT: + Convert the string representation of a formula to conjunctive + normal form. - None - - EXAMPLES: - - We show how the converted formula is printed in conjunctive normal form. - - :: + EXAMPLES:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a^b<->c") - sage: s.convert_cnf_recur(); s #long time - (~a|a|c)&(~b|a|c)&(~a|b|c)&(~b|b|c)&(~c|a|b)&(~c|~a|~b) + sage: s.convert_expression(); s + a^b<->c """ ttree = self.__tree[:] ttree = logicparser.apply_func(ttree, self.to_infix) @@ -1595,20 +1401,16 @@ def get_next_op(self, str): INPUT: - - ``self`` -- calling object - - - ``str`` -- a string. This contains a logical - expression. + - ``str`` -- a string; this contains a logical + expression OUTPUT: - The next operator as a string + The next operator as a string. EXAMPLES: - This example illustrates how to find the next operator in a formula. - - :: + This example illustrates how to find the next operator in a formula:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("f&p") diff --git a/src/sage/logic/logic.py b/src/sage/logic/logic.py index e59aee812b2..79811c94935 100644 --- a/src/sage/logic/logic.py +++ b/src/sage/logic/logic.py @@ -1,9 +1,9 @@ r""" -Manipulation of symbolic logic expressions. +Symbolic Logic Expressions An expression is created from a string that consists of the -operators !, &, |, ->, <->, which correspond to the logical -functions not, and, or, if then, if and only if, respectively. +operators ``!``, ``&``, ``|``, ``->``, ``<->``, which correspond to the +logical functions not, and, or, if then, if and only if, respectively. Variable names must start with a letter and contain only alpha-numerics and the underscore character. @@ -40,9 +40,8 @@ class SymbolicLogic: """ EXAMPLES: - This example illustrates how to create a boolean formula and print its table. - - :: + This example illustrates how to create a boolean formula and print + its table:: sage: log = SymbolicLogic() sage: s = log.statement("a&b|!(c|a)") @@ -65,15 +64,13 @@ def statement(self, s): INPUT: - - ``self`` -- calling object - - ``s`` -- a string containing the logic expression to be manipulated - ``global vars`` -- a dictionary with variable names as keys and the variables' current boolean values as dictionary values - - ``global vars_order`` -- a list of the variables in the order that they - are found + - ``global vars_order`` -- a list of the variables in the order + that they are found OUTPUT: @@ -85,34 +82,24 @@ def statement(self, s): EXAMPLES: - This example illustrates the creation of a statement. - - :: + This example illustrates the creation of a statement:: sage: log = SymbolicLogic() sage: s = log.statement("a&b|!(c|a)") - - :: - sage: s2 = log.statement("!((!(a&b)))") - It is an error to use invalid variable names. - - :: + It is an error to use invalid variable names:: sage: s = log.statement("3fe & @q") Invalid variable name: 3fe Invalid variable name: @q - It is also an error to use invalid syntax. - - :: + It is also an error to use invalid syntax:: sage: s = log.statement("a&&b") Malformed Statement sage: s = log.statement("a&((b)") Malformed Statement - """ global vars, vars_order toks, vars, vars_order = ['OPAREN'], {}, [] @@ -131,43 +118,41 @@ def truthtable(self, statement, start=0, end=-1): INPUT: - - ``self`` -- calling object - - - ``statement`` -- a list. It contains the tokens and the two global + - ``statement`` -- a list; it contains the tokens and the two global variables vars and vars_order - - ``start`` -- (default : 0) an integer. This represents the row of the - truth table from which to start. + - ``start`` -- (default: 0) an integer; this represents the row of + the truth table from which to start - - ``end`` -- (default : -1) an integer. This represents the last row of the - truth table to be created + - ``end`` -- (default: -1) an integer; this represents the last row + of the truth table to be created OUTPUT: - The truth table as a 2d array with the creating formula tacked to the front + The truth table as a 2d array with the creating formula tacked + to the front. EXAMPLES: - This example illustrates the creation of a statement. - - :: + This example illustrates the creation of a statement:: sage: log = SymbolicLogic() sage: s = log.statement("a&b|!(c|a)") sage: t = log.truthtable(s) #creates the whole truth table - We can now create truthtable of rows 1 to 5 - - :: + We can now create truthtable of rows 1 to 5:: sage: s2 = log.truthtable(s, 1, 5); s2 - [[['OPAREN', 'a', 'AND', 'b', 'OR', 'NOT', 'OPAREN', 'c', 'OR', 'a', 'CPAREN', 'CPAREN'], {'a': 'False', 'c': 'True', 'b': 'False'}, ['a', 'b', 'c']], ['False', 'False', 'True', 'False'], ['False', 'True', 'False', 'True'], ['False', 'True', 'True', 'True'], ['True', 'False', 'False', 'False']] + [[['OPAREN', 'a', 'AND', 'b', 'OR', 'NOT', 'OPAREN', 'c', 'OR', 'a', 'CPAREN', 'CPAREN'], + {'a': 'False', 'c': 'True', 'b': 'False'}, ['a', 'b', 'c']], + ['False', 'False', 'True', 'False'], ['False', 'True', 'False', 'True'], + ['False', 'True', 'True', 'True'], ['True', 'False', 'False', 'False']] .. NOTE:: When sent with no start or end parameters this is an - exponential time function requiring O(2**n) time, where - n is the number of variables in the logic expression + exponential time function requiring `O(2^n)` time, where + `n` is the number of variables in the logic expression """ global vars, vars_order toks, vars, vars_order = statement @@ -194,20 +179,17 @@ def print_table(self, table): INPUT: - - ``self`` -- calling object - - - ``table`` -- object created by truthtable() method. It contains - the variable values and the evaluation of the statement + - ``table`` -- object created by :meth:`truthtable()` method; it + contains the variable values and the evaluation of the statement OUTPUT: - A formatted version of the truth table + A formatted version of the truth table. EXAMPLES: - This example illustrates the creation of a statement and its truth table. - - :: + This example illustrates the creation of a statement and + its truth table:: sage: log = SymbolicLogic() sage: s = log.statement("a&b|!(c|a)") @@ -224,9 +206,7 @@ def print_table(self, table): True | True | False | True | True | True | True | True | - We can also print a shortened table. - - :: + We can also print a shortened table:: sage: t = log.truthtable(s, 1, 5) sage: log.print_table(t) @@ -236,7 +216,6 @@ def print_table(self, table): False | False | True | False | False | False | False | True | True | False | False | True | False | False | True | - """ statement = table[0] del table[0] @@ -318,35 +297,75 @@ def combine(self, statement1, statement2): #a c++ implementation of the ESPRESSO algorithm #to simplify the truthtable: probably Minilog def simplify(self, table): - x = 0 + """ + Call a C++ implementation of the ESPRESSO algorithm to simplify the + given truth table. + + .. TODO:: + + Implement this method. + + EXAMPLES:: + + sage: log = SymbolicLogic() + sage: s = log.statement("a&b|!(c|a)") + sage: t = log.truthtable(s) + sage: log.simplify(t) + Traceback (most recent call last): + ... + NotImplementedError + """ + raise NotImplementedError - #TODO: implement a prove function which test to - #see if the statement is a tautology or contradiction - #by calling a c++ library TBD def prove(self, statement): - x = 0 + """ + A function to test to see if the statement is a tautology or + contradiction by calling a C++ library. + + .. TODO:: + + Implement this method. + + EXAMPLES:: + + sage: log = SymbolicLogic() + sage: s = log.statement("a&b|!(c|a)") + sage: log.prove(s) + Traceback (most recent call last): + ... + NotImplementedError + """ + raise NotImplementedError def get_bit(x, c): r""" - Determine if bit c of the number x is 1. + Determine if bit ``c`` of the number ``x`` is 1. INPUT: - - ``x`` -- an integer. This is the number from which to take the bit. + - ``x`` -- an integer; this is the number from which to take the bit - - ``c`` -- an integer. This is the bit number to be taken. + - ``c`` -- an integer; this is the bit number to be taken OUTPUT: A boolean value to be determined as follows: - True - if bit c of x is 1 + - ``True`` if bit ``c`` of ``x`` is 1. - False - if bit c of x is not 1 + - ``False`` if bit ``c`` of ``x`` is not 1. .. NOTE:: - This function is for internal use by the SymbolicLogic class. + This function is for internal use by the :class:`SymbolicLogic` class. + + EXAMPLES:: + + sage: from sage.logic.logic import get_bit + sage: get_bit(int(2), int(1)) + 'True' + sage: get_bit(int(8), int(0)) + 'False' """ bits = [] while x > 0: @@ -363,25 +382,32 @@ def get_bit(x, c): def eval(toks): r""" - Evaluate the expression contained in toks. + Evaluate the expression contained in ``toks``. INPUT: - - ``toks`` -- a list of tokens. This represents a boolean expression. + - ``toks`` -- a list of tokens; this represents a boolean expression OUTPUT: A boolean value to be determined as follows: - True - if expression evaluates to True + - ``True`` if expression evaluates to ``True``. - False - if expression evaluates to False + - ``False`` if expression evaluates to ``False``. .. NOTE:: - This function is for internal use by the SymbolicLogic class. The - evaluations rely on setting the values of the variables in the global - dictionary vars. + This function is for internal use by the :class:`SymbolicLogic` class. + The evaluations rely on setting the values of the variables in the + global dictionary vars. + + TESTS:: + + sage: log = SymbolicLogic() + sage: s = log.statement("a&b|!(c|a)") + sage: sage.logic.logic.eval(s[0]) + 'True' """ stack = [] for tok in toks: @@ -398,26 +424,35 @@ def eval(toks): def eval_ltor_toks(lrtoks): r""" - Evaluates the expression contained in lrtoks. + Evaluates the expression contained in ``lrtoks``. INPUT: - - ``lrtoks`` -- a list of tokens. This represents a part of a boolean - formula that contains no inner parentheses. + - ``lrtoks`` -- a list of tokens; this represents a part of a boolean + formula that contains no inner parentheses OUTPUT: A boolean value to be determined as follows: - True - if expression evaluates to True + - ``True`` if expression evaluates to ``True``. - False - if expression evaluates to False + - ``False`` if expression evaluates to ``False``. .. NOTE:: - This function is for internal use by the SymbolicLogic class. The - evaluations rely on setting the values of the variables in the global - dictionary vars. + This function is for internal use by the :class:`SymbolicLogic` class. + The evaluations rely on setting the values of the variables in the + global dictionary vars. + + TESTS:: + + sage: log = SymbolicLogic() + sage: s = log.statement("a&b|!c") + sage: ltor = s[0][1:-1]; ltor + ['a', 'AND', 'b', 'OR', 'NOT', 'c'] + sage: sage.logic.logic.eval_ltor_toks(ltor) + 'True' """ reduce_monos(lrtoks) # monotonic ! operators go first reduce_bins(lrtoks) # then the binary operators @@ -427,20 +462,30 @@ def eval_ltor_toks(lrtoks): def reduce_bins(lrtoks): r""" - Evaluate lrtoks to a single boolean value. + Evaluate ``lrtoks`` to a single boolean value. INPUT: - - ``lrtoks`` -- a list of tokens. This represents a part of a boolean - formula that contains no inner parentheses or monotonic operators. + - ``lrtoks`` -- a list of tokens; this represents a part of a boolean + formula that contains no inner parentheses or monotonic operators OUTPUT: - None. The pointer to lrtoks is now a list containing True or False + ``None``; the pointer to lrtoks is now a list containing + ``True`` or ``False``. .. NOTE:: - This function is for internal use by the SymbolicLogic class. + This function is for internal use by the :class:`SymbolicLogic` class. + + TESTS:: + + sage: log = SymbolicLogic() + sage: s = log.statement("a&b|c") + sage: lrtoks = s[0][1:-1]; lrtoks + ['a', 'AND', 'b', 'OR', 'c'] + sage: sage.logic.logic.reduce_bins(lrtoks); lrtoks + ['False'] """ i = 0 while i < len(lrtoks): @@ -458,15 +503,26 @@ def reduce_monos(lrtoks): INPUT: - - ``lrtoks`` -- a list of tokens. This represents a part of a boolean - expression that contains now inner parentheses. + - ``lrtoks`` -- a list of tokens; this represents a part of a boolean + expression that contains now inner parentheses OUTPUT: - None. The pointer to lrtoks is now a list containing monotonic operators. + ``None``; the pointer to ``lrtoks`` is now a list containing + monotonic operators. .. NOTE:: - This function is for internal use by the SymbolicLogic class. + + This function is for internal use by the :class:`SymbolicLogic` class. + + TESTS:: + + sage: log = SymbolicLogic() + sage: s = log.statement("!a&!b") + sage: lrtoks = s[0][1:-1]; lrtoks + ['NOT', 'a', 'AND', 'NOT', 'b'] + sage: sage.logic.logic.reduce_monos(lrtoks); lrtoks + ['True', 'AND', 'True'] """ i = 0 while i < len(lrtoks): @@ -478,24 +534,35 @@ def reduce_monos(lrtoks): def eval_mon_op(args): r""" - Return a boolean value based on the truth table of the operator in args. + Return a boolean value based on the truth table of the operator + in ``args``. INPUT: - - ``args`` -- a list of length 2. This contains the token 'NOT' and - then a variable name. + - ``args`` -- a list of length 2; this contains the token 'NOT' and + then a variable name OUTPUT: A boolean value to be determined as follows: - True - if the variable in args is False + - ``True`` if the variable in ``args`` is ``False``. - False - if the variable in args is True + - ``False`` if the variable in ``args`` is ``True``. .. NOTE:: - This function is for internal use by the SymbolicLogic class. + This function is for internal use by the :class:`SymbolicLogic` class. + + TESTS:: + + sage: log = SymbolicLogic() + sage: s = log.statement("!(a&b)|!a"); s + [['OPAREN', 'NOT', 'OPAREN', 'a', 'AND', 'b', 'CPAREN', 'OR', 'NOT', 'a', 'CPAREN'], + {'a': 'False', 'b': 'False'}, + ['a', 'b']] + sage: sage.logic.logic.eval_mon_op(['NOT', 'a']) + 'True' """ if args[1] != 'True' and args[1] != 'False': val = vars[args[1]] @@ -509,21 +576,32 @@ def eval_mon_op(args): def eval_bin_op(args): r""" - Return a boolean value based on the truth table of the operator in args. + Return a boolean value based on the truth table of the operator + in ``args``. INPUT: - - ``args`` -- a list of length 3. This contains a variable name, - then a binary operator, and then a variable name, in that order. + - ``args`` -- a list of length 3; this contains a variable name, + then a binary operator, and then a variable name, in that order OUTPUT: - A boolean value. This is the evaluation of the operator based on the + A boolean value; this is the evaluation of the operator based on the truth values of the variables. .. NOTE:: - This function is for internal use by the SymbolicLogic class. + This function is for internal use by the :class:`SymbolicLogic` class. + + TESTS:: + + sage: log = SymbolicLogic() + sage: s = log.statement("!(a&b)"); s + [['OPAREN', 'NOT', 'OPAREN', 'a', 'AND', 'b', 'CPAREN', 'CPAREN'], + {'a': 'False', 'b': 'False'}, + ['a', 'b']] + sage: sage.logic.logic.eval_bin_op(['a', 'AND', 'b']) + 'False' """ if args[0] == 'False': lval = 'False' @@ -550,24 +628,34 @@ def eval_bin_op(args): def eval_and_op(lval, rval): r""" - Apply the 'and' operator to lval and rval. + Apply the 'and' operator to ``lval`` and ``rval``. INPUT: - - ``lval`` -- a string. This represents the value of the variable - appearing to the left of the 'and' operator. - - - ``rval`` -- a string. This represents the value of the variable - appearing to the right of the 'and' operator. + - ``lval`` -- a string; this represents the value of the variable + appearing to the left of the 'and' operator + - ``rval`` -- a string; this represents the value of the variable + appearing to the right of the 'and' operator OUTPUT: - The result of applying 'and' to lval and rval as a string. + The result of applying 'and' to ``lval`` and ``rval`` as a string. .. NOTE:: - This function is for internal use by the SymbolicLogic class. + This function is for internal use by the :class:`SymbolicLogic` class. + + TESTS:: + + sage: sage.logic.logic.eval_and_op('False', 'False') + 'False' + sage: sage.logic.logic.eval_and_op('False', 'True') + 'False' + sage: sage.logic.logic.eval_and_op('True', 'False') + 'False' + sage: sage.logic.logic.eval_and_op('True', 'True') + 'True' """ if lval == 'False' and rval == 'False': return 'False' @@ -580,23 +668,34 @@ def eval_and_op(lval, rval): def eval_or_op(lval, rval): r""" - Apply the 'or' operator to lval and rval + Apply the 'or' operator to ``lval`` and ``rval``. INPUT: - - ``lval`` -- a string. This represents the value of the variable - appearing to the left of the 'or' operator. + - ``lval`` -- a string; this represents the value of the variable + appearing to the left of the 'or' operator - - ``rval`` -- a string. This represents the value of the variable - appearing to the right of the 'or' operator. + - ``rval`` -- a string; this represents the value of the variable + appearing to the right of the 'or' operator OUTPUT: - A string representing the result of applying 'or' to lval and rval + A string representing the result of applying 'or' to ``lval`` and ``rval``. .. NOTE:: - This function is for internal use by the SymbolicLogic class. + This function is for internal use by the :class:`SymbolicLogic` class. + + TESTS:: + + sage: sage.logic.logic.eval_or_op('False', 'False') + 'False' + sage: sage.logic.logic.eval_or_op('False', 'True') + 'True' + sage: sage.logic.logic.eval_or_op('True', 'False') + 'True' + sage: sage.logic.logic.eval_or_op('True', 'True') + 'True' """ if lval == 'False' and rval == 'False': return 'False' @@ -609,23 +708,35 @@ def eval_or_op(lval, rval): def eval_ifthen_op(lval, rval): r""" - Apply the 'if then' operator to lval and rval + Apply the 'if then' operator to ``lval`` and ``rval``. INPUT: - - ``lval`` -- a string. This represents the value of the variable - appearing to the left of the 'if then' operator. + - ``lval`` -- a string; this represents the value of the variable + appearing to the left of the 'if then' operator - - ``rval`` -- a string. This represents the value of the variable - appearing to the right of the 'if then' operator. + - ``rval`` -- a string;t his represents the value of the variable + appearing to the right of the 'if then' operator OUTPUT: - A string representing the result of applying 'if then' to lval and rval. + A string representing the result of applying 'if then' to + ``lval`` and ``rval``. .. NOTE:: - This function is for internal use by the SymbolicLogic class. + This function is for internal use by the :class:`SymbolicLogic` class. + + TESTS:: + + sage: sage.logic.logic.eval_ifthen_op('False', 'False') + 'True' + sage: sage.logic.logic.eval_ifthen_op('False', 'True') + 'True' + sage: sage.logic.logic.eval_ifthen_op('True', 'False') + 'False' + sage: sage.logic.logic.eval_ifthen_op('True', 'True') + 'True' """ if lval == 'False' and rval == 'False': return 'True' @@ -638,23 +749,35 @@ def eval_ifthen_op(lval, rval): def eval_iff_op(lval, rval): r""" - Apply the 'if and only if' operator to lval and rval. + Apply the 'if and only if' operator to ``lval`` and ``rval``. INPUT: - - ``lval`` -- a string. This represents the value of the variable - appearing to the left of the 'if and only if' operator. + - ``lval`` -- a string; this represents the value of the variable + appearing to the left of the 'if and only if' operator - - ``rval`` -- a string. This represents the value of the variable - appearing to the right of the 'if and only if' operator. + - ``rval`` -- a string; this represents the value of the variable + appearing to the right of the 'if and only if' operator OUTPUT: - A string representing the result of applying 'if and only if' to lval and rval + A string representing the result of applying 'if and only if' + to ``lval`` and ``rval``. .. NOTE:: - This function is for internal use by the SymbolicLogic class. + This function is for internal use by the :class:`SymbolicLogic` class. + + TESTS:: + + sage: sage.logic.logic.eval_iff_op('False', 'False') + 'True' + sage: sage.logic.logic.eval_iff_op('False', 'True') + 'False' + sage: sage.logic.logic.eval_iff_op('True', 'False') + 'False' + sage: sage.logic.logic.eval_iff_op('True', 'True') + 'True' """ if lval == 'False' and rval == 'False': return 'True' @@ -667,22 +790,29 @@ def eval_iff_op(lval, rval): def tokenize(s, toks): r""" - Tokenize s and place the tokens of s in toks. + Tokenize ``s`` and place the tokens of ``s`` in ``toks``. INPUT: - - ``s`` -- a string. This contains a boolean expression. + - ``s`` -- a string; this contains a boolean expression - - ``toks`` -- a list. This will be populated with the tokens - of s. + - ``toks`` -- a list; this will be populated with the tokens of ``s`` OUTPUT: - None. The tokens of s are placed in toks. + ``None``; the tokens of ``s`` are placed in ``toks``. .. NOTE:: - This function is for internal use by the SymbolicLogic class. + This function is for internal use by the :class:`SymbolicLogic` class. + + EXAMPLES:: + + sage: from sage.logic.logic import tokenize + sage: toks = [] + sage: tokenize("(a&b)|c", toks) + sage: toks + ['OPAREN', 'a', 'AND', 'b', 'CPAREN', 'OR', 'c', 'CPAREN'] """ i = 0 while i < len(s): diff --git a/src/sage/logic/logicparser.py b/src/sage/logic/logicparser.py index d22128781f7..592802e4985 100644 --- a/src/sage/logic/logicparser.py +++ b/src/sage/logic/logicparser.py @@ -5,10 +5,12 @@ a single variable, or a formula composed of either two variables and a binary operator or one variable and a unary operator. The function parse produces a parse tree that is simplified for the purposes of more efficient truth value -evaluation. The function polish_parse produces the full parse tree of a boolean -formula which is used in functions related to proof and inference. That is, -parse is meant to be used with functions in the logic module that perform -semantic operations on a boolean formula, and polish_parse is to be used with +evaluation. The function :func:`~sage.logic.logicparser.polish_parse()` +produces the full parse tree of a boolean formula which is used in functions +related to proof and inference. That is, +:func:`~sage.logic.logicparser.parse()` is meant to be used with functions +in the logic module that perform semantic operations on a boolean formula, +and :func:`~sage.logic.logicparser.polish_parse()` is to be used with functions that perform syntactic operations on a boolean formula. AUTHORS: @@ -18,15 +20,19 @@ - Paul Scurek (2013-08-01): added polish_parse, cleaned up python code, updated docstring formatting -- Paul Scurek (2013-08-06): added recover_formula, recover_formula_internal, - prefix_to_infix, to_infix_internal +- Paul Scurek (2013-08-06): added + :func:`~sage.logic.logicparser.recover_formula()`, + :func:`~sage.logic.logicparser.recover_formula_internal()`, + :func:`~sage.logic.logicparser.prefix_to_infix()`, + :func:`~sage.logic.logicparser.to_infix_internal()` - Paul Scurek (2013-08-08): added get_trees, error handling in polish_parse, recover_formula_internal, and tree_parse EXAMPLES: -Find the parse tree and variables of a string representation of a boolean formula:: +Find the parse tree and variables of a string representation of a boolean +formula:: sage: import sage.logic.logicparser as logicparser sage: s = 'a|b&c' @@ -34,7 +40,8 @@ sage: t (['|', 'a', ['&', 'b', 'c']], ['a', 'b', 'c']) -Find the full syntax parse tree of a string representation of a boolean formula:: +Find the full syntax parse tree of a string representation of a boolean +formula:: sage: import sage.logic.logicparser as logicparser sage: s = '(a&b)->~~c' @@ -67,9 +74,8 @@ sage: r = ['(', '~', '~', 'a', '|', 'b', ')'] sage: logicparser.tree_parse(r, polish = True) ['|', ['~', ['~', 'a']], 'b'] - - """ + #***************************************************************************** # Copyright (C) 2007 Chris Gorecki # Copyright (C) 2013 Paul Scurek @@ -80,7 +86,6 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from types import * import string import propcalc import boolformula @@ -90,11 +95,11 @@ def parse(s): r""" - Return a parse tree from a boolean formula s. + Return a parse tree from a boolean formula ``s``. INPUT: - - ``s`` -- a string containing a boolean formula. + - ``s`` -- a string containing a boolean formula OUTPUT: @@ -106,9 +111,8 @@ def parse(s): EXAMPLES: - This example illustrates how to produce the parse tree of a boolean formula s. - - :: + This example illustrates how to produce the parse tree of a boolean + formula ``s``:: sage: import sage.logic.logicparser as logicparser sage: s = 'a|b&c' @@ -119,7 +123,7 @@ def parse(s): toks, vars_order = tokenize(s) tree = tree_parse(toks) # special case of tree == single variable - if isinstance(tree, StringType): + if isinstance(tree, str): return ['&', tree, tree], vars_order return tree, vars_order @@ -138,9 +142,7 @@ def polish_parse(s): EXAMPLES: This example illustrates how to find the full syntax parse tree - of a boolean formula. - - :: + of a boolean formula:: sage: import sage.logic.logicparser as logicparser sage: s = 'a|~~b' @@ -158,7 +160,7 @@ def polish_parse(s): toks, vars_order = tokenize(s) tree = tree_parse(toks, polish = True) # special case where the formula s is a single variable - if isinstance(tree, StringType): + if isinstance(tree, str): return vars_order return tree @@ -234,9 +236,7 @@ def recover_formula(prefix_tree): EXAMPLES: - This example illustrates the recovery of a formula from a parse tree. - - :: + This example illustrates the recovery of a formula from a parse tree:: sage: import sage.logic.propcalc as propcalc sage: import sage.logic.logicparser as logicparser @@ -244,29 +244,23 @@ def recover_formula(prefix_tree): sage: logicparser.recover_formula(t) '(a&~~c)->~(~c|d)' - :: - sage: f = propcalc.formula("a&(~~c|d)") sage: logicparser.recover_formula(f.full_tree()) 'a&(~~c|d)' - :: - sage: r = ['~', 'a'] sage: logicparser.recover_formula(r) '~a' - :: - sage: s = ['d'] sage: logicparser.recover_formula(s) 'd' .. NOTE:: - The function :func:`polish_parse` may be passed as an argument, - but :func:`tree_parse` may not unless the parameter ``polish`` - is set to ``True``. + The function :func:`~sage.logic.logicparser.polish_parse()` may be + passed as an argument, but :func:`~sage.logic.logicparser.tree_parse()` + may not unless the parameter ``polish`` is set to ``True``. AUTHORS: @@ -296,9 +290,7 @@ def recover_formula_internal(prefix_tree): EXAMPLES: - This example illustrates recovering the formula from a parse tree. - - :: + This example illustrates recovering the formula from a parse tree:: sage: import sage.logic.logicparser as logicparser sage: import sage.logic.propcalc as propcalc @@ -306,22 +298,16 @@ def recover_formula_internal(prefix_tree): sage: logicparser.recover_formula_internal(t) '(a->b)' - :: - sage: r = ['~', 'c'] sage: logicparser.recover_formula_internal(r) '~c' - :: - sage: s = ['d'] sage: logicparser.recover_formula_internal(s) 'd' - We can pass :func:`recover_formula_internal` as an argument - in :func:`apply_func`. - - :: + We can pass :func:`~sage.logic.logicparser.recover_formula_internal()` + as an argument in :func:`~sage.logic.logicparser.apply_func()`:: sage: f = propcalc.formula("~(d|c)<->(a&~~~c)") sage: logicparser.apply_func(f.full_tree(), logicparser.recover_formula_internal) @@ -329,13 +315,13 @@ def recover_formula_internal(prefix_tree): .. NOTE:: - This function is for internal use by :mod:`logicparser`. The function - recovers the formula of a simple parse tree in prefix form. A - simple parse tree contains at most one operator. + This function is for internal use by :mod:`~sage.logic.logicparser`. + The function recovers the formula of a simple parse tree in prefix + form. A simple parse tree contains at most one operator. - The function :func:`polish_parse` may be passed as an argument, - but :func:`tree_parse` may not unless the parameter ``polish`` - is set to ``True``. + The function :func:`~sage.logic.logicparser.polish_parse()` may be + passed as an argument, but :func:`~sage.logic.logicparser.tree_parse()` + may not unless the parameter ``polish`` is set to ``True``. AUTHORS: @@ -355,6 +341,7 @@ def recover_formula_internal(prefix_tree): return repr(bool_formula) + def prefix_to_infix(prefix_tree): r""" Convert a parse tree from prefix form to infix form. @@ -370,9 +357,7 @@ def prefix_to_infix(prefix_tree): EXAMPLES: - This example illustrates converting a prefix tree to an infix tree. - - :: + This example illustrates converting a prefix tree to an infix tree:: sage: import sage.logic.logicparser as logicparser sage: import sage.logic.propcalc as propcalc @@ -388,9 +373,9 @@ def prefix_to_infix(prefix_tree): .. NOTE:: - The function :func:`polish_parse` may be passed as an argument, - but :func:`tree_parse` may not unless the parameter ``polish`` - is set to ``True``. + The function :func:`~sage.logic.logicparser.polish_parse()` may be + passed as an argument, but :func:`~sage.logic.logicparser.tree_parse()` + may not unless the parameter ``polish`` is set to ``True``. AUTHORS: @@ -406,8 +391,8 @@ def to_infix_internal(prefix_tree): INPUT: - - ``prefix_tree`` -- a list. This is a simple parse tree - in prefix form with at most one operator. + - ``prefix_tree`` -- a list; this is a simple parse tree + in prefix form with at most one operator OUTPUT: @@ -416,9 +401,7 @@ def to_infix_internal(prefix_tree): EXAMPLES: This example illustrates converting a simple tree from prefix - to infix form. - - :: + to infix form:: sage: import sage.logic.logicparser as logicparser sage: import sage.logic.propcalc as propcalc @@ -426,9 +409,8 @@ def to_infix_internal(prefix_tree): sage: logicparser.to_infix_internal(t) ['a', '|', 'b'] - We can pass :func:`to_infix_internal` as an argument in :func:`apply_func`. - - :: + We can pass :func:`~sage.logic.logicparser.to_infix_internal()` as an + argument in :func:`~sage.logic.logicparser.apply_func()`:: sage: f = propcalc.formula("(a&~b)<->~~~(c|d)") sage: logicparser.apply_func(f.full_tree(), logicparser.to_infix_internal) @@ -436,13 +418,13 @@ def to_infix_internal(prefix_tree): .. NOTE:: - This function is for internal use by :mod:`logicparser`. It converts - a simple parse tree from prefix form to infix form. A simple parse - tree contains at most one operator. + This function is for internal use by :mod:`~sage.logic.logicparser`. + It converts a simple parse tree from prefix form to infix form. A + simple parse tree contains at most one operator. The function :func:`polish_parse` may be passed as an argument, - but :func:`tree_parse` may not unless the parameter ``polish`` - is set to ``True``. + but :func:`~sage.logic.logicparser.tree_parse()` may not unless the + parameter ``polish`` is set to ``True``. AUTHORS: @@ -454,7 +436,8 @@ def to_infix_internal(prefix_tree): def tokenize(s): r""" - Return the tokens and the distinct variables appearing in a boolean formula s. + Return the tokens and the distinct variables appearing in a boolean + formula ``s``. INPUT: @@ -462,16 +445,17 @@ def tokenize(s): OUTPUT: - The tokens and variables as an ordered pair of lists in the following order: + The tokens and variables as an ordered pair of lists in the following + order: - 1. A list containing the tokens of s, in the order they appear in s - 2. A list containing the distinct variables in s, in the order they appearn in s + 1. A list containing the tokens of ``s``, in the order they appear in ``s`` + 2. A list containing the distinct variables in ``s``, in the order + they appear in ``s`` EXAMPLES: - This example illustrates how to tokenize a string representation of a boolean formula. - - :: + This example illustrates how to tokenize a string representation of a + boolean formula:: sage: import sage.logic.logicparser as logicparser sage: s = 'a|b&c' @@ -497,73 +481,74 @@ def tokenize(s): # check to see if '-', '<' or '>' are used incorrectly elif s[i] in '<->': raise SyntaxError("'{}' can only be used as part of the operators '<->' or '->'.".format(s[i])) + if len(tok) > 0: toks.append(tok) i += skip continue - else: - # token is a variable name - if s[i] == ' ': - i += 1 - continue - while i < len(s) and s[i] not in __symbols and s[i] != ' ': - tok += s[i] - i += 1 + # token is a variable name + if s[i] == ' ': + i += 1 + continue - if len(tok) > 0: - if tok[0] not in string.letters: + while i < len(s) and s[i] not in __symbols and s[i] != ' ': + tok += s[i] + i += 1 + + if len(tok) > 0: + if tok[0] not in string.letters: + valid = 0 + for c in tok: + if c not in string.letters and c not in string.digits and c != '_': valid = 0 - for c in tok: - if c not in string.letters and c not in string.digits and c != '_': - valid = 0 - - if valid == 1: - toks.append(tok) - if tok not in vars_order: - vars_order.append(tok) - else: - msg = 'invalid variable name ' + tok - msg += ": identifiers must begin with a letter and contain only " - msg += "alphanumerics and underscores" - raise NameError(msg) + + if valid == 1: + toks.append(tok) + if tok not in vars_order: + vars_order.append(tok) + else: + msg = "invalid variable name " + tok + msg += ": identifiers must begin with a letter and contain only " + msg += "alphanumerics and underscores" + raise NameError(msg) toks.append(')') return toks, vars_order -def tree_parse(toks, polish = False): +def tree_parse(toks, polish=False): r""" - Return a parse tree from the tokens in toks. + Return a parse tree from the tokens in ``toks``. INPUT: - ``toks`` -- a list of tokens from a boolean formula - - ``polish`` -- (default: False) a boolean. When true, tree_parse will return - the full syntax parse tree. + - ``polish`` -- (default: ``False``) a boolean; when ``True``, + :func:`~sage.logic.logicparser.tree_parse()` will return + the full syntax parse tree OUTPUT: - A parse tree in the form of a nested list that depends on ``polish`` as follows: + A parse tree in the form of a nested list that depends on ``polish`` + as follows: - polish == False -- Return a simplified parse tree. + - If ``False``, then return a simplified parse tree. - polish == True -- Return the full syntax parse tree. + - If ``True``, then return the full syntax parse tree. EXAMPLES: - This example illustrates the use of tree_parse when polish == False. - - :: + This example illustrates the use of + :func:`~sage.logic.logicparser.tree_parse()` when ``polish`` is ``False``:: sage: import sage.logic.logicparser as logicparser sage: t = ['(', 'a', '|', 'b', '&', 'c', ')'] sage: logicparser.tree_parse(t) ['|', 'a', ['&', 'b', 'c']] - We now demonstrate the use of tree_parse when polish == True. - - :: + We now demonstrate the use of :func:`~sage.logic.logicparser.tree_parse()` + when ``polish`` is ``True``:: sage: t = ['(', 'a', '->', '~', '~', 'b', ')'] sage: logicparser.tree_parse(t) @@ -587,9 +572,9 @@ def tree_parse(toks, polish = False): stack.append(branch) return stack[0] -def parse_ltor(toks, n = 0, polish = False): +def parse_ltor(toks, n=0, polish=False): r""" - Return a parse tree from toks, where each token in toks is atomic. + Return a parse tree from ``toks``, where each token in ``toks`` is atomic. INPUT: @@ -598,22 +583,22 @@ def parse_ltor(toks, n = 0, polish = False): - ``n`` -- (default: 0) an integer representing which order of operations are occurring - - ``polish`` -- (default: False) a boolean. When true, double negations - are not cancelled and negated statements are turned into list of length two. + - ``polish`` -- (default: ``False``) a boolean; when ``True``, double + negations are not cancelled and negated statements are turned into + list of length two. OUTPUT: The parse tree as a nested list that depends on ``polish`` as follows: - polish == False - Return a simplified parse tree. + - If ``False``, then return a simplified parse tree. - polish == True - Return the full syntax parse tree. + - If ``True``, then return the full syntax parse tree. EXAMPLES: - This example illustrates the use of parse_ltor when polish == False. - - :: + This example illustrates the use of + :func:`~sage.logic.logicparser.parse_ltor()` when ``polish`` is ``False``:: sage: import sage.logic.logicparser as logicparser sage: t = ['a', '|', 'b', '&', 'c'] @@ -627,15 +612,12 @@ def parse_ltor(toks, n = 0, polish = False): sage: logicparser.parse_ltor(t) ['->', 'a', 'b'] - We now repeat the previous example, but with polish == True. - - :: + We now repeat the previous example, but with ``polish`` being ``True``:: sage: import sage.logic.logicparser as logicparser sage: t = ['a', '->', '~', '~', 'b'] sage: logicparser.parse_ltor(t, polish = True) ['->', 'a', ['~', ['~', 'b']]] - """ i = 0 for tok in toks: @@ -677,24 +659,23 @@ def parse_ltor(toks, n = 0, polish = False): def apply_func(tree, func): r""" - Apply func to each node of tree. Return a new parse tree. + Apply ``func`` to each node of ``tree``, and return a new parse tree. INPUT: - ``tree`` -- a parse tree of a boolean formula - - ``func`` -- a function to be applied to each node of tree. This may - be a function that comes from elsewhere in the logic module. + - ``func`` -- a function to be applied to each node of tree; this may + be a function that comes from elsewhere in the logic module OUTPUT: - The new parse tree in the form of a nested list + The new parse tree in the form of a nested list. EXAMPLES: - This example uses :func:`apply_func` where ``func`` switches two entries of tree. - - :: + This example uses :func:`~sage.logic.logicparser.apply_func()` where + ``func`` switches two entries of tree:: sage: import sage.logic.logicparser as logicparser sage: t = ['|', ['&', 'a', 'b'], ['&', 'a', 'c']] diff --git a/src/sage/logic/logictable.py b/src/sage/logic/logictable.py index 2f74e9f6dca..23982265a63 100644 --- a/src/sage/logic/logictable.py +++ b/src/sage/logic/logictable.py @@ -1,34 +1,37 @@ r""" -Module that creates truth tables of boolean formulas. +Logic Tables A logic table is essentially a 2-D array that is created by the statement class and stored in the private global variable table, along with a list containing the variable names to be used, in order. -The order in which the table is listed essentially amounts to counting in binary. -For instance, with the variables A, B, and C the truth table looks like: +The order in which the table is listed essentially amounts to counting in +binary. For instance, with the variables `A`, `B`, and `C` the truth table +looks like:: -A B C value -False False False ? -False False True ? -False True False ? -False True True ? -True False False ? -True False True ? -True True False ? -True True True ? + A B C value + False False False ? + False False True ? + False True False ? + False True True ? + True False False ? + True False True ? + True True False ? + True True True ? This is equivalent to counting in binary, where a table would appear thus; -2^2 2^1 2^0 value -0 0 0 0 -0 0 1 1 -0 1 0 2 -0 1 1 3 -1 0 0 4 -1 0 1 5 -1 1 0 6 -1 1 1 7 +:: + + 2^2 2^1 2^0 value + 0 0 0 0 + 0 0 1 1 + 0 1 0 2 + 0 1 1 3 + 1 0 0 4 + 1 0 1 5 + 1 1 0 6 + 1 1 1 7 Given that a table can be created corresponding to any range of acceptable values for a given statement, it is easy to find the value of a statement @@ -100,8 +103,8 @@ .. NOTE:: - For statements that contain a variable list that when printed is longer than - the \latex page, the columns of the table will run off the screen. + For statements that contain a variable list that when printed is longer + than the latex page, the columns of the table will run off the screen. """ #***************************************************************************** # Copyright (C) 2006 William Stein @@ -119,23 +122,19 @@ __vars_order = [] class Truthtable: + """ + A truth table. - def __init__(self, t, vo): - r""" - Initialize the data fields - - INPUT: - - - ``self'' -- calling object - - - ``t`` -- a 2-D array containing the table values - - - ``vo`` -- a list of the variables in the expression in order, - with each variable occurring only once + INPUT: - OUTPUT: + - ``t`` -- a 2-D array containing the table values - None + - ``vo`` -- a list of the variables in the expression in order, + with each variable occurring only once + """ + def __init__(self, t, vo): + r""" + Initialize the data fields. EXAMPLES: @@ -163,33 +162,26 @@ def _latex_(self): r""" Return a latex representation of the calling table object. - INPUT: - - - ``self`` -- calling object - OUTPUT: - The latex representation of the table + The latex representation of the table. EXAMPLES: - This example illustrates how to get the latex represenation of the table - - :: + This example illustrates how to get the latex representation of + the table:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("man->monkey&human") sage: latex(s.truthtable()) \\\begin{tabular}{llll}human & monkey & man & value \\\hline False & False & False & True \\False & False & True & True \\False & True & False & True \\False & True & True & True \\True & False & False & False \\True & False & True & False \\True & True & False & False \\True & True & True & True\end{tabular} - Now, we show that strange parameters can lead to a table header with no body. - - :: + Now, we show that strange parameters can lead to a table header + with no body:: sage: latex(s.truthtable(2, 1)) \\\begin{tabular}{llll}human & monkey & man & value \\\hli\end{tabular} """ - vars_len = [] rt = s = "" self.__vars_order.reverse() s = r'\\\begin{tabular}{' @@ -210,19 +202,14 @@ def __repr__(self): r""" Return a string representation of the calling table object. - INPUT: - - - ``self`` -- the calling object - OUTPUT: - The talbe as a 2-D array + The table as a 2-D string array. EXAMPLES: - This example illustrates how to display the truth table of a boolean formula. - - :: + This example illustrates how to display the truth table of a + boolean formula:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("man->monkey&human") @@ -237,9 +224,8 @@ def __repr__(self): True True False False True True True True - We now show that strange parameters can lead to the table header with no body. - - :: + We now show that strange parameters can lead to the table + header with no body:: sage: s.truthtable(2, 1) man monkey human value @@ -276,19 +262,13 @@ def get_table_list(self): r""" Return a list representation of the calling table object. - INPUT: - - - ``self`` -- calling object - OUTPUT: - A list representation of the table + A list representation of the table. EXAMPLES: - This example illustrates how to show the table as a list. - - :: + This example illustrates how to show the table as a list:: sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("man->monkey&human") diff --git a/src/sage/logic/propcalc.py b/src/sage/logic/propcalc.py index b1b76b06bc9..8368022e00e 100644 --- a/src/sage/logic/propcalc.py +++ b/src/sage/logic/propcalc.py @@ -1,5 +1,5 @@ r""" -Module that creates formulas of propositional calculus +Propositional Calculus Formulas consist of the following operators: @@ -150,21 +150,19 @@ def formula(s): r""" - Return an instance of :class:`BooleanFormula` + Return an instance of :class:`BooleanFormula`. INPUT: - - ``s`` -- a string that contains a logical expression. + - ``s`` -- a string that contains a logical expression OUTPUT: - An instance of :class:`BooleanFormula` + An instance of :class:`BooleanFormula`. EXAMPLES: - This example illustrates ways to create a boolean formula. - - :: + This example illustrates ways to create a boolean formula:: sage: import sage.logic.propcalc as propcalc sage: f = propcalc.formula("a&~b|c") @@ -172,9 +170,7 @@ def formula(s): sage: f&g|f ((a&~b|c)&(a^c<->b))|(a&~b|c) - We now demonstrate some possible errors. - - :: + We now demonstrate some possible errors:: sage: propcalc.formula("((a&b)") Traceback (most recent call last): diff --git a/src/sage/matrix/matrix_integer_dense.pyx b/src/sage/matrix/matrix_integer_dense.pyx index 6a7b1a8b4b7..2cec8c7f833 100644 --- a/src/sage/matrix/matrix_integer_dense.pyx +++ b/src/sage/matrix/matrix_integer_dense.pyx @@ -148,6 +148,11 @@ cdef extern from "iml.h": mpz_t mp_D) +fplll_fp_map = {None: None, + 'fp': 'double', + 'qd': 'long double', + 'xd': 'dpe', + 'rr': 'mpfr'} cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse r""" @@ -2419,19 +2424,16 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse #################################################################################### def LLL_gram(self): """ - LLL reduction of the lattice whose gram matrix is self. + LLL reduction of the lattice whose gram matrix is ``self``. INPUT: - - - ``M`` - gram matrix of a definite quadratic form - + - ``M`` -- gram matrix of a definite quadratic form OUTPUT: - - - ``U`` - unimodular transformation matrix such that - U.transpose() \* M \* U is LLL-reduced. + ``U`` - unimodular transformation matrix such that + ``U.T * M * U`` is LLL-reduced. ALGORITHM: Use PARI @@ -2447,7 +2449,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse [1 0] [0 1] - Semidefinite and indefinite forms no longer raise a ValueError:: + Semidefinite and indefinite forms no longer raise a ``ValueError``:: sage: Matrix(ZZ,2,2,[2,6,6,3]).LLL_gram() [-3 -1] @@ -2475,73 +2477,104 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse U[i,n-1] = - U[i,n-1] return U - def BKZ(self, delta=None, fp="rr", block_size=10, prune=0, use_givens=False): + def BKZ(self, delta=None, algorithm="fpLLL", fp=None, block_size=10, prune=0, use_givens=False, + precision=0, max_loops=0, max_time=0, auto_abort=False): """ Block Korkin-Zolotarev reduction. INPUT: + - ``delta`` -- (default: ``0.99``) LLL parameter - - ``fp`` - 'fp' - double precision: NTL's FP or - fpLLL's double + - ``algorithm`` -- (default: ``"fpLLL"``) ``"fpLLL"`` or ``"NTL"`` - - ``'qd'`` - quad doubles: NTL's QP + - ``fp`` -- floating point number implementation - - ``'qd1'`` - quad doubles: uses quad_float precision - to compute Gram-Schmidt, but uses double precision in the search - phase of the block reduction algorithm. This seems adequate for - most purposes, and is faster than 'qd', which uses quad_float - precision uniformly throughout. + - ``None`` -- NTL's exact reduction or fpLLL's wrapper (default) - - ``'xd'`` - extended exponent: NTL's XD + - ``'fp'`` -- double precision: NTL's FP or fpLLL's double - - ``'rr'`` - arbitrary precision (default) + - ``'qd'`` -- NTL's QP or fpLLL's long doubles - - ``block_size`` - specifies the size of the blocks - in the reduction. High values yield shorter vectors, but the - running time increases exponentially with - ``block_size``. ``block_size`` should be - between 2 and the number of rows of ``self`` (default: - 10) + - ``'qd1'`` -- quad doubles: Uses ``quad_float`` precision to compute + Gram-Schmidt, but uses double precision in the search phase of the + block reduction algorithm. This seems adequate for most purposes, + and is faster than ``'qd'``, which uses quad_float precision + uniformly throughout (NTL only). - - ``prune`` - The optional parameter - ``prune`` can be set to any positive number to invoke - the Volume Heuristic from [Schnorr and Horner, Eurocrypt '95]. This - can significantly reduce the running time, and hence allow much - bigger block size, but the quality of the reduction is of course - not as good in general. Higher values of ``prune`` mean - better quality, and slower running time. When ``prune`` - == 0, pruning is disabled. Recommended usage: for - ``block_size`` = 30, set 10 = ``prune`` = - 15. + - ``'xd'`` -- extended exponent: NTL's XD or fpLLL's dpe - - ``use_givens`` - use Given's orthogonalization. - This is a bit slower, but generally much more stable, and is really - the preferred orthogonalization strategy. For a nice description of - this, see Chapter 5 of [G. Golub and C. van Loan, Matrix - Computations, 3rd edition, Johns Hopkins Univ. Press, 1996]. + - ``'rr'`` -- arbitrary precision: NTL'RR or fpLLL's MPFR + - ``block_size`` -- (default: ``10``) Specifies the size of the blocks + in the reduction. High values yield shorter vectors, but the running + time increases double exponentially with ``block_size``. + ``block_size`` should be between 2 and the number of rows + of ``self``. - EXAMPLE:: + NLT SPECIFIC INPUTS: + + - ``prune`` -- (default: ``0``) The optional parameter ``prune`` can + be set to any positive number to invoke the Volume Heuristic from + [SH95]_. This can significantly reduce the running time, and hence + allow much bigger block size, but the quality of the reduction is + of course not as good in general. Higher values of ``prune`` mean + better quality, and slower running time. When ``prune`` is ``0``, + pruning is disabled. Recommended usage: for ``block_size==30``, set + ``10 <= prune <=15``. + + - ``use_givens`` -- Use Given's orthogonalization. This is a bit + slower, but generally much more stable, and is really the preferred + orthogonalization strategy. For a nice description of this, see + Chapter 5 of [GL96]_. + + fpLLL SPECIFIC INPUTS: + + - ``precision`` -- (default: ``0`` for automatic choice) bit + precision to use if ``fp='rr'`` is set + + - ``max_loops`` -- (default: ``0`` for no restriction) maximum + number of full loops + + - ``max_time`` -- (default: ``0`` for no restricion) stop after + time seconds (up to loop completion) + + - ``auto_abort`` -- (default: ``False``) heuristic, stop when the + average slope of `\log(||b_i^*||)` does not decrease fast enough + + EXAMPLES:: sage: A = Matrix(ZZ,3,3,range(1,10)) sage: A.BKZ() [ 0 0 0] [ 2 1 0] [-1 1 3] + sage: A = Matrix(ZZ,3,3,range(1,10)) sage: A.BKZ(use_givens=True) [ 0 0 0] [ 2 1 0] [-1 1 3] - :: - sage: A = Matrix(ZZ,3,3,range(1,10)) sage: A.BKZ(fp="fp") [ 0 0 0] [ 2 1 0] [-1 1 3] + + ALGORITHM: + + Calls either NTL or fpLLL. + + REFERENCES: + + .. [SH95] C. P. Schnorr and H. H. Hörner. *Attacking the Chor-Rivest + Cryptosystem by Improved Lattice Reduction*. Advances in Cryptology + - EUROCRYPT '95. LNCS Volume 921, 1995, pp 1-12. + + .. [GL96] G. Golub and C. van Loan. *Matrix Computations*. + 3rd edition, Johns Hopkins Univ. Press, 1996. + """ if delta is None: delta = 0.99 @@ -2551,153 +2584,169 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse raise TypeError("delta must be <= 1") delta = float(delta) - if fp is None: - fp = "rr" - - if fp == "fp": - algorithm = "BKZ_FP" - elif fp == "qd": - algorithm = "BKZ_QP" - elif fp == "qd1": - algorithm = "BKZ_QP1" - elif fp == "xd": - algorithm = "BKZ_XD" - elif fp == "rr": - algorithm = "BKZ_RR" - else: - raise TypeError("fp parameter not understood.") - - block_size = int(block_size) - if prune < 0: raise TypeError("prune must be >= 0") prune = int(prune) - if get_verbose() >= 2: - verbose = True - else: - verbose = False + verbose = get_verbose() >= 2 - A = self._ntl_() + block_size = int(block_size) - if algorithm == "BKZ_FP": - if not use_givens: - r = A.BKZ_FP(U=None, delta=delta, BlockSize=block_size, prune=prune, verbose=verbose) + if algorithm == "NTL": + if fp is None: + fp = "rr" + + if fp == "fp": + algorithm = "BKZ_FP" + elif fp == "qd": + algorithm = "BKZ_QP" + elif fp == "qd1": + algorithm = "BKZ_QP1" + elif fp == "xd": + algorithm = "BKZ_XD" + elif fp == "rr": + algorithm = "BKZ_RR" else: - r = A.G_BKZ_FP(U=None, delta=delta, BlockSize=block_size, prune=prune, verbose=verbose) + raise TypeError("fp parameter not understood.") - elif algorithm == "BKZ_QP": - if not use_givens: - r = A.BKZ_QP(U=None, delta=delta, BlockSize=block_size, prune=prune, verbose=verbose) - else: - r = A.G_BKZ_QP(U=None, delta=delta, BlockSize=block_size, prune=prune, verbose=verbose) + A = self._ntl_() - elif algorithm == "BKZ_QP1": - if not use_givens: - r = A.BKZ_QP1(U=None, delta=delta, BlockSize=block_size, prune=prune, verbose=verbose) - else: - r = A.G_BKZ_QP1(U=None, delta=delta, BlockSize=block_size, prune=prune, verbose=verbose) + if algorithm == "BKZ_FP": + if not use_givens: + r = A.BKZ_FP(U=None, delta=delta, BlockSize=block_size, + prune=prune, verbose=verbose) + else: + r = A.G_BKZ_FP(U=None, delta=delta, BlockSize=block_size, + prune=prune, verbose=verbose) - elif algorithm == "BKZ_XD": - if not use_givens: - r = A.BKZ_XD(U=None, delta=delta, BlockSize=block_size, prune=prune, verbose=verbose) - else: - r = A.G_BKZ_XD(U=None, delta=delta, BlockSize=block_size, prune=prune, verbose=verbose) + elif algorithm == "BKZ_QP": + if not use_givens: + r = A.BKZ_QP(U=None, delta=delta, BlockSize=block_size, + prune=prune, verbose=verbose) + else: + r = A.G_BKZ_QP(U=None, delta=delta, BlockSize=block_size, + prune=prune, verbose=verbose) - elif algorithm == "BKZ_RR": - if not use_givens: - r = A.BKZ_RR(U=None, delta=delta, BlockSize=block_size, prune=prune, verbose=verbose) - else: - r = A.G_BKZ_RR(U=None, delta=delta, BlockSize=block_size, prune=prune, verbose=verbose) + elif algorithm == "BKZ_QP1": + if not use_givens: + r = A.BKZ_QP1(U=None, delta=delta, BlockSize=block_size, + prune=prune, verbose=verbose) + else: + r = A.G_BKZ_QP1(U=None, delta=delta, BlockSize=block_size, + prune=prune, verbose=verbose) - self.cache("rank",ZZ(r)) - R = self.new_matrix(entries=map(ZZ,A.list())) + elif algorithm == "BKZ_XD": + if not use_givens: + r = A.BKZ_XD(U=None, delta=delta, BlockSize=block_size, + prune=prune, verbose=verbose) + else: + r = A.G_BKZ_XD(U=None, delta=delta, BlockSize=block_size, + prune=prune, verbose=verbose) + + elif algorithm == "BKZ_RR": + if not use_givens: + r = A.BKZ_RR(U=None, delta=delta, BlockSize=block_size, + prune=prune, verbose=verbose) + else: + r = A.G_BKZ_RR(U=None, delta=delta, BlockSize=block_size, + prune=prune, verbose=verbose) + + self.cache("rank",ZZ(r)) + R = self.new_matrix(entries=map(ZZ,A.list())) + + elif algorithm == "fpLLL": + from sage.libs.fplll.fplll import FP_LLL + fp = fplll_fp_map[fp] + A = FP_LLL(self) + A.BKZ(block_size=block_size, + delta=delta, + float_type=fp, + precision=precision, + verbose=verbose, + max_time=max_time, + max_loops=max_loops, + auto_abort=auto_abort) + R = A._sage_() return R - def LLL(self, delta=None, eta=None, algorithm="fpLLL:wrapper", fp=None, prec=0, early_red = False, use_givens = False): + def LLL(self, delta=None, eta=None, algorithm="fpLLL:wrapper", fp=None, prec=0, early_red=False, use_givens=False, use_siegel=False): r""" - Returns LLL reduced or approximated LLL reduced lattice R for this + Return LLL reduced or approximated LLL reduced lattice `R` for this matrix interpreted as a lattice. - A lattice `(b_1, b_2, ..., b_d)` is - `(\delta, \eta)` -LLL-reduced if the two following - conditions hold: + A lattice `(b_1, b_2, ..., b_d)` is `(\delta, \eta)`-LLL-reduced + if the two following conditions hold: + + - For any `i > j`, we have `\lvert \mu_{i,j} \rvert \leq \eta`. - - For any `i>j`, we have `|mu_{i, j}| <= \eta`, - - For any `i/` and - `b_i^*` is the `i`-th vector of the Gram-Schmidt + where `μ_{i,j} = \langle b_i, b_j^* \rangle / \langle b_j^*, b_j^* + \rangle` and `b_i^*` is the `i`-th vector of the Gram-Schmidt orthogonalisation of `(b_1, b_2, ..., b_d)`. - The default reduction parameters are `\delta=3/4` and - `eta=0.501`. The parameters `\delta` and - `\eta` must satisfy: `0.25 < \delta <= 1.0` and - `0.5 <= \eta < sqrt(\delta)`. Polynomial time + The default reduction parameters are `\delta = 3/4` and `\eta = 0.501`. + The parameters `\delta` and `\eta` must satisfy: `0.25 < \delta + \leq 1.0` and `0.5 \leq \eta < \sqrt{\delta}`. Polynomial time complexity is only guaranteed for `\delta < 1`. The lattice is returned as a matrix. Also the rank (and the - determinant) of self are cached if those are computed during the - reduction. Note that in general this only happens when self.rank() - == self.ncols() and the exact algorithm is used. + determinant) of ``self`` are cached if those are computed during + the reduction. Note that in general this only happens when + ``self.rank() == self.ncols()`` and the exact algorithm is used. INPUT: + - ``delta`` -- (default: ``0.99``) `\delta` parameter as described + above - - ``delta`` - parameter as described above (default: - 3/4) - - - ``eta`` - parameter as described above (default: - 0.501), ignored by NTL - - - ``algorithm`` - string (default: "fpLLL:wrapper") - one of the algorithms mentioned below - - - ``fp`` + - ``eta`` -- (default: ``0.501``) `\eta` parameter as described above, + ignored by NTL - - None - NTL's exact reduction or fpLLL's - wrapper + - ``algorithm`` -- string one of the algorithms listed below + (default: ``"fpLLL:wrapper"``). - - ``'fp'`` - double precision: NTL's FP or fpLLL's - double + - ``fp`` -- floating point number implementation: - - ``'qd'`` - quad doubles: NTL's QP + - ``None`` -- NTL's exact reduction or fpLLL's wrapper + - ``'fp'`` -- double precision: NTL's FP or fpLLL's double + - ``'qd'`` -- NTL's QP or fpLLL's long doubles + - ``'xd'`` -- extended exponent: NTL's XD or fpLLL's dpe + - ``'rr'`` -- arbitrary precision: NTL's RR or fpLLL's MPFR - - ``'xd'`` - extended exponent: NTL's XD or fpLLL's - dpe + - ``prec`` -- (default: auto choose) precision, ignored by NTL - - ``'rr'`` - arbitrary precision: NTL'RR or fpLLL's - MPFR + - ``early_red`` -- (default: ``False``) perform early reduction, + ignored by NTL - - ``prec`` - precision, ignored by NTL (default: auto - choose) + - ``use_givens`` -- (default: ``False``) use Givens orthogonalization + only applicable to approximate reductions and NTL; this is more + stable but slower - - ``early_red`` - perform early reduction, ignored by - NTL (default: False) + - ``use_siegel`` -- (default: ``False``) use Siegel's condition + instead of Lovasz's condition, ignored by NTL - - ``use_givens`` - use Givens orthogonalization - (default: False) only applicable to approximate reductions and NTL. - This is more stable but slower. + Also, if the verbose level is at least `2`, some more verbose output + is printed during the computation. - Also, if the verbose level is = 2, some more verbose output is - printed during the calculation if NTL is used. + AVAILABLE ALGORITHMS: - AVAILABLE ALGORITHMS: + - ``NTL:LLL`` - NTL's LLL + choice of ``fp``. - - ``NTL:LLL`` - NTL's LLL + fp + - ``fpLLL:heuristic`` - fpLLL's heuristic + choice of ``fp``. - - ``fpLLL:heuristic`` - fpLLL's heuristic + fp + - ``fpLLL:fast`` - fpLLL's fast + choice of ``fp``. - - ``fpLLL:fast`` - fpLLL's fast + - ``fpLLL:proved`` - fpLLL's proved + choice of ``fp``. - - ``fpLLL:wrapper`` - fpLLL's automatic choice - (default) + - ``fpLLL:wrapper`` - fpLLL's automatic choice (default). + OUTPUT: - OUTPUT: a matrix over the integers + A matrix over the integers. - EXAMPLE:: + EXAMPLES:: sage: A = Matrix(ZZ,3,3,range(1,10)) sage: A.LLL() @@ -2708,8 +2757,8 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse We compute the extended GCD of a list of integers using LLL, this example is from the Magma handbook:: - sage: Q = [ 67015143, 248934363018, 109210, 25590011055, 74631449, \ - 10230248, 709487, 68965012139, 972065, 864972271 ] + sage: Q = [ 67015143, 248934363018, 109210, 25590011055, 74631449, + ....: 10230248, 709487, 68965012139, 972065, 864972271 ] sage: n = len(Q) sage: S = 100 sage: X = Matrix(ZZ, n, n + 1) @@ -2751,15 +2800,15 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse ... TypeError: algorithm NTL:LLL_QD not supported - ALGORITHM: Uses the NTL library by Victor Shoup or fpLLL library by - Damien Stehle depending on the chosen algorithm. - - REFERENCES: + .. NOTE:: - - ``ntl.mat_ZZ`` or ``sage.libs.fplll.fplll`` for details on + See ``ntl.mat_ZZ`` or ``sage.libs.fplll.fplll`` for details on the used algorithms. + """ - if self.ncols()==0 or self.nrows()==0: + from sage.libs.fplll.fplll import FP_LLL + + if self.ncols() == 0 or self.nrows() == 0: verbose("Trivial matrix, nothing to do") return self @@ -2767,17 +2816,15 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse import sage.libs.ntl.all ntl_ZZ = sage.libs.ntl.all.ZZ - from sage.libs.fplll.fplll import FP_LLL - - if get_verbose() >= 2: verb = True - else: verb = False + verb = get_verbose() >= 2 - # auto choice + if prec < 0: + raise TypeError("precision prec must be >= 0") + prec = int(prec) - # FP choice if algorithm == 'NTL:LLL': if fp is None: - algorithm = 'NTL:LLL_FP' + algorithm = 'NTL:LLL' elif fp == 'fp': algorithm = 'NTL:LLL_FP' elif fp == 'qd': @@ -2786,21 +2833,9 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse algorithm = 'NTL:LLL_XD' elif fp == 'rr': algorithm = 'NTL:LLL_RR' - elif algorithm == 'fpLLL:heuristic': - if fp is None: - raise TypeError("if 'fpLLL:heuristic' is chosen, a floating point number implementation must be chosen") - elif fp == 'fp': - fp = 'double' - elif fp == 'qd': - raise TypeError("fpLLL does not support quad doubles.") - elif fp == 'xd': - fp = 'dpe' - elif fp == 'rr': - fp = 'mpfr' - if algorithm == "NTL:LLL": if delta is None: - delta = ZZ(3)/ZZ(4) + delta = ZZ(99)/ZZ(100) elif delta <= ZZ(1)/ZZ(4): raise TypeError("delta must be > 1/4") elif delta > 1: @@ -2810,6 +2845,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse b = delta.denom() else: + fp = fplll_fp_map[fp] if delta is None: delta = 0.99 elif delta <= 0.25: @@ -2823,10 +2859,6 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse elif eta < 0.5: raise TypeError("eta must be >= 0.5") - if prec < 0: - raise TypeError("precision prec must be >= 0") - int(prec) - if algorithm.startswith('NTL:'): A = sage.libs.ntl.all.mat_ZZ(self.nrows(),self.ncols(),map(ntl_ZZ,self.list())) @@ -2834,7 +2866,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse r, det2 = A.LLL(a,b, verbose=verb) det2 = ZZ(det2) try: - det = ZZ(det2.sqrt_approx()) + det = ZZ(det2.sqrt()) self.cache("det", det) except TypeError: pass @@ -2867,24 +2899,15 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse self.cache("rank",r) elif algorithm.startswith('fpLLL:'): - - A = sage.libs.fplll.fplll.FP_LLL(self) - if algorithm == 'fpLLL:wrapper': - A.wrapper(prec, eta, delta) - elif algorithm == 'fpLLL:heuristic': - if early_red: - A.heuristic_early_red(prec,eta,delta,fp) - else: - A.heuristic(prec,eta,delta,fp) - elif algorithm == 'fpLLL:fast': - if early_red: - A.fast_early_red(prec,eta,delta) - else: - A.fast(prec,eta,delta) - elif algorithm == 'fpLLL:proved': - A.proved(prec,eta,delta) - else: - raise TypeError("algorithm %s not supported"%algorithm) + A = FP_LLL(self) + method = algorithm.replace("fpLLL:","") + A.LLL(delta=delta, eta=eta, + method=method, + float_type=fp, + precision=prec, + verbose=verb, + siegel=use_siegel, + early_red=early_red) R = A._sage_() else: raise TypeError("algorithm %s not supported"%algorithm) @@ -2894,21 +2917,16 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse def is_LLL_reduced(self, delta=None, eta=None): r""" - Return ``True`` if this lattice is - `(\delta, \eta)`-LLL reduced. See ``self.LLL`` - for a definition of LLL reduction. + Return ``True`` if this lattice is `(\delta, \eta)`-LLL reduced. + See ``self.LLL`` for a definition of LLL reduction. INPUT: + - ``delta`` -- (default: `0.99`) parameter `\delta` as described above - - ``delta`` - parameter as described above (default: - 3/4) - - - ``eta`` - parameter as described above (default: - 0.501) + - ``eta`` -- (default: `0.501`) parameter `\eta` as described above - - EXAMPLE:: + EXAMPLES:: sage: A = random_matrix(ZZ, 10, 10) sage: L = A.LLL() @@ -2920,9 +2938,9 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse if eta is None: eta = 0.501 if delta is None: - delta = ZZ(3)/ZZ(4) + delta = ZZ(99) / ZZ(100) - if delta <= ZZ(1)/ZZ(4): + if delta <= ZZ(1) / ZZ(4): raise TypeError("delta must be > 1/4") elif delta > 1: raise TypeError("delta must be <= 1") @@ -2948,14 +2966,12 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse def prod_of_row_sums(self, cols): """ Return the product of the sums of the entries in the submatrix of - self with given columns. + ``self`` with given columns. INPUT: - - - ``cols`` - a list (or set) of integers representing - columns of self. - + - ``cols`` -- a list (or set) of integers representing columns + of ``self`` OUTPUT: an integer @@ -2970,6 +2986,7 @@ cdef class Matrix_integer_dense(matrix_dense.Matrix_dense): # dense or sparse 40 sage: a.prod_of_row_sums(set([0,2])) 40 + """ cdef Py_ssize_t c, row cdef mpz_t s, pr diff --git a/src/sage/misc/functional.py b/src/sage/misc/functional.py index b4f693e2182..065bba9ed5e 100644 --- a/src/sage/misc/functional.py +++ b/src/sage/misc/functional.py @@ -729,15 +729,22 @@ def integral(x, *args, **kwds): 2 Another symbolic integral, from :trac:`11238`, that used to return - zero incorrectly; with maxima 5.26.0 one gets 1/2*sqrt(pi)*e^(1/4), - whereas with 5.29.1 the expression is less pleasant, but still - has the same value:: - + zero incorrectly; with Maxima 5.26.0 one gets + ``1/2*sqrt(pi)*e^(1/4)``, whereas with 5.29.1, and even more so + with 5.33.0, the expression is less pleasant, but still has the + same value. Unfortunately, the computation takes a very long time + with the default settings, so we temporarily use the Maxima + setting ``domain: real``:: + + sage: sage.calculus.calculus.maxima('domain: real') + real sage: f = exp(-x) * sinh(sqrt(x)) sage: t = integrate(f, x, 0, Infinity); t # long time - 1/4*(sqrt(pi)*(erf(1) - 1) + sqrt(pi) + 2*e^(-1) - 2)*e^(1/4) - 1/4*(sqrt(pi)*(erf(1) - 1) - sqrt(pi) + 2*e^(-1) - 2)*e^(1/4) + 1/4*sqrt(pi)*(erf(1) - 1)*e^(1/4) - 1/4*(sqrt(pi)*(erf(1) - 1) - sqrt(pi) + 2*e^(-1) - 2)*e^(1/4) + 1/4*sqrt(pi)*e^(1/4) - 1/2*e^(1/4) + 1/2*e^(-3/4) sage: t.simplify_exp() # long time 1/2*sqrt(pi)*e^(1/4) + sage: sage.calculus.calculus.maxima('domain: complex') + complex An integral which used to return -1 before maxima 5.28. See :trac:`12842`:: diff --git a/src/sage/modular/arithgroup/arithgroup_generic.py b/src/sage/modular/arithgroup/arithgroup_generic.py index 37872965e11..e36434b004d 100644 --- a/src/sage/modular/arithgroup/arithgroup_generic.py +++ b/src/sage/modular/arithgroup/arithgroup_generic.py @@ -485,7 +485,7 @@ def nu3(self): if self.is_even(): return count else: - return count/2 + return count // 2 def __cmp__(self, other): r""" diff --git a/src/sage/modular/arithgroup/arithgroup_perm.py b/src/sage/modular/arithgroup/arithgroup_perm.py index 0bfa82305e0..caab38ac118 100644 --- a/src/sage/modular/arithgroup/arithgroup_perm.py +++ b/src/sage/modular/arithgroup/arithgroup_perm.py @@ -1417,10 +1417,10 @@ def to_even_subgroup(self,relabel=True): e.append((i,j)) # build the quotient permutations - ss2 = [None]*(N/2) - ss3 = [None]*(N/2) - ll = [None]*(N/2) - rr = [None]*(N/2) + ss2 = [None]*(N//2) + ss3 = [None]*(N//2) + ll = [None]*(N//2) + rr = [None]*(N//2) s3 = self._S3 l = self._L diff --git a/src/sage/modular/dirichlet.py b/src/sage/modular/dirichlet.py index 6914bf83bd0..93533fec8ef 100644 --- a/src/sage/modular/dirichlet.py +++ b/src/sage/modular/dirichlet.py @@ -76,6 +76,7 @@ import sage.structure.parent_gens as parent_gens from sage.rings.rational_field import is_RationalField +from sage.rings.complex_field import is_ComplexField from sage.rings.ring import is_Ring from sage.misc.cachefunc import cached_method @@ -852,8 +853,10 @@ def gauss_sum(self, a=1): """ G = self.parent() K = G.base_ring() + if is_ComplexField(K): + return self.gauss_sum_numerical() if not (number_field.is_CyclotomicField(K) or is_RationalField(K)): - raise NotImplementedError("Gauss sums only currently implemented when the base ring is a cyclotomic field or QQ.") + raise NotImplementedError("Gauss sums only currently implemented when the base ring is a cyclotomic field, QQ, or a complex field.") g = 0 m = G.modulus() L = rings.CyclotomicField(arith.lcm(m,G.zeta_order())) @@ -905,20 +908,32 @@ def gauss_sum_numerical(self, prec=53, a=1): sage: e.gauss_sum_numerical(a=2, prec=100) 4.7331654313260708324703713917e-30 - 1.7320508075688772935274463415*I sage: G = DirichletGroup(13) + sage: H = DirichletGroup(13, CC) sage: e = G.0 + sage: f = H.0 sage: e.gauss_sum_numerical() -3.07497205... + 1.8826966926...*I + sage: f.gauss_sum_numerical() + -3.07497205... + 1.8826966926...*I sage: abs(e.gauss_sum_numerical()) 3.60555127546... + sage: abs(f.gauss_sum_numerical()) + 3.60555127546... sage: sqrt(13.0) 3.60555127546399 """ G = self.parent() K = G.base_ring() - if not (number_field.is_CyclotomicField(K) or is_RationalField(K)): - raise NotImplementedError("Gauss sums only currently implemented when the base ring is a cyclotomic field or QQ.") - phi = K.complex_embedding(prec) - CC = phi.codomain() + if not (number_field.is_CyclotomicField(K) or is_RationalField(K) + or is_ComplexField(K)): + raise NotImplementedError("Gauss sums only currently implemented when the base ring is a cyclotomic field, QQ, or a complex field.") + + if is_ComplexField(K): + phi = lambda t : t + CC = K + else: + phi = K.complex_embedding(prec) + CC = phi.codomain() g = 0 m = G.modulus() @@ -1150,6 +1165,19 @@ def is_even(self): sage: [e.is_even() for e in G] [True, False, True, False, True, False, True, False, True, False, True, False] + sage: G = DirichletGroup(13, CC) + sage: e = G.0 + sage: e.is_even() + False + sage: e(-1) + -1.000000... + sage: [e.is_even() for e in G] + [True, False, True, False, True, False, True, False, True, False, True, False] + + sage: G = DirichletGroup(100000, CC) + sage: G.1.is_even() + True + Note that ``is_even`` need not be the negation of is_odd, e.g., in characteristic 2:: @@ -1159,7 +1187,11 @@ def is_even(self): sage: e.is_odd() True """ - return (self(-1) == self.base_ring()(1)) + R = self.base_ring() + # self(-1) is either +1 or -1 + if not R.is_exact(): + return abs(self(-1) - R(1)) < 0.5 + return self(-1) == R(1) @cached_method def is_odd(self): @@ -1176,6 +1208,17 @@ def is_odd(self): sage: [e.is_odd() for e in G] [False, True, False, True, False, True, False, True, False, True, False, True] + sage: G = DirichletGroup(13) + sage: e = G.0 + sage: e.is_odd() + True + sage: [e.is_odd() for e in G] + [False, True, False, True, False, True, False, True, False, True, False, True] + + sage: G = DirichletGroup(100000, CC) + sage: G.0.is_odd() + True + Note that ``is_even`` need not be the negation of is_odd, e.g., in characteristic 2:: @@ -1185,7 +1228,11 @@ def is_odd(self): sage: e.is_odd() True """ - return (self(-1) == self.base_ring()(-1)) + R = self.base_ring() + # self(-1) is either +1 or -1 + if not R.is_exact(): + return abs(self(-1) - R(-1)) < 0.5 + return self(-1) == R(-1) @cached_method def is_primitive(self): @@ -1202,6 +1249,13 @@ def is_primitive(self): False sage: (a*b).is_primitive() True + sage: G. = DirichletGroup(20, CC) + sage: a.is_primitive() + False + sage: b.is_primitive() + False + sage: (a*b).is_primitive() + True """ return (self.conductor() == self.modulus()) @@ -1571,7 +1625,7 @@ def element(self): this vector is mutable *only* because immutable vectors are implemented yet. - EXAMPLE:: + EXAMPLES:: sage: G. = DirichletGroup(20) sage: a.element() @@ -1582,10 +1636,18 @@ def element(self): try: return self.__element except AttributeError: - P = self.parent() - M = P._module - dlog = P._zeta_dlog - v = M([dlog[x] for x in self.values_on_gens()]) + P = self.parent() + M = P._module + C = P.base_ring() + if is_ComplexField(C): + R = C._real_field() + zeta = P._zeta + zeta_argument = zeta.argument() + v = M([int(round(x.argument()/zeta_argument)) + for x in self.values_on_gens()]) + else: + dlog = P._zeta_dlog + v = M([dlog[x] for x in self.values_on_gens()]) self.__element = v return v @@ -1854,10 +1916,17 @@ def __init__(self, modulus, zeta, zeta_order): a = zeta.parent()(1) v = {a:0} w = [a] - for i in range(1, self._zeta_order): - a = a * zeta - v[a] = i - w.append(a) + if is_ComplexField(zeta.parent()): + for i in range(1, self._zeta_order): + a = a * zeta + a._set_multiplicative_order(zeta_order/arith.GCD(zeta_order, i)) + v[a] = i + w.append(a) + else: + for i in range(1, self._zeta_order): + a = a * zeta + v[a] = i + w.append(a) self._zeta_powers = w # gives quickly the ith power of zeta self._zeta_dlog = v # dictionary that computes log_{zeta}(power of zeta). self._module = free_module.FreeModule(rings.IntegerModRing(zeta_order), diff --git a/src/sage/modular/etaproducts.py b/src/sage/modular/etaproducts.py index f5c51efc260..7ad8fa1f51a 100644 --- a/src/sage/modular/etaproducts.py +++ b/src/sage/modular/etaproducts.py @@ -845,8 +845,8 @@ def qexp_eta(ps_ring, prec): n = 1 while True: pm = -pm - v[n*(3*n-1)/2] = pm - v[n*(3*n+1)/2] = pm + v[n*(3*n-1)//2] = pm + v[n*(3*n+1)//2] = pm n += 1 except IndexError: pass diff --git a/src/sage/modular/modform/element.py b/src/sage/modular/modform/element.py index f7e2896a255..6aa5b53341e 100644 --- a/src/sage/modular/modform/element.py +++ b/src/sage/modular/modform/element.py @@ -584,7 +584,7 @@ def cuspform_lseries(self, prec=53, w = self.atkin_lehner_eigenvalue() if w is None: raise ValueError("Form is not an eigenform for Atkin-Lehner") - e = (-1)**(l/2)*w + e = (-1)**(l//2)*w L = Dokchitser(conductor = N, gammaV = [0,1], weight = l, diff --git a/src/sage/modules/diamond_cutting.py b/src/sage/modules/diamond_cutting.py new file mode 100644 index 00000000000..c6098fa5838 --- /dev/null +++ b/src/sage/modules/diamond_cutting.py @@ -0,0 +1,293 @@ +""" +Diamond cutting implementation + +AUTHORS: + +- Jan Poeschko (2012-07-02): initial version +""" + +#***************************************************************************** +# Copyright (C) 2012 Jan Poeschko +# +# 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.geometry.polyhedron.constructor import Polyhedron +from sage.matrix.constructor import matrix, identity_matrix +from sage.modules.free_module_element import vector +from sage.rings.rational_field import QQ + +from math import sqrt, floor, ceil + +def plane_inequality(v): + """ + Return the inequality for points on the same side as the origin + with respect to the plane through ``v`` normal to ``v``. + + EXAMPLES:: + + sage: from sage.modules.diamond_cutting import plane_inequality + sage: ieq = plane_inequality([1, -1]); ieq + [2, -1, 1] + sage: ieq[0] + vector(ieq[1:]) * vector([1, -1]) + 0 + """ + v = vector(v) + c = -v * v + if c < 0: + c, v = -c, -v + return [c] + list(v) + +def jacobi(M): + r""" + Compute the upper-triangular part of the Cholesky/Jacobi + decomposition of the symmetric matrix ``M``. + + Let `M` be a symmetric `n \times n`-matrix over a field `F`. + Let `m_{i,j}` denote the `(i,j)`-th entry of `M` for any + `1 \leq i \leq n` and `1 \leq j \leq n`. Then, the + upper-triangular part computed by this method is the + upper-triangular `n \times n`-matrix `Q` whose + `(i,j)`-th entry `q_{i,j}` satisfies + + .. MATH:: + + q_{i,j} = + \begin{cases} + \frac{1}{q_{i,i}} \left( m_{i,j} - \sum_{r j, + \end{cases} + + for all `1 \leq i \leq n` and `1 \leq j \leq n`. (These + equalities determine the entries of `Q` uniquely by + recursion.) This matrix `Q` is defined for all `M` in a + certain Zariski-dense open subset of the set of all + `n \times n`-matrices. + + EXAMPLES:: + + sage: from sage.modules.diamond_cutting import jacobi + sage: jacobi(identity_matrix(3) * 4) + [4 0 0] + [0 4 0] + [0 0 4] + + sage: def testall(M): + ....: Q = jacobi(M) + ....: for j in range(3): + ....: for i in range(j): + ....: if Q[i,j] * Q[i,i] != M[i,j] - sum(Q[r,i] * Q[r,j] * Q[r,r] for r in range(i)): + ....: return False + ....: for i in range(3): + ....: if Q[i,i] != M[i,i] - sum(Q[r,i] ** 2 * Q[r,r] for r in range(i)): + ....: return False + ....: for j in range(i): + ....: if Q[i,j] != 0: + ....: return False + ....: return True + + sage: M = Matrix(QQ, [[8,1,5], [1,6,0], [5,0,3]]) + sage: Q = jacobi(M); Q + [ 8 1/8 5/8] + [ 0 47/8 -5/47] + [ 0 0 -9/47] + sage: testall(M) + True + + sage: M = Matrix(QQ, [[3,6,-1,7],[6,9,8,5],[-1,8,2,4],[7,5,4,0]]) + sage: testall(M) + True + """ + dim = M.dimensions() + if dim[0] != dim[1]: + raise ValueError("the matrix must be square") + dim = dim[0] + q = [list(row) for row in M] + for i in range(dim - 1): + for j in range(i + 1, dim): + q[j][i] = q[i][j] + q[i][j] = q[i][j] / q[i][i] + for k in range(i + 1, dim): + for l in range(k, dim): + q[k][l] -= q[k][i] * q[i][l] + for i in range(1, dim): + for j in range(i): + q[i][j] = 0 + return matrix(q) + +def diamond_cut(V, GM, C, verbose=False): + r""" + Perform diamond cutting on polyhedron ``V`` with basis matrix ``GM`` + and radius ``C``. + + INPUT: + + - ``V`` -- polyhedron to cut from + + - ``GM`` -- half of the basis matrix of the lattice + + - ``C`` -- radius to use in cutting algorithm + + - ``verbose`` -- (default: ``False``) whether to print debug information + + OUTPUT: + + A :class:``Polyhedron`` instance. + + EXAMPLES:: + + sage: from sage.modules.diamond_cutting import diamond_cut + sage: V = Polyhedron([[0], [2]]) + sage: GM = matrix([2]) + sage: V = diamond_cut(V, GM, 4) + sage: V.vertices() + (A vertex at (2), A vertex at (0)) + """ + # coerce to floats + GM = GM.N() + C = float(C) + if verbose: + print("Cut\n{}\nwith radius {}".format(GM, C)) + + dim = GM.dimensions() + if dim[0] != dim[1]: + raise ValueError("the matrix must be square") + dim = dim[0] + T = [0] * dim + U = [0] * dim + x = [0] * dim + L = [0] * dim + + # calculate the Gram matrix + q = matrix([[sum(GM[i][k] * GM[j][k] for k in range(dim)) for j in range(dim)] for i in range(dim)]) + if verbose: + print( "q:\n{}".format(q.N()) ) + # apply Cholesky/Jacobi decomposition + q = jacobi(q) + if verbose: + print( "q:\n{}".format(q.N()) ) + + i = dim - 1 + T[i] = C + U[i] = 0 + + new_dimension = True + cut_count = 0 + inequalities = [] + while True: + if verbose: + print("Dimension: {}".format(i)) + if new_dimension: + Z = sqrt(T[i] / q[i][i]) + if verbose: + print("Z: {}".format(Z)) + L[i] = int(floor(Z - U[i])) + if verbose: + print("L: {}".format(L)) + x[i] = int(ceil(-Z - U[i]) - 1) + new_dimension = False + + x[i] += 1 + if verbose: + print("x: {}".format(x)) + if x[i] > L[i]: + i += 1 + elif i > 0: + T[i - 1] = T[i] - q[i][i] * (x[i] + U[i]) ** 2 + i -= 1 + U[i] = 0 + for j in range(i + 1, dim): + U[i] += q[i][j] * x[j] + new_dimension = True + else: + if all(elmt == 0 for elmt in x): + break + hv = [0] * dim + for k in range(dim): + for j in range(dim): + hv[k] += x[j] * GM[j][k] + hv = vector(hv) + + for hv in [hv, -hv]: + cut_count += 1 + if verbose: + print "\n%d) Cut using normal vector %s" % (cut_count, hv) + hv = [QQ(round(elmt, 6)) for elmt in hv] + inequalities.append(plane_inequality(hv)) + #cut = Polyhedron(ieqs=[plane_inequality(hv)]) + #V = V.intersection(cut) + + if verbose: + print("Final cut") + cut = Polyhedron(ieqs=inequalities) + V = V.intersection(cut) + + if verbose: + print("End") + + return V + +def calculate_voronoi_cell(basis, radius=None, verbose=False): + """ + Calculate the Voronoi cell of the lattice defined by basis + + INPUT: + + - ``basis`` -- embedded basis matrix of the lattice + + - ``radius`` -- radius of basis vectors to consider + + - ``verbose`` -- whether to print debug information + + OUTPUT: + + A :class:``Polyhedron`` instance. + + EXAMPLES:: + + sage: from sage.modules.diamond_cutting import calculate_voronoi_cell + sage: V = calculate_voronoi_cell(matrix([[1, 0], [0, 1]])) + sage: V.volume() + 1 + """ + + dim = basis.dimensions() + artificial_length = None + if dim[0] < dim[1]: + # introduce "artificial" basis points (representing infinity) + artificial_length = ceil(max(abs(v) for v in basis)) * 2 + additional_vectors = identity_matrix(dim[1]) * artificial_length + basis = basis.stack(additional_vectors) + # LLL-reduce to get quadratic matrix + basis = basis.LLL() + basis = matrix([v for v in basis if v]) + dim = basis.dimensions() + if dim[0] != dim[1]: + raise ValueError("invalid matrix") + basis = basis / 2 + + ieqs = [] + for v in basis: + ieqs.append(plane_inequality(v)) + ieqs.append(plane_inequality(-v)) + Q = Polyhedron(ieqs=ieqs) + + # twice the length of longest vertex in Q is a safe choice + if radius is None: + radius = 2 * max(abs(v) ** 2 for v in basis) + + V = diamond_cut(Q, basis, radius, verbose=verbose) + + if artificial_length is not None: + # remove inequalities introduced by artificial basis points + H = V.Hrepresentation() + H = [v for v in H if all(not V._is_zero(v.A() * w / 2 - v.b() and + not V._is_zero(v.A() * (-w) / 2 - v.b())) for w in additional_vectors)] + V = Polyhedron(ieqs=H) + + return V + diff --git a/src/sage/modules/fg_pid/fgp_module.py b/src/sage/modules/fg_pid/fgp_module.py index f3208aa85b3..03374aeee70 100644 --- a/src/sage/modules/fg_pid/fgp_module.py +++ b/src/sage/modules/fg_pid/fgp_module.py @@ -224,6 +224,7 @@ from sage.misc.cachefunc import cached_method import sage.misc.weak_dict +from functools import reduce _fgp_module = sage.misc.weak_dict.WeakValueDictionary() diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 0305916c4b6..c8bb4b88f4b 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -179,6 +179,7 @@ import sage.rings.finite_rings.integer_mod_ring import sage.rings.infinity import sage.rings.integer +from sage.rings.integer_ring import ZZ import sage.structure.parent_gens as gens from sage.categories.principal_ideal_domains import PrincipalIdealDomains from sage.categories.commutative_rings import CommutativeRings @@ -2810,6 +2811,16 @@ def span_of_basis(self, basis, base_ring=None, check=True, already_echelonized=F if is_FreeModule(basis): basis = basis.gens() if base_ring is None or base_ring == self.base_ring(): + try: + if self.is_dense(): + from free_module_integer import FreeModule_submodule_with_basis_integer + return FreeModule_submodule_with_basis_integer(self.ambient_module(), + basis=basis, check=check, + already_echelonized=already_echelonized, + lll_reduce=False) + except TypeError: + pass + return FreeModule_submodule_with_basis_pid( self.ambient_module(), basis=basis, check=check, already_echelonized=already_echelonized) @@ -5036,7 +5047,7 @@ def __call__(self, e, coerce=True, copy=True, check=True): class FreeModule_submodule_with_basis_pid(FreeModule_generic_pid): r""" - Construct a submodule of a free module over PID with a distiguished basis. + Construct a submodule of a free module over PID with a distinguished basis. INPUT: diff --git a/src/sage/modules/free_module_integer.py b/src/sage/modules/free_module_integer.py new file mode 100644 index 00000000000..1b879216590 --- /dev/null +++ b/src/sage/modules/free_module_integer.py @@ -0,0 +1,833 @@ +# -*- coding: utf-8 -*- +""" +Discrete Subgroups of `\\ZZ^n`. + +AUTHORS: + +- Martin Albrecht (2014-03): initial version + +- Jan Pöschko (2012-08): some code in this module was taken from Jan Pöschko's + 2012 GSoC project + +TESTS:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: L = IntegerLattice(random_matrix(ZZ, 10, 10)) + sage: TestSuite(L).run() + +""" + +#***************************************************************************** +# Copyright (C) 2012 Jan Poeschko +# Copyright (C) 2014 Martin Albrecht +# +# 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.libs.pari.pari_instance import pari +from sage.rings.integer_ring import ZZ +from sage.matrix.constructor import matrix +from sage.misc.cachefunc import cached_method +from sage.misc.method_decorator import MethodDecorator +from sage.modules.free_module import FreeModule_submodule_with_basis_pid, FreeModule_ambient_pid +from sage.modules.free_module_element import vector +from sage.modules.vector_integer_dense import Vector_integer_dense +from sage.structure.parent import Parent +from sage.rings.number_field.number_field_element import OrderElement_absolute + +def IntegerLattice(basis, lll_reduce=True): + r""" + Construct a new integer lattice from ``basis``. + + INPUT: + + - ``basis`` -- can be one of the following: + + - a list of vectors + + - a matrix over the integers + + - an element of an absolute order + + - ``lll_reduce`` -- (default: ``True``) run LLL reduction on the basis + on construction. + + EXAMPLES: + + We construct a lattice from a list of rows:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: IntegerLattice([[1,0,3], [0,2,1], [0,2,7]]) + Free module of degree 3 and rank 3 over Integer Ring + User basis matrix: + [-2 0 0] + [ 0 2 1] + [ 1 -2 2] + + Sage includes a generator for hard lattices from cryptography:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: A = sage.crypto.gen_lattice(type='modular', m=10, seed=42, dual=True) + sage: IntegerLattice(A) + Free module of degree 10 and rank 10 over Integer Ring + User basis matrix: + [ 0 1 2 0 1 2 -1 0 -1 -1] + [ 0 1 0 -3 0 0 0 0 3 -1] + [ 1 1 -1 0 -3 0 0 1 2 -2] + [-1 2 -1 -1 2 -2 1 -1 0 -1] + [ 1 0 -4 2 0 1 -2 -1 0 0] + [ 2 3 0 1 1 0 -2 3 0 0] + [-2 -3 -2 0 0 1 -1 1 3 -2] + [-3 0 -1 0 -2 -1 -2 1 -1 1] + [ 1 4 -1 1 2 2 1 0 3 1] + [-1 -1 0 -3 -1 2 2 3 -1 0] + + You can also construct the lattice directly:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: sage.crypto.gen_lattice(type='modular', m=10, seed=42, dual=True, lattice=True) + Free module of degree 10 and rank 10 over Integer Ring + User basis matrix: + [ 0 1 2 0 1 2 -1 0 -1 -1] + [ 0 1 0 -3 0 0 0 0 3 -1] + [ 1 1 -1 0 -3 0 0 1 2 -2] + [-1 2 -1 -1 2 -2 1 -1 0 -1] + [ 1 0 -4 2 0 1 -2 -1 0 0] + [ 2 3 0 1 1 0 -2 3 0 0] + [-2 -3 -2 0 0 1 -1 1 3 -2] + [-3 0 -1 0 -2 -1 -2 1 -1 1] + [ 1 4 -1 1 2 2 1 0 3 1] + [-1 -1 0 -3 -1 2 2 3 -1 0] + + We construct an ideal lattice from an element of an absolute order:: + + sage: K. = CyclotomicField(17) + sage: O = K.ring_of_integers() + sage: f = O.random_element(); f + -a^15 - a^12 - a^10 - 8*a^9 - a^8 - 4*a^7 + 3*a^6 + a^5 + 2*a^4 + 8*a^3 - a^2 + a + 1 + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: IntegerLattice(f) + Free module of degree 16 and rank 16 over Integer Ring + User basis matrix: + [ 1 1 -1 8 2 1 3 -4 -1 -8 -1 0 -1 0 0 -1] + [-1 0 1 1 -1 8 2 1 3 -4 -1 -8 -1 0 -1 0] + [ 1 1 0 1 2 2 0 9 3 2 4 -3 0 -7 0 1] + [ 1 0 1 1 0 1 2 2 0 9 3 2 4 -3 0 -7] + [ 2 -5 -2 -9 -2 -1 -2 -1 -1 -2 -1 0 0 -2 7 1] + [ 1 4 0 -5 -3 0 3 5 -2 2 0 -7 4 0 -6 -2] + [-7 4 0 -6 -2 0 1 4 0 -5 -3 0 3 5 -2 2] + [-1 0 0 -1 0 1 1 -1 8 2 1 3 -4 -1 -8 -1] + [-1 -1 -2 4 1 9 1 -1 0 1 -8 -1 -1 -4 3 2] + [-1 -2 6 -6 8 1 -3 5 3 1 1 0 -2 4 3 2] + [ 4 8 2 7 -3 2 -1 2 0 -4 0 3 6 3 0 -4] + [ 0 -1 0 1 1 -1 8 2 1 3 -4 -1 -8 -1 0 -1] + [ 2 -7 -1 0 -2 5 2 9 2 1 2 1 1 2 1 0] + [-1 7 -5 9 2 -2 6 4 2 2 1 -1 5 4 3 1] + [ 3 1 -6 0 3 1 -5 2 2 8 -4 4 -3 2 -6 -7] + [ 2 6 -4 4 0 -1 7 0 -6 3 9 1 -3 -1 4 3] + + We construct `\ZZ^n`:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: IntegerLattice(ZZ^10) + Free module of degree 10 and rank 10 over Integer Ring + User basis matrix: + [1 0 0 0 0 0 0 0 0 0] + [0 1 0 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0 0] + [0 0 0 1 0 0 0 0 0 0] + [0 0 0 0 1 0 0 0 0 0] + [0 0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 0 1 0 0 0] + [0 0 0 0 0 0 0 1 0 0] + [0 0 0 0 0 0 0 0 1 0] + [0 0 0 0 0 0 0 0 0 1] + + + Sage also interfaces with fpLLL's lattice generator:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: from sage.libs.fplll.fplll import gen_simdioph + sage: IntegerLattice(gen_simdioph(8, 20, 10), lll_reduce=False) + Free module of degree 8 and rank 8 over Integer Ring + User basis matrix: + [ 1024 829556 161099 11567 521155 769480 639201 689979] + [ 0 1048576 0 0 0 0 0 0] + [ 0 0 1048576 0 0 0 0 0] + [ 0 0 0 1048576 0 0 0 0] + [ 0 0 0 0 1048576 0 0 0] + [ 0 0 0 0 0 1048576 0 0] + [ 0 0 0 0 0 0 1048576 0] + [ 0 0 0 0 0 0 0 1048576] + + """ + + if isinstance(basis, OrderElement_absolute): + basis = basis.matrix() + elif isinstance(basis, FreeModule_ambient_pid): + basis = basis.basis_matrix() + + try: + basis = matrix(ZZ, basis) + except TypeError: + raise NotImplementedError("only integer lattices supported") + + return FreeModule_submodule_with_basis_integer(ZZ**basis.ncols(), + basis=basis, + lll_reduce=lll_reduce) + +class FreeModule_submodule_with_basis_integer(FreeModule_submodule_with_basis_pid): + r""" + This class represents submodules of `\ZZ^n` with a distinguished basis. + + However, most functionality in excess of standard submodules over PID + is for these submodules considered as discrete subgroups of `\ZZ^n`, i.e. + as lattices. That is, this class provides functions for computing LLL + and BKZ reduced bases for this free module with respect to the standard + Euclidean norm. + + EXAMPLE:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: L = IntegerLattice(sage.crypto.gen_lattice(type='modular', m=10, seed=42, dual=True)); L + Free module of degree 10 and rank 10 over Integer Ring + User basis matrix: + [ 0 1 2 0 1 2 -1 0 -1 -1] + [ 0 1 0 -3 0 0 0 0 3 -1] + [ 1 1 -1 0 -3 0 0 1 2 -2] + [-1 2 -1 -1 2 -2 1 -1 0 -1] + [ 1 0 -4 2 0 1 -2 -1 0 0] + [ 2 3 0 1 1 0 -2 3 0 0] + [-2 -3 -2 0 0 1 -1 1 3 -2] + [-3 0 -1 0 -2 -1 -2 1 -1 1] + [ 1 4 -1 1 2 2 1 0 3 1] + [-1 -1 0 -3 -1 2 2 3 -1 0] + sage: L.shortest_vector() + (0, 1, 2, 0, 1, 2, -1, 0, -1, -1) + + """ + def __init__(self, ambient, basis, check=True, echelonize=False, + echelonized_basis=None, already_echelonized=False, + lll_reduce=True): + r""" + Construct a new submodule of `\ZZ^n` with a distinguished basis. + + INPUT: + + - ``ambient`` -- ambient free module over a principal ideal domain + `\ZZ`, i.e. `\ZZ^n` + + - ``basis`` -- either a list of vectors or a matrix over the integers + + - ``check`` -- (default: ``True``) if ``False``, correctness of + the input will not be checked and type conversion may be omitted, + use with care + + - ``echelonize`` -- (default:``False``) if ``True``, ``basis`` will be + echelonized and the result will be used as the default basis of the + constructed submodule + + - `` echelonized_basis`` -- (default: ``None``) if not ``None``, must + be the echelonized basis spanning the same submodule as ``basis`` + + - ``already_echelonized`` -- (default: ``False``) if ``True``, + ``basis`` must be already given in the echelonized form + + - ``lll_reduce`` -- (default: ``True``) run LLL reduction on the basis + on construction + + EXAMPLES:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: IntegerLattice([[1,0,-2], [0,2,5], [0,0,7]]) + Free module of degree 3 and rank 3 over Integer Ring + User basis matrix: + [ 1 0 -2] + [ 1 -2 0] + [ 2 2 1] + + sage: IntegerLattice(random_matrix(ZZ, 5, 5, x=-2^20, y=2^20)) + Free module of degree 5 and rank 5 over Integer Ring + User basis matrix: + [ -7945 -381123 85872 -225065 12924] + [-158254 120252 189195 -262144 -345323] + [ 232388 -49556 306585 -31340 401528] + [-353460 213748 310673 158140 172810] + [-287787 333937 -145713 -482137 186529] + + sage: K. = NumberField(x^8+1) + sage: O = K.ring_of_integers() + sage: f = O.random_element(); f + a^7 - a^6 + 4*a^5 - a^4 + a^3 + 1 + + sage: IntegerLattice(f) + Free module of degree 8 and rank 8 over Integer Ring + User basis matrix: + [ 0 1 0 1 0 3 3 0] + [ 1 0 0 1 -1 4 -1 1] + [ 0 0 1 0 1 0 3 3] + [-4 1 -1 1 0 0 1 -1] + [ 1 -3 0 0 0 3 0 -2] + [ 0 -1 1 -4 1 -1 1 0] + [ 2 0 -3 -1 0 -3 0 0] + [-1 0 -1 0 -3 -3 0 0] + + """ + basis = matrix(ZZ, basis) + self._basis_is_LLL_reduced = False + + if lll_reduce: + basis = matrix([v for v in basis.LLL() if v]) + self._basis_is_LLL_reduced = True + + basis.set_immutable() + FreeModule_submodule_with_basis_pid.__init__(self, + ambient=ambient, + basis=basis, + check=check, + echelonize=echelonize, + echelonized_basis=echelonized_basis, + already_echelonized=already_echelonized) + + self._reduced_basis = basis.change_ring(ZZ) + + @property + def reduced_basis(self): + """ + This attribute caches the currently best known reduced basis for + ``self``, where "best" is defined by the Euclidean norm of the + first row vector. + + EXAMPLE:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: L = IntegerLattice(random_matrix(ZZ, 10, 10), lll_reduce=False) + sage: L.reduced_basis + [ -8 2 0 0 1 -1 2 1 -95 -1] + [ -2 -12 0 0 1 -1 1 -1 -2 -1] + [ 4 -4 -6 5 0 0 -2 0 1 -4] + [ -6 1 -1 1 1 -1 1 -1 -3 1] + [ 1 0 0 -3 2 -2 0 -2 1 0] + [ -1 1 0 0 1 -1 4 -1 1 -1] + [ 14 1 -5 4 -1 0 2 4 1 1] + [ -2 -1 0 4 -3 1 -5 0 -2 -1] + [ -9 -1 -1 3 2 1 -1 1 -2 1] + [ -1 2 -7 1 0 2 3 -1955 -22 -1] + + sage: _ = L.LLL() + sage: L.reduced_basis + [ 1 0 0 -3 2 -2 0 -2 1 0] + [ -1 1 0 0 1 -1 4 -1 1 -1] + [ -2 0 0 1 0 -2 -1 -3 0 -2] + [ -2 -2 0 -1 3 0 -2 0 2 0] + [ 1 1 1 2 3 -2 -2 0 3 1] + [ -4 1 -1 0 1 1 2 2 -3 3] + [ 1 -3 -7 2 3 -1 0 0 -1 -1] + [ 1 -9 1 3 1 -3 1 -1 -1 0] + [ 8 5 19 3 27 6 -3 8 -25 -22] + [ 172 -25 57 248 261 793 76 -839 -41 376] + + """ + return self._reduced_basis + + def LLL(self, *args, **kwds): + r""" + Return an LLL reduced basis for ``self``. + + A lattice basis `(b_1, b_2, ..., b_d)` is `(\delta, \eta)`-LLL-reduced + if the two following conditions hold: + + - For any `i > j`, we have `\lvert \mu_{i, j} \rvert \leq η`. + + - For any `i < d`, we have + `\delta \lvert b_i^* \rvert^2 \leq \lvert b_{i+1}^* + + \mu_{i+1, i} b_i^* \rvert^2`, + + where `\mu_{i,j} = \langle b_i, b_j^* \rangle / \langle b_j^*,b_j^* + \rangle` and `b_i^*` is the `i`-th vector of the Gram-Schmidt + orthogonalisation of `(b_1, b_2, \ldots, b_d)`. + + The default reduction parameters are `\delta = 3/4` and + `\eta = 0.501`. + + The parameters `\delta` and `\eta` must satisfy: + `0.25 < \delta \leq 1.0` and `0.5 \leq \eta < \sqrt{\delta}`. + Polynomial time complexity is only guaranteed for `\delta < 1`. + + INPUT: + + - ``*args`` -- passed through to + :meth:`sage.matrix.matrix_integer_dense.Matrix_integer_dense.LLL` + + - ``**kwds`` -- passed through to + :meth:`sage.matrix.matrix_integer_dense.Matrix_integer_dense.LLL` + + OUTPUT: + + An integer matrix which is an LLL-reduced basis for this lattice. + + EXAMPLES:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: A = random_matrix(ZZ, 10, 10, x=-2000, y=2000) + sage: L = IntegerLattice(A, lll_reduce=False); L + Free module of degree 10 and rank 10 over Integer Ring + User basis matrix: + [ -645 -1037 -1775 -1619 1721 -1434 1766 1701 1669 1534] + [ 1303 960 1998 -1838 1683 -1332 149 327 -849 -1562] + [-1113 -1366 1379 669 54 1214 -1750 -605 -1566 1626] + [-1367 1651 926 1731 -913 627 669 -1437 -132 1712] + [ -549 1327 -1353 68 1479 -1803 -456 1090 -606 -317] + [ -221 -1920 -1361 1695 1139 111 -1792 1925 -656 1992] + [-1934 -29 88 890 1859 1820 -1912 -1614 -1724 1606] + [ -590 -1380 1768 774 656 760 -746 -849 1977 -1576] + [ 312 -242 -1732 1594 -439 -1069 458 -1195 1715 35] + [ 391 1229 -1815 607 -413 -860 1408 1656 1651 -628] + sage: min(v.norm().n() for v in L.reduced_basis) + 3346.57... + + sage: L.LLL() + [ -888 53 -274 243 -19 431 710 -83 928 347] + [ 448 -330 370 -511 242 -584 -8 1220 502 183] + [ -524 -460 402 1338 -247 -279 -1038 -28 -159 -794] + [ 166 -190 -162 1033 -340 -77 -1052 1134 -843 651] + [ -47 -1394 1076 -132 854 -151 297 -396 -580 -220] + [-1064 373 -706 601 -587 -1394 424 796 -22 -133] + [-1126 398 565 -1418 -446 -890 -237 -378 252 247] + [ -339 799 295 800 425 -605 -730 -1160 808 666] + [ 755 -1206 -918 -192 -1063 -37 -525 -75 338 400] + [ 382 -199 -1839 -482 984 -15 -695 136 682 563] + sage: L.reduced_basis[0].norm().n() + 1613.74... + + """ + basis = self.reduced_basis + basis = [v for v in basis.LLL(*args, **kwds) if v] + basis = matrix(ZZ, len(basis), len(basis[0]), basis) + basis.set_immutable() + + if self.reduced_basis[0].norm() > basis[0].norm(): + self._reduced_basis = basis + return basis + + def BKZ(self, *args, **kwds): + """ + Return a Block Korkine-Zolotareff reduced basis for ``self``. + + INPUT: + + - ``*args`` -- passed through to + :meth:`sage.matrix.matrix_integer_dense.Matrix_integer_dense.BKZ` + + - ``*kwds`` -- passed through to + :meth:`sage.matrix.matrix_integer_dense.Matrix_integer_dense.BKZ` + + OUTPUT: + + An integer matrix which is a BKZ-reduced basis for this lattice. + + EXAMPLES:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: A = sage.crypto.gen_lattice(type='random', n=1, m=60, q=2^60, seed=42) + sage: L = IntegerLattice(A, lll_reduce=False) + sage: min(v.norm().n() for v in L.reduced_basis) + 4.17330740711759e15 + + sage: L.LLL() + 60 x 60 dense matrix over Integer Ring (use the '.str()' method to see the entries) + + sage: min(v.norm().n() for v in L.reduced_basis) + 5.19615242270663 + + sage: L.BKZ(block_size=10) + 60 x 60 dense matrix over Integer Ring (use the '.str()' method to see the entries) + + sage: min(v.norm().n() for v in L.reduced_basis) + 4.12310562561766 + + .. NOTE:: + + If ``block_size == L.rank()`` where ``L`` is this latice, then + this function performs Hermite-Korkine-Zolotareff (HKZ) reduction. + """ + basis = self.reduced_basis + basis = [v for v in basis.BKZ(*args, **kwds) if v] + basis = matrix(ZZ, len(basis), len(basis[0]), basis) + basis.set_immutable() + + if self.reduced_basis[0].norm() > basis[0].norm(): + self._reduced_basis = basis + return basis + + def HKZ(self, *args, **kwds): + r""" + Hermite-Korkine-Zolotarev (HKZ) reduce the basis. + + A basis `B` of a lattice `L`, with orthogonalized basis `B^*` such + that `B = M \cdot B^*` is HKZ reduced, if and only if, the following + properties are satisfied: + + #. The basis `B` is size-reduced, i.e., all off-diagonal + coefficients of `M` satisfy `|\mu_{i,j}| \leq 1/2` + + #. The vector `b_1` realizes the first minimum `\lambda_1(L)`. + + #. The projection of the vectors `b_2, \ldots,b_r` orthogonally to + `b_1` form an HKZ reduced basis. + + .. NOTE:: + + This is realised by calling + :func:`sage.modules.free_module_integer.FreeModule_submodule_with_basis_integer.BKZ` with + ``block_size == self.rank()``. + + INPUT: + + - ``*args`` -- passed through to :meth:`BKZ` + + - ``*kwds`` -- passed through to :meth:`BKZ` + + OUTPUT: + + An integer matrix which is a HKZ-reduced basis for this lattice. + + EXAMPLE:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: L = sage.crypto.gen_lattice(type='random', n=1, m=40, q=2^60, seed=42, lattice=True) + sage: L.HKZ() + 40 x 40 dense matrix over Integer Ring (use the '.str()' method to see the entries) + + sage: L.reduced_basis[0] + (-1, 0, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, + -1, 1, 0, 1, 1, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0) + """ + return self.BKZ(block_size=self.rank()) + + + @cached_method + def volume(self): + r""" + Return `vol(L)` which is `\sqrt{\det(B \cdot B^T)}` for any basis `B`. + + OUTPUT: + + An integer. + + EXAMPLE:: + + sage: L = sage.crypto.gen_lattice(m=10, seed=42, lattice=True) + sage: L.volume() + 14641 + """ + if self.rank() == self.degree(): + return abs(self.reduced_basis.determinant()) + else: + return self.gram_matrix().determinant().sqrt() + + @cached_method + def discriminant(self): + r""" + Return `|\det(G)|`, i.e. the absolute value of the determinant of the + Gram matrix `B \cdot B^T` for any basis `B`. + + OUTPUT: + + An integer. + + EXAMPLE:: + + sage: L = sage.crypto.gen_lattice(m=10, seed=42, lattice=True) + sage: L.discriminant() + 214358881 + """ + return abs(self.gram_matrix().determinant()) + + @cached_method + def is_unimodular(self): + """ + Return ``True`` if this lattice is unimodular. + + OUTPUT: + + A boolean. + + EXAMPLES:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: L = IntegerLattice([[1, 0], [0, 1]]) + sage: L.is_unimodular() + True + sage: IntegerLattice([[2, 0], [0, 3]]).is_unimodular() + False + """ + return self.volume() == 1 + + @cached_method + def shortest_vector(self, update_reduced_basis=True, algorithm="fplll", *args, **kwds): + r""" + Return a shortest vector. + + INPUT: + + - ``update_reduced_basis`` -- (default: ``True``) set this flag if + the found vector should be used to improve the basis + + - ``algorithm`` -- (default: ``"fplll"``) either ``"fplll"`` or + ``"pari"`` + + - ``*args`` -- passed through to underlying implementation + + - ``**kwds`` -- passed through to underlying implementation + + OUTPUT: + + A shortest non-zero vector for this lattice. + + EXAMPLES:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: A = sage.crypto.gen_lattice(type='random', n=1, m=30, q=2^40, seed=42) + sage: L = IntegerLattice(A, lll_reduce=False) + sage: min(v.norm().n() for v in L.reduced_basis) + 6.03890756700000e10 + + sage: L.shortest_vector().norm().n() + 3.74165738677394 + + sage: L = IntegerLattice(A, lll_reduce=False) + sage: min(v.norm().n() for v in L.reduced_basis) + 6.03890756700000e10 + + sage: L.shortest_vector(algorithm="pari").norm().n() + 3.74165738677394 + + sage: L = IntegerLattice(A, lll_reduce=True) + sage: L.shortest_vector(algorithm="pari").norm().n() + 3.74165738677394 + """ + if algorithm == "pari": + if self._basis_is_LLL_reduced: + B = self.basis_matrix().change_ring(ZZ) + qf = self.gram_matrix() + else: + B = self.reduced_basis.LLL() + qf = B*B.transpose() + + count, length, vectors = pari(qf).qfminim(0, None) + v = vectors.python().columns()[0] + w = v*B + elif algorithm == "fplll": + from sage.libs.fplll.fplll import FP_LLL + L = FP_LLL(self.reduced_basis) + w = L.shortest_vector(*args, **kwds) + else: + raise ValueError("algorithm '{}' unknown".format(algorithm)) + + if update_reduced_basis: + self.update_reduced_basis(w) + return w + + def update_reduced_basis(self, w): + """ + Inject the vector ``w`` and run LLL to update the basis. + + INPUT: + + - ``w`` -- a vector + + OUTPUT: + + Nothing is returned but the internal state is modified. + + EXAMPLE:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: A = sage.crypto.gen_lattice(type='random', n=1, m=30, q=2^40, seed=42) + sage: L = IntegerLattice(A) + sage: B = L.reduced_basis + sage: v = L.shortest_vector(update_reduced_basis=False) + sage: L.update_reduced_basis(v) + sage: bool(L.reduced_basis[0].norm() < B[0].norm()) + True + """ + w = matrix(ZZ, w) + L = w.stack(self.reduced_basis).LLL() + assert(L[0] == 0) + self._reduced_basis = L.matrix_from_rows(range(1,L.nrows())) + + + @cached_method + def voronoi_cell(self, radius=None): + """ + Compute the Voronoi cell of a lattice, returning a Polyhedron. + + INPUT: + + - ``radius`` -- (default: automatic determination) radius of ball + containing considered vertices + + OUTPUT: + + The Voronoi cell as a Polyhedron instance. + + The result is cached so that subsequent calls to this function + return instantly. + + EXAMPLES:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: L = IntegerLattice([[1, 0], [0, 1]]) + sage: V = L.voronoi_cell() + sage: V.Vrepresentation() + (A vertex at (1/2, -1/2), A vertex at (1/2, 1/2), A vertex at (-1/2, 1/2), A vertex at (-1/2, -1/2)) + + The volume of the Voronoi cell is the square root of the + discriminant of the lattice:: + + sage: L = IntegerLattice(Matrix(ZZ, 4, 4, [[0,0,1,-1],[1,-1,2,1],[-6,0,3,3,],[-6,-24,-6,-5]])); L + Free module of degree 4 and rank 4 over Integer Ring + User basis matrix: + [ 0 0 1 -1] + [ 1 -1 2 1] + [ -6 0 3 3] + [ -6 -24 -6 -5] + sage: V = L.voronoi_cell() # long time + sage: V.volume() # long time + 678 + sage: sqrt(L.discriminant()) + 678 + + Lattices not having full dimension are handled as well:: + + sage: L = IntegerLattice([[2, 0, 0], [0, 2, 0]]) + sage: V = L.voronoi_cell() + sage: V.Hrepresentation() + (An inequality (-1, 0, 0) x + 1 >= 0, An inequality (0, -1, 0) x + 1 >= 0, An inequality (1, 0, 0) x + 1 >= 0, An inequality (0, 1, 0) x + 1 >= 0) + + ALGORITHM: + + Uses parts of the algorithm from [Vit1996]_. + + REFERENCES: + + .. [Vit1996] E. Viterbo, E. Biglieri. *Computing the Voronoi Cell + of a Lattice: The Diamond-Cutting Algorithm*. + IEEE Transactions on Information Theory, 1996. + """ + if not self._basis_is_LLL_reduced: + self.LLL() + + B = self.reduced_basis + + from diamond_cutting import calculate_voronoi_cell + return calculate_voronoi_cell(B, radius=radius) + + def voronoi_relevant_vectors(self): + """ + Compute the embedded vectors inducing the Voronoi cell. + + OUTPUT: + + The list of Voronoi relevant vectors. + + EXAMPLES:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: L = IntegerLattice([[3, 0], [4, 0]]) + sage: L.voronoi_relevant_vectors() + [(-1, 0), (1, 0)] + """ + V = self.voronoi_cell() + + def defining_point(ieq): + """ + Compute the point defining an inequality. + + INPUT: + + - ``ieq`` -- an inequality in the form [c, a1, a2, ...] + meaning a1 * x1 + a2 * x2 + ... ≦ c + + OUTPUT: + + The point orthogonal to the hyperplane defined by ``ieq`` + in twice the distance from the origin. + """ + c = ieq[0] + a = ieq[1:] + n = sum(y ** 2 for y in a) + return vector([2 * y * c / n for y in a]) + + return [defining_point(ieq) for ieq in V.inequality_generator()] + + def closest_vector(self, t): + """ + Compute the closest vector in the embedded lattice to a given vector. + + INPUT: + + - ``t`` -- the target vector to compute the closest vector to + + OUTPUT: + + The vector in the lattice closest to ``t``. + + EXAMPLES:: + + sage: from sage.modules.free_module_integer import IntegerLattice + sage: L = IntegerLattice([[1, 0], [0, 1]]) + sage: L.closest_vector((-6, 5/3)) + (-6, 2) + + ALGORITHM: + + Uses the algorithm from [Mic2010]_. + + REFERENCES: + + .. [Mic2010] D. Micciancio, P. Voulgaris. *A Deterministic Single + Exponential Time Algorithm for Most Lattice Problems based on + Voronoi Cell Computations*. + Proceedings of the 42nd ACM Symposium Theory of Computation, 2010. + """ + voronoi_cell = self.voronoi_cell() + + def projection(M, v): + Mt = M.transpose() + P = Mt * (M * Mt) ** (-1) * M + return P * v + + t = projection(matrix(self.reduced_basis), vector(t)) + + def CVPP_2V(t, V, voronoi_cell): + t_new = t + while not voronoi_cell.contains(t_new.list()): + v = max(V, key=lambda v: t_new * v / v.norm() ** 2) + t_new = t_new - v + return t - t_new + + V = self.voronoi_relevant_vectors() + t = vector(t) + p = 0 + while not (ZZ(2 ** p) * voronoi_cell).contains(t): + p += 1 + t_new = t + i = p + while i >= 1: + V_scaled = [v * (2 ** (i - 1)) for v in V] + t_new = t_new - CVPP_2V(t_new, V_scaled, ZZ(2 ** (i - 1)) * voronoi_cell) + i -= 1 + return t - t_new + diff --git a/src/sage/plot/graphics.py b/src/sage/plot/graphics.py index 79ea4a41e3e..a96dabb2bea 100644 --- a/src/sage/plot/graphics.py +++ b/src/sage/plot/graphics.py @@ -1557,15 +1557,6 @@ def show(self, **kwds): sage: G = plot_vector_field((2^x,y^2),(x,1,10),(y,1,100)) sage: G.show(scale='semilogx',base=2) - But be sure to only plot things that will have a wide enough range - for the logarithmic scale to be interpretable:: - - sage: G = arc((2,3), 2, 1, angle=pi/2, sector=(0,pi/2)) - sage: G.show(scale=('loglog', 2)) - Traceback (most recent call last): - ... - ValueError: Either expand the range of the dependent variable to allow two different integer powers of your `base`, or change your `base` to a smaller number. - Add grid lines at the major ticks of the axes. :: @@ -1765,14 +1756,26 @@ def show(self, **kwds): When using logarithmic scale along the axis, make sure to have enough room for two ticks so that the user can tell what the scale is. This can be effected by increasing the range of the independent - variable, or by changing the ``base``.:: + variable, or by changing the ``base``, or by providing enough tick + locations by using the ``ticks`` parameter. - sage: p = list_plot(range(1, 10), plotjoined=True) - sage: p.show(scale='loglog') - Traceback (most recent call last): - ... - ValueError: Either expand the range of the dependent variable to allow two different integer powers of your `base`, or change your `base` to a smaller number. - sage: p.show(scale='loglog', base=8) # this works. + By default, sage will expand the variable range so that at least two + ticks are included along the logarithmic axis. However, if you + specify ``ticks`` manually, this safety measure can be defeated:: + + sage: list_plot_loglog([(1,2),(2,3)], plotjoined=True, ticks=[[1],[1]]) + doctest:...: UserWarning: The x-axis contains fewer than 2 ticks; the logarithmic scale of the plot may not be apparent to the reader. + doctest:...: UserWarning: The y-axis contains fewer than 2 ticks; the logarithmic scale of the plot may not be apparent to the reader. + + This one works, since the horizontal axis is automatically expanded + to contain two ticks and the vertical axis is provided with two ticks:: + + sage: list_plot_loglog([(1,2),(2,3)], plotjoined=True, ticks=[None,[1,10]]) + + Another example in the log scale where both the axes are automatically + expanded to show two major ticks:: + + sage: list_plot_loglog([(2,0.5), (3, 4)], plotjoined=True) When using ``title_pos``, it must be ensured that a list or a tuple of length two is used. Otherwise, an error is raised.:: @@ -2056,27 +2059,113 @@ def _matplotlib_tick_formatter(self, subplot, base=(10, 10), subplot.xaxis.set_major_formatter(x_formatter) subplot.yaxis.set_major_formatter(y_formatter) - # Check for whether there will be too few ticks in the log scale case - # If part of the data is nonpositive, we assume there are enough ticks - if scale[0] == 'log' and xmin > 0: - import math - base0 = base[0] - if (math.floor(math.log(xmax)/math.log(base0)) - - math.ceil(math.log(xmin)/math.log(base0)) < 1): - raise ValueError('Either expand the range of the independent ' - 'variable to allow two different integer powers of your `base`, ' - 'or change your `base` to a smaller number.') - if scale[1] == 'log' and ymin > 0: - import math - base1 = base[1] - if (math.floor(math.log(ymax)/math.log(base1)) - - math.ceil(math.log(ymin)/math.log(base1)) < 1): - raise ValueError('Either expand the range of the dependent ' - 'variable to allow two different integer powers of your `base`, ' - 'or change your `base` to a smaller number.') + # Check for whether there will be too few ticks in the log scale case. + # If there are not enough ticks (2 or more) to determine that the scale + # is non-linear, we throw a warning. + from warnings import warn + tickwarnmsg = 'The %s-axis contains fewer than 2 ticks; ' + tickwarnmsg += 'the logarithmic scale of the plot may not be apparent ' + tickwarnmsg += 'to the reader.' + + if (scale[0] == 'log' and not isinstance(x_locator, NullLocator) + and len(subplot.xaxis.get_ticklocs()) < 2): + warn(tickwarnmsg % 'x') + + if (scale[1] == 'log' and not isinstance(y_locator, NullLocator) + and len(subplot.yaxis.get_ticklocs()) < 2): + warn(tickwarnmsg % 'y') return (subplot, x_locator, y_locator, x_formatter, y_formatter) + + def _get_vmin_vmax(self, vmin, vmax, basev): + """ + Determine the min/max value for a variable plotted on a logarithmic + scale. The motivation is that we desire at least two ticks for a log + plot; otherwise the reader may assume that the scale is linear. + + We check if this case occurs (for e.g. assuming xmin < xmax): + + floor(logxmin) ceil(logxmax) + ----|---------+----------+----------|----------------------|-- + logxmin logxmax + + Or if this case occurs (assuming xmin < xmax): + + floor(logxmin) floor(logxmax) ceil(logxmax) + ----|---------+---------------------|-----+----------------|-- + logxmin logxmax + + + INPUT: + + - ``vmin`` - the current min for this variable (e.g. xmin or ymin) + + - ``vmax`` - the current max for this variable (e.g. xmax or ymax) + + - ``basev`` - the base of the logarithmic scale for this variable + + OUTPUT: + + A new (min,max) pair for this variable, suitable for its logarithmic + scale. + + EXAMPLES: + + On a base-10 logarithmic scale, we should have ``vmin``/``vmax`` + at least 10 units apart:: + + sage: p = Graphics() + sage: p._get_vmin_vmax(1,2,10) + (1, 10.0) + sage: p._get_vmin_vmax(1,5,10) + (1, 10.0) + sage: p._get_vmin_vmax(1,10,10) + (1, 10) + sage: p._get_vmin_vmax(1,11,10) + (1, 11) + sage: p._get_vmin_vmax(1,50,10) + (1, 50) + + Nonpositive values of ``vmin`` are not accepted due to the domain + of the logarithm function:: + + sage: p = Graphics() + sage: p._get_vmin_vmax(-1,2,10) + Traceback (most recent call last): + ... + ValueError: vmin must be positive + + And ``vmax`` must be greater than ``vmin``:: + + sage: p._get_vmin_vmax(1,-2,10) + Traceback (most recent call last): + ... + ValueError: vmin must be less than vmax + + """ + if vmin <= 0: + raise ValueError('vmin must be positive') + + if vmin >= vmax: + raise ValueError('vmin must be less than vmax') + + import math + logvmin = math.log(vmin)/math.log(basev) + logvmax = math.log(vmax)/math.log(basev) + + if math.floor(logvmax) - math.ceil(logvmin) < 0: + vmax = basev**math.ceil(logvmax) + vmin = basev**math.floor(logvmin) + elif math.floor(logvmax) - math.ceil(logvmin) < 1: + if logvmax-math.floor(logvmax) > math.ceil(logvmin)-logvmin: + vmax = basev**math.ceil(logvmax) + else: + vmin = basev**math.floor(logvmin) + + return vmin,vmax + + def matplotlib(self, filename=None, xmin=None, xmax=None, ymin=None, ymax=None, figsize=None, figure=None, sub=None, @@ -2236,6 +2325,20 @@ def matplotlib(self, filename=None, xscale, yscale, basex, basey = self._set_scale(figure, scale=scale, base=base) + # If any of the x-data are negative, we leave the min/max alone. + if xscale == 'log' and min(xmin, xmax) > 0: + if xmin < xmax: + xmin, xmax = self._get_vmin_vmax(xmin, xmax, basex) + else: + xmax, xmin = self._get_vmin_vmax(xmax, xmin, basex) + + # Likewise for the y-data. + if yscale == 'log' and min(ymin, ymax) > 0: + if ymin < ymax: + ymin, ymax = self._get_vmin_vmax(ymin, ymax, basey) + else: + ymax, ymin = self._get_vmin_vmax(ymax, ymin, basey) + #-------------------------- Set the legend -----------------------# if show_legend is None: show_legend = self._show_legend diff --git a/src/sage/plot/plot.py b/src/sage/plot/plot.py index 041d15fe635..cdbbc80750a 100644 --- a/src/sage/plot/plot.py +++ b/src/sage/plot/plot.py @@ -349,6 +349,7 @@ import os +from functools import reduce ## IMPORTANT: Do *not* import matplotlib at module scope. It takes a ## surprisingly long time to initialize itself. It's better if it is diff --git a/src/sage/plot/plot3d/base.pyx b/src/sage/plot/plot3d/base.pyx index 07ea20b0a75..1ecbc574672 100644 --- a/src/sage/plot/plot3d/base.pyx +++ b/src/sage/plot/plot3d/base.pyx @@ -31,6 +31,7 @@ TODO: - finish integrating tachyon - good default lights, camera from cpython.list cimport * import os +from functools import reduce from math import atan2 from random import randint import zipfile diff --git a/src/sage/quadratic_forms/quadratic_form.py b/src/sage/quadratic_forms/quadratic_form.py index 44c9956b4ed..4e3b1545556 100644 --- a/src/sage/quadratic_forms/quadratic_form.py +++ b/src/sage/quadratic_forms/quadratic_form.py @@ -434,7 +434,7 @@ def __init__(self, R, n=None, entries=None, unsafe_initialization=False, number_ ## TODO: Verify that R is a ring... ## Store the relevant variables - N = int(n*(n+1))/2 + N = n*(n+1)//2 self.__n = int(n) self.__base_ring = R self.__coeffs = [self.__base_ring(0) for i in range(N)] @@ -602,7 +602,7 @@ def __getitem__(self, ij): i = j j = tmp - return self.__coeffs[i*self.__n - i*(i-1)/2 + j - i] + return self.__coeffs[i*self.__n - i*(i-1)//2 + j - i] def __setitem__(self, ij, coeff): @@ -640,7 +640,7 @@ def __setitem__(self, ij, coeff): ## Set the entry try: - self.__coeffs[i*self.__n - i*(i-1)/2 + j -i] = self.__base_ring(coeff) + self.__coeffs[i*self.__n - i*(i-1)//2 + j -i] = self.__base_ring(coeff) except Exception: raise RuntimeError("Oops! This coefficient can't be coerced to an element of the base ring for the quadratic form.") diff --git a/src/sage/quadratic_forms/quadratic_form__automorphisms.py b/src/sage/quadratic_forms/quadratic_form__automorphisms.py index 680df6ef9e2..c2333189f73 100644 --- a/src/sage/quadratic_forms/quadratic_form__automorphisms.py +++ b/src/sage/quadratic_forms/quadratic_form__automorphisms.py @@ -107,7 +107,7 @@ def basis_of_short_vectors(self, show_lengths=False, safe_flag=True): ## Organize these vectors by length (and also introduce their negatives) - max_len = vec_len/2 + max_len = vec_len // 2 vector_list_by_length = [[] for _ in range(max_len + 1)] for v in vector_list: l = self(v) diff --git a/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py b/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py index a2c799d2e85..0af73e6ad7b 100644 --- a/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py +++ b/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py @@ -472,7 +472,7 @@ def is_hyperbolic(self, p): ## Compare local invariants ## (Note: since the dimension is even, the extra powers of 2 in ## self.det() := Det(2*Q) don't affect the answer!) - m = ZZ(self.dim() / 2) + m = ZZ(self.dim() // 2) if p == "infinity": return (self.signature() == 0) diff --git a/src/sage/quadratic_forms/quadratic_form__local_representation_conditions.py b/src/sage/quadratic_forms/quadratic_form__local_representation_conditions.py index f2d715da9b1..56bf0685d06 100644 --- a/src/sage/quadratic_forms/quadratic_form__local_representation_conditions.py +++ b/src/sage/quadratic_forms/quadratic_form__local_representation_conditions.py @@ -590,7 +590,7 @@ def is_locally_represented_at_place(self, m, p): for s in sqclass: #print "m =", m, " s =", s, " m/s =", (QQ(m)/s) if (QQ(m)/s).is_padic_square(p): - nu = valuation(m/s, p) + nu = valuation(m//s, p) return local_vec[sqclass.index(s) + 1] <= (nu / 2) diff --git a/src/sage/quadratic_forms/quadratic_form__mass__Conway_Sloane_masses.py b/src/sage/quadratic_forms/quadratic_form__mass__Conway_Sloane_masses.py index 934776fbdd5..cf5ce908a78 100644 --- a/src/sage/quadratic_forms/quadratic_form__mass__Conway_Sloane_masses.py +++ b/src/sage/quadratic_forms/quadratic_form__mass__Conway_Sloane_masses.py @@ -274,7 +274,7 @@ def conway_species_list_at_2(self): if jordan_list[i].is_even(): two_t = d else: - two_t = ZZ(2) * floor((d-1) / 2) + two_t = ZZ(2) * ((d-1) // 2) ## Determine if the form is bound if len(jordan_list) == 1: @@ -554,9 +554,9 @@ def conway_standard_p_mass(self, p): ## Some useful variables n = self.dim() if n % 2 == 0: - s = n / 2 + s = n // 2 else: - s = (n+1) / 2 + s = (n+1) // 2 ## Compute the inverse of the generic p-mass p_mass_inv = 2 * prod([1-p**(-i) for i in range(2, 2*s, 2)]) @@ -596,9 +596,9 @@ def conway_standard_mass(self): """ n = self.dim() if n % 2 == 0: - s = n / 2 + s = n // 2 else: - s = (n+1) / 2 + s = (n+1) // 2 ## DIAGNOSTIC #print "n = ", n diff --git a/src/sage/quadratic_forms/quadratic_form__mass__Siegel_densities.py b/src/sage/quadratic_forms/quadratic_form__mass__Siegel_densities.py index acf9a554b26..e04f96df95c 100644 --- a/src/sage/quadratic_forms/quadratic_form__mass__Siegel_densities.py +++ b/src/sage/quadratic_forms/quadratic_form__mass__Siegel_densities.py @@ -63,7 +63,7 @@ def mass__by_Siegel_densities(self, odd_algorithm="Pall", even_algorithm="Watson """ ## Setup n = self.dim() - s = floor((n-1)/2) + s = (n-1) // 2 if n % 2 != 0: char_d = squarefree_part(2*self.det()) ## Accounts for the det as a QF else: @@ -173,7 +173,7 @@ def Pall_mass_density_at_odd_prime(self, p): ## Step 2: Compute the list of local masses for each Jordan block jordan_mass_list = [] for (s,n,d) in modified_jordan_list: - generic_factor = prod([1 - p**(-2*j) for j in range(1, floor((n-1)/2)+1)]) + generic_factor = prod([1 - p**(-2*j) for j in range(1, (n-1)//2+1)]) #print "generic factor: ", generic_factor if (n % 2 == 0): m = n/2 @@ -364,7 +364,7 @@ def Kitaoka_mass_at_2(self): ## Compute P = product of the P_j P = QQ(1) for j in range(s_min, s_max + 1): - tmp_m = dim2_dict[j].dim() / 2 + tmp_m = dim2_dict[j].dim() // 2 P *= prod([QQ(1) - QQ(4**(-k)) for j in range(1, tmp_m + 1)]) ## Compute the product E := prod_j (1 / E_j) @@ -374,7 +374,7 @@ def Kitaoka_mass_at_2(self): ((diag_dict[j].dim() != 2) or (((diag_dict[j][0,0] - diag_dict[j][1,1]) % 4) != 0)): ## Deal with the complicated case: - tmp_m = dim2_dict[j].dim() / 2 + tmp_m = dim2_dict[j].dim() // 2 if dim2_dict[j].is_hyperbolic(2): E *= 2 / (1 + 2**(-tmp_m)) else: diff --git a/src/sage/quadratic_forms/quadratic_form__siegel_product.py b/src/sage/quadratic_forms/quadratic_form__siegel_product.py index f37496f0bad..82f7c325db7 100644 --- a/src/sage/quadratic_forms/quadratic_form__siegel_product.py +++ b/src/sage/quadratic_forms/quadratic_form__siegel_product.py @@ -99,7 +99,7 @@ def siegel_product(self, u): ## Make the odd generic factors if ((n % 2) == 1): - m = (n-1) / 2 + m = (n-1) // 2 d1 = fundamental_discriminant(((-1)**m) * 2*d * u) ## Replaced d by 2d here to compensate for the determinant f = abs(d1) ## gaining an odd power of 2 by using the matrix of 2Q instead ## of the matrix of Q. @@ -124,7 +124,7 @@ def siegel_product(self, u): ## Make the even generic factor if ((n % 2) == 0): - m = n / 2 + m = n // 2 d1 = fundamental_discriminant(((-1)**m) * d) f = abs(d1) diff --git a/src/sage/quadratic_forms/quadratic_form__ternary_Tornaria.py b/src/sage/quadratic_forms/quadratic_form__ternary_Tornaria.py index 72c6eb7fc8b..da799772cde 100644 --- a/src/sage/quadratic_forms/quadratic_form__ternary_Tornaria.py +++ b/src/sage/quadratic_forms/quadratic_form__ternary_Tornaria.py @@ -59,7 +59,7 @@ def disc(self): if is_odd(self.dim()): return self.base_ring()(self.det() / 2) ## This is not so good for characteristic 2. else: - return (-1)**(self.dim()/2) * self.det() + return (-1)**(self.dim()//2) * self.det() def content(self): diff --git a/src/sage/rings/arith.py b/src/sage/rings/arith.py index de6d3ea412b..9c2eb8d9c90 100644 --- a/src/sage/rings/arith.py +++ b/src/sage/rings/arith.py @@ -880,12 +880,12 @@ def eratosthenes(n): return [] s = range(3,n+3,2) mroot = n ** 0.5 - half = (n+1)/2 + half = (n+1) // 2 i = 0 m = 3 while m <= mroot: if s[i]: - j = (m*m-3)/2 + j = (m*m-3) // 2 s[j] = 0 while j < half: s[j] = 0 @@ -1818,7 +1818,7 @@ def __LCM_sequence(v): return g return g -def xlcm(m,n): +def xlcm(m, n): r""" Extended lcm function: given two positive integers `m,n`, returns a triple `(l,m_1,n_1)` such that `l=\mathop{\mathrm{lcm}}(m,n)=m_1 @@ -1833,17 +1833,17 @@ def xlcm(m,n): sage: xlcm(120,36) (360, 40, 9) """ - g=gcd(m,n) - l=m*n//g # = lcm(m,n) - g=gcd(m,n//g) # divisible by those primes which divide n to a - # higher power than m + g = gcd(m, n) + l = m*n//g # = lcm(m, n) + g = gcd(m, n//g) # divisible by those primes which divide n to a + # higher power than m while not g==1: - m//=g - g=gcd(m,g) + m //= g + g = gcd(m, g) - n=l//m; - return (l,m,n) + n = l//m + return (l, m, n) def xgcd(a, b): r""" @@ -3759,7 +3759,7 @@ def quadratic_residues(n): 159 """ n = abs(int(n)) - X = sorted(set([ZZ((a*a)%n) for a in range(n/2+1)])) + X = sorted(set([ZZ((a*a)%n) for a in range(n//2+1)])) return X ## This much slower than above, for obvious reasons. diff --git a/src/sage/rings/finite_rings/finite_field_givaro.py b/src/sage/rings/finite_rings/finite_field_givaro.py index f14c7511464..a0ad1adc1bd 100644 --- a/src/sage/rings/finite_rings/finite_field_givaro.py +++ b/src/sage/rings/finite_rings/finite_field_givaro.py @@ -508,7 +508,7 @@ def polynomial(self, name=None): ret = [] for i in range(self.degree()): ret.append(quo%b) - quo = quo/b + quo = quo // b ret = ret + [1] R = self.polynomial_ring(name) if name is None: diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index 4d371b275f2..36f42cb5561 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -146,6 +146,7 @@ include "sage/ext/stdsage.pxi" from cpython.list cimport * from cpython.number cimport * from cpython.int cimport * +from libc.stdint cimport uint64_t include "sage/ext/python_debug.pxi" include "../structure/coerce.pxi" # for parent_c include "sage/libs/pari/decl.pxi" @@ -3183,7 +3184,7 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): sage: n = Integer(17); float(n) 17.0 sage: n = Integer(902834098234908209348209834092834098); float(n) - 9.028340982349081e+35 + 9.028340982349083e+35 sage: n = Integer(-57); float(n) -57.0 sage: n.__float__() @@ -3191,7 +3192,7 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): sage: type(n.__float__()) """ - return mpz_get_d(self.value) + return mpz_get_d_nearest(self.value) def _rpy_(self): """ @@ -6468,3 +6469,111 @@ cdef inline Integer smallInteger(long value): z = PY_NEW(Integer) mpz_set_si(z.value, value) return z + + +# The except value is just some random double, it doesn't matter what it is. +cdef double mpz_get_d_nearest(mpz_t x) except? -648555075988944.5: + """ + Convert a ``mpz_t`` to a ``double``, with round-to-nearest-even. + This differs from ``mpz_get_d()`` which does round-to-zero. + + TESTS:: + + sage: x = ZZ(); float(x) + 0.0 + sage: x = 2^54 - 1 + sage: float(x) + 1.8014398509481984e+16 + sage: float(-x) + -1.8014398509481984e+16 + sage: x = 2^10000; float(x) + inf + sage: float(-x) + -inf + + :: + + sage: x = (2^53 - 1) * 2^971; float(x) # Largest double + 1.7976931348623157e+308 + sage: float(-x) + -1.7976931348623157e+308 + sage: x = (2^53) * 2^971; float(x) + inf + sage: float(-x) + -inf + sage: x = ZZ((2^53 - 1/2) * 2^971); float(x) + inf + sage: float(-x) + -inf + sage: x = ZZ((2^53 - 3/4) * 2^971); float(x) + 1.7976931348623157e+308 + sage: float(-x) + -1.7976931348623157e+308 + + AUTHORS: + + - Jeroen Demeyer (:trac:`16385`, based on :trac:`14416`) + """ + cdef mp_bitcnt_t sx = mpz_sizeinbase(x, 2) + + # Easy case: x is exactly representable as double. + if sx <= 53: + return mpz_get_d(x) + + cdef int resultsign = mpz_sgn(x) + + # Check for overflow + if sx > 1024: + if resultsign < 0: + return -1.0/0.0 + else: + return 1.0/0.0 + + # General case + + # We should shift x right by this amount in order + # to have 54 bits remaining. + cdef mp_bitcnt_t shift = sx - 54 + + # Compute q = trunc(x / 2^shift) and let remainder_is_zero be True + # if and only if no truncation occurred. + cdef int remainder_is_zero + remainder_is_zero = mpz_divisible_2exp_p(x, shift) + + sig_on() + + cdef mpz_t q + mpz_init(q) + mpz_tdiv_q_2exp(q, x, shift) + + # Convert abs(q) to a 64-bit integer. + cdef mp_limb_t* q_limbs = (<__mpz_struct*>q)._mp_d + cdef uint64_t q64 + if sizeof(mp_limb_t) >= 8: + q64 = q_limbs[0] + else: + assert sizeof(mp_limb_t) == 4 + q64 = q_limbs[1] + q64 = (q64 << 32) + q_limbs[0] + + mpz_clear(q) + sig_off() + + # Round q from 54 to 53 bits of precision. + if ((q64 & 1) == 0): + # Round towards zero + pass + else: + if not remainder_is_zero: + # Remainder is non-zero: round away from zero + q64 += 1 + else: + # Halfway case: round to even + q64 += (q64 & 2) - 1 + + # The conversion of q64 to double is *exact*. + # This is because q64 is even and satisfies 2^53 <= q64 <= 2^54. + cdef double d = q64 + if resultsign < 0: + d = -d + return ldexp(d, shift) diff --git a/src/sage/rings/invariant_theory.py b/src/sage/rings/invariant_theory.py index 4cdc6f54936..78e52726849 100644 --- a/src/sage/rings/invariant_theory.py +++ b/src/sage/rings/invariant_theory.py @@ -988,7 +988,7 @@ def discriminant(self): if is_odd(self._n): return A.det() / 2 else: - return (-1)**(self._n/2) * A.det() + return (-1)**(self._n//2) * A.det() @cached_method diff --git a/src/sage/rings/multi_power_series_ring_element.py b/src/sage/rings/multi_power_series_ring_element.py index 696205890b9..565ac5d49f4 100644 --- a/src/sage/rings/multi_power_series_ring_element.py +++ b/src/sage/rings/multi_power_series_ring_element.py @@ -1954,7 +1954,7 @@ def exp(self, prec=infinity): n_inv_factorial = R.base_ring().one() x_pow_n = Rbg.one() exp_x = Rbg.one().add_bigoh(prec) - for n in range(1,prec/val+1): + for n in range(1,prec//val+1): x_pow_n = (x_pow_n * x).add_bigoh(prec) n_inv_factorial /= n exp_x += x_pow_n * n_inv_factorial @@ -2046,7 +2046,7 @@ def log(self, prec=infinity): prec = R.default_prec() x_pow_n = Rbg.one() log_x = Rbg.zero().add_bigoh(prec) - for n in range(1,prec/val+1): + for n in range(1,prec//val+1): x_pow_n = (x_pow_n * x).add_bigoh(prec) log_x += x_pow_n / n result_bg = log_c - log_x diff --git a/src/sage/rings/number_field/morphism.py b/src/sage/rings/number_field/morphism.py index 2ff0baaf0cb..dcd3b75a0d4 100644 --- a/src/sage/rings/number_field/morphism.py +++ b/src/sage/rings/number_field/morphism.py @@ -66,7 +66,7 @@ def _coerce_impl(self, x): if x.parent() is self: return x if x.parent() == self: - return NumberFieldHomomorphism_im_gens(self, x.im_gens()) + return NumberFieldHomomorphism_im_gens(self, x.im_gens(), check=False) raise TypeError def _an_element_(self): diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 84e96e13103..ce00138e9a8 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -3043,6 +3043,56 @@ def prime_above(self, x, degree=None): raise ValueError("No prime of degree %s above %s" % (degree, self.ideal(x))) return ids[0] + def primes_of_bounded_norm_iter(self, B): + r""" + Iterator yielding all prime ideals with norm at most `B`. + + INPUT: + + - ``B`` -- a positive integer; upper bound on the norms of the + primes generated. + + OUTPUT: + + An iterator over all prime ideals of this number field of norm + at most `B`. + + .. note:: + + The output is not sorted by norm, but by size of the + underlying rational prime. + + EXAMPLES:: + + sage: K.=QuadraticField(-1) + sage: it=K.primes_of_bounded_norm_iter(10) + sage: list(it) + [Fractional ideal (i + 1), + Fractional ideal (3), + Fractional ideal (-i - 2), + Fractional ideal (i - 2)] + sage: list(K.primes_of_bounded_norm_iter(1)) + [] + """ + try: + B = ZZ(B.ceil()) + except (TypeError, AttributeError): + raise TypeError("%s is not valid bound on prime ideals" % B) + + if B<2: + raise StopIteration + + from sage.rings.arith import primes + if self is QQ: + for p in primes(B+1): + yield p + else: + for p in primes(B+1): + for pp in self.primes_above(p): + if pp.norm() <= B: + yield pp + + def primes_of_degree_one_iter(self, num_integer_primes=10000, max_iterations=100): r""" Return an iterator yielding prime ideals of absolute degree one and @@ -6675,7 +6725,6 @@ def _subfields_helper(self, degree=0, name=None, both_maps=True, optimize=False) self.__subfields[name, degree, both_maps, optimize] = ans return ans - def maximal_order(self, v=None): """ Return the maximal order, i.e., the ring of integers, associated to @@ -6683,22 +6732,20 @@ def maximal_order(self, v=None): INPUT: + - ``v`` - (default: ``None``) ``None``, a prime, or a list of primes. - - ``v`` - (default: None) None, a prime, or a list of - primes. + - if ``v`` is ``None``, return the maximal order. - - if v is None, return the maximal order. + - if ``v`` is a prime, return an order that is `p`-maximal. - - if v is a prime, return an order that is p-maximal. + - if ``v`` is a list, return an order that is maximal at each prime + in the list ``v``. - - if v is a list, return an order that is maximal at each - prime in the list v. + EXAMPLES: - EXAMPLES: In this example, the maximal order cannot be generated by - a single element. - - :: + In this example, the maximal order cannot be generated by a single + element:: sage: k. = NumberField(x^3 + x^2 - 2*x+8) sage: o = k.maximal_order() @@ -6729,29 +6776,26 @@ def maximal_order(self, v=None): sage: K.maximal_order(prime_range(10000)).basis() [1, a, a^2] """ - v = self._normalize_prime_list(v) + return self._maximal_order(self._normalize_prime_list(v)) - try: - return self.__maximal_order[v] - except AttributeError: - self.__maximal_order = {} - except KeyError: - pass + @cached_method + def _maximal_order(self, v): + r""" + Helper method which adds caching to :meth:`maximal_order`. - B = map(self, self._pari_integral_basis(v=v)) + EXAMPLES:: - if len(v) == 0 or v is None: - is_maximal = True - else: - is_maximal = False + sage: k. = NumberField(x^3 + x^2 - 2*x+8) + sage: k.maximal_order() is k.maximal_order() # indirect doctest + True + + """ + B = map(self, self._pari_integral_basis(v=v)) import sage.rings.number_field.order as order - O = order.absolute_order_from_module_generators(B, + return order.absolute_order_from_module_generators(B, check_integral=False, check_rank=False, - check_is_ring=False, is_maximal=is_maximal) - - self.__maximal_order[v] = O - return O + check_is_ring=False, is_maximal=not v) def order(self, *args, **kwds): r""" @@ -6760,21 +6804,20 @@ def order(self, *args, **kwds): INPUT: - - ``gens`` - list of elements of self; if no - generators are given, just returns the cardinality of this number - field (oo) for consistency. + - ``gens`` - list of elements in this number field; if no generators + are given, just returns the cardinality of this number field + (`\infty`) for consistency. - - ``check_is_integral`` - bool (default: True), - whether to check that each generator is integral. + - ``check_is_integral`` - bool (default: ``True``), whether to check + that each generator is integral. - - ``check_rank`` - bool (default: True), whether to - check that the ring generated by gens is of full rank. - - - ``allow_subfield`` - bool (default: False), if True - and the generators do not generate an order, i.e., they generate a - subring of smaller rank, instead of raising an error, return an - order in a smaller number field. + - ``check_rank`` - bool (default: ``True``), whether to check that the + ring generated by ``gens`` is of full rank. + - ``allow_subfield`` - bool (default: ``False``), if ``True`` and the + generators do not generate an order, i.e., they generate a subring + of smaller rank, instead of raising an error, return an order in a + smaller number field. EXAMPLES:: @@ -6820,8 +6863,57 @@ def order(self, *args, **kwds): if len(gens) == 1 and isinstance(gens[0], (list, tuple)): gens = gens[0] gens = map(self, gens) + return self._order(tuple(gens), **kwds) + + @cached_method + def _order(self, gens, **kwds): + r""" + Helper method for :meth:`order` which adds caching. See :meth:`order` + for a description of the parameters and keyword parameters. + + TESTS: + + Test that caching works:: + + sage: K. = NumberField(x^3 - 2) + sage: K.order(a) is K.order(a) + True + + Keywords have no influence on the caching:: + + sage: K.order(a) is K.order(a,check_is_integral=True) is K.order(a,check_is_integral=False) + True + + Even if the order lives in a different field, caching works (currently, + however, ``allow_subfield`` is incorrect :trac:`16046`):: + + sage: K. = NumberField(x**4+3) + sage: o = K.order([a**2], allow_subfield=True) + sage: o is K.order([a**2], allow_subfield=True) + True + + Different generators for the same order:: + + sage: K.order(a) is K.order(a,a^2) is K.order(a^2,a) + True + + """ import sage.rings.number_field.order as order - return order.absolute_order_from_ring_generators(gens, **kwds) + ret = order.absolute_order_from_ring_generators(gens, **kwds) + # we make sure that the result is a unique parent even if it the order + # lives in a different field + if ret.ambient() is not self: + return ret.ambient().order(gens, **kwds) + + gens = ret.gens() + if self._order.is_in_cache(gens): + # different ways of specifying the same set of generators lead to + # the same order - this is to make sure that orders are unique + # parents + return self._order(gens) + + self._order.set_cache(ret, gens) + return ret def vector_space(self): """ diff --git a/src/sage/rings/number_field/totallyreal_rel.py b/src/sage/rings/number_field/totallyreal_rel.py index b0c2509ea38..e7b55b71c61 100644 --- a/src/sage/rings/number_field/totallyreal_rel.py +++ b/src/sage/rings/number_field/totallyreal_rel.py @@ -562,7 +562,7 @@ def incr(self, f_out, verbose=False, haltk=0): betak = self.beta[k] akmin = [-numpy.polyval(gnks[j], betak[j][mk+1]) - \ abs(numpy.polyval(gnkm1s[j], betak[j][mk+1]))*eps_global for j in range(self.d)] - for i in range(1,(mk+1)/2+1): + for i in range(1,(mk+1)//2+1): # Use the fact that f(z) <= f(x)+|f'(x)|eps if |x-z| < eps # for sufficiently small eps, f(z) = 0, and f''(z) < 0. akmin = [max(akmin[j], @@ -571,7 +571,7 @@ def incr(self, f_out, verbose=False, haltk=0): akmax = [-numpy.polyval(gnks[j], betak[j][mk]) + \ abs(numpy.polyval(gnkm1s[j], betak[j][mk]))*eps_global for j in range(self.d)] - for i in range(1,mk/2+1): + for i in range(1,mk//2+1): akmax = [min(akmax[j], -numpy.polyval(gnks[j], betak[j][mk-2*i]) + \ abs(numpy.polyval(gnkm1s[j], betak[j][mk-2*i])*eps_global)) for j in range(self.d)] diff --git a/src/sage/rings/padics/factory.py b/src/sage/rings/padics/factory.py index 9c2d9e9eea4..bf6edc5f89f 100644 --- a/src/sage/rings/padics/factory.py +++ b/src/sage/rings/padics/factory.py @@ -38,6 +38,7 @@ ###################################################### from padic_extension_leaves import * +from functools import reduce #This imports all of the classes used in the ext_table below. ext_table = {} diff --git a/src/sage/rings/padics/padic_extension_generic.py b/src/sage/rings/padics/padic_extension_generic.py index 436d2bb246d..687da386411 100644 --- a/src/sage/rings/padics/padic_extension_generic.py +++ b/src/sage/rings/padics/padic_extension_generic.py @@ -21,6 +21,7 @@ from padic_generic import pAdicGeneric from padic_base_generic import pAdicBaseGeneric +from functools import reduce class pAdicExtensionGeneric(pAdicGeneric): def __init__(self, poly, prec, print_mode, names, element_class): diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index d45fd464b10..1eff3cd69f1 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -1482,7 +1482,7 @@ def __pow__(self, n): PPgens = PP.gens() newVars = [] - sh = PP.ngens()/P.ngens() - 1 + sh = PP.ngens()//P.ngens() - 1 blocklength = sh nM = sh+1 for i in range(P.ngens()): diff --git a/src/sage/rings/polynomial/infinite_polynomial_ring.py b/src/sage/rings/polynomial/infinite_polynomial_ring.py index 3c3e3e9f01a..684ed57dd5e 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_ring.py +++ b/src/sage/rings/polynomial/infinite_polynomial_ring.py @@ -240,6 +240,7 @@ from sage.structure.factory import UniqueFactory from sage.misc.cachefunc import cached_method import operator, re +from functools import reduce ############################################################### ## Ring Factory framework diff --git a/src/sage/rings/polynomial/multi_polynomial_ideal.py b/src/sage/rings/polynomial/multi_polynomial_ideal.py index 35240af5790..536dd8520d2 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ideal.py +++ b/src/sage/rings/polynomial/multi_polynomial_ideal.py @@ -1017,6 +1017,7 @@ def complete_primary_decomposition(self, algorithm="sy"): (Ideal (z^2 + 1, y - z^2) of Multivariate Polynomial Ring in x, y, z over Rational Field, Ideal (z^2 + 1, y - z^2) of Multivariate Polynomial Ring in x, y, z over Rational Field)] + sage: from functools import reduce sage: reduce(lambda Qi,Qj: Qi.intersection(Qj), [Qi for (Qi,radQi) in pd]) == I True @@ -1130,6 +1131,7 @@ def primary_decomposition(self, algorithm='sy'): :: + sage: from functools import reduce sage: reduce(lambda Qi,Qj: Qi.intersection(Qj), pd) == I True diff --git a/src/sage/rings/polynomial/polydict.pyx b/src/sage/rings/polynomial/polydict.pyx index d32991ea340..c117f44f29e 100644 --- a/src/sage/rings/polynomial/polydict.pyx +++ b/src/sage/rings/polynomial/polydict.pyx @@ -47,6 +47,7 @@ include 'sage/ext/cdefs.pxi' from cpython.dict cimport * import copy +from functools import reduce from sage.structure.element import generic_power from sage.misc.misc import cputime from sage.misc.latex import latex diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index ea87cb7f756..a56e7f7c9bc 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1901,21 +1901,28 @@ def irreducible_element(self, n, algorithm=None): - ``algorithm`` -- string: algorithm to use, or ``None`` - ``'random'``: try random polynomials until an irreducible - one is found. This is currently the only algorithm - available over non-prime finite fields. + one is found. + + - ``'first_lexicographic'``: try polynomials in + lexicographic order until an irreducible one is found. OUTPUT: A monic irreducible polynomial of degree `n` in ``self``. - EXAMPLE:: + EXAMPLES:: sage: GF(5^3, 'a')['x'].irreducible_element(2) x^2 + (4*a^2 + a + 4)*x + 2*a^2 + 2 + sage: GF(19)['x'].irreducible_element(21, algorithm="first_lexicographic") + x^21 + x + 5 + sage: GF(5**2, 'a')['x'].irreducible_element(17, algorithm="first_lexicographic") + x^17 + a*x + 4*a + 3 AUTHORS: - Peter Bruin (June 2013) + - Jean-Pierre Flori (May 2014) """ if n < 1: raise ValueError("degree must be at least 1") @@ -1925,6 +1932,11 @@ def irreducible_element(self, n, algorithm=None): f = self.gen()**n + self.random_element(n - 1) if f.is_irreducible(): return f + elif algorithm == "first_lexicographic": + for g in self.polynomials(max_degree=n-1): + f = self.gen()**n + g + if f.is_irreducible(): + return f else: raise ValueError("no such algorithm for finding an irreducible polynomial: %s" % algorithm) @@ -2203,8 +2215,7 @@ def irreducible_element(self, n, algorithm=None): ``RuntimeError`` if it is not found. - ``'first_lexicographic'``: return the lexicographically - smallest irreducible polynomial of degree `n`. Only - implemented for `p = 2`. + smallest irreducible polynomial of degree `n`. - ``'minimal_weight'``: return an irreducible polynomial of degree `n` with minimal number of non-zero coefficients. @@ -2265,7 +2276,8 @@ def irreducible_element(self, n, algorithm=None): if p == 2: return self(GF2X_BuildIrred_list(n)) else: - raise NotImplementedError("'first_lexicographic' option only implemented for p = 2") + # Fallback to PolynomialRing_dense_finite_field.irreducible_element + pass elif algorithm == "minimal_weight": if p == 2: return self(GF2X_BuildSparseIrred_list(n)) diff --git a/src/sage/rings/rational.pyx b/src/sage/rings/rational.pyx index dfa4858a723..62df180de09 100644 --- a/src/sage/rings/rational.pyx +++ b/src/sage/rings/rational.pyx @@ -1948,6 +1948,8 @@ cdef class Rational(sage.structure.element.FieldElement): 0.3333333333333333 sage: float(1/10) 0.1 + sage: n = QQ(902834098234908209348209834092834098); float(n) + 9.028340982349083e+35 TESTS: @@ -3455,7 +3457,7 @@ cdef double mpq_get_d_nearest(mpq_t x) except? -648555075988944.5: TESTS:: - sage: q= QQ(); float(q) + sage: q = QQ(); float(q) 0.0 sage: q = 2^-10000; float(q) 0.0 @@ -3572,10 +3574,9 @@ cdef double mpq_get_d_nearest(mpq_t x) except? -648555075988944.5: cdef mpz_t q, r mpz_init(q) mpz_init(r) - cdef bint remainder_is_zero + cdef int remainder_is_zero if shift > 0: - mpz_tdiv_r_2exp(r, a, shift) - remainder_is_zero = (mpz_cmp_ui(r, 0) == 0) + remainder_is_zero = mpz_divisible_2exp_p(a, shift) mpz_tdiv_q_2exp(q, a, shift) else: mpz_mul_2exp(q, a, -shift) @@ -3588,7 +3589,7 @@ cdef double mpq_get_d_nearest(mpq_t x) except? -648555075988944.5: if remainder_is_zero: remainder_is_zero = (mpz_cmp_ui(r, 0) == 0) - # Convert q to a 64-bit integer. + # Convert abs(q) to a 64-bit integer. cdef mp_limb_t* q_limbs = (<__mpz_struct*>q)._mp_d cdef uint64_t q64 if sizeof(mp_limb_t) >= 8: @@ -3628,6 +3629,7 @@ cdef double mpq_get_d_nearest(mpq_t x) except? -648555075988944.5: remainder_is_zero = ((q64 & mask) == 0) q64 = q64 >> add_shift + # Round q64 from 54 to 53 bits of precision. if ((q64 & 1) == 0): # Round towards zero pass @@ -3636,6 +3638,7 @@ cdef double mpq_get_d_nearest(mpq_t x) except? -648555075988944.5: # Remainder is non-zero: round away from zero q64 += 1 else: + # Halfway case: round to even q64 += (q64 & 2) - 1 # The conversion of q64 to double is *exact*. diff --git a/src/sage/rings/rational_field.py b/src/sage/rings/rational_field.py index d32c69b39e1..eeea96249d0 100644 --- a/src/sage/rings/rational_field.py +++ b/src/sage/rings/rational_field.py @@ -499,6 +499,43 @@ def range_by_height(self, start, end=None): yield self(height/other) yield self(-height/other) + def primes_of_bounded_norm_iter(self, B): + r""" + Iterator yielding all primes less than or equal to `B`. + + INPUT: + + - ``B`` -- a positive integer; upper bound on the primes generated. + + OUTPUT: + + An iterator over all integer primes less than or equal to `B`. + + .. note:: + + This function exists for compatibility with the related number + field method, though it returns prime integers, not ideals. + + EXAMPLES:: + + sage: it = QQ.primes_of_bounded_norm_iter(10) + sage: list(it) + [2, 3, 5, 7] + sage: list(QQ.primes_of_bounded_norm_iter(1)) + [] + """ + try: + B = ZZ(B.ceil()) + except (TypeError, AttributeError): + raise TypeError("%s is not valid bound on prime ideals" % B) + + if B<2: + raise StopIteration + + from sage.rings.arith import primes + for p in primes(B+1): + yield p + def discriminant(self): """ Return the discriminant of the field of rational numbers, which is 1. @@ -579,6 +616,56 @@ def embeddings(self, K): raise ValueError("no embeddings of the rational field into K.") return [self.hom(K)] + def places(self, all_complex=False, prec=None): + r""" + Return the collection of all infinite places of self, which + in this case is just the embedding of self into `\RR`. + + By default, this returns homomorphisms into ``RR``. If + ``prec`` is not None, we simply return homomorphisms into + ``RealField(prec)`` (or ``RDF`` if ``prec=53``). + + There is an optional flag ``all_complex``, which defaults to + False. If ``all_complex`` is True, then the real embeddings + are returned as embeddings into the corresponding complex + field. + + For consistency with non-trivial number fields. + + EXAMPLES:: + + sage: QQ.places() + [Ring morphism: + From: Rational Field + To: Real Field with 53 bits of precision + Defn: 1 |--> 1.00000000000000] + sage: QQ.places(prec=53) + [Ring morphism: + From: Rational Field + To: Real Double Field + Defn: 1 |--> 1.0] + sage: QQ.places(prec=200, all_complex=True) + [Ring morphism: + From: Rational Field + To: Complex Field with 200 bits of precision + Defn: 1 |--> 1.0000000000000000000000000000000000000000000000000000000000] + """ + import sage.rings.all + if prec is None: + R = sage.rings.all.RR + C = sage.rings.all.CC + elif prec == 53: + R = sage.rings.all.RDF + C = sage.rings.all.CDF + elif prec == infinity.Infinity: + R = sage.rings.all.AA + C = sage.rings.all.QQbar + else: + R = sage.rings.all.RealField(prec) + C = sage.rings.all.ComplexField(prec) + domain = C if all_complex else R + return [self.hom([domain(1)])] + def complex_embedding(self, prec=53): """ Return embedding of the rational numbers into the complex numbers. @@ -600,6 +687,30 @@ def complex_embedding(self, prec=53): CC = complex_field.ComplexField(prec) return self.hom([CC(1)]) + def residue_field(self, p, check=True): + r""" + Return the residue field of `\QQ` at the prime `p`, for + consistency with other number fields. + + INPUT: + + - ``p`` - a prime integer. + + - ``check`` (default True) - if True check the primality of + `p`, else do not. + + OUTPUT: The residue field at this prime. + + EXAMPLES:: + + sage: QQ.residue_field(5) + Residue field of Integers modulo 5 + sage: QQ.residue_field(next_prime(10^9)) + Residue field of Integers modulo 1000000007 + """ + from sage.rings.residue_field import ResidueField + return ResidueField(ZZ.ideal(p), check=check) + def gens(self): r""" Return a tuple of generators of `\QQ` which is only ``(1,)``. diff --git a/src/sage/rings/real_mpfr.pyx b/src/sage/rings/real_mpfr.pyx index 91d81c09c06..52386921bbc 100644 --- a/src/sage/rings/real_mpfr.pyx +++ b/src/sage/rings/real_mpfr.pyx @@ -86,6 +86,8 @@ TESTS:: sage: -1e30 -1.00000000000000e30 + sage: hex(-1. + 2^-52) + '-0xf.ffffffffffffp-4' Make sure we don't have a new field for every new literal:: @@ -1910,6 +1912,46 @@ cdef class RealNumber(sage.structure.element.RingElement): return z + def __hex__(self): + """ + Return a hexadecimal floating-point representation of ``self``, in the + style of C99 hexadecimal floating-point constants. + + EXAMPLES:: + + sage: RR(-1/3).hex() + '-0x5.5555555555554p-4' + sage: Reals(100)(123.456e789).hex() + '0xf.721008e90630c8da88f44dd2p+2624' + sage: (-0.).hex() + '-0x0p+0' + + :: + + sage: [(a.hex(), float(a).hex()) for a in [.5, 1., 2., 16.]] + [('0x8p-4', '0x1.0000000000000p-1'), + ('0x1p+0', '0x1.0000000000000p+0'), + ('0x2p+0', '0x1.0000000000000p+1'), + ('0x1p+4', '0x1.0000000000000p+4')] + + Special values:: + + sage: [RR(s).hex() for s in ['+inf', '-inf', 'nan']] + ['inf', '-inf', 'nan'] + """ + cdef char *s + cdef int r + sig_on() + r = mpfr_asprintf(&s, "%Ra", self.value) + sig_off() + if r < 0: # MPFR free()s its buffer itself in this case + raise RuntimeError("Unable to convert an mpfr number to a string.") + t = str(s) + mpfr_free_str(s) + return t + + hex = __hex__ + def __copy__(self): """ Return copy of ``self`` - since ``self`` is immutable, we just return diff --git a/src/sage/rings/universal_cyclotomic_field/universal_cyclotomic_field.py b/src/sage/rings/universal_cyclotomic_field/universal_cyclotomic_field.py index 19801bea14c..7b743f98931 100644 --- a/src/sage/rings/universal_cyclotomic_field/universal_cyclotomic_field.py +++ b/src/sage/rings/universal_cyclotomic_field/universal_cyclotomic_field.py @@ -391,7 +391,7 @@ def __init__(self,names="E",bracket="()",embedding=True): # getting the optional argument "bracket" right if isinstance(bracket,str) and len(bracket) % 2 == 0: bracket_len = len(bracket) - bracket = (bracket[:bracket_len/2],bracket[bracket_len/2:]) + bracket = (bracket[:bracket_len//2],bracket[bracket_len//2:]) else: raise ValueError("The given bracket %s is not a string of even length."%bracket) diff --git a/src/sage/schemes/elliptic_curves/ell_local_data.py b/src/sage/schemes/elliptic_curves/ell_local_data.py index ec8e952384c..5dd59f12ab2 100644 --- a/src/sage/schemes/elliptic_curves/ell_local_data.py +++ b/src/sage/schemes/elliptic_curves/ell_local_data.py @@ -406,6 +406,21 @@ def conductor_valuation(self): """ return self._fp + def discriminant_valuation(self): + """ + Return the valuation of the minimal discriminant from this local reduction data. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.ell_local_data import EllipticCurveLocalData + sage: E = EllipticCurve([0,0,0,0,64]); E + Elliptic Curve defined by y^2 = x^3 + 64 over Rational Field + sage: data = EllipticCurveLocalData(E,2) + sage: data.discriminant_valuation() + 4 + """ + return self._val_disc + def kodaira_symbol(self): r""" Return the Kodaira symbol from this local reduction data. @@ -471,8 +486,8 @@ def tamagawa_exponent(self): return cp ks = self._KS if ks._roman==1 and ks._n%2==0 and ks._starred: - return 2 - return 4 + return ZZ(2) + return ZZ(4) def bad_reduction_type(self): r""" diff --git a/src/sage/schemes/elliptic_curves/ell_number_field.py b/src/sage/schemes/elliptic_curves/ell_number_field.py index eff2c0a6608..6ed45de5107 100644 --- a/src/sage/schemes/elliptic_curves/ell_number_field.py +++ b/src/sage/schemes/elliptic_curves/ell_number_field.py @@ -2240,6 +2240,23 @@ def period_lattice(self, embedding): from sage.schemes.elliptic_curves.period_lattice import PeriodLattice_ell return PeriodLattice_ell(self,embedding) + def height_function(self): + """ + Return the canonical height function attached to self. + + EXAMPLE:: + + sage: K. = NumberField(x^2 - 5) + sage: E = EllipticCurve(K, '11a3') + sage: E.height_function() + EllipticCurveCanonicalHeight object associated to Elliptic Curve defined by y^2 + y = x^3 + (-1)*x^2 over Number Field in a with defining polynomial x^2 - 5 + + """ + if not hasattr(self, '_height_function'): + from sage.schemes.elliptic_curves.height import EllipticCurveCanonicalHeight + self._height_function = EllipticCurveCanonicalHeight(self) + return self._height_function + def is_isogenous(self, other, proof=True, maxnorm=100): """ Returns whether or not self is isogenous to other. diff --git a/src/sage/schemes/elliptic_curves/height.py b/src/sage/schemes/elliptic_curves/height.py new file mode 100644 index 00000000000..8d0ef41aade --- /dev/null +++ b/src/sage/schemes/elliptic_curves/height.py @@ -0,0 +1,2047 @@ +r""" +Canonical heights for elliptic curves over number fields + +Also, rigorous lower bounds for the canonical height of non-torsion +points, implementing the algorithms in [CS]_ (over `\QQ`) and [TT]_, +which also refer to [CPS]_. + +AUTHORS: + +- Robert Bradshaw (2010): initial version + +- John Cremona (2014): added many docstrings and doctests + +REFERENCES: + +.. [CS] J.E.Cremona, and S. Siksek, Computing a Lower Bound for the + Canonical Height on Elliptic Curves over `\QQ`, ANTS VII + Proceedings: F.Hess, S.Pauli and M.Pohst (eds.), ANTS VII, Lecture + Notes in Computer Science 4076 (2006), pages 275-286. + +.. [TT] T. Thongjunthug, Computing a lower bound for the canonical + height on elliptic curves over number fields, Math. Comp. 79 + (2010), pages 2431-2449. + +.. [CPS] J.E. Cremona, M. Prickett and S. Siksek, Height Difference + Bounds For Elliptic Curves over Number Fields, Journal of Number + Theory 116(1) (2006), pages 42-68. + +""" +############################################################################## +# Copyright (C) 2010 Robert Bradshaw +# 2014 John Cremona +# +# 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 numpy +import math, bisect + +from sage.rings.all import (ZZ, QQ, RR, RDF, RIF, CC, CDF, CIF, + infinity, RealField, ComplexField) + +from sage.misc.all import cached_method, cartesian_product_iterator +from sage.rings.arith import lcm, factor, factorial +from sage.ext.fast_callable import fast_callable +from sage.functions.log import log, exp +from sage.symbolic.all import SR + +from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + +class UnionOfIntervals: + r""" + A class representing a finite union of closed intervals in + `\RR` which can be scaled, shifted, intersected, etc. + + The intervals are represented as an ordered list of their + endpoints, which may include `-\infty` and `+\infty`. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: R = UnionOfIntervals([1,2,3,infinity]); R + ([1, 2] U [3, +Infinity]) + sage: R + 5 + ([6, 7] U [8, +Infinity]) + sage: ~R + ([-Infinity, 1] U [2, 3]) + sage: ~R | (10*R + 100) + ([-Infinity, 1] U [2, 3] U [110, 120] U [130, +Infinity]) + + .. TODO:: + + Unify :class:`UnionOfIntervals` with the class ``RealSet`` + introduced by :trac:`13125`; see :trac:`16063`. + + """ + def __init__(self, endpoints): + r""" + An union of intervals is initialized by giving an increasing list + of endpoints, the first of which may be `-\infty` and the last of + which may be `+\infty`. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: UnionOfIntervals([0,1]) + ([0, 1]) + sage: UnionOfIntervals([-infinity, pi, 17, infinity]) + ([-Infinity, pi] U [17, +Infinity]) + sage: UnionOfIntervals([]) + () + + sage: UnionOfIntervals([1]) + Traceback (most recent call last): + ... + ValueError: an even number of endpoints must be given (got 1) + sage: UnionOfIntervals([3,2,1,0]) + Traceback (most recent call last): + ... + ValueError: endpoints must be given in order + """ + if len(endpoints) % 2: + raise ValueError("an even number of endpoints must be given (got %s)" % len(endpoints)) + if endpoints != sorted(endpoints): + raise ValueError("endpoints must be given in order") + self._endpoints = endpoints + + def finite_endpoints(self): + r""" + Returns the finite endpoints of this union of intervals. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: UnionOfIntervals([0,1]).finite_endpoints() + [0, 1] + sage: UnionOfIntervals([-infinity, 0, 1, infinity]).finite_endpoints() + [0, 1] + """ + return [e for e in self._endpoints if -infinity < e < infinity] + + def intervals(self): + r""" + Returns the intervals in self, as a list of 2-tuples. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: UnionOfIntervals(range(10)).intervals() + [(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)] + sage: UnionOfIntervals([-infinity, pi, 17, infinity]).intervals() + [(-Infinity, pi), (17, +Infinity)] + """ + return zip(self._endpoints[::2], self._endpoints[1::2]) + + def is_empty(self): + r""" + Returns whether self is empty. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: UnionOfIntervals([3,4]).is_empty() + False + sage: all = UnionOfIntervals([-infinity, infinity]) + sage: all.is_empty() + False + sage: (~all).is_empty() + True + sage: A = UnionOfIntervals([0,1]) & UnionOfIntervals([2,3]) + sage: A.is_empty() + True + """ + return not self._endpoints + + def __add__(left, right): + r""" + If both left an right are unions of intervals, take their union, + otherwise treat the non-union of intervals as a scalar and shift. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: A = UnionOfIntervals([0, 1/2, 2, infinity]); A + ([0, 1/2] U [2, +Infinity]) + sage: A + 1 + ([1, 3/2] U [3, +Infinity]) + sage: pi + A + ([pi, pi + 1/2] U [pi + 2, +Infinity]) + sage: A + UnionOfIntervals([-infinity, -1]) + ([-Infinity, -1] U [0, 1/2] U [2, +Infinity]) + """ + if not isinstance(left, UnionOfIntervals): + left, right = right, left + elif not isinstance(right, UnionOfIntervals): + return UnionOfIntervals([right + e for e in left._endpoints]) + else: + return left.union([left, right]) + + def __mul__(left, right): + r""" + Scale a union of intervals on the left or right. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: A = UnionOfIntervals([0, 1/2, 2, infinity]); A + ([0, 1/2] U [2, +Infinity]) + sage: 2 * A + ([0, 1] U [4, +Infinity]) + sage: A * 100 + ([0, 50] U [200, +Infinity]) + sage: 1.5 * A + ([0.000000000000000, 0.750000000000000] U [3.00000000000000, +Infinity]) + """ + if not isinstance(right, UnionOfIntervals): + return UnionOfIntervals([e*right for e in left._endpoints]) + elif not isinstance(left, UnionOfIntervals): + return UnionOfIntervals([left*e for e in right._endpoints]) + else: + return NotImplemented + + def __rmul__(self, other): + r""" + Scale by an operand on the left. + + TESTS:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: A = UnionOfIntervals([0, 1/2, 2, infinity]); A + ([0, 1/2] U [2, +Infinity]) + sage: pi * A + ([0, 1/2*pi] U [2*pi, +Infinity]) + """ + return self * other + + def __radd__(self, other): + r""" + Add a scalar operand on the left. + + TESTS:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: A = UnionOfIntervals([0, 1/2, 2, infinity]); A + ([0, 1/2] U [2, +Infinity]) + sage: 100 + A + ([100, 201/2] U [102, +Infinity]) + """ + return self + other + + def __invert__(self): + r""" + Return the closure of the complement of self. + + .. NOTE:: + + We take the closure because open intervals are not supported. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: A = UnionOfIntervals([0, 1/2, 2, infinity]); A + ([0, 1/2] U [2, +Infinity]) + sage: ~A + ([-Infinity, 0] U [1/2, 2]) + sage: A | ~A + ([-Infinity, +Infinity]) + sage: A & ~A + ([0, 0] U [1/2, 1/2] U [2, 2]) + """ + endpoints = list(self._endpoints) + if endpoints[0] == -infinity: + del endpoints[0] + else: + endpoints.insert(0, -infinity) + if endpoints[-1] == infinity: + endpoints.pop() + else: + endpoints.append(infinity) + return UnionOfIntervals(endpoints) + + @staticmethod + def join(L, condition): + r""" + Utility function to form the union or intersection of a list of UnionOfIntervals. + + INPUT: + + - ``L`` (list) -- a list of UnionOfIntervals instances + + - ``condition`` (function) -- either ``any`` or ``all``, or + some other boolean function of a list of boolean values. + + OUTPUT: + + A new UnionOfIntervals instance representing the subset of + '\RR' equal to those reals in any/all/condition of the + UnionOfIntervals in the list. + + .. NOTE:: + + This is a static method for the class. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: A = UnionOfIntervals([1,3,5,7]); A + ([1, 3] U [5, 7]) + sage: B = A+1; B + ([2, 4] U [6, 8]) + sage: A.join([A,B],any) # union + ([1, 4] U [5, 8]) + sage: A.join([A,B],all) # intersection + ([2, 3] U [6, 7]) + sage: A.join([A,B],sum) # symmetric difference + ([1, 2] U [3, 4] U [5, 6] U [7, 8]) + """ + all = [] + for ix, region in enumerate(L): + for i, e in enumerate(region._endpoints): + all.append((e, -(not (i % 2)), ix)) + all.sort() + join = [] + in_join = False + in_L = [False] * len(L) + for e, start, ix in all: + in_L[ix] = start + if condition(in_L) != in_join: + join.append(e) + in_join = not in_join + return UnionOfIntervals(join) + + @classmethod + def union(cls, L): + r""" + Return the union of a list of UnionOfIntervals. + + INPUT: + + - ``L`` (list) -- a list of UnionOfIntervals instances + + OUTPUT: + + A new UnionOfIntervals instance representing the union of the + UnionOfIntervals in the list. + + .. NOTE:: + + This is a class method. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: A = UnionOfIntervals([1,3,5,7]); A + ([1, 3] U [5, 7]) + sage: B = A+1; B + ([2, 4] U [6, 8]) + sage: A.union([A,B]) + ([1, 4] U [5, 8]) + """ + return cls.join(L, any) + + @classmethod + def intersection(cls, L): + r""" + Return the intersection of a list of UnionOfIntervals. + + INPUT: + + - ``L`` (list) -- a list of UnionOfIntervals instances + + OUTPUT: + + A new UnionOfIntervals instance representing the intersection + of the UnionOfIntervals in the list. + + .. NOTE:: + + This is a class method. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: A = UnionOfIntervals([1,3,5,7]); A + ([1, 3] U [5, 7]) + sage: B = A+1; B + ([2, 4] U [6, 8]) + sage: A.intersection([A,B]) + ([2, 3] U [6, 7]) + """ + for R in L: + if R.is_empty(): + return R + return cls.join(L, all) + + def __or__(left, right): + r""" + Return the union of a two UnionOfIntervals instances. + + INPUT: + + - ``left``, ``right`` (UnionOfIntervals) -- two UnionOfIntervals instances + + OUTPUT: + + A new UnionOfIntervals instance representing the union of ``left`` and ``right``. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: A = UnionOfIntervals([1,3,5,7]); A + ([1, 3] U [5, 7]) + sage: B = A+1; B + ([2, 4] U [6, 8]) + sage: A | B + ([1, 4] U [5, 8]) + """ + return left.union([left, right]) + + def __and__(left, right): + r""" + Return the intersection of a two UnionOfIntervals instances. + + INPUT: + + - ``left``, ``right`` (UnionOfIntervals) -- two UnionOfIntervals instances + + OUTPUT: + + A new UnionOfIntervals instance representing the intersection of ``left`` and ``right``. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: A = UnionOfIntervals([1,3,5,7]); A + ([1, 3] U [5, 7]) + sage: B = A+1; B + ([2, 4] U [6, 8]) + sage: A & B + ([2, 3] U [6, 7]) + """ + return left.intersection([left, right]) + + def __contains__(self, x): + r""" + Return True if ``x`` is in the UnionOfIntervals. + + INPUT: + + - ``x`` (real) -- a real number + + OUTPUT: + + Boolean: True if and only if ``x`` is in the union of intervals. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: A = UnionOfIntervals([1,3,5,7]); A + ([1, 3] U [5, 7]) + sage: 1 in A + True + sage: 4 in A + False + sage: -infinity in A + False + sage: 'a' in A + False + """ + return x in self._endpoints or bisect.bisect_left(self._endpoints, x) % 2 == 1 + + def __str__(self): + r""" + Return the string representation of this UnionOfIntervals. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: A = UnionOfIntervals([1,3,5,7]) + sage: str(A) + '([1, 3] U [5, 7])' + """ + return repr(self) + + def __repr__(self): + r""" + Return the string representation of this UnionOfIntervals. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import UnionOfIntervals + sage: A = UnionOfIntervals([1,3,5,7]); A + ([1, 3] U [5, 7]) + """ + return "(%s)" % " U ".join(str(list(I)) for I in self.intervals()) + +def nonneg_region(f): + r""" + Returns the UnionOfIntervals representing the region where ``f`` is non-negative. + + INPUT: + + - ``f`` (polynomial) -- a univariate polynomial over `\RR`. + + OUTPUT: + + A UnionOfIntervals representing the set `\{x \in\RR mid f(x) \ge 0\}`. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import nonneg_region + sage: x = polygen(RR) + sage: nonneg_region(x^2-1) + ([-Infinity, -1.00000000000000] U [1.00000000000000, +Infinity]) + sage: nonneg_region(1-x^2) + ([-1.00000000000000, 1.00000000000000]) + sage: nonneg_region(1-x^3) + ([-Infinity, 1.00000000000000]) + sage: nonneg_region(x^3-1) + ([1.00000000000000, +Infinity]) + sage: nonneg_region((x-1)*(x-2)) + ([-Infinity, 1.00000000000000] U [2.00000000000000, +Infinity]) + sage: nonneg_region(-(x-1)*(x-2)) + ([1.00000000000000, 2.00000000000000]) + sage: nonneg_region((x-1)*(x-2)*(x-3)) + ([1.00000000000000, 2.00000000000000] U [3.00000000000000, +Infinity]) + sage: nonneg_region(-(x-1)*(x-2)*(x-3)) + ([-Infinity, 1.00000000000000] U [2.00000000000000, 3.00000000000000]) + sage: nonneg_region(x^4+1) + ([-Infinity, +Infinity]) + sage: nonneg_region(-x^4-1) + () + """ + roots = f.roots() + roots.sort() + sign_changes = [r for r,e in roots if e%2 == 1] + if (f.leading_coefficient() * (-1)**f.degree()) > 0: + sign_changes = [-infinity] + sign_changes + if f.leading_coefficient() > 0: + sign_changes += [infinity] + return UnionOfIntervals(sign_changes) + +def inf_max_abs(f, g, D): + r""" + Returns `\inf_D(\max(|f|, |g|))`. + + INPUT: + + - ``f``, ``g`` (polynomials) -- real univariate polynomaials + + - ``D`` (UnionOfIntervals) -- a subset of `\RR` + + OUTPUT: + + A real number approximating the value of `\inf_D(\max(|f|, |g|))`. + + ALGORITHM: + + The extreme values must occur at an endpoint of a subinterval of + `D` or at a point where one of `f`, `f'`, `g`, `g'`, `f\pm g` is + zero. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import inf_max_abs, UnionOfIntervals + sage: x = polygen(RR) + sage: f = (x-10)^4+1 + sage: g = 2*x^3+100 + sage: inf_max_abs(f,g,UnionOfIntervals([1,2,3,4,5,6])) + 425.638201706391 + sage: r0 = (f-g).roots()[0][0] + sage: r0 + 5.46053402234697 + sage: max(abs(f(r0)),abs(g(r0))) + 425.638201706391 + + """ + xs = f.roots() + f.derivative().roots() + xs += g.roots() + g.derivative().roots() + xs += (f-g).roots() + (f+g).roots() + xs = [r for r,e in xs if r in D] # ignore multiplicities and points outside D + xs += D.finite_endpoints() # include endpoints of intervals + if xs: + return min([max(abs(f(r)), abs(g(r))) for r in xs]) + return infinity + +def min_on_disk(f, tol, max_iter=10000): + r""" + Returns the minimum of a real-valued complex function on a square. + + INPUT: + + - ``f`` -- a function from CIF to RIF + + - ``tol`` (real) -- a positive real number + + - ``max_iter`` (integer, default 10000) -- a positive integer + bounding the number of iterations to be used + + OUTPUT: + + A 2-tuple `(s,t)`, where `t=f(s)` and `s` is a CIF element + contained in the disk `|z|\le1`, at which `f` takes its minumum + value. + + EXAMPLE:: + + sage: from sage.schemes.elliptic_curves.height import min_on_disk + sage: f = lambda x: (x^2+100).abs() + sage: s, t = min_on_disk(f, 0.0001) + sage: s, f(s), t + (0.01? + 1.00?*I, 99.01?, 99.0000000000000) + """ + # L holds a list of 4-tuples (mfs, ds, s, in_disk) where s is a + # subregion of the initial square, ds its relative diameter, + # mfs=-f(s) (actually minus the lower bound on f(s)) and in_disk + # is a flag indicating whether or not s is a subset of the unit + # disk. + + # We store the negative of the lower bound on f(s) so that we can + # use the bisect module to sort these 4-tuples. + + # Initially L contains one element, the whole unit box, which is + # not contained in the unit square. + + s = CIF(RIF(-1,1), RIF(-1,1)) + fs = f(s) + L = [(-fs.lower(), fs.relative_diameter(), s, False)] + + # min_max holds the minumum over L of fs.upper(). + + min_max = fs.upper() + + # We iterate at most max_iter times. At each step we look at the + # best so far and return it if is good enough, meaning that its + # relative diameter is less than the given tolerance; otherwise we + # bisect this best region (into 4 pieces) and replace the entry in + # L with at most 4 smaller entries. + + for k in range(max_iter): + value, err, region, in_disk = L.pop() + if err < tol: # reached desired tolerance, so return + return region, -value + for s in region.bisection(): # 4 sub-regions + if in_disk: + s_in_disk = True # if the original region si in the disk so are all its children + else: + r = abs(s) # otherwise we test each one + if r > 1: + continue # skip this subregion if it is entirely outside the disk + s_in_disk = r < 1 # meaning it is entirely inside the disk + + fs = f(s) + + if fs.upper() < min_max: # we definitely beat the record + min_max = fs.upper() + unneeded = bisect.bisect(L, (-min_max,)) + if unneeded > 100: # discard the worse entries (if there are many) + L = L[unneeded:] + + if fs.lower() < min_max: # we may beat the record, cannot yet tell: insert this region + # into the list at the appropriate palce to maintain sorting + bisect.insort(L, (-fs.lower(), fs.relative_diameter(), s, s_in_disk)) + + # If we get here, then even after max_iter iterations the tolerance has not been reached. + raise ValueError("too many iterations") + +two_pi_i_CDF = CDF(0, 2*RDF.pi()) +two_pi_i_CIF = CIF(0, 2*RIF.pi()) + +# Ideas: We know tau, so we know the direction of the diagonal. +# We can solve for x in p1, will this allow us to find the maxima exactly? + +def rat_term_CIF(z, try_strict=True): + r""" + Compute the value of `u/(1-u)^2` in ``CIF``, where `u=\exp(2\pi i z)`. + + INPUT: + + - ``z`` (complex) -- a CIF element + + - ``try_strict`` (bool) -- flag + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import rat_term_CIF + sage: z = CIF(0.5,0.2) + sage: rat_term_CIF(z) + -0.172467461182437? + 0.?e-16*I + sage: rat_term_CIF(z, False) + -0.172467461182437? + 0.?e-16*I + """ + two_pi_i_z = two_pi_i_CIF * z + r = (two_pi_i_z.real()).exp() # = |u| + x, y = two_pi_i_z.imag().cos(), two_pi_i_z.imag().sin() + + real_part = imag_part = None + + # If there are no local minima the intervals are strictly + # determined by their values at the endpoints. + + if try_strict: + + # evaluate the function at the four corners: + + corner_reals = [] + corner_imags = [] + for a, b in cartesian_product_iterator([z.real().endpoints(), z.imag().endpoints()]): + zz = CDF(a,b) + u = (two_pi_i_CDF*zz).exp() + f = u/(1-u)**2 + corner_reals.append(f.real()) + corner_imags.append(f.imag()) + + p1 = (((((r+2*x)*r - 6)*r + 2*x) * r) + 1) + # = r^4 + 2*r^3*x - 6*r^2 + 2*r*x + 1 + p2 = (r*(x*(r+2*x)-4)+x) + # = r^2*x + 2*r*x^2 - 4*r + x + + df_dr = (r**2-1) * p2 + df_dx = p1 * r + + dg_dr = p1 * y + dg_dx = r * df_dr / y + + if not dg_dr.contains_zero() or not dg_dx.contains_zero(): + real_part = RIF(min(corner_reals), max(corner_reals)) + + if not dg_dr.contains_zero() or not dg_dx.contains_zero(): + imag_part = RIF(min(corner_imags), max(corner_imags)) + + if real_part is None or imag_part is None: + denom = (1-r*(2*x-r))**2 + if real_part is None: + real_part = r*(x*(1+r**2)-2*r)/denom + if imag_part is None: + imag_part = -(r**2-1)*y*r/denom + + return CIF(real_part, imag_part) + +def eps(err, is_real): + r""" + Return a Real or Complex interval centered on 0 with radius err. + + INPUT: + + - ``err`` (real) -- a positive real number, the radius of the interval + + - ``is_real`` (boolean) -- if True, returns a real interval in + RIF, else a complex interval in CIF + + OUTPUT: + + An element of RIF or CIF (as specified), centered on 0, with given radius. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import eps + sage: eps(0.01, True) + 0.0? + sage: eps(0.01, False) + 0.0? + 0.0?*I + """ + e = RIF(-err, err) + if is_real: + return e + else: + return CIF(e, e) + + +class EllipticCurveCanonicalHeight: + r""" + Class for computing canonical heights of points on elliptic curves + defined over number fields, including rigorous lower bounds for + the canonical height of non-torsion points. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import EllipticCurveCanonicalHeight + sage: E = EllipticCurve([0,0,0,0,1]) + sage: EllipticCurveCanonicalHeight(E) + EllipticCurveCanonicalHeight object associated to Elliptic Curve defined by y^2 = x^3 + 1 over Rational Field + + Normally this object would be created like this:: + + sage: E.height_function() + EllipticCurveCanonicalHeight object associated to Elliptic Curve defined by y^2 = x^3 + 1 over Rational Field + """ + + def __init__(self, E): + r""" + Initialize the class with an elliptic curve. + + INPUT: + + - `E` -- an elliptic curve defined over a number field + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.height import EllipticCurveCanonicalHeight + sage: E = EllipticCurve([0,0,0,0,1]) + sage: EllipticCurveCanonicalHeight(E) + EllipticCurveCanonicalHeight object associated to Elliptic Curve defined by y^2 = x^3 + 1 over Rational Field + + An example over a number field:: + + sage: K.=QuadraticField(-1) + sage: E = EllipticCurve([0,i,0,i,i]) + sage: EllipticCurveCanonicalHeight(E) + EllipticCurveCanonicalHeight object associated to Elliptic Curve defined by y^2 = x^3 + i*x^2 + i*x + i over Number Field in i with defining polynomial x^2 + 1 + + TESTS: + + The base field must be a number field (or `\QQ`):: + + sage: from sage.schemes.elliptic_curves.height import EllipticCurveCanonicalHeight + sage: E = EllipticCurve(GF(7),[0,0,0,0,1]) + sage: EllipticCurveCanonicalHeight(E) + Traceback (most recent call last): + ... + ValueError: EllipticCurveCanonicalHeight class can only be created from an elliptic curve defined over a number field + """ + from sage.schemes.elliptic_curves.ell_generic import is_EllipticCurve + if is_EllipticCurve(E): + self.E = E + from sage.rings.number_field.number_field_base import is_NumberField + K = E.base_ring() + if is_NumberField(K): + self.K = K + else: + raise ValueError("EllipticCurveCanonicalHeight class can only be created from an elliptic curve defined over a number field") + else: + raise ValueError("EllipticCurveCanonicalHeight class can only be created from an elliptic curve") + + def __repr__(self): + r""" + Return the string representation. + + EXAMPLES:: + + sage: E = EllipticCurve([0,0,0,0,1]) + sage: E.height_function() + EllipticCurveCanonicalHeight object associated to Elliptic Curve defined by y^2 = x^3 + 1 over Rational Field + """ + return "EllipticCurveCanonicalHeight object associated to %s" % self.E + + def curve(self): + r""" + Return the elliptic curve. + + EXAMPLES:: + + sage: E = EllipticCurve([0,0,0,0,1]) + sage: H = E.height_function() + sage: H.curve() + Elliptic Curve defined by y^2 = x^3 + 1 over Rational Field + """ + return self.E + + def base_field(self): + r""" + Return the base field. + + EXAMPLES:: + + sage: E = EllipticCurve([0,0,0,0,1]) + sage: H = E.height_function() + sage: H.base_field() + Rational Field + """ + return self.K + + def __call__(self, P): + r""" + Return the canonical height of the point ``P``. + + INPUT: + + - ``P`` -- a point on the elliptic curve. + + OUTPUT: + + The canonical height of ``P``. + + EXAMPLES:: + + sage: E = EllipticCurve([0,0,1,-1,0]) + sage: P = E(0,0) + sage: P.height() + 0.0511114082399688 + sage: H = E.height_function() + sage: H(P) + 0.0511114082399688 + sage: H([0,0]) + 0.0511114082399688 + sage: H((0,0)) + 0.0511114082399688 + + Over a number field other than `\QQ`:: + + sage: K. = QuadraticField(-1) + sage: E = EllipticCurve(K, [0,0,0,1,-27]) + sage: H = E.height_function() + sage: H.base_field() + Number Field in i with defining polynomial x^2 + 1 + sage: H((1,5*i)) + 1.22257115164148 + """ + return self.E(P).height() + + @cached_method + def alpha(self, v, tol=0.01): + r""" + Return the constant `\alpha_v` associated to the embedding ``v``. + + INPUT: + + - ``v`` -- an embedding of the base field into `\RR` or `\CC` + + OUTPUT: + + The constant `\alpha_v`. In the notation of [CPS]_ (2006) and + [TT]_ (section 3.2), `\alpha_v^3=\epsilon_v`. The result is + cached since it only depends on the curve. + + EXAMPLES: + + Example 1 from [CPS]_ (2006):: + + sage: K.=QuadraticField(-1) + sage: E = EllipticCurve([0,0,0,1+5*i,3+i]) + sage: H = E.height_function() + sage: alpha = H.alpha(K.places()[0]) + sage: alpha + 1.12272013439355 + + Compare with `\log(\epsilon_v)=0.344562...` in [CPS]_:: + + sage: 3*alpha.log() + 0.347263296676126 + """ + from sage.rings.polynomial.polynomial_ring import polygen + b2, b4, b6, b8 = [v(b) for b in self.E.b_invariants()] + x = polygen(v.codomain()) + f = 4*x**3 + b2*x**2 + 2*b4*x + b6 + g = x**4 - b4*x**2 - 2*b6*x - b8 + F = f.reverse() << (4-f.degree()) + G = g.reverse() << (4-g.degree()) + + if v(self.K.gen()) in RR: + I = UnionOfIntervals([-1,1]) + min_fg = inf_max_abs(f, g, nonneg_region(f) & I) + min_FG = inf_max_abs(F, G, nonneg_region(F) & I) + return min(min_fg, min_FG) ** (-1/QQ(3)) + + else: + def pair_max(f, g): + f = f.change_ring(CIF) + g = g.change_ring(CIF) + max = type(RIF(0)).max + def max_f_g(z): + return max(abs(f(z)), abs(g(z))) + return max_f_g + pair_max_old = pair_max + def pair_max(f, g): + f = f.change_ring(CDF) + g = g.change_ring(CDF) + dfn = [fast_callable(f.derivative(n)/factorial(n), CDF) for n in range(f.degree()+1)] + dgn = [fast_callable(g.derivative(n)/factorial(n), CDF) for n in range(g.degree()+1)] + def max_f_g(s): + (a,b),(c,d) = s.real().endpoints(), s.imag().endpoints() + dx = a-b; dy = c-d + eta = RDF(dx*dx + dy*dy).sqrt() + z = CDF(s.center()) + err_f = sum(eta ** n * abs(df(z)) for n, df in enumerate(dfn) if n) + err_g = sum(eta ** n * abs(dg(z)) for n, dg in enumerate(dgn) if n) + return RIF(max(abs(f(z)), abs(g(z)))) + eps(max(err_f, err_g), True) + return max_f_g + _, min_fg = min_on_disk(pair_max(f, g), tol) + _, min_FG = min_on_disk(pair_max(F, G), tol) + return min(min_fg, min_FG) ** (-1/QQ(3)) + + @cached_method + def e_p(self, p): + r""" + Return the exponent of the group over the residue field at ``p``. + + INPUT: + + - ``p`` - a prime ideal of `K` (or a prime number if `K=\QQ`). + + OUTPUT: + + A positive integer `e_p`, the exponent of the group of + nonsingular points on the reduction of the elliptic curve + modulo `p`. The result is cached. + + EXAMPLES:: + + sage: K.=QuadraticField(-1) + sage: E = EllipticCurve([0,0,0,1+5*i,3+i]) + sage: H = E.height_function() + sage: H.e_p(K.prime_above(2)) + 2 + sage: H.e_p(K.prime_above(3)) + 10 + sage: H.e_p(K.prime_above(5)) + 9 + sage: E.conductor().norm().factor() + 2^10 * 20921 + sage: p1,p2 = K.primes_above(20921) + sage: E.local_data(p1) + Local data at Fractional ideal (40*i + 139): + Reduction type: good + ... + sage: H.e_p(p1) + 20815 + sage: E.local_data(p2) + Local data at Fractional ideal (-40*i + 139): + Reduction type: bad split multiplicative + ... + sage: H.e_p(p2) + 20920 + """ + kp = self.K.residue_field(p) + if self.E.has_bad_reduction(p): + if self.E.has_additive_reduction(p): + ep = kp.characteristic() + elif self.E.has_split_multiplicative_reduction(p): + ep = len(kp) - 1 + else: + ep = len(kp) + 1 + else: + ep = self.E.reduction(p).abelian_group().exponent() + return ZZ(ep) + + @cached_method + def DE(self, n): + r""" + Return the value `D_E(n)`. + + INPUT: + + - ``n`` (int) - a positive integer + + OUTPUT: + + The value `D_E(n)` as defined in [TT]_, section 4. + + EXAMPLES:: + + sage: K.=QuadraticField(-1) + sage: E = EllipticCurve([0,0,0,1+5*i,3+i]) + sage: H = E.height_function() + sage: [H.DE(n) for n in srange(1,6)] + [0, 2*log(5) + 2*log(2), 0, 2*log(13) + 2*log(5) + 4*log(2), 0] + """ + s = 0 + N = self.E.conductor() + B = (n+1) ** max(2, self.K.degree()) + for p in self.K.primes_of_bounded_norm_iter(B): + ep = self.e_p(p) + if ep.divides(n): + kp = self.K.residue_field(p) + s += 2*(1+(n/ep).valuation(kp.characteristic())) * log(len(kp)) + return s + + @cached_method + def ME(self): + r""" + Return the norm of the ideal `M_E`. + + OUTPUT: + + The norm of the ideal `M_E` as defined in [TT]_, section 3.1. + This is `1` if `E` is a global minimal model, and in general + measures the non-minimality of `E`. + + EXAMPLES:: + + sage: K.=QuadraticField(-1) + sage: E = EllipticCurve([0,0,0,1+5*i,3+i]) + sage: H = E.height_function() + sage: H.ME() + 1 + sage: E = EllipticCurve([0,0,0,0,1]) + sage: E.height_function().ME() + 1 + sage: E = EllipticCurve([0,0,0,0,64]) + sage: E.height_function().ME() + 4096 + sage: E.discriminant()/E.minimal_model().discriminant() + 4096 + """ + from sage.misc.misc import prod + if self.K is QQ: + return prod([p ** (e - self.E.local_data(p).discriminant_valuation()) for p, e in self.E.discriminant().factor()], QQ.one_element()) + + ME = prod([p.norm() ** (e - self.E.local_data(p).discriminant_valuation()) for p, e in self.K.ideal(self.E.discriminant()).factor()], QQ.one_element()) + return ME.norm() + + def B(self, n, mu): + r""" + Return the value `B_n(\mu)`. + + INPUT: + + - ``n`` (int) - a positive integer + + - ``mu`` (real) - a positive real number + + OUTPUT: + + The real value `B_n(\mu)` as defined in [TT]_, section 5. + + EXAMPLES: + + Example 10.2 from [TT]_:: + + sage: K.=QuadraticField(-1) + sage: E = EllipticCurve([0,1-i,i,-i,0]) + sage: H = E.height_function() + + In [TT]_ the value is given as 0.772:: + + sage: RealField(12)( H.B(5, 0.01) ) + 0.777 + """ + K = self.K + B = exp(K.degree() * n**2 * mu - RDF(self.DE(n))) / self.ME() ** 6 + for v in K.places(): + if v(K.gen()) in RR: + B *= self.alpha(v) + else: + B *= self.alpha(v) ** 2 + return B + + ###################################### + # Empty real intersection detection. # + ###################################### + + def psi(self, xi, v): + r""" + Return the normalised elliptic log of a point with this x-coordinate. + + INPUT: + + - ``xi`` (real) - the real x-coordinate of a point on the + curve in the connected component with respect to a real + embedding. + + - ``v`` (embedding) - a real embedding of the number field. + + OUTPUT: + + A real number in the interval [0.5,1] giving the elliptic + logarithm of a point on `E` with `x`-coordinate ``xi``, on the + connected component with respect to the embedding `v`, scaled + by the real period. + + EXAMPLES: + + An example over `\QQ`:: + + sage: E = EllipticCurve('389a') + sage: v = QQ.places()[0] + sage: L = E.period_lattice(v) + sage: P = E.lift_x(10/9) + sage: L(P) + 1.53151606047462 + sage: L(P) / L.real_period() + 0.615014189772115 + sage: H = E.height_function() + sage: H.psi(10/9,v) + 0.615014189772115 + + An example over a number field:: + + sage: K. = NumberField(x^3-2) + sage: E = EllipticCurve([0,0,0,0,a]) + sage: P = E.lift_x(1/3*a^2 + a + 5/3) + sage: v = K.real_places()[0] + sage: L = E.period_lattice(v) + sage: L(P) + 3.51086196882538 + sage: L(P) / L.real_period() + 0.867385122699931 + sage: xP = v(P.xy()[0]) + sage: H = E.height_function() + sage: H.psi(xP,v) + 0.867385122699931 + sage: H.psi(1.23,v) + 0.785854718241495 + """ + if xi > 1e9: + return 1 + L = self.E.period_lattice(v) + w1, w2 = L.basis() + from sage.schemes.elliptic_curves.constructor import EllipticCurve + ER = EllipticCurve([v(ai) for ai in self.E.a_invariants()]) + xP, yP = ER.lift_x(xi).xy() + t = L.e_log_RC(xP,yP) / w1 + if t < 0.5: + t = 1 - t + return t + + def S(self, xi1, xi2, v): + r""" + Return the union of intervals `S^{(v)}(\xi_1,\xi_2)`. + + INPUT: + + - ``xi1, xi2`` (real) - real numbers with `\xi_1\le\xi_2`. + + - ``v`` (embedding) - a real embedding of the field. + + OUTPUT: + + The union of intervals `S^{(v)}(\xi_1,\xi_2)` defined in [TT]_ + sectoin 6.1. + + EXAMPLES: + + An example over `\QQ`:: + + sage: E = EllipticCurve('389a') + sage: v = QQ.places()[0] + sage: H = E.height_function() + sage: H.S(2,3,v) + ([0.224512677391895, 0.274544821597130] U [0.725455178402870, 0.775487322608105]) + + An example over a number field:: + + sage: K. = NumberField(x^3-2) + sage: E = EllipticCurve([0,0,0,0,a]) + sage: v = K.real_places()[0] + sage: H = E.height_function() + sage: H.S(9,10,v) + ([0.0781194447253472, 0.0823423732016403] U [0.917657626798360, 0.921880555274653]) + """ + L = self.E.period_lattice(v) + w1, w2 = L.basis() + beta = L.elliptic_exponential(w1/2)[0] + if xi2 < beta: + return UnionOfIntervals([]) + elif xi1 < beta <= xi2: + a = self.psi(xi2, v) + return UnionOfIntervals([1-a, a]) + else: + a, b = self.psi(xi1, v), self.psi(xi2, v) + return UnionOfIntervals([1-b, 1-a, a, b]) + + def Sn(self, xi1, xi2, n, v): + r""" + Return the union of intervals `S_n^{(v)}(\xi_1,\xi_2)`. + + INPUT: + + - ``xi1, xi2`` (real) - real numbers with `\xi_1\le\xi_2`. + + - ``n`` (integer) - a positive integer. + + - ``v`` (embedding) - a real embedding of the field. + + OUTPUT: + + The union of intervals `S_n^{(v)}(\xi_1,\xi_2)` defined in [TT]_ + (Lemma 6.1). + + EXAMPLES: + + An example over `\QQ`:: + + sage: E = EllipticCurve('389a') + sage: v = QQ.places()[0] + sage: H = E.height_function() + sage: H.S(2,3,v) , H.Sn(2,3,1,v) + (([0.224512677391895, 0.274544821597130] U [0.725455178402870, 0.775487322608105]), + ([0.224512677391895, 0.274544821597130] U [0.725455178402870, 0.775487322608105])) + sage: H.Sn(2,3,6,v) + ([0.0374187795653158, 0.0457574702661884] U [0.120909196400478, 0.129247887101351] U [0.204085446231982, 0.212424136932855] U [0.287575863067145, 0.295914553768017] U [0.370752112898649, 0.379090803599522] U [0.454242529733812, 0.462581220434684] U [0.537418779565316, 0.545757470266188] U [0.620909196400478, 0.629247887101351] U [0.704085446231982, 0.712424136932855] U [0.787575863067145, 0.795914553768017] U [0.870752112898649, 0.879090803599522] U [0.954242529733812, 0.962581220434684]) + + An example over a number field:: + + sage: K. = NumberField(x^3-2) + sage: E = EllipticCurve([0,0,0,0,a]) + sage: v = K.real_places()[0] + sage: H = E.height_function() + sage: H.S(2,3,v) , H.Sn(2,3,1,v) + (([0.142172065860075, 0.172845716928584] U [0.827154283071416, 0.857827934139925]), + ([0.142172065860075, 0.172845716928584] U [0.827154283071416, 0.857827934139925])) + sage: H.Sn(2,3,6,v) + ([0.0236953443100124, 0.0288076194880974] U [0.137859047178569, 0.142971322356654] U [0.190362010976679, 0.195474286154764] U [0.304525713845236, 0.309637989023321] U [0.357028677643346, 0.362140952821431] U [0.471192380511903, 0.476304655689988] U [0.523695344310012, 0.528807619488097] U [0.637859047178569, 0.642971322356654] U [0.690362010976679, 0.695474286154764] U [0.804525713845236, 0.809637989023321] U [0.857028677643346, 0.862140952821431] U [0.971192380511903, 0.976304655689988]) + """ + SS = 1/ZZ(n) * self.S(xi1, xi2, v) + return UnionOfIntervals.union([t/ZZ(n) + SS for t in range(n)]) + + def real_intersection_is_empty(self, Bk, v): + r""" + Returns True iff an intersection of `S_n^{(v)}` sets is empty. + + INPUT: + + - ``Bk`` (list) - a list of reals. + + - ``v`` (embedding) - a real embedding of the number field. + + OUTPUT: + + True or False, according as the intersection of the unions of + intervals `S_n^{(v)}(-b,b)` for `b` in the list ``Bk`` is + empty or not. When ``Bk`` is the list of `b=B_n(\mu)` for + `n=1,2,3,\dots` for some `\mu>0` this means that all + non-torsion points on `E` with everywhere good reduction have + canonical height strictly greater than `\mu`, by [TT]_, + Proposition 6.2. + + EXAMPLES: + + An example over `\QQ`:: + + sage: E = EllipticCurve('389a') + sage: v = QQ.places()[0] + sage: H = E.height_function() + + The following two lines prove that the heights of non-torsion + points on `E` with everywhere good reduction have canonical + height strictly greater than 0.2, but fail to prove the same + for 0.3:: + + sage: H.real_intersection_is_empty([H.B(n,0.2) for n in srange(1,10)],v) + True + sage: H.real_intersection_is_empty([H.B(n,0.3) for n in srange(1,10)],v) + False + + An example over a number field:: + + sage: K. = NumberField(x^3-2) + sage: E = EllipticCurve([0,0,0,0,a]) + sage: v = K.real_places()[0] + sage: H = E.height_function() + + The following two lines prove that the heights of non-torsion + points on `E` with everywhere good reduction have canonical + height strictly greater than 0.07, but fail to prove the same + for 0.08:: + + sage: H.real_intersection_is_empty([H.B(n,0.07) for n in srange(1,5)],v) # long time (3.3s) + True + sage: H.real_intersection_is_empty([H.B(n,0.08) for n in srange(1,5)],v) + False + """ + return UnionOfIntervals.intersection([self.Sn(-B, B, k+1, v) for k,B in enumerate(Bk)]).is_empty() + + ######################################## + # Empty complex intersection detection.# + ######################################## + + def tau(self, v): + r""" + Return the normalised upper half-plane parameter `\tau` for + the period lattice with respect to the embedding `v`. + + INPUT: + + - ``v`` (embedding) - a real or complex embedding of the number field. + + OUTPUT: + + (Complex) `\tau = \omega_1/\omega_2` in the fundamental region + of the upper half-plane. + + EXAMPLES:: + + sage: E = EllipticCurve('37a') + sage: H = E.height_function() + sage: H.tau(QQ.places()[0]) + 1.22112736076463*I + """ + return self.E.period_lattice(v).tau() + + def wp_c(self, v): + r""" + Return a bound for the Weierstrass `\wp`-function. + + INPUT: + + - ``v`` (embedding) - a real or complex embedding of the number field. + + OUTPUT: + + (Real) `c>0` such that + + .. math:: + + |\wp(z) - z^-2| \le \frac{c^2|z|^2}{1-c|z|^2} + + whenever `c|z|^2<1`. Given the recurrence relations for the + Laurent series expansion of `\wp`, it is easy to see that + there is such a constant `c`. [Reference?] + + EXAMPLES:: + + sage: E = EllipticCurve('37a') + sage: H = E.height_function() + sage: H.wp_c(QQ.places()[0]) + 2.68744508779950 + + sage: K.=QuadraticField(-1) + sage: E = EllipticCurve([0,0,0,1+5*i,3+i]) + sage: H = E.height_function() + sage: H.wp_c(K.places()[0]) + 2.66213425640096 + """ + # Note that we normalise w1, w2 differently from [TT]_! + w2, w1 = self.E.period_lattice(v).normalised_basis() + return max(abs(v(self.E.c4()/240)) ** 0.5, + abs(v(self.E.c6()/6048)) ** (1.0/3)) * abs(w1)**2 + + def fk_intervals(self, v=None, N=20, domain=CIF): + r""" + Return a function approximating the Weierstrass function, with error. + + INPUT: + + - ``v`` (embedding) - an embedding of the number field. If + None (default) use the real embedding if the field is `\QQ` + and raise an error for other fields. + + - ``N`` (int) - The number of terms to use in the + `q`-expansion of `\wp`. + + - ``domain`` (complex field) - the model of `\CC` to use, for + example ``CDF`` of ``CIF`` (default). + + OUTPUT: + + A pair of functions fk, err which can be evaluated at complex + numbers `z` (in the correct ``domain``) to give an + approximation to `\wp(z)` and an upper bound on the error, + respectively. The Weierstrass function returned is with + respect to the normalised lattice `[1,\tau]` associated to the + given embedding. + + EXAMPLES:: + + sage: E = EllipticCurve('37a') + sage: L = E.period_lattice() + sage: w1, w2 = L.normalised_basis() + sage: z = CDF(0.3, 0.4) + + Compare the value give by the standard elliptic exponential + (scaled since ``fk`` is with respect to the normalised + lattice):: + + sage: L.elliptic_exponential(z*w2, to_curve=False)[0] * w2 ** 2 + -1.82543539306049 - 2.49336319992847*I + + to the value given by this function, and see the error:: + + sage: fk, err = E.height_function().fk_intervals(N=10) + sage: fk(CIF(z)) + -1.82543539306049? - 2.49336319992847?*I + sage: err(CIF(z)) + 2.71750621458744e-31 + + The same, but in the domain ``CDF`` instad of ``CIF``:: + + sage: fk, err = E.height_function().fk_intervals(N=10, domain=CDF) + sage: fk(z) + -1.82543539306 - 2.49336319993*I + """ + if v is None: + if self.K is QQ: + v = QQ.hom(RR) + else: + raise ValueError("must specify embedding") + # pre-compute some constants + tau = self.tau(v) + const_term = 1/CC(12) + qn = q = (2 * CC.gen() * CC.pi() * tau).exp() + for n in range(1, N): + const_term -= 2 * qn/(1-qn) ** 2 + qn *= q + + two_pi_i = 2 * domain.gen() * domain.pi() + neg_four_pi2 = -4 * domain.pi() ** 2 + const_term = domain(const_term) + tau = domain(tau) + + abs_q = abs(domain(q)) + abs_qN = abs(domain(qn)) + err_factor = abs(neg_four_pi2) / (1-abs_q) + err_term = 2*abs_qN/(1-abs_qN) ** 2 + + # choose u/(1-u)^2 evaluation method + if domain is CIF: + rat_term = rat_term_CIF + else: + def rat_term(z): + u = (two_pi_i*z).exp() + return u/(1-u)**2 + + # the actual series + def fk(z): + return (const_term + + sum([rat_term(z+n*tau) for n in range(1-N,N)]) + ) * neg_four_pi2 + + # the error function + def err(z): + alpha = z.imag() / tau.imag() + qNa = abs_q**(N+alpha) + qNai = abs_q**(N-alpha) + return (err_factor * (qNa/(1-qNa) ** 2 + qNai/(1-qNai) ** 2 + err_term)).upper() + + return fk, err + + @cached_method + def wp_intervals(self, v=None, N=20, abs_only=False): + r""" + Return a function approximating the Weierstrass function. + + INPUT: + + - ``v`` (embedding) - an embedding of the number field. If + None (default) use the real embedding if the field is `\QQ` + and raise an error for other fields. + + - ``N`` (int, default 20) - The number of terms to use in the + `q`-expansion of `\wp`. + + - ``abs_only`` (boolean, default False) - flag to determine + whether (if True) the error adjustment should use the + absolute value or (if False) the real and imaginary parts. + + OUTPUT: + + A function wp which can be evaluated at complex numbers `z` to + give an approximation to `\wp(z)`. The Weierstrass function + returned is with respect to the normalised lattice `[1,\tau]` + associated to the given embedding. For `z` which are not near + a lattice point the function ``fk`` is used, otherwise a + better approximation is used. + + EXAMPLES:: + + sage: E = EllipticCurve('37a') + sage: wp = E.height_function().wp_intervals() + sage: z = CDF(0.3, 0.4) + sage: wp(CIF(z)) + -1.82543539306049? - 2.4933631999285?*I + + sage: L = E.period_lattice() + sage: w1, w2 = L.normalised_basis() + sage: L.elliptic_exponential(z*w2, to_curve=False)[0] * w2^2 + -1.82543539306049 - 2.49336319992847*I + + sage: z = CDF(0.3, 0.1) + sage: wp(CIF(z)) + 8.5918243572165? - 5.4751982004351?*I + sage: L.elliptic_exponential(z*w2, to_curve=False)[0] * w2^2 + 8.59182435721650 - 5.47519820043503*I + """ + if v is None: + if self.K is QQ: + v = QQ.hom(RR) + else: + raise ValueError("must specify embedding") + + tau = self.tau(v) + fk, fk_err = self.fk_intervals(v, N) + c = self.wp_c(v) + + def wp(z): + + # center around origin + offset = (z.imag().lower() / tau.imag()).round() + if offset: + z -= CIF(offset * tau) + offset = z.real().lower().round() + if offset: + z -= offset + + # estimate using the series + approx = fk(z) + err = fk_err(z) + if abs_only: + approx = abs(approx) + approx += eps(err, abs_only) + # print "fk_approx", approx + + # refine using an estimate that's better near the pole + z_bound = abs(z).upper() + cz2 = c * z_bound ** 2 + if cz2 < 1: + err = (c * cz2) / (1 - cz2) + if abs_only: + pole_approx = abs(z) ** -2 + else: + pole_approx = z ** -2 + # print "pole approx", pole_approx + eps(err, abs_only) + # print approx in approx.intersection(pole_approx + eps(err, abs_only)) + approx = approx.intersection(pole_approx + eps(err, abs_only)) + + return approx + + return wp + + @cached_method + def wp_on_grid(self, v, N, half=False): + r""" + Return an array of the values of `\wp` on an `N\times N` grid. + + INPUT: + + - ``v`` (embedding) - an embedding of the number field. + + - ``N`` (int) - The number of terms to use in the + `q`-expansion of `\wp`. + + - ``half`` (boolean, default False) - if True, use an array of + size `N\times N/2` instead of `N\times N`. + + OUTPUT: + + An array of size either `N\times N/2` or `N\times N` whose + `(i,j)` entry is the value of the Weierstrass `\wp`-function + at `(i+.5)/N + (j+.5)*\tau/N`, a grid of points in the + fundamental region for the lattice `[1,\tau]`. + + EXAMPLES:: + + sage: E = EllipticCurve('37a') + sage: H = E.height_function() + sage: v = QQ.places()[0] + + The array of values on the grid shows symmetry, since `\wp` is + even:: + + sage: H.wp_on_grid(v,4) + array([[ 25.43920182, 5.28760943, 5.28760943, 25.43920182], + [ 6.05099485, 1.83757786, 1.83757786, 6.05099485], + [ 6.05099485, 1.83757786, 1.83757786, 6.05099485], + [ 25.43920182, 5.28760943, 5.28760943, 25.43920182]]) + + The array of values on the half-grid:: + + sage: H.wp_on_grid(v,4,True) + array([[ 25.43920182, 5.28760943], + [ 6.05099485, 1.83757786], + [ 6.05099485, 1.83757786], + [ 25.43920182, 5.28760943]]) + """ + tau = self.tau(v) + fk, err = self.fk_intervals(v, 15, CDF) + var_z = SR.var('z') + ff = fast_callable(fk(var_z), CDF, [var_z]) + N_or_half = N // (1+half) # array is NxN or Nx(N/2) + vals = numpy.empty((N,N_or_half)) # empty array tp hold values + for i in range(N): + for j in range(N_or_half): + vals[i,j] = abs(ff((i+.5)/N + (j+.5)*tau/N)) + return vals + + def complex_intersection_is_empty(self, Bk, v, verbose=False, use_half=True): + r""" + Returns True iff an intersection of `T_n^{(v)}` sets is empty. + + INPUT: + + - ``Bk`` (list) - a list of reals. + + - ``v`` (embedding) - a complex embedding of the number field. + + - ``verbose`` (boolean, default False) - verbosity flag. + + - ``use_half`` (boolean, default False) - if True, use only half + the fundamental region. + + OUTPUT: + + True or False, according as the intersection of the unions of + intervals `T_n^{(v)}(-b,b)` for `b` in the list ``Bk`` (see + [TT]_, section 7) is empty or not. When ``Bk`` is the list of + `b=\sqrt{B_n(\mu)}` for `n=1,2,3,\dots` for some `\mu>0` this + means that all non-torsion points on `E` with everywhere good + reduction have canonical height strictly greater than `\mu`, + by [TT]_, Proposition 7.8. + + EXAMPLES:: + + sage: K. = NumberField(x^3-2) + sage: E = EllipticCurve([0,0,0,0,a]) + sage: v = K.complex_embeddings()[0] + sage: H = E.height_function() + + The following two lines prove that the heights of non-torsion + points on `E` with everywhere good reduction have canonical + height strictly greater than 0.02, but fail to prove the same + for 0.03. For the first proof, using only `n=1,2,3` is not + sufficient:: + + sage: H.complex_intersection_is_empty([H.B(n,0.02) for n in [1,2,3]],v) # long time (~6s) + False + sage: H.complex_intersection_is_empty([H.B(n,0.02) for n in [1,2,3,4]],v) + True + sage: H.complex_intersection_is_empty([H.B(n,0.03) for n in [1,2,3,4]],v) # long time (4s) + False + + Using `n\le6` enables us to prove the lower bound 0.03. Note + that it takes longer when the result is ``False`` than when it + is ``True``:: + + sage: H.complex_intersection_is_empty([H.B(n,0.03) for n in [1..6]],v) + True + """ + b2 = v(self.E.b2()) + # Note that we normalise w1, w2 differently from [TT]_! + w2, w1 = self.E.period_lattice(v).normalised_basis() + tau = w2/w1 + bounds = [RDF((B.sqrt() + abs(b2)/12) * abs(w1) ** 2) for B in Bk] + vals = self.wp_on_grid(v, 30, half=use_half) + wp = self.wp_intervals(v, abs_only=True) + + k = len(bounds) + + # First try and prove a negative result (cheap). + if verbose: + print "trying to prove negative result..." + intersection = None + for B, n in sorted(zip(bounds, ZZ.range(1, k+1))): + T = PeriodicRegion(CDF(1), CDF(tau), vals < B, full=not use_half) + if intersection is None: + intersection = PeriodicRegion(CDF(1), CDF(tau), vals < B, full=not use_half) + else: + intersection &= T/n + if intersection.is_empty(): + break + else: + z = CIF(intersection.innermost_point()) + if all(wp((k+1)*z) < B for k, B in enumerate(bounds)): + return False + + # Now try to prove a positive result. + if verbose: + print "trying to prove positive result..." + intersection = None + for B, n in sorted(zip(bounds, ZZ.range(1, k+1))): + + T = PeriodicRegion(CDF(1), CDF(tau), vals < B, full=not use_half).expand().refine() + leaning_right = tau.real() / tau.imag() >= 0 + def check_line(z): + wpz = wp(z) + if wpz > B: + return True + # Try refining once before we declare failure. + z00, z01, z10, z11 = z.bisection() + if leaning_right: + start, end = z00, z11 + else: + start, end = z01, z10 + if wp(start) > B and wp(end) > B: + return True + return False + + # This step here is the bottleneck. + while not T.verify(check_line): + if verbose: + print "bad" + T = T.expand() + if intersection is None: + intersection = T + else: + intersection &= T/n + if intersection.is_empty(): + return True + + return False + + def test_mu(self, mu, N, verbose=True): + r""" + Return ``True`` if we can prove that `\mu` is a lower bound. + + INPUT: + + - ``mu`` (real) - a positive real number + + - ``N`` (integer) - upper bounf do the multiples to be used. + + - ``verbose`` (boolean, default True) - verbosity flag. + + OUTPUT: + + ``True`` or ``False``, according to whether we succeed in + proving that `\mu` is a lower bound for the canonical heights + of points of infinite order with everywhere good reduction. + + .. note:: + + A ``True`` result is rigorous; ``False`` only means that + the attempt failed: trying again with larger `N` may yield + ``True``. + + EXAMPLE:: + + sage: K. = NumberField(x^3-2) + sage: E = EllipticCurve([0,0,0,0,a]) + sage: H = E.height_function() + + This curve does have a point of good reduction whose canonical + point is approximately 1.68:: + + sage: P = E.gens()[0] + sage: P.height() + 1.68038085233673 + sage: P.has_good_reduction() + True + + Using `N=5` we can prove that 0.1 is a lower bound (in fact we + only need `N=2`), but not that 0.2 is:: + + sage: H.test_mu(0.1, 5) + B_1(0.100000000000000) = 1.51580969677387 + B_2(0.100000000000000) = 0.932072561526720 + True + sage: H.test_mu(0.2, 5) + B_1(0.200000000000000) = 2.04612906979932 + B_2(0.200000000000000) = 3.09458988474327 + B_3(0.200000000000000) = 27.6251108409484 + B_4(0.200000000000000) = 1036.24722370223 + B_5(0.200000000000000) = 3.67090854562318e6 + False + + Since 0.1 is a lower bound we can deduce that the point `P` is + either primitive or divisible by either 2 or 3. In fact it is + primitive:: + + sage: (P.height()/0.1).sqrt() + 4.09924487233530 + sage: P.division_points(2) + [] + sage: P.division_points(3) + [] + """ + # Compute the list of values `B_n(\mu)` for n in 1..N. If any + # of these is 1 we can return True right away (see [TT]_, + # Proposition 5.1). + Bk = [] + for n in ZZ.range(1, N+1): + b = self.B(n, mu) + if verbose: + print "B_%s(%s) = %s" % (n, mu, b) + if b < 1: + return True + Bk.append(b) + + # Each real or complex embedding of the number field gives us + # a chance to prove the lower bound. We try each in turn, + # stopping if one gives a True result. + + for v in self.K.places(): + if v(self.K.gen()) in RR: + if self.real_intersection_is_empty(Bk, v): + return True + else: + if self.complex_intersection_is_empty(Bk, v): + return True + return False # Couldn't prove it... + + def min_gr(self, tol, n_max, verbose=False): + r""" + Returns a lower bound for points of infinite order with good reduction. + + INPUT: + + - ``tol`` - tolerance in output (see below). + + - ``n_max`` - how many multiples to use in iteration. + + - ``verbose`` (boolean, default False) - verbosity flag. + + OUTPUT: + + A positive real `\mu` for which it has been established + rigorously that every point of infinite order on the elliptic + curve (defined over its ground field), which has good + reduction at all primes, has canonical height greater than + `\mu`, and such that it is not possible (at least without + increasing ``n_max``) to prove the same for + `\mu\cdot\text{tol}`. + + EXAMPLES: + + Example 1 from [CS]_ (where a lower bound of 1.9865 was + given):: + + sage: E = EllipticCurve([1, 0, 1, 421152067, 105484554028056]) # 60490d1 + sage: E.height_function().min_gr(.0001, 5) + 1.98684388147 + + Example 10.1 from [TT]_ (where a lower bound of 0.18 was + given):: + + sage: K. = QuadraticField(-1) + sage: E = EllipticCurve([0,0,0,91-26*i,-144-323*i]) + sage: H = E.height_function() + sage: H.min_gr(0.1,4) # long time (8.1s) + 0.162104944331 + + Example 10.2 from [TT]_:: + + sage: K. = QuadraticField(-1) + sage: E = EllipticCurve([0,1-i,i,-i,0]) + sage: H = E.height_function() + sage: H.min_gr(0.01,5) + 0.0150437964347 + + In this example the point `P=(0,0)` has height 0.023 so our + lower bound is quite good:: + + sage: P = E((0,0)) + sage: P.has_good_reduction() + True + sage: P.height() + 0.0230242154471211 + + Example 10.3 from [TT]_ (where the same bound of 0.25 is + given):: + + sage: K. = NumberField(x^3-2) + sage: E = EllipticCurve([0,0,0,-3*a-a^2,a^2]) + sage: H = E.height_function() + sage: H.min_gr(0.1,5) # long time (7.2s) + 0.25 + + """ + test = self.test_mu + if test(1, n_max, verbose): + mu = 2 + while test(mu, n_max, False): + mu *= 2 + mu /= 2 + else: + mu = .5 + while not test(mu, n_max, False): + mu /= 2 + # The true value lies between mu and eps * mu. + eps = 2.0 + while eps > tol+1: + if verbose: + print "height bound in [%s, %s]" % (mu, mu*eps) + eps = math.sqrt(eps) + if test(mu*eps, n_max, False): + mu = mu*eps + return RDF(mu) + + def min(self, tol, n_max, verbose=False): + r""" + Returns a lower bound for all points of infinite order. + + INPUT: + + - ``tol`` - tolerance in output (see below). + + - ``n_max`` - how many multiples to use in iteration. + + - ``verbose`` (boolean, default False) - verbosity flag. + + OUTPUT: + + A positive real `\mu` for which it has been established + rigorously that every point of infinite order on the elliptic + curve (defined over its ground field) has canonical height + greater than `\mu`, and such that it is not possible (at least + without increasing ``n_max``) to prove the same for + `\mu\cdot\text{tol}`. + + EXAMPLES: + + Example 1 from [CS]_ (where the same lower bound of 0.1126 was + given):: + + sage: E = EllipticCurve([1, 0, 1, 421152067, 105484554028056]) # 60490d1 + sage: E.height_function().min(.0001, 5) + 0.00112632873099 + + Example 10.1 from [TT]_ (where a lower bound of 0.18 was + given):: + + sage: K. = QuadraticField(-1) + sage: E = EllipticCurve([0,0,0,91-26*i,-144-323*i]) + sage: H = E.height_function() + sage: H.min(0.1,4) # long time (8.1s) + 0.162104944331 + + Example 10.2 from [TT]_:: + + sage: K. = QuadraticField(-1) + sage: E = EllipticCurve([0,1-i,i,-i,0]) + sage: H = E.height_function() + sage: H.min(0.01,5) # long time (4s) + 0.0150437964347 + + In this example the point `P=(0,0)` has height 0.023 so our + lower bound is quite good:: + + sage: P = E((0,0)) + sage: P.height() + 0.0230242154471211 + + Example 10.3 from [TT]_ (where the same bound of 0.0625 is + given):: + + sage: K. = NumberField(x^3-2) + sage: E = EllipticCurve([0,0,0,-3*a-a^2,a^2]) + sage: H = E.height_function() + sage: H.min(0.1,5) # long time (7s) + 0.0625 + + More examples over `\QQ`:: + + sage: E = EllipticCurve('37a') + sage: h = E.height_function() + sage: h.min(.01, 5) + 0.0398731805749 + sage: E.gen(0).height() + 0.0511114082399688 + + After base change the lower bound can decrease:: + + sage: K. = QuadraticField(-5) + sage: E.change_ring(K).height_function().min(0.5, 10) # long time (8s) + 0.0441941738242 + + sage: E = EllipticCurve('389a') + sage: h = E.height_function() + sage: h.min(0.1, 5) + 0.0573127527003 + sage: [P.height() for P in E.gens()] + [0.686667083305587, 0.327000773651605] + + """ + # The lcm of the exponents of all the component groups at + # finite places (allowing for everywhere good reduction!) + tp = lcm([L.tamagawa_exponent() for L in self.E.local_data()] + [ZZ(1)]) + + # Include infinite places: + if tp%2==1: + if self.K == QQ: + if self.E.real_components()==2: + tp*=2 + elif any([v(self.E.discriminant()>0) + for v in self.K.real_places()]): + tp *=2 + # Now tp is such that tp*P has good reduction at all places + # for all points P: + return self.min_gr(tol, n_max, verbose) / tp ** 2 diff --git a/src/sage/schemes/elliptic_curves/period_lattice.py b/src/sage/schemes/elliptic_curves/period_lattice.py index 227b32f6ff8..7025cc01b22 100644 --- a/src/sage/schemes/elliptic_curves/period_lattice.py +++ b/src/sage/schemes/elliptic_curves/period_lattice.py @@ -77,6 +77,13 @@ Defn: a |--> -2.645751311064591?*I +REFERENCES: + +.. [CT] J. E. Cremona and T. Thongjunthug, The Complex AGM, periods of + elliptic curves over $\CC$ and complex elliptic logarithms. + Journal of Number Theory Volume 133, Issue 8, August 2013, pages + 2813-2841. + AUTHORS: @@ -485,6 +492,60 @@ def normalised_basis(self, prec=None, algorithm='sage'): periods, mat = normalise_periods(w1,w2) return periods + @cached_method + def tau(self, prec=None, algorithm='sage'): + r""" + Return the upper half-plane parameter in the fundamental region. + + INPUT: + + - ``prec`` (default: ``None``) -- precision in bits (default + precision if ``None``). + + - ``algorithm`` (string, default 'sage') -- choice of + implementation (for real embeddings only) between 'sage' + (native Sage implementation) or 'pari' (use the PARI + library: only available for real embeddings). + + OUTPUT: + + (Complex) `\tau = \omega_1/\omega_2` where the lattice has the + form `\ZZ\omega_1 + \ZZ\omega_2`, normalised so that `\tau = + \omega_1/\omega_2` is in the fundamental region of the upper + half-plane. + + EXAMPLES:: + + sage: E = EllipticCurve('37a') + sage: L = E.period_lattice() + sage: L.tau() + 1.22112736076463*I + + :: + + sage: K. = NumberField(x^3-2) + sage: emb = K.embeddings(RealField())[0] + sage: E = EllipticCurve([0,1,0,a,a]) + sage: L = E.period_lattice(emb) + sage: tau = L.tau(); tau + -0.338718341018919 + 0.940887817679340*I + sage: tau.abs() + 1.00000000000000 + sage: -0.5 <= tau.real() <= 0.5 + True + + sage: emb = K.embeddings(ComplexField())[0] + sage: L = E.period_lattice(emb) + sage: tau = L.tau(); tau + 0.387694505032876 + 1.30821088214407*I + sage: tau.abs() + 1.36444961115933 + sage: -0.5 <= tau.real() <= 0.5 + True + """ + w1, w2 = self.normalised_basis(prec=prec, algorithm=algorithm) + return w1/w2 + @cached_method def _compute_periods_real(self, prec=None, algorithm='sage'): r""" @@ -1147,6 +1208,199 @@ def reduce(self, z): else: return C(z.real(),0) + def e_log_RC(self, xP, yP, prec=None, reduce=True): + r""" + Return the elliptic logarithm of a real or complex point. + + - ``xP, yP`` (real or complex) -- Coordinates of a point on + the embedded elliptic curve associated with this period + lattice. + + - ``prec`` (default: ``None``) -- real precision in bits + (default real precision if None). + + - ``reduce`` (default: ``True``) -- if ``True``, the result + is reduced with respect to the period lattice basis. + + OUTPUT: + + (complex number) The elliptic logarithm of the point `(xP,yP)` + with respect to this period lattice. If `E` is the elliptic + curve and `\sigma:K\to\CC` the embedding, the the returned + value `z` is such that `z\pmod{L}` maps to `(xP,yP)=\sigma(P)` + under the standard Weierstrass isomorphism from `\CC/L` to + `\sigma(E)`. If ``reduce`` is ``True``, the output is reduced + so that it is in the fundamental period parallelogram with + respect to the normalised lattice basis. + + ALGORITHM: + + Uses the complex AGM. See [CT]_ for details. + + EXAMPLES:: + + sage: E = EllipticCurve('389a') + sage: L = E.period_lattice() + sage: P = E([-1,1]) + sage: xP, yP = [RR(c) for c in P.xy()] + + The elliptic log from the real coordinates:: + + sage: L.e_log_RC(xP, yP) + 0.479348250190219 + 0.985868850775824*I + + The same elliptic log from the algebraic point:: + + sage: L(P) + 0.479348250190219 + 0.985868850775824*I + + A number field example:: + + sage: K. = NumberField(x^3-2) + sage: E = EllipticCurve([0,0,0,0,a]) + sage: v = K.real_places()[0] + sage: L = E.period_lattice(v) + sage: P = E.lift_x(1/3*a^2 + a + 5/3) + sage: L(P) + 3.51086196882538 + sage: xP, yP = [v(c) for c in P.xy()] + sage: L.e_log_RC(xP, yP) + 3.51086196882538 + + Elliptic logs of real points which do not come from algebraic + points:: + + sage: ER = EllipticCurve([v(ai) for ai in E.a_invariants()]) + sage: P = ER.lift_x(12.34) + sage: xP, yP = P.xy() + sage: xP, yP + (12.3400000000000, 43.3628968710567) + sage: L.e_log_RC(xP, yP) + 3.76298229503967 + sage: xP, yP = ER.lift_x(0).xy() + sage: L.e_log_RC(xP, yP) + 2.69842609082114 + + Elliptic logs of complex points:: + + sage: v = K.complex_embeddings()[0] + sage: L = E.period_lattice(v) + sage: P = E.lift_x(1/3*a^2 + a + 5/3) + sage: L(P) + 1.68207104397706 - 1.87873661686704*I + sage: xP, yP = [v(c) for c in P.xy()] + sage: L.e_log_RC(xP, yP) + 1.68207104397706 - 1.87873661686704*I + sage: EC = EllipticCurve([v(ai) for ai in E.a_invariants()]) + sage: xP, yP = EC.lift_x(0).xy() + sage: L.e_log_RC(xP, yP) + 1.03355715602040 - 0.867257428417356*I + """ + if prec is None: + prec = RealField().precision() + # Note: using log2(prec) + 3 guard bits is usually enough. + # To avoid computing a logarithm, we use 40 guard bits which + # should be largely enough in practice. + prec2 = prec + 40 + + R = RealField(prec2) + C = ComplexField(prec2) + pi = R.pi() + e1,e2,e3 = self._ei + a1,a2,a3 = [self.embedding(a) for a in self.E.ainvs()[:3]] + + wP = 2*yP+a1*xP+a3 + + # We treat the case of 2-torsion points separately. (Note + # that Cohen's algorithm does not handle these properly.) + + if wP.is_zero(): # 2-torsion treated separately + w1,w2 = self._compute_periods_complex(prec,normalise=False) + if xP==e1: + z = w2/2 + else: + if xP==e3: + z = w1/2 + else: + z = (w1+w2)/2 + if reduce: + z = self.reduce(z) + return z + + # NB The first block of code works fine for real embeddings as + # well as complex embeddings. The special code for real + # embeddings uses only real arithmetic in the iteration, and is + # based on Cremona and Thongjunthug. + + # An older version, based on Cohen's Algorithm 7.4.8 also uses + # only real arithmetic, and gives different normalisations, + # but also causes problems (see #10026). It is left in but + # commented out below. + + if self.real_flag==0: # complex case + + a = C((e1-e3).sqrt()) + b = C((e1-e2).sqrt()) + if (a+b).abs() < (a-b).abs(): b=-b + r = C(((xP-e3)/(xP-e2)).sqrt()) + if r.real()<0: r=-r + t = -C(wP)/(2*r*(xP-e2)) + # eps controls the end of the loop. Since we aim at a target + # precision of prec bits, eps = 2^(-prec) is enough. + eps = R(1) >> prec + while True: + s = b*r+a + a, b = (a+b)/2, (a*b).sqrt() + if (a+b).abs() < (a-b).abs(): b=-b + r = (a*(r+1)/s).sqrt() + if (r.abs()-1).abs() < eps: break + if r.real()<0: r=-r + t *= r + z = ((a/t).arctan())/a + z = ComplexField(prec)(z) + if reduce: + z = self.reduce(z) + return z + + if self.real_flag==-1: # real, connected case + z = C(self._abc[0]) # sqrt(e3-e1) + a, y, b = z.real(), z.imag(), z.abs() + uv = (xP-e1).sqrt() + u, v = uv.real().abs(), uv.imag().abs() + r = (u*a/(u*a+v*y)).sqrt() + t = -r*R(wP)/(2*(u**2+v**2)) + on_egg = False + else: # real, disconnected case + a = R(e3-e1).sqrt() + b = R(e3-e2).sqrt() + if (a+b).abs() < (a-b).abs(): b=-b + on_egg = (xP> prec + while True: + s = b*r+a + a, b = (a+b)/2, (a*b).sqrt() + r = (a*(r+1)/s).sqrt() + if (r-1).abs() < eps: break + t *= r + z = ((a/t).arctan())/a + if on_egg: + w1,w2 = self._compute_periods_real(prec) + z += w2/2 + z = ComplexField(prec)(z) + if reduce: + z = self.reduce(z) + return z + + def elliptic_logarithm(self, P, prec=None, reduce=True): r""" Return the elliptic logarithm of a point. @@ -1173,11 +1427,9 @@ def elliptic_logarithm(self, P, prec=None, reduce=True): in the fundamental period parallelogram with respect to the normalised lattice basis. - ALGORITHM: Uses the complex AGM. See [Cremona2010]_ for details. + ALGORITHM: - .. [Cremona2010] J. E. Cremona and T. Thongjunthug, The - Complex AGM, periods of elliptic curves over $\CC$ and - complex elliptic logarithms. Preprint 2010. + Uses the complex AGM. See [CT]_ for details. EXAMPLES:: @@ -1328,154 +1580,14 @@ def elliptic_logarithm(self, P, prec=None, reduce=True): prec = RealField().precision() if P.is_zero(): return ComplexField(prec)(0) - # Note: using log2(prec) + 3 guard bits is usually enough. - # To avoid computing a logarithm, we use 40 guard bits which - # should be largely enough in practice. - prec2 = prec + 40 - R = RealField(prec2) - C = ComplexField(prec2) - pi = R.pi() - e1,e2,e3 = self._ei - a1,a2,a3 = [self.embedding(a) for a in self.E.ainvs()[:3]] - xP, yP = [self.embedding(coord) for coord in P.xy()] - wP = 2*yP+a1*xP+a3 - - # We treat the case of 2-torsion points separately. (Note - # that Cohen's algorithm does not handle these properly.) - if wP.is_zero(): # 2-torsion treated separately - w1,w2 = self._compute_periods_complex(prec,normalise=False) - if xP==e1: - z = w2/2 - else: - if xP==e3: - z = w1/2 - else: - z = (w1+w2)/2 - if reduce: - z = self.reduce(z) - return z - - # NB The first block of code works fine for real embeddings as - # well as complex embeddings. The special code for real - # embeddings uses only real arithmetic in the iteration, and is - # based on Cremona and Thongjunthug. + # Compute the real or complex coordinates of P: - # An older version, based on Cohen's Algorithm 7.4.8 also uses - # only real arithmetic, and gives different normalisations, - # but also causes problems (see #10026). It is left in but - # commented out below. - - if self.real_flag==0: # complex case - - a = C((e1-e3).sqrt()) - b = C((e1-e2).sqrt()) - if (a+b).abs() < (a-b).abs(): b=-b - r = C(((xP-e3)/(xP-e2)).sqrt()) - if r.real()<0: r=-r - t = -C(wP)/(2*r*(xP-e2)) - # eps controls the end of the loop. Since we aim at a target - # precision of prec bits, eps = 2^(-prec) is enough. - eps = R(1) >> prec - while True: - s = b*r+a - a, b = (a+b)/2, (a*b).sqrt() - if (a+b).abs() < (a-b).abs(): b=-b - r = (a*(r+1)/s).sqrt() - if (r.abs()-1).abs() < eps: break - if r.real()<0: r=-r - t *= r - z = ((a/t).arctan())/a - z = ComplexField(prec)(z) - if reduce: - z = self.reduce(z) - return z - - if self.real_flag==-1: # real, connected case - z = C(self._abc[0]) # sqrt(e3-e1) - a, y, b = z.real(), z.imag(), z.abs() - uv = (xP-e1).sqrt() - u, v = uv.real().abs(), uv.imag().abs() - r = (u*a/(u*a+v*y)).sqrt() - t = -r*R(wP)/(2*(u**2+v**2)) - on_egg = False - else: # real, disconnected case - a = R(e3-e1).sqrt() - b = R(e3-e2).sqrt() - if (a+b).abs() < (a-b).abs(): b=-b - on_egg = (xP> prec - while True: - s = b*r+a - a, b = (a+b)/2, (a*b).sqrt() - r = (a*(r+1)/s).sqrt() - if (r-1).abs() < eps: break - t *= r - z = ((a/t).arctan())/a - if on_egg: - w1,w2 = self._compute_periods_real(prec) - z += w2/2 - z = ComplexField(prec)(z) - if reduce: - z = self.reduce(z) - return z + xP, yP = [self.embedding(coord) for coord in P.xy()] + # The real work is done over R or C now: -# if self.real_flag==-1: -# z = self._abc[1] # sqrt(e3-e2) -# beta = z.norm() -# alpha = 2*(e3-e2).real() -# a = beta.sqrt()*2 -# b = (alpha+2*beta).sqrt() -# c = (xP-e3+beta)/(xP-e3).sqrt() -# else: -# on_egg = (xP= 0: -# z = pi - z -# if wP > 0: -# z += pi -# z /= a -# elif self.real_flag==+1: -# z = (a/c).arcsin()/a -# w1 = w2 = None -# if wP > 0: -# if w1 is None: -# w1, w2 = self.basis(prec) -# z = w1 - z -# if on_egg: -# if w2 is None: -# w1, w2 = self.basis(prec) -# z += w2/2 -# z = ComplexField(prec)(z) -# if reduce: -# z = self.reduce(z) -# return z + return self.e_log_RC(xP, yP, prec, reduce=reduce) def elliptic_exponential(self, z, to_curve=True): r""" diff --git a/src/sage/schemes/elliptic_curves/period_lattice_region.pyx b/src/sage/schemes/elliptic_curves/period_lattice_region.pyx new file mode 100644 index 00000000000..606622ad0ac --- /dev/null +++ b/src/sage/schemes/elliptic_curves/period_lattice_region.pyx @@ -0,0 +1,702 @@ +r""" +Regions in fundamental domains of period lattices + +This module is used to represent sub-regions of a fundamental parallelogram +of the period lattice of an elliptic curve, used in computing minimum height +bounds. + +In particular, these are the approximating sets ``S^{(v)}`` in section 3.2 of +Thotsaphon Thongjunthug's Ph.D. Thesis and paper [TT]_. + +AUTHORS: + +- Robert Bradshaw (2010): initial version + +- John Cremona (2014): added some docstrings and doctests + +REFERENCES: + +.. [T] T. Thongjunthug, Computing a lower bound for the canonical + height on elliptic curves over number fields, Math. Comp. 79 + (2010), pages 2431-2449. + +""" + +############################################################################## +# Copyright (C) 2010 Robert Bradshaw +# +# 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 numpy as np +cimport numpy as np + +from sage.rings.all import CIF +from sage.rings.arith import gcd + +cdef class PeriodicRegion: + + # The generators of the lattice, a complex numbers. + cdef public w1 + cdef public w2 + + # A two-dimensional array of (essentially) booleans specifying the subset + # of parallelogram tiles that cover this region. + cdef readonly data + + # Flag specifying whether bitmap represents the whole fundamental + # parallelogram, or only half (in which case it is symmetric about + # the center). + cdef readonly bint full + + def __init__(self, w1, w2, data, full=True): + """ + EXAMPLE:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: S = PeriodicRegion(CDF(2), CDF(2*I), np.zeros((4, 4))) + sage: S.plot() + sage: data = np.zeros((4, 4)) + sage: data[1,1] = True + sage: S = PeriodicRegion(CDF(2), CDF(2*I+1), data) + sage: S.plot() + """ + if data.dtype is not np.int8: + data = data.astype(np.int8) + full = int(full) + self.w1 = w1 + self.w2 = w2 + self.data = data + self.full = full + + def is_empty(self): + """ + Returns whether this region is empty. + + EXAMPLES:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((4, 4)) + sage: PeriodicRegion(CDF(2), CDF(2*I), data).is_empty() + True + sage: data[1,1] = True + sage: PeriodicRegion(CDF(2), CDF(2*I), data).is_empty() + False + """ + return self.data.sum() == 0 + + def _ensure_full(self): + """ + Ensure the bitmap in self.data represents the entire fundamental + parallelogram, expanding symmetry by duplicating data if necessary. + + Mutates and returns self. + + EXAMPLES:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((4, 4)) + sage: S = PeriodicRegion(CDF(2), CDF(2*I), data, full=False) + sage: S.data.shape + (4, 4) + sage: S.full + False + sage: _ = S._ensure_full() + sage: S.full + True + sage: S.data.shape + (4, 8) + sage: _ = S._ensure_full() + sage: S.data.shape + (4, 8) + """ + if not self.full: + rows, cols = self.data.shape + new_data = np.ndarray((rows, 2*cols), self.data.dtype) + new_data[:,0:cols] = self.data + new_data[::-1,-1:cols-1:-1] = self.data + self.data = new_data + self.full = True + return self + + def ds(self): + """ + Returns the sides of each parallelogram tile. + + EXAMPLES:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((4, 4)) + sage: S = PeriodicRegion(CDF(2), CDF(2*I), data, full=False) + sage: S.ds() + (0.5, 0.25*I) + sage: _ = S._ensure_full() + sage: S.ds() + (0.5, 0.25*I) + + sage: data = np.zeros((8, 8)) + sage: S = PeriodicRegion(CDF(1), CDF(I + 1/2), data) + sage: S.ds() + (0.125, 0.0625 + 0.125*I) + """ + m, n = self.data.shape + return self.w1/m, self.w2/(n * (2-self.full)) + + def verify(self, condition): + r""" + Given a condition that should hold for every line segment on + the boundary, verify that it actually does so. + + INPUT: + + - ``condition`` (function) - a boolean-valued function on `\CC`. + + OUTPUT: + + True or False according to whether the condition holds for all + lines on the boundary. + + EXAMPLES:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((4, 4)) + sage: data[1, 1] = True + sage: S = PeriodicRegion(CDF(1), CDF(I), data) + sage: S.border() + [(1, 1, 0), (2, 1, 0), (1, 1, 1), (1, 2, 1)] + sage: condition = lambda z: z.real().abs()<0.5 + sage: S.verify(condition) + False + sage: condition = lambda z: z.real().abs()<1 + sage: S.verify(condition) + True + """ + for line in self.border(False): + if not condition(line): + return False + return True + + def refine(self, condition=None, int times=1): + r""" + Recursive function to refine the current tiling. + + INPUT: + + - ``condition`` (function, default None) - if not None, only + keep tiles in the refinement which satisfy the condition. + + - ``times`` (int, default 1) - the number of times to refine; + each refinement step halves the mesh size. + + OUTPUT: + + The refined PeriodicRegion. + + EXAMPLES:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((4, 4)) + sage: S = PeriodicRegion(CDF(2), CDF(2*I), data, full=False) + sage: S.ds() + (0.5, 0.25*I) + sage: S = S.refine() + sage: S.ds() + (0.25, 0.125*I) + sage: S = S.refine(2) + sage: S.ds() + (0.125, 0.0625*I) + """ + if times <= 0: + return self + cdef int i, j, m, n + cdef np.ndarray[np.npy_int8, ndim=2] less, fuzz, new_data + m, n = self.data.shape + if condition is None: + new_data = np.ndarray((2*m, 2*n), self.data.dtype) + new_data[0::2,0::2] = self.data + new_data[1::2,0::2] = self.data + new_data[0::2,1::2] = self.data + new_data[1::2,1::2] = self.data + else: + more = self.expand().data + less = self.contract().data + fuzz = less ^ more + new_data = np.zeros((2*m, 2*n), self.data.dtype) + dw1, dw2 = self.ds() + dw1 /= 2 + dw2 /= 2 + for i in range(2*m): + for j in range(2*n): + if less[i/2, j/2]: + new_data[i,j] = True + elif fuzz[i/2, j/2]: + new_data[i,j] = condition(dw1*(i+.5) + dw2*(j+.5)) + return PeriodicRegion(self.w1, self.w2, new_data, self.full).refine(condition, times-1) + + def expand(self, bint corners=True): + """ + Returns a region containing this region by adding all neighbors of + internal tiles. + + EXAMPLES:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((4, 4)) + sage: data[1,1] = True + sage: S = PeriodicRegion(CDF(1), CDF(I + 1/2), data) + sage: S.plot() + sage: S.expand().plot() + sage: S.expand().data + array([[1, 1, 1, 0], + [1, 1, 1, 0], + [1, 1, 1, 0], + [0, 0, 0, 0]], dtype=int8) + sage: S.expand(corners=False).plot() + sage: S.expand(corners=False).data + array([[0, 1, 0, 0], + [1, 1, 1, 0], + [0, 1, 0, 0], + [0, 0, 0, 0]], dtype=int8) + """ + cdef int i, j, m, n + m, n = self.data.shape + cdef np.ndarray[np.npy_int8, ndim=2] framed, new_data + framed = frame_data(self.data, self.full) + new_data = np.zeros((m+2, n+2), self.data.dtype) + for i in range(m): + for j in range(n): + if framed[i,j]: + new_data[i , j ] = True + new_data[i-1, j ] = True + new_data[i+1, j ] = True + new_data[i , j-1] = True + new_data[i , j+1] = True + if corners: + new_data[i-1, j-1] = True + new_data[i+1, j-1] = True + new_data[i+1, j+1] = True + new_data[i-1, j+1] = True + return PeriodicRegion(self.w1, self.w2, unframe_data(new_data, self.full), self.full) + + def contract(self, corners=True): + """ + Opposite (but not inverse) of expand; removes neighbors of complement. + + EXAMPLES:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((10, 10)) + sage: data[1:4,1:4] = True + sage: S = PeriodicRegion(CDF(1), CDF(I + 1/2), data) + sage: S.plot() + sage: S.contract().plot() + sage: S.contract().data.sum() + 1 + sage: S.contract().contract().is_empty() + True + """ + return ~(~self).expand(corners) + + def __contains__(self, z): + """ + Returns whether this region contains the given point. + + EXAMPLES:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((4, 4)) + sage: data[1,1] = True + sage: S = PeriodicRegion(CDF(2), CDF(2*I), data, full=False) + sage: CDF(0, 0) in S + False + sage: CDF(0.6, 0.6) in S + True + sage: CDF(1.6, 0.6) in S + False + sage: CDF(6.6, 4.6) in S + True + sage: CDF(6.6, -1.4) in S + True + + sage: w1 = CDF(1.4) + sage: w2 = CDF(1.2 * I - .3) + sage: S = PeriodicRegion(w1, w2, data) + sage: z = w1/2 + w2/2; z in S + False + sage: z = w1/3 + w2/3; z in S + True + sage: z = w1/3 + w2/3 + 5*w1 - 6*w2; z in S + True + """ + CC = self.w1.parent() + RR = CC.construction()[1] + basis_matrix = (RR**(2,2))((tuple(self.w1), tuple(self.w2))).transpose() + i, j = basis_matrix.solve_right((RR**2)(tuple(CC(z)))) + # Get the fractional part. + i -= int(i) + j -= int(j) + if i < 0: + i += 1 + if j < 0: + j += 1 + if not self.full and j > 0.5: + j = 1 - j + i = 1 - i + m, n = self.data.shape + return self.data[int(m * i), int(n * j)] + + def __div__(self, int n): + """ + Returns a new region of the same resolution that is the image + of this region under the map z -> z/n. + + The resolution is the same, so some detail may be lost. The result is + always at worse a superset of the true image. + + EXAMPLES:: + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + + sage: data = np.zeros((20, 20)) + sage: data[2, 2:12] = True + sage: data[2:6, 2] = True + sage: data[3, 3] = True + sage: S = PeriodicRegion(CDF(1), CDF(I + 1/2), data) + sage: S.plot() + sage: (S / 2).plot() + sage: (S / 3).plot() + sage: (S / 2 / 3) == (S / 6) == (S / 3 / 2) + True + + sage: data = np.zeros((100, 100)) + sage: data[2, 3] = True + sage: data[7, 9] = True + sage: S = PeriodicRegion(CDF(1), CDF(I), data) + sage: inside = [.025 + .035j, .075 + .095j] + sage: all(z in S for z in inside) + True + sage: all(z/2 + j/2 + k/2 in S/2 for z in inside for j in range(2) for k in range(2)) + True + sage: all(z/3 + j/3 + k/3 in S/3 for z in inside for j in range(3) for k in range(3)) + True + sage: outside = [.025 + .095j, .075 + .035j] + sage: any(z in S for z in outside) + False + sage: any(z/2 + j/2 + k/2 in S/2 for z in outside for j in range(2) for k in range(2)) + False + sage: any(z/3 + j/3 + k/3 in S/3 for z in outside for j in range(3) for k in range(3)) + False + """ + cdef int i, j, a, b, rows, cols, g + cdef double d, e + if n == 1: + return self + self._ensure_full() + cdef np.ndarray[np.npy_int8, ndim=2] data, new_data + data = self.data + rows, cols = self.data.shape + if n != 1: + new_data = np.zeros(self.data.shape, self.data.dtype) + d = 1.0/n + e = 1-d/2 + for i in range(rows): + for j in range(cols): + if data[i,j]: + for a in range(n): + for b in range(n): + new_data[((a*rows+i )*d), ((b*cols+j )*d)] = True + new_data[((a*rows+i+e)*d), ((b*cols+j )*d)] = True + new_data[((a*rows+i+e)*d), ((b*cols+j+e)*d)] = True + new_data[((a*rows+i )*d), ((b*cols+j+e)*d)] = True + data = new_data + return PeriodicRegion(self.w1, self.w2, data) + + def __invert__(self): + """ + Returns the complement of this region. + + EXAMPLES:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((4, 4)) + sage: data[1,1] = True + sage: S = PeriodicRegion(CDF(1), CDF(I), data) + sage: .3 + .3j in S + True + sage: .3 + .3j in ~S + False + sage: 0 in S + False + sage: 0 in ~S + True + """ + return PeriodicRegion(self.w1, self.w2, 1-self.data, self.full) + + def __and__(left, right): + """ + Returns the intersection of left and right. + + EXAMPLES:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((4, 4)) + sage: data[1, 1:3] = True + sage: S1 = PeriodicRegion(CDF(1), CDF(I), data) + sage: data = np.zeros((4, 4)) + sage: data[1:3, 1] = True + sage: S2 = PeriodicRegion(CDF(1), CDF(I), data) + sage: (S1 & S2).data + array([[0, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]], dtype=int8) + """ + assert isinstance(left, PeriodicRegion) and isinstance(right, PeriodicRegion) + assert left.w1 == right.w1 and left.w2 == right.w2 + if left.full ^ right.full: + left._ensure_full() + right._ensure_full() + return PeriodicRegion(left.w1, left.w2, left.data & right.data, left.full) + + def __xor__(left, right): + """ + Returns the union of left and right less the intersection of left and right. + + EXAMPLES:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((4, 4)) + sage: data[1, 1:3] = True + sage: S1 = PeriodicRegion(CDF(1), CDF(I), data) + sage: data = np.zeros((4, 4)) + sage: data[1:3, 1] = True + sage: S2 = PeriodicRegion(CDF(1), CDF(I), data) + sage: (S1 ^^ S2).data + array([[0, 0, 0, 0], + [0, 0, 1, 0], + [0, 1, 0, 0], + [0, 0, 0, 0]], dtype=int8) + """ + assert isinstance(left, PeriodicRegion) and isinstance(right, PeriodicRegion) + assert left.w1 == right.w1 and left.w2 == right.w2 + if left.full ^ right.full: + left._ensure_full() + right._ensure_full() + return PeriodicRegion(left.w1, left.w2, left.data ^ right.data, left.full) + + def __cmp__(left, right): + """ + Compares to regions. + + Note: this is good for equality but not an ordering relation. + + TESTS:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((4, 4)) + sage: data[1, 1] = True + sage: S1 = PeriodicRegion(CDF(1), CDF(I), data) + sage: data = np.zeros((4, 4)) + sage: data[1, 1] = True + sage: S2 = PeriodicRegion(CDF(1), CDF(I), data) + sage: data = np.zeros((4, 4)) + sage: data[2, 2] = True + sage: S3 = PeriodicRegion(CDF(1), CDF(I), data) + sage: S1 == S2 + True + sage: S2 == S3 + False + """ + c = cmp(type(left), type(right)) + if c: return c + c = cmp((left.w1, left.w2), (right.w1, right.w2)) + if c: return c + if left.full ^ right.full: + left._ensure_full() + right._ensure_full() + if (left.data == right.data).all(): + return 0 + else: + return 1 + + def border(self, raw=True): + """ + Returns the boundary of this region as set of tile boundaries. + + If raw is true, returns a list with respect to the internal bitmap, + otherwise returns complex intervals covering the border. + + EXAMPLES:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((4, 4)) + sage: data[1, 1] = True + sage: PeriodicRegion(CDF(1), CDF(I), data).border() + [(1, 1, 0), (2, 1, 0), (1, 1, 1), (1, 2, 1)] + sage: PeriodicRegion(CDF(2), CDF(I-1/2), data).border() + [(1, 1, 0), (2, 1, 0), (1, 1, 1), (1, 2, 1)] + + sage: PeriodicRegion(CDF(1), CDF(I), data).border(raw=False) + [0.25000000000000000? + 1.?*I, + 0.50000000000000000? + 1.?*I, + 1.? + 0.25000000000000000?*I, + 1.? + 0.50000000000000000?*I] + sage: PeriodicRegion(CDF(2), CDF(I-1/2), data).border(raw=False) + [0.3? + 1.?*I, + 0.8? + 1.?*I, + 1.? + 0.25000000000000000?*I, + 1.? + 0.50000000000000000?*I] + + sage: data[1:3, 2] = True + sage: PeriodicRegion(CDF(1), CDF(I), data).border() + [(1, 1, 0), (2, 1, 0), (1, 1, 1), (1, 2, 0), (1, 3, 1), (3, 2, 0), (2, 2, 1), (2, 3, 1)] + + """ + cdef np.ndarray[np.npy_int8, ndim=2] framed = frame_data(self.data, self.full) + cdef int m, n + m, n = self.data.shape + cdef int i, j + L = [] + for i in range(m): + for j in range(n): + if framed[i,j]: + if not framed[i-1, j]: + L.append((i, j, 0)) + if not framed[i+1, j]: + L.append((i+1, j, 0)) + if not framed[i, j-1]: + L.append((i, j, 1)) + if not framed[i, j+1]: + L.append((i, j+1, 1)) + if not raw: + dw1, dw2 = self.ds() + for ix, (i, j, dir) in enumerate(L): + L[ix] = CIF(i*dw1 + j*dw2).union(CIF((i+dir)*dw1 + (j+(1-dir))*dw2)) + return L + + def innermost_point(self): + """ + Returns a point well inside the region, specifically the center of + (one of) the last tile(s) to be removed on contraction. + + EXAMPLES:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((10, 10)) + sage: data[1:4, 1:4] = True + sage: data[1, 0:8] = True + sage: S = PeriodicRegion(CDF(1), CDF(I+1/2), data) + sage: S.innermost_point() + 0.375 + 0.25*I + sage: S.plot() + point(S.innermost_point()) + """ + if self.is_empty(): + from sage.categories.sets_cat import EmptySetError + raise EmptySetError("region is empty") + inside = self + while not inside.is_empty(): + self, inside = inside, inside.contract(corners=False) + i, j = tuple(np.transpose(self.data.nonzero())[0]) + dw1, dw2 = self.ds() + return float(i + 0.5) * dw1 + float(j + 0.5) * dw2 + + def plot(self, **kwds): + """ + Plots this region in the fundamental lattice. If full is False plots + only the lower half. Note that the true nature of this region is periodic. + + EXAMPLES:: + + sage: import numpy as np + sage: from sage.schemes.elliptic_curves.period_lattice_region import PeriodicRegion + sage: data = np.zeros((10, 10)) + sage: data[2, 2:8] = True + sage: data[2:5, 2] = True + sage: data[3, 3] = True + sage: S = PeriodicRegion(CDF(1), CDF(I + 1/2), data) + sage: plot(S) + plot(S.expand(), rgbcolor=(1, 0, 1), thickness=2) + """ + from sage.all import line + dw1, dw2 = self.ds() + L = [] + F = line([(0,0), tuple(self.w1), tuple(self.w1+self.w2), tuple(self.w2), (0,0)]) + if not self.full: + F += line([tuple(self.w2/2), tuple(self.w1+self.w2/2)]) + if 'rgbcolor' not in kwds: + kwds['rgbcolor'] = 'red' + for i, j, dir in self.border(): + ii, jj = i+dir, j+(1-dir) + L.append(line([tuple(i*dw1 + j*dw2), tuple(ii*dw1 + jj*dw2 )], **kwds)) + return sum(L, F) + + +cdef frame_data(data, bint full=True): + """ + Helper function for PeriodicRegion.expand() and + PeriodicRegion.border(). This makes "wrapping around" work + transparently for symmetric regions (full=False) as well. + + Idea: + + [[a, b, c] [[a, b, c, a, c], + [d, e, f] --> [d, e, f, d, f], + [g, h, i]] [g, h, i, g, i], + [a, b, c, a, c], + [g, h, i, g, i]] + """ + m, n = data.shape + framed = np.empty((m+2, n+2), data.dtype) + # center + framed[:-2,:-2] = data + # top and bottom + if full: + framed[:-2,-1] = data[:,-1] + framed[:-2,-2] = data[:, 0] + else: + framed[:-2,-1] = data[::-1, 0] + framed[:-2,-2] = data[::-1,-1] + # left and right + framed[-2,:] = framed[ 0,:] + framed[-1,:] = framed[-3,:] + return framed + +cdef unframe_data(framed, bint full=True): + """ + Helper function for PeriodicRegion.expand(). This glues the + borders together using the "or" operator. + """ + framed = framed.copy() + framed[ 0,:] |= framed[-2,:] + framed[-3,:] |= framed[-1,:] + if full: + framed[:-2,-3] |= framed[:-2,-1] + framed[:-2, 0] |= framed[:-2,-2] + else: + framed[-3::-1, 0] |= framed[:-2,-1] + framed[-3::-1,-3] |= framed[:-2,-2] + return framed[:-2,:-2] diff --git a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py index 8764393b987..6e46bdc9c29 100644 --- a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py +++ b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py @@ -131,7 +131,7 @@ def _frobenius_coefficient_bound_traces(self, n=1): """ g = self.genus() p = self.base_ring().characteristic() - return (ZZ(4*g).exact_log(p) + n/2).floor() + 1 + return (ZZ(4*g).exact_log(p) + n//2).floor() + 1 def frobenius_matrix_hypellfrob(self, N=None): """ diff --git a/src/sage/schemes/plane_curves/affine_curve.py b/src/sage/schemes/plane_curves/affine_curve.py index 3d13331a992..40e68c09e71 100644 --- a/src/sage/schemes/plane_curves/affine_curve.py +++ b/src/sage/schemes/plane_curves/affine_curve.py @@ -155,7 +155,7 @@ def local_coordinates(self, pt, n): S.eval('poly f = '+str(ft) + ';') c = S('coeffs(%s, t)'%ft) N = int(c.size()) - b = ["%s[%s,1],"%(c.name(), i) for i in range(2,N/2-4)] + b = ["%s[%s,1],"%(c.name(), i) for i in range(2,N//2-4)] b = ''.join(b) b = b[:len(b)-1] # to cut off the trailing comma cmd = 'ideal I = '+b @@ -385,7 +385,7 @@ def rational_points(self, algorithm="enum"): # with the expect interface could crop up. Also, this is vastly # faster (and more robust). v = singular('POINTS').sage_flattened_str_list() - pnts = [self(int(v[3*i]), int(v[3*i+1])) for i in range(len(v)/3) if int(v[3*i+2])!=0] + pnts = [self(int(v[3*i]), int(v[3*i+1])) for i in range(len(v)//3) if int(v[3*i+2])!=0] # remove multiple points pnts = sorted(set(pnts)) return pnts diff --git a/src/sage/schemes/plane_curves/projective_curve.py b/src/sage/schemes/plane_curves/projective_curve.py index 2780cb45da9..fc8110b39b9 100644 --- a/src/sage/schemes/plane_curves/projective_curve.py +++ b/src/sage/schemes/plane_curves/projective_curve.py @@ -541,7 +541,7 @@ def _points_via_singular(self, sort=True): # faster (and more robust). v = singular('POINTS').sage_flattened_str_list() pnts = [self(int(v[3*i]), int(v[3*i+1]), int(v[3*i+2])) - for i in range(len(v)/3)] + for i in range(len(v)//3)] # singular always dehomogenizes with respect to the last variable # so if this variable divides the curve equation, we need to add # points at infinity @@ -613,7 +613,7 @@ def riemann_roch_basis(self, D): R = X2[5][1][1] singular.set_ring(R) v = singular('POINTS').sage_flattened_str_list() - coords = [self(int(v[3*i]), int(v[3*i+1]), int(v[3*i+2])) for i in range(len(v)/3)] + coords = [self(int(v[3*i]), int(v[3*i+1]), int(v[3*i+2])) for i in range(len(v)//3)] # build correct representation of D for singular Dsupport = D.support() Dcoeffs = [] diff --git a/src/sage/stats/basic_stats.py b/src/sage/stats/basic_stats.py index 5a4fa5be600..bd736dbe81e 100644 --- a/src/sage/stats/basic_stats.py +++ b/src/sage/stats/basic_stats.py @@ -339,10 +339,10 @@ def median(v): return NaN values = sorted(v) if len(values) % 2 == 1: - return values[((len(values))+1)/2-1] + return values[((len(values))+1)//2-1] else: - lower = values[(len(values)+1)/2-1] - upper = values[len(values)/2] + lower = values[(len(values)+1)//2-1] + upper = values[len(values)//2] return (lower + upper)/ZZ(2) def moving_average(v, n): diff --git a/src/sage/symbolic/constants.py b/src/sage/symbolic/constants.py index 6f76834260c..0cfc6d5e0a3 100644 --- a/src/sage/symbolic/constants.py +++ b/src/sage/symbolic/constants.py @@ -848,7 +848,7 @@ class Log2(Constant): sage: maxima(log2) log(2) sage: maxima(log2).float() - .6931471805599453 + 0.6931471805599453 sage: gp(log2) 0.6931471805599453094172321215 # 32-bit 0.69314718055994530941723212145817656808 # 64-bit diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 28c7275339e..94b4542cbca 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -6674,7 +6674,7 @@ cdef class Expression(CommutativeRingElement): sage: float(SR(0.7).arctan2(0.6)) 0.8621700546672264 sage: maxima('atan2(0.7,0.6)') - .862170054667226... + 0.8621700546672264 sage: float(SR(0.7).arctan2(-0.6)) 2.279422598922567 sage: maxima('atan2(0.7,-0.6)') @@ -6682,7 +6682,7 @@ cdef class Expression(CommutativeRingElement): sage: float(SR(-0.7).arctan2(0.6)) -0.8621700546672264 sage: maxima('atan2(-0.7,0.6)') - -.862170054667226... + -0.8621700546672264 sage: float(SR(-0.7).arctan2(-0.6)) -2.279422598922567 sage: maxima('atan2(-0.7,-0.6)') @@ -6866,7 +6866,7 @@ cdef class Expression(CommutativeRingElement): sage: SR(1.0).tanh() 0.761594155955765 sage: maxima('tanh(1.0)') - .7615941559557649 + 0.7615941559557649 sage: plot(lambda x: SR(x).tanh(), -1, 1) To prevent automatic evaluation use the ``hold`` argument:: @@ -7022,8 +7022,8 @@ cdef class Expression(CommutativeRingElement): 0.549306144334055 sage: SR(0.5).arctanh().tanh() 0.500000000000000 - sage: maxima('atanh(0.5)') - .5493061443340... + sage: maxima('atanh(0.5)') # abs tol 2e-16 + 0.5493061443340548 To prevent automatic evaluation use the ``hold`` argument:: @@ -9263,7 +9263,7 @@ cdef class Expression(CommutativeRingElement): sage: from sage.calculus.calculus import maxima sage: sol = maxima(cos(x)==0).to_poly_solve(x) sage: sol.sage() - [[x == -1/2*pi + 2*pi*z...], [x == 1/2*pi + 2*pi*z...]] + [[x == 1/2*pi + pi*z82]] If a returned unsolved expression has a denominator, but the original one did not, this may also be true:: diff --git a/src/sage/symbolic/expression_conversions.py b/src/sage/symbolic/expression_conversions.py index 48ed0d4ec56..db429c9ba48 100644 --- a/src/sage/symbolic/expression_conversions.py +++ b/src/sage/symbolic/expression_conversions.py @@ -21,6 +21,7 @@ from sage.functions.all import exp from sage.symbolic.operators import arithmetic_operators, relation_operators, FDerivativeOperator from sage.rings.number_field.number_field_element_quadratic import NumberFieldElement_quadratic +from functools import reduce GaussianField = I.pyobject().parent() class FakeExpression(object): diff --git a/src/sage/symbolic/function.pyx b/src/sage/symbolic/function.pyx index eed56e65010..f774812bf62 100644 --- a/src/sage/symbolic/function.pyx +++ b/src/sage/symbolic/function.pyx @@ -15,6 +15,7 @@ Support for symbolic functions. include "sage/ext/interrupt.pxi" include "sage/ext/cdefs.pxi" +from functools import reduce from ginac cimport * from sage.structure.sage_object cimport SageObject diff --git a/src/sage/symbolic/integration/integral.py b/src/sage/symbolic/integration/integral.py index d0925292427..de384ee3587 100644 --- a/src/sage/symbolic/integration/integral.py +++ b/src/sage/symbolic/integration/integral.py @@ -394,9 +394,9 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None): ... ValueError: Computation failed since Maxima requested additional constraints; using the 'assume' command before integral evaluation - *may* help (example of legal syntax is 'assume(n+1>0)', see `assume?` + *may* help (example of legal syntax is 'assume(n>0)', see `assume?` for more details) - Is n+1 zero or nonzero? + Is n equal to -1? sage: assume(n > 0) sage: integral(x^n,x) x^(n + 1)/(n + 1) @@ -518,7 +518,7 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None): constraints; using the 'assume' command before integral evaluation *may* help (example of legal syntax is 'assume(a>0)', see `assume?` for more details) - Is a positive or negative? + Is a positive or negative? So we just assume that `a>0` and the integral works:: @@ -556,7 +556,7 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None): Traceback (most recent call last): ... ValueError: Computation failed since Maxima requested additional constraints; using the 'assume' command before integral evaluation *may* help (example of legal syntax is 'assume(50015104*y^2-50015103>0)', see `assume?` for more details) - Is 50015104*y^2-50015103 positive, negative, or zero? + Is 50015104*y^2-50015103 positive, negative or zero? sage: assume(y>1) sage: res = integral(f,x,0.0001414, 1.); res -2*y*arctan(0.0001414/y) + 2*y*arctan(1/y) + log(y^2 + 1.0) - 0.0001414*log(y^2 + 1.999396e-08) - 1.9997172 @@ -668,6 +668,7 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None): sage: F = integrate(f, t, 1, Infinity) sage: F(x=1, a=7).numerical_approx() # abs tol 1e-10 4.32025625668262 + sage: forget() Verify that MinusInfinity works with sympy (:trac:`12345`):: @@ -679,6 +680,12 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None): sage: N(integrate(sin(x^2)/(x^2), x, 1, infinity)) 0.285736646322858 + Check that :trac:`14209` is fixed:: + + sage: integral(e^(-abs(x))/cosh(x),x,-infinity,infinity) + 2*log(2) + sage: integral(e^(-abs(x))/cosh(x),x,-infinity,infinity) + 2*log(2) """ expression, v, a, b = _normalize_integral_input(expression, v, a, b) if algorithm is not None: diff --git a/src/sage/symbolic/relation.py b/src/sage/symbolic/relation.py index aa7ec1e0ffa..65681245e35 100644 --- a/src/sage/symbolic/relation.py +++ b/src/sage/symbolic/relation.py @@ -615,7 +615,7 @@ def solve(f, *args, **kwds): be implicitly an integer (hence the ``z``):: sage: solve([cos(x)*sin(x) == 1/2, x+y == 0],x,y) - [[x == 1/4*pi + pi*z78, y == -1/4*pi - pi*z78]] + [[x == 1/4*pi + pi*z80, y == -1/4*pi - pi*z80]] Expressions which are not equations are assumed to be set equal to zero, as with `x` in the following example:: diff --git a/src/sage/tensor/differential_form_element.py b/src/sage/tensor/differential_form_element.py index 599b7296810..3e96b68a812 100644 --- a/src/sage/tensor/differential_form_element.py +++ b/src/sage/tensor/differential_form_element.py @@ -75,7 +75,7 @@ def sort_subscript(subscript): # Check that offsets is a true permutation of 1..n n = len(offsets) - if sum(offsets) != n*(n+1)/2: + if sum(offsets) != n*(n+1)//2: sign = 0 else: sign = Permutation(offsets).signature() diff --git a/src/sage/tests/benchmark.py b/src/sage/tests/benchmark.py index 176e5055602..420facb168f 100644 --- a/src/sage/tests/benchmark.py +++ b/src/sage/tests/benchmark.py @@ -412,7 +412,7 @@ def maxima(self): """ R = PolynomialRing(self.base, self.nvars, 'x') - k = int(self.nvars/2) + k = self.nvars // 2 z0 = maxima(str(sum(R.gens()[:k]))) z1 = maxima(str(sum(R.gens()[k:]))) w = walltime() @@ -431,7 +431,7 @@ def maple(self): """ R = PolynomialRing(self.base, self.nvars, 'x') - k = int(self.nvars/2) + k = self.nvars // 2 z0 = maple(str(sum(R.gens()[:k]))) z1 = maple(str(sum(R.gens()[k:]))) w = walltime() @@ -450,7 +450,7 @@ def mathematica(self): """ R = PolynomialRing(self.base, self.nvars, 'x') - k = int(self.nvars/2) + k = self.nvars // 2 z0 = mathematica(str(sum(R.gens()[:k]))) z1 = mathematica(str(sum(R.gens()[k:]))) w = walltime() @@ -459,7 +459,7 @@ def mathematica(self): ## def gp(self): ## R = PolynomialRing(self.base, self.nvars) -## k = int(self.nvars/2) +## k = self.nvars // 2 ## z0 = gp(str(sum(R.gens()[:k]))) ## z1 = gp(str(sum(R.gens()[k:]))) ## gp.eval('gettime') @@ -478,7 +478,7 @@ def sage(self): """ R = PolynomialRing(self.base, self.nvars, 'x') - k = int(self.nvars/2) + k = self.nvars // 2 z0 = sum(R.gens()[:k]) z1 = sum(R.gens()[k:]) if self.allow_singular: @@ -504,7 +504,7 @@ def macaulay2(self): """ R = PolynomialRing(self.base, self.nvars, 'x') - k = int(self.nvars/2) + k = self.nvars // 2 z0 = macaulay2(sum(R.gens()[:k])) z1 = macaulay2(sum(R.gens()[k:])) t = walltime() @@ -524,7 +524,7 @@ def magma(self): """ R = magma.PolynomialRing(self.base, self.nvars) z0 = R.gen(1) - k = int(self.nvars/2) + k = self.nvars // 2 for i in range(2,k+1): z0 += R.gen(i) z1 = R.gen(k + 1) @@ -550,7 +550,7 @@ def __init__(self, nvars=2, base=QQ, allow_singular=True): ## def gp(self): ## R = PolynomialRing(self.base, self.nvars) -## k = int(self.nvars/2) +## k = self.nvars // 2 ## z0 = R(0) ## z1 = R(0) ## for i in range(k): @@ -576,7 +576,7 @@ def maxima(self): """ R = PolynomialRing(self.base, self.nvars, 'x') - k = int(self.nvars/2) + k = self.nvars // 2 z0 = R(0) z1 = R(0) for i in range(k): @@ -601,7 +601,7 @@ def macaulay2(self): """ R = PolynomialRing(self.base, self.nvars, 'x') - k = int(self.nvars/2) + k = self.nvars // 2 z0 = R(0) z1 = R(0) for i in range(k): @@ -626,7 +626,7 @@ def maple(self): """ R = PolynomialRing(self.base, self.nvars, 'x') - k = int(self.nvars/2) + k = self.nvars // 2 z0 = R(0) z1 = R(0) for i in range(k): @@ -651,7 +651,7 @@ def mathematica(self): """ R = PolynomialRing(self.base, self.nvars, 'x') - k = int(self.nvars/2) + k = self.nvars // 2 z0 = R(0) z1 = R(0) for i in range(k): @@ -676,7 +676,7 @@ def sage(self): """ R = PolynomialRing(self.base, self.nvars, 'x') - k = int(self.nvars/2) + k = self.nvars // 2 z0 = R(0) z1 = R(0) for i in range(k): @@ -707,7 +707,7 @@ def magma(self): """ R = magma.PolynomialRing(self.base, self.nvars) z0 = R.gen(1) - k = int(self.nvars/2) + k = self.nvars // 2 for i in range(2,k+1): z0 += magma(i)*R.gen(i) z1 = R.gen(k + 1) diff --git a/src/sage/tests/french_book/recequadiff.py b/src/sage/tests/french_book/recequadiff.py index 924b2333985..76fd47fbb89 100755 --- a/src/sage/tests/french_book/recequadiff.py +++ b/src/sage/tests/french_book/recequadiff.py @@ -248,7 +248,7 @@ Traceback (most recent call last): ... TypeError: ECL says: Maxima asks: - Is k positive, negative, or zero? + Is k positive, negative or zero? Sage example in ./recequadiff.tex, line 728:: diff --git a/src/sage/version.py b/src/sage/version.py index b61f6dd195f..8b693f3709d 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,4 +1,4 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '6.3.beta2' -date = '2014-05-24' +version = '6.3.beta3' +date = '2014-06-04'