diff --git a/VERSION.txt b/VERSION.txt index 4eee0209936..e6fbd0dcf06 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 8.4.beta6, Release Date: 2018-09-22 +SageMath version 8.4.beta7, Release Date: 2018-09-30 diff --git a/build/bin/sage-spkg b/build/bin/sage-spkg index 9dffb481dfa..2bd7be74384 100755 --- a/build/bin/sage-spkg +++ b/build/bin/sage-spkg @@ -881,32 +881,17 @@ if [ -d "$SAGE_DESTDIR" ]; then fi # Generate installed file manifest - FILE_LIST="" - FIRST=1 - old_IFS="$IFS"; IFS=$'\n' - for filename in $(find "$PREFIX" -type f -o -type l | sort); do - filename="${filename#$PREFIX/}" - if [ $FIRST -eq 1 ]; then - FILE_LIST="\"$filename\"" - FIRST=0 - else - FILE_LIST="${FILE_LIST},"$'\n '"\"${filename}\"" - fi - # Copy file from the temp install path into $SAGE_LOCAL - if [ ! -d "$SAGE_LOCAL/$(dirname "$filename")" ]; then - $SAGE_SUDO mkdir -p "$SAGE_LOCAL/$(dirname "$filename")" - fi - $SAGE_SUDO mv "$PREFIX/$filename" "${SAGE_LOCAL%/}/$filename" - if [ $? -ne 0 ]; then - error_msg "Error moving files for $PKG_NAME." - exit 1 - fi - done - IFS="$old_IFS" + FILE_LIST="$(cd "$PREFIX" && find . -type f -o -type l | sed 's|^\./||' | sort)" + # Copy files into $SAGE_LOCAL + $SAGE_SUDO cp -Rp "$PREFIX/." "$SAGE_LOCAL" + rm -rf "$SAGE_DESTDIR" + if [ $? -ne 0 ]; then + error_msg "Error copying files for $PKG_NAME." + exit 1 + fi # Remove the $SAGE_DESTDIR entirely once all files have been moved to their # final location. - rm -rf "$SAGE_DESTDIR" fi @@ -983,6 +968,7 @@ if [ "$SAGE_CHECK" = "yes" ]; then fi fi +FILE_LIST="$(echo "$FILE_LIST" | sed 's/^\(.\+\)$/"\1"/; 2,$s/^/ /; $!s/$/,/')" # Mark that the new package has been installed (and tested, if # applicable). diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index cc584b07585..53d9996ab09 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=00455e50894d088544c9b7e61e20c605696eea0c -md5=f42fb78fd137874c567f182196dc45c2 -cksum=3977387603 +sha1=585003ab668b8afea5ae14b7fd3f71da3f8f043a +md5=9058f4a34da70a5f5186b36c076d9a13 +cksum=3490824772 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 6d26270b57e..c9716b723ff 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -283 +284 diff --git a/build/pkgs/ecl/spkg-install b/build/pkgs/ecl/spkg-install index 34e1222980a..8f3dda20f5a 100644 --- a/build/pkgs/ecl/spkg-install +++ b/build/pkgs/ecl/spkg-install @@ -8,6 +8,13 @@ else CXXFLAGS="-g -O2 $CXXFLAGS" fi +if [ "$UNAME" = "CYGWIN" ]; then + # Some of ECL's sources rely on GNU-isms that are allowed by default on + # most glibcs, but not in newlib; https://trac.sagemath.org/ticket/25057 + CFLAGS="$CFLAGS -D_GNU_SOURCE" + CXXFLAGS="$CXXFLAGS -D_GNU_SOURCE" +fi + export CFLAGS export CXXFLAGS export LDFLAGS diff --git a/build/pkgs/fflas_ffpack/spkg-install b/build/pkgs/fflas_ffpack/spkg-install index 20f1a5c4021..b9ce28492ad 100644 --- a/build/pkgs/fflas_ffpack/spkg-install +++ b/build/pkgs/fflas_ffpack/spkg-install @@ -2,20 +2,6 @@ cd src if [ "$LINBOX_BLAS" != "" ]; then echo "Using environment variable LINBOX_BLAS=$LINBOX_BLAS" -elif [ "$UNAME" = "CYGWIN" ]; then - # TODO: we should install a suitable blas.pc on Cygwin - echo "Using system-wide Cygwin LAPACK BLAS." - if [ ! -f "/usr/lib/libblas.a" ]; then - echo >&2 "*************************************************" - echo >&2 "*" - echo >&2 "* Error: On Cygwin you must install the standard LAPACK Cygwin package" - echo >&2 "* via the Cygwin setup.exe program in the 'Math' category." - echo >&2 "*" - echo >&2 "*************************************************" - exit 1 - fi - LINBOX_BLAS="-lblas" - LINBOX_BLAS_CFLAGS="" else LINBOX_BLAS="$(pkg-config --libs cblas)" BLAS_CFLAGS="$(pkg-config --cflags cblas)" diff --git a/build/pkgs/nose/SPKG.txt b/build/pkgs/nose/SPKG.txt index 884de83e9b4..e978029b47a 100644 --- a/build/pkgs/nose/SPKG.txt +++ b/build/pkgs/nose/SPKG.txt @@ -23,12 +23,4 @@ Home Page: http://readthedocs.org/docs/nose/ == Special Update/Build Instructions == -Remove any .pyc files by running the spkg-src script. - -== Changelog == - -=== nose-1.1.2 (John H. Palmieri, June 14, 2012) === - * #9921: initial version. - -=== nose-1.3.3 (Martin W.-Raum, Juli 21, 2014) === - * #??: Updated +None. diff --git a/build/pkgs/nose/checksums.ini b/build/pkgs/nose/checksums.ini index 21000a6c6ba..b9477e8524b 100644 --- a/build/pkgs/nose/checksums.ini +++ b/build/pkgs/nose/checksums.ini @@ -1,4 +1,4 @@ -tarball=nose-VERSION.tar.bz2 -sha1=b3177ffaa32eb169e1d775d02c8a361d0d76808e -md5=aab52d77de9845d58ad717016f48668f -cksum=2069758669 +tarball=nose-VERSION.tar.gz +sha1=97f2a04c9d43b29ddf4794a1a1d1ba803f1074c6 +md5=4d3ad0ff07b61373d2cefc89c5d0b20b +cksum=3318391794 diff --git a/build/pkgs/nose/package-version.txt b/build/pkgs/nose/package-version.txt index 5e6364b56ed..3336003dccd 100644 --- a/build/pkgs/nose/package-version.txt +++ b/build/pkgs/nose/package-version.txt @@ -1 +1 @@ -1.3.3.p0 +1.3.7 diff --git a/build/pkgs/pycosat/SPKG.txt b/build/pkgs/pycosat/SPKG.txt new file mode 100644 index 00000000000..0d99e44ba15 --- /dev/null +++ b/build/pkgs/pycosat/SPKG.txt @@ -0,0 +1,28 @@ += pycosat = + +== Description == + +PicoSAT is a popular SAT solver written by Armin Biere in pure C. This package +provides efficient Python bindings to picosat on the C level, i.e. when +importing pycosat, the picosat solver becomes part of the Python process itself. +For ease of deployment, the picosat source (namely picosat.c and picosat.h) is +included in this project. These files have been extracted from the picosat +source. + +== License == + +MIT + +== Upstream Contact == + +PicoSAT: http://fmv.jku.at/picosat/ +pycosat: https://github.com/ContinuumIO/pycosat + +== Dependencies == + +None. + +== Special Update/Build Instructions == + +None. + diff --git a/build/pkgs/pycosat/checksums.ini b/build/pkgs/pycosat/checksums.ini new file mode 100644 index 00000000000..0ded8feb643 --- /dev/null +++ b/build/pkgs/pycosat/checksums.ini @@ -0,0 +1,4 @@ +tarball=pycosat-VERSION.tar.gz +sha1=1c2243fcc52491db8aa11558d4df626f28311757 +md5=08e378db1c15dc1668bc62897bd325a5 +cksum=2006887185 diff --git a/build/pkgs/pycosat/dependencies b/build/pkgs/pycosat/dependencies new file mode 100644 index 00000000000..d5dab729e18 --- /dev/null +++ b/build/pkgs/pycosat/dependencies @@ -0,0 +1,5 @@ +$(PYTHON) | pip + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/pycosat/package-version.txt b/build/pkgs/pycosat/package-version.txt new file mode 100644 index 00000000000..844f6a91acb --- /dev/null +++ b/build/pkgs/pycosat/package-version.txt @@ -0,0 +1 @@ +0.6.3 diff --git a/build/pkgs/pycosat/spkg-check b/build/pkgs/pycosat/spkg-check new file mode 100644 index 00000000000..a42c0346810 --- /dev/null +++ b/build/pkgs/pycosat/spkg-check @@ -0,0 +1,15 @@ +if [ "$SAGE_LOCAL" = "" ]; then + echo >&2 "Error: SAGE_LOCAL undefined - exiting..." + echo >&2 "Maybe run 'sage -sh'?" + exit 1 +fi + +echo "Testing pycosat..." + +cd src +python test_pycosat.py + +if [ $? -ne 0 ]; then + echo >&2 "Error running self tests." + exit 1 +fi diff --git a/build/pkgs/pycosat/spkg-install b/build/pkgs/pycosat/spkg-install new file mode 100644 index 00000000000..724f15c86a3 --- /dev/null +++ b/build/pkgs/pycosat/spkg-install @@ -0,0 +1,15 @@ +if [ "$SAGE_LOCAL" = "" ]; then + echo "SAGE_LOCAL undefined ... exiting"; + echo "Maybe run 'sage -sh'?" + exit 1 +fi + +cd src + +sdh_pip_install . + +if [ $? -ne 0 ]; then + echo "Error installing pycosat ... exiting" + exit 1 +fi + diff --git a/build/pkgs/pycosat/type b/build/pkgs/pycosat/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/pycosat/type @@ -0,0 +1 @@ +optional diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 2d4417fb40e..6d65dcce69e 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -1,5 +1,5 @@ # Sage version information for shell scripts # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='8.4.beta6' -SAGE_RELEASE_DATE='2018-09-22' -SAGE_VERSION_BANNER='SageMath version 8.4.beta6, Release Date: 2018-09-22' +SAGE_VERSION='8.4.beta7' +SAGE_RELEASE_DATE='2018-09-30' +SAGE_VERSION_BANNER='SageMath version 8.4.beta7, Release Date: 2018-09-30' diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 67c7c0813a4..5609cbfea50 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -870,7 +870,7 @@ written. sage: print "not like that" Traceback (most recent call last): ... - SyntaxError: invalid syntax + SyntaxError: ... sage: print("but like this") but like this @@ -1081,9 +1081,9 @@ framework. Here is a comprehensive list: 64-bit machines. Note that this particular flag is to be applied on the **output** lines, not the input lines:: - sage: hash(-920390823904823094890238490238484) - -873977844 # 32-bit - 6874330978542788722 # 64-bit + sage: hash(2^31 + 2^13) + -2147475456 # 32-bit + 2147491840 # 64-bit Using ``search_src`` from the Sage prompt (or ``grep``), one can easily find the aforementioned keywords. In the case of ``todo: not diff --git a/src/doc/en/faq/faq-usage.rst b/src/doc/en/faq/faq-usage.rst index 2347a1190db..d64a4ac071c 100644 --- a/src/doc/en/faq/faq-usage.rst +++ b/src/doc/en/faq/faq-usage.rst @@ -10,14 +10,15 @@ How do I get started? You can try out Sage without downloading anything: -* **SageMathCloud™:** Go to http://cloud.sagemath.org and set up a free - account. +* **CoCalc™:** Go to https://cocalc.com and set up a free account. If you log in, you will gain access to the latest version of Sage and to many other programs. + Note that this website is an independent commercial service. + * **Sage cell:** A "one-off" version of Sage, available for doing one - computation at a time. http://sagecell.sagemath.org/ + computation at a time. https://sagecell.sagemath.org/ To download a **pre-built binary** Sage distribution, visit http://sagemath.org/download.html and click on the link for the binary for your @@ -36,10 +37,6 @@ You can also run it from the command line of sage:: sage: notebook() # not tested -Where can I find more information about using SageMathCloud™? -""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" - -Visit the Frequently Asked Questions page for SageMathCloud™ at https://github.com/sagemath/cloud/wiki/FAQ . What are the prerequisites for installing a copy of Sage on my computer? """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" @@ -48,8 +45,8 @@ Most of the dependencies of Sage are shipped with Sage itself. In most cases, you can download a pre-built binary and use that without installing any dependencies. If you use Windows, you will need to install -`VirtualBox `_, which can be downloaded -from the page http://www.virtualbox.org/wiki/Downloads. After +`VirtualBox `_, which can be downloaded +from the page https://www.virtualbox.org/wiki/Downloads. After installing VirtualBox, you need to download a VirtualBox distribution of Sage available at http://www.sagemath.org/download-windows.html. Ensure you follow the @@ -146,11 +143,17 @@ every change applied to the file simple.py will be automatically updated in Sage Can I use SageMath with Python 3.x? """"""""""""""""""""""""""""""""""" -Currently, no (February 2017). Work in progress aims to allow this in -the not-so-far future. Until this task is completed, SageMath will continue -to use Python 2.x. +Currently, (September 2018) you can build the source code of SageMath with +Python 3 using the instructions at the bottom of +https://wiki.sagemath.org/Python3-compatible%20code + +Beware that this is still at an experimental stage. + +Work in progress aims to allow usage of Python 3 in the not-so-far +future. Until this task is completed, SageMath will continue to use +Python 2.x. -See :trac:`15530` for tracking the current progress. +See :trac:`15530` and :trac:`26212` for tracking the current progress. I'm seeing an error about "Permission denied" on a file called "sage-flags.txt". """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" @@ -209,7 +212,7 @@ from the `download page `_. If you would like to help with updating the Debian/Ubuntu version of Sage, please email the -`sage-devel `_ +`sage-devel `_ mailing list. @@ -218,9 +221,9 @@ Should I use the official version or development version? You are encouraged to use the latest official version of Sage. Development versions are frequently announced on the -`sage-devel `_ +`sage-devel `_ and -`sage-release `_ +`sage-release `_ mailing lists. An easy way of helping out with Sage development is to download the latest development release, compile it on your system, run all doctests, and report any compilation errors or doctest @@ -242,9 +245,9 @@ by a web search. * `Dive into Python `_ by Mark Pilgrim * `How to Think Like a Computer Scientist `_ by Jeffrey Elkner, Allen B. Downey, and Chris Meyers -* `Official Python Tutorial `_ +* `Official Python Tutorial `_ * `Python `_ home page and the - `Python standard documentation `_ + `Python standard documentation `_ Can I do X in Sage? @@ -273,7 +276,7 @@ When you type "0.6**2" in Python, it returns something like 0.35999999999999999. But when you do the same in Sage it returns 0.360000000000000. To understand why Python behaves as it does, see the -`Python Tutorial `_, +`Python Tutorial `_, especially the chapter "Floating Point Arithmetic: Issues and Limitations". What Sage does is "preparse" the input and transforms it like this:: @@ -305,7 +308,7 @@ arrow key and then press down arrow key, then the next line in history is fetched. This feature allows you to fetch as many successive lines in history as you like. However, Sage does not have a similar feature. The -`IPython `_ +`IPython `_ command prompt uses the readline library (via pyreadline), which evidently does not support this feature. Magma has its own custom "readline-like" library, which does support this feature. (Since so @@ -643,7 +646,7 @@ methods mentioned in the plot documentation, but this one is easiest:: The *reason* this is necessary is that Sage returns complex numbers for odd roots of negative numbers when numerically approximated, which -is a `standard convention `_. +is a `standard convention `_. sage: N((-1)^(1/3)) 0.500000000000000 + 0.866025403784439*I diff --git a/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst b/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst index 01df754243b..6b6cb718433 100644 --- a/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst +++ b/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst @@ -352,7 +352,7 @@ Two of the most useful of these options help in labeling graphs. :: sage: P1 = plot(f,(x,-1,1),axes_labels=['$x$','$y$'],legend_label='$f(x)$') - sage: P2 = plot(sin,(x,-1,1),axes_labels=['$x$','$y$'],legend_label='$\sin(x)$',color='red') + sage: P2 = plot(sin,(x,-1,1),axes_labels=['$x$','$y$'],legend_label=r'$\sin(x)$',color='red') sage: P1+P2 Graphics object consisting of 2 graphics primitives diff --git a/src/doc/en/reference/algebras/lie_algebras.rst b/src/doc/en/reference/algebras/lie_algebras.rst index d6b1d3103b9..79c46e8bfb7 100644 --- a/src/doc/en/reference/algebras/lie_algebras.rst +++ b/src/doc/en/reference/algebras/lie_algebras.rst @@ -6,6 +6,7 @@ Lie Algebras sage/algebras/lie_algebras/abelian sage/algebras/lie_algebras/affine_lie_algebra + sage/algebras/lie_algebras/bch sage/algebras/lie_algebras/classical_lie_algebra sage/algebras/lie_algebras/examples sage/algebras/lie_algebras/free_lie_algebra diff --git a/src/doc/en/reference/function_fields/index.rst b/src/doc/en/reference/function_fields/index.rst index 43442145901..fa39b438361 100644 --- a/src/doc/en/reference/function_fields/index.rst +++ b/src/doc/en/reference/function_fields/index.rst @@ -2,15 +2,17 @@ Algebraic Function Fields ========================= Sage allows basic computations with elements and ideals in orders of -algebraic function fields over arbitrary constant fields. +algebraic function fields over arbitrary constant fields. Advanced +computations, like computing the genus or a basis of the Riemann-Roch space of +a divisor, are available for global function fields. .. toctree:: :maxdepth: 1 sage/rings/function_field/function_field - sage/rings/function_field/function_field_element - sage/rings/function_field/function_field_order - sage/rings/function_field/function_field_ideal + sage/rings/function_field/element + sage/rings/function_field/order + sage/rings/function_field/ideal sage/rings/function_field/maps sage/rings/function_field/constructor diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 88843d1e3f9..13beb136e9c 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -51,6 +51,12 @@ REFERENCES: and T. Yalcin, *Block ciphers - focus on the linear layer (feat. PRIDE)*; in CRYPTO, (2014), pp. 57-76. +.. [AGHJLPR2017] Benjamin Assarf, Ewgenij Gawrilow, Katrin Herr, Michael Joswig, + Benjamin Lorenz, Andreas Paffenholz, and Thomas Rehn, + Computing convex hulls and counting integer points with + polymake, Math. Program. Comput. 9 (2017), no. 1, 1–38, + https://doi.org/10.1007/s12532-016-0104-z + .. [AguSot05] Marcelo Aguiar and Frank Sottile, *Structure of the Malvenuto-Reutenauer Hopf algebra of permutations*, @@ -694,6 +700,9 @@ REFERENCES: .. [CL2013] Maria Chlouveraki and Sofia Lambropoulou. *The Yokonuma-Hecke algebras and the HOMFLYPT polynomial*. (2015) :arxiv:`1204.1871v4`. + +.. [Cle1872] Alfred Clebsch, *Theorie der binären algebraischen Formen*, + Teubner, 1872. .. [CLRS2001] Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein, *Section 22.4: Topological sort*, @@ -1218,15 +1227,36 @@ REFERENCES: *On the category `\mathcal{O}` for rational Cherednik algebras*. Invent. Math. **154** (2003). :arxiv:`math/0212036`. +.. [GHJ2016] Ewgenij Gawrilow, Simon Hampe, and Michael Joswig, The polymake XML + file format, Mathematical software – ICMS 2016. 5th international + congress, Berlin, Germany, July 11–14, 2016. Proceedings, Berlin: + Springer, 2016, pp. 403–410, + https://doi.org/10.1007/978-3-319-42432-3_50, ISBN + 978-3-319-42431-6/pbk. + .. [GHJV1994] \E. Gamma, R. Helm, R. Johnson, J. Vlissides, *Design Patterns: Elements of Reusable Object-Oriented Software*. Addison-Wesley (1994). ISBN 0-201-63361-2. +.. [GJ1997] Ewgenij Gawrilow and Michael Joswig, polymake: a framework for + analyzing convex polytopes, Polytopes—combinatorics and + computation (Oberwolfach, 1997), DMV Sem., vol. 29, Birkhäuser, + Basel, 2000, pp. 43–73. + +.. [GJ2006] Ewgenij Gawrilow and Michael Joswig, Flexible object hierarchies in + polymake (extended abstract), Mathematical software—ICMS 2006, + Lecture Notes in Comput. Sci., vol. 4151, Springer, Berlin, 2006, + pp. 219–221, https://doi.org/10.1007/11832225_20 + .. [GJK+2014] Dimitar Grantcharov, Ji Hye Jung, Seok-Jin Kang, Masaki Kashiwara, Myungho Kim. *Crystal bases for the quantum queer superalgebra and semistandard decomposition tableaux.*; Trans. Amer. Math. Soc., 366(1): 457-489, 2014. :arxiv:`1103.1456v2`. +.. [GJRW2010] Ewgenij Gawrilow, Michael Joswig, Thilo Rörig, and Nikolaus Witte, + Drawing polytopal graphs with polymake, Comput. Vis. Sci. 13 + (2010), no. 2, 99–110, https://doi.org/10.1007/s00791-009-0127-3 + .. [GK2013] Roland Grinis and Alexander Kasprzyk, Normal forms of convex lattice polytopes, :arxiv:`1301.6641` @@ -1562,6 +1592,12 @@ REFERENCES: .. [JL2016] \M. Jones and L. Lapointe. *Pieri rules for Schur functions in superspace*. Preprint, :arxiv:`1608.08577` +.. [JMP2009] Michael Joswig, Benjamin Müller, and Andreas Paffenholz, polymake + and lattice polytopes, 21st International Conference on Formal + Power Series and Algebraic Combinatorics (FPSAC 2009), Discrete + Math. Theor. Comput. Sci. Proc., AK, Assoc. Discrete + Math. Theor. Comput. Sci., Nancy, 2009, pp. 491–502 + .. [Joh1990] \D.L. Johnson. *Presentations of Groups*. Cambridge University Press. (1990). @@ -2865,6 +2901,10 @@ REFERENCES: Manifolds*, Springer Basel AG (Basel) (1994); :doi:`10.1007/978-3-0348-8495-2` +.. [Var1984] V. S. Varadarajan. *Lie groups, Lie algebras, and their + representations*. Reprint of the 1974 edition. Graduate Texts in + Mathematics, 102. Springer-Verlag, New York, 1984. + .. [Vat2008] \D. Vatne, *The mutation class of `D_n` quivers*, :arxiv:`0810.4789v1`. .. [Vazirani2002] Monica Vazirani. *Parameterizing Hecek algebra modules: diff --git a/src/doc/en/reference/sat/index.rst b/src/doc/en/reference/sat/index.rst index 0d5bccd9f6a..4090e8773c9 100644 --- a/src/doc/en/reference/sat/index.rst +++ b/src/doc/en/reference/sat/index.rst @@ -86,6 +86,7 @@ Details on Specific Solvers sage/sat/solvers/satsolver sage/sat/solvers/dimacs + sage/sat/solvers/picosat sage/sat/solvers/sat_lp sage/sat/solvers/cryptominisat diff --git a/src/doc/en/thematic_tutorials/coercion_and_categories.rst b/src/doc/en/thematic_tutorials/coercion_and_categories.rst index 19da25edc7a..6ce0cd24113 100644 --- a/src/doc/en/thematic_tutorials/coercion_and_categories.rst +++ b/src/doc/en/thematic_tutorials/coercion_and_categories.rst @@ -539,8 +539,10 @@ methods are place-holders: There is no default implementation, but it is sage: from sage.misc.abstract_method import abstract_methods_of_class sage: abstract_methods_of_class(QuotientFields().element_class)['optional'] ['_add_', '_mul_'] - sage: abstract_methods_of_class(QuotientFields().element_class)['required'] + sage: abstract_methods_of_class(QuotientFields().element_class)['required'] # py2 ['__nonzero__', 'denominator', 'numerator'] + sage: abstract_methods_of_class(QuotientFields().element_class)['required'] # py3 + ['__bool__', 'denominator', 'numerator'] Hence, when implementing elements of a quotient field, it is *required* to implement methods returning the denominator and the numerator, and a method @@ -1493,8 +1495,10 @@ The elements have to provide more:: sage: abstract_methods_of_class(QuotientFields().element_class)['optional'] ['_add_', '_mul_'] - sage: abstract_methods_of_class(QuotientFields().element_class)['required'] + sage: abstract_methods_of_class(QuotientFields().element_class)['required'] # py2 ['__nonzero__', 'denominator', 'numerator'] + sage: abstract_methods_of_class(QuotientFields().element_class)['required'] # py3 + ['__bool__', 'denominator', 'numerator'] .. end of output diff --git a/src/doc/en/thematic_tutorials/functional_programming.rst b/src/doc/en/thematic_tutorials/functional_programming.rst index 4571800917b..402e1e5a7b9 100644 --- a/src/doc/en/thematic_tutorials/functional_programming.rst +++ b/src/doc/en/thematic_tutorials/functional_programming.rst @@ -82,7 +82,12 @@ Functional programming using map Functional programming is yet another style of programming in which a program is decomposed into various functions. The Python built-in functions ``map``, ``reduce`` and ``filter`` allow you to program in -the functional style. The function :: +the functional style. Note that in Python 3 (as compared to Python 2), +these functions have different behaviors, and ``reduce`` has been +removed: if you want to use ``reduce`` in Python 3, you must import it +from ``functools``. + +The function :: map(func, seq1, seq2, ...) @@ -109,7 +114,7 @@ Alternatively, you could use the Python built-in addition function sage: from operator import add sage: A = [1, 2, 3, 4] sage: B = [2, 3, 5, 7] - sage: map(add, A, B) + sage: list(map(add, A, B)) [3, 5, 8, 11] An advantage of ``map`` is that you do not need to explicitly define @@ -146,8 +151,8 @@ integers and ``M`` is a list of moduli. :: sage: def crt(A, M): ....: Mprod = prod(M) - ....: Mdiv = map(lambda x: Integer(Mprod / x), M) - ....: X = map(inverse_mod, Mdiv, M) + ....: Mdiv = list(map(lambda x: Integer(Mprod / x), M)) + ....: X = list(map(inverse_mod, Mdiv, M)) ....: x = sum([A[i]*X[i]*Mdiv[i] for i in range(len(A))]) ....: return mod(x, Mprod).lift() ... @@ -240,6 +245,7 @@ uses ``reduce`` and the built-in function ``operator.add`` to add together all integers in a given list. This is followed by using ``sum`` to accomplish the same task:: + sage: from functools import reduce # py3 sage: from operator import add sage: L = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] sage: reduce(add, L) @@ -255,6 +261,7 @@ is then implemented using the functions ``operator.add`` and ``reduce`` and ``map``. We then show how ``sum`` and ``map`` could be combined to produce the same result. :: + sage: from functools import reduce # py3 sage: from operator import add sage: from operator import mul sage: U = [1, 2, 3] @@ -276,10 +283,11 @@ using ``sum`` as was done previously. The version below uses ``operator.add`` and defines ``mul3`` to multiply three numbers instead of two. :: + sage: from functools import reduce # py3 sage: def crt(A, M): ....: from operator import add ....: Mprod = prod(M) - ....: Mdiv = map(lambda x: Integer(Mprod / x), M) + ....: Mdiv = list(map(lambda x: Integer(Mprod / x), M)) ....: X = map(inverse_mod, Mdiv, M) ....: mul3 = lambda a, b, c: a * b * c ....: x = reduce(add, map(mul3, A, X, Mdiv)) diff --git a/src/doc/en/thematic_tutorials/tutorial-implementing-algebraic-structures.rst b/src/doc/en/thematic_tutorials/tutorial-implementing-algebraic-structures.rst index f8fc3c7036e..c9a353b761a 100644 --- a/src/doc/en/thematic_tutorials/tutorial-implementing-algebraic-structures.rst +++ b/src/doc/en/thematic_tutorials/tutorial-implementing-algebraic-structures.rst @@ -100,9 +100,12 @@ methods ``product_on_basis``, ``one_basis``, ``_repr_`` and ask the category (TODO: find a slicker idiom for this):: sage: from sage.misc.abstract_method import abstract_methods_of_class - sage: abstract_methods_of_class(AlgebrasWithBasis(QQ).element_class) + sage: abstract_methods_of_class(AlgebrasWithBasis(QQ).element_class) # py2 {'optional': ['_add_', '_mul_'], 'required': ['__nonzero__', 'monomial_coefficients']} + sage: abstract_methods_of_class(AlgebrasWithBasis(QQ).element_class) # py3 + {'optional': ['_add_', '_mul_'], + 'required': ['__bool__', 'monomial_coefficients']} sage: abstract_methods_of_class(AlgebrasWithBasis(QQ).parent_class) {'optional': ['one_basis', 'product_on_basis'], 'required': ['__contains__']} diff --git a/src/doc/en/thematic_tutorials/tutorial-programming-python.rst b/src/doc/en/thematic_tutorials/tutorial-programming-python.rst index 4e96d9c1444..f0db90cdd6a 100644 --- a/src/doc/en/thematic_tutorials/tutorial-programming-python.rst +++ b/src/doc/en/thematic_tutorials/tutorial-programming-python.rst @@ -671,7 +671,7 @@ to use each item once. sage: g[0] Traceback (most recent call last): ... - TypeError: 'generator' object has no attribute '__getitem__' + TypeError: 'generator' object ... :: @@ -1112,7 +1112,7 @@ How does this work? :: sage: it = iter(GF(5)); it - + sage: next(it) 0 diff --git a/src/ext/singular/function_field/core.lib b/src/ext/singular/function_field/core.lib new file mode 100644 index 00000000000..42871de7676 --- /dev/null +++ b/src/ext/singular/function_field/core.lib @@ -0,0 +1,98 @@ +////////////////////////////////////////////////////////////////////////////// +version="version core.lib 1.0 March 2016"; +category="Commutative Algebra"; +info="LIBRARY: core.lib Core commutative algebra algorithms +AUTHORS: Kwankyu Lee, ekwankyu@gmail.com + +This library provides core algorithms for function fields in Sage. + +PROCEDURES: +core_normalize(I,[...]): Normalization of an affine ring over a prime finite + field. The code is excerpted from the normalP procedure in standard + Singular library normal.lib. As noted there, this procedure uses the + Leonard-Pellikaan-Singh-Swanson algorithm cf. [A. K. Singh, I. + Swanson: An algorithm for computing the integral closure, + arXiv:0901.0871] +"; +////////////////////////////////////////////////////////////////////////////// + +proc core_normalize(ideal id) +"USAGE: core_normalize(id) where id is a prime ideal of the basering R. +ASSUME: The ground field of R must be a prime finite field. +RETURN: Ideal l of R. If l=[l1,l2,...,lr], then l1/lr,l2/lr,...,1 are the module +generators of the integral closure of the ring R/id in its quotient field. +" +{ + def R = basering; + int n, p = nvars(R), char(R); + + int ii; + + if( size(id) == 0 ) { // id is a zero ideal. Then R=R/id is a UFD. + ideal U = 1; + return(U); + } + + list mstdid = mstd(id); + ideal J = mstdid[1]; // standard basis of the ideal id + ideal I = mstdid[2]; // generating set of the ideal id + int h = n - dim(J); // codimension of V(I), I is a prime ideal + + // compute the ideal of the singular locus + qring Q = J; // pass to quotient ring + ideal I = imap(R,I); + ideal J = imap(R,J); + + ideal M = minor(jacob(I),h,std(J)); // keep only minors not zero modulo J + M = std(M); + + // choose "shortest" nonzerodivisor of the ideal + ideal D = M[1]; + for( ii = 2; ii <= size(M); ii++ ) { + if( size(M[ii]) < size(D[1]) ) { + D = M[ii]; + } + } + + // start p-th power algorithm + ideal F = var(1)^p; + for( ii = 2; ii <= n; ii++ ) { + F = F,var(ii)^p; + } + + ideal Dp = D^(p-1); + ideal U = 1; + ideal K,L; + map phi = Q,F; // p-th power map from Q to Q + + list LK; + while(1) { + L = U*Dp + I; + K = preimage(Q,phi,L); + K = intersect(U,K); + LK = mstd(K); + K = LK[2]; // new U + + if( size(reduce(U,LK[1])) == 0 ) { // new U equals U + U = reduce(U,std(D)),D; // denominator comes last + U = simplify(U,6); // remove zero and repetitive generators + poly gg = gcd(U[1],U[size(U)]); + for( ii = 2; ii < size(U); ii++ ) { + gg = gcd(gg,U[ii]); + } + for( ii = 1; ii <= size(U); ii++ ) { + U[ii] = U[ii] / gg; + } + setring R; + ideal U = imap(Q,U); + return(U); + } + U = K; + } +} +example +{ "EXAMPLE:"; echo = 2; + ring r = 11,(x,y,z),wp(2,1,2); + ideal id = x*(z3 - xy4 + x2); + ideal c = core_normalize(id); +} diff --git a/src/module_list.py b/src/module_list.py index 1f4877a74f2..cba9b23560f 100644 --- a/src/module_list.py +++ b/src/module_list.py @@ -1245,8 +1245,8 @@ def uname_specific(name, value, alternative): ## ################################ - Extension('sage.rings.function_field.function_field_element', - sources = ['sage/rings/function_field/function_field_element.pyx']), + Extension('sage.rings.function_field.element', + sources = ['sage/rings/function_field/element.pyx']), ################################ ## diff --git a/src/sage/algebras/lie_algebras/bch.py b/src/sage/algebras/lie_algebras/bch.py new file mode 100644 index 00000000000..c2243767009 --- /dev/null +++ b/src/sage/algebras/lie_algebras/bch.py @@ -0,0 +1,175 @@ +r""" +The Baker-Campbell-Hausdorff formula + +AUTHORS: + +- Eero Hakavuori (2018-09-23): initial version +""" + +# **************************************************************************** +# Copyright (C) 2018 Eero Hakavuori +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.algebras.lie_algebras.lie_algebra import LieAlgebra +from sage.arith.misc import bernoulli +from sage.categories.lie_algebras import LieAlgebras +from sage.combinat.integer_vector import IntegerListsLex +from sage.functions.other import factorial +from sage.rings.rational_field import QQ +from sage.structure.element import canonical_coercion + + +def bch_iterator(X=None, Y=None): + r""" + A generator function which returns successive terms of the + Baker-Campbell-Hausdorff formula. + + INPUT: + + - ``X`` -- (optional) an element of a Lie algebra + - ``Y`` -- (optional) an element of a Lie algebra + + The BCH formula is an expression for `\log(\exp(X)\exp(Y))` as a sum of Lie + brackets of ``X`` and ``Y`` with rational coefficients. In arbitrary Lie + algebras, the infinite sum is only guaranteed to converge for ``X`` and + ``Y`` close to zero. + + If the elements ``X`` and ``Y`` are not given, then the iterator will + return successive terms of the abstract BCH formula, i.e., the BCH formula + for the generators of the free Lie algebra on 2 generators. + + If the Lie algebra containing ``X`` and ``Y`` is not nilpotent, the + iterator will output infinitely many elements. If the Lie algebra is + nilpotent, the number of elements outputted is equal to the nilpotency step. + + EXAMPLES: + + The terms of the abstract BCH formula up to fifth order brackets:: + + sage: from sage.algebras.lie_algebras.bch import bch_iterator + sage: bch = bch_iterator() + sage: next(bch) + X + Y + sage: next(bch) + 1/2*[X, Y] + sage: next(bch) + 1/12*[X, [X, Y]] + 1/12*[[X, Y], Y] + sage: next(bch) + 1/24*[X, [[X, Y], Y]] + sage: next(bch) + -1/720*[X, [X, [X, [X, Y]]]] + 1/180*[X, [X, [[X, Y], Y]]] + + 1/360*[[X, [X, Y]], [X, Y]] + 1/180*[X, [[[X, Y], Y], Y]] + + 1/120*[[X, Y], [[X, Y], Y]] - 1/720*[[[[X, Y], Y], Y], Y] + + For nilpotent Lie algebras the BCH formula only has finitely many terms:: + + sage: L = LieAlgebra(QQ, 2, step=3) + sage: L.inject_variables() + Defining X_1, X_2, X_12, X_112, X_122 + sage: [Z for Z in bch_iterator(X_1, X_2)] + [X_1 + X_2, 1/2*X_12, 1/12*X_112 + 1/12*X_122] + sage: [Z for Z in bch_iterator(X_1 + X_2, X_12)] + [X_1 + X_2 + X_12, 1/2*X_112 - 1/2*X_122, 0] + + The elements ``X`` and ``Y`` don't need to be elements of the same Lie + algebra if there is a coercion from one to the other:: + + sage: L = LieAlgebra(QQ, 3, step=2) + sage: L.inject_variables() + Defining X_1, X_2, X_3, X_12, X_13, X_23 + sage: S = L.subalgebra(X_1, X_2) + sage: bch1 = [Z for Z in bch_iterator(S(X_1), S(X_2))]; bch1 + [X_1 + X_2, 1/2*X_12] + sage: bch1[0].parent() == S + True + sage: bch2 = [Z for Z in bch_iterator(S(X_1), X_3)]; bch2 + [X_1 + X_3, 1/2*X_13] + sage: bch2[0].parent() == L + True + + The BCH formula requires a coercion from the rationals:: + + sage: L. = LieAlgebra(ZZ, 2, step=2) + sage: bch = bch_iterator(X, Y); next(bch) + Traceback (most recent call last): + ... + TypeError: the BCH formula is not well defined since Integer Ring has no coercion from Rational Field + + TESTS: + + Compare to the BCH formula up to degree 5 given by wikipedia:: + + sage: from sage.algebras.lie_algebras.bch import bch_iterator + sage: bch = bch_iterator() + sage: L. = LieAlgebra(QQ) + sage: L = L.Lyndon() + sage: computed_BCH = L.sum(next(bch) for k in range(5)) + sage: wikiBCH = X + Y + 1/2*L[X,Y] + 1/12*(L[X,[X,Y]] + L[Y,[Y,X]]) + sage: wikiBCH += -1/24*L[Y,[X,[X,Y]]] + sage: wikiBCH += -1/720*(L[Y,[Y,[Y,[Y,X]]]] + L[X,[X,[X,[X,Y]]]]) + sage: wikiBCH += 1/360*(L[X,[Y,[Y,[Y,X]]]] + L[Y,[X,[X,[X,Y]]]]) + sage: wikiBCH += 1/120*(L[Y,[X,[Y,[X,Y]]]] + L[X,[Y,[X,[Y,X]]]]) + sage: computed_BCH == wikiBCH + True + + ALGORITHM: + + The BCH formula `\log(\exp(X)\exp(Y)) = \sum_k Z_k` is computed starting + from `Z_1 = X + Y`, by the recursion + + .. MATH:: + + (m+1)Z_{m+1} = \frac{1}{2}[X - Y, Z_m] + + \sum_{2\leq 2p \leq m}\frac{B_{2p}}{(2p)!}\sum_{k_1+\cdots+k_{2p}=m} + [Z_{k_1}, [\cdots [Z_{k_{2p}}, X + Y]\cdots], + + where `B_{2p}` are the Bernoulli numbers, see Lemma 2.15.3. in [Var1984]_. + + .. WARNING:: + + The time needed to compute each successive term increases exponentially. + For example on one machine iterating through `Z_{11},...,Z_{18}` for a + free Lie algebra, computing each successive term took 4-5 times longer, + going from 0.1s for `Z_{11}` to 21 minutes for `Z_{18}`. + """ + if X is None or Y is None: + L = LieAlgebra(QQ, ['X', 'Y']).Lyndon() + X, Y = L.lie_algebra_generators() + else: + X, Y = canonical_coercion(X, Y) + L = X.parent() + + R = L.base_ring() + if not R.has_coerce_map_from(QQ): + raise TypeError("the BCH formula is not well defined since %s " + "has no coercion from %s" % (R, QQ)) + + xdif = X - Y + Z = [0, X + Y] # 1-based indexing for convenience + m = 1 + yield Z[1] + + while True: + m += 1 + if L in LieAlgebras.Nilpotent and m > L.step(): + return + + # apply the recursion formula of [Var1984] + Zm = ~QQ(2 * m) * xdif.bracket(Z[-1]) + for p in range(1, (m - 1) // 2 + 1): + partitions = IntegerListsLex(m - 1, length=2 * p, min_part=1) + coeff = bernoulli(2 * p) / QQ(m * factorial(2 * p)) + for kvec in partitions: + W = Z[1] + for k in kvec: + W = Z[k].bracket(W) + Zm += coeff * W + + Z.append(Zm) + yield Zm diff --git a/src/sage/algebras/lie_algebras/quotient.py b/src/sage/algebras/lie_algebras/quotient.py index 2184ef04da2..3fe674b0935 100644 --- a/src/sage/algebras/lie_algebras/quotient.py +++ b/src/sage/algebras/lie_algebras/quotient.py @@ -119,7 +119,7 @@ class LieQuotient_finite_dimensional_with_basis(LieAlgebraWithStructureCoefficie Defining Y_1, Y_2, Y_3, Y_4, Y_5 sage: lcs = Q.lower_central_series() sage: [I.basis().list() for I in lcs] - [[Y_1, Y_2, Y_3, Y_4, Y_5], [Y_3, Y_4, Y_5], [Y_4, Y_5], [Y_5], []] + [[Y_1, Y_2, Y_3, Y_4, Y_5], [Y_5, Y_4, Y_3], [Y_5, Y_4], [Y_5], []] sage: Y_2.bracket(Y_3) Y_5 @@ -153,6 +153,17 @@ class LieQuotient_finite_dimensional_with_basis(LieAlgebraWithStructureCoefficie sage: all([X.parent() is L for X, L in zip(lifts,quots)]) True + Verify a quotient construction when the basis ordering and indices ordering + are different, see :trac:`26352`:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: I2 = L.ideal([a+b, a+c]) + sage: I2.basis() + Family (b + a, c + a) + sage: Q = L.quotient(I2) + sage: Q.basis() + Finite family {'a': a} + A test suite:: sage: L. = LieAlgebra(QQ, 2, step=3) @@ -201,7 +212,7 @@ def __classcall_private__(cls, I, ambient=None, names=None, "not implemented") # extract an index set from a complementary basis to the ideal - I_supp = [I.lift(X).leading_support() for X in I.basis()] + I_supp = [X.leading_support() for X in I.leading_monomials()] inv = ambient.basis().inverse_family() sorted_indices = [inv[X] for X in ambient.basis()] index_set = [i for i in sorted_indices if i not in I_supp] diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index c802b7fd1b8..4b6faa08e7f 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -22,7 +22,6 @@ from sage.categories.morphism import SetMorphism from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute -from sage.modules.free_module_element import vector from sage.sets.family import Family from sage.sets.finite_enumerated_set import FiniteEnumeratedSet from sage.structure.parent import Parent @@ -39,6 +38,7 @@ class LieSubalgebra_finite_dimensional_with_basis(Parent, UniqueRepresentation): - ``gens`` -- a list of generators of the subalgebra - ``ideal`` -- (default: ``False``) a boolean; if ``True``, then ``gens`` is interpreted as the generating set of an ideal instead of a subalgebra + - ``order`` -- (optional) the key used to sort the indices of ``ambient`` - ``category`` -- (optional) a subcategory of subobjects of finite dimensional Lie algebras with basis @@ -96,15 +96,23 @@ class LieSubalgebra_finite_dimensional_with_basis(Parent, UniqueRepresentation): sage: I.reduce(el) (x-y)*X + Giving a different ``order`` may change the reduction of elements:: + + sage: I = L.ideal(X + Y, order=lambda s: ['Z','Y','X'].index(s)) + sage: I.basis() + Family (X + Y, Z) + sage: I.reduce(el) + (-x+y)*Y + A subalgebra of a subalgebra is a subalgebra of the original:: sage: sc = {('X','Y'): {'Z': 1}, ('X','Z'): {'W': 1}} sage: L. = LieAlgebra(QQ, sc) sage: S1 = L.subalgebra([Y, Z, W]); S1 Subalgebra generated by (Y, Z, W) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field - sage: S2 = S1.subalgebra(S1.basis()[1:]); S2 + sage: S2 = S1.subalgebra(S1.gens()[1:]); S2 Subalgebra generated by (Z, W) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field - sage: S3 = S2.subalgebra(S2.basis()[1:]); S3 + sage: S3 = S2.subalgebra(S2.gens()[1:]); S3 Subalgebra generated by (W) of Lie algebra on 4 generators (X, Y, Z, W) over Rational Field An ideal of an ideal is not necessarily an ideal of the original:: @@ -119,7 +127,7 @@ class LieSubalgebra_finite_dimensional_with_basis(Parent, UniqueRepresentation): False sage: K = L.ideal(J.basis().list()) sage: K.basis() - Family (Z, W) + Family (W, Z) TESTS: @@ -156,7 +164,8 @@ class LieSubalgebra_finite_dimensional_with_basis(Parent, UniqueRepresentation): """ @staticmethod - def __classcall_private__(cls, ambient, gens, ideal=False, category=None): + def __classcall_private__(cls, ambient, gens, ideal=False, + order=None, category=None): """ Normalize input to ensure a unique representation. @@ -200,9 +209,9 @@ def __classcall_private__(cls, ambient, gens, ideal=False, category=None): category = category.Nilpotent() sup = super(LieSubalgebra_finite_dimensional_with_basis, cls) - return sup.__classcall__(cls, ambient, gens, ideal, category) + return sup.__classcall__(cls, ambient, gens, ideal, order, category) - def __init__(self, ambient, gens, ideal, category=None): + def __init__(self, ambient, gens, ideal, order=None, category=None): r""" Initialize ``self``. @@ -218,6 +227,16 @@ def __init__(self, ambient, gens, ideal, category=None): self._gens = gens self._is_ideal = ideal + # initialize helper variables for ordering + if order is None: + order = lambda x: x + self._order = order + self._reversed_indices = sorted(ambient.indices(), key=order, + reverse=True) + # helper to reorder a vector that has been jumbled by the above + self._reorganized_indices = [self._reversed_indices.index(i) + for i in ambient.indices()] + sup = super(LieSubalgebra_finite_dimensional_with_basis, self) sup.__init__(ambient.base_ring(), category=category) @@ -386,17 +405,23 @@ def _element_constructor_(self, x): sup = super(LieSubalgebra_finite_dimensional_with_basis, self) return sup._element_constructor_(x) - # for submodule computations, the order of the basis is reversed so that - # the pivot elements in the echelon form are the leading terms def _to_m(self, X): r""" - Return the reversed vector of an element of the ambient Lie algebra. + Return the vector of an element of the ambient Lie algebra with indices + reorganized in decreasing order according to ``self._order``. + + This is used internally for submodule computations, so that the pivot + elements in the echelon form are given by + :meth:`~sage.categories.modules_with_basis.ModulesWithBasis.ElementMethods.leading_term` + using the sort key ``self._order``. INPUT: - ``X`` -- an element of the ambient Lie algebra - EXAMPLES:: + EXAMPLES: + + If the basis is in increasing order, it is reversed:: sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) sage: I = L.ideal([x, z]) @@ -405,26 +430,65 @@ def _to_m(self, X): (1, 2, 3) sage: I._to_m(el) (3, 2, 1) + + Otherwise the components can have a more complicated permutation:: + + sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) + sage: I = L.ideal([x, z]) + sage: el = x + 2*z + 3*y + sage: el.to_vector() + (1, 2, 3) + sage: I._to_m(el) + (2, 3, 1) """ - return vector(self.ambient().base_ring(), reversed(X.to_vector())) + mc = X.monomial_coefficients() + M = self.ambient().module() + R = M.base_ring() + B = M.basis() + return M.sum(R(mc[self._reversed_indices[i]]) * B[i] + for i in range(len(B)) if self._reversed_indices[i] in mc) def _from_m(self, v): r""" - Return the element of the ambient Lie algebra from a reversed vector. + Return the element of the ambient Lie algebra from a reorganized vector. + + This is used internally for submodule computations, so that the pivot + elements in the echelon form are given by + :meth:`~sage.categories.modules_with_basis.ModulesWithBasis.ElementMethods.leading_term` + using the sort key `self._order`. INPUT: - ``v`` -- a vector - EXAMPLES:: + EXAMPLES: + + Unscrambling of a vector when the basis is in increasing order:: sage: L. = LieAlgebra(QQ, {('x','y'): {'z': 1}}) sage: I = L.ideal([x, z]) sage: I._from_m([3, 2, 1]) x + 2*y + 3*z + + The map is the inverse of :meth:`_to_m`:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: S = L.subalgebra(L.basis().list()) + sage: v = S._to_m(c + 2*a + 3*e + 4*f + 5*b + 6*d); v + (4, 3, 6, 1, 5, 2) + sage: S._from_m(v) + c + 2*a + 3*e + 4*f + 5*b + 6*d + sage: all(S._from_m(S._to_m(X)) == X for X in L.some_elements()) + True """ - R = self.ambient().base_ring() - return self.ambient().from_vector(vector(R, reversed(v))) + L = self.ambient() + M = L.module() + R = M.base_ring() + v_self = M.coordinate_vector(v) + B = M.basis() + v_sorted = M.sum(R(v_self[self._reorganized_indices[i]]) * B[i] + for i in range(len(B))) + return L.from_vector(v_sorted) @lazy_attribute def _indices(self): @@ -442,6 +506,21 @@ def _indices(self): """ return FiniteEnumeratedSet(self.basis().keys()) + def indices(self): + r""" + Return the set of indices for the basis of ``self``. + + EXAMPLES:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: S = L.subalgebra([x, y]) + sage: S.indices() + {0, 1} + sage: [S.basis()[k] for k in S.indices()] + [x, y] + """ + return self._indices + @cached_method def zero(self): r""" @@ -576,25 +655,23 @@ def basis(self): A basis of a subalgebra:: - sage: sc = {('x','y'): {'z': 1}, ('x','z'): {'w': 1}} - sage: L. = LieAlgebra(QQ, sc) - sage: L.subalgebra([x + y, z + w]).basis() - Family (x + y, z, w) + sage: sc = {('a','b'): {'c': 1}, ('a','c'): {'d': 1}} + sage: L. = LieAlgebra(QQ, sc) + sage: L.subalgebra([a + b, c + d]).basis() + Family (a + b, c, d) A basis of an ideal:: sage: sc = {('x','y'): {'z': 1}, ('x','z'): {'w': 1}} sage: L. = LieAlgebra(QQ, sc) sage: L.ideal([x + y + z + w]).basis() - Family (x + y, z, w) + Family (w, x + y, z) """ L = self.ambient() B = [self._to_m(X) for X in L.basis()] - # use ambient module in case L is an ideal or subalgebra - m = L.module().ambient_module() - + m = L.module() sm = m.submodule([self._to_m(X) for X in self.gens()]) d = 0 @@ -608,8 +685,39 @@ def basis(self): for v in B for w in SB] sm = m.submodule(sm.basis() + brackets) - return Family(reversed([self.element_class(self, self._from_m(v)) - for v in sm.echelonized_basis()])) + basis = [self.element_class(self, self._from_m(v)) + for v in sm.echelonized_basis()] + sortkey = lambda X: self.lift(X).leading_support(key=self._order) + return Family(sorted(basis, key=sortkey)) + + @cached_method + def leading_monomials(self): + r""" + Return the set of leading monomials of the basis of ``self``. + + EXAMPLES: + + A basis of an ideal and the corresponding leading monomials:: + + sage: sc = {('a','b'): {'c': 2}, ('a','c'): {'d': 4}} + sage: L. = LieAlgebra(ZZ, sc) + sage: I = L.ideal(a + b) + sage: I.basis() + Family (a + b, 2*c, 4*d) + sage: I.leading_monomials() + Family (b, c, d) + + A different ordering can give different leading monomials:: + + sage: key = lambda s: ['d','c','b','a'].index(s) + sage: I = L.ideal(a + b, order=key) + sage: I.basis() + Family (a + b, 2*c, 4*d) + sage: I.leading_monomials() + Family (a, c, d) + """ + return Family(self.lift(X).leading_monomial(key=self._order) + for X in self.basis()) def from_vector(self, v): r""" @@ -787,7 +895,7 @@ def reduce(self, X): R = self.base_ring() for Y in self.basis(): Y = self.lift(Y) - k, c = Y.leading_item() + k, c = Y.leading_item(key=self._order) if R.is_field(): X = X - X[k] / c * Y diff --git a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py index 9bb99f40fca..9a9016732c4 100644 --- a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py @@ -605,7 +605,7 @@ def subalgebra(self, *gens, **kwds): gens = gens[0] category = kwds.pop('category', None) return LieSubalgebra_finite_dimensional_with_basis( - self, gens, category=category) + self, gens, category=category, **kwds) def ideal(self, *gens, **kwds): r""" @@ -641,7 +641,7 @@ def ideal(self, *gens, **kwds): gens = gens[0] category = kwds.pop('category', None) return LieSubalgebra_finite_dimensional_with_basis( - self, gens, ideal=True, category=category) + self, gens, ideal=True, category=category, **kwds) @cached_method def is_ideal(self, A): diff --git a/src/sage/categories/lie_algebras.py b/src/sage/categories/lie_algebras.py index 487d53268eb..e54b7221841 100644 --- a/src/sage/categories/lie_algebras.py +++ b/src/sage/categories/lie_algebras.py @@ -607,6 +607,70 @@ def is_nilpotent(self): True """ + def bch(self, X, Y, prec=None): + r""" + Return the element `\log(\exp(X)\exp(Y))`. + + The BCH formula is an expression for `\log(\exp(X)\exp(Y))` + as a sum of Lie brackets of ``X ` and ``Y`` with rational + coefficients. It is only defined if the base ring of + ``self`` has a coercion from the rationals. + + INPUT: + + - ``X`` -- an element of ``self`` + - ``Y`` -- an element of ``self`` + - ``prec`` -- an integer; the maximum length of Lie brackets to be + considered in the formula + + EXAMPLES: + + The BCH formula for the generators of a free nilpotent Lie + algebra of step 4:: + + sage: L = LieAlgebra(QQ, 2, step=4) + sage: L.inject_variables() + Defining X_1, X_2, X_12, X_112, X_122, X_1112, X_1122, X_1222 + sage: L.bch(X_1, X_2) + X_1 + X_2 + 1/2*X_12 + 1/12*X_112 + 1/12*X_122 + 1/24*X_1122 + + An example of the BCH formula in a quotient:: + + sage: Q = L.quotient(X_112 + X_122) + sage: x, y = Q.basis().list()[:2] + sage: Q.bch(x, y) + X_1 + X_2 + 1/2*X_12 - 1/24*X_1112 + + The BCH formula for a non-nilpotent Lie algebra requires the + precision to be explicitly stated:: + + sage: L. = LieAlgebra(QQ) + sage: L.bch(X, Y) + Traceback (most recent call last): + ... + ValueError: the Lie algebra is not known to be nilpotent, so you must specify the precision + sage: L.bch(X, Y, 4) + X + 1/12*[X, [X, Y]] + 1/24*[X, [[X, Y], Y]] + 1/2*[X, Y] + 1/12*[[X, Y], Y] + Y + + The BCH formula requires a coercion from the rationals:: + + sage: L. = LieAlgebra(ZZ, 2, step=2) + sage: L.bch(X, Y) + Traceback (most recent call last): + ... + TypeError: the BCH formula is not well defined since Integer Ring has no coercion from Rational Field + """ + if self not in LieAlgebras.Nilpotent and prec is None: + raise ValueError("the Lie algebra is not known to be nilpotent," + " so you must specify the precision") + from sage.algebras.lie_algebras.bch import bch_iterator + if prec is None: + return self.sum(Z for Z in bch_iterator(X, Y)) + bch = bch_iterator(X, Y) + return self.sum(next(bch) for k in range(prec)) + + baker_campbell_hausdorff = bch + def _test_jacobi_identity(self, **options): """ Test that the Jacobi identity is satisfied on (not diff --git a/src/sage/combinat/chas/wqsym.py b/src/sage/combinat/chas/wqsym.py index 63bcaf7e1a6..ecbedb9fcd5 100644 --- a/src/sage/combinat/chas/wqsym.py +++ b/src/sage/combinat/chas/wqsym.py @@ -47,6 +47,7 @@ from sage.combinat.free_module import CombinatorialFreeModule from sage.combinat.set_partition_ordered import OrderedSetPartitions from sage.combinat.shuffle import ShuffleProduct_overlapping, ShuffleProduct +from sage.rings.integer_ring import ZZ class WQSymBasis_abstract(CombinatorialFreeModule, BindableClass): """ @@ -80,7 +81,7 @@ def _repr_term(self, osp): TESTS:: sage: M = WordQuasiSymmetricFunctions(QQ).M() - sage: elt = M[[1,2]]*M[[1]]; elt + sage: elt = M[[[1,2]]] * M[[[1]]]; elt M[{1, 2}, {3}] + M[{1, 2, 3}] + M[{3}, {1, 2}] sage: M.options.objects = "words" sage: elt @@ -99,7 +100,7 @@ def _repr_compositions(self, osp): EXAMPLES:: sage: M = WordQuasiSymmetricFunctions(QQ).M() - sage: elt = M[[1,2]] * M[[1]]; elt + sage: elt = M[[[1,2]]] * M[[[1]]]; elt M[{1, 2}, {3}] + M[{1, 2, 3}] + M[{3}, {1, 2}] sage: M.options.display = "tight" sage: elt @@ -134,7 +135,7 @@ def _repr_words(self, osp): EXAMPLES:: sage: M = WordQuasiSymmetricFunctions(QQ).M() - sage: elt = M[[1,2]]*M[[1]]; elt + sage: elt = M[[[1,2]]]*M[[[1]]]; elt M[{1, 2}, {3}] + M[{1, 2, 3}] + M[{3}, {1, 2}] sage: M.options.objects = "words" sage: elt @@ -297,14 +298,18 @@ class WordQuasiSymmetricFunctions(UniqueRepresentation, Parent): set of all `j \in \{1, 2, \ldots, n\}` such that `u_j = i`. The basis element `\mathbf{M}_u` is also denoted as `\mathbf{M}_P` - in this situation and is implemented using the latter indexing. - The basis `(\mathbf{M}_P)_P` is called the *Monomial basis* and - is implemented as + in this situation. The basis `(\mathbf{M}_P)_P` is called the + *Monomial basis* and is implemented as :class:`~sage.combinat.chas.wqsym.WordQuasiSymmetricFunctions.Monomial`. Other bases are the cone basis (aka C basis), the characteristic basis (aka X basis), the Q basis and the Phi basis. + Bases of `WQSym` are implemented (internally) using ordered set + partitions. However, the user may access specific basis vectors using + either packed words or ordered set partitions. See the examples below, + noting especially the section on ambiguities. + `WQSym` is endowed with a connected graded Hopf algebra structure (see Section 2.2 of [NoThWi08]_, Section 1.1 of [FoiMal14]_ and Section 4.3.2 of [MeNoTh11]_) given by @@ -346,7 +351,9 @@ class WordQuasiSymmetricFunctions(UniqueRepresentation, Parent): - [NoThWi08]_ - [BerZab05]_ - EXAMPLES:: + EXAMPLES: + + Constructing the algebra and its Monomial basis:: sage: WQSym = algebras.WQSym(ZZ) sage: WQSym @@ -356,19 +363,58 @@ class WordQuasiSymmetricFunctions(UniqueRepresentation, Parent): Word Quasi-symmetric functions over Integer Ring in the Monomial basis sage: M[[]] M[] - sage: M[[1,2,3]] + + Calling basis elements using packed words:: + + sage: x = M[1,2,1]; x + M[{1, 3}, {2}] + sage: x == M[[1,2,1]] == M[Word([1,2,1])] + True + sage: y = M[1,1,2] - M[1,2,2]; y + -M[{1}, {2, 3}] + M[{1, 2}, {3}] + + Calling basis elements using ordered set partitions:: + + sage: z = M[[1,2,3],]; z M[{1, 2, 3}] + sage: z == M[[[1,2,3]]] == M[OrderedSetPartition([[1,2,3]])] + True sage: M[[1,2],[3]] M[{1, 2}, {3}] + + Note that expressions above are output in terms of ordered set partitions, + even when input as packed words. Output as packed words can be achieved + by modifying the global options. (See :meth:`OrderedSetPartitions.options` + for further details.):: + + sage: M.options.objects = "words" + sage: y + -M[1, 2, 2] + M[1, 1, 2] + sage: M.options.display = "compact" + sage: y + -M[122] + M[112] + sage: z + M[111] + + The options should be reset to display as ordered set partitions:: + + sage: M.options._reset() + sage: z + M[{1, 2, 3}] + + Illustration of the Hopf algebra structure:: + sage: M[[2, 3], [5], [6], [4], [1]].coproduct() M[] # M[{2, 3}, {5}, {6}, {4}, {1}] + M[{1, 2}] # M[{3}, {4}, {2}, {1}] + M[{1, 2}, {3}] # M[{3}, {2}, {1}] + M[{1, 2}, {3}, {4}] # M[{2}, {1}] + M[{1, 2}, {4}, {5}, {3}] # M[{1}] + M[{2, 3}, {5}, {6}, {4}, {1}] # M[] - sage: M[[1,2,3]] * M[[1,2],[3]] + sage: _ == M[5,1,1,4,2,3].coproduct() + True + sage: M[[1,1,1]] * M[[1,1,2]] # packed words M[{1, 2, 3}, {4, 5}, {6}] + M[{1, 2, 3, 4, 5}, {6}] + M[{4, 5}, {1, 2, 3}, {6}] + M[{4, 5}, {1, 2, 3, 6}] + M[{4, 5}, {6}, {1, 2, 3}] - sage: M[[1,2,3]].antipode() + sage: M[[1,2,3],].antipode() # ordered set partition -M[{1, 2, 3}] sage: M[[1], [2], [3]].antipode() -M[{1, 2, 3}] - M[{2, 3}, {1}] - M[{3}, {1, 2}] - M[{3}, {2}, {1}] @@ -379,6 +425,29 @@ class WordQuasiSymmetricFunctions(UniqueRepresentation, Parent): 3*M[{1}, {2}] + 3*M[{1, 2}] - M[{1, 2, 3}] - M[{2, 3}, {1}] - M[{3}, {1, 2}] - M[{3}, {2}, {1}] + .. rubric:: Ambiguities + + Some ambiguity arises when accessing basis vectors with the dictionary syntax, + i.e., ``M[...]``. A common example is when referencing an ordered set partition + with one part. For example, in the expression ``M[[1,2]]``, does ``[[1,2]]`` + refer to an ordered set partition or does ``[1,2]`` refer to a packed word? + We choose the latter: if the received arguments do not behave like a tuple of + iterables, then view them as describing a packed word. (In the running example, + one argument is received, which behaves as a tuple of integers.) Here are a + variety of ways to get the same basis vector:: + + sage: x = M[1,1]; x + M[{1, 2}] + sage: x == M[[1,1]] # treated as word + True + sage: x == M[[1,2],] == M[[[1,2]]] # treated as ordered set partitions + True + + sage: M[[1,3],[2]] # treat as ordered set partition + M[{1, 3}, {2}] + sage: M[[1,3],[2]] == M[1,2,1] # treat as word + True + TESTS:: sage: M = WordQuasiSymmetricFunctions(QQ).M() @@ -451,7 +520,7 @@ class options(GlobalOptions): sage: WQ = WordQuasiSymmetricFunctions(QQ) sage: M = WQ.M() - sage: elt = M[[1,2]]*M[[1]]; elt + sage: elt = M[[[1,2]]]*M[[[1]]]; elt M[{1, 2}, {3}] + M[{1, 2, 3}] + M[{3}, {1, 2}] sage: M.options.display = "tight" sage: elt @@ -608,7 +677,7 @@ class Characteristic(WQSymBasis_abstract): sage: X = WQSym.X(); X Word Quasi-symmetric functions over Rational Field in the Characteristic basis - sage: X[[1,2,3]] * X[[1,2],[3]] + sage: X[[[1,2,3]]] * X[[1,2],[3]] X[{1, 2, 3}, {4, 5}, {6}] - X[{1, 2, 3, 4, 5}, {6}] + X[{4, 5}, {1, 2, 3}, {6}] - X[{4, 5}, {1, 2, 3, 6}] + X[{4, 5}, {6}, {1, 2, 3}] @@ -618,11 +687,11 @@ class Characteristic(WQSymBasis_abstract): + X[{1, 3}, {2}] # X[{1}] + X[{1, 4}, {3}, {2}] # X[] sage: M = WQSym.M() - sage: M(X[[1, 2, 3]]) + sage: M(X[[1, 2, 3],]) -M[{1, 2, 3}] sage: M(X[[1, 3], [2]]) M[{1, 3}, {2}] - sage: X(M[[1, 2, 3]]) + sage: X(M[[1, 2, 3],]) -X[{1, 2, 3}] sage: X(M[[1, 3], [2]]) X[{1, 3}, {2}] @@ -1330,7 +1399,7 @@ class StronglyFiner(WQSymBasis_abstract): M[{1}, {4}, {2}, {3}, {5}, {6}] + M[{1}, {4}, {2, 3}, {5}, {6}] + M[{1, 4}, {2}, {3}, {5}, {6}] + M[{1, 4}, {2, 3}, {5}, {6}] - sage: Phi[[1]] * Phi[[1, 3], [2]] + sage: Phi[[1],] * Phi[[1, 3], [2]] Phi[{1, 2, 4}, {3}] + Phi[{2}, {1, 4}, {3}] + Phi[{2, 4}, {1, 3}] + Phi[{2, 4}, {3}, {1}] sage: Phi[[3, 5], [1, 4], [2]].coproduct() @@ -1894,17 +1963,42 @@ def __getitem__(self, p): EXAMPLES:: sage: M = algebras.WQSym(QQ).M() - sage: M[[1, 3, 2]] + sage: M[1, 2, 1] # pass a word + M[{1, 3}, {2}] + sage: _ == M[[1, 2, 1]] == M[Word([1,2,1])] + True + sage: M[[1, 2, 3]] + M[{1}, {2}, {3}] + + sage: M[[[1, 2, 3]]] # pass an ordered set partition M[{1, 2, 3}] + sage: _ == M[[1,2,3],] == M[OrderedSetPartition([[1,2,3]])] + True sage: M[[1,3],[2]] M[{1, 3}, {2}] - sage: M[OrderedSetPartition([[2],[1,4],[3,5]])] - M[{2}, {1, 4}, {3, 5}] + + TESTS:: + + sage: M[[]] + M[] + sage: M[1, 2, 1] == M[Word([2,3,2])] == M[Word('aca')] + True + sage: M[[[1,2]]] == M[1,1] == M[1/1,2/2] == M[2/1,2/1] == M['aa'] + True + sage: M[1] == M[1,] == M[Word([1])] == M[OrderedSetPartition([[1]])] == M[[1],] + True """ + if p in ZZ: + p = [ZZ(p)] + if all(s in ZZ for s in p): + return self.monomial(self._indices.from_finite_word([ZZ(s) for s in p])) + + if all(isinstance(s, str) for s in p): + return self.monomial(self._indices.from_finite_word(p)) try: return self.monomial(self._indices(p)) except TypeError: - return self.monomial(self._indices([p])) + raise ValueError("cannot convert %s into an element of %s"%(p, self._indices)) def is_field(self, proof=True): """ @@ -1954,7 +2048,7 @@ def degree_on_basis(self, t): EXAMPLES:: sage: A = algebras.WQSym(QQ).M() - sage: u = OrderedSetPartition([[2,1]]) + sage: u = OrderedSetPartition([[2,1],]) sage: A.degree_on_basis(u) 2 sage: u = OrderedSetPartition([[2], [1]]) @@ -2456,9 +2550,9 @@ def to_quasisymmetric_function(self): sage: M = algebras.WQSym(QQ).M() sage: M[[1,3],[2]].to_quasisymmetric_function() M[2, 1] - sage: (M[[1,3],[2]] + 3*M[[2,3],[1]] - M[[1,2,3]]).to_quasisymmetric_function() + sage: (M[[1,3],[2]] + 3*M[[2,3],[1]] - M[[1,2,3],]).to_quasisymmetric_function() 4*M[2, 1] - M[3] - sage: X, Y = M[[1,3],[2]], M[[1,2,3]] + sage: X, Y = M[[1,3],[2]], M[[1,2,3],] sage: X.to_quasisymmetric_function() * Y.to_quasisymmetric_function() == (X*Y).to_quasisymmetric_function() True diff --git a/src/sage/combinat/cluster_algebra_quiver/mutation_type.py b/src/sage/combinat/cluster_algebra_quiver/mutation_type.py index 7dd87119481..4edbef7619f 100644 --- a/src/sage/combinat/cluster_algebra_quiver/mutation_type.py +++ b/src/sage/combinat/cluster_algebra_quiver/mutation_type.py @@ -1284,9 +1284,8 @@ def load_data(n): data_dict = dict() for filename in [getfilename(DOT_SAGE),getfilename(SAGE_SHARE)]: if os.path.isfile(filename): - f = open(filename,'r') - data_new = cPickle.load(f) - f.close() + with open(filename, 'rb') as fobj: + data_new = cPickle.load(fobj) data_dict.update(data_new) return data_dict diff --git a/src/sage/combinat/set_partition_ordered.py b/src/sage/combinat/set_partition_ordered.py index 10489c886bc..aabd3766ff2 100644 --- a/src/sage/combinat/set_partition_ordered.py +++ b/src/sage/combinat/set_partition_ordered.py @@ -231,7 +231,7 @@ def check(self): sage: s = OS([[1, 3], [2, 4]]) sage: s.check() """ - assert self in self.parent() + assert self in self.parent(), "%s not in %s" % (self, self.parent()) def _hash_(self): """ @@ -937,6 +937,14 @@ def __contains__(self, x): sage: OS = OrderedSetPartitions([1,2,3,4]) sage: all(sp in OS for sp in OS) True + sage: [[1,2], [], [3,4]] in OS + False + sage: [Set([1,2]), Set([3,4])] in OS + True + sage: [set([1,2]), set([3,4])] in OS + Traceback (most recent call last): + ... + TypeError: X (=set([1, 2])) must be a Set """ #x must be a list if not isinstance(x, (OrderedSetPartition, list, tuple)): @@ -948,10 +956,10 @@ def __contains__(self, x): return False #Check to make sure each element of the list - #is a set + #is a nonempty set u = Set([]) for s in x: - if not isinstance(s, (set, frozenset, Set_generic)): + if not s or not isinstance(s, (set, frozenset, Set_generic)): return False u = u.union(s) @@ -1297,11 +1305,15 @@ def __contains__(self, x): sage: AOS = OrderedSetPartitions() sage: all(sp in AOS for sp in OS) True - sage: [[1,3],[4],[5,2]] in AOS + sage: AOS.__contains__([[1,3], [4], [5,2]]) + True + sage: AOS.__contains__([Set([1,3]), Set([4]), Set([5,2])]) True - sage: [[1,4],[3]] in AOS + sage: [Set([1,4]), Set([3])] in AOS False - sage: [[1,3],[4,2],[2,5]] in AOS + sage: [Set([1,3]), Set([4,2]), Set([2,5])] in AOS + False + sage: [Set([1,2]), Set()] in AOS False """ if isinstance(x, OrderedSetPartition): @@ -1314,10 +1326,10 @@ def __contains__(self, x): if not isinstance(x, (list, tuple)): return False - # Check to make sure each element of the list is a set - if any(not isinstance(s, (set, frozenset, list, tuple, Set_generic)) - for s in x): + # Check to make sure each element of the list is a nonempty set + if not all(s and isinstance(s, (set, frozenset, list, tuple, Set_generic)) for s in x): return False + if not all(isinstance(s, (set, frozenset, Set_generic)) or len(s) == len(set(s)) for s in x): return False X = set(reduce(lambda A,B: A.union(B), x, set())) diff --git a/src/sage/combinat/sf/sf.py b/src/sage/combinat/sf/sf.py index d2f7599b673..9f0b67cbb32 100644 --- a/src/sage/combinat/sf/sf.py +++ b/src/sage/combinat/sf/sf.py @@ -581,7 +581,7 @@ class function on the symmetric group where the elements We can also construct the `\bar{q}` basis that can be used to determine character tables for Hecke algebras (with quadratic - relation `T_i^2 = (1-q) T_i + q`:: + relation `T_i^2 = (1-q) T_i + q`):: sage: Sym = SymmetricFunctions(ZZ['q'].fraction_field()) sage: qbar = Sym.hecke_character() diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index c81509458ec..a890b0bf101 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -3656,8 +3656,8 @@ def reduced_kronecker_product(self, x): sage: s[2].reduced_kronecker_product(s[2]) s[] + s[1] + s[1, 1] + s[1, 1, 1] + 2*s[2] + 2*s[2, 1] + s[2, 2] + s[3] + s[3, 1] + s[4] - Taking the reduced Kronecker product with `1 = s_{\empty}` is the - identity map on the ring of symmetric functions:: + Taking the reduced Kronecker product with `1 = s_{\emptyset}` + is the identity map on the ring of symmetric functions:: sage: all( s[Partition([])].reduced_kronecker_product(s[lam]) ....: == s[lam] for i in range(4) @@ -3870,8 +3870,8 @@ def left_padded_kronecker_product(self, x): sage: h[2].left_padded_kronecker_product(h[3]) h[2, 1] + h[2, 1, 1] + h[3, 2] - Taking the left-padded Kronecker product with `1 = h_{\empty}` is - the identity map on the ring of symmetric functions:: + Taking the left-padded Kronecker product with `1 = h_{\emptyset}` + is the identity map on the ring of symmetric functions:: sage: all( h[Partition([])].left_padded_kronecker_product(h[lam]) ....: == h[lam] for i in range(4) diff --git a/src/sage/combinat/words/paths.py b/src/sage/combinat/words/paths.py index 7578530d77e..9be7a2e5bb6 100644 --- a/src/sage/combinat/words/paths.py +++ b/src/sage/combinat/words/paths.py @@ -1609,8 +1609,6 @@ def animate(self): Animation with 9 frames sage: show(a) # optional -- ImageMagick sage: a.gif(delay=35, iterations=3) # optional -- ImageMagick - doctest:...: DeprecationWarning: use tmp_filename instead - See http://trac.sagemath.org/17234 for details. :: diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index b2adda8303b..4bf81a7da6f 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -2239,13 +2239,13 @@ def spqr_tree(G, algorithm="Hopcroft_Tarjan", solver=None, verbose=0): sage: spqr_tree(G) Traceback (most recent call last): ... - ValueError: Graph is not biconnected + ValueError: graph is not biconnected sage: G = Graph([(0, 0)], loops=True) sage: spqr_tree(G) Traceback (most recent call last): ... - ValueError: Graph is not biconnected + ValueError: graph is not biconnected sage: spqr_tree(Graph(), algorithm="easy") Traceback (most recent call last): @@ -2707,11 +2707,11 @@ class TriconnectivitySPQR: INPUT: - - ``G`` -- the input graph. If ``G`` is a DiGraph, the computation is done - on the underlying Graph (i.e., ignoring edge orientation). + - ``G`` -- graph; if ``G`` is a :class:`DiGraph`, the computation is done + on the underlying :class:`Graph` (i.e., ignoring edge orientation) - - ``check`` -- boolean (default: ``True``); indicates whether ``G`` needs to - be tested for biconnectivity. + - ``check`` -- boolean (default: ``True``); indicates whether ``G`` + needs to be tested for biconnectivity .. SEEALSO:: @@ -2809,7 +2809,7 @@ class TriconnectivitySPQR: sage: tric = TriconnectivitySPQR(G) Traceback (most recent call last): ... - ValueError: Graph is not connected + ValueError: graph is not connected A graph with a cut vertex:: @@ -2818,18 +2818,14 @@ class TriconnectivitySPQR: sage: tric = TriconnectivitySPQR(G) Traceback (most recent call last): ... - ValueError: Graph has a cut vertex + ValueError: graph has a cut vertex .. TODO:: - - Remove recursion in methods ``__dfs1``, ``__dfs2``, ``__path_finder`` - and ``__path_search``. This currently restricts the size of graphs as - maximum recursion depth might be exceeded. - - - Cythonize the code for more efficiency. Many data structures can be - turned into integer arrays. More care is needed for the doubly linked - list and for the lists of lists. Note that the internal graph copy - must allow edge addition due to the insertion of virtual edges. + Cythonize the code for more efficiency. Many data structures can be + turned into integer arrays. More care is needed for the doubly linked + list and for the lists of lists. Note that the internal graph copy + must allow edge addition due to the insertion of virtual edges. """ def __init__(self, G, check=True): """ @@ -2837,11 +2833,12 @@ class TriconnectivitySPQR: INPUT: - - ``G`` -- the input graph. If ``G`` is a DiGraph, the computation is - done on the underlying Graph (i.e., ignoring edge orientation). + - ``G`` -- graph; if ``G`` is a :class:`DiGraph`, the computation is + done on the underlying :class:`Graph` (i.e., ignoring edge + orientation) - ``check`` -- boolean (default: ``True``); indicates whether ``G`` - needs to be tested for biconnectivity. + needs to be tested for biconnectivity """ self.n = G.order() self.m = G.size() @@ -2849,7 +2846,7 @@ class TriconnectivitySPQR: # Trivial cases if self.n < 2: - raise ValueError("Graph is not biconnected") + raise ValueError("graph is not biconnected") elif self.n == 2 and self.m: # a P block with at least 1 edge self.comp_list_new = [G.edges()] @@ -2858,10 +2855,10 @@ class TriconnectivitySPQR: return elif self.m < self.n -1: # less edges than a tree - raise ValueError("Graph is not connected") + raise ValueError("graph is not connected") elif self.m < self.n: # less edges than a cycle - raise ValueError("Graph is not biconnected") + raise ValueError("graph is not biconnected") from sage.graphs.graph import Graph @@ -2870,10 +2867,10 @@ class TriconnectivitySPQR: # - edges are relabeled with distinct labels in order to distinguish # between multi-edges self.int_to_vertex = G.vertices() - self.vertex_to_int = {u:i for i,u in enumerate(self.int_to_vertex)} + self.vertex_to_int = {u: i for i,u in enumerate(self.int_to_vertex)} self.int_to_original_edge_label = [] # to associate original edge label self.graph_copy = Graph(self.n, multiedges=True) - for i,(u, v, l) in enumerate(G.edge_iterator()): + for i, (u, v, l) in enumerate(G.edge_iterator()): self.graph_copy.add_edge(self.vertex_to_int[u], self.vertex_to_int[v], i) self.int_to_original_edge_label.append(l) @@ -2894,7 +2891,7 @@ class TriconnectivitySPQR: # A dictionary whose key is an edge e, value is a pointer to element in # self.highpt containing the edge e. Used in the `path_search` function. - self.in_high = {e:None for e in self.graph_copy.edge_iterator()} + self.in_high = {e: None for e in self.graph_copy.edge_iterator()} # Translates DFS number of a vertex to its new number self.old_to_new = [0 for i in range(self.n+1)] @@ -2966,16 +2963,16 @@ class TriconnectivitySPQR: if check: # If graph is disconnected if self.dfs_counter < self.n: - raise ValueError("Graph is not connected") + raise ValueError("graph is not connected") # If graph has a cut vertex if self.cut_vertex != None: - raise ValueError("Graph has a cut vertex") + raise ValueError("graph has a cut vertex") # Identify reversed edges to reflect the palm tree arcs and fronds for e in self.graph_copy.edge_iterator(): up = (self.dfs_number[e[1]] - self.dfs_number[e[0]]) > 0 - if (up and self.edge_status[e]==2) or (not up and self.edge_status[e]==1): + if (up and self.edge_status[e] == 2) or (not up and self.edge_status[e] == 1): # Add edge to the set reverse_edges self.reverse_edges.add(e) @@ -2997,7 +2994,7 @@ class TriconnectivitySPQR: def __tstack_push(self, h, a, b): """ - Push ``(h, a, b)`` triple on Tstack + Push ``(h, a, b)`` triple on ``Tstack``. """ self.t_stack_top += 1 self.t_stack_h[self.t_stack_top] = h @@ -3006,14 +3003,14 @@ class TriconnectivitySPQR: def __tstack_push_eos(self): """ - Push end-of-stack marker on Tstack + Push end-of-stack marker on ``Tstack``. """ self.t_stack_top += 1 self.t_stack_a[self.t_stack_top] = -1 def __tstack_not_eos(self): """ - Return true iff end-of-stack marker is not on top of Tstack + Return ``True`` iff end-of-stack marker is not on top of ``Tstack``. """ return self.t_stack_a[self.t_stack_top] != -1 @@ -3025,8 +3022,9 @@ class TriconnectivitySPQR: def __new_component(self, edges=[], type_c=0): """ - Create a new component and add `edges` to it. - type_c = 0 for bond, 1 for polygon, 2 for triconnected component + Create a new component and add ``edges`` to it. + ``type_c = 0`` for bond, ``1`` for polygon, ``2`` for + triconnected component. """ c = _Component(edges, type_c) self.components_list.append(c) @@ -3034,7 +3032,7 @@ class TriconnectivitySPQR: def __new_virtual_edge(self, u, v): """ - Return a new virtual edge between `u` and `v`. + Return a new virtual edge between ``u`` and ``v``. """ e = (u, v, "newVEdge"+str(self.virtual_edge_num)) self.virtual_edge_num += 1 @@ -3043,7 +3041,8 @@ class TriconnectivitySPQR: def __high(self, v): """ - Return the high(v) value, which is the first value in highpt list of v. + Return the ``high(v)`` value, which is the first value in + ``highpt`` list of ``v``. """ head = self.highpt[v].get_head() if head is None: @@ -3053,7 +3052,8 @@ class TriconnectivitySPQR: def __del_high(self, e): """ - Delete edge e from the highpt list of the endpoint v it belongs to. + Delete edge ``e`` from the ``highpt`` list of the endpoint ``v`` + it belongs to. """ if e in self.in_high: it = self.in_high[e] @@ -3066,14 +3066,15 @@ class TriconnectivitySPQR: def __bucket_sort(self, bucket, edge_list): """ - Use radix sort to sort the buckets + Use radix sort to sort the buckets. """ # if only one edge is present if len(bucket) == 1: return # Create n bucket linked lists - bucket_list = [] + cdef list bucket_list = [] + cdef Py_ssize_t i for i in range(self.n): bucket_list.append(_LinkedList()) @@ -3106,11 +3107,11 @@ class TriconnectivitySPQR: def __sort_edges(self): """ - A helper function for `split_multiple_edges` to sort the edges of - graph_copy. + A helper function for :meth:`split_multiple_edges` to sort the + edges of ``graph_copy``. - Sorts the edges of `graph_copy` and stores the sorted edges in a linked - list. The head pointer of the linked list is returned. + Sorts the edges of ``graph_copy`` and stores the sorted edges in + a linked list. The head pointer of the linked list is returned. This function is an implementation of the sorting algorithm given in [Hopcroft1973]_. @@ -3120,8 +3121,8 @@ class TriconnectivitySPQR: for e in self.graph_copy.edges(sort=False): edge_list.append(_LinkedListNode(e)) - bucketMin = {} # Contains the lower index of edge end point - bucketMax = {} # Contains the higher index of edge end point + cdef dict bucketMin = {} # Contains the lower index of edge end point + cdef dict bucketMax = {} # Contains the higher index of edge end point # In `graph_copy`, every edge `(u, v)` is such that `u < v`. # Hence, `bucketMin` of an edge `(u, v)` will be `u` @@ -3147,7 +3148,7 @@ class TriconnectivitySPQR: be created, all the `k` edges are deleted from the graph and the virtual edge between `u` and `v` is added to the graph. """ - comp = [] + cdef list comp = [] if self.graph_copy.has_multiple_edges(): sorted_edges = self.__sort_edges() while sorted_edges.next: @@ -3185,24 +3186,21 @@ class TriconnectivitySPQR: comp.append(newVEdge) self.__new_component(comp) - def __dfs1(self, v, u=None, check=True): + def __dfs1(self, start, check=True): """ - This function builds the palm-tree of the graph using a dfs traversal. + Build the palm-tree of the graph using a dfs traversal. - Also populates the lists ``lowpt1``, ``lowpt2``, ``nd``, ``parent``, and - ``dfs_number``. It updates the dict ``edge_status`` to reflect palm - tree arcs and fronds. + Also populates the lists ``lowpt1``, ``lowpt2``, ``nd``, ``parent``, + and ``dfs_number``. It updates the dict ``edge_status`` to reflect + palm tree arcs and fronds. INPUT: - - ``v`` -- The start vertex for DFS. - - - ``u`` -- The parent vertex of ``v`` in the palm tree. + - ``start`` -- the start vertex for DFS - - ``check`` -- if ``True``, the graph is tested for biconnectivity. If - the graph has a cut vertex, the cut vertex is returned. If set to - ``False``, the graph is assumed to be biconnected, function returns - ``None``. + - ``check`` -- if ``True``, the graph is tested for biconnectivity; if + the graph has a cut vertex, the cut vertex is returned; otherwise + the graph is assumed to be biconnected, function returns ``None`` OUTPUT: @@ -3210,31 +3208,60 @@ class TriconnectivitySPQR: vertex is returned. If no cut vertex is found, return ``None``. - If ``check`` is set to ``False``, ``None`` is returned. """ - first_son = None # For testing biconnectivity + cdef list stack = [start] + cdef list adjacency = [iter(self.graph_copy_adjacency[v]) for v in range(self.n)] + cdef list first_son = [None for v in range(self.n)] # For testing biconnectivity + self.parent = [None for v in range(self.n)] s1 = None # Storing the cut vertex, if there is one - self.dfs_counter += 1 - self.dfs_number[v] = self.dfs_counter - self.parent[v] = u - self.degree[v] = self.graph_copy.degree(v) - self.lowpt1[v] = self.lowpt2[v] = self.dfs_number[v] - self.nd[v] = 1 - for e in self.graph_copy_adjacency[v]: - if self.edge_status[e]: - continue - w = e[0] if e[0] != v else e[1] # Opposite vertex of edge e - if self.dfs_number[w] == 0: - self.edge_status[e] = 1 # tree edge - if first_son is None: - first_son = w - self.tree_arc[w] = e - s1 = self.__dfs1(w, v, check) + while stack: + v = stack[-1] + + if not self.dfs_number[v]: + self.dfs_counter += 1 + self.dfs_number[v] = self.dfs_counter + self.degree[v] = self.graph_copy.degree(v) + self.lowpt1[v] = self.lowpt2[v] = self.dfs_number[v] + self.nd[v] = 1 + + try: + e = next(adjacency[v]) + while self.edge_status[e]: + e = next(adjacency[v]) + + w = e[0] if e[0] != v else e[1] # Opposite vertex of edge e + if not self.dfs_number[w]: + self.edge_status[e] = 1 # tree edge + if first_son[v] is None: + first_son[v] = w + self.tree_arc[w] = e + + stack.append(w) + self.parent[w] = v + + else: + self.edge_status[e] = 2 # frond + if self.dfs_number[w] < self.lowpt1[v]: + self.lowpt2[v] = self.lowpt1[v] + self.lowpt1[v] = self.dfs_number[w] + elif self.dfs_number[w] > self.lowpt1[v]: + self.lowpt2[v] = min(self.lowpt2[v], self.dfs_number[w]) + + except StopIteration: + # We trackback, so w takes the value of v and we pop the stack + w = stack.pop() + + # Test termination + if not stack: + break + + v = stack[-1] if check: # Check for cut vertex. # The situation in which there is no path from w to an # ancestor of v : we have identified a cut vertex - if (self.lowpt1[w] >= self.dfs_number[v]) and (w != first_son or u != None): + if (self.lowpt1[w] >= self.dfs_number[v]) and (w != first_son[v] or self.parent[v] is not None): s1 = v # Calculate the `lowpt1` and `lowpt2` values. @@ -3253,20 +3280,12 @@ class TriconnectivitySPQR: self.nd[v] += self.nd[w] - else: - self.edge_status[e] = 2 # frond - if self.dfs_number[w] < self.lowpt1[v]: - self.lowpt2[v] = self.lowpt1[v] - self.lowpt1[v] = self.dfs_number[w] - elif self.dfs_number[w] > self.lowpt1[v]: - self.lowpt2[v] = min(self.lowpt2[v], self.dfs_number[w]) - return s1 # s1 is None if graph does not have a cut vertex def __build_acceptable_adj_struct(self): """ - Builds the adjacency lists for each vertex with certain properties of + Build the adjacency lists for each vertex with certain properties of the ordering, using the ``lowpt1`` and ``lowpt2`` values. The list ``adj`` and the dictionary ``in_adj`` are populated. @@ -3276,7 +3295,8 @@ class TriconnectivitySPQR: added to adjacency list. """ max_size = 3*self.n + 2 - bucket = [[] for _ in range(max_size + 1)] + cdef Py_ssize_t i + cdef list bucket = [[] for i in range(max_size + 1)] for e in self.graph_copy.edge_iterator(): edge_type = self.edge_status[e] @@ -3313,41 +3333,60 @@ class TriconnectivitySPQR: self.adj[e[0]].append(node) self.in_adj[e] = node - def __path_finder(self, v): + def __path_finder(self, start): """ - This function is a helper function for `__dfs2` function. + This function is a helper function for :meth:`__dfs2` function. + Calculate ``newnum[v]`` and identify the edges which start a new path. + + INPUT: + + - ``start`` -- the start vertex """ - self.newnum[v] = self.dfs_counter - self.nd[v] + 1 - e_node = self.adj[v].get_head() - while e_node: - e = e_node.get_data() - e_node = e_node.next - w = e[1] if e[0] == v else e[0] # opposite vertex of e - if self.new_path: - self.new_path = False - self.starts_path[e] = True - if self.edge_status[e] == 1: # tree arc - self.__path_finder(w) - self.dfs_counter -= 1 + cdef list stack = [start] + cdef list seen = [False] * self.n + cdef list pointer_e_node = [self.adj[v].get_head() for v in range(self.n)] + + while stack: + v = stack[-1] + if not seen[v]: + self.newnum[v] = self.dfs_counter - self.nd[v] + 1 + seen[v] = True + e_node = pointer_e_node[v] + + if e_node: + e = e_node.get_data() + pointer_e_node[v] = e_node.next + w = e[1] if e[0] == v else e[0] # opposite vertex of e + if self.new_path: + self.new_path = False + self.starts_path[e] = True + if self.edge_status[e] == 1: # tree arc + stack.append(w) + else: + # Identified a new frond that enters `w`. Add to `highpt[w]`. + highpt_node = _LinkedListNode(self.newnum[v]) + self.highpt[w].append(highpt_node) + self.in_high[e] = highpt_node + self.new_path = True + else: - # Identified a new frond that enters `w`. Add to `highpt[w]`. - highpt_node = _LinkedListNode(self.newnum[v]) - self.highpt[w].append(highpt_node) - self.in_high[e] = highpt_node - self.new_path = True + # We trackback + self.dfs_counter -= 1 + w = stack.pop() def __dfs2(self): """ - Update the values of ``lowpt1`` and ``lowpt2`` lists with the help of - new numbering obtained from ``__path_finder`` function. + Update the values of ``lowpt1`` and ``lowpt2`` lists with the + help of new numbering obtained from :meth:`__path_finder`. Populate ``highpt`` values. """ - self.in_high = {e:None for e in self.graph_copy.edge_iterator()} + cdef Py_ssize_t i + self.in_high = {e: None for e in self.graph_copy.edge_iterator()} self.dfs_counter = self.n self.newnum = [0 for i in range(self.n)] - self.starts_path = {e:False for e in self.graph_copy.edge_iterator()} + self.starts_path = {e: False for e in self.graph_copy.edge_iterator()} self.new_path = True @@ -3364,41 +3403,98 @@ class TriconnectivitySPQR: self.lowpt1[v] = self.old_to_new[self.lowpt1[v]] self.lowpt2[v] = self.old_to_new[self.lowpt2[v]] - def __path_search(self, v): + def __path_search(self, start): """ Find the separation pairs and construct the split components. + Check for type-1 and type-2 separation pairs, and construct the split components while also creating new virtual edges wherever required. + + INPUT: + + - ``start`` -- the start vertex """ - y = 0 - vnum = self.newnum[v] - outv = self.adj[v].get_length() - e_node = self.adj[v].get_head() - while e_node: - e = e_node.get_data() - it = e_node + cdef list stack_v = [start] + cdef dict y_dict = {start: 0} + cdef dict outv_dict = {start: self.adj[start].get_length()} + cdef dict e_node_dict = {start: self.adj[start].get_head()} + + while stack_v: + v = stack_v[-1] + e_node = e_node_dict[v] + + if e_node: + # Restore values of variables + y = y_dict[v] + vnum = self.newnum[v] + outv = outv_dict[v] + e = e_node.get_data() + it = e_node + if e in self.reverse_edges: + w = e[0] # target + else: + w = e[1] + wnum = self.newnum[w] + + if self.edge_status[e] == 1: # e is a tree arc + if self.starts_path[e]: # if a new path starts at edge e + # Pop all (h,a,b) from tstack where a > lowpt1[w] + if self.t_stack_a[self.t_stack_top] > self.lowpt1[w]: + while self.t_stack_a[self.t_stack_top] > self.lowpt1[w]: + y = max(y, self.t_stack_h[self.t_stack_top]) + b = self.t_stack_b[self.t_stack_top] + self.t_stack_top -= 1 + self.__tstack_push(y, self.lowpt1[w], b) + + else: + self.__tstack_push(wnum + self.nd[w] - 1, self.lowpt1[w], vnum) + self.__tstack_push_eos() + + # We emulate the recursive call on w using a stack + stack_v.append(w) + y_dict[w] = 0 + outv_dict[w] = self.adj[w].get_length() + e_node_dict[w] = self.adj[w].get_head() + y_dict[v] = y + continue + + else: # e is a frond + if self.starts_path[e]: + # pop all (h,a,b) from tstack where a > w + if self.t_stack_a[self.t_stack_top] > wnum: + while self.t_stack_a[self.t_stack_top] > wnum: + y = max(y, self.t_stack_h[self.t_stack_top]) + b = self.t_stack_b[self.t_stack_top] + self.t_stack_top -= 1 + self.__tstack_push(y, wnum, b) + + else: + self.__tstack_push(vnum, wnum, vnum) + self.e_stack.append(e) # add (v,w) to ESTACK - if e in self.reverse_edges: - w = e[0] # target else: - w = e[1] - wnum = self.newnum[w] - if self.edge_status[e] == 1: # e is a tree arc - if self.starts_path[e]: # if a new path starts at edge e - y = 0 - # Pop all (h,a,b) from tstack where a > lowpt1[w] - if self.t_stack_a[self.t_stack_top] > self.lowpt1[w]: - while self.t_stack_a[self.t_stack_top] > self.lowpt1[w]: - y = max(y, self.t_stack_h[self.t_stack_top]) - b = self.t_stack_b[self.t_stack_top] - self.t_stack_top -= 1 - self.__tstack_push(y, self.lowpt1[w], b) + # We are done with v, so we trackback + stack_v.pop() - else: - self.__tstack_push(wnum + self.nd[w] - 1, self.lowpt1[w], vnum) - self.__tstack_push_eos() + # Test termination + if not stack_v: + continue + + # Restore state of variables + v = stack_v[-1] + e_node = e_node_dict[v] + y = y_dict[v] + vnum = self.newnum[v] + outv = outv_dict[v] + e = e_node.get_data() + it = e_node + if e in self.reverse_edges: + w = e[0] # target + else: + w = e[1] + wnum = self.newnum[w] - self.__path_search(w) + # Continue operations with tree arc e self.e_stack.append(self.tree_arc[w]) temp_node = self.adj[w].get_head() @@ -3410,9 +3506,8 @@ class TriconnectivitySPQR: # Type-2 separation pair check # while v is not the start_vertex - while vnum != 1 and ((self.t_stack_a[self.t_stack_top] == vnum) or \ - (self.degree[w] == 2 and self.newnum[temp_target] > wnum)): - + while vnum != 1 and ((self.t_stack_a[self.t_stack_top] == vnum) + or (self.degree[w] == 2 and self.newnum[temp_target] > wnum)): a = self.t_stack_a[self.t_stack_top] b = self.t_stack_b[self.t_stack_top] e_virt = None @@ -3540,8 +3635,8 @@ class TriconnectivitySPQR: temp_target = temp[1] # start type-1 check - if self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum and \ - (self.parent[v] != self.start_vertex or outv >= 2): + if (self.lowpt2[w] >= vnum and self.lowpt1[w] < vnum + and (self.parent[v] != self.start_vertex or outv >= 2)): # type-1 separation pair - (self.node_at[self.lowpt1[w]], v) # Create a new component and add edges to it comp = _Component([], 0) @@ -3556,8 +3651,8 @@ class TriconnectivitySPQR: xx = self.newnum[xy[0]] #source y = self.newnum[xy[1]] #target - if not ((wnum <= xx and xx < wnum + self.nd[w]) or \ - (wnum <= y and y < wnum + self.nd[w])): + if not ((wnum <= xx and xx < wnum + self.nd[w]) + or (wnum <= y and y < wnum + self.nd[w])): break comp.add_edge(self.__estack_pop()) @@ -3571,8 +3666,8 @@ class TriconnectivitySPQR: self.components_list.append(comp) comp = None - if (xx == vnum and y == self.lowpt1[w]) or \ - (y == vnum and xx == self.lowpt1[w]): + if ((xx == vnum and y == self.lowpt1[w]) + or (y == vnum and xx == self.lowpt1[w])): comp_bond = _Component([], type_c=0) # new triple bond eh = self.__estack_pop() if self.in_adj[eh] != it: @@ -3636,45 +3731,31 @@ class TriconnectivitySPQR: self.t_stack_top -= 1 self.t_stack_top -= 1 - while self.__tstack_not_eos() and self.t_stack_b[self.t_stack_top] != vnum \ - and self.__high(v) > self.t_stack_h[self.t_stack_top]: + while (self.__tstack_not_eos() and self.t_stack_b[self.t_stack_top] != vnum + and self.__high(v) > self.t_stack_h[self.t_stack_top]): self.t_stack_top -= 1 - outv -= 1 - - else: # e is a frond - if self.starts_path[e]: - y = 0 - # pop all (h,a,b) from tstack where a > w - if self.t_stack_a[self.t_stack_top] > wnum: - while self.t_stack_a[self.t_stack_top] > wnum: - y = max(y, self.t_stack_h[self.t_stack_top]) - b = self.t_stack_b[self.t_stack_top] - self.t_stack_top -= 1 - self.__tstack_push(y, wnum, b) - - else: - self.__tstack_push(vnum, wnum, vnum) - self.e_stack.append(e) # add (v,w) to ESTACK + outv_dict[v] -= 1 # Go to next edge in adjacency list - e_node = e_node.next + e_node_dict[v] = e_node.next def __assemble_triconnected_components(self): """ - Iterate through all the split components built by ``__path_finder`` and - merges two bonds or two polygons that share an edge for contructing the - final triconnected components. - Subsequently, convert the edges in triconnected components into original - vertices and edges. The triconnected components are stored in - `self.comp\_list\_new` and `self.comp\_type`. - """ - comp1 = {} # The index of first component that an edge belongs to - comp2 = {} # The index of second component that an edge belongs to - item1 = {} # Pointer to the edge node in component1 - item2 = {} # Pointer to the edge node in component2 - num_components = len(self.components_list) - visited = [False for i in range(num_components)] + Iterate through all the split components built by + :meth:`__path_finder` and merges two bonds or two polygons that + share an edge for contructing the final triconnected components. + Subsequently, convert the edges in triconnected components into + original vertices and edges. The triconnected components are stored + in ``self.comp_list_new`` and ``self.comp_type``. + """ + cdef Py_ssize_t i + cdef dict comp1 = {} # The index of first component that an edge belongs to + cdef dict comp2 = {} # The index of second component that an edge belongs to + cdef dict item1 = {} # Pointer to the edge node in component1 + cdef dict item2 = {} # Pointer to the edge node in component2 + cdef Py_ssize_t num_components = len(self.components_list) + cdef list visited = [False for i in range(num_components)] # For each edge, we populate the comp1, comp2, item1 and item2 values for i in range(num_components): # for each component @@ -3725,7 +3806,7 @@ class TriconnectivitySPQR: c2 = self.components_list[j] # If the two components are not the same type, do not merge - if (c1_type != c2.component_type): + if c1_type != c2.component_type: e_node = e_node_next # Go to next edge continue @@ -3769,21 +3850,23 @@ class TriconnectivitySPQR: def __build_spqr_tree(self): """ Build the SPQR-tree of the graph and store it in variable - ``self.spqr_tree``. See :meth:`~TriconnectivitySPQR.get_spqr_tree`. + ``self.spqr_tree``. See + :meth:`~sage.graphs.connectivity.TriconnectivitySPQR.get_spqr_tree`. """ from sage.graphs.graph import Graph # Types of components 0: "P", 1: "S", 2: "R" - component_type = ["P", "S", "R"] + cdef list component_type = ["P", "S", "R"] self.spqr_tree = Graph(multiedges=False, name='SPQR-tree of {}'.format(self.graph_name)) if len(self.comp_list_new) == 1 and self.comp_type[0] == 0: self.spqr_tree.add_vertex(('Q' if len(self.comp_list_new[0]) == 1 else 'P', - Graph(self.comp_list_new[0], immutable=True, multiedges=True))) + Graph(self.comp_list_new[0], immutable=True, multiedges=True))) return - int_to_vertex = [] - partner_nodes = {} + cdef list int_to_vertex = [] + cdef dict partner_nodes = {} + cdef Py_ssize_t i for i in range(len(self.comp_list_new)): # Create a new tree vertex @@ -3831,7 +3914,8 @@ class TriconnectivitySPQR: Triconnected: [(1, 13, None), (2, 13, None), (3, 13, None), (3, 1, 'newVEdge11'), (2, 3, None), (1, 2, None)] """ # The types are {0: "Bond", 1: "Polygon", 2: "Triconnected"} - prefix = ["Bond", "Polygon", "Triconnected"] + cdef list prefix = ["Bond", "Polygon", "Triconnected"] + cdef Py_ssize_t i for i in range(len(self.comp_list_new)): print("{}: {}".format(prefix[self.comp_type[i]], self.comp_list_new[i])) @@ -3855,9 +3939,10 @@ class TriconnectivitySPQR: ('Bond', [(1, 0, 'newVEdge1'), (1, 0, 'newVEdge3'), (1, 0, 'newVEdge4')]), ('Polygon', [(1, 3, None), (1, 0, 'newVEdge4'), (2, 3, None), (0, 2, None)])] """ - comps = [] + cdef list comps = [] + cdef Py_ssize_t i # The types are {0: "Bond", 1: "Polygon", 2: "Triconnected"} - prefix = ["Bond", "Polygon", "Triconnected"] + cdef list prefix = ["Bond", "Polygon", "Triconnected"] for i in range(len(self.comp_list_new)): comps.append((prefix[self.comp_type[i]], self.comp_list_new[i])) return comps @@ -3886,7 +3971,9 @@ class TriconnectivitySPQR: The edges of the tree indicate the 2-vertex cuts of the graph. - OUTPUT: ``SPQR-tree`` a tree whose vertices are labeled with the block's + OUTPUT: + + ``SPQR-tree`` a tree whose vertices are labeled with the block's type and the subgraph of three-blocks in the decomposition. EXAMPLES:: @@ -3950,3 +4037,4 @@ class TriconnectivitySPQR: [('P', Multi-graph on 2 vertices)] """ return self.spqr_tree + diff --git a/src/sage/interfaces/polymake.py b/src/sage/interfaces/polymake.py index 79ad9d876d1..8215e97160a 100644 --- a/src/sage/interfaces/polymake.py +++ b/src/sage/interfaces/polymake.py @@ -1,6 +1,12 @@ r""" Interface to polymake +polymake (https://polymake.org) is a mature open source package for +research in polyhedral geometry and related fields, developed since 1997 +by Ewgenij Gawrilow and Michael Joswig and various contributors. + +polymake has been described in [GJ1997]_, [GJ2006]_, [JMP2009]_, [GJRW2010]_, +[GHJ2016]_, and [AGHJLPR2017]_. """ @@ -105,12 +111,12 @@ class Polymake(ExtraTabCompletion, Expect): In order to use this interface, you need to either install the optional polymake package for Sage, or install polymake system-wide - on your computer. + on your computer; it is available from https://polymake.org. Type ``polymake.[tab]`` for a list of most functions available from your polymake install. Type - ``polymake.Function?`` for polymake's help about a given ``Function`` - Type ``polymake(...)`` to create a new Magma + ``polymake.Function?`` for polymake's help about a given ``Function``. + Type ``polymake(...)`` to create a new polymake object, and ``polymake.eval(...)`` to run a string using polymake and get the result back as a string. diff --git a/src/sage/matrix/matrix_mod2_dense.pyx b/src/sage/matrix/matrix_mod2_dense.pyx index 65e8191418f..20335a131bb 100644 --- a/src/sage/matrix/matrix_mod2_dense.pyx +++ b/src/sage/matrix/matrix_mod2_dense.pyx @@ -1678,12 +1678,23 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse sage: f,s = A.__reduce__() sage: f(*s) == A True + + TESTS: + + Check that :trac:`24589` is fixed:: + + sage: A = random_matrix(GF(2),10,10) + sage: loads(dumps(A)).is_immutable() + False + sage: A.set_immutable() + sage: loads(dumps(A)).is_immutable() + True """ cdef int i,j, r,c, size r, c = self.nrows(), self.ncols() if r == 0 or c == 0: - return unpickle_matrix_mod2_dense_v1, (r, c, None, 0) + return unpickle_matrix_mod2_dense_v2, (r, c, None, 0, self._is_immutable) sig_on() cdef gdImagePtr im = gdImageCreate(c, r) @@ -1701,7 +1712,7 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse data = [buf[i] for i in range(size)] gdFree(buf) gdImageDestroy(im) - return unpickle_matrix_mod2_dense_v1, (r,c, data, size) + return unpickle_matrix_mod2_dense_v2, (r,c, data, size, self._is_immutable) cpdef _export_as_string(self): """ @@ -1937,26 +1948,47 @@ cdef inline unsigned long parity_mask(m4ri_word a): return -parity(a) -def unpickle_matrix_mod2_dense_v1(r, c, data, size): - """ +def unpickle_matrix_mod2_dense_v2(r, c, data, size, immutable=False): + r""" Deserialize a matrix encoded in the string ``s``. INPUT: - - r -- number of rows of matrix - - c -- number of columns of matrix - - s -- a string - - size -- length of the string s + - ``r`` -- number of rows of matrix + - ``c`` -- number of columns of matrix + - ``s`` -- a string + - ``size`` -- length of the string ``s`` + - ``immutable`` -- (default: ``False``) whether the + matrix is immutable or not EXAMPLES:: sage: A = random_matrix(GF(2),100,101) - sage: _,(r,c,s,s2) = A.__reduce__() - sage: from sage.matrix.matrix_mod2_dense import unpickle_matrix_mod2_dense_v1 - sage: unpickle_matrix_mod2_dense_v1(r,c,s,s2) == A + sage: _, (r,c,s,s2,i) = A.__reduce__() + sage: from sage.matrix.matrix_mod2_dense import unpickle_matrix_mod2_dense_v2 + sage: unpickle_matrix_mod2_dense_v2(r,c,s,s2,i) == A True sage: loads(dumps(A)) == A True + + TESTS: + + Check that old pickles before :trac:`24589` still work:: + + sage: s = ("x\x9ck`J.NLO\xd5\xcbM,)\xca\xac\x80R\xf1\xb9\xf9)F\xf1)\xa9y" + ....: "\xc5\xa9\\\xa5y\x05\x99\xc9\xd99\xa9\xf1\x18R\xf1e\x86\\\x85" + ....: "\x8c\x1a\xdeL\xdeL\xb1\x85L\x1a^\x9d\xff\xff\xff\xf7\x0e\xf0" + ....: "\xf6\xf3v\xf7\xe6\xf5\xe6\xf2\x96\x02b\x060\xe4\xf5\xf6\xf4" + ....: "\xf6\xf0v\xf1\x0e\x82\xf2\x99\xe04\xa373\x94\xed\xe1]\xe15" + ....: "\x1fd@:T\x80\rh\x94\x8fw\x88\xb7+\x84\xef\x05\x94\xfb\x8fD," + ....: "\x05\x117A\x04H\x97\xd7]\x90V\x88FN\xef\x02\xa0i\x91\xde\xc5" + ....: "`\x1e\x9f\xd7\x11\x98\x14\x94\xc9\xe85\x15Di{\xf3yKC\xb5\xf0" + ....: "\x00\x1d\xe8\xe2\xed\x08\xb4\x8d\xc3k&H2\x19hF\x82w\x06X\x92" + ....: "\x11(\xc5\xe0u\x10$\x9c\x0ft\x9d\xa1\xd7\x03\x84]\x0c@\x8d\xae@" + ....: "\x1f\xbbx\xad\x03\t:y'x5\x01\x19\xa9\xde9%A\x85\xccz\x000I\x88D") + sage: loads(s) + [1 0] + [0 1] """ from sage.matrix.constructor import Matrix from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF @@ -1986,8 +2018,17 @@ def unpickle_matrix_mod2_dense_v1(r, c, data, size): for j from 0 <= j < c: mzd_write_bit(A._entries, i, j, 1-gdImageGetPixel(im, j, i)) gdImageDestroy(im) + + if immutable: + A.set_immutable() + return A +from sage.misc.persist import register_unpickle_override +register_unpickle_override('sage.matrix.matrix_mod2_dense', + 'unpickle_matrix_mod2_dense_v1', + unpickle_matrix_mod2_dense_v2) + def from_png(filename): """ Returns a dense matrix over GF(2) from a 1-bit PNG image read from diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index dd9dfc264dd..c8bccc42ee5 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -50,7 +50,7 @@ from .viewer import browser from .sphinxify import sphinxify import sage.version -from sage.env import SAGE_DOC_SRC, SAGE_DOC, SAGE_SRC +from sage.env import SAGE_DOC, SAGE_SRC # The detex function does two kinds of substitutions: math, which # should only be done on the command line -- in the notebook, these @@ -846,57 +846,8 @@ def _search_src_or_doc(what, string, extra1='', extra2='', extra3='', exts = ['html'] title = 'Documentation' base_path = os.path.join(SAGE_DOC, 'html') - doc_path = SAGE_DOC_SRC - - from sage_setup.docbuild.build_options import LANGUAGES, OMIT - # List of languages - lang = LANGUAGES - # Documents in SAGE_DOC_SRC/LANG/ to omit - omit = OMIT - - # List of documents, minus the omitted ones - documents = [] - for L in lang: - documents += [os.path.join(L, dir) for dir - in os.listdir(os.path.join(doc_path, L)) - if dir not in omit] - - # Check to see if any documents are missing. This just - # checks to see if the appropriate output directory exists, - # not that it contains a complete build of the docs. - missing = [os.path.join(base_path, doc) - for doc in documents if not - os.path.exists(os.path.join(base_path, doc))] - num_missing = len(missing) - if num_missing > 0: - print("""Warning, the following Sage documentation hasn't been built, -so documentation search results may be incomplete: -""") - for s in missing: - print(s) - if num_missing > 1: - print(""" -You can build these with 'sage -docbuild DOCUMENT html', -where DOCUMENT is one of""", end=' ') - for s in missing: - if s.find('en') != -1: - print("'{}',".format(os.path.split(s)[-1]), end=' ') - else: - print("'{}',".format(os.path.join( - os.path.split(os.path.split(s)[0])[-1], - os.path.split(s)[-1])), end=' ') - print(""" -or you can use 'sage -docbuild all html' to build all of the missing documentation.""") - else: - s = missing[0] - if s.find('en') != -1: - s = os.path.split(s)[-1] - else: - s = os.path.join( - os.path.split(os.path.split(s)[0])[-1], - os.path.split(s)[-1]) - print(""" -You can build this with 'sage -docbuild {} html'.""".format(s)) + if not os.path.exists(base_path): + print("""Warning: the Sage documentation is not available""") strip = len(base_path) results = [] @@ -925,7 +876,8 @@ def _search_src_or_doc(what, string, extra1='', extra2='', extra3='', filename = os.path.join(dirpath, f) if re.search(path_re, filename): if multiline: - line = open(filename).read() + with open(filename) as fobj: + line = fobj.read() if re.search(regexp, line, flags): match_list = line else: @@ -937,9 +889,10 @@ def _search_src_or_doc(what, string, extra1='', extra2='', extra3='', if match_list: results.append(filename[strip:].lstrip("/") + '\n') else: - match_list = [(lineno, line) for lineno, line in - enumerate(open(filename).read().splitlines(True)) - if re.search(regexp, line, flags)] + with open(filename) as fobj: + match_list = [(lineno, line) + for lineno, line in enumerate(fobj) + if re.search(regexp, line, flags)] for extra in extra_regexps: if extra: match_list = [s for s in match_list diff --git a/src/sage/modular/modsym/space.py b/src/sage/modular/modsym/space.py index 26a6d5fe296..75ff364fb38 100644 --- a/src/sage/modular/modsym/space.py +++ b/src/sage/modular/modsym/space.py @@ -2260,7 +2260,7 @@ def abvarquo_rational_cuspidal_subgroup(self): is rational. In fact, for each elliptic curve quotient, the `\QQ`-rational subgroup of the image of the cuspidal subgroup in the quotient is a nontrivial subgroup of `E(\QQ)_{tor}`. - Thus not all torsion in the quotient is cuspidal! + Thus not all torsion in the quotient is cuspidal!:: sage: M = ModularSymbols(66).cuspidal_subspace().new_subspace() sage: D = M.decomposition() diff --git a/src/sage/plot/animate.py b/src/sage/plot/animate.py index eaad0e7833d..b10cdac4680 100644 --- a/src/sage/plot/animate.py +++ b/src/sage/plot/animate.py @@ -877,8 +877,6 @@ def ffmpeg(self, savefile=None, show_path=False, output_format=None, TESTS:: sage: a.ffmpeg(output_format='gif',delay=30,iterations=5) # optional -- ffmpeg - doctest:...: DeprecationWarning: use tmp_filename instead - See http://trac.sagemath.org/17234 for details. """ if not self._have_ffmpeg(): msg = """Error: ffmpeg does not appear to be installed. Saving an animation to diff --git a/src/sage/plot/plot3d/base.pyx b/src/sage/plot/plot3d/base.pyx index 8dc2d748d8d..cda5cc93805 100644 --- a/src/sage/plot/plot3d/base.pyx +++ b/src/sage/plot/plot3d/base.pyx @@ -848,7 +848,7 @@ cdef class Graphics3d(SageObject): COLOR 1.0 1.0 1.0 TEXFUNC 0 Texdef texture... - Ambient 0.333333333333 Diffuse 0.666666666667 Specular 0.0 Opacity 1.0 + Ambient 0.3333333333333333 Diffuse 0.6666666666666666 Specular 0.0 Opacity 1.0 Color 1.0 1.0 0.0 TexFunc 0 Sphere center 1.0 -2.0 3.0 Rad 5.0 texture... @@ -860,7 +860,7 @@ cdef class Graphics3d(SageObject): begin_scene ... Texdef texture... - Ambient 0.333333333333 Diffuse 0.666666666667 Specular 0.0 Opacity 1.0 + Ambient 0.3333333333333333 Diffuse 0.6666666666666666 Specular 0.0 Opacity 1.0 Color 1.0 1.0 0.0 TexFunc 0 TRI V0 ... @@ -1766,9 +1766,9 @@ end_scene""" % (render_params.antialiasing, ['solid surface', 'facet normal 0.5257311121191338 0.8506508083520398 -0.0', ' outer loop', - ' vertex 0.0 1.2360679775 -0.472135955', - ' vertex 0.0 1.2360679775 0.472135955', - ' vertex 0.7639320225 0.7639320225 0.7639320225'] + ' vertex 0.0 1.2360679774997898 -0.4721359549995796', + ' vertex 0.0 1.2360679774997898 0.4721359549995796', + ' vertex 0.7639320225002102 0.7639320225002102 0.7639320225002102'] """ from sage.modules.free_module import FreeModule RR3 = FreeModule(RDF, 3) @@ -1778,11 +1778,11 @@ end_scene""" % (render_params.antialiasing, self.triangulate() faces = self.face_list() - code = ("facet normal {} {} {}\n" + code = ("facet normal {!r} {!r} {!r}\n" " outer loop\n" - " vertex {} {} {}\n" - " vertex {} {} {}\n" - " vertex {} {} {}\n" + " vertex {!r} {!r} {!r}\n" + " vertex {!r} {!r} {!r}\n" + " vertex {!r} {!r} {!r}\n" " endloop\n" "endfacet\n") @@ -1906,7 +1906,7 @@ end_scene""" % (render_params.antialiasing, sage: a = implicit_plot3d(x^2+y^2+z^2-9,[x,-5,5],[y,-5,5],[z,-5,5]) sage: a_amf = a.amf_ascii_string() sage: a_amf[:160] - '2.94871794872-0.384615384615-0.39358974359' + '2.948717948717948-0.384615384615385-0.3935' sage: p = polygon3d([[0,0,0], [1,2,3], [3,0,0]]) sage: print(p.amf_ascii_string(name='triangle')) @@ -1923,7 +1923,7 @@ end_scene""" % (render_params.antialiasing, string_list = [''.format(name)] string_list += [''] - vertex_template = '{}{}{}' + vertex_template = '{!r}{!r}{!r}' for v in self.vertices(): string_list += [vertex_template.format(*v)] string_list += [''] diff --git a/src/sage/plot/plot3d/index_face_set.pyx b/src/sage/plot/plot3d/index_face_set.pyx index 278b1788bc7..eaf2340d07a 100644 --- a/src/sage/plot/plot3d/index_face_set.pyx +++ b/src/sage/plot/plot3d/index_face_set.pyx @@ -32,6 +32,8 @@ AUTHORS: #***************************************************************************** from __future__ import print_function, absolute_import +from textwrap import dedent + from libc.math cimport isfinite, INFINITY from libc.string cimport memset, memcpy from cysignals.memory cimport check_calloc, check_allocarray, check_reallocarray, sig_free @@ -761,34 +763,35 @@ cdef class IndexFaceSet(PrimitiveObject): - + """ cdef Py_ssize_t i - points = ",".join(["%s %s %s" % (self.vs[i].x, - self.vs[i].y, - self.vs[i].z) + vs = self.vs + fs = self._faces + points = ",".join(["%r %r %r" % (vs[i].x, vs[i].y, vs[i].z) for i from 0 <= i < self.vcount]) - coordIndex = ",-1,".join([",".join([str(self._faces[i].vertices[j]) - for j from 0 <= j < self._faces[i].n]) - for i from 0 <= i < self.fcount]) + coord_idx = ",-1,".join([",".join([repr(fs[i].vertices[j]) + for j from 0 <= j < fs[i].n]) + for i from 0 <= i < self.fcount]) if not self.global_texture: - colorIndex = ",".join([str(self._faces[i].color.r) + " " - + str(self._faces[i].color.g) + " " - + str(self._faces[i].color.b) - for i from 0 <= i < self.fcount]) - return """ - - - - -""" % (coordIndex, points, colorIndex) - return """ - - - -""" % (coordIndex, points) + color_idx = ",".join(['%r %r %r' % (fs[i].color.r, fs[i].color.g, fs[i].color.b) + for i from 0 <= i < self.fcount]) + # Note: Don't use f-strings, since Sage on Python 2 still expects + # this to return a plain str instead of a unicode + return dedent(""" + + + + + """.format(coord_idx=coord_idx, points=points, color_idx=color_idx)) + + return dedent(""" + + + + """.format(coord_idx=coord_idx, points=points)) def bounding_box(self): r""" diff --git a/src/sage/plot/plot3d/plot3d.py b/src/sage/plot/plot3d/plot3d.py index 6400aabbbd2..25284372e79 100644 --- a/src/sage/plot/plot3d/plot3d.py +++ b/src/sage/plot/plot3d/plot3d.py @@ -127,8 +127,6 @@ def f(x,y): return math.exp(x/5)*math.cos(y) - Oscar Lazo, William Cauchois, Jason Grout (2009-2010): Adding coordinate transformations """ -from __future__ import absolute_import -from six import iteritems #***************************************************************************** # Copyright (C) 2007 Robert Bradshaw @@ -145,6 +143,11 @@ def f(x,y): return math.exp(x/5)*math.cos(y) # http://www.gnu.org/licenses/ #***************************************************************************** +from __future__ import absolute_import + +import inspect + +from six import iteritems from .tri_plot import TrianglePlot from .index_face_set import IndexFaceSet diff --git a/src/sage/plot/plot3d/shapes2.py b/src/sage/plot/plot3d/shapes2.py index c0e1aa4b3bf..b4e9b287baa 100644 --- a/src/sage/plot/plot3d/shapes2.py +++ b/src/sage/plot/plot3d/shapes2.py @@ -774,7 +774,12 @@ def tachyon_repr(self, render_params): cen = self.loc else: cen = transform.transform_point(self.loc) - return "Sphere center %s %s %s Rad %s %s" % (cen[0], cen[1], cen[2], self.size * TACHYON_PIXEL, self.texture.id) + + radius = self.size * TACHYON_PIXEL + texture = self.texture.id + return ("Sphere center {center[0]!r} {center[1]!r} {center[2]!r} " + "Rad {radius!r} {texture}").format(center=cen, radius=radius, + texture=texture) def obj_repr(self, render_params): """ @@ -913,7 +918,7 @@ def tachyon_repr(self, render_params): sage: L = line3d([(cos(i),sin(i),i^2) for i in srange(0,10,.01)],color='red') sage: L.tachyon_repr(L.default_render_params())[0] - 'FCylinder base 1.0 0.0 0.0 apex 0.999950000417 0.00999983333417 0.0001 rad 0.005 texture...' + 'FCylinder base 1.0 0.0 0.0 apex 0.9999500004166653 0.009999833334166664 0.0001 rad 0.005 texture...' """ T = render_params.transform cmds = [] @@ -927,10 +932,12 @@ def tachyon_repr(self, render_params): cmds.append(A.tachyon_repr(render_params)) render_params.pop_transform() else: - cmds.append("FCylinder base %s %s %s apex %s %s %s rad %s %s" % (px, py, pz, - x, y, z, - radius, - self.texture.id)) + cmd = ('FCylinder base {pos[0]!r} {pos[1]!r} {pos[2]!r} ' + 'apex {apex[0]!r} {apex[1]!r} {apex[2]!r} ' + 'rad {radius!r} {texture}').format( + pos=(px, py, pz), apex=(x, y, z), radius=radius, + texture=self.texture.id) + cmds.append(cmd) px, py, pz = x, y, z return cmds diff --git a/src/sage/plot/plot3d/texture.py b/src/sage/plot/plot3d/texture.py index dfe6596d23d..ca6eaccc717 100644 --- a/src/sage/plot/plot3d/texture.py +++ b/src/sage/plot/plot3d/texture.py @@ -35,6 +35,11 @@ - Robert Bradshaw (2007-07-07) Initial version. """ + +from __future__ import division + +from textwrap import dedent + from sage.misc.fast_methods import WithEqualityById from sage.structure.sage_object import SageObject @@ -350,19 +355,29 @@ def tachyon_str(self): sage: from sage.plot.plot3d.texture import Texture sage: t = Texture(opacity=0.6) sage: t.tachyon_str() - 'Texdef texture...\n Ambient 0.33333333333... Diffuse 0.66666666666... Specular 0.0 Opacity 0.6\n Color 0.4 0.4 1.0\n TexFunc 0' + 'Texdef texture...\n Ambient 0.3333333333333333 Diffuse 0.6666666666666666 Specular 0.0 Opacity 0.6\n Color 0.4 0.4 1.0\n TexFunc 0' """ - total_color = float(sum(self.ambient) + sum(self.diffuse) + sum(self.specular)) + + total_ambient = sum(self.ambient) + total_diffuse = sum(self.diffuse) + total_specular = sum(self.specular) + + total_color = total_ambient + total_diffuse + total_specular + if total_color == 0: total_color = 1 - return "Texdef %s\n" % self.id + \ - " Ambient %s Diffuse %s Specular %s Opacity %s\n" % \ - (sum(self.ambient)/total_color, - sum(self.diffuse)/total_color, - sum(self.specular)/total_color, - self.opacity) + \ - " Color %s %s %s\n" % (self.color[0], self.color[1], self.color[2]) + \ - " TexFunc 0" + + ambient = total_ambient / total_color + diffuse = total_diffuse / total_color + specular = total_specular / total_color + + return dedent("""\ + Texdef {id} + Ambient {ambient!r} Diffuse {diffuse!r} Specular {specular!r} Opacity {opacity!r} + Color {color[0]!r} {color[1]!r} {color[2]!r} + TexFunc 0""" + ).format(id=self.id, ambient=ambient, diffuse=diffuse, + specular=specular, opacity=self.opacity, color=self.color) def x3d_str(self): r""" @@ -375,8 +390,13 @@ def x3d_str(self): sage: t.x3d_str() "" """ - return "" % \ - (self.color[0], self.color[1], self.color[2], self.shininess, self.specular[0], self.specular[0], self.specular[0]) + return ( + "" + "" + "").format(color=self.color, shininess=self.shininess, + specular=self.specular[0]) def mtl_str(self): r""" @@ -389,13 +409,18 @@ def mtl_str(self): sage: t.mtl_str() 'newmtl texture...\nKa 0.2 0.2 0.5\nKd 0.4 0.4 1.0\nKs 0.0 0.0 0.0\nillum 1\nNs 1.0\nd 0.6' """ - return "\n".join(["newmtl %s" % self.id, - "Ka %s %s %s" % self.ambient, - "Kd %s %s %s" % self.diffuse, - "Ks %s %s %s" % self.specular, - "illum %s" % (2 if sum(self.specular) > 0 else 1), - "Ns %s" % self.shininess, - "d %s" % self.opacity, ]) + return dedent("""\ + newmtl {id} + Ka {ambient[0]!r} {ambient[1]!r} {ambient[2]!r} + Kd {diffuse[0]!r} {diffuse[1]!r} {diffuse[2]!r} + Ks {specular[0]!r} {specular[1]!r} {specular[2]!r} + illum {illumination} + Ns {shininess!r} + d {opacity!r}""" + ).format(id=self.id, ambient=self.ambient, diffuse=self.diffuse, + specular=self.specular, + illumination=(2 if sum(self.specular) > 0 else 1), + shininess=self.shininess, opacity=self.opacity) def jmol_str(self, obj): r""" diff --git a/src/sage/repl/preparse.py b/src/sage/repl/preparse.py index c6d5c935c7d..8fa17bec307 100644 --- a/src/sage/repl/preparse.py +++ b/src/sage/repl/preparse.py @@ -1560,7 +1560,8 @@ def preparse_file_named_to_stream(name, out): stream \code{out}. """ name = os.path.abspath(name) - contents = open(name).read() + with open(name) as f: + contents = f.read() contents = handle_encoding_declaration(contents, out) parsed = preparse_file(contents) out.write('#'*70+'\n') @@ -1575,7 +1576,6 @@ def preparse_file_named(name): """ from sage.misc.temporary_file import tmp_filename tmpfilename = tmp_filename(os.path.basename(name)) + '.py' - out = open(tmpfilename, 'w') - preparse_file_named_to_stream(name, out) - out.close() + with open(tmpfilename, 'w') as out: + preparse_file_named_to_stream(name, out) return tmpfilename diff --git a/src/sage/rings/function_field/constructor.py b/src/sage/rings/function_field/constructor.py index d9094c68e8b..aa35dbd75c1 100644 --- a/src/sage/rings/function_field/constructor.py +++ b/src/sage/rings/function_field/constructor.py @@ -23,14 +23,6 @@ - Julian Rueth (2011-09-14): replaced ``@cached_function`` with ``UniqueFactory`` -EXAMPLES:: - - sage: K. = FunctionField(QQ); K - Rational function field in x over Rational Field - sage: L. = FunctionField(QQ); L - Rational function field in x over Rational Field - sage: K is L - True """ from __future__ import absolute_import #***************************************************************************** diff --git a/src/sage/rings/function_field/function_field_element.pyx b/src/sage/rings/function_field/element.pyx similarity index 98% rename from src/sage/rings/function_field/function_field_element.pyx rename to src/sage/rings/function_field/element.pyx index 6f174d98af6..ffc869888b7 100644 --- a/src/sage/rings/function_field/function_field_element.pyx +++ b/src/sage/rings/function_field/element.pyx @@ -49,9 +49,9 @@ def is_FunctionFieldElement(x): EXAMPLES:: sage: t = FunctionField(QQ,'t').gen() - sage: sage.rings.function_field.function_field_element.is_FunctionFieldElement(t) + sage: sage.rings.function_field.element.is_FunctionFieldElement(t) True - sage: sage.rings.function_field.function_field_element.is_FunctionFieldElement(0) + sage: sage.rings.function_field.element.is_FunctionFieldElement(0) False """ if isinstance(x, FunctionFieldElement): @@ -65,7 +65,7 @@ def make_FunctionFieldElement(parent, element_class, representing_element): EXAMPLES:: - sage: from sage.rings.function_field.function_field_element import make_FunctionFieldElement + sage: from sage.rings.function_field.element import make_FunctionFieldElement sage: K. = FunctionField(QQ) sage: make_FunctionFieldElement(K, K._element_class, (x+1)/x) (x + 1)/x @@ -79,7 +79,7 @@ cdef class FunctionFieldElement(FieldElement): EXAMPLES:: sage: t = FunctionField(QQ,'t').gen() - sage: isinstance(t, sage.rings.function_field.function_field_element.FunctionFieldElement) + sage: isinstance(t, sage.rings.function_field.element.FunctionFieldElement) True """ cdef readonly object _x @@ -794,6 +794,12 @@ cdef class FunctionFieldElement_rational(FunctionFieldElement): def valuation(self, v): """ + Return the valuation of the element with respect to a prime element. + + INPUT: + + - ``v`` -- a prime element of the function field + EXAMPLES:: sage: K. = FunctionField(QQ) @@ -810,7 +816,7 @@ cdef class FunctionFieldElement_rational(FunctionFieldElement): def is_square(self): """ - Returns whether self is a square. + Return whether the element is a square. EXAMPLES:: @@ -889,6 +895,7 @@ cdef class FunctionFieldElement_rational(FunctionFieldElement): -1/2*x + 1/2 sage: (t*(x+1) - 1) in I True + """ assert len(I.gens()) == 1 f = I.gens()[0]._x diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index 9c53f31dfd4..9ddf8aca49b 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -75,6 +75,36 @@ sage: TestSuite(R).run() sage: TestSuite(S).run() # long time (3s) +Global function fields +---------------------- + +Most of advanced computations are available only for global function fields as +yet. A global function field in Sage is an extension field of a rational function field +over a *finite* constant field by an irreducible separable polynomial over the +rational function field. + +EXAMPLES: + +A fundamental computation for a global or any function field is to get a basis +of its maximal order and maximal infinite order, and then do arithmetic with +ideals of those maximal orders:: + + sage: K. = FunctionField(GF(3)); _. = K[] + sage: L. = K.extension(t^4 + t - x^5) + sage: O = L.maximal_order() + sage: O.basis() + (1, y, 1/x*y^2 + 1/x*y, 1/x^3*y^3 + 2/x^3*y^2 + 1/x^3*y) + sage: I = O.ideal(x,y); I + Ideal (x, y + x) of Maximal order of Function field in y defined by y^4 + y + 2*x^5 + sage: J = I^-1 + sage: J.basis_matrix() + [ 1 0 0 0] + [1/x 1/x 0 0] + [ 0 0 1 0] + [ 0 0 0 1] + sage: L.maximal_order_infinite().basis() + (1, 1/x^2*y, 1/x^3*y^2, 1/x^4*y^3) + AUTHORS: - William Stein (2010): initial version @@ -118,9 +148,8 @@ from sage.categories.homset import Hom from sage.categories.function_fields import FunctionFields -CAT = FunctionFields() -from .function_field_element import ( +from .element import ( FunctionFieldElement, FunctionFieldElement_rational, FunctionFieldElement_polymod, @@ -159,28 +188,26 @@ class FunctionField(Field): """ Abstract base class for all function fields. + INPUT: + + - ``base_field`` -- field; the base of this function field + + - ``names`` -- string that gives the name of the generator + EXAMPLES:: sage: K. = FunctionField(QQ) sage: K Rational function field in x over Rational Field """ - def __init__(self, base_field, names, category=CAT): + def __init__(self, base_field, names, category=FunctionFields()): """ Initialize. - INPUT: - - - ``base_field`` -- field; the base of this function field - - - ``names`` -- string that gives the name of the generator - TESTS:: sage: K. = FunctionField(QQ) - sage: from sage.rings.function_field.function_field import FunctionField - sage: isinstance(K, FunctionField) - True + sage: TestSuite(K).run() """ Field.__init__(self, base_field, names=names, category=category) @@ -376,17 +403,17 @@ def order_with_basis(self, basis, check=True): sage: O = L.order_with_basis([1, x^2 + x*y, (2/3)*y^2]); O Traceback (most recent call last): ... - ValueError: The module generated by basis [1, x*y + x^2, 2/3*y^2] must be closed under multiplication + ValueError: the module generated by basis (1, x*y + x^2, 2/3*y^2) must be closed under multiplication and this happens when the identity is not in the module spanned by the basis:: sage: O = L.order_with_basis([x, x^2 + x*y, (2/3)*y^2]) Traceback (most recent call last): ... - ValueError: The identity element must be in the module spanned by basis [x, x*y + x^2, 2/3*y^2] + ValueError: the identity element must be in the module spanned by basis (x, x*y + x^2, 2/3*y^2) """ - from .function_field_order import FunctionFieldOrder_basis - return FunctionFieldOrder_basis([self(a) for a in basis], check=check) + from .order import FunctionFieldOrder_basis + return FunctionFieldOrder_basis(tuple([self(a) for a in basis]), check=check) def order(self, x, check=True): """ @@ -430,9 +457,101 @@ def order(self, x, check=True): raise NotImplementedError return self.order_with_basis(basis, check=check) + def order_infinite_with_basis(self, basis, check=True): + """ + Return the order with given basis over the maximal infinite order of + the base field. + + INPUT: + + - ``basis`` -- list of elements of the function field + + - ``check`` -- boolean (default: ``True``); if ``True``, check that the basis + is really linearly independent and that the module it spans is closed + under multiplication, and contains the identity element. + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^3 + x^3 + 4*x + 1) + sage: O = L.order_infinite_with_basis([1, 1/x*y, 1/x^2*y^2]); O + Infinite order in Function field in y defined by y^3 + x^3 + 4*x + 1 + sage: O.basis() + (1, 1/x*y, 1/x^2*y^2) + + Note that 1 does not need to be an element of the basis, as long it is + in the module spanned by it:: + + sage: O = L.order_infinite_with_basis([1+1/x*y,1/x*y, 1/x^2*y^2]); O + Infinite order in Function field in y defined by y^3 + x^3 + 4*x + 1 + sage: O.basis() + (1/x*y + 1, 1/x*y, 1/x^2*y^2) + + The following error is raised when the module spanned by the basis is + not closed under multiplication:: + + sage: O = L.order_infinite_with_basis([1,y, 1/x^2*y^2]); O + Traceback (most recent call last): + ... + ValueError: the module generated by basis (1, y, 1/x^2*y^2) must be closed under multiplication + + and this happens when the identity is not in the module spanned by the + basis:: + + sage: O = L.order_infinite_with_basis([1/x,1/x*y, 1/x^2*y^2]) + Traceback (most recent call last): + ... + ValueError: the identity element must be in the module spanned by basis (1/x, 1/x*y, 1/x^2*y^2) + """ + from .order import FunctionFieldOrderInfinite_basis + return FunctionFieldOrderInfinite_basis(tuple([self(g) for g in basis]), check=check) + + def order_infinite(self, x, check=True): + """ + Return the order generated by ``x`` over the maximal infinite order. + + INPUT: + + - ``x`` -- element or a list of elements of the function field + + - ``check`` -- boolean; if ``True``, check that ``x`` really generates an order + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^3 + x^3 + 4*x + 1) + sage: L.order_infinite(y) # todo: not implemented + + sage: Z = K.order(x); Z + Order in Rational function field in x over Rational Field + sage: Z.basis() + (1,) + + Orders with multiple generators, not yet supported:: + + sage: Z = K.order_infinite([x,x^2]); Z + Traceback (most recent call last): + ... + NotImplementedError + """ + if not isinstance(x, (list, tuple)): + x = [x] + if len(x) == 1: + g = x[0] + basis = [self(1)] + for i in range(self.degree()-1): + basis.append(basis[-1]*g) + else: + raise NotImplementedError + return self.order_infinite_with_basis(tuple(basis), check=check) + def _coerce_map_from_(self, source): """ - Return True if there is a coerce map from R to self. + Return ``True`` if there is a coerce map from ``R`` to the function field. + + INPUT: + + - ``source`` -- ring EXAMPLES:: @@ -466,8 +585,8 @@ def _coerce_map_from_(self, source): sage: M.has_coerce_map_from(L) True """ - from .function_field_order import FunctionFieldOrder - if isinstance(source, FunctionFieldOrder): + from .order import FunctionFieldOrder_base + if isinstance(source, FunctionFieldOrder_base): K = source.fraction_field() if K is self: return self._generic_coerce_map(source) @@ -475,7 +594,6 @@ def _coerce_map_from_(self, source): K_to_self = self.coerce_map_from(K) if source_to_K and K_to_self: return K_to_self * source_to_K - from sage.categories.function_fields import FunctionFields if source in FunctionFields(): if source.base_field() is source: if self.base_field() is self: @@ -649,7 +767,7 @@ def valuation(self, prime): for isomorphisms to and from that function field EXAMPLES: - + We create valuations that correspond to finite rational places of a function field:: @@ -665,9 +783,9 @@ def valuation(self, prime): sage: v = K.valuation(x - 1); v (x - 1)-adic valuation - + Similarly, for a finite non-rational place:: - + sage: v = K.valuation(x^2 + 1); v (x^2 + 1)-adic valuation sage: v(x^2 + 1) @@ -676,7 +794,7 @@ def valuation(self, prime): 0 Or for the infinite place:: - + sage: v = K.valuation(1/x); v Valuation at the infinite place sage: v(x) @@ -685,15 +803,15 @@ def valuation(self, prime): Instead of specifying a generator of a place, we can define a valuation on a rational function field by giving a discrete valuation on the underlying polynomial ring:: - + sage: R. = QQ[] sage: w = valuations.GaussValuation(R, valuations.TrivialValuation(QQ)).augmentation(x - 1, 1) sage: v = K.valuation(w); v (x - 1)-adic valuation - + Note that this allows us to specify valuations which do not correspond to a place of the function field:: - + sage: w = valuations.GaussValuation(R, QQ.valuation(2)) sage: v = K.valuation(w); v 2-adic valuation @@ -759,6 +877,14 @@ class FunctionField_polymod(FunctionField): Function fields defined by a univariate polynomial, as an extension of the base field. + INPUT: + + - ``polynomial`` -- univariate polynomial over a function field + + - ``names`` -- tuple of length 1 or string; variable names + + - ``category`` -- category (default: category of function fields) + EXAMPLES: We make a function field defined by a degree 5 polynomial over the @@ -799,30 +925,22 @@ class FunctionField_polymod(FunctionField): :: - sage: K.=FunctionField(QQ) + sage: K. = FunctionField(QQ) sage: R. = K[] - sage: L.=K.extension(x^2-y^2) - sage: (y-x)*(y+x) + sage: L. = K.extension(x^2 - y^2) + sage: (y - x)*(y + x) 0 - sage: 1/(y-x) + sage: 1/(y - x) 1 - sage: y-x==0; y+x==0 + sage: y - x == 0; y + x == 0 False False """ - def __init__(self, polynomial, names, element_class=FunctionFieldElement_polymod, category=CAT): + def __init__(self, polynomial, names, element_class=FunctionFieldElement_polymod, category=FunctionFields()): """ Create a function field defined as an extension of another function field by adjoining a root of a univariate polynomial. - INPUT: - - - ``polynomial`` -- univariate polynomial over a function field - - - ``names`` -- tuple of length 1 or string; variable names - - - ``category`` -- category (default: category of function fields) - EXAMPLES: We create an extension of a function field:: @@ -830,11 +948,7 @@ def __init__(self, polynomial, names, element_class=FunctionFieldElement_polymod sage: K. = FunctionField(QQ); R. = K[] sage: L = K.extension(y^5 - x^3 - 3*x + x*y); L Function field in y defined by y^5 + x*y - x^3 - 3*x - - Note the type:: - - sage: type(L) - + sage: TestSuite(L).run() # long time We can set the variable name, which doesn't have to be y:: @@ -881,24 +995,6 @@ def __init__(self, polynomial, names, element_class=FunctionFieldElement_polymod self._populate_coercion_lists_(coerce_list=[base_field, self._ring]) self._gen = self(self._ring.gen()) - def __reduce__(self): - """ - Return the arguments which were used to create this instance. - - The rationale for this is explained in the documentation of - :class:`UniqueRepresentation`. - - EXAMPLES:: - - sage: K. = FunctionField(QQ); R. = K[] - sage: L = K.extension(y^2 - x) - sage: clazz,args = L.__reduce__() - sage: clazz(*args) - Function field in y defined by y^2 - x - """ - from .constructor import FunctionFieldExtension - return FunctionFieldExtension, (self._polynomial, self._names) - def __hash__(self): """ Return hash of the function field. @@ -948,9 +1044,9 @@ def gen(self, n=0): sage: L.gen(1) Traceback (most recent call last): ... - IndexError: Only one generator. + IndexError: there is only one generator """ - if n != 0: raise IndexError("Only one generator.") + if n != 0: raise IndexError("there is only one generator") return self._gen def ngens(self): @@ -1276,7 +1372,7 @@ def degree(self, base=None): sage: L.degree(M) Traceback (most recent call last): ... - ValueError: base must be None or the rational function field + ValueError: base must be the rational function field itself """ if base is None: @@ -1691,7 +1787,7 @@ def genus(self): return int(curveIdeal._singular_().genus()) else: - raise NotImplementedError("Computation of genus over the rational " + raise NotImplementedError("computation of genus over the rational " "function field not implemented yet") @cached_method @@ -2354,28 +2450,67 @@ def change_variable_name(self, name): class FunctionField_global(FunctionField_polymod): """ Global function fields. - """ - def __init__(self, polynomial, names): - """ - Initialize the function field. - INPUT: + INPUT: - - ``polynomial`` -- monic irreducible and separable polynomial + - ``polynomial`` -- monic irreducible and separable polynomial - - ``names`` -- name of the generator of the function field + - ``names`` -- name of the generator of the function field - EXAMPLES:: + EXAMPLES:: + + sage: K.=FunctionField(GF(5)); _.=K[] + sage: L.=K.extension(Y^3-(x^3-1)/(x^3-2)) + sage: L + Function field in y defined by y^3 + (4*x^3 + 1)/(x^3 + 3) + """ + def __init__(self, polynomial, names): + """ + Initialize. + + TESTS:: sage: K.=FunctionField(GF(5)); _.=K[] sage: L.=K.extension(Y^3-(x^3-1)/(x^3-2)) - sage: L - Function field in y defined by y^3 + (4*x^3 + 1)/(x^3 + 3) + sage: TestSuite(L).run() """ FunctionField_polymod.__init__(self, polynomial, names, element_class=FunctionFieldElement_global) - self._cache = {} + def maximal_order(self): + """ + Return the maximal order of the function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); + sage: R. = PolynomialRing(K); + sage: F. = K.extension(t^4 + x^12*t^2 + x^18*t + x^21 + x^18); + sage: O = F.maximal_order() + sage: O.basis() + (1, 1/x^4*y, 1/x^11*y^2 + 1/x^2, 1/x^15*y^3 + 1/x^6*y) + """ + from .order import FunctionFieldMaximalOrder_global + return FunctionFieldMaximalOrder_global(self) + + def maximal_order_infinite(self): + """ + Return the maximal infinite order of the function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: F.maximal_order_infinite() + Maximal infinite order of Function field in y defined by y^3 + x^6 + x^4 + x^2 + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: L.maximal_order_infinite() + Maximal infinite order of Function field in y defined by y^2 + y + (x^2 + 1)/x + """ + from .order import FunctionFieldMaximalOrderInfinite_global + return FunctionFieldMaximalOrderInfinite_global(self) @cached_method def _inversion_isomorphism(self): @@ -2447,12 +2582,177 @@ class FunctionField_global_integral(FunctionField_global): which is integral over the maximal order of the base rational function field with a finite constant field. """ - pass + def _maximal_order_basis(self): + """ + Return a basis of the maximal order of the function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); + sage: R. = PolynomialRing(K); + sage: F. = K.extension(t^4 + x^12*t^2 + x^18*t + x^21 + x^18); + sage: F._maximal_order_basis() + [1, 1/x^4*y, 1/x^11*y^2 + 1/x^2, 1/x^15*y^3 + 1/x^6*y] + + The basis of the maximal order *always* starts with 1. This is assumed + in some algorithms. + """ + from sage.matrix.constructor import matrix + + from sage.libs.singular.function import singular_function, lib + from sage.env import SAGE_EXTCODE + lib(SAGE_EXTCODE + '/singular/function_field/core.lib') + normalize = singular_function('core_normalize') + + k = self.constant_base_field() + K = self.base_field() # rational function field + n = self.degree() + + # construct the defining polynomial of the function field + # as a two-variate polynomial g in the ring k[y,x] where + # k is the constant base field + S,(y,x) = PolynomialRing(k, names='y,x', order='lex').objgens() + v = self.polynomial().list() + g = sum([v[i].numerator().subs(x) * y**i for i in range(len(v))]) + + # Singular "normalP" algorithm assumes affine domain over + # a prime field. So we constuct gflat lifting g as in + # k_prime[yy,xx,zz]/(k_poly) where k = k_prime[zz]/(k_poly) + R = PolynomialRing(k.prime_subfield(), names='yy,xx,zz') + gflat = R.zero() + for m in g.monomials(): + c = g.monomial_coefficient(m).polynomial('zz') + gflat += R(c) * R(m) # R(m) is a monomial in yy and xx + + k_poly = R(k.polynomial('zz')) + + # invoke Singular + pols_in_R = normalize(R.ideal([k_poly, gflat])) + + # reconstruct polynomials in S + h = R.hom([y,x,k.gen()],S) + pols_in_S = [h(f) for f in pols_in_R] + + # reconstruct polynomials in the function field + x = K.gen() + y = self.gen() + pols = [] + for f in pols_in_S: + p = f.polynomial(S.gen(0)) + s = 0 + for i in range(p.degree()+1): + s += p[i].subs(x) * y**i + pols.append(s) + + # Now if pols = [g1,g2,...gn,g0], then the g1/g0,g2/g0,...,gn/g0, + # and g0/g0=1 are the module generators of the integral closure + # of the equation order Sb = k[xb,yb] in its fraction field, + # that is, the function field. The integral closure of k[x] + # is then obtained by multiplying these generators with powers of y + # as the equation order itself is an integral extension of k[x]. + d = ~ pols[-1] + _basis = [] + for f in pols: + b = d * f + for i in range(n): + _basis.append(b) + b *= y + + # Finally we reduce _basis to get a basis over k[x]. This is done of + # course by Hermite normal form computation. Here we apply a trick to + # get a basis that starts with 1 and is ordered in increasing + # y-degrees. The trick is to use the reversed Hermite normal form. + # Note that it is important that the overall denominator l lies in k[x]. + V, fr_V, to_V = self.vector_space() + basis_V = [to_V(b) for b in _basis] + l = lcm([v.denominator() for v in basis_V]) + + # Why do we have 'reversed' here? I don't know. But without it, the + # time to get hermite_form_reversed dramatically increases. + _mat = matrix([[c.numerator() for c in l*v] for v in reversed(basis_V)]) + + # compute the reversed hermite form + _mat.reverse_rows_and_columns() + _mat._hermite_form_euclidean(normalization=lambda p: ~p.lc()) + _mat.reverse_rows_and_columns() + + basis = [fr_V(v) / l for v in _mat if not v.is_zero()] + return basis + + @cached_method + def equation_order(self): + """ + Return the equation order of the function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: F.equation_order() + Order in Function field in y defined by y^3 + x^6 + x^4 + x^2 + """ + from .order import FunctionFieldOrder_basis + a = self.gen() + basis = [a**i for i in range(self.degree())] + return FunctionFieldOrder_basis(tuple(basis)) + + @cached_method + def primitive_integal_element_infinite(self): + """ + Return a primitive integral element over the base maximal infinite order. + + This element is integral over the maximal infinite order of the base + rational function field and the function field is a simple extension by + this element over the base order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: b = F.primitive_integal_element_infinite(); b + 1/x^2*y + sage: b.minimal_polynomial('t') + t^3 + (x^4 + x^2 + 1)/x^4 + """ + f = self.polynomial() + n = f.degree() + y = self.gen() + x = self.base_field().gen() + + cf = max([(f[i].numerator().degree()/(n-i)).ceil() for i in range(n) + if f[i] != 0]) + return y*x**(-cf) + + @cached_method + def equation_order_infinite(self): + """ + Return the infinite equation order of the function field. + + This is by definition `o[b]` where `b` is the primitive integral + element from :meth:`primitive_integal_element_infinite()` and `o` is + the maximal infinite order of the base rational function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: F.equation_order_infinite() + Infinite order in Function field in y defined by y^3 + x^6 + x^4 + x^2 + """ + from .order import FunctionFieldOrderInfinite_basis + b = self.primitive_integal_element_infinite() + basis = [b**i for i in range(self.degree())] + return FunctionFieldOrderInfinite_basis(tuple(basis)) class RationalFunctionField(FunctionField): """ - Rational function field `K(t)` in one variable, over an arbitrary - base field. + Rational function field in one variable, over an arbitrary base field. + + INPUT: + + - ``constant_field`` -- arbitrary field + + - ``names`` -- string or tuple of length 1 EXAMPLES:: @@ -2474,7 +2774,7 @@ class RationalFunctionField(FunctionField): sage: K.constant_field() Finite Field of size 7 sage: K.maximal_order() - Maximal order in Rational function field in t over Finite Field of size 7 + Maximal order of Rational function field in t over Finite Field of size 7 We define a morphism:: @@ -2488,22 +2788,16 @@ class RationalFunctionField(FunctionField): """ def __init__(self, constant_field, names, element_class = FunctionFieldElement_rational, - category=CAT): + category=FunctionFields()): """ - Create a rational function field in one variable. - - INPUT: - - - ``constant_field`` -- arbitrary field - - - ``names`` -- string or tuple of length 1 + Initialize. EXAMPLES:: sage: K. = FunctionField(CC); K Rational function field in t over Complex Field with 53 bits of precision - sage: K.category() - Category of function fields + sage: TestSuite(K).run() + sage: FunctionField(QQ[I], 'alpha') Rational function field in alpha over Number Field in I with defining polynomial x^2 + 1 @@ -2540,7 +2834,7 @@ def __init__(self, constant_field, names, def __reduce__(self): """ - Returns the arguments which were used to create this instance. The + Return the arguments which were used to create this instance. The rationale for this is explained in the documentation of :class:`UniqueRepresentation`. @@ -2682,7 +2976,7 @@ def _to_polynomial(self, f): K = f.parent().constant_base_field() if f.denominator() in K: return f.numerator()/K(f.denominator()) - raise ValueError("Only polynomials can be converted to the underlying polynomial ring") + raise ValueError("only polynomials can be converted to the underlying polynomial ring") def _to_bivariate_polynomial(self, f): """ @@ -2846,19 +3140,29 @@ def polynomial_ring(self, var='x'): """ return self[var] - @cached_method - def vector_space(self): + @cached_method(key=lambda self, base: None) + def vector_space(self, base=None): """ - Return a vector space and an isomorphism from the function field to the - vector space and its iverse isomorphism. + Return a vector space `V` and isomorphisms from the field to `V` and + from `V` to the field. + + This function allows us to identify the elements of this field with + elements of a one-dimensional vector space over the field itself. This + method exists so that all function fields (rational or not) have the + same interface. + + INPUT: + + - ``base`` -- the base field of the vector space; must be the function + field itself (the default) OUTPUT: - - a vector space `V` over the constant field + - a vector space `V` over base field - - an isomorphism from `V` to the function field + - an isomorphism from `V` to the field - - the inverse isomorphism from the function field to `V` + - the inverse isomorphism from the field to `V` EXAMPLES:: @@ -2869,9 +3173,23 @@ def vector_space(self): To: Rational function field in x over Rational Field, Isomorphism: From: Rational function field in x over Rational Field To: Vector space of dimension 1 over Rational function field in x over Rational Field) + + TESTS:: + + sage: K.vector_space() + (Vector space of dimension 1 over Rational function field in x over Rational Field, Isomorphism: + From: Vector space of dimension 1 over Rational function field in x over Rational Field + To: Rational function field in x over Rational Field, Isomorphism: + From: Rational function field in x over Rational Field + To: Vector space of dimension 1 over Rational function field in x over Rational Field) + """ - V = self.base_field()**1 from .maps import MapVectorSpaceToFunctionField, MapFunctionFieldToVectorSpace + if base is None: + base = self + elif base is not self: + raise ValueError("base must be the rational function field itself") + V = base**1 from_V = MapVectorSpaceToFunctionField(V, self) to_V = MapFunctionFieldToVectorSpace(self, V) return (V, from_V, to_V) @@ -2909,8 +3227,8 @@ def degree(self, base=None): """ if base is None: base = self - if base is not self: - raise ValueError("base must be None or the rational function field") + elif base is not self: + raise ValueError("base must be the rational function field itself") from sage.rings.integer_ring import ZZ return ZZ(1) @@ -3010,7 +3328,7 @@ def hom(self, im_gens, base_morphism=None): x = im_gens[0] R = x.parent() if base_morphism is None and not R.has_coerce_map_from(self.constant_field()): - raise ValueError("You must specify a morphism on the base field") + raise ValueError("you must specify a morphism on the base field") from .maps import FunctionFieldMorphism_rational return FunctionFieldMorphism_rational(self.Hom(R), x, base_morphism) @@ -3035,34 +3353,55 @@ def field(self): @cached_method def maximal_order(self): """ - Return the maximal order of this function field. Since this - is a rational function field it is of the form K(t), and the - maximal order is by definition K[t]. + Return the maximal order of the function field. + + Since this is a rational function field it is of the form `K(t)`, and the + maximal order is by definition `K[t]`, where `K` is the constant field. EXAMPLES:: sage: K. = FunctionField(QQ) sage: K.maximal_order() - Maximal order in Rational function field in t over Rational Field + Maximal order of Rational function field in t over Rational Field sage: K.equation_order() - Maximal order in Rational function field in t over Rational Field + Maximal order of Rational function field in t over Rational Field """ - from .function_field_order import FunctionFieldOrder_rational - return FunctionFieldOrder_rational(self) + from .order import FunctionFieldMaximalOrder_rational + return FunctionFieldMaximalOrder_rational(self) equation_order = maximal_order + @cached_method + def maximal_order_infinite(self): + """ + Return the maximal infinite order of the function field. + + By definition, this is the valuation ring of the degree valuation of + the rational function field. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: K.maximal_order_infinite() + Maximal infinite order of Rational function field in t over Rational Field + sage: K.equation_order_infinite() + Maximal infinite order of Rational function field in t over Rational Field + """ + from .order import FunctionFieldMaximalOrderInfinite_rational + return FunctionFieldMaximalOrderInfinite_rational(self) + + equation_order_infinite = maximal_order_infinite + def constant_base_field(self): """ - Return the field that this rational function field is a - transcendental extension of. + Return the field of which the rational function field is a + transcendental extension. EXAMPLES:: sage: K. = FunctionField(QQ) - sage: K.constant_field() + sage: K.constant_base_field() Rational Field - """ return self._constant_field @@ -3080,60 +3419,6 @@ def genus(self): """ return 0 - @cached_method(key=lambda self, base: None) - def vector_space(self, base=None): - """ - Return a vector space `V` and isomorphisms from the field to `V` and - from `V` to the field. - - This function allows us to identify the elements of this field with - elements of a one-dimensional vector space over the field itself. This - method exists so that all function fields (rational or not) have the - same interface. - - INPUT: - - - ``base`` -- the base field of the vector space; must be the function - field itself (the default) - - OUTPUT: - - - a vector space `V` over base field - - - an isomorphism from `V` to the field - - - the inverse isomorphism from the field to `V` - - EXAMPLES:: - - sage: K. = FunctionField(QQ) - sage: K.vector_space() - (Vector space of dimension 1 over Rational function field in x over Rational Field, Isomorphism: - From: Vector space of dimension 1 over Rational function field in x over Rational Field - To: Rational function field in x over Rational Field, Isomorphism: - From: Rational function field in x over Rational Field - To: Vector space of dimension 1 over Rational function field in x over Rational Field) - - TESTS:: - - sage: K.vector_space() - (Vector space of dimension 1 over Rational function field in x over Rational Field, Isomorphism: - From: Vector space of dimension 1 over Rational function field in x over Rational Field - To: Rational function field in x over Rational Field, Isomorphism: - From: Rational function field in x over Rational Field - To: Vector space of dimension 1 over Rational function field in x over Rational Field) - - """ - from .maps import MapVectorSpaceToFunctionField, MapFunctionFieldToVectorSpace - if base is None: - base = self - if base is not self: - raise ValueError("base must be the rational function field or None") - V = base**1 - from_V = MapVectorSpaceToFunctionField(V, self) - to_V = MapFunctionFieldToVectorSpace(self, V) - return (V, from_V, to_V) - def change_variable_name(self, name): r""" Return a field isomorphic to this field with variable ``name``. diff --git a/src/sage/rings/function_field/function_field_ideal.py b/src/sage/rings/function_field/function_field_ideal.py deleted file mode 100644 index 28d81f6fd9d..00000000000 --- a/src/sage/rings/function_field/function_field_ideal.py +++ /dev/null @@ -1,330 +0,0 @@ -r""" -Ideals of function fields - -Ideals of an order of a function field include all fractional ideals of the order. Sage provides basic arithmetic with fractional ideals. - -AUTHORS: - -- William Stein (2010): initial version - -- Maarten Derickx (2011-09-14): fixed ideal_with_gens_over_base() - -EXAMPLES: - -Ideals in the maximal order of a rational function field:: - - sage: K. = FunctionField(QQ) - sage: O = K.maximal_order() - sage: I = O.ideal(x^3+1); I - Ideal (x^3 + 1) of Maximal order in Rational function field in x over Rational Field - sage: I^2 - Ideal (x^6 + 2*x^3 + 1) of Maximal order in Rational function field in x over Rational Field - sage: ~I - Ideal (1/(x^3 + 1)) of Maximal order in Rational function field in x over Rational Field - sage: ~I * I - Ideal (1) of Maximal order in Rational function field in x over Rational Field - -Ideals in the equation order of an extension of a rational function field:: - - sage: K. = FunctionField(QQ); R. = K[] - sage: L. = K.extension(y^2-x^3-1) - sage: O = L.equation_order() - sage: I = O.ideal(y); I - Ideal (x^3 + 1, -y) of Order in Function field in y defined by y^2 - x^3 - 1 - sage: I^2 - Ideal (x^3 + 1, (-x^3 - 1)*y) of Order in Function field in y defined by y^2 - x^3 - 1 - sage: ~I - Ideal (-1, (1/(x^3 + 1))*y) of Order in Function field in y defined by y^2 - x^3 - 1 - sage: ~I * I - Ideal (1, y) of Order in Function field in y defined by y^2 - x^3 - 1 - sage: I.intersection(~I) - Ideal (x^3 + 1, -y) of Order in Function field in y defined by y^2 - x^3 - 1 -""" -from __future__ import absolute_import -#***************************************************************************** -# Copyright (C) 2010 William Stein -# Copyright (C) 2011 Maarten Derickx -# -# Distributed under the terms of the GNU General Public License (GPL) -# as published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** - -from sage.rings.ideal import Ideal_generic -from sage.structure.richcmp import richcmp - - -class FunctionFieldIdeal(Ideal_generic): - """ - A fractional ideal of a function field. - - EXAMPLES:: - - sage: K. = FunctionField(GF(7)) - sage: O = K.maximal_order() - sage: I = O.ideal(x^3+1) - sage: isinstance(I, sage.rings.function_field.function_field_ideal.FunctionFieldIdeal) - True - """ - pass - -class FunctionFieldIdeal_module(FunctionFieldIdeal): - """ - A fractional ideal specified by a finitely generated module over - the integers of the base field. - - EXAMPLES: - - An ideal in an extension of a rational function field:: - - sage: K. = FunctionField(QQ); R. = K[] - sage: L. = K.extension(y^2 - x^3 - 1) - sage: O = L.equation_order() - sage: I = O.ideal(y) - sage: I - Ideal (x^3 + 1, -y) of Order in Function field in y defined by y^2 - x^3 - 1 - sage: I^2 - Ideal (x^3 + 1, (-x^3 - 1)*y) of Order in Function field in y defined by y^2 - x^3 - 1 - """ - def __init__(self, ring, module): - """ - INPUT: - - - ``ring`` -- an order in a function field - - ``module`` -- a module - - EXAMPLES:: - - sage: K. = FunctionField(QQ); R. = K[] - sage: L. = K.extension(y^2 - x^3 - 1) - sage: O = L.equation_order() - sage: I = O.ideal(y) - sage: type(I) - - """ - self._ring = ring - self._module = module - self._structure = ring.fraction_field().vector_space() - V, from_V, to_V = self._structure - gens = tuple([from_V(a) for a in module.basis()]) - Ideal_generic.__init__(self, ring, gens, coerce=False) - - def __contains__(self, x): - """ - Return True if x is in this ideal. - - EXAMPLES:: - - sage: K. = FunctionField(GF(7)); R. = K[] - sage: L. = K.extension(y^2 - x^3 - 1) - sage: O = L.equation_order() - sage: I = O.ideal_with_gens_over_base([1, y]); I - Ideal (1, y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 - sage: y in I - True - sage: y/x in I - False - sage: y^2 - 2 in I - True - """ - return self._structure[2](x) in self._module - - def module(self): - """ - Return module over the maximal order of the base field that - underlies self. - - The formation of this module is compatible with the vector - space corresponding to the function field. - - OUTPUT: - - - a module over the maximal order of the base field of self - - EXAMPLES:: - - sage: K. = FunctionField(GF(7)) - sage: O = K.maximal_order(); O - Maximal order in Rational function field in x over Finite Field of size 7 - sage: K.polynomial_ring() - Univariate Polynomial Ring in x over Rational function field in x over Finite Field of size 7 - sage: I = O.ideal_with_gens_over_base([x^2 + 1, x*(x^2+1)]) - sage: I.gens() - (x^2 + 1,) - sage: I.module() - Free module of degree 1 and rank 1 over Maximal order in Rational function field in x over Finite Field of size 7 - User basis matrix: - [x^2 + 1] - sage: V, from_V, to_V = K.vector_space(); V - Vector space of dimension 1 over Rational function field in x over Finite Field of size 7 - sage: I.module().is_submodule(V) - True - """ - return self._module - - def __add__(self, other): - """ - Add self and ``other``. - - EXAMPLES:: - - sage: K. = FunctionField(GF(7)); R. = K[] - sage: L. = K.extension(y^2 - x^3 - 1) - sage: O = L.equation_order() - sage: I = O.ideal(y); J = O.ideal(y+1) - sage: Z = I + J; Z - Ideal (y + 1, 6*y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 - sage: 1 in Z - True - sage: O.ideal(y^2) + O.ideal(y^3) == O.ideal(y^2,y^3) - True - """ - if not isinstance(other, FunctionFieldIdeal_module): - other = self.ring().ideal(other) - return FunctionFieldIdeal_module(self.ring(), self.module() + other.module()) - - def intersection(self, other): - """ - Return the intersection of the ideals self and ``other``. - - EXAMPLES:: - - sage: K. = FunctionField(GF(7)); R. = K[] - sage: L. = K.extension(y^2 - x^3 - 1) - sage: O = L.equation_order() - sage: I = O.ideal(y^3); J = O.ideal(y^2) - sage: Z = I.intersection(J); Z - Ideal (x^6 + 2*x^3 + 1, (6*x^3 + 6)*y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 - sage: y^2 in Z - False - sage: y^3 in Z - True - """ - if not isinstance(other, FunctionFieldIdeal_module): - other = self.ring().ideal(other) - if self.ring() != other.ring(): - raise ValueError("rings must be the same") - return FunctionFieldIdeal_module(self.ring(), self.module().intersection(other.module())) - - def __richcmp__(self, other, op): - """ - Compare self and ``other``. - - EXAMPLES:: - - sage: K. = FunctionField(GF(7)); R. = K[] - sage: L. = K.extension(y^2 - x^3 - 1) - sage: O = L.equation_order() - sage: I = O.ideal(y*(y+1)); J = O.ideal((y^2-2)*(y+1)) - sage: I+J == J+I # indirect test - True - sage: I == J - False - sage: I < J - True - sage: J < I - False - """ - if not isinstance(other, FunctionFieldIdeal_module): - other = self.ring().ideal(other) - if self.ring() != other.ring(): - raise ValueError("rings must be the same") - return richcmp(self.module(), other.module(), op) - - def __invert__(self): - """ - Return the inverse of this fractional ideal. - - EXAMPLES:: - - sage: K. = FunctionField(GF(7)); R. = K[] - sage: L. = K.extension(y^2 - x^3 - 1) - sage: O = L.equation_order() - sage: I = O.ideal(y) - sage: ~I - Ideal (6, (1/(x^3 + 1))*y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 - sage: I^(-1) - Ideal (6, (1/(x^3 + 1))*y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 - sage: ~I * I - Ideal (1, y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 - """ - if len(self.gens()) == 0: - raise ZeroDivisionError - - # NOTE: If I = (g0, ..., gn), then {x : x*I is in R} - # is the intersection over i of {x : x*gi is in R} - # Thus (I + J)^(-1) = I^(-1) intersect J^(-1). - - G = self.gens() - R = self.ring() - inv = R.ideal(~G[0]) - for g in G[1:]: - inv = inv.intersection(R.ideal(~g)) - return inv - -def ideal_with_gens(R, gens): - """ - Return fractional ideal in the order ``R`` with generators ``gens`` - over ``R``. - - EXAMPLES:: - - sage: K. = FunctionField(QQ); R. = K[] - sage: L. = K.extension(y^2 - x^3 - 1) - sage: O = L.equation_order() - sage: sage.rings.function_field.function_field_ideal.ideal_with_gens(O, [y]) - Ideal (x^3 + 1, -y) of Order in Function field in y defined by y^2 - x^3 - 1 - """ - K = R.fraction_field() - return ideal_with_gens_over_base(R, [b*K(g) for b in R.basis() for g in gens]) - -def ideal_with_gens_over_base(R, gens): - """ - Return fractional ideal in the order ``R`` with generators ``gens`` - over the maximal order of the base field. - - EXAMPLES:: - - sage: K. = FunctionField(QQ); R. = K[] - sage: L. = K.extension(y^2 - x^3 - 1) - sage: O = L.equation_order() - sage: sage.rings.function_field.function_field_ideal.ideal_with_gens_over_base(O, [x^3+1,-y]) - Ideal (x^3 + 1, -y) of Order in Function field in y defined by y^2 - x^3 - 1 - - TESTS:: - - sage: K. = FunctionField(QQ) - sage: O = K.maximal_order() - sage: I = O*x - sage: ~I - Ideal (1/x) of Maximal order in Rational function field in x over Rational Field - sage: ~I == O.ideal(1/x) - True - sage: O.ideal([x,1/x]) - Ideal (1/x) of Maximal order in Rational function field in x over Rational Field - sage: O.ideal([1/x,1/(x+1)]) - Ideal (1/(x^2 + x)) of Maximal order in Rational function field in x over Rational Field - """ - K = R.fraction_field() - V, from_V, to_V = K.vector_space() - - # We handle the case of a rational function field separately, - # since this is the base case and is used, e.g,. internally - # by the linear algebra Hermite form code. - from . import function_field_order - if isinstance(R, function_field_order.FunctionFieldOrder_rational): - from sage.modules import free_module_element - gens = free_module_element.vector(x.element() for x in gens) - d = gens.denominator() - gens *= d - v = R._ring.ideal(gens.list()).gens_reduced() - assert len(v) == 1 - basis = [to_V(v[0]/d)] - M = V.span_of_basis(basis, check=False, already_echelonized=True, base_ring=R) - else: - # General case - S = V.base_field().maximal_order() - M = V.span([to_V(b) for b in gens], base_ring=S) - - return FunctionFieldIdeal_module(R, M) diff --git a/src/sage/rings/function_field/function_field_order.py b/src/sage/rings/function_field/function_field_order.py deleted file mode 100644 index 3ca846ef3cc..00000000000 --- a/src/sage/rings/function_field/function_field_order.py +++ /dev/null @@ -1,490 +0,0 @@ -r""" -Orders of function fields - -AUTHORS: - -- William Stein (2010): initial version - -- Maarten Derickx (2011-09-14): fixed ideal_with_gens_over_base() for rational function fields - -- Julian Rueth (2011-09-14): added check in _element_constructor_ - -EXAMPLES: - -Maximal orders in rational function fields:: - - sage: K. = FunctionField(QQ) - sage: O = K.maximal_order() - sage: I = O.ideal(1/x); I - Ideal (1/x) of Maximal order in Rational function field in x over Rational Field - sage: 1/x in O - False - -Equation orders in extensions of rational function fields:: - - sage: K. = FunctionField(GF(3)); R. = K[] - sage: L. = K.extension(y^3-y-x) - sage: O = L.equation_order() - sage: 1/y in O - False - sage: x/y in O - True -""" -from __future__ import absolute_import -#***************************************************************************** -# Copyright (C) 2010 William Stein -# Copyright (C) 2011 Maarten Derickx -# Copyright (C) 2011 Julian Rueth -# -# Distributed under the terms of the GNU General Public License (GPL) -# as published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** - -from sage.rings.ring import IntegralDomain, PrincipalIdealDomain -from sage.rings.ideal import is_Ideal - -class FunctionFieldOrder(IntegralDomain): - """ - Base class for orders in function fields. - """ - def __init__(self, fraction_field): - """ - INPUT: - - - ``fraction_field`` -- the function field in which this is an order. - - EXAMPLES:: - - sage: R = FunctionField(QQ,'y').maximal_order() - sage: isinstance(R, sage.rings.function_field.function_field_order.FunctionFieldOrder) - True - """ - IntegralDomain.__init__(self, self) - self._fraction_field = fraction_field - - def _repr_(self): - """ - EXAMPLES:: - - sage: FunctionField(QQ,'y').maximal_order()._repr_() - 'Maximal order in Rational function field in y over Rational Field' - """ - return "Order in %s"%self.fraction_field() - - def is_finite(self): - """ - Returns False since orders are never finite. - - EXAMPLES:: - - sage: FunctionField(QQ,'y').maximal_order().is_finite() - False - """ - return False - - def is_field(self, proof=True): - """ - Returns False since orders are never fields. - - EXAMPLES:: - - sage: FunctionField(QQ,'y').maximal_order().is_field() - False - """ - return False - - def is_noetherian(self): - """ - Returns True since orders in function fields are noetherian. - - EXAMPLES:: - - sage: FunctionField(QQ,'y').maximal_order().is_noetherian() - True - """ - return True - - def fraction_field(self): - """ - Returns the function field in which this is an order. - - EXAMPLES:: - - sage: FunctionField(QQ,'y').maximal_order().fraction_field() - Rational function field in y over Rational Field - """ - return self._fraction_field - - function_field = fraction_field - - def ideal_with_gens_over_base(self, gens): - """ - Returns the fractional ideal with basis ``gens`` over the - maximal order of the base field. That this is really an ideal - is not checked. - - INPUT: - - - ``gens`` -- list of elements that are a basis for the - ideal over the maximal order of the base field - - EXAMPLES: - - We construct an ideal in a rational function field:: - - sage: K. = FunctionField(QQ) - sage: O = K.maximal_order() - sage: I = O.ideal_with_gens_over_base([y]); I - Ideal (y) of Maximal order in Rational function field in y over Rational Field - sage: I*I - Ideal (y^2) of Maximal order in Rational function field in y over Rational Field - - We construct some ideals in a nontrivial function field:: - - sage: K. = FunctionField(GF(7)); R. = K[] - sage: L. = K.extension(y^2 - x^3 - 1) - sage: O = L.equation_order(); O - Order in Function field in y defined by y^2 + 6*x^3 + 6 - sage: I = O.ideal_with_gens_over_base([1, y]); I - Ideal (1, y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 - sage: I.module() - Free module of degree 2 and rank 2 over Maximal order in Rational function field in x over Finite Field of size 7 - Echelon basis matrix: - [1 0] - [0 1] - - There is no check if the resulting object is really an ideal:: - - sage: K. = FunctionField(GF(7)); R. = K[] - sage: L. = K.extension(y^2 - x^3 - 1) - sage: O = L.equation_order() - sage: I = O.ideal_with_gens_over_base([y]); I - Ideal (y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 - sage: y in I - True - sage: y^2 in I - False - """ - from .function_field_ideal import ideal_with_gens_over_base - return ideal_with_gens_over_base(self, [self(a) for a in gens]) - - def ideal(self, *gens): - """ - Returns the fractional ideal generated by the elements in ``gens``. - - INPUT: - - - ``gens`` -- a list of generators or an ideal in a ring which - coerces to this order. - - EXAMPLES:: - - sage: K. = FunctionField(QQ) - sage: O = K.maximal_order() - sage: O.ideal(y) - Ideal (y) of Maximal order in Rational function field in y over Rational Field - sage: O.ideal([y,1/y]) == O.ideal(y,1/y) # multiple generators may be given as a list - True - - A fractional ideal of a nontrivial extension:: - - sage: K. = FunctionField(GF(7)); R. = K[] - sage: O = K.maximal_order() - sage: I = O.ideal(x^2-4) - sage: L. = K.extension(y^2 - x^3 - 1) - sage: S = L.equation_order() - sage: S.ideal(1/y) - Ideal (1, (6/(x^3 + 1))*y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 - sage: I2 = S.ideal(x^2-4); I2 - Ideal (x^2 + 3, (x^2 + 3)*y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 - sage: I2 == S.ideal(I) - True - """ - if len(gens) == 1: - gens = gens[0] - if not isinstance(gens, (list, tuple)): - if is_Ideal(gens): - gens = gens.gens() - else: - gens = [gens] - from .function_field_ideal import ideal_with_gens - return ideal_with_gens(self, gens) - -class FunctionFieldOrder_basis(FunctionFieldOrder): - """ - An order given by a basis over the maximal order of the base - field. - """ - def __init__(self, basis, check=True): - """ - EXAMPLES:: - - sage: K. = FunctionField(GF(7)); R. = K[] - sage: L. = K.extension(y^4 + x*y + 4*x + 1) - sage: O = L.equation_order(); O - Order in Function field in y defined by y^4 + x*y + 4*x + 1 - sage: type(O) - - - The basis only defines an order if the module it generates is closed under multiplication - and contains the identity element (only checked when ``check`` is True):: - - sage: K. = FunctionField(QQ) - sage: R. = K[] - sage: L. = K.extension(y^5 - (x^3 + 2*x*y + 1/x)) - sage: y.is_integral() - False - sage: L.order(y) - Traceback (most recent call last): - ... - ValueError: The module generated by basis [1, y, y^2, y^3, y^4] must be closed under multiplication - - The basis also has to be linearly independent and of the same rank as the degree of the function field of its elements (only checked when ``check`` is True):: - - sage: L.order(L(x)) - Traceback (most recent call last): - ... - ValueError: Basis [1, x, x^2, x^3, x^4] is not linearly independent - sage: sage.rings.function_field.function_field_order.FunctionFieldOrder_basis([y,y,y^3,y^4,y^5]) - Traceback (most recent call last): - ... - ValueError: Basis [y, y, y^3, y^4, 2*x*y + (x^4 + 1)/x] is not linearly independent - """ - if len(basis) == 0: - raise ValueError("basis must have positive length") - - fraction_field = basis[0].parent() - if len(basis) != fraction_field.degree(): - raise ValueError("length of basis must equal degree of field") - - FunctionFieldOrder.__init__(self, fraction_field) - - self._basis = tuple(basis) - V, fr, to = fraction_field.vector_space() - R = fraction_field.base_field().maximal_order() - self._module = V.span([to(b) for b in basis], base_ring=R) - self._ring = fraction_field.polynomial_ring() - self._populate_coercion_lists_(coerce_list=[self._ring]) - if check: - if self._module.rank() != fraction_field.degree(): - raise ValueError("Basis %s is not linearly independent"%(basis)) - if not to(fraction_field(1)) in self._module: - raise ValueError("The identity element must be in the module spanned by basis %s"%(basis)) - if not all(to(a*b) in self._module for a in basis for b in basis): - raise ValueError("The module generated by basis %s must be closed under multiplication"%(basis)) - IntegralDomain.__init__(self, self, names = fraction_field.variable_names(), normalize = False) - - def _element_constructor_(self, f, check=True): - """ - Make ``f`` into an element of this order. - - INPUT: - - - ``f`` -- the element - - ``check`` -- check if the element is in the order - - EXAMPLES:: - - sage: K. = FunctionField(QQ) - sage: K.maximal_order()._element_constructor_(x) - x - """ - fraction_field=self.fraction_field() - - if f.parent() is fraction_field: - f = f.element() - f = self._ring(f) - if check: - V, fr, to = fraction_field.vector_space() - f_vector = to(fraction_field(f)) - if not f_vector in self._module: - raise TypeError("%r is not an element of %r"%(f_vector,self)) - return fraction_field._element_class(self, f) - - def fraction_field(self): - """ - Returns the function field in which this is an order. - - EXAMPLES:: - - sage: K. = FunctionField(GF(7)); R. = K[] - sage: L. = K.extension(y^4 + x*y + 4*x + 1) - sage: O = L.equation_order() - sage: O.fraction_field() - Function field in y defined by y^4 + x*y + 4*x + 1 - """ - return self._fraction_field - - def polynomial(self): - """ - Returns the defining polynomial of the function field of which this is an order. - - EXAMPLES:: - - sage: K. = FunctionField(GF(7)); R. = K[] - sage: L. = K.extension(y^4 + x*y + 4*x + 1) - sage: O = L.equation_order() - sage: O.polynomial() - y^4 + x*y + 4*x + 1 - """ - return self._fraction_field.polynomial() - - def basis(self): - """ - Returns a basis of self over the maximal order of the base field. - - EXAMPLES:: - - sage: K. = FunctionField(GF(7)); R. = K[] - sage: L. = K.extension(y^4 + x*y + 4*x + 1) - sage: O = L.equation_order() - sage: O.basis() - (1, y, y^2, y^3) - """ - return self._basis - - def free_module(self): - """ - Returns the free module formed by the basis over the maximal order of the base field. - - EXAMPLES:: - - sage: K. = FunctionField(GF(7)); R. = K[] - sage: L. = K.extension(y^4 + x*y + 4*x + 1) - sage: O = L.equation_order() - sage: O.free_module() - Free module of degree 4 and rank 4 over Maximal order in Rational function field in x over Finite Field of size 7 - Echelon basis matrix: - [1 0 0 0] - [0 1 0 0] - [0 0 1 0] - [0 0 0 1] - """ - return self._module - -class FunctionFieldOrder_rational(PrincipalIdealDomain, FunctionFieldOrder): - """ - The maximal order in a rational function field. - """ - def __init__(self, function_field): - """ - EXAMPLES:: - - sage: K. = FunctionField(GF(19)); K - Rational function field in t over Finite Field of size 19 - sage: R = K.maximal_order(); R - Maximal order in Rational function field in t over Finite Field of size 19 - sage: type(R) - - """ - FunctionFieldOrder.__init__(self, function_field) - PrincipalIdealDomain.__init__(self, self, names = function_field.variable_names(), normalize = False) - self._ring = function_field._ring - self._populate_coercion_lists_(coerce_list=[self._ring]) - self._gen = self(self._ring.gen()) - self._basis = (self(1),) - - def basis(self): - """ - Returns the basis (=1) for this order as a module over the polynomial ring. - - EXAMPLES:: - - sage: K. = FunctionField(GF(19)) - sage: O = K.maximal_order() - sage: O.basis() - (1,) - sage: parent(O.basis()[0]) - Maximal order in Rational function field in t over Finite Field of size 19 - """ - return self._basis - - def ideal(self, *gens): - """ - Returns the fractional ideal generated by ``gens``. - - EXAMPLES:: - - sage: K. = FunctionField(QQ) - sage: O = K.maximal_order() - sage: O.ideal(x) - Ideal (x) of Maximal order in Rational function field in x over Rational Field - sage: O.ideal([x,1/x]) == O.ideal(x,1/x) # multiple generators may be given as a list - True - sage: O.ideal(x^3+1,x^3+6) - Ideal (1) of Maximal order in Rational function field in x over Rational Field - sage: I = O.ideal((x^2+1)*(x^3+1),(x^3+6)*(x^2+1)); I - Ideal (x^2 + 1) of Maximal order in Rational function field in x over Rational Field - sage: O.ideal(I) - Ideal (x^2 + 1) of Maximal order in Rational function field in x over Rational Field - """ - if len(gens) == 1: - gens = gens[0] - if not isinstance(gens, (list, tuple)): - if is_Ideal(gens): - gens = gens.gens() - else: - gens = (gens,) - from .function_field_ideal import ideal_with_gens - return ideal_with_gens(self, gens) - - def _repr_(self): - """ - EXAMPLES:: - - sage: FunctionField(QQ,'y').maximal_order()._repr_() - 'Maximal order in Rational function field in y over Rational Field' - """ - return "Maximal order in %s"%self.fraction_field() - - def gen(self, n=0): - """ - Returns the ``n``-th generator of self. Since there is only one generator ``n`` must be 0. - - EXAMPLES:: - - sage: O = FunctionField(QQ,'y').maximal_order() - sage: O.gen() - y - sage: O.gen(1) - Traceback (most recent call last): - ... - IndexError: Only one generator. - """ - if n != 0: raise IndexError("Only one generator.") - return self._gen - - def ngens(self): - """ - Returns 1, the number of generators of self. - - EXAMPLES:: - - sage: FunctionField(QQ,'y').maximal_order().ngens() - 1 - """ - return 1 - - def _element_constructor_(self, f): - """ - Make ``f`` into an element of this order. - - EXAMPLES:: - - sage: K. = FunctionField(QQ) - sage: O = K.maximal_order() - sage: O._element_constructor_(y) - y - sage: O._element_constructor_(1/y) - Traceback (most recent call last): - ... - TypeError: 1/y is not an element of Maximal order in Rational function field in y over Rational Field - """ - if f.parent() is self.fraction_field(): - if not f.denominator() in self.fraction_field().constant_base_field(): - raise TypeError("%r is not an element of %r"%(f,self)) - f = f.element() - from .function_field_element import FunctionFieldElement_rational - return FunctionFieldElement_rational(self, self._ring(f)) diff --git a/src/sage/rings/function_field/ideal.py b/src/sage/rings/function_field/ideal.py new file mode 100644 index 00000000000..c9eea7ea37a --- /dev/null +++ b/src/sage/rings/function_field/ideal.py @@ -0,0 +1,2835 @@ +r""" +Ideals of function fields + +Ideals of an order of a function field include all fractional ideals of the order. +Sage provides basic arithmetic with fractional ideals. + +The fractional ideals of the maximal order of a global function field forms a multiplicative +monoid. Sage allows advanced arithmetic with the fractional ideals. For example, an ideal +of the maximal order can be factored into a product of prime ideals. + +EXAMPLES: + +Ideals in the maximal order of a rational function field:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal(x^3 + 1); I + Ideal (x^3 + 1) of Maximal order of Rational function field in x over Rational Field + sage: I^2 + Ideal (x^6 + 2*x^3 + 1) of Maximal order of Rational function field in x over Rational Field + sage: ~I + Ideal (1/(x^3 + 1)) of Maximal order of Rational function field in x over Rational Field + sage: ~I * I + Ideal (1) of Maximal order of Rational function field in x over Rational Field + +Ideals in the equation order of an extension of a rational function field:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(y); I + Ideal (x^3 + 1, -y) of Order in Function field in y defined by y^2 - x^3 - 1 + sage: I^2 + Ideal (x^3 + 1, (-x^3 - 1)*y) of Order in Function field in y defined by y^2 - x^3 - 1 + +Ideals in the maximal order of a global function field:: + + sage: K. = FunctionField(GF(2)); R. = K[] + sage: L. = K.extension(y^2 - x^3*y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: I^2 + Ideal (x) of Maximal order of Function field in y defined by y^2 + x^3*y + x + sage: ~I + Ideal (1, 1/x*y) of Maximal order of Function field in y defined by y^2 + x^3*y + x + sage: ~I * I + Ideal (1) of Maximal order of Function field in y defined by y^2 + x^3*y + x + + sage: J = O.ideal(x+y) * I + sage: J.factor() + (Ideal (x, y) of Maximal order of Function field in y defined by y^2 + x^3*y + x)^2 * + (Ideal (x^3 + x + 1, y + x) of Maximal order of Function field in y defined by y^2 + x^3*y + x) + +Ideals in the maximal infinite order of a global function field:: + + sage: K. = FunctionField(GF(3^2)); R. = K[] + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/y) + sage: I + I == I + True + sage: I^2 + Ideal (1/x^3,1/x^4*y) of Maximal infinite order of Function field in y defined by y^3 + y^2 + 2*x^4 + sage: ~I + Ideal (x,y) of Maximal infinite order of Function field in y defined by y^3 + y^2 + 2*x^4 + sage: ~I * I + Ideal (1) of Maximal infinite order of Function field in y defined by y^3 + y^2 + 2*x^4 + sage: I.factor() + (Ideal (1/x,1/x^3*y^2) of Maximal infinite order of Function field in y defined by y^3 + y^2 + 2*x^4)^4 + +AUTHORS: + +- William Stein (2010): initial version + +- Maarten Derickx (2011-09-14): fixed ideal_with_gens_over_base() + +- Kwankyu Lee (2017-04-30): added ideals for global function fields + +""" +from __future__ import absolute_import +#***************************************************************************** +# Copyright (C) 2010 William Stein +# Copyright (C) 2011 Maarten Derickx +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +import operator +import itertools + +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_import import lazy_import + +from sage.structure.parent import Parent +from sage.structure.element import Element +from sage.structure.richcmp import richcmp +from sage.structure.factorization import Factorization +from sage.structure.unique_representation import UniqueRepresentation + +from sage.modules.free_module_element import vector + +from sage.categories.monoids import Monoids + +from sage.rings.infinity import infinity +from sage.rings.ideal import Ideal_generic + +lazy_import('sage.matrix.constructor', 'matrix') + +class FunctionFieldIdeal(Element): + """ + Fractional ideals of function fields. + + INPUT: + + - ``ring`` -- ring of the ideal + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)) + sage: O = K.equation_order() + sage: O.ideal(x^3+1) + Ideal (x^3 + 1) of Maximal order of Rational function field in x over Finite Field of size 7 + """ + def __init__(self, ring): + """ + Initialize. + + TESTS:: + + sage: K. = FunctionField(GF(7)) + sage: O = K.equation_order() + sage: I = O.ideal(x^3+1) + sage: TestSuite(I).run() + """ + Element.__init__(self, ring.ideal_monoid()) + self._ring = ring + + def _div_(self, other): + """ + Return the ideal divided by the ``other`` ideal. + + INPUT: + + - ``other`` -- ideal + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)) + sage: O = K.equation_order() + sage: I = O.ideal(x^3+1) + sage: I / I + Ideal (1) of Maximal order of Rational function field in x + over Finite Field of size 7 + """ + return self * ~other + + def gens_reduced(self): + r""" + Return reduced generators. This just returns the generators for now. + + This method is provided so that ideals in funtion fields have the method + :meth:`gens_reduced()`, just like ideals of number fields. Sage linear algebra + machinery sometimes requires this. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)) + sage: O = K.equation_order() + sage: I = O.ideal(x,x^2,x^2+x) + sage: I.gens_reduced() + (x,) + + """ + return self.gens() + + def ring(self): + """ + Return the ring to which this ideal belongs. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)) + sage: O = K.equation_order() + sage: I = O.ideal(x,x^2,x^2+x) + sage: I.ring() + Maximal order of Rational function field in x over Finite Field of size 7 + """ + return self._ring + + def base_ring(self): + r""" + Return the base ring of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(x^2 + 1) + sage: I.base_ring() + Order in Function field in y defined by y^2 - x^3 - 1 + """ + return self.ring() + +class FunctionFieldIdeal_rational(FunctionFieldIdeal): + """ + Fractional ideals of the maximal order of a rational function field. + + INPUT: + + - ``ring`` -- the maximal order of the rational function field. + + - ``gen`` -- generator of the ideal, an element of the function field. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal(1/(x^2+x)); I + Ideal (1/(x^2 + x)) of Maximal order of Rational function field in x over Rational Field + """ + def __init__(self, ring, gen): + """ + Initialize. + + TESTS:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal(1/(x^2+x)) + sage: TestSuite(I).run() + """ + FunctionFieldIdeal.__init__(self, ring) + self._gen = gen + + def __hash__(self): + """ + Return the hash computed from the data. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal(1/(x^2+x)) + sage: d = { I: 1, I^2: 2 } + """ + return hash( (self._ring, self._gen) ) + + def _repr_(self): + """ + Return the string representation of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal(x,1/(x+1)); I + Ideal (1/(x + 1)) of Maximal order of Rational function field in x over Rational Field + """ + return "Ideal (%s) of %s"%(self._gen, self._ring) + + def __contains__(self, element): + """ + Test if ``element`` is in this ideal. + + INPUT: + + - ``element`` -- element of the function field + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal(1/(x+1)) + sage: x in I + True + """ + return (element / self._gen) in self._ring + + def _richcmp_(self, other, op): + """ + Compare the element with the other element with respect + to the comparison operator. + + INPUT: + + - ``other`` -- element + + - ``op`` -- comparison operator + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal(x,x^2+1) + sage: J = O.ideal(x^2+x+1,x) + sage: I == J + True + sage: I = O.ideal(x) + sage: J = O.ideal(x+1) + sage: I < J + True + """ + return richcmp(self._gen, other._gen, op) + + def _add_(self, other): + """ + Add this ideal with the ``other`` ideal. + + INPUT: + + - ``other`` -- ideal + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal(x,x^2+1) + sage: J = O.ideal(x^2+x+1,x) + sage: I + J == J + I + True + """ + return self._ring.ideal([self._gen, other._gen]) + + def _mul_(self, other): + """ + Multiply this ideal with the ``other`` ideal. + + INPUT: + + - ``other`` -- ideal + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal(x,x^2+x) + sage: J = O.ideal(x^2,x) + sage: I * J == J * I + True + """ + return self._ring.ideal([self._gen * other._gen]) + + def _acted_upon_(self, other, on_left): + """ + Multiply ``other`` with this ideal on the right. + + INPUT: + + - ``other`` -- ideal + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal(x^3+x^2) + sage: 2 * I + Ideal (x^3 + x^2) of Maximal order of Rational function field in x over Rational Field + sage: x * I + Ideal (x^4 + x^3) of Maximal order of Rational function field in x over Rational Field + """ + return self._ring.ideal([other * self._gen]) + + def __invert__(self): + """ + Return the ideal inverse of this fractional ideal. + + EXAMPLES:: + + sage: F. = FunctionField(QQ) + sage: O = F.maximal_order() + sage: I = O.ideal(x/(x^2+1)) + sage: ~I + Ideal ((x^2 + 1)/x) of Maximal order of Rational function field + in x over Rational Field + """ + return self._ring.ideal([~(self._gen)]) + + def denominator(self): + """ + Return the denominator of this fractional ideal. + + EXAMPLES:: + + sage: F. = FunctionField(QQ) + sage: O = F.maximal_order() + sage: I = O.ideal(x/(x^2+1)) + sage: I.denominator() + x^2 + 1 + """ + return self._gen.denominator() + + def is_prime(self): + """ + Return ``True`` if this is a prime ideal. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal(x^3+x^2) + sage: [f.is_prime() for f,m in I.factor()] + [True, True] + """ + return self._gen.denominator() == 1 and self._gen.numerator().is_prime() + + @cached_method + def module(self): + """ + Return the module, that is the ideal viewed as a module over the ring. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal(x^3+x^2) + sage: I.module() + Free module of degree 1 and rank 1 over Maximal order of Rational + function field in x over Rational Field + Echelon basis matrix: + [x^3 + x^2] + sage: J = 0*I + sage: J.module() + Free module of degree 1 and rank 0 over Maximal order of Rational + function field in x over Rational Field + Echelon basis matrix: + [] + """ + V, fr, to = self.ring().fraction_field().vector_space() + return V.span([to(g) for g in self.gens()], base_ring=self.ring()) + + def gen(self): + """ + Return the unique generator of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(4)) + sage: O = K.maximal_order() + sage: I = O.ideal(x^2+x) + sage: I.gen() + x^2 + x + """ + return self._gen + + def gens(self): + """ + Return the tuple of the unique generator of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(4)) + sage: O = K.maximal_order() + sage: I = O.ideal(x^2+x) + sage: I.gens() + (x^2 + x,) + """ + return (self._gen,) + + def gens_over_base(self): + """ + Return the generator of this ideal as a rank one module over the maximal + order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(4)) + sage: O = K.maximal_order() + sage: I = O.ideal(x^2+x) + sage: I.gens_over_base() + (x^2 + x,) + """ + return (self._gen,) + + def factor(self): + """ + Return the factorization of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(4)) + sage: O = K.maximal_order() + sage: I = O.ideal(x^3*(x+1)^2) + sage: I.factor() + (Ideal (x) of Maximal order of Rational function field in x + over Finite Field in z2 of size 2^2)^3 * + (Ideal (x + 1) of Maximal order of Rational function field in x + over Finite Field in z2 of size 2^2)^2 + """ + factors = self._factor() + return Factorization(factors, cr=True) + + def _factor(self): + """ + Return the list of prime and multiplicity pairs of the + factorization of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(4)) + sage: O = K.maximal_order() + sage: I = O.ideal(x^3*(x+1)^2) + sage: I._factor() + [(Ideal (x) of Maximal order of Rational function field in x + over Finite Field in z2 of size 2^2, 3), + (Ideal (x + 1) of Maximal order of Rational function field in x + over Finite Field in z2 of size 2^2, 2)] + """ + factors = [] + for f,m in self._gen.factor(): + factors.append( (self.ring().ideal(f), m) ) + return factors + +class FunctionFieldIdeal_module(FunctionFieldIdeal, Ideal_generic): + """ + A fractional ideal specified by a finitely generated module over + the integers of the base field. + + INPUT: + + - ``ring`` -- an order in a function field + + - ``module`` -- a module of the order + + EXAMPLES: + + An ideal in an extension of a rational function field:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(y) + sage: I + Ideal (x^3 + 1, -y) of Order in Function field in y defined by y^2 - x^3 - 1 + sage: I^2 + Ideal (x^3 + 1, (-x^3 - 1)*y) of Order in Function field in y defined by y^2 - x^3 - 1 + """ + def __init__(self, ring, module): + """ + Initialize. + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(y) + sage: TestSuite(I).run() + """ + FunctionFieldIdeal.__init__(self, ring) + + self._module = module + self._structure = ring.fraction_field().vector_space() + + V, from_V, to_V = self._structure + self._gens = tuple([from_V(a) for a in module.basis()]) + + # module generators are still ideal generators + Ideal_generic.__init__(self, ring, self._gens, coerce=False) + + def __contains__(self, x): + """ + Return ``True`` if ``x`` is in this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(y); I + Ideal (x^3 + 1, -y) of Order in Function field in y defined by y^2 - x^3 - 1 + sage: y in I + True + sage: y/x in I + False + sage: y^2 - 2 in I + False + """ + return self._structure[2](x) in self._module + + def __hash__(self): + """ + Return the hash of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(y) + sage: d = {I: 1} # indirect doctest + """ + return hash((self._ring,self._module)) + + def _richcmp_(self, other, op): + """ + Compare this ideal with the ``other`` ideal with respect to ``op``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(x, y); J = O.ideal(y^2 - 2) + sage: I + J == J + I # indirect test + True + sage: I + I == I # indirect doctest + True + sage: I == J + False + sage: I < J + True + sage: J < I + False + """ + return richcmp(self.module().basis(), other.module().basis(), op) + + def module(self): + """ + Return the module over the maximal order of the base field that + underlies this ideal. + + The formation of the module is compatible with the vector + space corresponding to the function field. + + OUTPUT: + + - a module over the maximal order of the base field of the ideal + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order(); O + Order in Function field in y defined by y^2 - x^3 - 1 + sage: I = O.ideal(x^2 + 1) + sage: I.gens() + (x^2 + 1, (x^2 + 1)*y) + sage: I.module() + Free module of degree 2 and rank 2 over Maximal order of Rational function field in x over Rational Field + Echelon basis matrix: + [x^2 + 1 0] + [ 0 x^2 + 1] + sage: V, from_V, to_V = L.vector_space(); V + Vector space of dimension 2 over Rational function field in x over Rational Field + sage: I.module().is_submodule(V) + True + """ + return self._module + + def __repr__(self): + """ + Return a string representation of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: O.ideal(x^2 + 1) + Ideal (x^2 + 1, (x^2 + 1)*y) of Order in Function field in y defined by y^2 - x^3 - 1 + """ + return "Ideal (%s) of %s"%(', '.join([repr(g) for g in self.gens()]), self.ring()) + + def gens(self): + """ + Return a set of generators of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(x^2 + 1) + sage: I.gens() + (x^2 + 1, (x^2 + 1)*y) + """ + return self._gens + + def gen(self, i): + """ + Return the ``i``-th generator in the current basis of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(x^2 + 1) + sage: I.gen(1) + (x^2 + 1)*y + """ + return self._gens[i] + + def ngens(self): + """ + Return the number of generators in the basis. + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(x^2 + 1) + sage: I.ngens() + 2 + """ + return len(self._gens) + + def _add_(self, other): + """ + Add this ideal with the ``other`` ideal. + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(y) + sage: J = O.ideal(x+y) + sage: I + J + Ideal ((-x^2 + x)*y + 1, -y) of Order in Function field in y defined by y^2 - x^3 - 1 + """ + return self.ring().ideal(self.gens() + other.gens()) + + def _mul_(self, other): + """ + Multiply this ideal with the ``other`` ideal. + + Example:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(y) + sage: J = O.ideal(x+y) + sage: I * J + Ideal ((-x^5 + x^4 - x^2 + x)*y + x^3 + 1, (x^3 - x^2 + 1)*y) of Order in Function field in y defined by y^2 - x^3 - 1 + """ + return self.ring().ideal([x*y for x in self.gens() for y in other.gens()]) + + def _acted_upon_(self, other, on_left): + """ + Multiply this ideal on the right with ``other``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(y) + sage: x * I + Ideal (x^4 + x, -x*y) of Order in Function field in y defined by y^2 - x^3 - 1 + """ + return self.ring().ideal([other * x for x in self.gens()]) + + def intersection(self, other): + """ + Return the intersection of this ideal and ``other``. + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(y^3); J = O.ideal(y^2) + sage: Z = I.intersection(J); Z + Ideal (x^6 + 2*x^3 + 1, (-x^3 - 1)*y) of Order in Function field in y defined by y^2 - x^3 - 1 + sage: y^2 in Z + False + sage: y^3 in Z + True + """ + if not isinstance(other, FunctionFieldIdeal): + try: + if self.ring().has_coerce_map_from(other): + return self + except (TypeError,ArithmeticError,ValueError): + pass + other = self.ring().ideal(other) + + basis = self.module().intersection(other.module()).basis() + + V, from_V, to_V = self._structure + return self.ring().ideal_with_gens_over_base([from_V(a) for a in basis]) + + def __invert__(self): + """ + Return the inverse of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(y) + sage: ~I + Ideal (-1, (1/(x^3 + 1))*y) of Order in Function field in y defined by y^2 - x^3 - 1 + sage: I^-1 + Ideal (-1, (1/(x^3 + 1))*y) of Order in Function field in y defined by y^2 - x^3 - 1 + sage: ~I * I + Ideal (1, y) of Order in Function field in y defined by y^2 - x^3 - 1 + """ + if len(self.gens()) == 0: + raise ZeroDivisionError + + # NOTE: If I = (g0, ..., gn), then {x : x*I is in R} + # is the intersection over i of {x : x*gi is in R} + # Thus (I + J)^(-1) = I^(-1) intersect J^(-1). + + G = self.gens() + R = self.ring() + inv = R.ideal(~G[0]) + for g in G[1:]: + inv = inv.intersection(R.ideal(~g)) + return inv + +class FunctionFieldIdeal_global(FunctionFieldIdeal): + """ + Fractional ideals of canonical function fields + + INPUT: + + - ``ring`` -- order in a function field + + - ``hnf`` -- matrix in hermite normal form + + - ``denominator`` -- denominator + + The rows of ``hnf`` is a basis of the ideal, which itself is + ``denominator`` times the fractional ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); R. = K[] + sage: L. = K.extension(y^2 - x^3*y - x) + sage: O = L.maximal_order() + sage: O.ideal(y) + Ideal (x, y) of Maximal order of Function field in y defined by y^2 + x^3*y + x + """ + def __init__(self, ring, hnf, denominator=1): + """ + Initialize. + + TESTS:: + + sage: K. = FunctionField(GF(2)); R. = K[] + sage: L. = K.extension(y^2 - x^3*y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: TestSuite(I).run() + """ + FunctionFieldIdeal.__init__(self, ring) + + # the denominator and the entries of the hnf are + # univariate polynomials. + self._hnf = hnf + self._denominator = denominator + + # for prime ideals + self._relative_degree = None + self._ramification_index = None + self._prime_below = None + self._beta = None + + # beta in matrix form for fast multiplicaton + self._beta_matrix = None + + # (p, q) with irreducibl polynomial p and q an element of O in vector + # form, together generating the prime ideal. This data is obtained by + # Kummer's theorem when this prime ideal is constructed. This is used + # for fast multiplication with other ideal. + self._kummer_form = None + + def __bool__(self): + """ + Test if this ideal is zero. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); R. = K[] + sage: L. = K.extension(y^2 - x^3*y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(y); I + Ideal (x, y) of Maximal order of Function field in y defined by y^2 + x^3*y + x + sage: I.is_zero() + False + sage: J = 0*I; J + Zero ideal of Maximal order of Function field in y defined by y^2 + x^3*y + x + sage: J.is_zero() + True + + sage: K.=FunctionField(GF(2)); _.=K[] + sage: L.=K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y); I + Ideal (x^2 + 1, y) of Maximal order of Function field in y + defined by y^2 + y + (x^2 + 1)/x + sage: I.is_zero() + False + sage: J = 0*I; J + Zero ideal of Maximal order of Function field in y defined by y^2 + y + (x^2 + 1)/x + sage: J.is_zero() + True + """ + return self._hnf.nrows() != 0 + + __nonzero__ = __bool__ + + def __hash__(self): + """ + Return the hash of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(1/y) + sage: { I: 2 }[I] == 2 + True + + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(1/y) + sage: { I: 2 }[I] == 2 + True + """ + return hash((self._ring, self._hnf, self._denominator)) + + def __contains__(self, x): + """ + Return ``True`` if ``x`` is in this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); _. = K[] + sage: L. = K.extension(Y^2 - x^3 - 1) + sage: O = L.maximal_order() + sage: I = O.ideal([y]); I + Ideal (x^3 + 1, y) of Maximal order of Function field in y + defined by y^2 + 6*x^3 + 6 + sage: x * y in I + True + sage: y / x in I + False + sage: y^2 - 2 in I + False + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal([y]); I + Ideal (x^2 + 1, y) of Maximal order of Function field in y + defined by y^2 + y + (x^2 + 1)/x + sage: x * y in I + True + sage: y / x in I + False + sage: y^2 - 2 in I + False + """ + vec = self.ring().coordinate_vector(self._denominator * x) + v = [] + for e in vec: + if e.denominator() != 1: + return False + v.append(e.numerator()) + vec = vector(v) + return vec in self._hnf.image() + + def __invert__(self): + """ + Return the inverse fractional ideal of this ideal. + + EXAMPLES:: + sage: K. = FunctionField(GF(7)); _. = K[] + sage: L. = K.extension(Y^2 - x^3 - 1) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: ~I + Ideal (1, (1/(x^3 + 1))*y) of Maximal order of Function field in y defined by y^2 + 6*x^3 + 6 + sage: I^(-1) + Ideal (1, (1/(x^3 + 1))*y) of Maximal order of Function field in y defined by y^2 + 6*x^3 + 6 + sage: ~I * I + Ideal (1) of Maximal order of Function field in y defined by y^2 + 6*x^3 + 6 + + sage: K. = FunctionField(GF(2)); _.=K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: ~I + Ideal (x, (x/(x^2 + 1))*y + x/(x^2 + 1)) of Maximal order + of Function field in y defined by y^2 + y + (x^2 + 1)/x + sage: I^(-1) + Ideal (x, (x/(x^2 + 1))*y + x/(x^2 + 1)) of Maximal order + of Function field in y defined by y^2 + y + (x^2 + 1)/x + sage: ~I * I + Ideal (1) of Maximal order of Function field in y defined by y^2 + y + (x^2 + 1)/x + """ + R = self.ring() + T = R._codifferent_matrix() + I = self * R.codifferent() + J = I._denominator * (I._hnf * T).inverse() + return R._ideal_from_vectors(J.columns()) + + def _richcmp_(self, other, op): + """ + Compare this ideal with the other ideal with respect to ``op``. + + INPUT: + + - ``other`` -- ideal + + - ``op`` -- comparison operator + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(1/y) + sage: I == I + I + True + sage: I == I * I + False + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(1/y) + sage: I == I + I + True + sage: I == I * I + False + sage: I < I * I + True + sage: I > I * I + False + """ + return richcmp((self._denominator, self._hnf), (other._denominator, other._hnf), op) + + def _repr_(self): + """ + Return string representation. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(y); I + Ideal (x, y) of Maximal order of Function field in y defined by y^2 + x^3*y + x + + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y); I + Ideal (x^2 + 1, y) of Maximal order of Function field in y + defined by y^2 + y + (x^2 + 1)/x + """ + if self.is_zero(): + return "Zero ideal of %s" % (self._ring,) + + s = ', '.join([repr(g) for g in self.gens_two()]) + return "Ideal (%s) of %s"%(s, self._ring) + + def _add_(self, other): + """ + Add with other ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: J = O.ideal(x+y) + sage: I + J + Ideal (x, y) of Maximal order of Function field in y defined by y^2 + x^3*y + x + + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: J = O.ideal(x+y) + sage: I + J + Ideal (1, y) of Maximal order of Function field in y defined by y^2 + y + (x^2 + 1)/x + """ + ds = self._denominator + do = other._denominator + vecs1 = [do * r for r in self._hnf] + vecs2 = [ds * r for r in other._hnf] + return self._ring._ideal_from_vectors_and_denominator(vecs1 + vecs2, ds * do) + + def _mul_(self, other): + """ + Multiply with other ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: J = O.ideal(x+y) + sage: I * J + Ideal (x^4 + x^2 + x, x*y + x^4 + x) of Maximal order + of Function field in y defined by y^2 + x^3*y + x + + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: J = O.ideal(x+y) + sage: I * J + Ideal ((x^4 + x^3 + x + 1)/x, (x + 1)*y + (x^2 + 1)/x) of Maximal order + of Function field in y defined by y^2 + y + (x^2 + 1)/x + """ + O = self._ring + mul = O._mul_vecs + + if self._kummer_form is not None: + p, q = self._kummer_form + vecs = list(p * other._hnf) + [mul(q, v) for v in other._hnf] + elif other._kummer_form is not None: + p, q = other._kummer_form + vecs = list(p * self._hnf) + [mul(q, v) for v in self._hnf] + else: + vecs = [mul(r1,r2) for r1 in self._hnf for r2 in other._hnf] + + return O._ideal_from_vectors_and_denominator(vecs, self._denominator * other._denominator) + + def _acted_upon_(self, other, on_left): + """ + Multiply ``other`` and this ideal on the right. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(x+y) + sage: J = O.ideal(x) + sage: x * I == I * J + True + + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(x+y) + sage: J = O.ideal(x) + sage: x * I == I * J + True + """ + O = self._ring + mul = O._mul_vecs + + # compute the vector form of other element + v = O.coordinate_vector(other) + d = v.denominator() + vec = vector([(d * c).numerator() for c in v]) + + # multiply with the ideal + vecs = [mul(vec, r) for r in self._hnf] + + return O._ideal_from_vectors_and_denominator(vecs, d * self._denominator) + + def intersect(self, other): + """ + Intersect this ideal with the other ideal as fractional ideals. + + INPUT: + + - ``other`` -- ideal + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(x+y) + sage: J = O.ideal(x) + sage: I.intersect(J) == I * J * (I + J)^-1 + True + + """ + from sage.matrix.special import block_matrix + + A = self._hnf + B = other._hnf + + ds = self.denominator() + do = other.denominator() + d = ds.lcm(do) + if not d.is_one(): + A = (d // ds) * A + B = (d // do) * B + + MS = A.matrix_space() + I = MS.identity_matrix() + O = MS.zero() + n = A.ncols() + + # intersect the row spaces of A and B + M = block_matrix([[I,I],[A,O],[O,B]]) + + # reversed Hermite form + M.reverse_rows_and_columns() + U = M._hermite_form_euclidean(transformation=True, + normalization=lambda p: ~p.lc()) + U.reverse_rows_and_columns() + + vecs = [U[i][:n] for i in range(n)] + + return self._ring._ideal_from_vectors_and_denominator(vecs, d) + + def hnf(self): + """ + Return the matrix in hermite normal form representing this ideal. + + See also :meth:`denominator` + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.maximal_order() + sage: I = O.ideal(y*(y+1)); I.hnf() + [x^6 + x^3 0] + [ x^3 + 1 1] + """ + return self._hnf + + def denominator(self): + """ + Return the denominator of this fractional ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.maximal_order() + sage: I = O.ideal(y/(y+1)) + sage: d = I.denominator(); d + x^3 + sage: d in O + True + """ + return self._denominator + + @cached_method + def module(self): + """ + Return the module, that is the ideal viewed as a module + over the base maximal order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: F. = K.extension(y^2 - x^3 - 1) + sage: O = F.maximal_order() + sage: I = O.ideal(x,1/y) + sage: I.module() + Free module of degree 2 and rank 2 over Maximal order + of Rational function field in x over Finite Field of size 7 + Echelon basis matrix: + [ 1 0] + [ 0 1/(x^3 + 1)] + """ + F = self.ring().fraction_field() + V, fr, to = F.vector_space() + O = F.base_field().maximal_order() + return V.span([to(g) for g in self.gens_over_base()], base_ring=O) + + @cached_method + def gens_over_base(self): + """ + Return the generators of this ideal as a module over the + maximal order of the base rational function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(x+y) + sage: I.gens_over_base() + (x^4 + x^2 + x, y + x) + + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(x+y) + sage: I.gens_over_base() + (x^3 + 1, y + x) + """ + gens = [] + for row in self._hnf: + gens.append(sum([c1*c2 for c1,c2 in zip(row, self._ring.basis())])) + denom_inv = ~self._denominator + return tuple([denom_inv * b for b in gens]) + + def gens(self): + """ + Return a set of generators of this ideal. + + This provides whatever set of generators as quickly + as possible. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 - x^3*Y - x) + sage: O = L.maximal_order() + sage: I = O.ideal(x+y) + sage: I.gens() + (x^4 + x^2 + x, y + x) + + sage: L. = K.extension(Y^2 +Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(x+y) + sage: I.gens() + (x^3 + 1, y + x) + """ + if self._gens_two.is_in_cache(): + return self._gens_two.cache + else: + return self.gens_over_base() + + def gens_two(self): + """ + Return two generators of this fractional ideal. + + If the ideal is principal, one generator may be returned. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(y) + sage: I # indirect doctest + Ideal (x^3 + x^2 + x, y) of Maximal order of Function field + in y defined by y^3 + x^6 + x^4 + x^2 + sage: ~I # indirect doctest + Ideal (1, (1/(x^6 + x^4 + x^2))*y^2) of Maximal order of Function field + in y defined by y^3 + x^6 + x^4 + x^2 + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: I # indirect doctest + Ideal (x^2 + 1, y) of Maximal order of Function field in y + defined by y^2 + y + (x^2 + 1)/x + sage: ~I # indirect doctest + Ideal (x, (x/(x^2 + 1))*y + x/(x^2 + 1)) of Maximal order + of Function field in y defined by y^2 + y + (x^2 + 1)/x + """ + d = self.denominator() + return tuple(e/d for e in self._gens_two()) + + @cached_method + def _gens_two(self): + """ + Return a set of two generators of the integral ideal, that is + the denominator times this fractional ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(4)); _. = K[] + sage: F. = K.extension(Y^3 + x^3*Y + x) + sage: O = F.maximal_order() + sage: I = O.ideal(x^2,x*y,x+y) + sage: I._gens_two() + (x, y) + """ + O = self.ring() + F = O.fraction_field() + + if self._kummer_form is not None: # prime ideal + _g1, _g2 = self._kummer_form + g1 = F(_g1) + g2 = sum([c1*c2 for c1,c2 in zip(_g2, O.basis())]) + return (g1,g2) + + ### start to search for two generators + + hnf = self._hnf + + norm = reduce(operator.mul, hnf.diagonal()) + + if norm.is_constant(): # unit ideal + return (F(1),) + + # one generator; see .ideal_below() + l = hnf[0][0] + p = l.degree() + l = F(l) + + if self._hnf == O.ideal(l)._hnf: # principal ideal + return (l,) + + R = hnf.base_ring() + + basis = [] + for row in hnf: + basis.append(sum([c1*c2 for c1,c2 in zip(row, O.basis())])) + + n = len(basis) + alpha = None + + def check(alpha): + alpha_norm = alpha.norm().numerator() # denominator is 1 + return norm.gcd(alpha_norm // norm) == 1 + + # Trial 1: search for alpha among generators + for alpha in basis: + if check(alpha): + return (l, alpha) + + # Trial 2: exhaustive search for alpha using only polynomials + # with coefficients 0 or 1 + for d in range(p): + G = itertools.product(itertools.product([0,1],repeat=d+1), repeat=n) + for g in G: + alpha = sum([R(c1)*c2 for c1,c2 in zip(g, basis)]) + if check(alpha): + return (l, alpha) + + # Trial 3: exhaustive search for alpha using all polynomials + for d in range(p): + G = itertools.product(R.polynomials(max_degree=d), repeat=n) + for g in G: + # discard duplicate cases + if max(c.degree() for c in g) != d: continue + for j in range(n): + if g[j] != 0: break + if g[j].leading_coefficient() != 1: continue + + alpha = sum([c1*c2 for c1,c2 in zip(g, basis)]) + if check(alpha): + return (l, alpha) + + # should not reach here + raise ValueError("no two generators found") + + @cached_method + def basis_matrix(self): + """ + Return the matrix of basis vectors of this ideal as a module. + + The basis matrix is by definition the hermite norm form of the ideal + divided by the denominator. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(x,1/y) + sage: I.denominator() * I.basis_matrix() == I.hnf() + True + """ + d = self.denominator() + m = (d * self).hnf() + if d != 1: + m = ~d * m + m.set_immutable() + return m + + def is_integral(self): + """ + Return ``True`` if this is an integral ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(x,1/y) + sage: I.is_integral() + False + sage: J = I.denominator() * I + sage: J.is_integral() + True + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(x,1/y) + sage: I.is_integral() + False + sage: J = I.denominator() * I + sage: J.is_integral() + True + """ + return self.denominator() == 1 + + def ideal_below(self): + """ + Return the ideal below this ideal. + + This is defined only for integral ideals. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(x,1/y) + sage: I.ideal_below() + Traceback (most recent call last): + ... + TypeError: not an integral ideal + sage: J = I.denominator() * I + sage: J.ideal_below() + Ideal (x^3 + x^2 + x) of Maximal order of Rational function field + in x over Finite Field of size 2 + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(x,1/y) + sage: I.ideal_below() + Traceback (most recent call last): + ... + TypeError: not an integral ideal + sage: J = I.denominator() * I + sage: J.ideal_below() + Ideal (x^3 + x) of Maximal order of Rational function field + in x over Finite Field of size 2 + """ + if not self.is_integral(): + raise TypeError("not an integral ideal") + + K = self.ring().fraction_field().base_field().maximal_order() + + # self._hnf is in reversed hermite normal form, that is, lower + # triangular form. Thus the generator of the ideal below is + # just the (0,0) entry of the normal form. When self._hnf was in + # hermite normal form, that is, upper triangular form, then the + # generator can be computed in the following way: + # + # m = matrix([hnf[0].parent().gen(0)] + list(hnf)) + # _,T = m.hermite_form(transformation=True) + # return T[-1][0] + # + # This is certainly less efficient! This is an argument for using + # reversed hermite normal form for ideal representation. + l = self._hnf[0][0] + + return K.ideal(l) + + def factor(self): + """ + Return the factorization of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(y) + sage: I == I.factor().prod() + True + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: I == I.factor().prod() + True + """ + return Factorization(self._factor(), cr=True) + + def _factor(self): + """ + Return the factorization of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(y) + sage: I == I.factor().prod() # indirect doctest + True + """ + O = self.ring() + F = O.fraction_field() + o = F.base_field().maximal_order() + + # First we collect primes below self + d = self._denominator + i = d * self + + factors = [] + primes = set([o.ideal(p) for p,_ in d.factor()] + [p for p,_ in i.ideal_below().factor()]) + for prime in primes: + qs = [q[0] for q in O.decomposition(prime)] + for q in qs: + exp = q.valuation(self) + if exp != 0: + factors.append((q,exp)) + return factors + + def norm(self): + """ + Return the norm of this fractional ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: O = F.maximal_order() + sage: i1 = O.ideal(x) + sage: i2 = O.ideal(y) + sage: i3 = i1 * i2 + sage: i3.norm() == i1.norm() * i2.norm() + True + sage: i1.norm() + x^3 + sage: i1.norm() == x ** F.degree() + True + sage: i2.norm() + x^6 + x^4 + x^2 + sage: i2.norm() == y.norm() + True + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: i1 = O.ideal(x) + sage: i2 = O.ideal(y) + sage: i3 = i1 * i2 + sage: i3.norm() == i1.norm() * i2.norm() + True + sage: i1.norm() + x^2 + sage: i1.norm() == x ** L.degree() + True + sage: i2.norm() + (x^2 + 1)/x + sage: i2.norm() == y.norm() + True + """ + return reduce(operator.mul, self.basis_matrix().diagonal()) + + @cached_method + def is_prime(self): + """ + Return ``True`` if this ideal is a prime ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(y) + sage: [f.is_prime() for f,_ in I.factor()] + [True, True] + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: [f.is_prime() for f,_ in I.factor()] + [True, True] + """ + factors = self.factor() + if len(factors) == 1 and factors[0][1] == 1: # prime! + prime = factors[0][0] + assert self == prime + self._relative_degree = prime._relative_degree + self._ramification_index = prime._ramification_index + self._prime_below = prime._prime_below + self._beta = prime._beta + self._beta_matrix = prime._beta_matrix + return True + else: + return False + + ################################################### + # The following methods are only for prime ideals # + ################################################### + + def valuation(self, ideal): + """ + Return the valuation of ``ideal`` at this prime ideal. + + INPUT: + + - ``ideal`` -- fractional ideal + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(x, (1/(x^3 + x^2 + x))*y^2) + sage: I.is_prime() + True + sage: J = O.ideal(y) + sage: I.valuation(J) + 2 + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: [f.valuation(I) for f,_ in I.factor()] + [-1, 2] + + The method closely follows Algorithm 4.8.17 of [Coh1993]_. + """ + if ideal.is_zero(): return infinity + + O = self.ring() + F = O.fraction_field() + n = F.degree() + + # beta_matrix is for fast multiplication with beta. For performance, + # this is computed here rather than when the prime is constructed. + if self._beta_matrix is None: + beta = self._beta + m = [] + for i in range(n): + mtable_row = O._mtable[i] + c = sum(beta[j] * mtable_row[j] for j in range(n)) + m.append(c) + self._beta_matrix = matrix(m) + + B = self._beta_matrix + + # Step 1: compute the valuation of the denominator times the ideal + # + # This part is highly optimized as it is critical for + # overall performance of the function field machinery. + p = self.prime_below().gen().numerator() + h = ideal._hnf.list() + val = min([c.valuation(p) for c in h]) + i = self._ramification_index * val + while True: + ppow = p ** val + h = (matrix(n, [c // ppow for c in h]) * B).list() + val = min([c.valuation(p) for c in h]) + if val.is_zero(): + break + i += self._ramification_index * (val - 1) + 1 + + # Step 2: compute the valuation of the denominator + j = self._ramification_index * ideal.denominator().valuation(p) + + # Step 3: return the valuation + return i - j + + def prime_below(self): + """ + Return the prime lying below this prime ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: O = F.maximal_order() + sage: I = O.ideal(y) + sage: [f.prime_below() for f,_ in I.factor()] + [Ideal (x) of Maximal order of Rational function field in x + over Finite Field of size 2, Ideal (x^2 + x + 1) of Maximal order + of Rational function field in x over Finite Field of size 2] + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: O = L.maximal_order() + sage: I = O.ideal(y) + sage: [f.prime_below() for f,_ in I.factor()] + [Ideal (x) of Maximal order of Rational function field in x over Finite Field of size 2, + Ideal (x + 1) of Maximal order of Rational function field in x over Finite Field of size 2] + """ + return self._prime_below + +class FunctionFieldIdealInfinite(FunctionFieldIdeal): + """ + Base class of ideals of maximal infinite orders + """ + pass + +class FunctionFieldIdealInfinite_rational(FunctionFieldIdealInfinite): + """ + Fractional ideal of the maximal order of rational function field. + + INPUT: + + - ``ring`` -- infinite maximal order + + - ``gen``-- generator + + Note that the infinite maximal order is a principal ideal domain. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: Oinf = K.maximal_order_infinite() + sage: Oinf.ideal(x) + Ideal (x) of Maximal infinite order of Rational function field in x over Finite Field of size 2 + """ + def __init__(self, ring, gen): + """ + Initialize. + + TESTS:: + + sage: K. = FunctionField(GF(2)) + sage: Oinf = K.maximal_order_infinite() + sage: I = Oinf.ideal(x) + sage: TestSuite(I).run() + """ + FunctionFieldIdealInfinite.__init__(self, ring) + self._gen = gen + + def __hash__(self): + """ + Return the hash of this fractional ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: Oinf = K.maximal_order_infinite() + sage: I = Oinf.ideal(x) + sage: J = Oinf.ideal(1/x) + sage: d = { I: 1, J: 2 } + """ + return hash( (self.ring(), self._gen) ) + + def _richcmp_(self, other, op): + """ + Compare this ideal and ``other`` with respect to ``op``. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: Oinf = K.maximal_order_infinite() + sage: I = Oinf.ideal(x+1) + sage: J = Oinf.ideal(x^2+x) + sage: I + J == J + True + """ + return richcmp(self._gen, other._gen, op) + + def _repr_(self): + """ + Return the string representation of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: Oinf = K.maximal_order_infinite() + sage: I = Oinf.ideal(x/(x^2+1)) + sage: I + Ideal (1/x) of Maximal infinite order of Rational function field + in x over Finite Field of size 2 + """ + return "Ideal (%s) of %s"%(self._gen, self._ring) + + def _add_(self, other): + """ + Add this ideal with the other ideal. + + INPUT: + + - ``other`` -- ideal + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: Oinf = K.maximal_order_infinite() + sage: I = Oinf.ideal(x/(x^2+1)) + sage: J = Oinf.ideal(1/(x+1)) + sage: I + J + Ideal (1/x) of Maximal infinite order of Rational function field + in x over Finite Field of size 2 + """ + return self._ring.ideal([self._gen, other._gen]) + + def _mul_(self, other): + """ + Multiply this ideal with the ``other`` ideal. + + INPUT: + + - ``other`` -- ideal + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: Oinf = K.maximal_order_infinite() + sage: I = Oinf.ideal(x/(x^2+1)) + sage: J = Oinf.ideal(1/(x+1)) + sage: I * J + Ideal (1/x^2) of Maximal infinite order of Rational function field + in x over Finite Field of size 2 + """ + return self._ring.ideal([self._gen * other._gen]) + + def _acted_upon_(self, other, on_left): + """ + Multiply this ideal with the ``other`` ideal. + + INPUT: + + - ``other`` -- ideal + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: Oinf = K.maximal_order_infinite() + sage: I = Oinf.ideal(x/(x^2+1)) + sage: x * I + Ideal (1) of Maximal infinite order of Rational function field + in x over Finite Field of size 2 + """ + return self._ring.ideal([other * self._gen]) + + def __invert__(self): + """ + Return the multiplicative inverse of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: Oinf = K.maximal_order_infinite() + sage: I = Oinf.ideal(x/(x^2 + 1)) + sage: ~I # indirect doctest + Ideal (x) of Maximal infinite order of Rational function field in x + over Finite Field of size 2 + """ + return self._ring.ideal([~self._gen]) + + def is_prime(self): + """ + Return ``True`` if this ideal is a prime ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: Oinf = K.maximal_order_infinite() + sage: I = Oinf.ideal(x/(x^2 + 1)) + sage: I.is_prime() + True + """ + x = self._ring.fraction_field().gen() + return self._gen == 1/x + + def gen(self): + """ + Return the generator of this principal ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: Oinf = K.maximal_order_infinite() + sage: I = Oinf.ideal((x+1)/(x^3+x),(x^2+1)/x^4) + sage: I.gen() + 1/x^2 + """ + return self._gen + + def gens(self): + """ + Return the generator of this principal ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: Oinf = K.maximal_order_infinite() + sage: I = Oinf.ideal((x+1)/(x^3+x),(x^2+1)/x^4) + sage: I.gens() + (1/x^2,) + """ + return (self._gen,) + + def gens_over_base(self): + """ + Return the generator of this ideal as a rank one module + over the infinite maximal order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: Oinf = K.maximal_order_infinite() + sage: I = Oinf.ideal((x+1)/(x^3+x),(x^2+1)/x^4) + sage: I.gens_over_base() + (1/x^2,) + """ + return (self._gen,) + + def factor(self): + """ + Return the factorization of this ideal into prime ideals. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: Oinf = K.maximal_order_infinite() + sage: I = Oinf.ideal((x+1)/(x^3+1)) + sage: I.factor() + (Ideal (1/x) of Maximal infinite order of Rational function field + in x over Finite Field of size 2)^2 + """ + g = ~(self.ring().fraction_field().gen()) + m = self._gen.denominator().degree() - self._gen.numerator().degree() + if m == 0: + factors = [] + else: + factors = [(self.ring().ideal(g), m)] + return Factorization(factors, cr=True) + + def valuation(self, ideal): + """ + Return the valuation of ``ideal`` at this prime ideal. + + INPUT: + + - ``ideal`` -- fractional ideal + + EXAMPLES:: + + sage: F. = FunctionField(QQ) + sage: O = F.maximal_order_infinite() + sage: p = O.ideal(1/x) + sage: p.valuation(O.ideal(x/(x+1))) + 0 + sage: p.valuation(O.ideal(0)) + +Infinity + """ + if not self.is_prime(): + raise TypeError("not a prime ideal") + + f = ideal.gen() + if f == 0: + return infinity + else: + return f.denominator().degree() - f.numerator().degree() + +class FunctionFieldIdealInfinite_module(FunctionFieldIdealInfinite, Ideal_generic): + """ + A fractional ideal specified by a finitely generated module over + the integers of the base field. + + INPUT: + + - ``ring`` -- order in a function field + + - ``module`` -- module + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: O.ideal(y) + Ideal (x^3 + 1, -y) of Order in Function field in y defined by y^2 - x^3 - 1 + """ + def __init__(self, ring, module): + """ + Initialize. + + TESTS:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal(y) + sage: TestSuite(I).run() + """ + FunctionFieldIdealInfinite.__init__(self, ring) + + self._module = module + self._structure = ring.fraction_field().vector_space() + + V, from_V, to_V = self._structure + gens = tuple([from_V(a) for a in module.basis()]) + self._gens = gens + + # module generators are still ideal generators + Ideal_generic.__init__(self, ring, self._gens, coerce=False) + + def __contains__(self, x): + """ + Return ``True`` if ``x`` is in this ideal. + + INPUT: + + - ``x`` -- element of the function field + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal_with_gens_over_base([1, y]); I + Ideal (1, y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 + sage: y in I + True + sage: y/x in I + False + sage: y^2 - 2 in I + True + """ + return self._structure[2](x) in self._module + + def __hash__(self): + """ + Return the hash of this ideal + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal_with_gens_over_base([1, y]) + sage: d = {I: 2} # indirect doctest + """ + return hash((self._ring,self._module)) + + def __eq__(self, other): + """ + Test equality of this ideal with the ``other`` ideal. + + INPUT: + + - ``other`` -- ideal + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal_with_gens_over_base([1, y]) + sage: I == I + I # indirect doctest + True + """ + if not isinstance(other, FunctionFieldIdeal_module): + other = self.ring().ideal(other) + if self.ring() != other.ring(): + raise ValueError("rings must be the same") + + if (self.module().is_submodule(other.module()) and + other.module().is_submodule(self.module())): + return True + else: + return False + + def _repr_short(self): + """ + Represent the list of generators. + + EXAMPLES:: + + sage: P. = QQ[] + sage: P*[a^2,a*b+c,c^3] + Ideal (a^2, a*b + c, c^3) of Multivariate Polynomial Ring in a, b, c over Rational Field + sage: (P*[a^2,a*b+c,c^3])._repr_short() + '(a^2, a*b + c, c^3)' + + If the string representation of a generator contains a line break, + the generators are not represented from left to right but from + top to bottom. This is the case, e.g., for matrices:: + + sage: MS = MatrixSpace(QQ,2,2) + sage: MS*[MS.1,2] + Left Ideal + ( + [0 1] + [0 0], + + [2 0] + [0 2] + ) + of Full MatrixSpace of 2 by 2 dense matrices over Rational Field + + + """ + L = [] + has_return = False + for x in self.gens(): + s = repr(x) + if '\n' in s: + has_return = True + s = s.replace('\n','\n ') + L.append(s) + if has_return: + return '\n(\n %s\n)\n'%(',\n\n '.join(L)) + return '(%s)'%(', '.join(L)) + + def _repr_(self): + """ + Return a string representation of this ideal. + + EXAMPLES:: + + sage: P. = QQ[] + sage: P*[a^2,a*b+c,c^3] # indirect doctest + Ideal (a^2, a*b + c, c^3) of Multivariate Polynomial Ring in a, b, c over Rational Field + """ + return "Ideal %s of %s"%(self._repr_short(), self.ring()) + + def module(self): + """ + Return the module over the maximal order of the base field that + underlies this ideal. + + The formation of the module is compatible with the vector + space corresponding to the function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)) + sage: O = K.maximal_order(); O + Maximal order of Rational function field in x over Finite Field of size 7 + sage: K.polynomial_ring() + Univariate Polynomial Ring in x over Rational function field in x over Finite Field of size 7 + sage: I = O.ideal([x^2 + 1, x*(x^2+1)]) + sage: I.gens() + (x^2 + 1,) + sage: I.module() + Free module of degree 1 and rank 1 over Maximal order of Rational function field in x over Finite Field of size 7 + Echelon basis matrix: + [x^2 + 1] + sage: V, from_V, to_V = K.vector_space(); V + Vector space of dimension 1 over Rational function field in x over Finite Field of size 7 + sage: I.module().is_submodule(V) + True + """ + return self._module + +class FunctionFieldIdealInfinite_global(FunctionFieldIdealInfinite): + """ + Ideals of the infinite maximal order. + + INPUT: + + - ``ring`` -- infinite maximal order of the function field + + - ``ideal`` -- ideal in the inverted function field + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3+t^2-x^4) + sage: Oinf = F.maximal_order_infinite() + sage: Oinf.ideal(1/y) + Ideal (1/x^2,1/x^4*y^2) of Maximal infinite order of Function field + in y defined by y^3 + y^2 + 2*x^4 + """ + def __init__(self, ring, ideal): + """ + Initialize this ideal. + + TESTS:: + + sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3+t^2-x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/y) + sage: TestSuite(I).run() + """ + FunctionFieldIdealInfinite.__init__(self, ring) + self._ideal = ideal + + def __hash__(self): + """ + Return the hash of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/y) + sage: d = { I: 1 } + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(1/y) + sage: d = { I: 1 } + """ + return hash((self.ring(), self._ideal)) + + def _repr_(self): + """ + Return the string representation of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: Oinf.ideal(1/y) + Ideal (1/x^2,1/x^4*y^2) of Maximal infinite order of Function field + in y defined by y^3 + y^2 + 2*x^4 + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: Oinf.ideal(1/y) + Ideal (1/x,1/x*y) of Maximal infinite order of Function field in y + defined by y^2 + y + (x^2 + 1)/x + """ + return self._repr_short() + " of {}".format(self.ring()) + + def _repr_short(self): + """ + Return the short string representation of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); R. = K[] + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/y) + sage: I._repr_short() + 'Ideal (1/x^2,1/x^4*y^2)' + """ + gens_str = ','.join([repr(g) for g in self.gens_two()]) + return "Ideal ({})".format(gens_str) + + def _add_(self, other): + """ + Add this ideal with the ``other`` ideal. + + INPUT: + + - ``ideal`` -- ideal + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3+t^2-x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/x^2*1/y) + sage: J = Oinf.ideal(1/x) + sage: I + J + Ideal (1/x) of Maximal infinite order of Function field in y + defined by y^3 + y^2 + 2*x^4 + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(1/x^2*1/y) + sage: J = Oinf.ideal(1/x) + sage: I + J + Ideal (1/x) of Maximal infinite order of Function field in y + defined by y^2 + y + (x^2 + 1)/x + """ + return FunctionFieldIdealInfinite_global(self._ring, self._ideal + other._ideal) + + def _mul_(self, other): + """ + Multiply this ideal with the ``other`` ideal. + + INPUT: + + - ``other`` -- ideal + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3+t^2-x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/x^2*1/y) + sage: J = Oinf.ideal(1/x) + sage: I * J + Ideal (1/x^5,1/x^7*y^2) of Maximal infinite order of Function field + in y defined by y^3 + y^2 + 2*x^4 + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(1/x^2*1/y) + sage: J = Oinf.ideal(1/x) + sage: I * J + Ideal (1/x^4,1/x^4*y) of Maximal infinite order of Function field in y + defined by y^2 + y + (x^2 + 1)/x + """ + return FunctionFieldIdealInfinite_global(self._ring, self._ideal * other._ideal) + + def __pow__(self, n): + """ + Raise this ideal to ``n``-th power. + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3+t^2-x^4) + sage: Oinf = F.maximal_order_infinite() + sage: J = Oinf.ideal(1/x) + sage: J^3 + Ideal (1/x^3) of Maximal infinite order of Function field + in y defined by y^3 + y^2 + 2*x^4 + """ + return FunctionFieldIdealInfinite_global(self._ring, self._ideal ** n) + + def __invert__(self): + """ + Return the inverted ideal of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: J = Oinf.ideal(y) + sage: ~J + Ideal (1/x^2,1/x^4*y^2) of Maximal infinite order + of Function field in y defined by y^3 + y^2 + 2*x^4 + sage: J * ~J + Ideal (1) of Maximal infinite order of Function field + in y defined by y^3 + y^2 + 2*x^4 + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: J = Oinf.ideal(y) + sage: ~J + Ideal (1/x,1/x*y) of Maximal infinite order of Function field in y + defined by y^2 + y + (x^2 + 1)/x + sage: J * ~J + Ideal (1) of Maximal infinite order of Function field in y + defined by y^2 + y + (x^2 + 1)/x + """ + return FunctionFieldIdealInfinite_global(self._ring, ~ self._ideal) + + def _richcmp_(self, other, op): + """ + Compare this ideal with the ``other`` ideal with respect to ``op``. + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3+t^2-x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/x^2*1/y) + sage: J = Oinf.ideal(1/x) + sage: I * J == J * I + True + sage: I + J == J + True + sage: I + J == I + False + sage: (I < J) == (not J < I) + True + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(1/x^2*1/y) + sage: J = Oinf.ideal(1/x) + sage: I * J == J * I + True + sage: I + J == J + True + sage: I + J == I + False + sage: (I < J) == (not J < I) + True + """ + return richcmp(self._ideal, other._ideal, op) + + @property + def _relative_degree(self): + """ + Return the relative degree of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(1/x) + sage: [J._relative_degree for J,_ in I.factor()] + [1] + """ + if not self.is_prime(): + raise TypeError("not a prime ideal") + + return self._ideal._relative_degree + + def gens(self): + """ + Return a set of generators of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3+t^2-x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(x+y) + sage: I.gens() + (x, y, 1/x^2*y^2) + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(x+y) + sage: I.gens() + (x, y) + """ + F = self.ring().fraction_field() + iF,from_iF,to_iF = F._inversion_isomorphism() + return tuple(from_iF(b) for b in self._ideal.gens()) + + def gens_two(self): + """ + Return a set of at most two generators of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3+t^2-x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(x+y) + sage: I.gens_two() + (x, y) + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2+Y+x+1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(x+y) + sage: I.gens_two() + (x,) + """ + F = self.ring().fraction_field() + iF,from_iF,to_iF = F._inversion_isomorphism() + return tuple(from_iF(b) for b in self._ideal.gens_two()) + + def gens_over_base(self): + """ + Return a set of generators of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); _. = K[] + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(x + y) + sage: I.gens_over_base() + (x, y, 1/x^2*y^2) + """ + F = self.ring().fraction_field() + iF,from_iF,to_iF = F._inversion_isomorphism() + return tuple(from_iF(g) for g in self._ideal.gens_over_base()) + + def ideal_below(self): + """ + Return a set of generators of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); _. = K[] + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/y^2) + sage: I.ideal_below() + Ideal (x^3) of Maximal order of Rational function field + in x over Finite Field in z2 of size 3^2 + """ + return self._ideal.ideal_below() + + def is_prime(self): + """ + Return ``True`` if this ideal is a prime ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3 + t^2 - x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/x) + sage: I.factor() + (Ideal (1/x,1/x^3*y^2) of Maximal infinite order of Function field + in y defined by y^3 + y^2 + 2*x^4)^3 + sage: I.is_prime() + False + sage: J = I.factor()[0][0] + sage: J.is_prime() + True + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(1/x) + sage: I.factor() + (Ideal (1/x,1/x*y) of Maximal infinite order of Function field in y + defined by y^2 + y + (x^2 + 1)/x)^2 + sage: I.is_prime() + False + sage: J = I.factor()[0][0] + sage: J.is_prime() + True + """ + return self._ideal.is_prime() + + @cached_method + def prime_below(self): + """ + Return the prime of the base order that underlies this prime ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(3^2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3+t^2-x^4) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(1/x) + sage: I.factor() + (Ideal (1/x,1/x^3*y^2) of Maximal infinite order of Function field + in y defined by y^3 + y^2 + 2*x^4)^3 + sage: J = I.factor()[0][0] + sage: J.is_prime() + True + sage: J.prime_below() + Ideal (1/x) of Maximal infinite order of Rational function field + in x over Finite Field in z2 of size 3^2 + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(1/x) + sage: I.factor() + (Ideal (1/x,1/x*y) of Maximal infinite order of Function field in y + defined by y^2 + y + (x^2 + 1)/x)^2 + sage: J = I.factor()[0][0] + sage: J.is_prime() + True + sage: J.prime_below() + Ideal (1/x) of Maximal infinite order of Rational function field in x + over Finite Field of size 2 + """ + if not self.is_prime(): + raise TypeError("not a prime ideal") + + F = self.ring().fraction_field() + K = F.base_field() + return K.maximal_order_infinite().prime_ideal() + + def factor(self): + """ + Return factorization of this ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3 - x^2*(x^2+x+1)^2) + sage: Oinf = F.maximal_order_infinite() + sage: f= 1/x + sage: I = Oinf.ideal(f) + sage: I.factor() + (Ideal (1/x,1/x^4*y^2 + 1/x^2*y + 1) of Maximal infinite order + of Function field in y defined by y^3 + x^6 + x^4 + x^2) * + (Ideal (1/x,1/x^2*y + 1) of Maximal infinite order + of Function field in y defined by y^3 + x^6 + x^4 + x^2) + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2+Y+x+1/x) + sage: Oinf = L.maximal_order_infinite() + sage: f= 1/x + sage: I = Oinf.ideal(f) + sage: I.factor() + (Ideal (1/x,1/x*y) of Maximal infinite order of Function field in y + defined by y^2 + y + (x^2 + 1)/x)^2 + """ + return Factorization(self._factor(), cr=True) + + def _factor(self): + """ + Return factorization of the ideal. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: Oinf = F.maximal_order_infinite() + sage: f= 1/x + sage: I = Oinf.ideal(f) + sage: I.factor() # indirect doctest + (Ideal (1/x,1/x^4*y^2 + 1/x^2*y + 1) of Maximal infinite order + of Function field in y defined by y^3 + x^6 + x^4 + x^2) * + (Ideal (1/x,1/x^2*y + 1) of Maximal infinite order + of Function field in y defined by y^3 + x^6 + x^4 + x^2) + """ + O = self.ring() + + factors = [] + for iprime, exp in O._to_iF(self).factor(): + prime = FunctionFieldIdealInfinite_global(O, iprime) + factors.append((prime, exp)) + + return factors + + def valuation(self, ideal): + """ + Return the valuation of ``ideal`` with respect to this prime ideal. + + INPUT: + + - ``ideal`` -- fractional ideal + + EXAMPLES:: + + sage: K.=FunctionField(GF(2)); _. = K[] + sage: L.=K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(y) + sage: [f.valuation(I) for f,_ in I.factor()] + [-1] + """ + if not self.is_prime(): + raise TypeError("not a prime ideal") + + return self._ideal.valuation(self.ring()._to_iF(ideal)) + +class IdealMonoid(UniqueRepresentation, Parent): + r""" + The monoid of ideals in orders of function fields. + + INPUT: + + - ``R`` -- order + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: O = K.maximal_order() + sage: M = O.ideal_monoid(); M + Monoid of ideals of Maximal order of Rational function field in x over Finite Field of size 2 + """ + + def __init__(self, R): + """ + Initialize the ideal monoid. + + TESTS:: + + sage: K. = FunctionField(GF(2)) + sage: O = K.maximal_order() + sage: M = O.ideal_monoid() + sage: TestSuite(M).run() + """ + self.Element = R._ideal_class + Parent.__init__(self, category = Monoids()) + + self.__R = R + self._populate_coercion_lists_() + + def _repr_(self): + """ + Return the string representation of the ideal monoid. + + TESTS:: + + sage: K. = FunctionField(GF(2)) + sage: O = K.maximal_order() + sage: M = O.ideal_monoid(); M._repr_() + 'Monoid of ideals of Maximal order of Rational function field in x over Finite Field of size 2' + """ + return "Monoid of ideals of %s"%self.__R + + def ring(self): + """ + Return the ring of which this is the ideal monoid. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: O = K.maximal_order() + sage: M = O.ideal_monoid(); M.ring() is O + True + """ + return self.__R + + def _element_constructor_(self, x): + """ + Create an ideal in the monoid from ``x``. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: O = K.maximal_order() + sage: M = O.ideal_monoid() + sage: M(x) + Ideal (x) of Maximal order of Rational function field in x over Finite Field of size 2 + sage: M([x-4, 1/x]) + Ideal (1/x) of Maximal order of Rational function field in x over Finite Field of size 2 + """ + try: # x is an ideal + x = x.gens() + except AttributeError: + pass + return self.__R.ideal(x) + + def _coerce_map_from_(self, x): + """ + Used by coercion framework. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: O = K.maximal_order() + sage: M = O.ideal_monoid() + sage: M.has_coerce_map_from(O) # indirect doctest + True + sage: M.has_coerce_map_from(O.ideal_monoid()) + True + """ + if isinstance(x, IdealMonoid): + return self.ring().has_coerce_map_from(x.ring()) + else: + return self.ring().has_coerce_map_from(x) + + def _an_element_(self): + """ + Return an element of the ideal monoid. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)) + sage: O = K.maximal_order() + sage: M = O.ideal_monoid() + sage: M.an_element() # indirect doctest; random + Ideal (x) of Maximal order of Rational function field in x + over Finite Field of size 2 + """ + x = self.__R.an_element() + return self.__R.ideal([x]) diff --git a/src/sage/rings/function_field/order.py b/src/sage/rings/function_field/order.py new file mode 100644 index 00000000000..2ea20cb788d --- /dev/null +++ b/src/sage/rings/function_field/order.py @@ -0,0 +1,2462 @@ +r""" +Orders of function fields + +An order of a function field is a subring that is, as a module over the base +maximal order, finitely generated and of maximal rank `n`, where `n` is the +extension degree of the function field. All orders are subrings of maximal +orders. + +A rational function field has two maximal orders: maximal finite order `o` and +maximal infinite order `o_\infty`. The maximal order of a rational function +field over constant field `k` is just the polynomial ring `o=k[x]`. The +maximal infinite order is the set of rational functions whose denominator has +degree greater than or equal to that of the numerator. + +EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal(1/x); I + Ideal (1/x) of Maximal order of Rational function field in x over Rational Field + sage: 1/x in O + False + sage: Oinf = K.maximal_order_infinite() + sage: 1/x in Oinf + True + +In an extension of a rational function field, an order over the maximal finite +order is called a finite order while an order over the maximal infinite order +is called an infinite order. Thus a function field has one maximal finite order +`O` and one maximal infinite order `O_\infty`. There are other non-maximal +orders such as equation orders:: + + sage: K. = FunctionField(GF(3)); R. = K[] + sage: L. = K.extension(y^3-y-x) + sage: O = L.equation_order() + sage: 1/y in O + False + sage: x/y in O + True + +Sage provides an extensive functionality for computations in maximal orders of +global function fields. For example, you can decompose a prime ideal of a +rational function field in an extension:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: o = K.maximal_order() + sage: p = o.ideal(x+1) + sage: p.is_prime() + True + + sage: F. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: O = F.maximal_order() + sage: O.decomposition(p) + [(Ideal (x + 1, y + 1) of Maximal order + of Function field in y defined by y^3 + x^6 + x^4 + x^2, 1, 1), + (Ideal (x + 1, y^2 + y + 1) of Maximal order + of Function field in y defined by y^3 + x^6 + x^4 + x^2, 2, 1)] + + sage: p1,relative_degree,ramification_index = O.decomposition(p)[1] + sage: p1.parent() + Monoid of ideals of Maximal order of Function field in y + defined by y^3 + x^6 + x^4 + x^2 + sage: relative_degree + 2 + sage: ramification_index + 1 + +AUTHORS: + +- William Stein (2010): initial version + +- Maarten Derickx (2011-09-14): fixed ideal_with_gens_over_base() for rational function fields + +- Julian Rueth (2011-09-14): added check in _element_constructor_ + +- Kwankyu Lee (2017-04-30): added maximal orders of global function fields + +""" +from __future__ import absolute_import +#***************************************************************************** +# Copyright (C) 2010 William Stein +# Copyright (C) 2011 Maarten Derickx +# Copyright (C) 2011 Julian Rueth +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_import import lazy_import + +from sage.modules.free_module_element import vector +from sage.arith.all import lcm, gcd + +from sage.structure.parent import Parent +from sage.structure.unique_representation import CachedRepresentation, UniqueRepresentation + +from sage.categories.integral_domains import IntegralDomains +from sage.categories.principal_ideal_domains import PrincipalIdealDomains +from sage.categories.euclidean_domains import EuclideanDomains + +from .ideal import ( + IdealMonoid, + FunctionFieldIdeal, + FunctionFieldIdeal_module, + FunctionFieldIdeal_rational, + FunctionFieldIdeal_global, + FunctionFieldIdealInfinite_module, + FunctionFieldIdealInfinite_rational, + FunctionFieldIdealInfinite_global) + +lazy_import('sage.matrix.special', 'block_matrix') +lazy_import('sage.matrix.constructor', 'matrix') + +class FunctionFieldOrder_base(CachedRepresentation, Parent): + """ + Base class for orders in function fields. + + INPUT: + + - ``field`` -- function field + + EXAMPLES:: + + sage: F = FunctionField(QQ,'y') + sage: F.maximal_order() + Maximal order of Rational function field in y over Rational Field + """ + def __init__(self, field, ideal_class=FunctionFieldIdeal, category=None): + """ + Initialize. + + TESTS:: + + sage: F = FunctionField(QQ,'y') + sage: O = F.maximal_order() + sage: TestSuite(O).run(skip='_test_gcd_vs_xgcd') + """ + category = IntegralDomains().or_subcategory(category).Infinite() + Parent.__init__(self, category=category, facade=field) + + self._ideal_class = ideal_class # element class for parent ideal monoid + self._field = field + + def is_field(self): + """ + Return ``False`` since orders are never fields. + + EXAMPLES:: + + sage: FunctionField(QQ,'y').maximal_order().is_field() + False + """ + return False + + def is_noetherian(self): + """ + Return ``True`` since orders in function fields are noetherian. + + EXAMPLES:: + + sage: FunctionField(QQ,'y').maximal_order().is_noetherian() + True + """ + return True + + def function_field(self): + """ + Return the function field to which the order belongs. + + EXAMPLES:: + + sage: FunctionField(QQ,'y').maximal_order().function_field() + Rational function field in y over Rational Field + """ + return self._field + + fraction_field = function_field + + def is_subring(self, other): + """ + Return ``True`` if the order is a subring of the other order. + + INPUT: + + - ``other`` -- order of the function field or the field itself + + EXAMPLES:: + + sage: F = FunctionField(QQ,'y') + sage: O = F.maximal_order() + sage: O.is_subring(F) + True + """ + if other is self._field: + return True + else: + raise NotImplementedError + + def ideal_monoid(self): + """ + Return the monoid of ideals of the order. + + EXAMPLES:: + + sage: FunctionField(QQ,'y').maximal_order().ideal_monoid() + Monoid of ideals of Maximal order of Rational function field in y over Rational Field + """ + return IdealMonoid(self) + +class FunctionFieldOrder(FunctionFieldOrder_base): + """ + Base class for orders in function fields. + """ + def _repr_(self): + """ + Return the string representation. + + EXAMPLES:: + + sage: FunctionField(QQ,'y').maximal_order() + Maximal order of Rational function field in y over Rational Field + """ + return "Order in {}".format(self._field) + +class FunctionFieldOrder_basis(FunctionFieldOrder): + """ + Order given by a basis over the maximal order of the base field. + + INPUT: + + - ``basis`` -- list of elements of the function field + + - ``check`` -- (default: ``True``) if ``True``, check whether the module + that ``basis`` generates forms an order + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.equation_order(); O + Order in Function field in y defined by y^4 + x*y + 4*x + 1 + + The basis only defines an order if the module it generates is closed under + multiplication and contains the identity element:: + + sage: K. = FunctionField(QQ) + sage: R. = K[] + sage: L. = K.extension(y^5 - (x^3 + 2*x*y + 1/x)) + sage: y.is_integral() + False + sage: L.order(y) + Traceback (most recent call last): + ... + ValueError: the module generated by basis (1, y, y^2, y^3, y^4) must be closed under multiplication + + The basis also has to be linearly independent and of the same rank as the + degree of the function field of its elements (only checked when ``check`` + is ``True``):: + + sage: L.order(L(x)) + Traceback (most recent call last): + ... + ValueError: basis (1, x, x^2, x^3, x^4) is not linearly independent + sage: sage.rings.function_field.order.FunctionFieldOrder_basis((y,y,y^3,y^4,y^5)) + Traceback (most recent call last): + ... + ValueError: basis (y, y, y^3, y^4, 2*x*y + (x^4 + 1)/x) is not linearly independent + """ + def __init__(self, basis, check=True): + """ + Initialize. + + TESTS:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.equation_order() + sage: TestSuite(O).run() + """ + if len(basis) == 0: + raise ValueError("basis must have positive length") + + field = basis[0].parent() + if len(basis) != field.degree(): + raise ValueError("length of basis must equal degree of field") + + FunctionFieldOrder.__init__(self, field, ideal_class=FunctionFieldIdeal_module) + + V, from_V, to_V = field.vector_space() + + R = V.base_field().maximal_order() + self._module = V.span([to_V(b) for b in basis], base_ring=R) + + self._from_module= from_V + self._to_module = to_V + self._basis = tuple(basis) + self._ring = field.polynomial_ring() + self._populate_coercion_lists_(coerce_list=[self._ring]) + + if check: + if self._module.rank() != field.degree(): + raise ValueError("basis {} is not linearly independent".format(basis)) + if not to_V(field(1)) in self._module: + raise ValueError("the identity element must be in the module spanned by basis {}".format(basis)) + if not all(to_V(a*b) in self._module for a in basis for b in basis): + raise ValueError("the module generated by basis {} must be closed under multiplication".format(basis)) + + def _element_constructor_(self, f): + """ + Constuct an element of this order from ``f``. + + INPUT: + + - ``f`` -- element + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: K.maximal_order()._element_constructor_(x) + x + """ + F = self.function_field() + + try: + f = F(f) + except TypeError: + raise TypeError("unable to convert to an element of {}".format(F)) + + V, fr_V, to_V = F.vector_space() + f_vector = to_V(f) + if not f_vector in self._module: + raise TypeError("{} is not an element of {}".format(f_vector, self)) + + return f + + def ideal_with_gens_over_base(self, gens): + """ + Return the fractional ideal with basis ``gens`` over the + maximal order of the base field. + + It is not checked that the ``gens`` really generates an ideal. + + INPUT: + + - ``gens`` -- list of elements of the function field + + EXAMPLES: + + We construct an ideal in a rational function field:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal([y]); I + Ideal (y) of Maximal order of Rational function field in y over Rational Field + sage: I*I + Ideal (y^2) of Maximal order of Rational function field in y over Rational Field + + We construct some ideals in a nontrivial function field:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order(); O + Order in Function field in y defined by y^2 + 6*x^3 + 6 + sage: I = O.ideal_with_gens_over_base([1, y]); I + Ideal (1, y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 + sage: I.module() + Free module of degree 2 and rank 2 over Maximal order of Rational function field in x over Finite Field of size 7 + Echelon basis matrix: + [1 0] + [0 1] + + There is no check if the resulting object is really an ideal:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal_with_gens_over_base([y]); I + Ideal (y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 + sage: y in I + True + sage: y^2 in I + False + """ + F = self.function_field() + S = F.base_field().maximal_order() + + gens = [F(a) for a in gens] + + V, from_V, to_V = F.vector_space() + M = V.span([to_V(b) for b in gens], base_ring=S) + + return self.ideal_monoid().element_class(self, M) + + def ideal(self, *gens): + """ + Return the fractional ideal generated by the elements in ``gens``. + + INPUT: + + - ``gens`` -- list of generators or an ideal in a ring which + coerces to this order + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: O.ideal(y) + Ideal (y) of Maximal order of Rational function field in y over Rational Field + sage: O.ideal([y,1/y]) == O.ideal(y,1/y) # multiple generators may be given as a list + True + + A fractional ideal of a nontrivial extension:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: O = K.maximal_order() + sage: I = O.ideal(x^2-4) + sage: L. = K.extension(y^2 - x^3 - 1) + sage: S = L.equation_order() + sage: S.ideal(1/y) + Ideal (1, (6/(x^3 + 1))*y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 + sage: I2 = S.ideal(x^2-4); I2 + Ideal (x^2 + 3, (x^2 + 3)*y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 + sage: I2 == S.ideal(I) + True + """ + if len(gens) == 1: + gens = gens[0] + if not isinstance(gens, (list, tuple)): + if isinstance(gens, FunctionFieldIdeal): + gens = gens.gens() + else: + gens = [gens] + K = self.function_field() + + return self.ideal_with_gens_over_base([b*K(g) for b in self.basis() for g in gens]) + + def polynomial(self): + """ + Return the defining polynomial of the function field of which this is an order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.equation_order() + sage: O.polynomial() + y^4 + x*y + 4*x + 1 + """ + return self._field.polynomial() + + def basis(self): + """ + Return a basis of the order over the maximal order of the base field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.equation_order() + sage: O.basis() + (1, y, y^2, y^3) + """ + return self._basis + + def free_module(self): + """ + Return the free module formed by the basis over the maximal order + of the base function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.equation_order() + sage: O.free_module() + Free module of degree 4 and rank 4 over Maximal order of Rational + function field in x over Finite Field of size 7 + Echelon basis matrix: + [1 0 0 0] + [0 1 0 0] + [0 0 1 0] + [0 0 0 1] + """ + return self._module + + def coordinate_vector(self, e): + """ + Return the cooridinates of ``e`` with respect to the basis of the order. + + INPUT: + + - ``e`` -- element of the order or the function field + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.equation_order() + sage: f = (x + y)^3 + sage: O.coordinate_vector(f) + (x^3, 3*x^2, 3*x, 1) + """ + return self._module.coordinate_vector(self._to_module(e), check=False) + +class FunctionFieldOrderInfinite(FunctionFieldOrder_base): + """ + Base class for infinite orders in function fields. + """ + def _repr_(self): + """ + EXAMPLES:: + + sage: FunctionField(QQ,'y').maximal_order_infinite() + Maximal infinite order of Rational function field in y over Rational Field + """ + return "Infinite order in {}".format(self.function_field()) + +class FunctionFieldOrderInfinite_basis(FunctionFieldOrderInfinite): + """ + Order given by a basis over the infinite maximal order of the base + field. + + INPUT: + + - ``basis`` -- elements of the function field + + - ``check`` -- boolean (default: ``True``); if ``True``, check the basis generates + an order + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.equation_order_infinite(); O + Infinite order in Function field in y defined by y^4 + x*y + 4*x + 1 + + The basis only defines an order if the module it generates is closed under + multiplication and contains the identity element (only checked when + ``check`` is ``True``):: + + sage: O = L.order_infinite_with_basis([1, y, 1/x^2*y^2, y^3]); O + Traceback (most recent call last): + ... + ValueError: the module generated by basis (1, y, 1/x^2*y^2, y^3) must be closed under multiplication + + The basis also has to be linearly independent and of the same rank as the + degree of the function field of its elements (only checked when ``check`` + is ``True``):: + + sage: O = L.order_infinite_with_basis([1, y, 1/x^2*y^2, 1 + y]); O + Traceback (most recent call last): + ... + ValueError: The given basis vectors must be linearly independent. + + Note that 1 does not need to be an element of the basis, as long as it is + in the module spanned by it:: + + sage: O = L.order_infinite_with_basis([1 + 1/x*y, 1/x*y, 1/x^2*y^2, 1/x^3*y^3]); O + Infinite order in Function field in y defined by y^4 + x*y + 4*x + 1 + sage: O.basis() + (1/x*y + 1, 1/x*y, 1/x^2*y^2, 1/x^3*y^3) + """ + def __init__(self, basis, check=True): + """ + Initialize. + + TESTS:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.equation_order_infinite() + sage: TestSuite(O).run() + """ + if len(basis) == 0: + raise ValueError("basis must have positive length") + + field = basis[0].parent() + if len(basis) != field.degree(): + raise ValueError("length of basis must equal degree of field") + + FunctionFieldOrderInfinite.__init__(self, field, ideal_class=FunctionFieldIdealInfinite_module) + + # function field element f is in this order if and only if + # W.coordinate_vector(to(f)) in M + V, fr, to = field.vector_space() + R = field.base_field().maximal_order_infinite() + W = V.span_of_basis([to(v) for v in basis]) + from sage.modules.free_module import FreeModule + M = FreeModule(R,W.dimension()) + self._basis = tuple(basis) + self._ambient_space = W + self._module = M + + self._ring = field.polynomial_ring() + self._populate_coercion_lists_(coerce_list=[self._ring]) + + if check: + if self._module.rank() != field.degree(): + raise ValueError("basis {} is not linearly independent".format(basis)) + if not W.coordinate_vector(to(field(1))) in self._module: + raise ValueError("the identity element must be in the module spanned by basis {}".format(basis)) + if not all(W.coordinate_vector(to(a*b)) in self._module for a in basis for b in basis): + raise ValueError("the module generated by basis {} must be closed under multiplication".format(basis)) + + def _element_constructor_(self, f): + """ + Construct an element of this order. + + INPUT: + + - ``f`` -- element + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: O(x) + x + sage: O(1/x) + Traceback (most recent call last): + ... + TypeError: 1/x is not an element of Maximal order of Rational function field in x over Rational Field + """ + F = self.function_field() + try: + f = F(f) + except TypeError: + raise TypeError("unable to convert to an element of {}".format(F)) + + V, fr_V, to_V = F.vector_space() + W = self._ambient_space + if not W.coordinate_vector(to_V(f)) in self._module: + raise TypeError("{} is not an element of {}".format(f, self)) + + return f + + def ideal_with_gens_over_base(self, gens): + """ + Return the fractional ideal with basis ``gens`` over the + maximal order of the base field. + + It is not checked that ``gens`` really generates an ideal. + + INPUT: + + - ``gens`` -- list of elements that are a basis for the ideal over the + maximal order of the base field + + EXAMPLES: + + We construct an ideal in a rational function field:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: I = O.ideal([y]); I + Ideal (y) of Maximal order of Rational function field in y over Rational Field + sage: I*I + Ideal (y^2) of Maximal order of Rational function field in y over Rational Field + + We construct some ideals in a nontrivial function field:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order(); O + Order in Function field in y defined by y^2 + 6*x^3 + 6 + sage: I = O.ideal_with_gens_over_base([1, y]); I + Ideal (1, y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 + sage: I.module() + Free module of degree 2 and rank 2 over Maximal order of Rational function field in x over Finite Field of size 7 + Echelon basis matrix: + [1 0] + [0 1] + + There is no check if the resulting object is really an ideal:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal_with_gens_over_base([y]); I + Ideal (y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 + sage: y in I + True + sage: y^2 in I + False + """ + F = self.function_field() + S = F.base_field().maximal_order_infinite() + + gens = [F(a) for a in gens] + + V, from_V, to_V = F.vector_space() + M = V.span([to_V(b) for b in gens], base_ring=S) # not work + + return self.ideal_monoid().element_class(self, M) + + def ideal(self, *gens): + """ + Return the fractional ideal generated by the elements in ``gens``. + + INPUT: + + - ``gens`` -- list of generators or an ideal in a ring which coerces + to this order + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: O.ideal(y) + Ideal (y) of Maximal order of Rational function field in y over Rational Field + sage: O.ideal([y,1/y]) == O.ideal(y,1/y) # multiple generators may be given as a list + True + + A fractional ideal of a nontrivial extension:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: O = K.maximal_order_infinite() + sage: I = O.ideal(x^2-4) + sage: L. = K.extension(y^2 - x^3 - 1) + sage: S = L.order_infinite_with_basis([1, 1/x^2*y]) + """ + if len(gens) == 1: + gens = gens[0] + if not isinstance(gens, (list, tuple)): + if isinstance(gens, FunctionFieldIdeal): + gens = gens.gens() + else: + gens = [gens] + K = self.function_field() + + return self.ideal_with_gens_over_base([b*K(g) for b in self.basis() for g in gens]) + + def polynomial(self): + """ + Return the defining polynomial of the function field of which this is an order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.equation_order() + sage: O.polynomial() + y^4 + x*y + 4*x + 1 + """ + return self._field.polynomial() + + def basis(self): + """ + Return a basis of this order over the maximal order of the base field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.equation_order() + sage: O.basis() + (1, y, y^2, y^3) + """ + return self._basis + + def free_module(self): + """ + Return the free module formed by the basis over the maximal order of + the base field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.equation_order() + sage: O.free_module() + Free module of degree 4 and rank 4 over Maximal order of Rational + function field in x over Finite Field of size 7 + Echelon basis matrix: + [1 0 0 0] + [0 1 0 0] + [0 0 1 0] + [0 0 0 1] + """ + return self._module + +class FunctionFieldMaximalOrder(UniqueRepresentation, FunctionFieldOrder): + """ + Base class of maximal orders of function fields. + """ + def _repr_(self): + """ + Return the string representation of the order. + + EXAMPLES:: + + sage: FunctionField(QQ,'y').maximal_order()._repr_() + 'Maximal order of Rational function field in y over Rational Field' + """ + return "Maximal order of %s"%(self.function_field(),) + +class FunctionFieldMaximalOrder_rational(FunctionFieldMaximalOrder): + """ + Maximal orders of rational function fields. + + INPUT: + + - ``field`` -- a function field + + EXAMPLES:: + + sage: K. = FunctionField(GF(19)); K + Rational function field in t over Finite Field of size 19 + sage: R = K.maximal_order(); R + Maximal order of Rational function field in t over Finite Field of size 19 + """ + def __init__(self, field): + """ + Initialize. + + TESTS:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: TestSuite(O).run(skip='_test_gcd_vs_xgcd') + """ + FunctionFieldMaximalOrder.__init__(self, field, ideal_class=FunctionFieldIdeal_rational, + category=EuclideanDomains()) + + self._populate_coercion_lists_(coerce_list=[field._ring]) + + self._ring = field._ring + self._gen = self(self._ring.gen()) + self._basis = (self.one(),) + + def _element_constructor_(self, f): + """ + Make ``f`` a function field element of this order. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: O._element_constructor_(y) + y + sage: O._element_constructor_(1/y) + Traceback (most recent call last): + ... + TypeError: 1/y is not an element of Maximal order of Rational function field in y over Rational Field + """ + F = self.function_field() + try: + f = F(f) + except TypeError: + raise TypeError("unable to convert to an element of {}".format(F)) + + if not f.denominator() in self.function_field().constant_base_field(): + raise TypeError("%r is not an element of %r"%(f,self)) + + return f + + def ideal_with_gens_over_base(self, gens): + """ + Return the fractional ideal with generators ``gens``. + + INPUT: + + - ``gens`` -- elements of the function field + + EXAMPLES:: + + sage: K. = FunctionField(QQ); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: O.ideal_with_gens_over_base([x^3+1,-y]) + Ideal (x^3 + 1, -y) of Order in Function field in y defined by y^2 - x^3 - 1 + """ + return self.ideal(gens) + + def _residue_field(self, ideal, name=None): + """ + Return a field isomorphic to the residue field at the prime ideal. + + INPUT: + + - ``ideal`` -- prime ideal of the order + + - ``name`` -- string; name of the generator of the residue field + + OUTPUT: + + - a field isomorphic to the residue field + + - an isomorphism from the finite field to the residue field + + - the inverse isomorphism + + The residue field is by definition `k[x]/q` where `q` is the irreducible + polynomial generating the prime ideal and `k` is the constant base field. + + EXAMPLES:: + + sage: F. = FunctionField(GF(2)) + sage: O = F.maximal_order() + sage: I = O.ideal(x^2+x+1) + sage: R, fr_R, to_R = O._residue_field(I) + sage: R + Finite Field in z2 of size 2^2 + sage: [to_R(fr_R(e)) == e for e in R] + [True, True, True, True] + sage: to_R(x*(x+1)) == to_R(x) * to_R(x+1) + True + """ + F = self.function_field() + + if not F.is_global(): + raise NotImplementedError + + q = ideal.gen().element().numerator() + R, _from_R, _to_R = self._residue_field_global(q, name=name) + + def from_R(e): + return F(_from_R(e)) + + def to_R(f): + return _to_R(f.numerator()) + + return R, from_R, to_R + + def _residue_field_global(self, q, name=None): + """ + Return a finite field isomorphic to the residue field at q. + + This method assumes a global rational function field, that is, + the constant base field is a finite field. + + INPUT: + + - ``q`` -- irreducible polynomial + + - ``name`` -- string; name of the generator of the extension field + + OUTPUT: + + - a finite field + + - a function that outputs a polynomial lifting a finite field element + + - a function that outputs a finite field element for a polynomial + + The residue field is by definition `k[x]/q` where `k` is the base field. + + EXAMPLES:: + + sage: k. = GF(4) + sage: F. = FunctionField(k) + sage: O = F.maximal_order() + sage: O._ring + Univariate Polynomial Ring in x over Finite Field in a of size 2^2 + sage: f = x^3 + x + 1 + sage: _f = f.numerator() + sage: _f.is_irreducible() + True + sage: K, fr_K, to_K = O._residue_field_global(_f) + sage: K + Finite Field in z6 of size 2^6 + sage: all(to_K(fr_K(e)) == e for e in K) + True + + sage: k. = GF(2) + sage: F. = FunctionField(k) + sage: O = F.maximal_order() + sage: O._ring + Univariate Polynomial Ring in x over Finite Field of size 2 (using GF2X) + sage: f = x^3 + x + 1 + sage: _f = f.numerator() + sage: _f.is_irreducible() + True + sage: K, fr_K, to_K = O._residue_field_global(_f) + sage: all(to_K(fr_K(e)) == e for e in K) + True + + """ + # polynomial ring over the base field + R = self._ring + + # base field of extension degree r over the prime field + k = R.base_ring() + a = k.gen() + r = k.degree() + + # extend the base field to a field of degree r*s over the + # prime field + s = q.degree() + K,sigma = k.extension(s, map=True, name=name) + + # find a root beta in K satisfying the irreducible q + S = K['X'] + beta = S([sigma(c) for c in q.list()]).roots()[0][0] + + # V is a vector space over the prime subfield of k of degree r*s + V = K.vector_space() + + w = K.one() + beta_pow = [] + for i in range(s): + beta_pow.append(w) + w *= beta + + w = K.one() + sigma_a = sigma(a) + sigma_a_pow = [] + for i in range(r): + sigma_a_pow.append(w) + w *= sigma_a + + basis = [V(sap * bp) for bp in beta_pow for sap in sigma_a_pow] + W = V.span_of_basis(basis) + + def to_K(f): + coeffs = (f % q).list() + return sum((sigma(c) * beta_pow[i] for i, c in enumerate(coeffs)), K.zero()) + + if r == 1: # take care of the prime field case + def fr_K(g): + co = W.coordinates(V(g), check=False) + return R([k(co[j]) for j in range(s)]) + else: + def fr_K(g): + co = W.coordinates(V(g), check=False) + return R([k(co[i:i+r]) for i in range(0, r*s, r)]) + + return K, fr_K, to_K + + def basis(self): + """ + Return the basis (=1) of the order as a module over the polynomial ring. + + EXAMPLES:: + + sage: K. = FunctionField(GF(19)) + sage: O = K.maximal_order() + sage: O.basis() + (1,) + """ + return self._basis + + def gen(self, n=0): + """ + Return the ``n``-th generator of the order. Since there is only one generator ``n`` must be 0. + + EXAMPLES:: + + sage: O = FunctionField(QQ,'y').maximal_order() + sage: O.gen() + y + sage: O.gen(1) + Traceback (most recent call last): + ... + IndexError: there is only one generator + """ + if n != 0: + raise IndexError("there is only one generator") + return self._gen + + def ngens(self): + """ + Return 1 the number of generators of the order. + + EXAMPLES:: + + sage: FunctionField(QQ,'y').maximal_order().ngens() + 1 + """ + return 1 + + def ideal(self, *gens): + """ + Return the fractional ideal generated by ``gens``. + + INPUT: + + - ``gens`` -- elements of the function field + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: O.ideal(x) + Ideal (x) of Maximal order of Rational function field in x over Rational Field + sage: O.ideal([x,1/x]) == O.ideal(x,1/x) # multiple generators may be given as a list + True + sage: O.ideal(x^3+1,x^3+6) + Ideal (1) of Maximal order of Rational function field in x over Rational Field + sage: I = O.ideal((x^2+1)*(x^3+1),(x^3+6)*(x^2+1)); I + Ideal (x^2 + 1) of Maximal order of Rational function field in x over Rational Field + sage: O.ideal(I) + Ideal (x^2 + 1) of Maximal order of Rational function field in x over Rational Field + """ + if len(gens) == 1: + gens = gens[0] + if not isinstance(gens, (list, tuple)): + if isinstance(gens, FunctionFieldIdeal): + gens = gens.gens() + else: + gens = (gens,) + K = self.function_field() + gens = [K(e) for e in gens if e != 0] + if len(gens) == 0: + gen = K(0) + else: + d = lcm([c.denominator() for c in gens]).monic() + g = gcd([(d*c).numerator() for c in gens]).monic() + gen = K(g/d) + + return self.ideal_monoid().element_class(self, gen) + +class FunctionFieldMaximalOrder_global(FunctionFieldMaximalOrder): + """ + Maximal orders of global function fields. + + INPUT: + + - ``field`` -- function field to which this maximal order belongs + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: L.maximal_order() + Maximal order of Function field in y defined by y^4 + x*y + 4*x + 1 + """ + def __init__(self, field): + """ + Initialize. + + TESTS:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.maximal_order() + sage: TestSuite(O).run() + """ + FunctionFieldMaximalOrder.__init__(self, field, ideal_class=FunctionFieldIdeal_global) + + from .function_field import FunctionField_global_integral + if isinstance(field, FunctionField_global_integral): + basis = field._maximal_order_basis() + else: + model, from_model, to_model = field.monic_integral_model('z') + basis = [from_model(g) for g in model._maximal_order_basis()] + + V, fr, to = field.vector_space() + R = field.base_field().maximal_order() + + # This module is over R, but linear algebra over R (MaximalOrder) + # is not well supported in Sage. So we keep it as a vector space + # over rational function field. + self._module = V.span_of_basis([to(b) for b in basis]) + self._module_base_ring = R + self._basis = tuple(basis) + self._from_module = fr + self._to_module = to + + # multiplication table (lower triangular) + n = len(basis) + self._mtable = [] + for i in range(n): + row = [] + for j in range(n): + row.append(self._coordinate_vector(basis[i] * basis[j])) + self._mtable.append(row) + + zero = vector(R._ring,n*[0]) + def mul_vecs(f,g): + s = zero + for i in range(n): + if f[i].is_zero(): + continue + for j in range(n): + if g[j].is_zero(): + continue + s += f[i] * g[j] * self._mtable[i][j] + return s + self._mul_vecs = mul_vecs + + # We prepare for using Kummer's theorem to decompose primes. Note + # that Kummer's theorem applies to the most of places. Here we find + # places for which the theorem does not apply. + + # this element is integral over k[x] and a generator of the field. + for gen in basis[1:]: + phi = gen.minimal_polynomial() + if phi.degree() == n: + break + + if phi.degree() == n: + gen_vec = self._coordinate_vector(gen) + g = gen_vec.parent().gen(0) # x + gen_vec_pow = [g] + for i in range(n): + g = mul_vecs(g, gen_vec) + gen_vec_pow.append(g) + + # find places where {1,gen,...,gen^(n-1)} is not integral basis + W = V.span_of_basis([to(gen ** i) for i in range(phi.degree())]) + + supp = [] + for g in basis: + for c in W.coordinate_vector(to(g), check=False): + if not c.is_zero(): + supp += [f for f,_ in c.denominator().factor()] + supp = set(supp) + + self._kummer_gen = gen + self._kummer_gen_vec_pow = gen_vec_pow + self._kummer_polynomial = phi + self._kummer_places = supp + + def _element_constructor_(self, f): + """ + Construct an element of this order from ``f``. + + INPUT: + + - ``f`` -- element convertible to the function field + + EXAMPLES:: + + sage: K. = FunctionField(GF(4)); _. = K[] + sage: L. = K.extension(Y^2-x*Y+x^2+1) + sage: O = L.maximal_order() + sage: y in O + True + sage: 1/y in O + False + sage: x in O + True + sage: 1/x in O + False + sage: L.=K.extension(Y^2+Y+x+1/x) + sage: O = L.maximal_order() + sage: 1 in O + True + sage: y in O + False + sage: x*y in O + True + sage: x^2*y in O + True + """ + F = self.function_field() + f = F(f) + # check if f is in this order + if not all(e in self._module_base_ring for e in self.coordinate_vector(f)): + raise TypeError( "{} is not an element of {}".format(f, self) ) + + return f + + def ideal_with_gens_over_base(self, gens): + """ + Return the fractional ideal with basis ``gens`` over the + maximal order of the base field. + + INPUT: + + - ``gens`` -- list of elements that generates the ideal over the + maximal order of the base field + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.maximal_order(); O + Maximal order of Function field in y defined by y^2 + 6*x^3 + 6 + sage: I = O.ideal_with_gens_over_base([1, y]); I + Ideal (1) of Maximal order of Function field in y defined by y^2 + 6*x^3 + 6 + sage: I.module() + Free module of degree 2 and rank 2 over Maximal order of Rational function field in x over Finite Field of size 7 + Echelon basis matrix: + [1 0] + [0 1] + + There is no check if the resulting object is really an ideal:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.equation_order() + sage: I = O.ideal_with_gens_over_base([y]); I + Ideal (y) of Order in Function field in y defined by y^2 + 6*x^3 + 6 + sage: y in I + True + sage: y^2 in I + False + """ + return self._ideal_from_vectors([self.coordinate_vector(g) for g in gens]) + + def _ideal_from_vectors(self, vecs): + """ + Return an ideal generated as a module by vectors over rational function + field. + + INPUT: + + - ``vec`` -- list of vectors + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.maximal_order() + sage: v1 = O.coordinate_vector(x^3+1) + sage: v2 = O.coordinate_vector(y) + sage: v1 + (x^3 + 1, 0) + sage: v2 + (0, 1) + sage: O._ideal_from_vectors([v1,v2]) + Ideal (x^3 + 1, y) of Maximal order of Function field in y + defined by y^2 + 6*x^3 + 6 + """ + d = lcm([v.denominator() for v in vecs]) + vecs = [[(d*c).numerator() for c in v] for v in vecs] + return self._ideal_from_vectors_and_denominator(vecs, d, check=False) + + def _ideal_from_vectors_and_denominator(self, vecs, d=1, check=True): + """ + Return an ideal generated as a module by vectors divided by ``d`` over + the polynomial ring underlying the rational function field. + + INPUT: + + - ``vec`` -- list of vectors over the polynomial ring + + - ``d`` -- (default: 1) a nonzero element of the polynomial ring + + - ``check`` -- boolean (default: ``True``); if ``True``, compute the real + denominator of the vectors, possibly different from ``d``. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^2 - x^3 - 1) + sage: O = L.maximal_order() + sage: I = O.ideal(y^2) + sage: m = I.basis_matrix() + sage: v1 = m[0] + sage: v2 = m[1] + sage: v1 + (x^3 + 1, 0) + sage: v2 + (0, x^3 + 1) + sage: O._ideal_from_vectors([v1,v2]) # indirect doctest + Ideal (x^3 + 1) of Maximal order of Function field in y + defined by y^2 + 6*x^3 + 6 + """ + R = self._module_base_ring._ring + + d = R(d) # make it sure that d is in the polynomial ring + + if check and not d.is_one(): # check if d is true denominator + M = [] + g = d + for v in vecs: + for c in v: + g = g.gcd(c) + if g.is_one(): + break + else: + M += list(v) + continue # for v in vecs + mat = matrix(R, vecs) + break + else: + d = d // g + mat = matrix(R, len(vecs), [c // g for c in M]) + else: + mat = matrix(R, vecs) + + # IMPORTANT: make it sure that pivot polynomials monic + # so that we get a unique hnf. Here the hermite form + # algorithm also makes the pivots monic. + + # compute the reverse hermite form with zero rows deleted + mat.reverse_rows_and_columns() + mat._hermite_form_euclidean(normalization=lambda p: ~p.lc()) + mat.reverse_rows_and_columns() + i = 0 + while i < mat.nrows() and mat.row(i).is_zero(): + i += 1 + hnf = mat[i:] # remove zero rows + + return self.ideal_monoid().element_class(self, hnf, d) + + def ideal(self, *gens, **kwargs): + """ + Return the fractional ideal generated by the elements in ``gens``. + + INPUT: + + - ``gens`` -- list of generators + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: O = K.maximal_order() + sage: I = O.ideal(x^2-4) + sage: L. = K.extension(y^2 - x^3 - 1) + sage: S = L.maximal_order() + sage: S.ideal(1/y) + Ideal (1, (1/(x^3 + 1))*y) of Maximal order of Function field + in y defined by y^2 + 6*x^3 + 6 + sage: I2 = S.ideal(x^2-4); I2 + Ideal (x^2 + 3) of Maximal order of Function field in y defined by y^2 + 6*x^3 + 6 + sage: I2 == S.ideal(I) + True + """ + if len(gens) == 1: + gens = gens[0] + if not isinstance(gens, (list, tuple)): + if isinstance(gens, FunctionFieldIdeal): + gens = gens.gens() + else: + gens = (gens,) + F = self.function_field() + mgens = [b*F(g) for g in gens for b in self.basis()] + return self.ideal_with_gens_over_base(mgens) + + def polynomial(self): + """ + Return the defining polynomial of the function field of which this is an order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.equation_order() + sage: O.polynomial() + y^4 + x*y + 4*x + 1 + """ + return self._field.polynomial() + + def basis(self): + """ + Return a basis of the order over the maximal order of the base function + field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.equation_order() + sage: O.basis() + (1, y, y^2, y^3) + """ + return self._basis + + def gen(self, n=0): + """ + Return the ``n``-th generator of the order. + + The basis elements of the order are generators. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: O = L.maximal_order() + sage: O.gen() + 1 + sage: O.gen(1) + y + sage: O.gen(2) + (1/(x^3 + x^2 + x))*y^2 + sage: O.gen(3) + Traceback (most recent call last): + ... + IndexError: there are only 3 generators + """ + if not ( n >= 0 and n < self.ngens() ): + raise IndexError("there are only {} generators".format(self.ngens())) + + return self._basis[n] + + def ngens(self): + """ + Return the number of generators of the order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: Oinf = L.maximal_order() + sage: Oinf.ngens() + 3 + """ + return len(self._basis) + + def free_module(self): + """ + Return the free module formed by the basis over the maximal order of the base field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.maximal_order() + sage: O.free_module() + Free module of degree 4 and rank 4 over Maximal order of Rational function field in x over Finite Field of size 7 + User basis matrix: + [1 0 0 0] + [0 1 0 0] + [0 0 1 0] + [0 0 0 1] + """ + return self._module.change_ring(self._module_base_ring) + + def coordinate_vector(self, e): + """ + Return the cooridinates of ``e`` with respect to the basis of this order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.maximal_order() + sage: O.coordinate_vector(y) + (0, 1, 0, 0) + sage: O.coordinate_vector(x*y) + (0, x, 0, 0) + """ + return self._module.coordinate_vector(self._to_module(e)) + + def _coordinate_vector(self, e): + """ + Return the cooridinate vector of ``e`` with respect to the basis + of the order. + + Assuming ``e`` is in the maximal order, the coordinates are given + as univariate polynomials in the underlying ring of the maximal + order of the rational function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.maximal_order() + sage: O._coordinate_vector(y) + (0, 1, 0, 0) + sage: O._coordinate_vector(x*y) + (0, x, 0, 0) + """ + v = self._module.coordinate_vector(self._to_module(e), check=False) + return vector([c.numerator() for c in v]) + + @cached_method + def different(self): + """ + Return the different ideal of the function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.maximal_order() + sage: O.different() + Ideal (x^4 + 4*x^3 + 3*x^2 + 6*x + 4, y + 2*x^3 + x^2 + 6*x + 1) + of Maximal order of Function field in y defined by y^4 + x*y + 4*x + 1 + """ + return ~self.codifferent() + + @cached_method + def codifferent(self): + """ + Return the codifferent ideal of the function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.maximal_order() + sage: O.codifferent() + Ideal (1, (1/(x^4 + 4*x^3 + 3*x^2 + 6*x + 4))*y^3 + + ((5*x^3 + 6*x^2 + x + 6)/(x^4 + 4*x^3 + 3*x^2 + 6*x + 4))*y^2 + + ((x^3 + 2*x^2 + 2*x + 2)/(x^4 + 4*x^3 + 3*x^2 + 6*x + 4))*y + + 6*x/(x^4 + 4*x^3 + 3*x^2 + 6*x + 4)) of Maximal order of Function field + in y defined by y^4 + x*y + 4*x + 1 + """ + T = self._codifferent_matrix() + return self._ideal_from_vectors(T.inverse().columns()) + + @cached_method + def _codifferent_matrix(self): + """ + Return the matrix `T` defined in Proposition 4.8.19 of [Coh1993]_. + + EXAMPLES:: + + sage: K. = FunctionField(GF(7)); R. = K[] + sage: L. = K.extension(y^4 + x*y + 4*x + 1) + sage: O = L.maximal_order() + sage: O._codifferent_matrix() + [ 4 0 0 4*x] + [ 0 0 4*x 5*x + 3] + [ 0 4*x 5*x + 3 0] + [ 4*x 5*x + 3 0 3*x^2] + """ + rows = [] + for u in self.basis(): + row = [] + for v in self.basis(): + row.append((u*v).trace()) + rows.append(row) + T = matrix(rows) + return T + + @cached_method + def p_radical(self, prime): + """ + Return the ``prime``-radical of the maximal order. + + INPUT: + + - ``prime`` -- prime ideal of the maximal order of the base + rational function field + + The algorithm is outlined in Section 6.1.3 of [Coh1993]_. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(t^3 - x^2 * (x^2 + x + 1)^2) + sage: o = K.maximal_order() + sage: O = F.maximal_order() + sage: p = o.ideal(x+1) + sage: O.p_radical(p) + Ideal (x + 1) of Maximal order of Function field in y + defined by y^3 + x^6 + x^4 + x^2 + """ + g = prime.gens()[0] + + if not (g.denominator() == 1 and g.numerator().is_irreducible()): + raise ValueError('not a prime ideal') + + F = self.function_field() + n = F.degree() + o = prime.ring() + p = g.numerator() + + # Fp is isomorphic to the residue field o/p + Fp, fr_Fp, to_Fp = o._residue_field_global(p) + + # exp = q^j should be at least extension degree where q is + # the order of the residue field o/p + q = F.constant_base_field().order()**p.degree() + exp = q + while exp <= F.degree(): + exp = exp**q + + # radical equals to the kernel of the map x |-> x^exp + mat = [] + for g in self.basis(): + v = [to_Fp(c) for c in self._coordinate_vector(g**exp)] + mat.append(v) + mat = matrix(Fp,mat) + ker = mat.kernel() + + # construct module generators of the p-radical + vecs = [] + for i in range(n): + v = vector([p if j == i else 0 for j in range(n)]) + vecs.append(v) + for b in ker.basis(): + v = vector([fr_Fp(c) for c in b]) + vecs.append(v) + + return self._ideal_from_vectors(vecs) + + @cached_method + def decomposition(self, ideal): + """ + Return the decomposition of the prime ideal. + + INPUT: + + - ``ideal`` -- prime ideal of the base maximal order + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); R. = K[] + sage: F. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: o = K.maximal_order() + sage: O = F.maximal_order() + sage: p = o.ideal(x+1) + sage: O.decomposition(p) + [(Ideal (x + 1, y + 1) of Maximal order + of Function field in y defined by y^3 + x^6 + x^4 + x^2, 1, 1), + (Ideal (x + 1, y^2 + y + 1) of Maximal order + of Function field in y defined by y^3 + x^6 + x^4 + x^2, 2, 1)] + """ + F = self.function_field() + n = F.degree() + + p = ideal.gen().numerator() + o = ideal.ring() + + # Fp is isomorphic to the residue field o/p + Fp, fr, to = o._residue_field_global(p) + P,X = Fp['X'].objgen() + + V = Fp**n # Ob = O/pO + + mtable = [] + for i in range(n): + row = [] + for j in range(n): + row.append( V([to(e) for e in self._mtable[i][j]]) ) + mtable.append(row) + + if not p in self._kummer_places: + ##################################### + # Decomposition by Kummer's theorem # + ##################################### + # gen is self._kummer_gen + gen_vec_pow = self._kummer_gen_vec_pow + mul_vecs = self._mul_vecs + + f = self._kummer_polynomial + fp = P([to(c.numerator()) for c in f.list()]) + decomposition = [] + for q, exp in fp.factor(): + # construct O.ideal([p,q(gen)]) + gen_vecs = list(matrix.diagonal(n * [p])) + c = q.list() + + # q(gen) in vector form + qgen = sum(fr(c[i]) * gen_vec_pow[i] for i in range(len(c))) + + I = matrix.identity(o._ring, n) + for i in range(n): + gen_vecs.append(mul_vecs(qgen,I[i])) + prime = self._ideal_from_vectors_and_denominator(gen_vecs) + + # Compute an element beta in O but not in pO. How to find beta + # is explained in Section 4.8.3 of [Coh1993]. We keep beta + # as a vector over k[x] with respect to the basis of O. + + # p and qgen generates the prime; modulo pO, qgenb generates the prime + qgenb = [to(qgen[i]) for i in range(n)] + m =[] + for i in range(n): + m.append(sum(qgenb[j] * mtable[i][j] for j in range(n))) + beta = [fr(c) for c in matrix(m).left_kernel().basis()[0]] + + prime.is_prime.set_cache(True) + prime._prime_below = ideal + prime._relative_degree = q.degree() + prime._ramification_index = exp + prime._beta = beta + + prime._kummer_form = (p, qgen) + + decomposition.append((prime, q.degree(), exp)) + else: + ############################# + # Buchman-Lenstra algorithm # + ############################# + pO = self.ideal(p) + Ip = self.p_radical(ideal) + Ob = matrix.identity(Fp, n) + + def bar(I): # transfer to O/pO + m = [] + for v in I._hnf: + m.append([to(e) for e in v]) + h = matrix(m).echelon_form() + return cut_last_zero_rows(h) + + def liftb(Ib): + m = [vector([fr(e) for e in v]) for v in Ib] + m += [v for v in pO._hnf] + return self._ideal_from_vectors_and_denominator(m,1) + + def cut_last_zero_rows(h): + i = h.nrows() + while i > 0 and h.row(i-1).is_zero(): + i -= 1 + return h[:i] + + def mul_vec(v1,v2): + s = 0 + for i in range(n): + for j in range(n): + s += v1[i] * v2[j] * mtable[i][j] + return s + + def pow(v, r): # r > 0 + m = v + while r > 1: + m = mul_vec(m,v) + r -= 1 + return m + + # Algorithm 6.2.7 of [Coh1993] + def div(Ib, Jb): + # compute a basis of Jb/Ib + sJb = Jb.row_space() + sIb = Ib.row_space() + sJbsIb,proj_sJbsIb,lift_sJbsIb = sJb.quotient_abstract(sIb) + supplement_basis = [lift_sJbsIb(v) for v in sJbsIb.basis()] + + m = [] + for b in V.gens(): # basis of Ob = O/pO + b_row = [] # row vector representation of the map a -> a*b + for a in supplement_basis: + b_row += lift_sJbsIb(proj_sJbsIb( mul_vec(a,b) )) + m.append(b_row) + return matrix(Fp,n,m).left_kernel().basis_matrix() + + # Algorithm 6.2.5 of [Coh1993] + def mul(Ib, Jb): + m = [] + for v1 in Ib: + for v2 in Jb: + m.append(mul_vec(v1,v2)) + h = matrix(m).echelon_form() + return cut_last_zero_rows(h) + + def add(Ib,Jb): + m = block_matrix([[Ib], [Jb]]) + h = m.echelon_form() + return cut_last_zero_rows(h) + + # K_1, K_2, ... + Lb = IpOb = bar(Ip+pO) + Kb = [Lb] + while not Lb.is_zero(): + Lb = mul(Lb,IpOb) + Kb.append(Lb) + + # J_1, J_2, ... + Jb =[Kb[0]] + [div(Kb[j],Kb[j-1]) for j in range(1,len(Kb))] + + # H_1, H_2, ... + Hb = [div(Jb[j],Jb[j+1]) for j in range(len(Jb)-1)] + [Jb[-1]] + + q = Fp.order() + + def split(h): + # VsW represents O/H as a vector space + W = h.row_space() # H/pO + VsW,to_VsW,lift_to_V = V.quotient_abstract(W) + + # compute the space K of elements in O/H that satisfy a^q-a=0 + l = [lift_to_V(b) for b in VsW.basis()] + + images = [to_VsW(pow(x, q) - x) for x in l] + K = VsW.hom(images, VsW).kernel() + + if K.dimension() == 0: + return [] + if K.dimension() == 1: # h is prime + return [(liftb(h),VsW.dimension())] # relative degree + + # choose a such that a^q - a is 0 but a is not in Fp + for a in K.basis(): + # IMPORTANT: This criterion is based on the assumption + # that O.basis() starts with 1. + if a.support() != [0]: + break + else: + raise AssertionError("no appropriate value found") + + a = lift_to_V(a) + # compute the minimal polynomial of a + m = [to_VsW(Ob[0])] # 1 in VsW + apow = a + while True: + v = to_VsW(apow) + try: + sol = matrix(m).solve_left(v) + except ValueError: + m.append(v) + apow = mul_vec(apow, a) + continue + break + + minpol = X**len(sol) - P(list(sol)) + + # The minimal polynomial of a has only linear factors and at least two + # of them. We set f to the first factor and g to the product of the rest. + fac = minpol.factor() + f = fac[0][0] + g = (fac/f).expand() + d,u,v = f.xgcd(g) + + assert d == 1, "Not relatively prime {} and {}".format(f,g) + + # finally, idempotent! + e = lift_to_V(sum([c1*c2 for c1,c2 in zip(u*f,m)])) + + h1 = add(h, matrix([mul_vec(e,Ob[i]) for i in range(n)])) + h2 = add(h, matrix([mul_vec(Ob[0]-e,Ob[i]) for i in range(n)])) + + return split(h1) + split(h2) + + decomposition = [] + for i in range(len(Hb)): + index = i + 1 # Hb starts with H_1 + for prime, degree in split(Hb[i]): + # Compute an element beta in O but not in pO. How to find beta + # is explained in Section 4.8.3 of [Coh1993]. We keep beta + # as a vector over k[x] with respect to the basis of O. + m =[] + for i in range(n): + r = [] + for g in prime._hnf: + r += sum(to(g[j]) * mtable[i][j] for j in range(n)) + m.append(r) + beta = [fr(e) for e in matrix(m).left_kernel().basis()[0]] + + prime.is_prime.set_cache(True) + prime._prime_below = ideal + prime._relative_degree = degree + prime._ramification_index = index + prime._beta = beta + + decomposition.append((prime, degree, index)) + + return decomposition + +class FunctionFieldMaximalOrderInfinite(FunctionFieldMaximalOrder, FunctionFieldOrderInfinite): + """ + Base class of maximal infinite orders of function fields. + """ + def _repr_(self): + """ + EXAMPLES:: + + sage: FunctionField(QQ,'y').maximal_order_infinite() + Maximal infinite order of Rational function field in y over Rational Field + + sage: K. = FunctionField(GF(2)); R. = PolynomialRing(K) + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: F.maximal_order_infinite() + Maximal infinite order of Function field in y defined by y^3 + x^6 + x^4 + x^2 + """ + return "Maximal infinite order of %s"%(self.function_field(),) + +class FunctionFieldMaximalOrderInfinite_rational(FunctionFieldMaximalOrderInfinite): + """ + Maximal infinite orders of rational function fields. + + INPUT: + + - ``field`` -- a rational function field + + EXAMPLES:: + + sage: K. = FunctionField(GF(19)); K + Rational function field in t over Finite Field of size 19 + sage: R = K.maximal_order_infinite(); R + Maximal infinite order of Rational function field in t over Finite Field of size 19 + """ + def __init__(self, field, category=None): + """ + Initialize. + + TESTS:: + + sage: K. = FunctionField(GF(19)) + sage: O = K.maximal_order_infinite() + sage: TestSuite(O).run(skip='_test_gcd_vs_xgcd') + """ + FunctionFieldOrderInfinite.__init__(self, field, ideal_class=FunctionFieldIdealInfinite_rational, + category=PrincipalIdealDomains().or_subcategory(category)) + self._populate_coercion_lists_(coerce_list=[field.constant_base_field()]) + + def _element_constructor_(self, f): + """ + Make ``f`` an element of this order. + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order() + sage: O._element_constructor_(y) + y + sage: O._element_constructor_(1/y) + Traceback (most recent call last): + ... + TypeError: 1/y is not an element of Maximal order of Rational function field in y over Rational Field + """ + F = self.function_field() + try: + f = F(f) + except TypeError: + raise TypeError("unable to convert to an element of {}".format(F)) + + if f.denominator().degree() < f.numerator().degree(): + raise TypeError("{} is not an element of {}".format(f, self)) + + return f + + def basis(self): + """ + Return the basis (=1) of the order as a module over the polynomial ring. + + EXAMPLES:: + + sage: K. = FunctionField(GF(19)) + sage: O = K.maximal_order() + sage: O.basis() + (1,) + """ + return 1/self.function_field().gen() + + def gen(self, n=0): + """ + Return the ``n``-th generator of self. Since there is only one generator ``n`` must be 0. + + EXAMPLES:: + + sage: O = FunctionField(QQ,'y').maximal_order() + sage: O.gen() + y + sage: O.gen(1) + Traceback (most recent call last): + ... + IndexError: there is only one generator + """ + if n != 0: + raise IndexError("there is only one generator") + return self._gen + + def ngens(self): + """ + Return 1 the number of generators of the order. + + EXAMPLES:: + + sage: FunctionField(QQ,'y').maximal_order().ngens() + 1 + """ + return 1 + + def prime_ideal(self): + """ + Return the unique prime ideal of the order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(19)) + sage: O = K.maximal_order_infinite() + sage: O.prime_ideal() + Ideal (1/t) of Maximal infinite order of Rational function field in t + over Finite Field of size 19 + """ + return self.ideal( 1/self.function_field().gen() ) + + def ideal(self, *gens): + """ + Return the fractional ideal generated by ``gens``. + + INPUT: + + - ``gens`` -- elements of the function field + + EXAMPLES:: + + sage: K. = FunctionField(QQ) + sage: O = K.maximal_order_infinite() + sage: O.ideal(x) + Ideal (x) of Maximal infinite order of Rational function field in x over Rational Field + sage: O.ideal([x,1/x]) == O.ideal(x,1/x) # multiple generators may be given as a list + True + sage: O.ideal(x^3+1,x^3+6) + Ideal (x^3) of Maximal infinite order of Rational function field in x over Rational Field + sage: I = O.ideal((x^2+1)*(x^3+1),(x^3+6)*(x^2+1)); I + Ideal (x^5) of Maximal infinite order of Rational function field in x over Rational Field + sage: O.ideal(I) + Ideal (x^5) of Maximal infinite order of Rational function field in x over Rational Field + """ + if len(gens) == 1: + gens = gens[0] + if not isinstance(gens, (list, tuple)): + if isinstance(gens, FunctionFieldIdeal): + gens = gens.gens() + else: + gens = (gens,) + K = self.function_field() + gens = [K(g) for g in gens] + try: + d = max(g.numerator().degree() - g.denominator().degree() for g in gens if g != 0) + gen = K.gen() ** d + except ValueError: # all gens are zero + gen = K(0) + + return self.ideal_monoid().element_class(self, gen) + +class FunctionFieldMaximalOrderInfinite_global(FunctionFieldMaximalOrderInfinite): + """ + Maximal infinite orders of global function fields. + + INPUT: + + - ``field`` -- function field + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: F.maximal_order_infinite() + Maximal infinite order of Function field in y defined by y^3 + x^6 + x^4 + x^2 + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: L.maximal_order_infinite() + Maximal infinite order of Function field in y defined by y^2 + y + (x^2 + 1)/x + """ + def __init__(self, field, category=None): + """ + Initialize. + + TESTS:: + + sage: K. = FunctionField(GF(2)); _. = PolynomialRing(K) + sage: F. = K.extension(t^3-x^2*(x^2+x+1)^2) + sage: O = F.maximal_order_infinite() + sage: TestSuite(O).run() + """ + FunctionFieldOrderInfinite.__init__(self, field, ideal_class=FunctionFieldIdealInfinite_global) + + M, from_M, to_M = field._inversion_isomorphism() + basis = [from_M(g) for g in M.maximal_order().basis()] + + V, from_V, to_V = field.vector_space() + R = field.base_field().maximal_order_infinite() + + self._basis = tuple(basis) + self._module = V.span_of_basis([to_V(v) for v in basis]) + self._module_base_ring = R + self._to_module = to_V + + def _element_constructor_(self, f): + """ + Make ``f`` an element of this order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: Oinf.basis() + (1, 1/x*y) + sage: 1 in Oinf + True + sage: 1/x*y in Oinf + True + sage: x*y in Oinf + False + sage: 1/x in Oinf + True + """ + F = self.function_field() + + try: + f = F(f) + except TypeError: + raise TypeError("unable to convert to an elemen of {}".format(F)) + + O = F.base_field().maximal_order_infinite() + coordinates = self.coordinate_vector(f) + if not all(c in O for c in coordinates): + raise TypeError("%r is not an element of %r"%(f,self)) + + return f + + def basis(self): + """ + Return a basis of this order as a module over the maximal order + of the base function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: Oinf = L.maximal_order_infinite() + sage: Oinf.basis() + (1, 1/x^2*y, (1/(x^4 + x^3 + x^2))*y^2) + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: Oinf.basis() + (1, 1/x*y) + """ + return self._basis + + def gen(self, n=0): + """ + Return the ``n``-th generator of the order. + + The basis elements of the order are generators. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: Oinf = L.maximal_order_infinite() + sage: Oinf.gen() + 1 + sage: Oinf.gen(1) + 1/x^2*y + sage: Oinf.gen(2) + (1/(x^4 + x^3 + x^2))*y^2 + sage: Oinf.gen(3) + Traceback (most recent call last): + ... + IndexError: there are only 3 generators + """ + if not ( n >= 0 and n < self.ngens() ): + raise IndexError("there are only {} generators".format(self.ngens())) + + return self._basis[n] + + def ngens(self): + """ + Return the number of generators of the order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: Oinf = L.maximal_order_infinite() + sage: Oinf.ngens() + 3 + """ + return len(self._basis) + + def ideal(self, *gens): + """ + Return the ideal generated by ``gens``. + + INPUT: + + - ``gens`` -- tuple of elements of the function field + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: Oinf = F.maximal_order_infinite() + sage: I = Oinf.ideal(x,y); I + Ideal (x^2) of Maximal infinite order of Function field + in y defined by y^3 + x^6 + x^4 + x^2 + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(x,y); I + Ideal (x) of Maximal infinite order of Function field in y defined by y^2 + y + (x^2 + 1)/x + """ + if len(gens) == 1: + gens = gens[0] + if not type(gens) in (list,tuple): + gens = (gens,) + mgens = [g * b for g in gens for b in self._basis] + return self.ideal_with_gens_over_base(mgens) + + def ideal_with_gens_over_base(self, gens): + """ + Return the ideal generated by ``gens`` as a module. + + INPUT: + + - ``gens`` -- tuple of elements of the function field + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); R. = K[] + sage: F. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: Oinf = F.maximal_order_infinite() + sage: Oinf.ideal_with_gens_over_base((x^2, y, (1/(x^2 + x + 1))*y^2)) + Ideal (x^2) of Maximal infinite order of Function field in y + defined by y^3 + x^6 + x^4 + x^2 + """ + F = self.function_field() + iF, from_iF, to_iF = F._inversion_isomorphism() + iO = iF.maximal_order() + + ideal = iO.ideal_with_gens_over_base([to_iF(g) for g in gens]) + + if not ideal.is_zero(): + # Now the ideal does not correspond exactly to the ideal in the + # maximal infinite order through the inversion isomorphism. The + # reason is that the ideal also has factors not lying over x. + # The following procedure removes the spurious factors. The idea + # is that for an integral ideal I, J_n = I + (xO)^n stabilizes + # if n is large enough, and then J_n is the I with the spurious + # factors removed. For a fractional ideal, we also need to find + # the largest factor x^m that divides the denominator. + d = ideal.denominator() + h = ideal.hnf() + x = d.parent().gen() + + # find the largest factor x^m that divides the denominator + i = 0 + while d[i].is_zero(): + i += 1 + d = x ** i + + # find the largest n such that I + (xO)^n stabilizes + h1 = h + MS = h1.matrix_space() + k = MS.identity_matrix() + while True: + k = x * k + + h2 = block_matrix([[h],[k]]) + h2.reverse_rows_and_columns() + h2._hermite_form_euclidean(normalization=lambda p: ~p.lc()) + h2.reverse_rows_and_columns() + i = 0 + while i < h2.nrows() and h2.row(i).is_zero(): + i += 1 + h2 = h2[i:] # remove zero rows + + if h2 == h1: + break + h1 = h2 + + # reconstruct ideal + ideal = iO._ideal_from_vectors_and_denominator(list(h1), d) + + return self.ideal_monoid().element_class(self, ideal) + + def _to_iF(self, I): + """ + Return the ideal in the inverted function field from ``I``. + + INPUT: + + - ``I`` -- ideal of the function field + + EXAMPLES:: + + sage: K.=FunctionField(GF(2)); _. = K[] + sage: L.=K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: I = Oinf.ideal(y) + sage: Oinf._to_iF(I) + Ideal (1, 1/x*s) of Maximal order of Function field in s + defined by s^2 + x*s + x^3 + x + """ + F = self.function_field() + iF,from_iF,to_iF = F._inversion_isomorphism() + iO = iF.maximal_order() + iI = iO.ideal_with_gens_over_base([to_iF(b) for b in I.gens_over_base()]) + return iI + + def decomposition(self): + """ + Return prime ideal decomposition of `pO_\infty` where `p` is the unique + prime ideal of the maximal infinite order of the rational function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(t^3 - x^2*(x^2 + x + 1)^2) + sage: Oinf = F.maximal_order_infinite() + sage: Oinf.decomposition() + [(Ideal (1/x,1/x^2*y + 1) of Maximal infinite order + of Function field in y defined by y^3 + x^6 + x^4 + x^2, 1, 1), + (Ideal (1/x,1/x^4*y^2 + 1/x^2*y + 1) of Maximal infinite order + of Function field in y defined by y^3 + x^6 + x^4 + x^2, 2, 1)] + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: Oinf.decomposition() + [(Ideal (1/x,1/x*y) of Maximal infinite order of Function field in y + defined by y^2 + y + (x^2 + 1)/x, 1, 2)] + """ + F = self.function_field() + iF,from_iF,to_iF = F._inversion_isomorphism() + + x = iF.base_field().gen() + iO = iF.maximal_order() + io = iF.base_field().maximal_order() + ip = io.ideal(x) + + dec = [] + for iprime, deg, exp in iO.decomposition(ip): + prime = FunctionFieldIdealInfinite_global(self, iprime) + dec.append((prime, deg, exp)) + return dec + + def different(self): + """ + Return the different ideal of the maximal infinite order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: Oinf.different() + Ideal (1/x) of Maximal infinite order of Function field in y + defined by y^2 + y + (x^2 + 1)/x + """ + T = self._codifferent_matrix() + codiff_gens = [] + for c in T.inverse().columns(): + codiff_gens.append(sum([ci*bi for ci,bi in zip(c,self.basis())])) + codiff = self.ideal_with_gens_over_base(codiff_gens) + return ~codiff + + @cached_method + def _codifferent_matrix(self): + """ + Return the codifferent matrix of the maximal infinite order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: Oinf._codifferent_matrix() + [ 0 1/x] + [ 1/x 1/x^2] + """ + rows = [] + for u in self.basis(): + row = [] + for v in self.basis(): + row.append((u*v).trace()) + rows.append(row) + T = matrix(rows) + return T + + def coordinate_vector(self, e): + """ + Return the cooridinates of ``e`` with respect to the basis of the order. + + INPUT: + + - ``e`` -- element of the function field + + The returned coordinates are in the base maximal infinite order if and only + if the element is in the order. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: L. = K.extension(Y^2 + Y + x + 1/x) + sage: Oinf = L.maximal_order_infinite() + sage: f = 1/y^2 + sage: f in Oinf + True + sage: Oinf.coordinate_vector(f) + ((x^3 + x^2 + x)/(x^4 + 1), x^3/(x^4 + 1)) + """ + return self._module.coordinate_vector(self._to_module(e)) diff --git a/src/sage/rings/invariant_theory.py b/src/sage/rings/invariant_theory.py index 5c96637ccda..d67394a90c9 100644 --- a/src/sage/rings/invariant_theory.py +++ b/src/sage/rings/invariant_theory.py @@ -93,6 +93,11 @@ REFERENCES: .. [WpInvariantTheory] :wikipedia:`Glossary_of_invariant_theory` + +AUTHORS: + +- Volker Braun (2013-01-24): initial version +- Jesper Noordsij (2018-05-18): support for binary quintics added """ #***************************************************************************** @@ -105,7 +110,6 @@ #***************************************************************************** -from sage.misc.functional import is_odd from sage.matrix.constructor import matrix from sage.structure.sage_object import SageObject from sage.structure.richcmp import richcmp_method, richcmp @@ -166,6 +170,124 @@ def _guess_variables(polynomial, *args): else: return tuple(args) +def transvectant(f, g, h=1, scale='default'): + r""" + Return the h-th transvectant of f and g. + + INPUT: + + - ``f,g`` -- two homogeneous binary forms in the same polynomial ring. + + - ``h`` -- the order of the transvectant. If it is not specified, + the first transvectant is returned. + + - ``scale`` -- the scaling factor applied to the result. Possible values + are ``'default'`` and ``'none'``. The ``'default'`` scaling factor is + the one that appears in the output statement below, if the scaling + factor is ``'none'`` the quotient of factorials is left out. + + OUTPUT: + + The h-th transvectant of the listed forms `f` and `g`: + + .. MATH:: + + (f,g)_h = \frac{(d_f-h)! \cdot (d_g-h)!}{d_f! \cdot d_g!}\left( + \left(\frac{\partial}{\partial x}\frac{\partial}{\partial z'} + - \frac{\partial}{\partial x'}\frac{\partial}{\partial z} + \right)^h \left(f(x,z) \cdot g(x',z')\right) + \right)_{(x',z')=(x,z)} + + EXAMPLES:: + + sage: from sage.rings.invariant_theory import AlgebraicForm, transvectant + sage: R. = QQ[] + sage: f = AlgebraicForm(2, 5, x^5 + 5*x^4*y + 5*x*y^4 + y^5) + sage: transvectant(f, f, 4) + Binary quadratic given by 2*x^2 - 4*x*y + 2*y^2 + sage: transvectant(f, f, 8) + Binary form of degree -6 given by 0 + + The default scaling will yield an error for fields of positive + characteristic below `d_f!` or `d_g!` as the denominator of the scaling + factor will not be invertible in that case. The scale argument ``'none'`` + can be used to compute the transvectant in this case:: + + sage: R. = GF(5)[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: f = AlgebraicForm(2, 5, p, x0, x1) + sage: transvectant(f, f, 4) + Traceback (most recent call last): + ... + ZeroDivisionError + sage: transvectant(f, f, 4, scale='none') + Binary quadratic given by -a3^2*x0^2 + a2*a4*x0^2 + a2*a3*x0*x1 + - a1*a4*x0*x1 - a2^2*x1^2 + a1*a3*x1^2 + + The additional factors that appear when ``scale='none'`` is used can be + seen if we consider the same transvectant over the rationals and compare + it to the scaled version:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: f = AlgebraicForm(2, 5, p, x0, x1) + sage: transvectant(f, f, 4) + Binary quadratic given by 3/50*a3^2*x0^2 - 4/25*a2*a4*x0^2 + + 2/5*a1*a5*x0^2 + 1/25*a2*a3*x0*x1 - 6/25*a1*a4*x0*x1 + 2*a0*a5*x0*x1 + + 3/50*a2^2*x1^2 - 4/25*a1*a3*x1^2 + 2/5*a0*a4*x1^2 + sage: transvectant(f, f, 4, scale='none') + Binary quadratic given by 864*a3^2*x0^2 - 2304*a2*a4*x0^2 + + 5760*a1*a5*x0^2 + 576*a2*a3*x0*x1 - 3456*a1*a4*x0*x1 + + 28800*a0*a5*x0*x1 + 864*a2^2*x1^2 - 2304*a1*a3*x1^2 + 5760*a0*a4*x1^2 + + If the forms are given as inhomogeneous polynomials, the homogenisation + might fail if the polynomial ring has multiple variables. You can + circumvent this by making sure the base ring of the polynomial has only + one variable:: + + sage: R. = QQ[] + sage: quintic = invariant_theory.binary_quintic(x^5+x^3+2*x^2+y^5, x) + sage: transvectant(quintic, quintic, 2) + Traceback (most recent call last): + ... + ValueError: Polynomial is not homogeneous. + sage: R. = QQ[] + sage: S. = R[] + sage: quintic = invariant_theory.binary_quintic(x^5+x^3+2*x^2+y^5, x) + sage: transvectant(quintic, quintic, 2) + Binary sextic given by 1/5*x^6 + 6/5*x^5*h + (-3/25)*x^4*h^2 + + (2*y^5 - 8/25)*x^3*h^3 + (-12/25)*x^2*h^4 + 3/5*y^5*x*h^5 + + 2/5*y^5*h^6 + """ + f = f.homogenized() + g = g.homogenized() + R = f._ring + if not g._ring is R: + raise ValueError('All input forms must be in the same polynomial ring.') + x = f._variables[0] + y = f._variables[1] + degree = f._d + g._d - 2*h + if h > f._d or h > g._d: + tv = R(0) + else: + from sage.functions.other import binomial, factorial + if scale == 'default': + scalar = factorial(f._d-h) * factorial(g._d-h) \ + * R(factorial(f._d)*factorial(g._d))**(-1) + elif scale == 'none': + scalar = 1 + else: + raise ValueError('Unknown scale type: %s' %scale) + def diff(j): + df = f.form().derivative(x,j).derivative(y,h-j) + dg = g.form().derivative(x,h-j).derivative(y,j) + return (-1)**j * binomial(h,j) * df * dg + tv = scalar * sum([diff(j) for j in range(h+1)]) + if not tv.parent() is R: + S = tv.parent() + x = S(x) + y = S(y) + return AlgebraicForm(2, degree, tv, x, y) ###################################################################### @@ -174,7 +296,7 @@ class FormsBase(SageObject): """ The common base class of :class:`AlgebraicForm` and :class:`SeveralAlgebraicForms`. - + This is an abstract base class to provide common methods. It does not make much sense to instantiate it. @@ -188,7 +310,7 @@ class FormsBase(SageObject): def __init__(self, n, homogeneous, ring, variables): """ The Python constructor. - + TESTS:: sage: from sage.rings.invariant_theory import FormsBase @@ -199,7 +321,7 @@ def __init__(self, n, homogeneous, ring, variables): self._homogeneous = homogeneous self._ring = ring self._variables = variables - + def _jacobian_determinant(self, *args): """ @@ -217,7 +339,6 @@ def _jacobian_determinant(self, *args): EXAMPLES:: - sage: R. = QQ[] sage: from sage.rings.invariant_theory import FormsBase sage: f = FormsBase(2, True, R, (x, y)) @@ -491,7 +612,7 @@ def _check_covariant(self, method_name, g=None, invariant=False): F = self._ring.base_ring() g = random_matrix(F, self._n, algorithm='unimodular') v = vector(self.variables()) - g_v = g*v + g_v = g * v transform = dict( (v[i], g_v[i]) for i in range(self._n) ) # The covariant of the transformed polynomial g_self = self.__class__(self._n, self._d, self.form().subs(transform), self.variables()) @@ -535,6 +656,11 @@ def _repr_(self): sage: quartic = invariant_theory.binary_quartic(x^4+y^4) sage: quartic._repr_() 'Binary quartic with coefficients (1, 0, 0, 0, 1)' + + sage: from sage.rings.invariant_theory import AlgebraicForm + sage: form = AlgebraicForm(2, 5, x^5 + y^5) + sage: form._repr_() + 'Binary quintic given by x^5 + y^5' """ s = '' ary = ['Unary', 'Binary', 'Ternary', 'Quaternary', 'Quinary', @@ -542,16 +668,22 @@ def _repr_(self): try: s += ary[self._n-1] except IndexError: - s += 'algebraic' - ic = ['monic', 'quadratic', 'cubic', 'quartic', 'quintic', + s += 'Algebraic' + ic = ['constant form', 'monic', 'quadratic', 'cubic', 'quartic', 'quintic', 'sextic', 'septimic', 'octavic', 'nonic', 'decimic', 'undecimic', 'duodecimic'] s += ' ' + if self._d < 0: + s += 'form of degree {}'.format(self._d) + else: + try: + s += ic[self._d] + except IndexError: + s += 'form' try: - s += ic[self._d-1] - except IndexError: - s += 'form' - s += ' with coefficients ' + str(self.coeffs()) + s += ' with coefficients ' + str(self.coeffs()) + except AttributeError: + s += ' given by ' + str(self.form()) return s @@ -575,7 +707,7 @@ def form(self): return self._polynomial polynomial = form - + def homogenized(self, var='h'): """ @@ -595,7 +727,7 @@ def homogenized(self, var='h'): sage: T. = QQ[] sage: quadratic = invariant_theory.binary_quadratic(t^2 + 2*t + 3) - sage: quadratic + sage: quadratic Binary quadratic with coefficients (1, 3, 2) sage: quadratic.homogenized() Binary quadratic with coefficients (1, 3, 2) @@ -610,6 +742,11 @@ def homogenized(self, var='h'): sage: quadratic = invariant_theory.ternary_quadratic(x^2 + 1, [x,y]) sage: quadratic.homogenized().form() x^2 + h^2 + + sage: R. = QQ[] + sage: quintic = invariant_theory.binary_quintic(x^4 + 1, x) + sage: quintic.homogenized().form() + x^4*h + h^5 """ if self._homogeneous: return self @@ -622,8 +759,11 @@ def homogenized(self, var='h'): R = PolynomialRing(self._ring.base_ring(), [str(self._ring.gen(0)), str(var)]) polynomial = R(self._polynomial).homogenize(var) variables = R.gens() + if polynomial.total_degree() < self._d: + k = self._d - polynomial.total_degree() + polynomial = polynomial * R(var)**k return self.__class__(self._n, self._d, polynomial, variables) - + def _extract_coefficients(self, monomials): """ Return the coefficients of ``monomials``. @@ -655,7 +795,7 @@ def _extract_coefficients(self, monomials): sage: m = [x^3, y^3, 1, x^2*y, x^2, x*y^2, y^2, x, y, x*y] sage: base._extract_coefficients(m) (a30, a03, a00, a21, a20, a12, a02, a10, a01, a11) - + sage: T. = QQ[] sage: univariate = AlgebraicForm(2, 3, t^3+2*t^2+3*t+4) sage: m = [t^3, 1, t, t^2] @@ -676,7 +816,7 @@ def _extract_coefficients(self, monomials): if R.ngens() == 1: # Univariate polynomials assert indices == [0] - coefficient_monomial_iter = [(c, R.gen(0)**i) for i,c in + coefficient_monomial_iter = [(c, R.gen(0)**i) for i,c in enumerate(self._polynomial.padded_list())] def index(monomial): if monomial in R.base_ring(): @@ -717,7 +857,7 @@ def coefficients(self): """ return self.coeffs() - + def transformed(self, g): r""" Return the image under a linear transformation of the variables. @@ -746,7 +886,7 @@ def transformed(self, g): True sage: g = matrix(QQ, [[1, 0, 0], [-1, 1, -3], [-5, -5, 16]]) sage: cubic.transformed(g) - Ternary cubic with coefficients (-356, -373, 12234, -1119, 3578, -1151, + Ternary cubic with coefficients (-356, -373, 12234, -1119, 3578, -1151, 3582, -11766, -11466, 7360) sage: cubic.transformed(g).transformed(g.inverse()) == cubic True @@ -756,7 +896,7 @@ def transformed(self, g): else: from sage.modules.all import vector v = vector(self._ring, self._variables) - g_v = g * v + g_v = vector(self._ring, g*v) transform = dict( (v[i], g_v[i]) for i in range(self._n) ) # The covariant of the transformed polynomial return self.__class__(self._n, self._d, @@ -979,6 +1119,7 @@ def discriminant(self): sage: quadratic.discriminant() 4*a*b*c - c*d^2 - b*e^2 + d*e*f - a*f^2 """ + from sage.misc.functional import is_odd A = 2*self._matrix_() if is_odd(self._n): return A.det() / 2 @@ -1026,7 +1167,7 @@ def dual(self): ....: a10*x*z + a01*y*z + a00*z^2 ) sage: quadratic = invariant_theory.ternary_quadratic(p, x,y,z) sage: quadratic.dual().dual().form().factor() - (1/4) * + (1/4) * (a20*x^2 + a11*x*y + a02*y^2 + a10*x*z + a01*y*z + a00*z^2) * (4*a20*a02*a00 - a20*a01^2 - a11^2*a00 + a11*a10*a01 - a02*a10^2) @@ -1391,6 +1532,779 @@ def h_covariant(self): (2*a1**3 - 3*a0*a1*a2 + a0**2*a3) * xpow[6] +###################################################################### + +class BinaryQuintic(AlgebraicForm): + """ + Invariant theory of a binary quintic form. + + You should use the :class:`invariant_theory + ` factory object to construct instances + of this class. See :meth:`~InvariantTheoryFactory.binary_quintic` + for details. + + REFERENCES: + + For a description of all invariants and covariants of a binary + quintic, see section 73 of [Cle1872]_. + + TESTS:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic._check_covariant('form') + sage: quintic._check_covariant('A_invariant', invariant=True) + sage: quintic._check_covariant('B_invariant', invariant=True) + sage: quintic._check_covariant('C_invariant', invariant=True) + sage: quintic._check_covariant('R_invariant', invariant=True) + sage: quintic._check_covariant('H_covariant') + sage: quintic._check_covariant('i_covariant') + sage: quintic._check_covariant('T_covariant') + sage: quintic._check_covariant('j_covariant') + sage: quintic._check_covariant('tau_covariant') + sage: quintic._check_covariant('theta_covariant') + sage: quintic._check_covariant('alpha_covariant') + sage: quintic._check_covariant('beta_covariant') + sage: quintic._check_covariant('gamma_covariant') + sage: quintic._check_covariant('delta_covariant') + sage: TestSuite(quintic).run() + + Testing that more general coefficient rings also work as expected:: + + sage: R. = QQ[] + sage: S. = R[] + sage: p = a0*x^5+a1*x^4*y+a2*x^3*y^2+a3*x^2*y^3+a4*x*y^4+a5*y^5 + sage: quintic = invariant_theory.binary_quintic(p) + sage: quintic.i_covariant() + (3/50*a2^2 - 4/25*a1*a3 + 2/5*a0*a4)*x^2 + (1/25*a2*a3 - 6/25*a1*a4 + + 2*a0*a5)*x*y + (3/50*a3^2 - 4/25*a2*a4 + 2/5*a1*a5)*y^2 + """ + + def __init__(self, n, d, polynomial, *args): + """ + The Python constructor. + + TESTS:: + + sage: R. = QQ[] + sage: from sage.rings.invariant_theory import BinaryQuintic + sage: BinaryQuintic(2, 5, x^5+2*x^3*y^2+3*x*y^4) + Binary quintic with coefficients (0, 3, 0, 2, 0, 1) + """ + assert n == 2 and d == 5 + super(BinaryQuintic, self).__init__(2, 5, polynomial, *args) + self._x = self._variables[0] + self._y = self._variables[1] + + @cached_method + def monomials(self): + """ + List the basis monomials of the form. + + This functions lists a basis of monomials of the space of binary + quintics of which this form is an element. + + OUTPUT: + A tuple of monomials. They are in the same order as + :meth:`coeffs`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: quintic = invariant_theory.binary_quintic(x^5+y^5) + sage: quintic.monomials() + (y^5, x*y^4, x^2*y^3, x^3*y^2, x^4*y, x^5) + """ + x0 = self._x + x1 = self._y + if self._homogeneous: + return (x1**5, x1**4*x0, x1**3*x0**2, x1**2*x0**3, x1*x0**4, x0**5) + else: + return (self._ring.one(), x0, x0**2, x0**3, x0**4, x0**5) + + @cached_method + def coeffs(self): + """ + The coefficients of a binary quintic. + + Given + + .. MATH:: + + f(x) = a_0 x_1^5 + a_1 x_0 x_1^4 + a_2 x_0^2 x_1^3 + + a_3 x_0^3 x_1^2 + a_4 x_0^4 x_1 + a_5 x_1^5 + + this function returns `a = (a_0, a_1, a_2, a_3, a_4, a_5)` + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.coeffs() + (a0, a1, a2, a3, a4, a5) + + sage: R. = QQ[] + sage: p = a0 + a1*x + a2*x^2 + a3*x^3 + a4*x^4 + a5*x^5 + sage: quintic = invariant_theory.binary_quintic(p, x) + sage: quintic.coeffs() + (a0, a1, a2, a3, a4, a5) + """ + return self._extract_coefficients(self.monomials()) + + def scaled_coeffs(self): + """ + The coefficients of a binary quintic. + + Given + + .. MATH:: + + f(x) = a_0 x_1^5 + 5 a_1 x_0 x_1^4 + 10 a_2 x_0^2 x_1^3 + + 10 a_3 x_0^3 x_1^2 + 5 a_4 x_0^4 x_1 + a_5 x_1^5 + + this function returns `a = (a_0, a_1, a_2, a_3, a_4, a_5)` + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + 5*a1*x1^4*x0 + 10*a2*x1^3*x0^2 + 10*a3*x1^2*x0^3 + 5*a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.scaled_coeffs() + (a0, a1, a2, a3, a4, a5) + + sage: R. = QQ[] + sage: p = a0 + 5*a1*x + 10*a2*x^2 + 10*a3*x^3 + 5*a4*x^4 + a5*x^5 + sage: quintic = invariant_theory.binary_quintic(p, x) + sage: quintic.scaled_coeffs() + (a0, a1, a2, a3, a4, a5) + """ + coeff = self.coeffs() + return (coeff[0], coeff[1]/5, coeff[2]/10, coeff[3]/10, coeff[4]/5, \ + coeff[5]) + + @cached_method + def H_covariant(self, as_form=False): + """ + Return the covariant `H` of a binary quintic. + + INPUT: + + - ``as_form`` -- if ``as_form`` is ``False``, the result will be returned + as polynomial (default). If it is ``True`` the result is returned as + an object of the class :class:`AlgebraicForm`. + + OUTPUT: + + The `H`-covariant of the binary quintic as polynomial or as binary form. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.H_covariant() + -2/25*a4^2*x0^6 + 1/5*a3*a5*x0^6 - 3/25*a3*a4*x0^5*x1 + + 3/5*a2*a5*x0^5*x1 - 3/25*a3^2*x0^4*x1^2 + 3/25*a2*a4*x0^4*x1^2 + + 6/5*a1*a5*x0^4*x1^2 - 4/25*a2*a3*x0^3*x1^3 + 14/25*a1*a4*x0^3*x1^3 + + 2*a0*a5*x0^3*x1^3 - 3/25*a2^2*x0^2*x1^4 + 3/25*a1*a3*x0^2*x1^4 + + 6/5*a0*a4*x0^2*x1^4 - 3/25*a1*a2*x0*x1^5 + 3/5*a0*a3*x0*x1^5 + - 2/25*a1^2*x1^6 + 1/5*a0*a2*x1^6 + + sage: quintic.H_covariant(as_form=True) + Binary sextic given by ... + """ + cov = transvectant(self, self, 2) + if as_form: + return cov + else: + return cov.polynomial() + + @cached_method + def i_covariant(self, as_form=False): + """ + Return the covariant `i` of a binary quintic. + + INPUT: + + - ``as_form`` -- if ``as_form`` is ``False``, the result will be returned + as polynomial (default). If it is ``True`` the result is returned as + an object of the class :class:`AlgebraicForm`. + + OUTPUT: + + The `i`-covariant of the binary quintic as polynomial or as binary form. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.i_covariant() + 3/50*a3^2*x0^2 - 4/25*a2*a4*x0^2 + 2/5*a1*a5*x0^2 + 1/25*a2*a3*x0*x1 + - 6/25*a1*a4*x0*x1 + 2*a0*a5*x0*x1 + 3/50*a2^2*x1^2 - 4/25*a1*a3*x1^2 + + 2/5*a0*a4*x1^2 + + sage: quintic.i_covariant(as_form=True) + Binary quadratic given by ... + """ + cov = transvectant(self, self, 4) + if as_form: + return cov + else: + return cov.polynomial() + + @cached_method + def T_covariant(self, as_form=False): + """ + Return the covariant `T` of a binary quintic. + + INPUT: + + - ``as_form`` -- if ``as_form`` is ``False``, the result will be returned + as polynomial (default). If it is ``True`` the result is returned as + an object of the class :class:`AlgebraicForm`. + + OUTPUT: + + The `T`-covariant of the binary quintic as polynomial or as binary form. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.T_covariant() + 2/125*a4^3*x0^9 - 3/50*a3*a4*a5*x0^9 + 1/10*a2*a5^2*x0^9 + + 9/250*a3*a4^2*x0^8*x1 - 3/25*a3^2*a5*x0^8*x1 + 1/50*a2*a4*a5*x0^8*x1 + + 2/5*a1*a5^2*x0^8*x1 + 3/250*a3^2*a4*x0^7*x1^2 + 8/125*a2*a4^2*x0^7*x1^2 + ... + 11/25*a0*a1*a4*x0^2*x1^7 - a0^2*a5*x0^2*x1^7 - 9/250*a1^2*a2*x0*x1^8 + + 3/25*a0*a2^2*x0*x1^8 - 1/50*a0*a1*a3*x0*x1^8 - 2/5*a0^2*a4*x0*x1^8 + - 2/125*a1^3*x1^9 + 3/50*a0*a1*a2*x1^9 - 1/10*a0^2*a3*x1^9 + + sage: quintic.T_covariant(as_form=True) + Binary nonic given by ... + """ + H = self.H_covariant(as_form=True) + cov = transvectant(H, self, 1) + if as_form: + return cov + else: + return cov.polynomial() + + @cached_method + def j_covariant(self, as_form=False): + """ + Return the covariant `j` of a binary quintic. + + INPUT: + + - ``as_form`` -- if ``as_form`` is ``False``, the result will be returned + as polynomial (default). If it is ``True`` the result is returned as + an object of the class :class:`AlgebraicForm`. + + OUTPUT: + + The `j`-covariant of the binary quintic as polynomial or as binary form. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.j_covariant() + -3/500*a3^3*x0^3 + 3/125*a2*a3*a4*x0^3 - 6/125*a1*a4^2*x0^3 + - 3/50*a2^2*a5*x0^3 + 3/25*a1*a3*a5*x0^3 - 3/500*a2*a3^2*x0^2*x1 + + 3/250*a2^2*a4*x0^2*x1 + 3/125*a1*a3*a4*x0^2*x1 - 6/25*a0*a4^2*x0^2*x1 + - 3/25*a1*a2*a5*x0^2*x1 + 3/5*a0*a3*a5*x0^2*x1 - 3/500*a2^2*a3*x0*x1^2 + + 3/250*a1*a3^2*x0*x1^2 + 3/125*a1*a2*a4*x0*x1^2 - 3/25*a0*a3*a4*x0*x1^2 + - 6/25*a1^2*a5*x0*x1^2 + 3/5*a0*a2*a5*x0*x1^2 - 3/500*a2^3*x1^3 + + 3/125*a1*a2*a3*x1^3 - 3/50*a0*a3^2*x1^3 - 6/125*a1^2*a4*x1^3 + + 3/25*a0*a2*a4*x1^3 + + sage: quintic.j_covariant(as_form=True) + Binary cubic given by ... + """ + x0 = self._x + x1 = self._y + i = self.i_covariant() + minusi = AlgebraicForm(2, 2, -i, x0, x1) + cov = transvectant(minusi, self, 2) + if as_form: + return cov + else: + return cov.polynomial() + + @cached_method + def tau_covariant(self, as_form=False): + r""" + Return the covariant `\tau` of a binary quintic. + + INPUT: + + - ``as_form`` -- if ``as_form`` is ``False``, the result will be returned + as polynomial (default). If it is ``True`` the result is returned as + an object of the class :class:`AlgebraicForm`. + + OUTPUT: + + The `\tau`-covariant of the binary quintic as polynomial or as binary form. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.tau_covariant() + 1/62500*a2^2*a3^4*x0^2 - 3/62500*a1*a3^5*x0^2 + - 1/15625*a2^3*a3^2*a4*x0^2 + 1/6250*a1*a2*a3^3*a4*x0^2 + + 3/6250*a0*a3^4*a4*x0^2 - 1/31250*a2^4*a4^2*x0^2 + ... + - 2/125*a0*a1*a2^2*a4*a5*x1^2 - 4/125*a0*a1^2*a3*a4*a5*x1^2 + + 2/25*a0^2*a2*a3*a4*a5*x1^2 - 8/625*a1^4*a5^2*x1^2 + + 8/125*a0*a1^2*a2*a5^2*x1^2 - 2/25*a0^2*a2^2*a5^2*x1^2 + + sage: quintic.tau_covariant(as_form=True) + Binary quadratic given by ... + """ + j = self.j_covariant(as_form=True) + cov = transvectant(j, j, 2) + if as_form: + return cov + else: + return cov.polynomial() + + @cached_method + def theta_covariant(self, as_form=False): + r""" + Return the covariant `\theta` of a binary quintic. + + INPUT: + + - ``as_form`` -- if ``as_form`` is ``False``, the result will be returned + as polynomial (default). If it is ``True`` the result is returned as + an object of the class :class:`AlgebraicForm`. + + OUTPUT: + + The `\theta`-covariant of the binary quintic as polynomial or as binary form. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.theta_covariant() + -1/625000*a2^3*a3^5*x0^2 + 9/1250000*a1*a2*a3^6*x0^2 + - 27/1250000*a0*a3^7*x0^2 + 3/250000*a2^4*a3^3*a4*x0^2 + - 7/125000*a1*a2^2*a3^4*a4*x0^2 - 3/312500*a1^2*a3^5*a4*x0^2 + ... + + 6/625*a0^2*a1*a2^2*a4*a5^2*x1^2 + 24/625*a0^2*a1^2*a3*a4*a5^2*x1^2 + - 12/125*a0^3*a2*a3*a4*a5^2*x1^2 + 8/625*a0*a1^4*a5^3*x1^2 + - 8/125*a0^2*a1^2*a2*a5^3*x1^2 + 2/25*a0^3*a2^2*a5^3*x1^2 + + sage: quintic.theta_covariant(as_form=True) + Binary quadratic given by ... + """ + i = self.i_covariant(as_form=True) + tau = self.tau_covariant(as_form=True) + cov = transvectant(i, tau, 1) + if as_form: + return cov + else: + return cov.polynomial() + + @cached_method + def alpha_covariant(self, as_form=False): + r""" + Return the covariant `\alpha` of a binary quintic. + + INPUT: + + - ``as_form`` -- if ``as_form`` is ``False``, the result will be returned + as polynomial (default). If it is ``True`` the result is returned as + an object of the class :class:`AlgebraicForm`. + + OUTPUT: + + The `\alpha`-covariant of the binary quintic as polynomial or as binary form. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.alpha_covariant() + 1/2500*a2^2*a3^3*x0 - 3/2500*a1*a3^4*x0 - 1/625*a2^3*a3*a4*x0 + + 3/625*a1*a2*a3^2*a4*x0 + 3/625*a0*a3^3*a4*x0 + 2/625*a1*a2^2*a4^2*x0 + - 6/625*a1^2*a3*a4^2*x0 - 12/625*a0*a2*a3*a4^2*x0 + 24/625*a0*a1*a4^3*x0 + ... + - 12/625*a1^2*a2*a3*a5*x1 - 1/125*a0*a2^2*a3*a5*x1 + + 8/125*a0*a1*a3^2*a5*x1 + 24/625*a1^3*a4*a5*x1 - 8/125*a0*a1*a2*a4*a5*x1 + - 4/25*a0^2*a3*a4*a5*x1 - 4/25*a0*a1^2*a5^2*x1 + 2/5*a0^2*a2*a5^2*x1 + + sage: quintic.alpha_covariant(as_form=True) + Binary monic given by ... + """ + i = self.i_covariant() + x0 = self._x + x1 = self._y + i2 = AlgebraicForm(2, 4, i**2, x0, x1) + cov = transvectant(i2, self, 4) + if as_form: + return cov + else: + return cov.polynomial() + + @cached_method + def beta_covariant(self, as_form=False): + r""" + Return the covariant `\beta` of a binary quintic. + + INPUT: + + - ``as_form`` -- if ``as_form`` is ``False``, the result will be returned + as polynomial (default). If it is ``True`` the result is returned as + an object of the class :class:`AlgebraicForm`. + + OUTPUT: + + The `\beta`-covariant of the binary quintic as polynomial or as binary form. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.beta_covariant() + -1/62500*a2^3*a3^4*x0 + 9/125000*a1*a2*a3^5*x0 - 27/125000*a0*a3^6*x0 + + 13/125000*a2^4*a3^2*a4*x0 - 31/62500*a1*a2^2*a3^3*a4*x0 + - 3/62500*a1^2*a3^4*a4*x0 + 27/15625*a0*a2*a3^4*a4*x0 + ... + - 16/125*a0^2*a1*a3^2*a5^2*x1 - 28/625*a0*a1^3*a4*a5^2*x1 + + 6/125*a0^2*a1*a2*a4*a5^2*x1 + 8/25*a0^3*a3*a4*a5^2*x1 + + 4/25*a0^2*a1^2*a5^3*x1 - 2/5*a0^3*a2*a5^3*x1 + + sage: quintic.beta_covariant(as_form=True) + Binary monic given by ... + """ + i = self.i_covariant(as_form=True) + alpha = self.alpha_covariant(as_form=True) + cov = transvectant(i, alpha, 1) + if as_form: + return cov + else: + return cov.polynomial() + + @cached_method + def gamma_covariant(self, as_form=False): + r""" + Return the covariant `\gamma` of a binary quintic. + + INPUT: + + - ``as_form`` -- if ``as_form`` is ``False``, the result will be returned + as polynomial (default). If it is ``True`` the result is returned as + an object of the class :class:`AlgebraicForm`. + + OUTPUT: + + The `\gamma`-covariant of the binary quintic as polynomial or as binary form. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.gamma_covariant() + 1/156250000*a2^5*a3^6*x0 - 3/62500000*a1*a2^3*a3^7*x0 + + 27/312500000*a1^2*a2*a3^8*x0 + 27/312500000*a0*a2^2*a3^8*x0 + - 81/312500000*a0*a1*a3^9*x0 - 19/312500000*a2^6*a3^4*a4*x0 + ... + - 32/3125*a0^2*a1^3*a2^2*a5^4*x1 + 6/625*a0^3*a1*a2^3*a5^4*x1 + - 8/3125*a0^2*a1^4*a3*a5^4*x1 + 8/625*a0^3*a1^2*a2*a3*a5^4*x1 + - 2/125*a0^4*a2^2*a3*a5^4*x1 + + sage: quintic.gamma_covariant(as_form=True) + Binary monic given by ... + """ + alpha = self.alpha_covariant(as_form=True) + tau = self.tau_covariant(as_form=True) + cov = transvectant(tau, alpha, 1) + if as_form: + return cov + else: + return cov.polynomial() + + @cached_method + def delta_covariant(self, as_form=False): + r""" + Return the covariant `\delta` of a binary quintic. + + INPUT: + + - ``as_form`` -- if ``as_form`` is ``False``, the result will be returned + as polynomial (default). If it is ``True`` the result is returned as + an object of the class :class:`AlgebraicForm`. + + OUTPUT: + + The `\delta`-covariant of the binary quintic as polynomial or as binary form. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.delta_covariant() + 1/1562500000*a2^6*a3^7*x0 - 9/1562500000*a1*a2^4*a3^8*x0 + + 9/625000000*a1^2*a2^2*a3^9*x0 + 9/781250000*a0*a2^3*a3^9*x0 + - 9/1562500000*a1^3*a3^10*x0 - 81/1562500000*a0*a1*a2*a3^10*x0 + ... + + 64/3125*a0^3*a1^3*a2^2*a5^5*x1 - 12/625*a0^4*a1*a2^3*a5^5*x1 + + 16/3125*a0^3*a1^4*a3*a5^5*x1 - 16/625*a0^4*a1^2*a2*a3*a5^5*x1 + + 4/125*a0^5*a2^2*a3*a5^5*x1 + + sage: quintic.delta_covariant(as_form=True) + Binary monic given by ... + """ + alpha = self.alpha_covariant(as_form=True) + theta = self.theta_covariant(as_form=True) + cov = transvectant(theta, alpha, 1) + if as_form: + return cov + else: + return cov.polynomial() + + @cached_method + def A_invariant(self): + """ + Return the invariant `A` of a binary quintic. + + OUTPUT: + + The `A`-invariant of the binary quintic. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.A_invariant() + 4/625*a2^2*a3^2 - 12/625*a1*a3^3 - 12/625*a2^3*a4 + + 38/625*a1*a2*a3*a4 + 6/125*a0*a3^2*a4 - 18/625*a1^2*a4^2 + - 16/125*a0*a2*a4^2 + 6/125*a1*a2^2*a5 - 16/125*a1^2*a3*a5 + - 2/25*a0*a2*a3*a5 + 4/5*a0*a1*a4*a5 - 2*a0^2*a5^2 + """ + i = self.i_covariant(as_form=True) + A = transvectant(i, i, 2).polynomial() + try: + K = self._ring.base_ring() + return K(A) + except TypeError: + return A + + @cached_method + def B_invariant(self): + """ + Return the invariant `B` of a binary quintic. + + OUTPUT: + + The `B`-invariant of the binary quintic. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.B_invariant() + 1/1562500*a2^4*a3^4 - 3/781250*a1*a2^2*a3^5 + 9/1562500*a1^2*a3^6 + - 3/781250*a2^5*a3^2*a4 + 37/1562500*a1*a2^3*a3^3*a4 + - 57/1562500*a1^2*a2*a3^4*a4 + 3/312500*a0*a2^2*a3^4*a4 + ... + + 8/625*a0^2*a1^2*a4^2*a5^2 - 4/125*a0^3*a2*a4^2*a5^2 - 16/3125*a1^5*a5^3 + + 4/125*a0*a1^3*a2*a5^3 - 6/125*a0^2*a1*a2^2*a5^3 + - 4/125*a0^2*a1^2*a3*a5^3 + 2/25*a0^3*a2*a3*a5^3 + """ + i = self.i_covariant(as_form=True) + tau = self.tau_covariant(as_form=True) + B = transvectant(i, tau, 2).polynomial() + try: + K = self._ring.base_ring() + return K(B) + except TypeError: + return B + + @cached_method + def C_invariant(self): + """ + Return the invariant `C` of a binary quintic. + + OUTPUT: + + The `C`-invariant of the binary quintic. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.C_invariant() + -3/1953125000*a2^6*a3^6 + 27/1953125000*a1*a2^4*a3^7 + - 249/7812500000*a1^2*a2^2*a3^8 - 3/78125000*a0*a2^3*a3^8 + + 3/976562500*a1^3*a3^9 + 27/156250000*a0*a1*a2*a3^9 + ... + + 192/15625*a0^2*a1^3*a2^2*a3*a5^4 - 36/3125*a0^3*a1*a2^3*a3*a5^4 + + 24/15625*a0^2*a1^4*a3^2*a5^4 - 24/3125*a0^3*a1^2*a2*a3^2*a5^4 + + 6/625*a0^4*a2^2*a3^2*a5^4 + """ + tau = self.tau_covariant(as_form=True) + C = transvectant(tau, tau, 2).polynomial() + try: + K = self._ring.base_ring() + return K(C) + except TypeError: + return C + + @cached_method + def R_invariant(self): + """ + Return the invariant `R` of a binary quintic. + + OUTPUT: + + The `R`-invariant of the binary quintic. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.R_invariant() + 3/3906250000000*a1^2*a2^5*a3^11 - 3/976562500000*a0*a2^6*a3^11 + - 51/7812500000000*a1^3*a2^3*a3^12 + 27/976562500000*a0*a1*a2^4*a3^12 + + 27/1953125000000*a1^4*a2*a3^13 - 81/1562500000000*a0*a1^2*a2^2*a3^13 + ... + + 384/9765625*a0*a1^10*a5^7 - 192/390625*a0^2*a1^8*a2*a5^7 + + 192/78125*a0^3*a1^6*a2^2*a5^7 - 96/15625*a0^4*a1^4*a2^3*a5^7 + + 24/3125*a0^5*a1^2*a2^4*a5^7 - 12/3125*a0^6*a2^5*a5^7 + """ + beta = self.beta_covariant(as_form=True) + gamma = self.gamma_covariant(as_form=True) + R = transvectant(beta, gamma, 1).polynomial() + try: + K = self._ring.base_ring() + return K(R) + except TypeError: + return R + + @cached_method + def clebsch_invariants(self, as_tuple=False): + """ + Return the invariants of a binary quintic as described by Clebsch. + + The following invariants are returned: `A`, `B`, `C` and `R`. + + OUTPUT: + + The Clebsch invariants of the binary quintic. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = 2*x1^5 + 4*x1^4*x0 + 5*x1^3*x0^2 + 7*x1^2*x0^3 - 11*x1*x0^4 + x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.clebsch_invariants() + {'A': -276032/625, + 'B': 4983526016/390625, + 'C': -247056495846408/244140625, + 'R': -148978972828696847376/30517578125} + + sage: quintic.clebsch_invariants(as_tuple=True) + (-276032/625, + 4983526016/390625, + -247056495846408/244140625, + -148978972828696847376/30517578125) + + """ + if self._ring.characteristic() in [2, 3, 5]: + raise NotImplementedError('No invariants implemented for fields \ + of characteristic 2, 3 or 5.') # todo: add support + else: + invariants = {} + invariants['A'] = self.A_invariant() + invariants['B'] = self.B_invariant() + invariants['C'] = self.C_invariant() + invariants['R'] = self.R_invariant() + if as_tuple == True: + return (invariants['A'], invariants['B'], invariants['C'], \ + invariants['R']) + else: + return invariants + + @cached_method + def arithmetic_invariants(self): + r""" + Return a set of generating arithmetic invariants of a binary quintic. + + An arithmetic invariants is an invariant whose coefficients are + integers for a general binary quintic. They are linear combinations + of the Clebsch invariants, such that they still generate the ring of + invariants. + + OUTPUT: + + The arithmetic invariants of the binary quintic. They are given by + + .. MATH:: + + \begin{aligned} + I_4 & = 2^{-1} \cdot 5^4 \cdot A \\ + I_8 & = 5^5 \cdot (2^{-1} \cdot 47 \cdot A^2 - 2^2 \cdot B) \\ + I_{12} & = 5^{10} \cdot (2^{-1} \cdot 3 \cdot A^3 + - 2^5 \cdot 3^{-1} \cdot C) \\ + I_{18} & = 2^8 \cdot 3^{-1} \cdot 5^{15} \cdot R \\ + \end{aligned} + + where `A`, `B`, `C` and `R` are the + :meth:`BinaryQuintic.clebsch_invariants`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p = 2*x1^5 + 4*x1^4*x0 + 5*x1^3*x0^2 + 7*x1^2*x0^3 - 11*x1*x0^4 + x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: quintic.arithmetic_invariants() + {'I12': -1156502613073152, + 'I18': -12712872348048797642752, + 'I4': -138016, + 'I8': 14164936192} + + We can check that the coefficients of the invariants have no common divisor + for a general quintic form:: + + sage: R. = QQ[] + sage: p = a0*x1^5 + a1*x1^4*x0 + a2*x1^3*x0^2 + a3*x1^2*x0^3 + a4*x1*x0^4 + a5*x0^5 + sage: quintic = invariant_theory.binary_quintic(p, x0, x1) + sage: invs = quintic.arithmetic_invariants() + sage: [invs[x].content() for x in invs] + [1, 1, 1, 1] + + """ + R = self._ring + clebsch = self.clebsch_invariants() + invariants = {} + invariants['I4'] = R(2)**-1*5**4*clebsch['A'] + invariants['I8'] = 5**5 * (R(2)**-1*47*clebsch['A']**2 + -2**2*clebsch['B']) + invariants['I12'] = 5**10 * (R(2)**-1*3*clebsch['A']**3 + -2**5*R(3)**-1*clebsch['C']) + invariants['I18'] = 2**8*R(3)**-1*5**15 * clebsch['R'] + return invariants + ###################################################################### def _covariant_conic(A_scaled_coeffs, B_scaled_coeffs, monomials): """ @@ -1481,7 +2395,6 @@ def monomials(self): EXAMPLES:: - sage: R. = QQ[] sage: quadratic = invariant_theory.ternary_quadratic(x^2+y*z) sage: quadratic.monomials() @@ -1911,7 +2824,6 @@ def Theta_covariant(self): EXAMPLES:: - sage: R. = QQ[] sage: cubic = invariant_theory.ternary_cubic(x^3+y^3+z^3) sage: cubic.Theta_covariant() @@ -1958,8 +2870,8 @@ def J_covariant(self): """ F = self._ring.base_ring() return 1 / F(9) * self._jacobian_determinant( - [self.form(), 3], - [self.Hessian(), 3], + [self.form(), 3], + [self.Hessian(), 3], [self.Theta_covariant(), 6]) def syzygy(self, U, S, T, H, Theta, J): @@ -1970,7 +2882,7 @@ def syzygy(self, U, S, T, H, Theta, J): INPUT: - ``U``, ``S``, ``T``, ``H``, ``Theta``, ``J`` -- - polynomials from the same polynomial ring. + polynomials from the same polynomial ring. OUTPUT: @@ -1990,7 +2902,7 @@ def syzygy(self, U, S, T, H, Theta, J): sage: T = cubic.T_invariant() sage: H = cubic.Hessian() sage: Theta = cubic.Theta_covariant() - sage: J = cubic.J_covariant() + sage: J = cubic.J_covariant() sage: cubic.syzygy(U, S, T, H, Theta, J) 0 """ @@ -2053,7 +2965,7 @@ def __init__(self, forms): if not all(set(f._variables) == s for f in forms): raise ValueError('All forms must be in the same variables.') self._forms = forms - + def __richcmp__(self, other, op): """ Compare ``self`` with ``other``. @@ -2091,8 +3003,8 @@ def _repr_(self): Joint binary quadratic with coefficients (1, 1, 0) and binary quadratic with coefficients (0, 0, 1) sage: SeveralAlgebraicForms([q1, q2, q3]) # indirect doctest - Joint binary quadratic with coefficients (1, 1, 0), binary - quadratic with coefficients (0, 0, 1), and binary quadratic + Joint binary quadratic with coefficients (1, 1, 0), binary + quadratic with coefficients (0, 0, 1), and binary quadratic with coefficients (1, 1, 2) """ if self.n_forms() == 1: @@ -2147,9 +3059,9 @@ def get_form(self, i): True """ return self._forms[i] - + __getitem__ = get_form - + def homogenized(self, var='h'): """ @@ -2170,10 +3082,10 @@ def homogenized(self, var='h'): sage: R. = QQ[] sage: q = invariant_theory.quaternary_biquadratic(x^2+1, y^2+1, [x,y,z]) sage: q - Joint quaternary quadratic with coefficients (1, 0, 0, 1, 0, 0, 0, 0, 0, 0) + Joint quaternary quadratic with coefficients (1, 0, 0, 1, 0, 0, 0, 0, 0, 0) and quaternary quadratic with coefficients (0, 1, 0, 1, 0, 0, 0, 0, 0, 0) sage: q.homogenized() - Joint quaternary quadratic with coefficients (1, 0, 0, 1, 0, 0, 0, 0, 0, 0) + Joint quaternary quadratic with coefficients (1, 0, 0, 1, 0, 0, 0, 0, 0, 0) and quaternary quadratic with coefficients (0, 1, 0, 1, 0, 0, 0, 0, 0, 0) sage: type(q) is type(q.homogenized()) True @@ -2528,7 +3440,7 @@ class TwoQuaternaryQuadratics(TwoAlgebraicForms): sage: R. = QQ[] sage: inv = invariant_theory.quaternary_biquadratic(w^2+x^2, y^2+z^2, w, x, y, z) sage: inv - Joint quaternary quadratic with coefficients (1, 1, 0, 0, 0, 0, 0, 0, 0, 0) and + Joint quaternary quadratic with coefficients (1, 1, 0, 0, 0, 0, 0, 0, 0, 0) and quaternary quadratic with coefficients (0, 0, 1, 1, 0, 0, 0, 0, 0, 0) sage: TestSuite(inv).run() @@ -2581,7 +3493,7 @@ def Delta_prime_invariant(self): True """ return self.get_form(1).matrix().det() - + def _Theta_helper(self, scaled_coeffs_1, scaled_coeffs_2): """ @@ -2821,7 +3733,7 @@ def J_covariant(self): This is the Jacobian determinant of the two biquadratics, the `T`-covariant, and the `T'`-covariant with respect to the four homogeneous variables. - + EXAMPLES:: sage: R. = QQ[] @@ -2839,7 +3751,7 @@ def J_covariant(self): [self.T_covariant(), 4], [self.T_prime_covariant(), 4]) - + def syzygy(self, Delta, Theta, Phi, Theta_prime, Delta_prime, U, V, T, T_prime, J): """ Return the syzygy evaluated on the invariants and covariants. @@ -2848,14 +3760,14 @@ def syzygy(self, Delta, Theta, Phi, Theta_prime, Delta_prime, U, V, T, T_prime, - ``Delta``, ``Theta``, ``Phi``, ``Theta_prime``, ``Delta_prime``, ``U``, ``V``, ``T``, ``T_prime``, ``J`` -- - polynomials from the same polynomial ring. + polynomials from the same polynomial ring. OUTPUT: Zero if the ``U`` is the first polynomial, ``V`` the second polynomial, and the remaining input are the invariants and covariants of a quaternary biquadratic. - + EXAMPLES:: sage: R. = QQ[] @@ -2896,13 +3808,13 @@ def syzygy(self, Delta, Theta, Phi, Theta_prime, Delta_prime, U, V, T, T_prime, )* V + \ ( (Delta*Phi*Delta_prime) * T**2 + (3*Delta*Theta_prime*Delta_prime - Theta*Phi*Delta_prime) * T*T_prime + - (2*Delta*Delta_prime**2 - 2*Theta*Theta_prime*Delta_prime + (2*Delta*Delta_prime**2 - 2*Theta*Theta_prime*Delta_prime + Phi**2*Delta_prime) * T_prime**2 ) * U**2 + \ ( (Delta*Theta*Delta_prime + 2*Delta*Phi*Theta_prime - Theta**2*Theta_prime) * T**2 + - (4*Delta*Phi*Delta_prime - 3*Theta**2*Delta_prime + (4*Delta*Phi*Delta_prime - 3*Theta**2*Delta_prime - 3*Delta*Theta_prime**2 + Theta*Phi*Theta_prime) * T*T_prime + - (Delta*Theta_prime*Delta_prime + 2*Delta_prime*Phi*Theta + (Delta*Theta_prime*Delta_prime + 2*Delta_prime*Phi*Theta - Theta*Theta_prime**2) * T_prime**2 ) * U*V + \ ( (2*Delta**2*Delta_prime - 2*Delta*Theta*Theta_prime + Delta*Phi**2) * T**2 + @@ -2914,12 +3826,12 @@ def syzygy(self, Delta, Theta, Phi, Theta_prime, Delta_prime, U, V, T, T_prime, ) * U**3 + \ ( (4*Delta**2*Delta_prime**2 - Delta*Theta*Theta_prime*Delta_prime - 2*Delta*Phi**2*Delta_prime + Theta**2*Phi*Delta_prime) * T + - (-5*Delta*Theta*Delta_prime**2 + Delta*Phi*Theta_prime*Delta_prime + (-5*Delta*Theta*Delta_prime**2 + Delta*Phi*Theta_prime*Delta_prime + 2*Theta**2*Theta_prime*Delta_prime - Theta*Phi**2*Delta_prime) * T_prime ) * U**2*V + \ ( (-5*Delta**2*Theta_prime*Delta_prime + Delta*Theta*Phi*Delta_prime + 2*Delta*Theta*Theta_prime**2 - Delta*Phi**2*Theta_prime) * T + - (4*Delta**2*Delta_prime**2 - Delta*Theta*Theta_prime*Delta_prime + (4*Delta**2*Delta_prime**2 - Delta*Theta*Theta_prime*Delta_prime - 2*Delta*Phi**2*Delta_prime + Delta*Phi*Theta_prime**2) * T_prime ) * U*V**2 + \ ( (-2*Delta**2*Phi*Delta_prime + Delta**2*Theta_prime**2) * T + @@ -2928,11 +3840,11 @@ def syzygy(self, Delta, Theta, Phi, Theta_prime, Delta_prime, U, V, T, T_prime, (Delta**2*Delta_prime**3) * U**4 + \ (-3*Delta**2*Theta_prime*Delta_prime**2 + 3*Delta*Theta*Phi*Delta_prime**2 - Theta**3*Delta_prime**2) * U**3*V + \ - (-3*Delta**2*Phi*Delta_prime**2 + 3*Delta*Theta**2*Delta_prime**2 - + 3*Delta**2*Theta_prime**2*Delta_prime - - 3*Delta*Theta*Phi*Theta_prime*Delta_prime + (-3*Delta**2*Phi*Delta_prime**2 + 3*Delta*Theta**2*Delta_prime**2 + + 3*Delta**2*Theta_prime**2*Delta_prime + - 3*Delta*Theta*Phi*Theta_prime*Delta_prime + Delta*Phi**3*Delta_prime) * U**2*V**2 + \ - (-3*Delta**2*Theta*Delta_prime**2 + 3*Delta**2*Phi*Theta_prime*Delta_prime + (-3*Delta**2*Theta*Delta_prime**2 + 3*Delta**2*Phi*Theta_prime*Delta_prime - Delta**2*Theta_prime**3) * U*V**3 + \ (Delta**3*Delta_prime**2) * V**4 @@ -2949,7 +3861,7 @@ class InvariantTheoryFactory(object): sage: invariant_theory.ternary_cubic(x^3+y^3+z^3) Ternary cubic with coefficients (1, 1, 1, 0, 0, 0, 0, 0, 0, 0) """ - + def __repr__(self): """ Return a string representation. @@ -3062,7 +3974,7 @@ def binary_quadratic(self, quadratic, *args): Invariant theory of a quadratic in two variables. INPUT: - + - ``quadratic`` -- a quadratic form. - ``x``, ``y`` -- the homogeneous variables. If ``y`` is @@ -3089,7 +4001,7 @@ def quaternary_quadratic(self, quadratic, *args): Invariant theory of a quadratic in four variables. INPUT: - + - ``quadratic`` -- a quadratic form. - ``w``, ``x``, ``y``, ``z`` -- the homogeneous variables. If @@ -3159,6 +4071,67 @@ def binary_quartic(self, quartic, *args, **kwds): """ return BinaryQuartic(2, 4, quartic, *args, **kwds) + def binary_quintic(self, quintic, *args, **kwds): + """ + Create a binary quintic for computing invariants. + + A binary quintic is a homogeneous polynomial of degree 5 in two + variables. The algebra of invariants of a binary quintic is generated + by the invariants `A`, `B` and `C` of respective degrees 4, 8 and 12 + (see :meth:`~BinaryQuintic.A_invariant`, + :meth:`~BinaryQuintic.B_invariant` and + :meth:`~BinaryQuintic.C_invariant`). + + INPUT: + + - ``quintic`` -- a homogeneous polynomial of degree five in two + variables or a (possibly inhomogeneous) polynomial of degree at most + five in one variable. + + - ``*args`` -- the two homogeneous variables. If only one variable is + given, the polynomial ``quintic`` is assumed to be univariate. If + no variables are given, they are guessed. + + REFERENCES: + + - :wikipedia:`Invariant_of_a_binary_form` + - [Cle1872]_ + + EXAMPLES: + + If no variables are provided, they will be guessed:: + + sage: R. = QQ[] + sage: quintic = invariant_theory.binary_quintic(x^5+y^5) + sage: quintic + Binary quintic with coefficients (1, 0, 0, 0, 0, 1) + + If only one variable is given, the quintic is the homogenisation of + the provided polynomial:: + + sage: quintic = invariant_theory.binary_quintic(x^5+y^5, x) + sage: quintic + Binary quintic with coefficients (y^5, 0, 0, 0, 0, 1) + sage: quintic.is_homogeneous() + False + + If the polynomial has three or more variables, the variables should be + specified:: + + sage: R. = QQ[] + sage: quintic = invariant_theory.binary_quintic(x^5+z*y^5) + Traceback (most recent call last): + ... + ValueError: Need 2 or 1 variables, got (x, y, z) + sage: quintic = invariant_theory.binary_quintic(x^5+z*y^5, x, y) + sage: quintic + Binary quintic with coefficients (z, 0, 0, 0, 0, 1) + + sage: type(quintic) + + """ + return BinaryQuintic(2, 5, quintic, *args, **kwds) + def ternary_quadratic(self, quadratic, *args, **kwds): """ Invariants of a quadratic in three variables. @@ -3335,8 +4308,8 @@ def quaternary_biquadratic(self, quadratic1, quadratic2, *args, **kwds): Distance between two spheres [Salmon]_ :: sage: R. = QQ[] - sage: S1 = -r1^2 + x^2 + y^2 + z^2 - sage: S2 = -r2^2 + (x-a)^2 + (y-b)^2 + (z-c)^2 + sage: S1 = -r1^2 + x^2 + y^2 + z^2 + sage: S2 = -r2^2 + (x-a)^2 + (y-b)^2 + (z-c)^2 sage: inv = invariant_theory.quaternary_biquadratic(S1, S2, [x, y, z]) sage: inv.Delta_invariant() -r1^2 diff --git a/src/sage/rings/padics/padic_generic.py b/src/sage/rings/padics/padic_generic.py index 8a0c9f666ee..47ce2a884a4 100644 --- a/src/sage/rings/padics/padic_generic.py +++ b/src/sage/rings/padics/padic_generic.py @@ -38,6 +38,7 @@ from .local_generic import LocalGeneric from sage.rings.ring import PrincipalIdealDomain from sage.rings.integer import Integer +from sage.rings.infinity import Infinity from sage.rings.padics.padic_printing import pAdicPrinter from sage.rings.padics.precision_error import PrecisionError from sage.misc.cachefunc import cached_method @@ -69,6 +70,7 @@ def __init__(self, base, p, prec, print_mode, names, element_class, category=Non category = category.Metric().Complete() LocalGeneric.__init__(self, base, prec, names, element_class, category) self._printer = pAdicPrinter(self, print_mode) + self._qth_roots_of_unity = [ (1, Infinity) ] def some_elements(self): r""" @@ -1179,6 +1181,224 @@ def valuation(self): from sage.rings.padics.padic_valuation import pAdicValuation return pAdicValuation(self) + def _primitive_qth_root_of_unity(self, exponent): + """ + Compute the ``p^exponent``-th roots of unity in this ring. + + INPUT: + + - ``exponent`` -- an integer or ``Infinity`` + + OUTPUT: + + A triple ``(zeta,n,nextzeta)`` where + + - ``zeta`` is a generator of the group of ``p^exponent``-th + roots of unity in this ring, and + + - ``p^n`` is the order of ``zeta``. + + - ``nextzeta`` is the result of ``zeta._inverse_pth_root()`` + if ``n`` is positive and ``None`` otherwise + + TESTS:: + + sage: K. = Qq(2^3, 5) + sage: S. = K[] + sage: L. = K.extension(x^2 + 2*x + 2) + sage: zeta = L.primitive_root_of_unity(); zeta # indirect doctest + a + a*pi + pi^2 + a*pi^4 + a*pi^5 + a^2*pi^8 + a^2*pi^9 + O(pi^10) + sage: zeta.parent() is L + True + """ + n = len(self._qth_roots_of_unity) + + # We check if the result is cached + if exponent < n-1: + return self._qth_roots_of_unity[exponent][0], exponent, self._qth_roots_of_unity[exponent+1] + zeta, accuracy = self._qth_roots_of_unity[-1] + if accuracy is not Infinity: + return self._qth_roots_of_unity[-2][0], n-2, (zeta, accuracy) + + # It is not, so we compute it + while accuracy is Infinity and n <= exponent + 1: + self._qth_roots_of_unity[-1] = (self(zeta), Infinity) # to avoid multiple conversions + if n == 1: # case of pth root of unity + p = self.prime() + e = self.absolute_e() + k = self.residue_field() + if e % (p-1) != 0: + # No pth root of unity in this ring + zeta = accuracy = None + else: + rho = -k(self(p).expansion(e)) + try: + r = rho.nth_root(p-1) + except ValueError: + # No pth root of unity in this ring + zeta = accuracy = None + else: + # We compute a primitive pth root of unity + m = e // (p-1) + prec = self.precision_cap() + e * (1 + m.valuation(p)) + ring = self.change(prec=prec) + zeta = 1 + (ring(r).lift_to_precision() << m) + curprec = m*p + 1 + while curprec < prec: + curprec -= e + curprec = min(2*curprec + e, p*curprec) + zeta = zeta.lift_to_precision(min(prec,curprec)) + zeta += zeta * (1 - zeta**p) // p + else: + zeta, accuracy = zeta._inverse_pth_root() + assert accuracy is not None + self._qth_roots_of_unity.append((zeta, accuracy)) + n += 1 + return self._qth_roots_of_unity[-2][0], n-2, self._qth_roots_of_unity[-1] + + def primitive_root_of_unity(self, n=None, order=False): + """ + Return a generator of the group of ``n``-th roots of unity + in this ring. + + INPUT: + + - ``n`` -- an integer or ``None`` (default: ``None``): + + - ``order`` -- a boolean (default: ``False``) + + OUTPUT: + + A generator of the group of ``n``-th roots of unity. + If ``n`` is ``None``, a generator of the full group of roots + of unity is returned. + + If ``order`` is ``True``, the order of the above group is + returned as well. + + EXAMPLES:: + + sage: R = Zp(5, 10) + sage: zeta = R.primitive_root_of_unity(); zeta + 2 + 5 + 2*5^2 + 5^3 + 3*5^4 + 4*5^5 + 2*5^6 + 3*5^7 + 3*5^9 + O(5^10) + sage: zeta == R.teichmuller(2) + True + + Now we consider an example with non trivial ``p``-th roots of unity:: + + sage: W = Zp(3, 2) + sage: S. = W[] + sage: R. = W.extension((x+1)^6 + (x+1)^3 + 1) + + sage: zeta, order = R.primitive_root_of_unity(order=True) + sage: zeta + 2 + 2*pi + 2*pi^3 + 2*pi^7 + 2*pi^8 + 2*pi^9 + pi^11 + O(pi^12) + sage: order + 18 + sage: zeta.multiplicative_order() + 18 + + sage: zeta, order = R.primitive_root_of_unity(24, order=True) + sage: zeta + 2 + pi^3 + 2*pi^7 + 2*pi^8 + 2*pi^10 + 2*pi^11 + O(pi^12) + sage: order # equal to gcd(18,24) + 6 + sage: zeta.multiplicative_order() + 6 + + """ + p = self.prime() + k = self.residue_field() + prec = self.precision_cap() + c = k.cardinality() + + # We compute a primitive qth root of unity + # where q is the highest power of p dividing exponent + if n is None: + qthzeta, s, _ = self._primitive_qth_root_of_unity(Infinity) + m = c - 1 + else: + qthzeta, s, _ = self._primitive_qth_root_of_unity(n.valuation(p)) + m = n.gcd(c - 1) + qthzeta = self(qthzeta) + + # We now compute a primitive mth root of qthzeta + if m == 1: + zeta = qthzeta + else: + zeta = self(k.multiplicative_generator() ** ((c-1) // m)) + invm = self(1/m) + curprec = 1 + while curprec < prec: + curprec *= 2 + zeta = zeta.lift_to_precision(min(prec,curprec)) + zeta += invm * zeta * (1 - qthzeta*zeta**m) + + if order: + return zeta, m * p**s + else: + return zeta + + def roots_of_unity(self, n=None): + """ + Return all the ``n``-th roots of unity in this ring. + + INPUT: + + - ``n`` -- an integer or ``None`` (default: ``None``); if + ``None``, the full group of roots of unity is returned. + + EXAMPLES:: + + sage: R = Zp(5, 10) + sage: roots = R.roots_of_unity(); roots + [1 + O(5^10), + 2 + 5 + 2*5^2 + 5^3 + 3*5^4 + 4*5^5 + 2*5^6 + 3*5^7 + 3*5^9 + O(5^10), + 4 + 4*5 + 4*5^2 + 4*5^3 + 4*5^4 + 4*5^5 + 4*5^6 + 4*5^7 + 4*5^8 + 4*5^9 + O(5^10), + 3 + 3*5 + 2*5^2 + 3*5^3 + 5^4 + 2*5^6 + 5^7 + 4*5^8 + 5^9 + O(5^10)] + + sage: R.roots_of_unity(10) + [1 + O(5^10), + 4 + 4*5 + 4*5^2 + 4*5^3 + 4*5^4 + 4*5^5 + 4*5^6 + 4*5^7 + 4*5^8 + 4*5^9 + O(5^10)] + + In this case, the roots of unity are the Teichmuller representatives:: + + sage: R.teichmuller_system() + [1 + O(5^10), + 2 + 5 + 2*5^2 + 5^3 + 3*5^4 + 4*5^5 + 2*5^6 + 3*5^7 + 3*5^9 + O(5^10), + 3 + 3*5 + 2*5^2 + 3*5^3 + 5^4 + 2*5^6 + 5^7 + 4*5^8 + 5^9 + O(5^10), + 4 + 4*5 + 4*5^2 + 4*5^3 + 4*5^4 + 4*5^5 + 4*5^6 + 4*5^7 + 4*5^8 + 4*5^9 + O(5^10)] + + In general, there might be more roots of unity (it happens when the ring has non + trivial ``p``-th roots of unity):: + + sage: W. = Zq(3^2, 2) + sage: S. = W[] + sage: R. = W.extension((x+1)^2 + (x+1) + 1) + + sage: roots = R.roots_of_unity(); roots + [1 + O(pi^4), + a + 2*a*pi + 2*a*pi^2 + a*pi^3 + O(pi^4), + ... + 1 + pi + O(pi^4), + a + a*pi^2 + 2*a*pi^3 + O(pi^4), + ... + 1 + 2*pi + pi^2 + O(pi^4), + a + a*pi + a*pi^2 + O(pi^4), + ...] + sage: len(roots) + 24 + + We check that the logarithm of each root of unity vanishes:: + + sage: for root in roots: + ....: if root.log() != 0: raise ValueError + + """ + zeta, order = self.primitive_root_of_unity(n, order=True) + return [ zeta**i for i in range(order) ] + + class ResidueReductionMap(Morphism): """ Reduction map from a p-adic ring or field to its residue field or ring. diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index af231532009..87a8d41b6f6 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -37,6 +37,7 @@ from sage.rings.padics.local_generic_element cimport LocalGenericElement from sage.rings.padics.precision_error import PrecisionError from sage.rings.rational cimport Rational from sage.rings.integer cimport Integer +from sage.rings.integer_ring import ZZ from sage.rings.infinity import infinity from sage.structure.element import coerce_binop @@ -1809,10 +1810,13 @@ cdef class pAdicGenericElement(LocalGenericElement): return (self.valuation() % 2 == 0) and (self.unit_part().residue(1).is_square()) else: e = parent.absolute_e() - if self.precision_relative() < 1 + 2*e: + try: + self.add_bigoh(self.valuation() + 2*e + 1).nth_root(2) + except ValueError: + return False + except PrecisionError: raise PrecisionError("not enough precision to be sure that this element has a square root") - sq = self.add_bigoh(self.valuation() + 2*e + 1)._square_root() - return sq is not None + return True def is_squarefree(self): r""" @@ -3381,7 +3385,7 @@ cdef class pAdicGenericElement(LocalGenericElement): ValueError: element is not a square In particular, an error is raised when we try to compute the square - root of an inexact + root of an inexact zero. TESTS:: @@ -3391,6 +3395,41 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: s == c or s == -c True + sage: Q2 = Qp(2,20,'capped-rel') + sage: Q2(1).square_root() + 1 + O(2^19) + sage: Q2(4).square_root() + 2 + O(2^20) + + sage: Q5 = Qp(5,20,'capped-rel') + sage: Q5(1).square_root() + 1 + O(5^20) + sage: Q5(-1).square_root() == Q5.teichmuller(2) or Q5(-1).square_root() == Q5.teichmuller(3) + True + + sage: Z3 = Zp(3,20,'capped-abs') + sage: Z3(1).square_root() + 1 + O(3^20) + sage: Z3(4).square_root() in [ Z3(2), -Z3(2) ] + True + sage: Z3(9).square_root() + 3 + O(3^19) + + sage: Z2 = Zp(2,20,'capped-abs') + sage: Z2(1).square_root() + 1 + O(2^19) + sage: Z2(4).square_root() + 2 + O(2^18) + sage: Z2(9).square_root() in [ Z2(3), -Z2(3) ] + True + sage: Z2(17).square_root() + 1 + 2^3 + 2^5 + 2^6 + 2^7 + 2^9 + 2^10 + 2^13 + 2^16 + 2^17 + O(2^19) + + sage: Z5 = Zp(5,20,'capped-abs') + sage: Z5(1).square_root() + 1 + O(5^20) + sage: Z5(-1).square_root() == Z5.teichmuller(2) or Z5(-1).square_root() == Z5.teichmuller(3) + True """ # We first check trivial cases and precision if self._is_exact_zero(): @@ -3405,9 +3444,9 @@ cdef class pAdicGenericElement(LocalGenericElement): else: algorithm = "sage" + ans = None if algorithm == "pari": from sage.libs.pari.all import PariError - ans = None try: # use pari ans = parent(self.__pari__().sqrt()) @@ -3416,7 +3455,10 @@ cdef class pAdicGenericElement(LocalGenericElement): # an extension field pass elif algorithm == "sage": - ans = self._square_root() + try: + ans = self.nth_root(2) + except ValueError: + pass if ans is not None: if list(ans.expansion()) > list((-ans).expansion()): ans = -ans @@ -3431,138 +3473,375 @@ cdef class pAdicGenericElement(LocalGenericElement): else: raise ValueError("element is not a square") - def _square_root(self): + + def nth_root(self, n, all=False): """ - Return the square root of this `p`-adic number - or ``None`` if this number does not have a square root + Return the nth root of this element. - NOTE: + INPUT: - This is the Sage implementation used in :meth:`square_root`. + - ``n`` -- an integer - This method assumes that the input is given at relative precision - at least 1 if `p > 2` and relative precision at least `2e + 1` if - `p = 2` (where `e` is the absolute ramification index). - This is the minimal precision to be sure whether this number has - or has not a square root. + - ``all`` -- a boolean (default: ``False``): if ``True``, + return all ntn roots of this element, instead of just one. - TESTS:: + EXAMPLES:: - sage: Q2 = Qp(2,20,'capped-rel') - sage: Q2(1)._square_root() - 1 + O(2^19) - sage: Q2(4)._square_root() - 2 + O(2^20) + sage: A = Zp(5,10) + sage: x = A(61376); x + 1 + 5^3 + 3*5^4 + 4*5^5 + 3*5^6 + O(5^10) + sage: y = x.nth_root(4); y + 2 + 5 + 2*5^2 + 4*5^3 + 3*5^4 + 5^6 + O(5^10) + sage: y^4 == x + True - sage: Q5 = Qp(5,20,'capped-rel') - sage: Q5(1)._square_root() - 1 + O(5^20) - sage: Q5(-1)._square_root() == Q5.teichmuller(2) or Q5(-1).square_root() == Q5.teichmuller(3) + sage: x.nth_root(4, all=True) + [2 + 5 + 2*5^2 + 4*5^3 + 3*5^4 + 5^6 + O(5^10), + 4 + 4*5 + 4*5^2 + 4*5^4 + 3*5^5 + 5^6 + 3*5^7 + 5^8 + 5^9 + O(5^10), + 3 + 3*5 + 2*5^2 + 5^4 + 4*5^5 + 3*5^6 + 4*5^7 + 4*5^8 + 4*5^9 + O(5^10), + 1 + 4*5^3 + 5^5 + 3*5^6 + 5^7 + 3*5^8 + 3*5^9 + O(5^10)] + + When `n` is divisible by the underlying prime `p`, we + are losing precision (which is consistant with the fact + that raising to the pth power increases precision):: + + sage: z = x.nth_root(5); z + 1 + 5^2 + 3*5^3 + 2*5^4 + 5^5 + 3*5^7 + 2*5^8 + O(5^9) + sage: z^5 + 1 + 5^3 + 3*5^4 + 4*5^5 + 3*5^6 + O(5^10) + + Everything works over extensions as well:: + + sage: W. = Zq(5^3) + sage: S. = W[] + sage: R. = W.extension(x^7 - 5) + sage: R(5).nth_root(7) + pi + O(pi^141) + sage: R(5).nth_root(7, all=True) + [pi + O(pi^141)] + + An error is raised if the given element is not a nth power + in the ring:: + + sage: R(5).nth_root(11) + Traceback (most recent call last): + ... + ValueError: This element is not a nth power + + Similarly, when precision on the input is too small, an error + is raised: + + sage: x = R(1,6); x + 1 + O(pi^6) + sage: x.nth_root(5) + Traceback (most recent call last): + ... + PrecisionError: Not enough precision to be sure that this element is a nth power + + TESTS: + + We check that it works over different fields:: + + sage: K. = Qq(2^3) + sage: S. = K[] + sage: L. = K.extension(x^2 + 2*x + 2) + sage: elt = L.random_element() + sage: elt in (elt^8).nth_root(8, all=True) + True + sage: elt = L.random_element() + sage: elt in (elt^16).nth_root(16, all=True) + True + sage: elt = L.random_element() + sage: elt in (elt^56).nth_root(56, all=True) True - sage: Z3 = Zp(3,20,'capped-abs') - sage: Z3(1).square_root() - 1 + O(3^20) - sage: Z3(4).square_root() in [ Z3(2), -Z3(2) ] + sage: K. = Qq(3^2) + sage: S. = K[] + sage: Z = (1+x)^3 + sage: E = Z^2 + Z + 1 + sage: L. = K.extension(E) + sage: elt = L.random_element() + sage: elt in (elt^9).nth_root(9, all=True) + True + sage: elt = L.random_element() + sage: elt in (elt^27).nth_root(27, all=True) + True + sage: elt = L.random_element() + sage: elt in (elt^108).nth_root(108, all=True) True - sage: Z3(9).square_root() - 3 + O(3^19) - sage: Z2 = Zp(2,20,'capped-abs') - sage: Z2(1).square_root() - 1 + O(2^19) - sage: Z2(4).square_root() - 2 + O(2^18) - sage: Z2(9).square_root() in [ Z2(3), -Z2(3) ] + sage: K. = ZqCA(3^2) + sage: S. = K[] + sage: Z = (1+x)^3 + 3*x^2 + sage: E = Z^2 + Z + 1 + sage: L. = K.extension(E) + sage: elt = L.random_element() + sage: elt in (elt^9).nth_root(9, all=True) + True + sage: elt = L.random_element() + sage: elt in (elt^27).nth_root(27, all=True) + True + sage: elt = L.random_element() + sage: elt in (elt^108).nth_root(108, all=True) True - sage: Z2(17).square_root() - 1 + 2^3 + 2^5 + 2^6 + 2^7 + 2^9 + 2^10 + 2^13 + 2^16 + 2^17 + O(2^19) - sage: Z5 = Zp(5,20,'capped-abs') - sage: Z5(1).square_root() - 1 + O(5^20) - sage: Z5(-1).square_root() == Z5.teichmuller(2) or Z5(-1).square_root() == Z5.teichmuller(3) + sage: K. = Qq(3^2) + sage: S. = K[] + sage: Z = (1+x)^3 + 3*x^3 + sage: E = (Z^2 + Z + 1)(a*x).monic() + sage: L. = K.extension(E) + sage: elt = L.random_element() + sage: elt in (elt^9).nth_root(9, all=True) + True + sage: elt = L.random_element() + sage: elt in (elt^27).nth_root(27, all=True) True + sage: elt = L.random_element() + sage: elt in (elt^108).nth_root(108, all=True) + True + + """ + n = ZZ(n) + if n == 0: + raise ValueError("n must be a nonzero integer") + elif n == 1: + return self + elif n < 0: + return (~self).nth_root(-n) + parent = self.parent() + K = parent.fraction_field() # due to conversion issues + p = parent.prime() + e = parent.absolute_e() + ep = e // (p-1) + + # We first check trivial cases + if self._is_exact_zero(): + return self + if self.is_zero(): + raise PrecisionError("Not enough precision to be sure that this element is a nth power") + + v = n.valuation(p) + m = n // (p**v) + + # We check the valuation + val = self.valuation() + if val % n != 0: + raise ValueError("This element is not a nth power") + # and the residue + a = K(self) >> val + abar = a.residue() + try: + xbar = abar.nth_root(m) + except ValueError: + raise ValueError("This element is not a nth power") + + # We take the inverse mth root at small precision + prec = a.precision_absolute() + minprec = v*e + ep + 1 + if m == 1: + parity = 0 + root = a.add_bigoh(minprec) + else: + parity = 1 + root = K(~xbar) + invm = K(1/m) + curprec = 1 + while curprec < min(minprec,prec): + curprec *= 2 + root = root.lift_to_precision(min(minprec,prec,curprec)) + root += invm * root * (1 - a*(root**m)) + + # We now extract the (p^v)-th root + zeta, s, nextzeta = K._primitive_qth_root_of_unity(v) + nextzeta = (parent(nextzeta[0]), nextzeta[1]) # nextzeta[0] may have a wrong parent (with more precision) + for i in range(v): + if s > 0 and i >= s: + root, accuracy = root._inverse_pth_root(twist=zeta, hint=nextzeta) + else: + root, accuracy = root._inverse_pth_root() + if accuracy is not infinity and accuracy is not None: + raise ValueError("This element is not a nth power") + + # We check the precision + if v > 0 and prec < minprec: + raise PrecisionError("Not enough precision to be sure that this element is a nth power") + + # We lift the root using Newton iteration + if v % 2 == parity: + root = ~root + invn = K(1/n) + curprec = minprec + while curprec < prec: + curprec -= v*e + curprec = min(2*curprec + v*e, p*curprec + (v-1)*e) + root = root.lift_to_precision(min(prec,curprec)) + root += invn * root * (1 - a*(root**n)) + root = (~root) << (val // n) + + if all: + return [ parent(root*zeta) for zeta in K.roots_of_unity(n) ] + else: + return parent(root) + + def _inverse_pth_root(self, twist=None, hint=None): + """ + In its simplest form, computes the inverse of + ``p``-th root of this element. + + This is an helper function used in :meth:`nth_root` + and :meth:`primitive_root_of_unity`. + + INPUT: + + - ``twist`` -- an element in the same parent or ``None`` + (default: ``None``) + + - ``hint`` -- a tuple or ``None`` (default: ``None``); if not + ``None``, it has to be the output of ``twist._inverse_pth_root()`` + + OUTPUT: + + When ``twist`` is ``None``, the output is a couple + ``(invroot, accuracy)`` where: + + - ``accuracy`` is the highest valuation of an element of + the form ``self * x^p - 1`` for `x` varying in the + parent of this element, and + + - ``invroot`` is an element realizing this maximum. + + If the precision on the element is not enough to determine + ``accuracy``, the value ``None`` is returned. + + When ``twist`` is not ``None``, the maximum is taken over + all elements of the form ``self * x^p * twist^i - 1`` for + for `x` varying in the parent of this element and `i` + varying in the range `\{0, 1, \ldots, p-1\}` + + .. NOTE:: + + This function assumes that the input element and ``twist`` + (if given) are units in the integer ring. + + TESTS:: + + sage: R = Zp(11) + sage: [ R.teichmuller(x).nth_root(11) == R.teichmuller(x) for x in range(1,11) ] # indirect doctest + [True, True, True, True, True, True, True, True, True, True] + + sage: W. = Zq(5^3) + sage: S. = W[] + sage: R. = W.extension(x^8 + 15*a*x - 5) + sage: y = R.random_element() + sage: for n in [5, 10, 15]: + ....: z = y**n + ....: assert z.nth_root(n)**n == z # indirect doctest + """ ring = self.parent() p = ring.prime() e = ring.absolute_e() + ep = e // (p-1) - # First, we check valuation and renormalize if needed - val = self.valuation() - if val % 2 == 1: - return None - a = self >> val + if twist is None: + accuracy = None + else: + if hint is None: + invroottwist, accuracy = twist._inverse_pth_root() + else: + invroottwist, accuracy = hint + if accuracy is None: + raise NotImplementedError("Try to increase the precision cap of the parent...") + + a = self prec = a.precision_absolute() - # We compute the square root of 1/a in the residue field - abar = a.residue() - try: - xbar = 1/abar.sqrt(extend=False) - except ValueError: - return None - x = ring(xbar) - curprec = 1 - - # When p is 2, we lift sqrt(1/a) modulo 2*pi (pi = uniformizer) - if p == 2: # We assume here that the relative precision is at least 2*e + 1 - x = x.lift_to_precision(e+1) - - # We will need 1/a at higher precision - ainv = ~(a.add_bigoh(2*e+1)) - - # If x^2 is not correct modulo pi^2, there is no solution - if (ainv - x**2).valuation() < 2: - return None - - # We lift modulo 2 - k = ring.residue_field() - while curprec < e: # curprec is the number of correct digits of x - # recomputing x^2 is not necessary: - # we can alternatively update it after each update of x - # (which is theoretically a bit faster) - b = (ainv - x**2) >> (2*curprec) - if b == 0: break - for i in range(e - curprec): - if i % 2 == 0: - try: - # We shouldn't recompute the expansion of b at the iteration - cbar = k(b.expansion(i)).sqrt(extend=False) - except ValueError: - return None + # We will need 1/a at higher precision + ainv = ~(a.add_bigoh(e+ep+1)) + + # We lift modulo pi^(e // (p-1)) + k = ring.residue_field() + x = ring(0) + curprec = 0 # curprec is the valuation of (ainv - x^p) + while curprec < min(prec, e+ep): + # recomputing x^p is not necessary: + # we can alternatively update it after each update of x + # (which is theoretically a bit faster) + b = ainv - x**p + if b == 0: break + curprec = b.valuation() + bexp = iter(b.unit_part().expansion()) + maxprec = prec + while curprec < maxprec: + try: + coeff = k(next(bexp)) + except StopIteration: + coeff = k(0) + if coeff != 0: + if curprec % p == 0: + cbar = coeff.nth_root(p) c = ring(cbar).lift_to_precision() - x += c << (curprec + i//2) + exponent = curprec // p + x += c << exponent + maxprec = min(maxprec, exponent + e) + elif accuracy == curprec: + alpha = (twist * invroottwist.add_bigoh(1 + curprec // p)**p - 1) >> curprec + exponent = coeff / (ainv.residue() * alpha.residue()) + try: + exponent = ZZ(exponent) + except TypeError: + return x, curprec + else: + ainv //= twist**exponent + a *= twist**exponent + x *= invroottwist**exponent + break else: - if k(b.expansion(i)) != 0: - return None - curprec = (curprec + e + 1) // 2 - - # We lift one step further + return x, curprec + curprec += 1 + + # We check if the precision was enough + # We didn't do it before because we could have proved + # that there is no pth root in the previous step. + if prec < e + ep + 1: + x = x.add_bigoh((prec+p-1) // p) + return x, None + + # We lift one step further + curprec = e + ep + if e % (p-1) == 0: + rho = k(ring(p).expansion(e)) + b = ainv - x**p + b >>= curprec + coeff = -a.residue()*b.residue() + if accuracy == curprec: + sigma = rho * (-rho).nth_root(p-1) # should never fail + alpha = (twist * invroottwist.add_bigoh(ep+1)**p - 1) >> curprec + alpha = alpha.residue() + tr = (alpha/sigma).trace() + if tr != 0: + exponent = ZZ(-(coeff/sigma).trace() / tr) + coeff += exponent*alpha + ainv //= twist**exponent + a *= twist**exponent + x *= invroottwist**exponent from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing S = PolynomialRing(k, name='x') - b = ainv - x**2 - if b.valuation() < 2*e: - return None - b >>= (2*e) - AS = S([ b.residue(), xbar*k(ring(2).expansion(e)), 1 ]) + AS = S([ coeff, rho ] + (p-2)*[0] + [1]) roots = AS.roots() if len(roots) == 0: - return None - x += ring(roots[0][0]) << e - - # For Newton iteration, we redefine curprec - # as (a lower bound on) valuation(a*x^2 - 1) - curprec = 2*e + 1 + return x, curprec + x += ring(roots[0][0] * x.residue()) << ep # We perform Newton iteration + curprec += 1 while curprec < prec: - if p == 2: - curprec -= e - curprec <<= 1 + curprec -= e + curprec = min(2*curprec + e, p*curprec) x = x.lift_to_precision(min(prec,curprec)) - x += x * (1 - a*x*x) / 2 + x += x * (1 - a*x**p) / p - return (a*x) << (val // 2) + return x, infinity def __abs__(self): diff --git a/src/sage/rings/valuation/augmented_valuation.py b/src/sage/rings/valuation/augmented_valuation.py index f9c85ee50b6..cae40e17800 100644 --- a/src/sage/rings/valuation/augmented_valuation.py +++ b/src/sage/rings/valuation/augmented_valuation.py @@ -548,7 +548,7 @@ def extensions(self, ring): sage: w.extensions(GaussianIntegers().fraction_field()['x']) [[ Gauss valuation induced by 2-adic valuation, v(x^2 + x + 1) = 1 ]] - + """ if ring is self.domain(): return [self] @@ -653,7 +653,7 @@ def monic_integral_model(self, G): """ return self._base_valuation.monic_integral_model(G) - + def _ge_(self, other): r""" Return whether this valuation is greater or equal than ``other`` @@ -738,7 +738,7 @@ def _residue_ring_generator_name(self): sage: w = v.augmentation(x^2 + x + 1, 1) sage: w._residue_ring_generator_name() 'u1' - + """ base = self._base_valuation.residue_ring().base() # we need a name for a generator that is not present already in base @@ -767,7 +767,7 @@ def _relative_size(self, f): coefficients is going to lead to a significant shrinking of the coefficients of ``f``. - EXAMPLES:: + EXAMPLES:: sage: R. = QQ[] sage: K. = QQ.extension(u^2 + u+ 1) @@ -1028,7 +1028,7 @@ def _residue_field_generator(self): u1 A case with non-trivial base valuation:: - + sage: R. = Qq(4, 10) sage: S. = R[] sage: v = GaussValuation(S) @@ -1075,7 +1075,7 @@ def lift(self, F): x A case with non-trivial base valuation:: - + sage: R. = Qq(4, 10) sage: S. = R[] sage: v = GaussValuation(S) @@ -1095,7 +1095,7 @@ def lift(self, F): # We only have to do that if psi is non-trivial if self.psi().degree() > 1: from sage.rings.polynomial.polynomial_quotient_ring_element import PolynomialQuotientRingElement - from sage.rings.function_field.function_field_element import FunctionFieldElement_polymod + from sage.rings.function_field.element import FunctionFieldElement_polymod if isinstance(F, PolynomialQuotientRingElement): G = F.lift() elif isinstance(F, FunctionFieldElement_polymod): @@ -1329,7 +1329,7 @@ def _residue_field_generator(self): sage: w = v.augmentation(x^2 + x + u, 1/2) sage: w._residue_field_generator() u1 - + """ if self.residue_ring() == self._base_valuation.residue_ring(): assert self.psi().degree() == 1 @@ -1576,7 +1576,7 @@ def _Q_reciprocal(self, e=1): """ if e == 1: return self.equivalence_reciprocal(self._Q(1), check=False) - + tau = self.value_group().index(self._base_valuation.value_group()) v = -self._mu * tau ret = self._pow(self._Q_reciprocal(1), e, error=v*e, effective_degree=0) @@ -1806,7 +1806,7 @@ def simplify(self, f, error=None, force=False, effective_degree=None, size_heuri valuations = list(self.valuations(f, coefficients=coefficients)) return self.domain().change_ring(self.domain())([ 0 if valuations[i] > error - else self._base_valuation.simplify(c, error=error-i*self._mu, force=force, phiadic=True) + else self._base_valuation.simplify(c, error=error-i*self._mu, force=force, phiadic=True) for (i,c) in enumerate(coefficients)])(self.phi()) else: # We iterate through the coefficients of the polynomial (in the @@ -1943,14 +1943,14 @@ class NonFinalFiniteAugmentedValuation(FiniteAugmentedValuation, NonFinalAugment def __init__(self, parent, v, phi, mu): r""" TESTS:: - + sage: R. = QQ[] sage: v = GaussValuation(R, QQ.valuation(2)) sage: w = v.augmentation(x, 1) sage: from sage.rings.valuation.augmented_valuation import NonFinalFiniteAugmentedValuation sage: isinstance(w, NonFinalFiniteAugmentedValuation) True - + """ FiniteAugmentedValuation.__init__(self, parent, v, phi, mu) NonFinalAugmentedValuation.__init__(self, parent, v, phi, mu) diff --git a/src/sage/sandpiles/sandpile.py b/src/sage/sandpiles/sandpile.py index 271515e7a78..a5d0c8eff1b 100644 --- a/src/sage/sandpiles/sandpile.py +++ b/src/sage/sandpiles/sandpile.py @@ -319,6 +319,8 @@ from collections import Counter from copy import deepcopy from inspect import getdoc +from textwrap import dedent + import os # CHECK: possibly unnecessary after removing 4ti2-dependent methods from sage.calculus.functional import derivative from sage.combinat.integer_vector import integer_vectors_nk_fast_iter @@ -340,10 +342,57 @@ from sage.plot.colors import rainbow from sage.arith.all import falling_factorial, lcm from sage.rings.all import Integer, PolynomialRing, QQ, ZZ -from sage.symbolic.all import I, pi +from sage.symbolic.all import I, pi, SR # TODO: remove the following line once 4ti2 functions are removed -path_to_zsolve = os.path.join(SAGE_LOCAL,'bin','zsolve') +path_to_zsolve = os.path.join(SAGE_LOCAL, 'bin', 'zsolve') + + + +def _sandpile_help(cls, usage, verbose=True): + """ + Prints help text for classes in this module; see the ``help()`` methods on + individual classes in this module for example usage. + """ + + # We collect the first sentence of each docstring. The sentence is, + # by definition, from the beginning of the string to the first + # occurrence of a period or question mark. If neither of these appear + # in the string, take the sentence to be the empty string. If the + # latter occurs, something should be changed. + from sage.misc.sagedoc import detex + methods = [] + for attr in sorted(vars(cls)): + if attr[0] != '_': + doc = getdoc(getattr(cls, attr)) + period = doc.find('.') + question = doc.find('?') + if period == -1 and question == -1: + doc = '' # Neither appears! + else: + if period == -1: + period = len(doc) + 1 + if question == -1: + question = len(doc) + 1 + if period < question: + doc = doc.split('.')[0] + doc = detex(doc).strip() + '.' + else: + doc = doc.split('?')[0] + doc = detex(doc).strip() + '?' + methods.append((attr, doc)) + + print(usage) + print() + + mlen = max(len(attr) for attr, doc in methods) + if verbose: + for attr, doc in methods: + print(attr.ljust(mlen), '--', doc) + else: + for attr, _ in methods: + print(attr) + class Sandpile(DiGraph): """ @@ -443,42 +492,11 @@ def help(verbose=True): zero_config -- The all-zero configuration. zero_div -- The all-zero divisor. """ - # We collect the first sentence of each docstring. The sentence is, - # by definition, from the beginning of the string to the first - # occurrence of a period or question mark. If neither of these appear - # in the string, take the sentence to be the empty string. If the - # latter occurs, something should be changed. - from sage.misc.sagedoc import detex - methods = [] - for i in sorted(Sandpile.__dict__): - if i[0]!='_': - s = eval('getdoc(Sandpile.' + i +')') - period = s.find('.') - question = s.find('?') - if period==-1 and question==-1: - s = '' # Neither appears! - else: - if period==-1: - period = len(s) + 1 - if question==-1: - question = len(s) + 1 - if period < question: - s = s.split('.')[0] - s = detex(s).strip() + '.' - else: - s = s.split('?')[0] - s = detex(s).strip() + '?' - methods.append([i,s]) - print('For detailed help with any method FOO listed below,') - print('enter "Sandpile.FOO?" or enter "S.FOO?" for any Sandpile S.') - print('') - mlen = max([len(i[0]) for i in methods]) - if verbose: - for i in methods: - print(i[0].ljust(mlen), '--', i[1]) - else: - for i in methods: - print(i[0]) + + _sandpile_help(Sandpile, dedent("""\ + For detailed help with any method FOO listed below, + enter "Sandpile.FOO?" or enter "S.FOO?" for any Sandpile S."""), + verbose=verbose) def __init__(self, g, sink=None): r""" @@ -2579,13 +2597,13 @@ def _set_resolution(self): # convert the resolution to a list of Sage poly matrices result = [] zero = self._ring.gens()[0]*0 - for i in range(1,len(res)+1): + for i in range(1, len(res)+1): syz_mat = [] - new = [res[i][j] for j in range(1,res[i].size()+1)] + new = [res[i][j] for j in range(1, int(res[i].size())+1)] for j in range(self._betti[i]): row = new[j].transpose().sage_matrix(self._ring) row = [r for r in row[0]] - if len(row) +# +# 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/ +#***************************************************************************** + +# Support of Python 3 +from __future__ import division, absolute_import, print_function, unicode_literals + +from .satsolver import SatSolver + +class PicoSAT(SatSolver): + r""" + PicoSAT Solver. + + INPUT: + + - ``verbosity`` -- an integer between 0 and 2 (default: 0); verbosity + + - ``prop_limit`` -- an integer (default: 0); the propagation limit + + EXAMPLES:: + + sage: from sage.sat.solvers.picosat import PicoSAT + sage: solver = PicoSAT() # optional - pycosat + """ + def __init__(self, verbosity=0, prop_limit=0): + r""" + Constuct a new PicoSAT instance. + + See the documentation class for the description of inputs. + + EXAMPLES:: + + sage: from sage.sat.solvers.picosat import PicoSAT + sage: solver = PicoSAT() # optional - pycosat + """ + self._verbosity = int(verbosity) + if prop_limit is None: + self._prop_limit = 0 + else: + self._prop_limit = int(prop_limit) + try: + import pycosat + except ImportError: + from sage.misc.package import PackageNotFoundError + raise PackageNotFoundError("pycosat") + self._solve = pycosat.solve + self._nvars = 0 + self._clauses = [] + + def var(self, decision=None): + r""" + Return a *new* variable. + + INPUT: + + - ``decision`` -- ignored; accepted for compatibility with other solvers + + EXAMPLES:: + + sage: from sage.sat.solvers.picosat import PicoSAT + sage: solver = PicoSAT() # optional - pycosat + sage: solver.var() # optional - pycosat + 1 + + sage: solver.add_clause((-1,2,-4)) # optional - pycosat + sage: solver.var() # optional - pycosat + 5 + """ + return self._nvars + 1 + + def nvars(self): + r""" + Return the number of variables. Note that for compatibility with DIMACS + convention, the number of variables corresponds to the maximal index of + the variables used. + + EXAMPLES:: + + sage: from sage.sat.solvers.picosat import PicoSAT + sage: solver = PicoSAT() # optional - pycosat + sage: solver.nvars() # optional - pycosat + 0 + + If a variable with intermediate index is not used, it is still + considered as a variable:: + + sage: solver.add_clause((1,-2,4)) # optional - pycosat + sage: solver.nvars() # optional - pycosat + 4 + """ + return self._nvars + + def add_clause(self, lits): + r""" + Add a new clause to set of clauses. + + INPUT: + + - ``lits`` -- a tuple of nonzero integers + + .. NOTE:: + + If any element ``e`` in ``lits`` has ``abs(e)`` greater + than the number of variables generated so far, then new + variables are created automatically. + + EXAMPLES:: + + sage: from sage.sat.solvers.picosat import PicoSAT + sage: solver = PicoSAT() # optional - pycosat + sage: solver.add_clause((1, -2 , 3)) # optional - pycosat + """ + if 0 in lits: + raise ValueError("0 should not appear in the clause: {}".format(lits)) + # pycosat does not handle Sage integers + lits = [int(i) for i in lits] + self._nvars = max(self._nvars, max(abs(i) for i in lits)) + self._clauses.append(lits) + + def __call__(self, assumptions=None): + r""" + Solve this instance. + + OUTPUT: + + - If this instance is SAT: A tuple of length ``nvars() + 1``, + where the ``i``-th entry holds an assignment for the + ``i``-th variables (the ``0``-th entry is always ``None``). + + - If this instance is UNSAT: ``False``. + + EXAMPLES:: + + sage: from sage.sat.solvers.picosat import PicoSAT + sage: solver = PicoSAT() # optional - pycosat + sage: solver.add_clause((1,2)) # optional - pycosat + sage: solver.add_clause((-1,2)) # optional - pycosat + sage: solver.add_clause((-1,-2)) # optional - pycosat + sage: solver() # optional - pycosat + (None, False, True) + + sage: solver.add_clause((1,-2)) # optional - pycosat + sage: solver() # optional - pycosat + False + """ + #import pycosat + #self._solve = pycosat.solve + sol = self._solve(self._clauses, verbose=self._verbosity, + prop_limit=self._prop_limit, vars=self._nvars) + # sol = pycosat.solve(self._clauses) + if sol == 'UNSAT': + return False + else: + return (None,) + tuple([s > 0 for s in sol]) + + def __repr__(self): + r""" + TESTS:: + + sage: from sage.sat.solvers.picosat import PicoSAT + sage: solver = PicoSAT() # optional - pycosat + sage: solver # optional - pycosat + PicoSAT solver: 0 variables, 0 clauses. + """ + return "PicoSAT solver: {} variables, {} clauses.".format(self.nvars(), len(self.clauses())) + + def clauses(self, filename=None): + r""" + Return original clauses. + + INPUT: + + - ``filename`` -- (optional) if given, clauses are written to + ``filename`` in DIMACS format + + OUTPUT: + + If ``filename`` is ``None`` then a list of ``lits`` is returned, + where ``lits`` is a list of literals. + + If ``filename`` points to a writable file, then the list of original + clauses is written to that file in DIMACS format. + + EXAMPLES:: + + sage: from sage.sat.solvers.picosat import PicoSAT + sage: solver = PicoSAT() # optional - pycosat + sage: solver.add_clause((1,2,3,4,5,6,7,8,-9)) # optional - pycosat + sage: solver.clauses() # optional - pycosat + [[1, 2, 3, 4, 5, 6, 7, 8, -9]] + + DIMACS format output:: + + sage: from sage.sat.solvers.picosat import PicoSAT + sage: solver = PicoSAT() # optional - pycosat + sage: solver.add_clause((1, 2, 4)) # optional - pycosat + sage: solver.add_clause((1, 2, -4)) # optional - pycosat + sage: fn = tmp_filename() # optional - pycosat + sage: solver.clauses(fn) # optional - pycosat + sage: print(open(fn).read()) # optional - pycosat + p cnf 4 2 + 1 2 4 0 + 1 2 -4 0 + + """ + if filename is None: + return self._clauses + else: + from sage.sat.solvers.dimacs import DIMACS + DIMACS.render_dimacs(self._clauses, filename, self.nvars()) + diff --git a/src/sage/sat/solvers/satsolver.pyx b/src/sage/sat/solvers/satsolver.pyx index a624f68ccad..b7499fcae8a 100644 --- a/src/sage/sat/solvers/satsolver.pyx +++ b/src/sage/sat/solvers/satsolver.pyx @@ -91,22 +91,25 @@ cdef class SatSolver: r""" Reads DIMAC files. - Reads in DIMAC formatted lines (lazily) from a - file or file object and adds the corresponding - clauses into this solver instance. Note that the + Reads in DIMAC formatted lines (lazily) from a file or file object and + adds the corresponding clauses into this solver instance. Note that the DIMACS format is not well specified, see http://people.sc.fsu.edu/~jburkardt/data/cnf/cnf.html, - http://www.satcompetition.org/2009/format-benchmarks2009.html, - and http://elis.dvo.ru/~lab_11/glpk-doc/cnfsat.pdf. - The differences were summarized in the discussion on - the ticket :trac:`16924`. This method assumes the following - DIMACS format + http://www.satcompetition.org/2009/format-benchmarks2009.html, and + http://elis.dvo.ru/~lab_11/glpk-doc/cnfsat.pdf. + + The differences were summarized in the discussion on the ticket + :trac:`16924`. This method assumes the following DIMACS format: - Any line starting with "c" is a comment - Any line starting with "p" is a header - Any variable 1-n can be used - Every line containing a clause must end with a "0" + The format is extended to allow lines starting with "x" defining ``xor`` + clauses, with the notation introduced in cryptominisat, see + https://www.msoos.org/xor-clauses/ + INPUT: - ``filename`` - The name of a file as a string or a file object @@ -120,8 +123,31 @@ cdef class SatSolver: sage: solver.read(file_object) sage: solver.clauses() [((1, -3), False, None), ((2, 3, -1), False, None)] + + With xor clauses:: + + sage: from six import StringIO # for python 2/3 support + sage: file_object = StringIO("c A sample .cnf file with xor clauses.\np cnf 3 3\n1 2 0\n3 0\nx1 2 3 0") + sage: from sage.sat.solvers.cryptominisat import CryptoMiniSat # optional - cryptominisat + sage: solver = CryptoMiniSat() # optional - cryptominisat + sage: solver.read(file_object) # optional - cryptominisat + sage: solver.clauses() # optional - cryptominisat + [((1, 2), False, None), ((3,), False, None), ((1, 2, 3), True, True)] + sage: solver() # optional - cryptominisat + (None, True, True, True) + + TESTS:: + + sage: from six import StringIO # for python 2/3 support + sage: file_object = StringIO("c A sample .cnf file with xor clauses.\np cnf 3 3\n1 2 0\n3 0\nx1 2 3 0") + sage: from sage.sat.solvers.sat_lp import SatLP + sage: solver = SatLP() + sage: solver.read(file_object) + Traceback (most recent call last): + ... + NotImplementedError: the solver "an ILP-based SAT Solver" does not support xor clauses """ - if isinstance(filename,str): + if isinstance(filename, str): file_object = open(filename, "r") else: file_object = filename @@ -130,10 +156,20 @@ cdef class SatSolver: continue # comment if line.startswith("p"): continue # header - line = line.split(" ") - clause = [int(e) for e in line if e] - clause = clause[:-1] # strip trailing zero - self.add_clause(clause) + if line.startswith("x"): + line = line[1:].split(" ") + clause = [int(e) for e in line if e] + clause = clause[:-1] # strip trailing zero + try: + self.add_xor_clause(clause) + except AttributeError: + file_object.close() + raise NotImplementedError('the solver "{}" does not support xor clauses'.format(self)) + else: + line = line.split(" ") + clause = [int(e) for e in line if e] + clause = clause[:-1] # strip trailing zero + self.add_clause(clause) def __call__(self, assumptions=None): """ @@ -289,11 +325,13 @@ def SAT(solver=None, *args, **kwds): - ``"cryptominisat"`` -- note that the cryptominisat package must be installed. + - ``"picosat"`` -- note that the pycosat package must be installed. + - ``"LP"`` -- use :class:`~sage.sat.solvers.sat_lp.SatLP` to solve the SAT instance. - - ``None`` (default) -- use CryptoMiniSat if available, and a LP solver - otherwise. + - ``None`` (default) -- use CryptoMiniSat if available, else PicoSAT if + available, and a LP solver otherwise. EXAMPLES:: @@ -311,17 +349,27 @@ def SAT(solver=None, *args, **kwds): sage: SAT(solver="cryptominisat") # optional - cryptominisat CryptoMiniSat solver: 0 variables, 0 clauses. + + Forcing PicoSat:: + + sage: SAT(solver="picosat") # optional - pycosat + PicoSAT solver: 0 variables, 0 clauses. """ if solver is None: import pkgutil - if pkgutil.find_loader('pycryptosat') is None: - solver = "LP" - else: + if pkgutil.find_loader('pycryptosat') is not None: solver = "cryptominisat" + elif pkgutil.find_loader('pycosat') is not None: + solver = "picosat" + else: + solver = "LP" if solver == 'cryptominisat': from sage.sat.solvers.cryptominisat import CryptoMiniSat return CryptoMiniSat(*args, **kwds) + elif solver == 'picosat': + from sage.sat.solvers.picosat import PicoSAT + return PicoSAT(*args, **kwds) elif solver == "LP": from .sat_lp import SatLP return SatLP() diff --git a/src/sage/schemes/elliptic_curves/ell_generic.py b/src/sage/schemes/elliptic_curves/ell_generic.py index b9cd5bc3c92..1d2f6ee229d 100644 --- a/src/sage/schemes/elliptic_curves/ell_generic.py +++ b/src/sage/schemes/elliptic_curves/ell_generic.py @@ -2923,6 +2923,7 @@ def hyperelliptic_polynomials(self): a1, a2, a3, a4, a6 = self.ainvs() return R([a6, a4, a2, 1]), R([a3, a1]) + @cached_method def pari_curve(self): """ Return the PARI curve corresponding to this elliptic curve. @@ -2954,6 +2955,21 @@ def pari_curve(self): sage: E.j_invariant() 3*5^-1 + O(5) + Over a number field:: + + sage: K. = QuadraticField(2) + sage: E = EllipticCurve([1,a]) + sage: E.pari_curve() + [Mod(0, y^2 - 2), Mod(0, y^2 - 2), Mod(0, y^2 - 2), Mod(1, y^2 - 2), + Mod(y, y^2 - 2), Mod(0, y^2 - 2), Mod(2, y^2 - 2), Mod(4*y, y^2 - 2), + Mod(-1, y^2 - 2), Mod(-48, y^2 - 2), Mod(-864*y, y^2 - 2), + Mod(-928, y^2 - 2), Mod(3456/29, y^2 - 2), Vecsmall([5]), + [[y^2 - 2, [2, 0], 8, 1, [[1, -1.41421356237310; + 1, 1.41421356237310], [1, -1.41421356237310; 1, 1.41421356237310], + [1, -1; 1, 1], [2, 0; 0, 4], [4, 0; 0, 2], [2, 0; 0, 1], + [2, [0, 2; 1, 0]], []], [-1.41421356237310, 1.41421356237310], + [1, y], [1, 0; 0, 1], [1, 0, 0, 2; 0, 1, 1, 0]]], [0, 0, 0, 0, 0]] + PARI no longer requires that the `j`-invariant has negative `p`-adic valuation:: sage: E = EllipticCurve(Qp,[1, 1]) @@ -2962,14 +2978,12 @@ def pari_curve(self): sage: E.pari_curve() [0, 0, 0, 1, 1, 0, 2, 4, -1, -48, -864, -496, 6912/31, Vecsmall([2]), [O(5^3)], [0, 0]] """ - try: - return self._pari_curve - except AttributeError: - pass - - from sage.libs.pari.all import pari - self._pari_curve = pari(list(self.a_invariants())).ellinit() - return self._pari_curve + from sage.categories.number_fields import NumberFields + from sage.libs.pari import pari + if self.base_ring() in NumberFields(): + return pari.ellinit(self.a_invariants(), self.base_ring()) + else: + return pari.ellinit(self.a_invariants()) # This method is defined so that pari(E) returns exactly the same # as E.pari_curve(). This works even for classes that inherit from diff --git a/src/sage/version.py b/src/sage/version.py index 951110298cf..38c1599def4 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,5 +1,5 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '8.4.beta6' -date = '2018-09-22' -banner = 'SageMath version 8.4.beta6, Release Date: 2018-09-22' +version = '8.4.beta7' +date = '2018-09-30' +banner = 'SageMath version 8.4.beta7, Release Date: 2018-09-30'