diff --git a/.lgtm.yml b/.lgtm.yml index b0978fd288d..3696b57dacc 100644 --- a/.lgtm.yml +++ b/.lgtm.yml @@ -6,6 +6,11 @@ path_classifiers: imports_only: - "**/all.py" - "**/*catalog*.py" + - "**/species/library.py" + - "**/matroids/advanced.py" + - "**/categories/basic.py" + - "**/interacts/geometry.py" + - "**/combinat/ribbon.py" extraction: python: python_setup: diff --git a/VERSION.txt b/VERSION.txt index d44f31ea138..128a5e5d489 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 9.1.beta1, Release Date: 2020-01-21 +SageMath version 9.1.beta2, Release Date: 2020-01-26 diff --git a/bootstrap b/bootstrap index 7a80f157468..21517243ea2 100755 --- a/bootstrap +++ b/bootstrap @@ -75,7 +75,7 @@ install_config_rpath() { bootstrap () { rm -f m4/sage_spkg_configures.m4 spkg_configures="" - for filename in $(find build/pkgs -type f -name spkg-configure.m4); do + for filename in $(find build/pkgs -type f -name spkg-configure.m4 | sort); do pkgname="$(echo $filename | cut -d/ -f3)" echo "m4_sinclude([$filename])" >> m4/sage_spkg_configures.m4 spkg_configures="$spkg_configures diff --git a/build/make/deps b/build/make/deps index d12ae30da24..d85a084f188 100644 --- a/build/make/deps +++ b/build/make/deps @@ -170,6 +170,7 @@ sagelib: \ $(inst_ratpoints) \ $(inst_readline) \ $(inst_rw) \ + $(inst_sage_conf) \ $(inst_singular) \ $(inst_six) \ $(inst_symmetrica) \ diff --git a/build/make/install b/build/make/install index 70459c0138f..f67e640a6b9 100755 --- a/build/make/install +++ b/build/make/install @@ -72,38 +72,51 @@ The following package(s) may have failed to build (not necessarily during this run of 'make $@'): EOF - for f in "$SAGE_LOGS"/*.log; do + # Sort in chronological order by log file. + for f in `ls -tr "$SAGE_LOGS"/*.log 2>/dev/null`; do # Look for recent error message in log file. # Note that "tail -n 20 ..." doesn't work on Solaris. if tail -20 "$f" 2>/dev/null | grep "^Error" &>/dev/null; then base_f=`basename $f .log` + # stat(1) is not portable between Linux and macOS, so we extract it from ls -lf + timestamp=`ls -l $f | awk -F' ' '{print $6, $7, $8}'` 2> /dev/null cat >&2 <&2 </dev/null`; do # Look for recent error message in log file. # Note that "tail -n 20 ..." doesn't work on Solaris. if tail -100 "$f" 2>/dev/null | grep "^Error" &>/dev/null; then base_f=`basename $f .log` + # stat(1) is not portable between Linux and macOS, so we extract it from ls -lf + timestamp=`ls -l $f | awk -F' ' '{print $6, $7, $8}'` 2> /dev/null cat >&2 <&2 <rows[i][j/m4ri_radix] = mzd_read_bits(M, startrow+i, startcol+j, m4ri_radix); + S->rows[i][j/m4ri_radix] &= ~S->high_bitmask; + S->rows[i][j/m4ri_radix] |= mzd_read_bits(M, startrow+i, startcol+j, ncols - j) & S->high_bitmask; +diff --git a/tests/test_misc.c b/tests/test_misc.c +index a9a9547..b1a2e32 100644 +--- a/tests/test_misc.c ++++ b/tests/test_misc.c +@@ -76,6 +76,31 @@ int test_png(rci_t m, rci_t n) { + return ret; + } + ++int test_submatrix(const rci_t m, const rci_t n, const rci_t lowr, const rci_t lowc, const rci_t highr, const rci_t highc) { ++ printf("submatrix: m: %4d, n: %4d, (%4d, %4d, %4d, %4d)", m, n, lowr, lowc, highr, highc); ++ assert(highr-lowr > 0); ++ assert(highc-lowc > 0); ++ mzd_t *M = mzd_init(m, n); ++ mzd_randomize(M); ++ mzd_t *S = mzd_init(highr-lowr, highc-lowc); ++ mzd_submatrix(S, M, lowr, lowc, highr, highc); ++ int ret = 0; ++ for(rci_t i=0; irows[i] */ -- uint64_t dummy; /*!< ensures sizeof(mzd_t) == 64 */ -- - } mzd_t; - - /** diff --git a/build/pkgs/m4rie/checksums.ini b/build/pkgs/m4rie/checksums.ini index faa02bff414..6c4abb19145 100644 --- a/build/pkgs/m4rie/checksums.ini +++ b/build/pkgs/m4rie/checksums.ini @@ -1,4 +1,4 @@ tarball=m4rie-VERSION.tar.gz -sha1=d0c5407046131184fc34056d478c8b4e9e22bb1a -md5=c2c04cbfcc5d56ffdeb5133109272b8c -cksum=1547829740 +sha1=728524509dd30da2dc960814b61d0bae67f83043 +md5=e7685c63e7bab4c5a0922a161499ba83 +cksum=127676695 diff --git a/build/pkgs/m4rie/package-version.txt b/build/pkgs/m4rie/package-version.txt index 7f766d767b0..ba948c594d4 100644 --- a/build/pkgs/m4rie/package-version.txt +++ b/build/pkgs/m4rie/package-version.txt @@ -1 +1 @@ -20150908.p0 +20200115 diff --git a/build/pkgs/planarity/spkg-configure.m4 b/build/pkgs/planarity/spkg-configure.m4 new file mode 100644 index 00000000000..9ec8ddc64f6 --- /dev/null +++ b/build/pkgs/planarity/spkg-configure.m4 @@ -0,0 +1,20 @@ +SAGE_SPKG_CONFIGURE([planarity], [ + AC_LANG_PUSH([C]) + AC_CHECK_HEADER([planarity/planarity.h], [ + AC_CHECK_LIB([planarity], [gp_InitGraph], [ + AC_MSG_CHECKING([for planarity version 3.0 or later]) + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[#include ]], + [[vertexRec v;] + [v.link[0]=1;]]) + ], [ + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + sage_spkg_install_planarity=yes + ]) + ], [sage_spkg_install_planarity=yes]) + ], [sage_spkg_install_planarity=yes]) + AC_LANG_POP() +]) diff --git a/build/pkgs/sage_conf/README.rst b/build/pkgs/sage_conf/README.rst new file mode 100644 index 00000000000..604aa0c9b33 --- /dev/null +++ b/build/pkgs/sage_conf/README.rst @@ -0,0 +1,2 @@ +This is a module that provides configuration information to sagelib +at the time of its installation and at its runtime. diff --git a/build/pkgs/sage_conf/dependencies b/build/pkgs/sage_conf/dependencies new file mode 100644 index 00000000000..e52a88f906c --- /dev/null +++ b/build/pkgs/sage_conf/dependencies @@ -0,0 +1 @@ +$(PYTHON) ../pkgs/sage_conf/src/sage_conf.py ../pkgs/sage_conf/src/setup.cfg | pip setuptools diff --git a/build/pkgs/sage_conf/spkg-install b/build/pkgs/sage_conf/spkg-install new file mode 100755 index 00000000000..bdb5c41ac95 --- /dev/null +++ b/build/pkgs/sage_conf/spkg-install @@ -0,0 +1,12 @@ +#! /usr/bin/env bash +# From sage-spkg. +# For type=script packages, the build rule in build/make/Makefile sources +# sage-env but not sage-dist-helpers. +lib="$SAGE_ROOT/build/bin/sage-dist-helpers" +source "$lib" +if [ $? -ne 0 ]; then + echo >&2 "Error: failed to source $lib" + echo >&2 "Is $SAGE_ROOT the correct SAGE_ROOT?" + exit 1 +fi +cd $SAGE_ROOT/build/pkgs/sage_conf/src && sdh_pip_install . diff --git a/build/pkgs/sage_conf/src/.gitignore b/build/pkgs/sage_conf/src/.gitignore new file mode 100644 index 00000000000..c94082c9d9a --- /dev/null +++ b/build/pkgs/sage_conf/src/.gitignore @@ -0,0 +1,2 @@ +/sage_conf.py +/setup.cfg diff --git a/build/pkgs/sage_conf/src/sage_conf.py.in b/build/pkgs/sage_conf/src/sage_conf.py.in new file mode 100644 index 00000000000..44c8e8c7625 --- /dev/null +++ b/build/pkgs/sage_conf/src/sage_conf.py.in @@ -0,0 +1,31 @@ +# @configure_input@ + +VERSION = "@PACKAGE_VERSION@" + +MAXIMA = "@prefix@/bin/maxima" + +# The following must not be used during build to determine source or installation +# location of sagelib. See comments in SAGE_ROOT/src/Makefile.in +SAGE_LOCAL = "@prefix@" +SAGE_ROOT = "@abs_top_srcdir@" + +# Entry point 'sage-config'. It does not depend on any packages. + +def _main(): + from argparse import ArgumentParser + from sys import exit, stdout + parser = ArgumentParser() + parser.add_argument('--version', help="show version", action="version", + version='%(prog)s ' + VERSION) + parser.add_argument("VARIABLE", nargs='?', help="output the value of VARIABLE") + args = parser.parse_args() + d = globals() + if args.VARIABLE: + stdout.write('{}\n'.format(d[args.VARIABLE])) + else: + for k, v in d.items(): + if not k.startswith('_'): + stdout.write('{}={}\n'.format(k, v)) + +if __name__ == "__main__": + _main() diff --git a/build/pkgs/sage_conf/src/setup.cfg.in b/build/pkgs/sage_conf/src/setup.cfg.in new file mode 100644 index 00000000000..d4209eda75c --- /dev/null +++ b/build/pkgs/sage_conf/src/setup.cfg.in @@ -0,0 +1,19 @@ +# @configure_input@ + +[metadata] +name = sage_conf +version = @PACKAGE_VERSION@ +description = Sage: Open Source Mathematics Software: Configuration for sagelib +long_description = file: README.rst +license = GNU General Public License (GPL) v3 or later +author = The Sage Developers +author_email = https://groups.google.com/group/sage-support +url = https://www.sagemath.org + +[options] +py_modules = + sage_conf + +[options.entry_points] +console_scripts = + sage-config = sage_conf:_main diff --git a/build/pkgs/sage_conf/src/setup.py b/build/pkgs/sage_conf/src/setup.py new file mode 100644 index 00000000000..8bf1ba938af --- /dev/null +++ b/build/pkgs/sage_conf/src/setup.py @@ -0,0 +1,2 @@ +from setuptools import setup +setup() diff --git a/build/pkgs/sage_conf/type b/build/pkgs/sage_conf/type new file mode 100644 index 00000000000..84f7e31d99b --- /dev/null +++ b/build/pkgs/sage_conf/type @@ -0,0 +1 @@ +script diff --git a/build/pkgs/sqlite/spkg-configure.m4 b/build/pkgs/sqlite/spkg-configure.m4 new file mode 100644 index 00000000000..ff33a4546d6 --- /dev/null +++ b/build/pkgs/sqlite/spkg-configure.m4 @@ -0,0 +1,34 @@ +SAGE_SPKG_CONFIGURE([sqlite], [ + m4_pushdef([SAGE_SQLITE3_MIN_VERSION_MAJOR], [3]) + m4_pushdef([SAGE_SQLITE3_MIN_VERSION_MINOR], [8]) + m4_pushdef([SAGE_SQLITE3_MIN_VERSION_MICRO], [7]) + m4_pushdef([SAGE_SQLITE3_MIN_VERSION], [SAGE_SQLITE3_MIN_VERSION_MAJOR.SAGE_SQLITE3_MIN_VERSION_MINOR.SAGE_SQLITE3_MIN_VERSION_MICRO]) + AC_MSG_CHECKING([libsqlite3 >= sqlite3_min_version]) + dnl https://www.sqlite.org/c3ref/libversion.html + dnl https://www.sqlite.org/c3ref/c_source_id.html + SQLITE_SAVED_LIBS="$LIBS" + LIBS="$LIBS -lsqlite3" + AC_RUN_IFELSE([ + AC_LANG_PROGRAM([[ + #include + #include + #include + #include + ]], + [[ + assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 ); + if (SQLITE_VERSION_NUMBER < ]]SAGE_SQLITE3_MIN_VERSION_MAJOR[[*1000000 + ]]SAGE_SQLITE3_MIN_VERSION_MINOR[[*1000 + ]]SAGE_SQLITE3_MIN_VERSION_MICRO[[) + exit(1); + else + exit(0); + ]]) + ], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + LIBS="$SQLITE_SAVED_LIBS" + sage_spkg_install_sqlite=yes]) + m4_popdef([SAGE_SQLITE3_MIN_VERSION_MAJOR]) + m4_popdef([SAGE_SQLITE3_MIN_VERSION_MINOR]) + m4_popdef([SAGE_SQLITE3_MIN_VERSION_MICRO]) + m4_popdef([SAGE_SQLITE3_MIN_VERSION]) +]) diff --git a/build/pkgs/tides/spkg-check b/build/pkgs/tides/spkg-check index 27cd9419538..709f4602cbc 100644 --- a/build/pkgs/tides/spkg-check +++ b/build/pkgs/tides/spkg-check @@ -1,2 +1,2 @@ cd src -$MAKE check +sdh_make check AM_CFLAGS="" AM_FFLAGS="" diff --git a/build/sage_bootstrap/download/app.py b/build/sage_bootstrap/download/app.py index 31083b57a2c..69acdb78e2d 100644 --- a/build/sage_bootstrap/download/app.py +++ b/build/sage_bootstrap/download/app.py @@ -4,18 +4,16 @@ """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2016 Volker Braun # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** -import os -import sys import logging log = logging.getLogger() diff --git a/build/sage_bootstrap/download/cmdline.py b/build/sage_bootstrap/download/cmdline.py index dc3ef44cd84..c26e9d08e35 100644 --- a/build/sage_bootstrap/download/cmdline.py +++ b/build/sage_bootstrap/download/cmdline.py @@ -5,18 +5,16 @@ This module handles the main "sage-download-file" commandline utility. """ - -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2016 Volker Braun # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** -import os import sys import logging log = logging.getLogger() diff --git a/configure.ac b/configure.ac index 0dcc489375b..07c88a3229b 100644 --- a/configure.ac +++ b/configure.ac @@ -449,6 +449,8 @@ dnl AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([build/make/Makefile-auto build/make/Makefile src/Makefile]) AC_CONFIG_FILES([src/bin/sage-env-config]) +AC_CONFIG_FILES([build/pkgs/sage_conf/src/sage_conf.py build/pkgs/sage_conf/src/setup.cfg]) + dnl Create basic directories needed for Sage AC_CONFIG_COMMANDS(mkdirs, [ diff --git a/m4/sage_spkg_configure.m4 b/m4/sage_spkg_configure.m4 index d6d7bc672e0..e47c7c458fb 100644 --- a/m4/sage_spkg_configure.m4 +++ b/m4/sage_spkg_configure.m4 @@ -51,13 +51,11 @@ m4_pushdef([SPKG_USE_SYSTEM], [sage_use_system_]SPKG_NAME) # BEGIN SAGE_SPKG_CONFIGURE_]m4_toupper($1)[ AC_MSG_NOTICE([=== checking whether to install the $1 SPKG ===]) AC_ARG_WITH([system-]SPKG_NAME, - AS_HELP_STRING(--with-system-SPKG_NAME, - [detect and use an existing system SPKG_NAME (default is yes)]), + AS_HELP_STRING(--with-system-SPKG_NAME={no|yes (default)|force (exit with an error if no usable version is found)}, + [detect and use an existing system SPKG_NAME]), [AS_VAR_SET(SPKG_USE_SYSTEM, [$withval])], [AS_VAR_SET(SPKG_USE_SYSTEM, [yes])] ) -m4_divert_once([HELP_WITH], AS_HELP_STRING(--with-system-SPKG_NAME=force, - [require use of an existing system SPKG_NAME])) AS_VAR_SET([sage_spkg_name], SPKG_NAME) diff --git a/src/bin/sage-preparse b/src/bin/sage-preparse index 5cf95f6d87b..4ee9cdc1856 100755 --- a/src/bin/sage-preparse +++ b/src/bin/sage-preparse @@ -88,11 +88,12 @@ def do_preparse(f, files_before=[]): fname = f + ".py" if os.path.exists(fname): - if AUTOGEN_MSG not in open(fname).read(): - print("Refusing to overwrite existing non-autogenerated file {!r}." - .format(os.path.abspath(fname))) - print("Please delete or move this file manually.") - sys.exit(1) + with open(fname) as fin: + if AUTOGEN_MSG not in fin.read(): + print("Refusing to overwrite existing non-autogenerated file {!r}." + .format(os.path.abspath(fname))) + print("Please delete or move this file manually.") + sys.exit(1) # TODO: # I am commenting this "intelligence" out, since, e.g., if I change @@ -104,7 +105,8 @@ def do_preparse(f, files_before=[]): # return # Finally open the file - F = open(f).read() + with open(f) as fin: + F = fin.read() # Check to see if a coding is specified in the .sage file. If it is, # then we want to copy it over to the new file and not include it in diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index dc09574db4e..712f84c4041 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='9.1.beta1' -SAGE_RELEASE_DATE='2020-01-21' -SAGE_VERSION_BANNER='SageMath version 9.1.beta1, Release Date: 2020-01-21' +SAGE_VERSION='9.1.beta2' +SAGE_RELEASE_DATE='2020-01-26' +SAGE_VERSION_BANNER='SageMath version 9.1.beta2, Release Date: 2020-01-26' diff --git a/src/doc/en/reference/categories/index.rst b/src/doc/en/reference/categories/index.rst index 8fbc0964f3a..e09e3e24e4d 100644 --- a/src/doc/en/reference/categories/index.rst +++ b/src/doc/en/reference/categories/index.rst @@ -177,6 +177,7 @@ Individual Categories sage/categories/super_modules sage/categories/super_modules_with_basis sage/categories/supercommutative_algebras + sage/categories/supercrystals sage/categories/topological_spaces sage/categories/triangular_kac_moody_algebras sage/categories/unique_factorization_domains diff --git a/src/doc/en/reference/power_series/index.rst b/src/doc/en/reference/power_series/index.rst index c30f4b49467..f32608df685 100644 --- a/src/doc/en/reference/power_series/index.rst +++ b/src/doc/en/reference/power_series/index.rst @@ -19,6 +19,9 @@ Power Series Rings and Laurent Series Rings sage/rings/lazy_laurent_series_ring sage/rings/lazy_laurent_series_operator + sage/rings/puiseux_series_ring + sage/rings/puiseux_series_ring_element + sage/rings/tate_algebra .. include:: ../footer.txt diff --git a/src/doc/en/reference/rings/index.rst b/src/doc/en/reference/rings/index.rst index 925f0422609..2f24df507b9 100644 --- a/src/doc/en/reference/rings/index.rst +++ b/src/doc/en/reference/rings/index.rst @@ -46,6 +46,16 @@ Fraction Fields sage/rings/fraction_field sage/rings/fraction_field_element +Ring Extensions +--------------- + +.. toctree:: + :maxdepth: 2 + + sage/rings/ring_extension + sage/rings/ring_extension_element + sage/rings/ring_extension_morphism + Utilities --------- diff --git a/src/fpickle_setup.py b/src/fpickle_setup.py index f9572d2546d..1c800a7f012 100644 --- a/src/fpickle_setup.py +++ b/src/fpickle_setup.py @@ -4,7 +4,6 @@ a library file. It solves the issue from :trac:`11874`. """ import types -import copy from six.moves import copyreg from six import get_method_function, get_method_self diff --git a/src/module_list.py b/src/module_list.py index c1adcf006cc..a535785d23d 100644 --- a/src/module_list.py +++ b/src/module_list.py @@ -1192,6 +1192,9 @@ def uname_specific(name, value, alternative): Extension('sage.rings.tate_algebra_ideal', sources = ['sage/rings/tate_algebra_ideal.pyx']), + Extension('sage.rings.puiseux_series_ring_element', + sources = ['sage/rings/puiseux_series_ring_element.pyx']), + Extension('sage.rings.rational', sources = ['sage/rings/rational.pyx'], libraries=['ntl']), @@ -1220,6 +1223,18 @@ def uname_specific(name, value, alternative): Extension('sage.rings.ring', sources = ['sage/rings/ring.pyx']), + Extension('sage.rings.ring_extension', + sources = ['sage/rings/ring_extension.pyx']), + + Extension('sage.rings.ring_extension_element', + sources = ['sage/rings/ring_extension_element.pyx']), + + Extension('sage.rings.ring_extension_morphism', + sources = ['sage/rings/ring_extension_morphism.pyx']), + + Extension('sage.rings.ring_extension_conversion', + sources = ['sage/rings/ring_extension_conversion.pyx']), + Extension('*', ['sage/rings/convert/*.pyx']), ################################ diff --git a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_ideal.py b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_ideal.py index a1b5ea3083e..1ec0a14c3a2 100644 --- a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_ideal.py +++ b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_ideal.py @@ -3,15 +3,15 @@ """ from __future__ import absolute_import -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2011 Johan Bosman # Copyright (C) 2011, 2013 Peter Bruin # # 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/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from .finite_dimensional_algebra_element import FiniteDimensionalAlgebraElement @@ -19,7 +19,6 @@ from sage.structure.element import is_Matrix from sage.rings.ideal import Ideal_generic from sage.structure.element import parent -from sage.structure.sage_object import SageObject from sage.misc.cachefunc import cached_method from functools import reduce diff --git a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_morphism.py b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_morphism.py index 9f80a285edd..f339848c02f 100644 --- a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_morphism.py +++ b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_morphism.py @@ -2,23 +2,22 @@ Morphisms Between Finite Algebras """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2011 Johan Bosman # Copyright (C) 2011, 2013 Peter Bruin # # 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/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.misc.cachefunc import cached_method -from sage.categories.homset import Hom from sage.rings.morphism import RingHomomorphism_im_gens from sage.rings.homset import RingHomset_generic -from sage.matrix.constructor import matrix from sage.structure.element import is_Matrix + class FiniteDimensionalAlgebraMorphism(RingHomomorphism_im_gens): """ Create a morphism between two :class:`finite-dimensional algebras `. diff --git a/src/sage/algebras/letterplace/free_algebra_letterplace.pyx b/src/sage/algebras/letterplace/free_algebra_letterplace.pyx index 7a8400052ee..a9d09adc4ff 100644 --- a/src/sage/algebras/letterplace/free_algebra_letterplace.pyx +++ b/src/sage/algebras/letterplace/free_algebra_letterplace.pyx @@ -403,7 +403,7 @@ cdef class FreeAlgebra_letterplace(Algebra): """ return self.__ngens-self._nb_slackvars <= 1 - def is_field(self): + def is_field(self, proof=True): """ Tell whether this free algebra is a field. @@ -419,7 +419,7 @@ cdef class FreeAlgebra_letterplace(Algebra): False """ - return (not (self.__ngens-self._nb_slackvars)) and self._base.is_field() + return (not (self.__ngens-self._nb_slackvars)) and self._base.is_field(proof=proof) def _repr_(self): """ diff --git a/src/sage/all.py b/src/sage/all.py index 460adc79968..97f01b40055 100644 --- a/src/sage/all.py +++ b/src/sage/all.py @@ -68,6 +68,40 @@ import operator import math +############ setup warning filters before importing Sage stuff #### +import warnings + +__with_pydebug = hasattr(sys, 'gettotalrefcount') # This is a Python debug build (--with-pydebug) +if __with_pydebug: + # a debug build does not install the default warning filters. Sadly, this breaks doctests so we + # have to re-add them: + warnings.filterwarnings('ignore', category=PendingDeprecationWarning) + warnings.filterwarnings('ignore', category=ImportWarning) + warnings.filterwarnings('ignore', category=ResourceWarning) +else: + warnings.filters.remove(('ignore', None, DeprecationWarning, None, 0)) + +# The psutil swap_memory() function tries to collect some statistics +# that may not be available and that we don't need. Hide the warnings +# that are emitted if the stats aren't available (Trac #28329). That +# function is called in two places, so let's install this filter +# before the first one is imported from sage.misc.all below. +warnings.filterwarnings('ignore', category=RuntimeWarning, + message=r"'sin' and 'sout' swap memory stats couldn't be determined") + +# Ignore all deprecations from IPython etc. +warnings.filterwarnings('ignore', category=DeprecationWarning, + module='.*(IPython|ipykernel|jupyter_client|jupyter_core|nbformat|notebook|ipywidgets|storemagic)') +# Ignore collections.abc warnings, there are a lot of them but they are +# harmless. +warnings.filterwarnings('ignore', category=DeprecationWarning, + message='.*collections[.]abc.*') +# However, be sure to keep OUR deprecation warnings +warnings.filterwarnings('default', category=DeprecationWarning, + message=r'[\s\S]*See https\?://trac\.sagemath\.org/[0-9]* for details.') +################ end setup warnings ############################### + + from sage.env import SAGE_ROOT, SAGE_SRC, SAGE_DOC_SRC, SAGE_LOCAL, DOT_SAGE, SAGE_ENV @@ -82,14 +116,6 @@ import sage.misc.lazy_import -# The psutil swap_memory() function tries to collect some statistics -# that may not be available and that we don't need. Hide the warnings -# that are emitted if the stats aren't available (Trac #28329). That -# function is called in two places, so let's install this filter -# before the first one is imported from sage.misc.all below. -import warnings -warnings.filterwarnings('ignore', category=RuntimeWarning, - message=r"'sin' and 'sout' swap memory stats couldn't be determined") from sage.misc.all import * # takes a while from sage.typeset.all import * from sage.repl.all import * @@ -310,21 +336,6 @@ def _write_started_file(): O.close() -try: - warnings.filters.remove(('ignore', None, DeprecationWarning, None, 0)) -except ValueError: - pass # SAGE_DEBUG=yes builds do not install default warning filters, ignore -# Ignore all deprecations from IPython etc. -warnings.filterwarnings('ignore', category=DeprecationWarning, - module='.*(IPython|ipykernel|jupyter_client|jupyter_core|nbformat|notebook|ipywidgets|storemagic)') -# Ignore collections.abc warnings, there are a lot of them but they are -# harmless. -warnings.filterwarnings('ignore', category=DeprecationWarning, - message='.*collections[.]abc.*') -# However, be sure to keep OUR deprecation warnings -warnings.filterwarnings('default', category=DeprecationWarning, - message=r'[\s\S]*See https\?://trac\.sagemath\.org/[0-9]* for details.') - # Set a new random number seed as the very last thing # (so that printing initial_seed() and using that seed # in set_random_seed() will result in the same sequence you got at diff --git a/src/sage/categories/action.pyx b/src/sage/categories/action.pyx index 7a007b4dba1..fb39aaafa0a 100644 --- a/src/sage/categories/action.pyx +++ b/src/sage/categories/action.pyx @@ -569,5 +569,3 @@ cdef class ActionEndomorphism(Morphism): return ActionEndomorphism(self._action, inv_g) else: return (~self._action)(self._g) - - diff --git a/src/sage/categories/commutative_rings.py b/src/sage/categories/commutative_rings.py index 2a1f5a0f7a4..ca292e5fd4e 100644 --- a/src/sage/categories/commutative_rings.py +++ b/src/sage/categories/commutative_rings.py @@ -85,6 +85,133 @@ def _test_divides(self, **options): else: tester.assertTrue(test) + def over(self, base=None, gen=None, gens=None, name=None, names=None): + r""" + Return this ring, considered as an extension of ``base``. + + INPUT: + + - ``base`` -- a commutative ring or a morphism or ``None`` + (default: ``None``); the base of this extension or its defining + morphism + + - ``gen`` -- a generator of this extension (over its base) or ``None`` + (default: ``None``); + + - ``gens`` -- a list of generators of this extension (over its base) + or ``None`` (default: ``None``); + + - ``name`` -- a variable name or ``None`` (default: ``None``) + + - ``names`` -- a list or a tuple of variable names or ``None`` + (default: ``None``) + + EXAMPLES: + + We construct an extension of finite fields:: + + sage: F = GF(5^2) + sage: k = GF(5^4) + sage: z4 = k.gen() + + sage: K = k.over(F) + sage: K + Field in z4 with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base + + If not explicitely given, the default generator of the top ring + (here k) is used and the same name is kept:: + + sage: K.gen() + z4 + sage: K(z4) + z4 + + However, it is possible to specify another generator and/or + another name. For example:: + + sage: Ka = k.over(F, name='a') + sage: Ka + Field in a with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base + sage: Ka.gen() + a + sage: Ka(z4) + a + + sage: Kb = k.over(F, gen=-z4+1, name='b') + sage: Kb + Field in b with defining polynomial x^2 + z2*x + 4 over its base + sage: Kb.gen() + b + sage: Kb(-z4+1) + b + + Note that the shortcut ``K.`` is also available:: + + sage: KKa. = k.over(F) + sage: KKa is Ka + True + + Building an extension on top of another extension is allowed:: + + sage: L = GF(5^12).over(K) + sage: L + Field in z12 with defining polynomial x^3 + (1 + (4*z2 + 2)*z4)*x^2 + (2 + 2*z4)*x - z4 over its base + sage: L.base_ring() + Field in z4 with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base + + The successive bases of an extension are accessible via the + method :meth:`sage.rings.ring_extension.RingExtension_generic.bases`:: + + sage: L.bases() + [Field in z12 with defining polynomial x^3 + (1 + (4*z2 + 2)*z4)*x^2 + (2 + 2*z4)*x - z4 over its base, + Field in z4 with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base, + Finite Field in z2 of size 5^2] + + When ``base`` is omitted, the canonical base of the ring is used:: + + sage: S. = QQ[] + sage: E = S.over() + sage: E + Univariate Polynomial Ring in x over Rational Field over its base + sage: E.base_ring() + Rational Field + + Here is an example where ``base`` is a defining morphism:: + + sage: k. = QQ.extension(x^2 - 2) + sage: l. = QQ.extension(x^4 - 2) + sage: f = k.hom([b^2]) + sage: L = l.over(f) + sage: L + Field in b with defining polynomial x^2 - a over its base + sage: L.base_ring() + Number Field in a with defining polynomial x^2 - 2 + + Similarly, one can create a tower of extensions:: + + sage: K = k.over() + sage: L = l.over(Hom(K,l)(f)) + sage: L + Field in b with defining polynomial x^2 - a over its base + sage: L.base_ring() + Field in a with defining polynomial x^2 - 2 over its base + sage: L.bases() + [Field in b with defining polynomial x^2 - a over its base, + Field in a with defining polynomial x^2 - 2 over its base, + Rational Field] + """ + from sage.rings.ring_extension import RingExtension + if name is not None: + if names is not None: + raise ValueError("keyword argument 'name' cannot be combined with 'names'") + names = (name,) + if gen is not None: + if gens is not None: + raise ValueError("keyword argument 'gen' cannot be combined with 'gens'") + gens = (gen,) + return RingExtension(self, base, gens, names) + + class ElementMethods: pass diff --git a/src/sage/categories/regular_supercrystals.py b/src/sage/categories/regular_supercrystals.py index cccb2e94fa3..a4cc095f51c 100644 --- a/src/sage/categories/regular_supercrystals.py +++ b/src/sage/categories/regular_supercrystals.py @@ -17,7 +17,7 @@ from sage.misc.cachefunc import cached_method from sage.categories.category_singleton import Category_singleton -from sage.categories.crystals import Crystals +from sage.categories.supercrystals import SuperCrystals from sage.categories.tensor import TensorProductsCategory @@ -32,7 +32,7 @@ class RegularSuperCrystals(Category_singleton): sage: C Category of regular super crystals sage: C.super_categories() - [Category of finite crystals] + [Category of finite super crystals] Parents in this category should implement the following methods: @@ -96,287 +96,9 @@ def super_categories(self): sage: from sage.categories.regular_supercrystals import RegularSuperCrystals sage: C = RegularSuperCrystals() sage: C.super_categories() - [Category of finite crystals] + [Category of finite super crystals] """ - return [Crystals().Finite()] - - class ParentMethods: - @cached_method - def digraph(self): - r""" - Return the :class:`DiGraph` associated to ``self``. - - EXAMPLES:: - - sage: B = crystals.Letters(['A', [1,3]]) - sage: G = B.digraph(); G - Multi-digraph on 6 vertices - sage: Q = crystals.Letters(['Q',3]) - sage: G = Q.digraph(); G - Multi-digraph on 3 vertices - sage: G.edges() - [(1, 2, -1), (1, 2, 1), (2, 3, -2), (2, 3, 2)] - - The edges of the crystal graph are by default colored using - blue for edge 1, red for edge 2, green for edge 3, and dashed with - the corresponding color for barred edges. Edge 0 is dotted black:: - - sage: view(G) # optional - dot2tex graphviz, not tested (opens external window) - """ - from sage.graphs.digraph import DiGraph - from sage.misc.latex import LatexExpr - from sage.combinat.root_system.cartan_type import CartanType - - G = DiGraph(multiedges=True) - G.add_vertices(self) - for i in self.index_set(): - for x in G: - y = x.f(i) - if y is not None: - G.add_edge(x, y, i) - - def edge_options(data): - u, v, l = data - edge_opts = { 'edge_string': '->', 'color': 'black' } - if l > 0: - edge_opts['color'] = CartanType._colors.get(l, 'black') - edge_opts['label'] = LatexExpr(str(l)) - elif l < 0: - edge_opts['color'] = "dashed," + CartanType._colors.get(-l, 'black') - edge_opts['label'] = LatexExpr("\\overline{%s}" % str(-l)) - else: - edge_opts['color'] = "dotted," + CartanType._colors.get(l, 'black') - edge_opts['label'] = LatexExpr(str(l)) - return edge_opts - - G.set_latex_options(format="dot2tex", edge_labels=True, edge_options=edge_options) - - return G - - def genuine_highest_weight_vectors(self): - r""" - Return the tuple of genuine highest weight elements of ``self``. - - EXAMPLES:: - - sage: B = crystals.Letters(['A', [1,2]]) - sage: B.genuine_highest_weight_vectors() - (-2,) - - sage: T = B.tensor(B) - sage: T.genuine_highest_weight_vectors() - ([-2, -1], [-2, -2]) - sage: s1, s2 = T.connected_components() - sage: s = s1 + s2 - sage: s.genuine_highest_weight_vectors() - ([-2, -1], [-2, -2]) - """ - return tuple([x[0] for x in self._genuine_highest_lowest_weight_vectors()]) - - connected_components_generators = genuine_highest_weight_vectors - - def connected_components(self): - r""" - Return the connected components of ``self`` as subcrystals. - - EXAMPLES:: - - sage: B = crystals.Letters(['A', [1,2]]) - sage: B.connected_components() - [Subcrystal of The crystal of letters for type ['A', [1, 2]]] - - sage: T = B.tensor(B) - sage: T.connected_components() - [Subcrystal of Full tensor product of the crystals - [The crystal of letters for type ['A', [1, 2]], - The crystal of letters for type ['A', [1, 2]]], - Subcrystal of Full tensor product of the crystals - [The crystal of letters for type ['A', [1, 2]], - The crystal of letters for type ['A', [1, 2]]]] - """ - category = RegularSuperCrystals() - index_set = self.index_set() - cartan_type = self.cartan_type() - CCs = [] - - for mg in self.connected_components_generators(): - if not isinstance(mg, tuple): - mg = (mg,) - subcrystal = self.subcrystal(generators=mg, - index_set=index_set, - cartan_type=cartan_type, - category=category) - CCs.append(subcrystal) - - return CCs - - def genuine_lowest_weight_vectors(self): - r""" - Return the tuple of genuine lowest weight elements of ``self``. - - EXAMPLES:: - - sage: B = crystals.Letters(['A', [1,2]]) - sage: B.genuine_lowest_weight_vectors() - (3,) - - sage: T = B.tensor(B) - sage: T.genuine_lowest_weight_vectors() - ([3, 3], [3, 2]) - sage: s1, s2 = T.connected_components() - sage: s = s1 + s2 - sage: s.genuine_lowest_weight_vectors() - ([3, 3], [3, 2]) - """ - return tuple([x[1] for x in self._genuine_highest_lowest_weight_vectors()]) - - @cached_method - def _genuine_highest_lowest_weight_vectors(self): - r""" - Return the genuine lowest and highest weight elements of ``self``. - - EXAMPLES:: - - sage: B = crystals.Letters(['A', [1,2]]) - sage: B._genuine_highest_lowest_weight_vectors() - ((-2, 3),) - - sage: T = B.tensor(B) - sage: T._genuine_highest_lowest_weight_vectors() - (([-2, -1], [3, 3]), ([-2, -2], [3, 2])) - sage: s1, s2 = T.connected_components() - sage: s = s1 + s2 - sage: s._genuine_highest_lowest_weight_vectors() - (([-2, -1], [3, 3]), ([-2, -2], [3, 2])) - - An example with fake highest/lowest weight elements - from [BKK2000]_:: - - sage: B = crystals.Tableaux(['A', [1,1]], shape=[3,2,1]) - sage: B._genuine_highest_lowest_weight_vectors() - (([[-2, -2, -2], [-1, -1], [1]], [[-1, 1, 2], [1, 2], [2]]),) - """ - X = [] - for G in self.digraph().connected_components_subgraphs(): - src = G.sources() - sinks = G.sinks() - max_dist = -1 - pair = None - for s in src: - for t in sinks: - d = G.distance(s, t) - if d < float('inf') and d > max_dist: - pair = (s, t) - max_dist = d - X.append(pair) - return tuple(X) - - def tensor(self, *crystals, **options): - """ - Return the tensor product of ``self`` with the crystals ``B``. - - EXAMPLES:: - - sage: B = crystals.Letters(['A',[1,2]]) - sage: C = crystals.Tableaux(['A',[1,2]], shape = [2,1]) - sage: T = C.tensor(B); T - Full tensor product of the crystals [Crystal of BKK tableaux of shape [2, 1] of gl(2|3), - The crystal of letters for type ['A', [1, 2]]] - sage: S = B.tensor(C); S - Full tensor product of the crystals [The crystal of letters for type ['A', [1, 2]], - Crystal of BKK tableaux of shape [2, 1] of gl(2|3)] - sage: G = T.digraph() - sage: H = S.digraph() - sage: G.is_isomorphic(H, edge_labels= True) - True - """ - cartan_type = self.cartan_type() - if any(c.cartan_type() != cartan_type for c in crystals): - raise ValueError("all crystals must be of the same Cartan type") - - if cartan_type.letter == 'Q': - from sage.combinat.crystals.tensor_product import FullTensorProductOfQueerSuperCrystals - return FullTensorProductOfQueerSuperCrystals((self,) + tuple(crystals), **options) - else: - from sage.combinat.crystals.tensor_product import FullTensorProductOfSuperCrystals - return FullTensorProductOfSuperCrystals((self,) + tuple(crystals), **options) - - def character(self): - """ - Return the character of ``self``. - - .. TODO:: - - Once the `WeylCharacterRing` is implemented, make this - consistent with the implementation in - :meth:`sage.categories.classical_crystals.ClassicalCrystals.ParentMethods.character`. - - EXAMPLES:: - - sage: B = crystals.Letters(['A',[1,2]]) - sage: B.character() - B[(1, 0, 0, 0, 0)] + B[(0, 1, 0, 0, 0)] + B[(0, 0, 1, 0, 0)] - + B[(0, 0, 0, 1, 0)] + B[(0, 0, 0, 0, 1)] - """ - from sage.rings.all import ZZ - A = self.weight_lattice_realization().algebra(ZZ) - return A.sum(A(x.weight()) for x in self) - - @cached_method - def highest_weight_vectors(self): - """ - Return the highest weight vectors of ``self``. - - EXAMPLES:: - - sage: B = crystals.Letters(['A', [1,2]]) - sage: B.highest_weight_vectors() - (-2,) - - sage: T = B.tensor(B) - sage: T.highest_weight_vectors() - ([-2, -2], [-2, -1]) - - We give an example from [BKK2000]_ that has fake - highest weight vectors:: - - sage: B = crystals.Tableaux(['A', [1,1]], shape=[3,2,1]) - sage: B.highest_weight_vectors() - ([[-2, -2, -2], [-1, -1], [1]], - [[-2, -2, -2], [-1, 2], [1]], - [[-2, -2, 2], [-1, -1], [1]]) - sage: B.genuine_highest_weight_vectors() - ([[-2, -2, -2], [-1, -1], [1]],) - """ - return tuple(self.digraph().sources()) - - @cached_method - def lowest_weight_vectors(self): - """ - Return the lowest weight vectors of ``self``. - - EXAMPLES:: - - sage: B = crystals.Letters(['A', [1,2]]) - sage: B.lowest_weight_vectors() - (3,) - - sage: T = B.tensor(B) - sage: sorted(T.lowest_weight_vectors()) - [[3, 2], [3, 3]] - - We give an example from [BKK2000]_ that has fake - lowest weight vectors:: - - sage: B = crystals.Tableaux(['A', [1,1]], shape=[3,2,1]) - sage: sorted(B.lowest_weight_vectors()) - [[[-2, 1, 2], [-1, 2], [1]], - [[-2, 1, 2], [-1, 2], [2]], - [[-1, 1, 2], [1, 2], [2]]] - sage: B.genuine_lowest_weight_vectors() - ([[-1, 1, 2], [1, 2], [2]],) - """ - return tuple(self.digraph().sinks()) + return [SuperCrystals().Finite()] class ElementMethods: def epsilon(self, i): @@ -429,72 +151,6 @@ def phi(self, i): else: string_length += 1 - def is_genuine_highest_weight(self, index_set=None): - """ - Return whether ``self`` is a genuine highest weight element. - - INPUT: - - - ``index_set`` -- (optional) the index set of the (sub)crystal - on which to check - - EXAMPLES:: - - sage: B = crystals.Tableaux(['A', [1,1]], shape=[3,2,1]) - sage: for b in B.highest_weight_vectors(): - ....: print("{} {}".format(b, b.is_genuine_highest_weight())) - [[-2, -2, -2], [-1, -1], [1]] True - [[-2, -2, -2], [-1, 2], [1]] False - [[-2, -2, 2], [-1, -1], [1]] False - sage: [b for b in B if b.is_genuine_highest_weight([-1,0])] - [[[-2, -2, -2], [-1, -1], [1]], - [[-2, -2, -2], [-1, -1], [2]], - [[-2, -2, -2], [-1, 2], [2]], - [[-2, -2, 2], [-1, -1], [2]], - [[-2, -2, 2], [-1, 2], [2]], - [[-2, -2, -2], [-1, 2], [1]], - [[-2, -2, 2], [-1, -1], [1]], - [[-2, -2, 2], [-1, 2], [1]]] - """ - P = self.parent() - if index_set is None or set(index_set) == set(P.index_set()): - return self in P.genuine_highest_weight_vectors() - S = P.subcrystal(generators=P, index_set=index_set, category=P.category()) - return any(self == x.value for x in S.genuine_highest_weight_vectors()) - - def is_genuine_lowest_weight(self, index_set=None): - """ - Return whether ``self`` is a genuine lowest weight element. - - INPUT: - - - ``index_set`` -- (optional) the index set of the (sub)crystal - on which to check - - EXAMPLES:: - - sage: B = crystals.Tableaux(['A', [1,1]], shape=[3,2,1]) - sage: for b in sorted(B.lowest_weight_vectors()): - ....: print("{} {}".format(b, b.is_genuine_lowest_weight())) - [[-2, 1, 2], [-1, 2], [1]] False - [[-2, 1, 2], [-1, 2], [2]] False - [[-1, 1, 2], [1, 2], [2]] True - sage: [b for b in B if b.is_genuine_lowest_weight([-1,0])] - [[[-2, -1, 1], [-1, 1], [1]], - [[-2, -1, 1], [-1, 1], [2]], - [[-2, 1, 2], [-1, 1], [2]], - [[-2, 1, 2], [-1, 1], [1]], - [[-1, -1, 1], [1, 2], [2]], - [[-1, -1, 1], [1, 2], [1]], - [[-1, 1, 2], [1, 2], [2]], - [[-1, 1, 2], [1, 2], [1]]] - """ - P = self.parent() - if index_set is None or set(index_set) == set(P.index_set()): - return self in P.genuine_lowest_weight_vectors() - S = P.subcrystal(generators=P, index_set=index_set, category=P.category()) - return any(self == x.value for x in S.genuine_lowest_weight_vectors()) - class TensorProducts(TensorProductsCategory): """ The category of regular crystals constructed by tensor @@ -505,8 +161,8 @@ def extra_super_categories(self): """ EXAMPLES:: - sage: RegularCrystals().TensorProducts().extra_super_categories() - [Category of regular crystals] + sage: from sage.categories.regular_supercrystals import RegularSuperCrystals + sage: RegularSuperCrystals().TensorProducts().extra_super_categories() + [Category of regular super crystals] """ return [self.base_category()] - diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index a8f648277f5..6153bfd9bf4 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -195,6 +195,49 @@ def _is_nonzero(self): """ return bool(self.codomain().one()) + def extend_to_fraction_field(self): + r""" + Return the extension of the morphism to fraction fields of + the domain and the codomain. + + EXAMPLES:: + + sage: S. = QQ[] + sage: f = S.hom([x+1]); f + Ring endomorphism of Univariate Polynomial Ring in x over Rational Field + Defn: x |--> x + 1 + + sage: g = f.extend_to_fraction_field(); g + Ring endomorphism of Fraction Field of Univariate Polynomial Ring in x over Rational Field + Defn: x |--> x + 1 + sage: g(x) + x + 1 + sage: g(1/x) + 1/(x + 1) + + If this morphism is not injective, it does not extend to the fraction + field and an error is raised:: + + sage: f = GF(5).coerce_map_from(ZZ) + sage: f.extend_to_fraction_field() + Traceback (most recent call last): + ... + ValueError: the morphism is not injective + """ + from sage.rings.morphism import RingHomomorphism_from_fraction_field + if self.domain().is_field() and self.codomain().is_field(): + return self + try: + if not self.is_injective(): + raise ValueError("the morphism is not injective") + except NotImplementedError: # we trust the user + pass + domain = self.domain().fraction_field() + codomain = self.codomain().fraction_field() + parent = domain.Hom(codomain) # category = category=self.category_for() ??? + return RingHomomorphism_from_fraction_field(parent, self) + + class SubcategoryMethods: def NoZeroDivisors(self): diff --git a/src/sage/categories/supercrystals.py b/src/sage/categories/supercrystals.py new file mode 100644 index 00000000000..abd528c1d86 --- /dev/null +++ b/src/sage/categories/supercrystals.py @@ -0,0 +1,402 @@ +r""" +Supercrystals +""" + +#***************************************************************************** +# Copyright (C) 2017 Franco Saliola +# 2017 Anne Schilling +# 2019 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.categories.category_singleton import Category_singleton +from sage.categories.category_with_axiom import CategoryWithAxiom +from sage.categories.crystals import Crystals +from sage.categories.tensor import TensorProductsCategory + +class SuperCrystals(Category_singleton): + def super_categories(self): + r""" + EXAMPLES:: + + sage: from sage.categories.supercrystals import SuperCrystals + sage: C = SuperCrystals() + sage: C.super_categories() + [Category of crystals] + """ + return [Crystals()] + + class ParentMethods: + def tensor(self, *crystals, **options): + """ + Return the tensor product of ``self`` with the crystals ``B``. + + EXAMPLES:: + + sage: B = crystals.Letters(['A',[1,2]]) + sage: C = crystals.Tableaux(['A',[1,2]], shape = [2,1]) + sage: T = C.tensor(B); T + Full tensor product of the crystals [Crystal of BKK tableaux of shape [2, 1] of gl(2|3), + The crystal of letters for type ['A', [1, 2]]] + sage: S = B.tensor(C); S + Full tensor product of the crystals [The crystal of letters for type ['A', [1, 2]], + Crystal of BKK tableaux of shape [2, 1] of gl(2|3)] + sage: G = T.digraph() + sage: H = S.digraph() + sage: G.is_isomorphic(H, edge_labels= True) + True + """ + cartan_type = self.cartan_type() + if any(c.cartan_type() != cartan_type for c in crystals): + raise ValueError("all crystals must be of the same Cartan type") + + if cartan_type.letter == 'Q': + from sage.combinat.crystals.tensor_product import FullTensorProductOfQueerSuperCrystals + return FullTensorProductOfQueerSuperCrystals((self,) + tuple(crystals), **options) + else: + from sage.combinat.crystals.tensor_product import FullTensorProductOfSuperCrystals + return FullTensorProductOfSuperCrystals((self,) + tuple(crystals), **options) + + class Finite(CategoryWithAxiom): + class ParentMethods: + @cached_method(key=lambda s,i: tuple(i) if i is not None else s.index_set()) + def digraph(self, index_set=None): + r""" + Return the :class:`DiGraph` associated to ``self``. + + EXAMPLES:: + + sage: B = crystals.Letters(['A', [1,3]]) + sage: G = B.digraph(); G + Multi-digraph on 6 vertices + sage: Q = crystals.Letters(['Q',3]) + sage: G = Q.digraph(); G + Multi-digraph on 3 vertices + sage: G.edges() + [(1, 2, -1), (1, 2, 1), (2, 3, -2), (2, 3, 2)] + + The edges of the crystal graph are by default colored using + blue for edge 1, red for edge 2, green for edge 3, and dashed with + the corresponding color for barred edges. Edge 0 is dotted black:: + + sage: view(G) # optional - dot2tex graphviz, not tested (opens external window) + """ + from sage.graphs.digraph import DiGraph + from sage.misc.latex import LatexExpr + from sage.combinat.root_system.cartan_type import CartanType + + if index_set is None: + index_set = self.index_set() + + G = DiGraph(multiedges=True) + G.add_vertices(self) + for i in index_set: + for x in G: + y = x.f(i) + if y is not None: + G.add_edge(x, y, i) + + def edge_options(data): + u, v, l = data + edge_opts = { 'edge_string': '->', 'color': 'black' } + if l > 0: + edge_opts['color'] = CartanType._colors.get(l, 'black') + edge_opts['label'] = LatexExpr(str(l)) + elif l < 0: + edge_opts['color'] = "dashed," + CartanType._colors.get(-l, 'black') + edge_opts['label'] = LatexExpr("\\overline{%s}" % str(-l)) + else: + edge_opts['color'] = "dotted," + CartanType._colors.get(l, 'black') + edge_opts['label'] = LatexExpr(str(l)) + return edge_opts + + G.set_latex_options(format="dot2tex", edge_labels=True, edge_options=edge_options) + return G + + def genuine_highest_weight_vectors(self): + r""" + Return the tuple of genuine highest weight elements of ``self``. + + EXAMPLES:: + + sage: B = crystals.Letters(['A', [1,2]]) + sage: B.genuine_highest_weight_vectors() + (-2,) + + sage: T = B.tensor(B) + sage: T.genuine_highest_weight_vectors() + ([-2, -1], [-2, -2]) + sage: s1, s2 = T.connected_components() + sage: s = s1 + s2 + sage: s.genuine_highest_weight_vectors() + ([-2, -1], [-2, -2]) + """ + return tuple([x[0] for x in self._genuine_highest_lowest_weight_vectors()]) + + connected_components_generators = genuine_highest_weight_vectors + + def connected_components(self): + r""" + Return the connected components of ``self`` as subcrystals. + + EXAMPLES:: + + sage: B = crystals.Letters(['A', [1,2]]) + sage: B.connected_components() + [Subcrystal of The crystal of letters for type ['A', [1, 2]]] + + sage: T = B.tensor(B) + sage: T.connected_components() + [Subcrystal of Full tensor product of the crystals + [The crystal of letters for type ['A', [1, 2]], + The crystal of letters for type ['A', [1, 2]]], + Subcrystal of Full tensor product of the crystals + [The crystal of letters for type ['A', [1, 2]], + The crystal of letters for type ['A', [1, 2]]]] + """ + category = SuperCrystals() + from sage.categories.regular_supercrystals import RegularSuperCrystals + if self in RegularSuperCrystals(): + category = RegularSuperCrystals() + index_set = self.index_set() + cartan_type = self.cartan_type() + CCs = [] + + for mg in self.connected_components_generators(): + if not isinstance(mg, tuple): + mg = (mg,) + subcrystal = self.subcrystal(generators=mg, + index_set=index_set, + cartan_type=cartan_type, + category=category) + CCs.append(subcrystal) + + return CCs + + def genuine_lowest_weight_vectors(self): + r""" + Return the tuple of genuine lowest weight elements of ``self``. + + EXAMPLES:: + + sage: B = crystals.Letters(['A', [1,2]]) + sage: B.genuine_lowest_weight_vectors() + (3,) + + sage: T = B.tensor(B) + sage: T.genuine_lowest_weight_vectors() + ([3, 3], [3, 2]) + sage: s1, s2 = T.connected_components() + sage: s = s1 + s2 + sage: s.genuine_lowest_weight_vectors() + ([3, 3], [3, 2]) + """ + return tuple([x[1] for x in self._genuine_highest_lowest_weight_vectors()]) + + @cached_method + def _genuine_highest_lowest_weight_vectors(self): + r""" + Return the genuine lowest and highest weight elements of ``self``. + + EXAMPLES:: + + sage: B = crystals.Letters(['A', [1,2]]) + sage: B._genuine_highest_lowest_weight_vectors() + ((-2, 3),) + + sage: T = B.tensor(B) + sage: T._genuine_highest_lowest_weight_vectors() + (([-2, -1], [3, 3]), ([-2, -2], [3, 2])) + sage: s1, s2 = T.connected_components() + sage: s = s1 + s2 + sage: s._genuine_highest_lowest_weight_vectors() + (([-2, -1], [3, 3]), ([-2, -2], [3, 2])) + + An example with fake highest/lowest weight elements + from [BKK2000]_:: + + sage: B = crystals.Tableaux(['A', [1,1]], shape=[3,2,1]) + sage: B._genuine_highest_lowest_weight_vectors() + (([[-2, -2, -2], [-1, -1], [1]], [[-1, 1, 2], [1, 2], [2]]),) + """ + X = [] + for G in self.digraph().connected_components_subgraphs(): + src = G.sources() + sinks = G.sinks() + max_dist = -1 + pair = None + for s in src: + for t in sinks: + d = G.distance(s, t) + if d < float('inf') and d > max_dist: + pair = (s, t) + max_dist = d + X.append(pair) + return tuple(X) + + def character(self): + """ + Return the character of ``self``. + + .. TODO:: + + Once the `WeylCharacterRing` is implemented, make this + consistent with the implementation in + :meth:`sage.categories.classical_crystals.ClassicalCrystals.ParentMethods.character`. + + EXAMPLES:: + + sage: B = crystals.Letters(['A',[1,2]]) + sage: B.character() + B[(1, 0, 0, 0, 0)] + B[(0, 1, 0, 0, 0)] + B[(0, 0, 1, 0, 0)] + + B[(0, 0, 0, 1, 0)] + B[(0, 0, 0, 0, 1)] + """ + from sage.rings.all import ZZ + A = self.weight_lattice_realization().algebra(ZZ) + return A.sum(A(x.weight()) for x in self) + + @cached_method + def highest_weight_vectors(self): + """ + Return the highest weight vectors of ``self``. + + EXAMPLES:: + + sage: B = crystals.Letters(['A', [1,2]]) + sage: B.highest_weight_vectors() + (-2,) + + sage: T = B.tensor(B) + sage: T.highest_weight_vectors() + ([-2, -2], [-2, -1]) + + We give an example from [BKK2000]_ that has fake + highest weight vectors:: + + sage: B = crystals.Tableaux(['A', [1,1]], shape=[3,2,1]) + sage: B.highest_weight_vectors() + ([[-2, -2, -2], [-1, -1], [1]], + [[-2, -2, -2], [-1, 2], [1]], + [[-2, -2, 2], [-1, -1], [1]]) + sage: B.genuine_highest_weight_vectors() + ([[-2, -2, -2], [-1, -1], [1]],) + """ + return tuple(self.digraph().sources()) + + @cached_method + def lowest_weight_vectors(self): + """ + Return the lowest weight vectors of ``self``. + + EXAMPLES:: + + sage: B = crystals.Letters(['A', [1,2]]) + sage: B.lowest_weight_vectors() + (3,) + + sage: T = B.tensor(B) + sage: sorted(T.lowest_weight_vectors()) + [[3, 2], [3, 3]] + + We give an example from [BKK2000]_ that has fake + lowest weight vectors:: + + sage: B = crystals.Tableaux(['A', [1,1]], shape=[3,2,1]) + sage: sorted(B.lowest_weight_vectors()) + [[[-2, 1, 2], [-1, 2], [1]], + [[-2, 1, 2], [-1, 2], [2]], + [[-1, 1, 2], [1, 2], [2]]] + sage: B.genuine_lowest_weight_vectors() + ([[-1, 1, 2], [1, 2], [2]],) + """ + return tuple(self.digraph().sinks()) + + class ElementMethods: + def is_genuine_highest_weight(self, index_set=None): + """ + Return whether ``self`` is a genuine highest weight element. + + INPUT: + + - ``index_set`` -- (optional) the index set of the (sub)crystal + on which to check + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A', [1,1]], shape=[3,2,1]) + sage: for b in B.highest_weight_vectors(): + ....: print("{} {}".format(b, b.is_genuine_highest_weight())) + [[-2, -2, -2], [-1, -1], [1]] True + [[-2, -2, -2], [-1, 2], [1]] False + [[-2, -2, 2], [-1, -1], [1]] False + sage: [b for b in B if b.is_genuine_highest_weight([-1,0])] + [[[-2, -2, -2], [-1, -1], [1]], + [[-2, -2, -2], [-1, -1], [2]], + [[-2, -2, -2], [-1, 2], [2]], + [[-2, -2, 2], [-1, -1], [2]], + [[-2, -2, 2], [-1, 2], [2]], + [[-2, -2, -2], [-1, 2], [1]], + [[-2, -2, 2], [-1, -1], [1]], + [[-2, -2, 2], [-1, 2], [1]]] + """ + P = self.parent() + if index_set is None or set(index_set) == set(P.index_set()): + return self in P.genuine_highest_weight_vectors() + S = P.subcrystal(generators=P, index_set=index_set, category=P.category()) + return any(self == x.value for x in S.genuine_highest_weight_vectors()) + + def is_genuine_lowest_weight(self, index_set=None): + """ + Return whether ``self`` is a genuine lowest weight element. + + INPUT: + + - ``index_set`` -- (optional) the index set of the (sub)crystal + on which to check + + EXAMPLES:: + + sage: B = crystals.Tableaux(['A', [1,1]], shape=[3,2,1]) + sage: for b in sorted(B.lowest_weight_vectors()): + ....: print("{} {}".format(b, b.is_genuine_lowest_weight())) + [[-2, 1, 2], [-1, 2], [1]] False + [[-2, 1, 2], [-1, 2], [2]] False + [[-1, 1, 2], [1, 2], [2]] True + sage: [b for b in B if b.is_genuine_lowest_weight([-1,0])] + [[[-2, -1, 1], [-1, 1], [1]], + [[-2, -1, 1], [-1, 1], [2]], + [[-2, 1, 2], [-1, 1], [2]], + [[-2, 1, 2], [-1, 1], [1]], + [[-1, -1, 1], [1, 2], [2]], + [[-1, -1, 1], [1, 2], [1]], + [[-1, 1, 2], [1, 2], [2]], + [[-1, 1, 2], [1, 2], [1]]] + """ + P = self.parent() + if index_set is None or set(index_set) == set(P.index_set()): + return self in P.genuine_lowest_weight_vectors() + S = P.subcrystal(generators=P, index_set=index_set, category=P.category()) + return any(self == x.value for x in S.genuine_lowest_weight_vectors()) + + class TensorProducts(TensorProductsCategory): + """ + The category of regular crystals constructed by tensor + product of regular crystals. + """ + @cached_method + def extra_super_categories(self): + """ + EXAMPLES:: + + sage: from sage.categories.supercrystals import SuperCrystals + sage: SuperCrystals().TensorProducts().extra_super_categories() + [Category of super crystals] + """ + return [self.base_category()] + diff --git a/src/sage/combinat/cartesian_product.py b/src/sage/combinat/cartesian_product.py index ea4e5d73587..87d7037d517 100644 --- a/src/sage/combinat/cartesian_product.py +++ b/src/sage/combinat/cartesian_product.py @@ -1,7 +1,7 @@ r""" Cartesian Products """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 Mike Hansen , # # Distributed under the terms of the GNU General Public License (GPL) @@ -13,8 +13,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from __future__ import absolute_import from six.moves import range @@ -25,7 +25,6 @@ from inspect import isgenerator import sage.misc.prandom as rnd from sage.misc.mrange import xmrange_iter, _is_finite, _len -from .combinat import CombinatorialClass from .ranker import unrank from sage.rings.infinity import infinity diff --git a/src/sage/combinat/crystals/catalog.py b/src/sage/combinat/crystals/catalog.py index 47491cc57d9..33e8a019168 100644 --- a/src/sage/combinat/crystals/catalog.py +++ b/src/sage/combinat/crystals/catalog.py @@ -48,7 +48,7 @@ * :class:`KyotoPathModel ` * :class:`Letters ` * :class:`LSPaths ` -* :class:`Minimaj ` +* :class:`Minimaj ` * :class:`NakajimaMonomials ` * :class:`OddNegativeRoots ` * :class:`ProjectedLevelZeroLSPaths diff --git a/src/sage/combinat/crystals/elementary_crystals.py b/src/sage/combinat/crystals/elementary_crystals.py index 57ace5bce60..83f1f1fd86a 100644 --- a/src/sage/combinat/crystals/elementary_crystals.py +++ b/src/sage/combinat/crystals/elementary_crystals.py @@ -71,7 +71,9 @@ from sage.categories.crystals import Crystals from sage.categories.finite_crystals import FiniteCrystals from sage.categories.highest_weight_crystals import HighestWeightCrystals -from sage.categories.classical_crystals import ClassicalCrystals +from sage.categories.regular_crystals import RegularCrystals +from sage.categories.supercrystals import SuperCrystals +from sage.categories.regular_supercrystals import RegularSuperCrystals from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets from sage.structure.element import Element from sage.structure.parent import Parent @@ -267,9 +269,13 @@ def __init__(self, cartan_type, weight): sage: B = crystals.elementary.T("A2", 5*la[2]) sage: TestSuite(B).run() """ - Parent.__init__(self, category = (FiniteCrystals(), HighestWeightCrystals())) self._weight = weight self._cartan_type = cartan_type + if self._cartan_type.type() == 'Q': + category = SuperCrystals().Finite() + else: + category = (FiniteCrystals(), HighestWeightCrystals()) + Parent.__init__(self, category=category) self.module_generators = (self.element_class(self),) def _repr_(self): @@ -448,11 +454,22 @@ class RCrystal(UniqueRepresentation, Parent): the crystal graph of `R_{\lambda} \otimes B(\infty)` generated from the component `r_{\lambda} \otimes u_{\infty}`. - INPUT: + There is also a dual version of this crystal given by + `R^{\vee}_{\lambda} = \{ r^{\vee}_{\lambda} \}` with the crystal + structure defined by - - ``cartan_type`` -- A Cartan type + .. MATH:: - - ``weight`` -- An element of the weight lattice of type ``cartan_type`` + \mathrm{wt}(r^{\vee}_{\lambda}) = \lambda, \quad + e_i r^{\vee}_{\lambda} = f_i r^{\vee}_{\lambda} = 0, \quad + \varepsilon_i(r^{\vee}_{\lambda}) = 0, \quad + \varphi_i(r^{\vee}_{\lambda}) = \langle h_i, \lambda\rangle. + + INPUT: + + - ``cartan_type`` -- a Cartan type + - ``weight`` -- an element of the weight lattice of type ``cartan_type`` + - ``dual`` -- (default: ``False``) boolean EXAMPLES: @@ -478,7 +495,7 @@ class RCrystal(UniqueRepresentation, Parent): """ @staticmethod - def __classcall_private__(cls, cartan_type, weight=None): + def __classcall_private__(cls, cartan_type, weight=None, dual=False): r""" Normalize input to ensure a unique representation. @@ -495,9 +512,9 @@ def __classcall_private__(cls, cartan_type, weight=None): weight = cartan_type cartan_type = weight.parent().cartan_type() cartan_type = CartanType(cartan_type) - return super(RCrystal, cls).__classcall__(cls, cartan_type, weight) + return super(RCrystal, cls).__classcall__(cls, cartan_type, weight, dual) - def __init__(self, cartan_type, weight): + def __init__(self, cartan_type, weight, dual): r""" Initialize ``self``. @@ -507,9 +524,14 @@ def __init__(self, cartan_type, weight): sage: B = crystals.elementary.R("A2",5*la[2]) sage: TestSuite(B).run() """ - Parent.__init__(self, category = (FiniteCrystals(),HighestWeightCrystals())) self._weight = weight self._cartan_type = cartan_type + self._dual = dual + if self._cartan_type.type() == 'Q': + category = SuperCrystals().Finite() + else: + category = (FiniteCrystals(), HighestWeightCrystals()) + Parent.__init__(self, category=category) self.module_generators = (self.element_class(self),) def _repr_(self): @@ -519,11 +541,16 @@ def _repr_(self): EXAMPLES:: sage: la = RootSystem(['E',6]).weight_lattice().fundamental_weights() - sage: B = crystals.elementary.R(['E',6],la[6]) + sage: B = crystals.elementary.R(['E',6], la[6]) sage: B The R crystal of weight Lambda[6] and type ['E', 6] + + sage: crystals.elementary.R(['E',6], la[1], dual=True) + The dual R crystal of weight Lambda[1] and type ['E', 6] """ - return "The R crystal of weight {0!s} and type {1!s}".format(self._weight,self._cartan_type) + dual_str = " dual" if self._dual else "" + return "The{} R crystal of weight {} and type {}".format( + dual_str, self._weight, self._cartan_type) def _element_constructor_(self, weight): r""" @@ -607,7 +634,13 @@ def _latex_(self): sage: r = R.highest_weight_vector() sage: latex(r) {r_{\Lambda_{1}}} + + sage: R = crystals.elementary.R("G2", la[1], dual=True) + sage: latex(R.highest_weight_vector()) + {r^{\vee}_{\Lambda_{1}}} """ + if self.parent()._dual: + return r"{r^{\vee}_{"+self.parent()._weight._latex_()+"}}" return "{r_{"+self.parent()._weight._latex_()+"}}" def epsilon(self, i): @@ -624,14 +657,22 @@ def epsilon(self, i): EXAMPLES:: sage: la = RootSystem(['A',2]).weight_lattice().fundamental_weights() - sage: R = crystals.elementary.R("A2",la[1]) + sage: R = crystals.elementary.R("A2", la[1]) sage: r = R.highest_weight_vector() sage: [r.epsilon(i) for i in R.index_set()] [-1, 0] + + sage: R = crystals.elementary.R("A2", la[1], dual=True) + sage: r = R.highest_weight_vector() + sage: [r.epsilon(i) for i in R.index_set()] + [0, 0] """ - P = self.parent().weight_lattice_realization() - h = P.simple_coroots() - return -P(self.weight()).scalar(h[i]) + if self.parent()._dual: + return ZZ.zero() + else: + P = self.parent().weight_lattice_realization() + h = P.simple_coroots() + return -P(self.weight()).scalar(h[i]) def phi(self, i): r""" @@ -644,12 +685,22 @@ def phi(self, i): EXAMPLES:: sage: la = RootSystem("C5").weight_lattice().fundamental_weights() - sage: R = crystals.elementary.R("C5",la[4]+la[5]) + sage: R = crystals.elementary.R("C5", la[4]+la[5]) sage: r = R.highest_weight_vector() sage: [r.phi(i) for i in R.index_set()] [0, 0, 0, 0, 0] + + sage: R = crystals.elementary.R("C5", la[4]+la[5], dual=True) + sage: r = R.highest_weight_vector() + sage: [r.phi(i) for i in R.index_set()] + [0, 0, 0, 1, 1] """ - return ZZ.zero() + if self.parent()._dual: + P = self.parent().weight_lattice_realization() + h = P.simple_coroots() + return P(self.weight()).scalar(h[i]) + else: + return ZZ.zero() def weight(self): r""" @@ -1031,14 +1082,15 @@ def __classcall_private__(cls, cartan_type, P=None): """ if cartan_type in RootLatticeRealizations: P = cartan_type + cartan_type = P.cartan_type() elif P is None: cartan_type = CartanType(cartan_type) P = cartan_type.root_system().ambient_space() if P is None: P = cartan_type.root_system().weight_lattice() - return super(ComponentCrystal, cls).__classcall__(cls, P) + return super(ComponentCrystal, cls).__classcall__(cls, cartan_type, P) - def __init__(self, P): + def __init__(self, cartan_type, P): r""" Initialize ``self``. @@ -1047,9 +1099,13 @@ def __init__(self, P): sage: B = crystals.elementary.Component("D4") sage: TestSuite(B).run() """ - Parent.__init__(self, category = ClassicalCrystals()) self._weight_lattice_realization = P - self._cartan_type = P.cartan_type() + self._cartan_type = cartan_type + if self._cartan_type.type() == 'Q': + category = RegularSuperCrystals() + else: + category = (RegularCrystals().Finite(), HighestWeightCrystals()) + Parent.__init__(self, category=category) self.module_generators = (self.element_class(self),) def _repr_(self): diff --git a/src/sage/combinat/crystals/infinity_crystals.py b/src/sage/combinat/crystals/infinity_crystals.py index 0ef0e30edf9..940c02aed63 100644 --- a/src/sage/combinat/crystals/infinity_crystals.py +++ b/src/sage/combinat/crystals/infinity_crystals.py @@ -30,6 +30,8 @@ from sage.structure.parent import Parent from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets from sage.categories.highest_weight_crystals import HighestWeightCrystals +from sage.categories.crystals import Crystals +from sage.categories.supercrystals import SuperCrystals from sage.categories.homset import Hom from sage.misc.cachefunc import cached_method from sage.misc.flatten import flatten @@ -39,7 +41,8 @@ from sage.combinat.crystals.letters import CrystalOfLetters from sage.combinat.crystals.tensor_product import CrystalOfWords from sage.combinat.crystals.tensor_product_element import (CrystalOfTableauxElement, - InfinityCrystalOfTableauxElement, InfinityCrystalOfTableauxElementTypeD) + InfinityCrystalOfTableauxElement, InfinityCrystalOfTableauxElementTypeD, + InfinityQueerCrystalOfTableauxElement) class InfinityCrystalOfTableaux(CrystalOfWords): @@ -201,6 +204,8 @@ def __classcall_private__(cls, cartan_type): cartan_type = CartanType(cartan_type) if cartan_type.type() == 'D': return InfinityCrystalOfTableauxTypeD(cartan_type) + if cartan_type.type() == 'Q': + return DualInfinityQueerCrystalOfTableaux(cartan_type) return super(InfinityCrystalOfTableaux, cls).__classcall__(cls, cartan_type) def __init__(self, cartan_type): @@ -628,3 +633,96 @@ class Element(InfinityCrystalOfTableauxElementTypeD, InfinityCrystalOfTableaux.E """ pass +######################################################### +## Queer superalgebra + +class DualInfinityQueerCrystalOfTableaux(CrystalOfWords): + @staticmethod + def __classcall_private__(cls, cartan_type): + """ + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(['A',4]) + sage: B2 = crystals.infinity.Tableaux(CartanType(['A',4])) + sage: B is B2 + True + """ + cartan_type = CartanType(cartan_type) + return super(DualInfinityQueerCrystalOfTableaux, cls).__classcall__(cls, cartan_type) + + def __init__(self, cartan_type): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(['A',2]) + sage: TestSuite(B).run() # long time + """ + Parent.__init__(self, category=(SuperCrystals(), InfiniteEnumeratedSets())) + self._cartan_type = cartan_type + self.letters = CrystalOfLetters(cartan_type) + self.module_generators = (self.module_generator(),) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(['A',4]); B + The infinity crystal of tableaux of type ['A', 4] + """ + return "The dual infinity crystal of tableaux of type %s" % self._cartan_type + + @cached_method + def module_generator(self): + r""" + Return the module generator (or highest weight element) of ``self``. + + The module generator is the unique semistandard hoook tableau of shape + `(n, n-1, \ldots,2, 1)` with weight `0`. + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(["Q",5]) + sage: B.module_generator() + [[5, 5, 5, 5, 5], [4, 4, 4, 4], [3, 3, 3], [2, 2], [1]] + """ + n = self._cartan_type.rank() + 1 + row_lens = list(reversed(range(1, n+1))) + module_generator = flatten([[val]*val for val in row_lens]) + return self.element_class(self, [self.letters(x) for x in module_generator], row_lens) + + @cached_method + def index_set(self): + r""" + Return the index set of ``self``. + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(["Q",3]) + sage: B.index_set() + (1, 2, -1) + """ + n = self._cartan_type.rank() + return tuple(range(1, n+1)) + (-1,) + + def _element_constructor_(self, *args, **options): + """ + Construct an element of ``self`` from the input data. + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(["Q",4]) + sage: t = B([[4,4,4,4,2,1],[3,3,3],[2,2],[1]]) + sage: t + [[4, 4, 4, 4, 2, 1], [3, 3, 3], [2, 2], [1]] + """ + return self.element_class(self, *args, **options) + + class Element(InfinityQueerCrystalOfTableauxElement): + pass + diff --git a/src/sage/combinat/crystals/subcrystal.py b/src/sage/combinat/crystals/subcrystal.py index af9a7ac0899..cf29da97860 100644 --- a/src/sage/combinat/crystals/subcrystal.py +++ b/src/sage/combinat/crystals/subcrystal.py @@ -29,6 +29,7 @@ from sage.structure.element_wrapper import ElementWrapper from sage.categories.crystals import Crystals from sage.categories.finite_crystals import FiniteCrystals +from sage.categories.supercrystals import SuperCrystals from sage.categories.regular_supercrystals import RegularSuperCrystals from sage.combinat.root_system.cartan_type import CartanType from sage.rings.integer import Integer @@ -109,7 +110,7 @@ class Subcrystal(UniqueRepresentation, Parent): sage: T = crystals.Tableaux(['A',[1,1]], [2,1]) sage: S = T.subcrystal(max_depth=3) sage: S.category() - Category of regular super crystals + Category of finite super crystals """ @staticmethod def __classcall_private__(cls, ambient, contained=None, generators=None, @@ -140,10 +141,10 @@ def __classcall_private__(cls, ambient, contained=None, generators=None, generators = ambient.module_generators category = Crystals().or_subcategory(category) + if ambient in SuperCrystals(): + category = category & SuperCrystals() if ambient in FiniteCrystals() or isinstance(contained, frozenset): category = category.Finite() - if ambient in RegularSuperCrystals(): - category = category & RegularSuperCrystals() if virtualization is not None: if scaling_factors is None: @@ -342,35 +343,35 @@ def _richcmp_(self, other, op): For != operator:: sage: ([(i,j) for i in range(len(S)) for j in range(len(S)) if S[i]!=S[j]] - ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if + ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if ....: S[i].value!=S[j].value]) True For < operator:: sage: ([(i,j) for i in range(len(S)) for j in range(len(S)) if S[i] operator:: sage: ([(i,j) for i in range(len(S)) for j in range(len(S)) if S[i]>S[j]] - ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if + ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if ....: S[i].value>S[j].value]) True For >= operator:: sage: ([(i,j) for i in range(len(S)) for j in range(len(S)) if S[i]>=S[j]] - ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if + ....: == [(i,j) for i in range(len(S)) for j in range(len(S)) if ....: S[i].value>=S[j].value]) True """ diff --git a/src/sage/combinat/crystals/tensor_product.py b/src/sage/combinat/crystals/tensor_product.py index cf217dc4e07..f1602ff9148 100644 --- a/src/sage/combinat/crystals/tensor_product.py +++ b/src/sage/combinat/crystals/tensor_product.py @@ -12,10 +12,13 @@ - Ben Salisbury, Travis Scrimshaw (2013): Refactored tensor products to handle non-regular crystals and created new subclass to take advantage of the regularity +- Travis Scrimshaw (2020): Added queer crystal """ #***************************************************************************** # Copyright (C) 2007 Anne Schilling # Nicolas Thiery +# 2020 Travis Scrimshaw +# Ben Salisbury # # Distributed under the terms of the GNU General Public License (GPL) # @@ -645,9 +648,9 @@ class FullTensorProductOfSuperCrystals(FullTensorProductOfCrystals): class Element(TensorProductOfSuperCrystalsElement): pass -class FullTensorProductOfQueerSuperCrystals(FullTensorProductOfCrystals): - r""" - Tensor product of queer super crystals. +class QueerSuperCrystalsMixin(object): + """ + Mixin class with methods for a finite queer supercrystal. """ @cached_method def index_set(self): @@ -662,7 +665,7 @@ def index_set(self): (-4, -3, -2, -1, 1, 2) """ n = self.cartan_type().n - return tuple(range(-2*n,0)) + tuple(range(1,n+1)) + return tuple(range(-2*n, 0)) + tuple(range(1, n+1)) @cached_method def _long_element(self): @@ -683,6 +686,10 @@ def _long_element(self): n = self.cartan_type().n return tuple(Permutations(n+1).long_element().reduced_word()) +class FullTensorProductOfQueerSuperCrystals(FullTensorProductOfCrystals, QueerSuperCrystalsMixin): + r""" + Tensor product of queer super crystals. + """ class Element(TensorProductOfQueerSuperCrystalsElement): pass @@ -806,6 +813,13 @@ class CrystalOfTableaux(CrystalOfWords): sage: T.cardinality() 1392 + We can also construct the tableaux for `\mathfrak{q}(n)` as + given by [GJK+2014]_:: + + sage: T = crystals.Tableaux(['Q', 3], shape=[3,1]) + sage: T.cardinality() + 24 + TESTS: Base cases:: @@ -887,6 +901,11 @@ def __classcall_private__(cls, cartan_type, shapes = None, shape = None): shape = shapes from sage.combinat.crystals.bkk_crystals import CrystalOfBKKTableaux return CrystalOfBKKTableaux(cartan_type, shape=shape) + if cartan_type.letter == 'Q': + if any(shape[i] == shape[i+1] for i in range(len(shape)-1)): + raise ValueError("not a strict partition") + shape = Partition(shape) + return CrystalOfQueerTableaux(cartan_type, shape=shape) n = cartan_type.rank() # standardize shape/shapes input into a tuple of tuples assert operator.xor(shape is not None, shapes is not None) @@ -1016,3 +1035,115 @@ def _element_constructor_(self, *args, **options): class Element(CrystalOfTableauxElement): pass + +class CrystalOfQueerTableaux(CrystalOfWords, QueerSuperCrystalsMixin): + """ + A queer crystal of the semistandard decomposition tableaux of a given shape. + + INPUT: + + - ``cartan_type`` -- a Cartan type + - ``shape`` -- a shape + """ + def __init__(self, cartan_type, shape): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: T = crystals.Tableaux(['Q',3], shape=[4,2]) + sage: TestSuite(T).run() + sage: T = crystals.Tableaux(['Q',4], shape=[4,1]) + sage: TestSuite(T).run() # long time + """ + from sage.categories.regular_supercrystals import RegularSuperCrystals + from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets + Parent.__init__(self, category=(RegularSuperCrystals(), FiniteEnumeratedSets())) + self.shape = shape + self._cartan_type = cartan_type + self.letters = CrystalOfLetters(cartan_type) + n = cartan_type.rank() + 1 + data = sum(([self.letters(n-i)] * row_len for i,row_len in enumerate(shape)), []) + mg = self.element_class(self, list=data) + self.module_generators = (mg,) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: crystals.Tableaux(['Q',3], shape=[4,2]) + The crystal of tableaux of type ['Q', 3] and shape [4, 2] + """ + return "The crystal of tableaux of type {} and shape {}".format(self._cartan_type, self.shape) + + class Element(TensorProductOfQueerSuperCrystalsElement): + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['Q',3], shape=[3,2,1]) + sage: B.an_element() + [[3, 3, 3], [2, 2], [1]] + """ + return repr([list(reversed(row)) for row in self.rows()]) + + def _ascii_art_(self): + r""" + Return an ASCII art representation of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['Q',3], shape=[3,2,1]) + sage: t = B.an_element() + sage: t._ascii_art_() + 3 3 3 + 2 2 + 1 + """ + from sage.typeset.ascii_art import AsciiArt + ret = [" "*(3*i) + "".join("%3s" % str(x) for x in reversed(row)) + for i, row in enumerate(self.rows())] + return AsciiArt(ret) + + def _latex_(self): + r""" + Return latex code for ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['Q',3], shape=[3,2,1]) + sage: t = B.an_element() + sage: latex(t) + {\def\lr#1{\multicolumn{1}{|@{\hspace{.6ex}}c@{\hspace{.6ex}}|}{\raisebox{-.3ex}{$#1$}}} + \raisebox{-.6ex}{$\begin{array}[b]{*{3}c}\cline{1-3} + \lr{3}&\lr{3}&\lr{3}\\\cline{1-3} + &\lr{2}&\lr{2}\\\cline{2-3} + &&\lr{1}\\\cline{3-3} + \end{array}$} + } + """ + from sage.combinat.output import tex_from_array + return tex_from_array([[None]*i + list(reversed(row)) + for i, row in enumerate(self.rows())]) + + def rows(self): + """ + Return the list of rows of ``self``. + + EXAMPLES:: + + sage: B = crystals.Tableaux(['Q',3], shape=[3,2,1]) + sage: t = B.an_element() + sage: t.rows() + [[3, 3, 3], [2, 2], [1]] + """ + ret = [] + pos = 0 + for l in self.parent().shape: + ret.append(self[pos:pos+l]) + pos += l + return ret diff --git a/src/sage/combinat/crystals/tensor_product_element.pxd b/src/sage/combinat/crystals/tensor_product_element.pxd index 341985e65b8..53a60d5a327 100644 --- a/src/sage/combinat/crystals/tensor_product_element.pxd +++ b/src/sage/combinat/crystals/tensor_product_element.pxd @@ -24,3 +24,12 @@ cdef class TensorProductOfSuperCrystalsElement(TensorProductOfRegularCrystalsEle cdef class CrystalOfBKKTableauxElement(TensorProductOfSuperCrystalsElement): pass + +cdef class TensorProductOfQueerSuperCrystalsElement(TensorProductOfRegularCrystalsElement): + pass + +cdef class InfinityQueerCrystalOfTableauxElement(TensorProductOfQueerSuperCrystalsElement): + cdef list _row_lengths + +cdef Py_ssize_t count_leading(list row, letter) + diff --git a/src/sage/combinat/crystals/tensor_product_element.pyx b/src/sage/combinat/crystals/tensor_product_element.pyx index 83b0bb4a823..7054e52ae88 100644 --- a/src/sage/combinat/crystals/tensor_product_element.pyx +++ b/src/sage/combinat/crystals/tensor_product_element.pyx @@ -1426,7 +1426,7 @@ cdef class TensorProductOfQueerSuperCrystalsElement(TensorProductOfRegularCrysta [GJK+2014]_. Given crystals `B_1` and `B_2` of type `\mathfrak{q}_{n+1}`, we define the tensor product `b_1 \otimes b_2 \in B_1 \otimes B_2`, where `b_1 \in B_1` and `b_2 \in B_2`, as the following: - + In addition to the tensor product rule for type `A_n`, the tensor product rule for `e_{-1}` and `f_{-1}` on `b_1\otimes b_2` are given by @@ -1502,22 +1502,23 @@ cdef class TensorProductOfQueerSuperCrystalsElement(TensorProductOfRegularCrysta [2, 2, 1, 1] """ if i > 0: - return TensorProductOfRegularCrystalsElement.e(self, i) + from sage.categories.regular_supercrystals import RegularSuperCrystals + if self._parent in RegularSuperCrystals(): + return TensorProductOfRegularCrystalsElement.e(self, i) + else: + return TensorProductOfCrystalsElement.e(self, i) cdef tuple w cdef int k, a, l l = len(self._list) if i == -1: - k = 0 - wt = self._list[k].weight() - v = wt[0] + wt[1] - while v == 0 and k < l - 1: - k += 1 - wt = self[k].weight() - v += wt[0] + wt[1] - b = self._list[k].e(i) - if b is None: - return None - return self._set_index(k, b) + for k in range(l): + b = self._list[k].e(i) + if b is not None: + return self._set_index(k, b) + if self._list[k].f(i) is not None: + # There are no (-1)-string of length > 1 + return None + return None n = self._parent.cartan_type().n if i < -1 and i >= -n: j = -i @@ -1561,22 +1562,23 @@ cdef class TensorProductOfQueerSuperCrystalsElement(TensorProductOfRegularCrysta [2, 1] """ if i > 0: - return TensorProductOfRegularCrystalsElement.f(self, i) + from sage.categories.regular_supercrystals import RegularSuperCrystals + if self._parent in RegularSuperCrystals(): + return TensorProductOfRegularCrystalsElement.f(self, i) + else: + return TensorProductOfCrystalsElement.f(self, i) cdef tuple w cdef int k, a, l l = len(self._list) if i == -1: - k = 0 - wt = self._list[k].weight() - v = wt[0] + wt[1] - while v == 0 and k < l - 1: - k += 1 - wt = self._list[k].weight() - v += wt[0] + wt[1] - b = self._list[k].f(i) - if b is None: - return None - return self._set_index(k, b) + for k in range(l): + b = self._list[k].f(i) + if b is not None: + return self._set_index(k, b) + if self._list[k].e(i) is not None: + # There are no (-1)-string of length > 1 + return None + return None n = self._parent.cartan_type().n if i < -1 and i >= -n: j = -i @@ -1648,6 +1650,228 @@ cdef class TensorProductOfQueerSuperCrystalsElement(TensorProductOfRegularCrysta x = x.f(i) return string_length +cdef class InfinityQueerCrystalOfTableauxElement(TensorProductOfQueerSuperCrystalsElement): + def __init__(self, parent, list, row_lengths=[]): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(['Q',4]) + sage: t = B([[4,4,4,4,2,1],[3,3,3],[2,2],[1]]) + sage: t + [[4, 4, 4, 4, 2, 1], [3, 3, 3], [2, 2], [1]] + sage: TestSuite(t).run() + """ + if not row_lengths and list and not isinstance(list[0], parent.letters.element_class): + ret = [] + L = parent.letters + row_lengths = [] + for row in list: + ret.extend(L(val) for val in reversed(row)) + row_lengths.append(len(row)) + list = ret + self._row_lengths = row_lengths + super(InfinityQueerCrystalOfTableauxElement, self).__init__(parent, list) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(['Q',4]) + sage: t = B.an_element() + sage: t + [[4, 4, 4, 4], [3, 3, 3], [2, 2], [1]] + """ + return repr([list(reversed(row)) for row in self.rows()]) + + def _ascii_art_(self): + """ + Return an ASCII art representation of ``self``. + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(['Q',4]) + sage: t = B([[4,4,4,4,2,1],[3,3,3],[2,2],[1]]) + sage: ascii_art(t) + 4 4 4 4 2 1 + 3 3 3 + 2 2 + 1 + """ + from sage.typeset.ascii_art import AsciiArt + ret = [" "*(3*i) + "".join("%3s" % str(x) for x in reversed(row)) + for i, row in enumerate(self.rows())] + return AsciiArt(ret) + + def _latex_(self): + r""" + Return latex code for ``self``. + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(['Q',4]) + sage: t = B([[4,4,4,4,4,2,1],[3,3,3,3],[2,2,1],[1]]) + sage: latex(t) + {\def\lr#1{\multicolumn{1}{|@{\hspace{.6ex}}c@{\hspace{.6ex}}|}{\raisebox{-.3ex}{$#1$}}} + \raisebox{-.6ex}{$\begin{array}[b]{*{7}c}\cline{1-7} + \lr{4}&\lr{4}&\lr{4}&\lr{4}&\lr{4}&\lr{2}&\lr{1}\\\cline{1-7} + &\lr{3}&\lr{3}&\lr{3}&\lr{3}\\\cline{2-5} + &&\lr{2}&\lr{2}&\lr{1}\\\cline{3-5} + &&&\lr{1}\\\cline{4-4} + \end{array}$} + } + """ + from sage.combinat.output import tex_from_array + return tex_from_array([[None]*i + list(reversed(row)) + for i, row in enumerate(self.rows())]) + + def rows(self): + """ + Return the list of rows of ``self``. + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(['Q',4]) + sage: t = B([[4,4,4,4,4,2,1],[3,3,3,3],[2,2,1],[1]]) + sage: t.rows() + [[1, 2, 4, 4, 4, 4, 4], [3, 3, 3, 3], [1, 2, 2], [1]] + """ + if not self: + return [] + + cdef list ret = [] + cdef Py_ssize_t pos = 0 + for l in self._row_lengths: + ret.append(self._list[pos:pos+l]) + pos += l + return ret + + def e(self, i): + r""" + Return the action of `e_i` on ``self``. + + INPUT: + + - ``i`` -- an element of the index set + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(['Q',4]) + sage: t = B([[4,4,4,4,4,2,1],[3,3,3,3],[2,2,1],[1]]) + sage: t.e(1) + [[4, 4, 4, 4, 4, 4, 2, 1], [3, 3, 3, 3, 3], [2, 2, 1, 1], [1]] + sage: t.e(3) + [[4, 4, 4, 4, 4, 3, 2, 1], [3, 3, 3, 3], [2, 2, 1], [1]] + sage: t.e(-1) + """ + ret = super(InfinityQueerCrystalOfTableauxElement, self).e(i) + if ret is None: + return None + ( ret)._row_lengths = self._row_lengths + if i < 0: + i = -i + L = self._parent.letters + n = self._parent._cartan_type.n + rows = ret.rows() + row_lens = list(self._row_lengths) + if count_leading(rows[n-i], L(i+1)) != len(rows[n-i+1]) + 1: + for j in range(n-i+1): + rows[j].append(L(n+1-j)) + row_lens[j] += 1 + return type(self)(self._parent, sum(rows, []), row_lens) + + def f(self, i): + r""" + Return the action of `f_i` on ``self``. + + INPUT: + + - ``i`` -- an element of the index set + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(['Q',4]) + sage: t = B([[4,4,4,4,4,2,1],[3,3,3,3],[2,2,1],[1]]) + sage: t.f(1) + [[4, 4, 4, 4, 4, 2, 2], [3, 3, 3, 3], [2, 2, 1], [1]] + sage: t.f(3) + sage: t.f(-1) + [[4, 4, 4, 4, 4, 2, 2], [3, 3, 3, 3], [2, 2, 1], [1]] + """ + ret = super(InfinityQueerCrystalOfTableauxElement, self).f(i) + if ret is None: + return None + ( ret)._row_lengths = self._row_lengths + if i < 0: + i = -i + L = self._parent.letters + n = self._parent._cartan_type.n + rows = ret.rows() + row_lens = list(self._row_lengths) + if count_leading(rows[n-i], L(i+1)) != len(rows[n-i+1]) + 1: + for j in range(n-i+1): + rows[j].pop() + row_lens[j] -= 1 + return type(self)(self._parent, sum(rows, []), row_lens) + + def epsilon(self, i): + r""" + Return `\varepsilon_i` of ``self``. + + INPUT: + + - ``i`` -- an element of the index set + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(['Q',4]) + sage: t = B([[4,4,4,4,4,2,1],[3,3,3,3],[2,2,1],[1]]) + sage: [t.epsilon(i) for i in B.index_set()] + [-1, 1, -2, 0] + """ + if i == -1: + if self.e(-1) is None: + return ZZ.zero() + return ZZ.one() + P = self._parent.weight_lattice_realization() + h = P.simple_coroots() + return self.phi(i) - P(self.weight()).scalar(h[i]) + + def weight(self): + r""" + Return the weight of ``self``. + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux(['Q',4]) + sage: t = B([[4,4,4,4,4,2,1],[3,3,3,3],[2,2,1],[1]]) + sage: t.weight() + (4, 2, 2, 0) + """ + ret = super(InfinityQueerCrystalOfTableauxElement, self).weight() + L = self._parent.letters + n = self._parent._cartan_type.n + 1 + zero = self._parent.weight_lattice_realization().zero() + La = self._parent.weight_lattice_realization().fundamental_weights() + def fwt(i): + return zero if i == n else La[i] + ret -= sum((self._row_lengths[i] - 1 - self._row_lengths[i+1])*(fwt(n-i)-fwt(n-i-1)) + for i in range(n-1)) + for i, l in enumerate(self._row_lengths[1:]): + ret -= L(n-i).weight() * (l + 1) + ret -= L(1).weight() # From the 1 on the bottom row + return ret + +cdef Py_ssize_t count_leading(list row, letter): + cdef Py_ssize_t i + for i in range(len(row)-1,-1,-1): + if row[i] != letter: + return len(row) - 1 - i + return len(row) + # for unpickling from sage.misc.persist import register_unpickle_override register_unpickle_override('sage.combinat.crystals.tensor_product', 'ImmutableListWithParent', ImmutableListWithParent) diff --git a/src/sage/combinat/free_dendriform_algebra.py b/src/sage/combinat/free_dendriform_algebra.py index 1d6d11c1058..176aa6cf759 100644 --- a/src/sage/combinat/free_dendriform_algebra.py +++ b/src/sage/combinat/free_dendriform_algebra.py @@ -30,7 +30,6 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.misc.cachefunc import cached_method from sage.sets.family import Family -from sage.misc.lazy_import import lazy_import from sage.structure.coerce_exceptions import CoercionException diff --git a/src/sage/combinat/free_prelie_algebra.py b/src/sage/combinat/free_prelie_algebra.py index 7961b286c29..ce6abd8f63d 100644 --- a/src/sage/combinat/free_prelie_algebra.py +++ b/src/sage/combinat/free_prelie_algebra.py @@ -15,7 +15,7 @@ # the License, or (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** -from six import iteritems +from six import string_types from sage.categories.magmatic_algebras import MagmaticAlgebras from sage.categories.lie_algebras import LieAlgebras @@ -32,12 +32,12 @@ LabelledRootedTrees, LabelledRootedTree) -from sage.misc.lazy_import import lazy_import from sage.misc.lazy_attribute import lazy_attribute from sage.misc.cachefunc import cached_method from sage.sets.family import Family from sage.structure.coerce_exceptions import CoercionException +from sage.rings.infinity import Infinity class FreePreLieAlgebra(CombinatorialFreeModule): @@ -177,7 +177,7 @@ def __classcall_private__(cls, R, names=None): True """ if names is not None: - if ',' in names: + if isinstance(names, string_types) and ',' in names: names = [u for u in names if u != ','] names = Alphabet(names) @@ -243,16 +243,32 @@ def _repr_(self): sage: algebras.FreePreLie(QQ, '@') # indirect doctest Free PreLie algebra on one generator ['@'] over Rational Field + + sage: algebras.FreePreLie(QQ, ZZ) # indirect doctest + Free PreLie algebra on generators indexed by Integer Ring + over Rational Field + + sage: enum = EnumeratedSets().Infinite().example() + sage: algebras.FreePreLie(QQ, enum) # indirect doctest + Free PreLie algebra on generators indexed by An example of an + infinite enumerated set: the non negative integers + over Rational Field """ n = self.algebra_generators().cardinality() - if n == 1: + finite = bool(n < Infinity) + if not finite: + gen = "generators indexed by" + elif n == 1: gen = "one generator" else: gen = "{} generators".format(n) s = "Free PreLie algebra on {} {} over {}" - try: - return s.format(gen, self._alphabet.list(), self.base_ring()) - except NotImplementedError: + if finite: + try: + return s.format(gen, self._alphabet.list(), self.base_ring()) + except NotImplementedError: + return s.format(gen, self._alphabet, self.base_ring()) + else: return s.format(gen, self._alphabet, self.base_ring()) def gen(self, i): @@ -715,7 +731,7 @@ def _apply_functor_to_morphism(self, f): def action(x): return codom._from_dict({a: f(b) - for a, b in iteritems(x.monomial_coefficients())}) + for a, b in x.monomial_coefficients().items()}) return dom.module_morphism(function=action, codomain=codom) def __eq__(self, other): diff --git a/src/sage/databases/oeis.py b/src/sage/databases/oeis.py index c7293068279..552d2368e8a 100644 --- a/src/sage/databases/oeis.py +++ b/src/sage/databases/oeis.py @@ -51,9 +51,9 @@ 2: = [a_0; a_1, a_2, a_3, ...] = [3; 7, 15, 1, 292, ...] sage: c.comments() # optional -- internet - 0: The first 5,821,569,425 terms were computed by _Eric W. Weisstein_ on Sep 18 2011. - 1: The first 10,672,905,501 terms were computed by _Eric W. Weisstein_ on Jul 17 2013. - 2: The first 15,000,000,000 terms were computed by _Eric W. Weisstein_ on Jul 27 2013. + 0: The first 5821569425 terms were computed by _Eric W. Weisstein_ on Sep 18 2011. + 1: The first 10672905501 terms were computed by _Eric W. Weisstein_ on Jul 17 2013. + 2: The first 15000000000 terms were computed by _Eric W. Weisstein_ on Jul 27 2013. :: diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 9ea585e06f3..2f00a37679c 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -253,7 +253,10 @@ def subst(m): # For example, on Python 3 we strip all u prefixes from unicode strings in the # expected output, because we never expect to see those on Python 3. if six.PY2: - _repr_fixups = [] + _repr_fixups = [ + (lambda g, w: ' + It also works without the line continuation:: @@ -1063,9 +1066,9 @@ def check_output(self, want, got, optionflags): classes) between Python 2 and Python 3:: sage: int - + sage: float - + """ got = self.human_readable_escape_sequences(got) if isinstance(want, MarkedOutput): diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index 4bf0ed8bf47..7c950f2465a 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -27,7 +27,6 @@ import doctest from Cython.Utils import is_package_dir from sage.cpython.string import bytes_to_str -from sage.repl.preparse import preparse from sage.repl.load import load from sage.misc.lazy_attribute import lazy_attribute from .parsing import SageDocTestParser @@ -110,6 +109,7 @@ def get_basename(path): fully_qualified_path = fully_qualified_path[:-9] return fully_qualified_path.replace(os.path.sep, '.') + class DocTestSource(object): """ This class provides a common base class for different sources of doctests. @@ -278,7 +278,6 @@ def _create_doctests(self, namespace, tab_okay=None): self.linking = False doctests = [] in_docstring = False - tab_found = False unparsed_doc = False doc = [] start = None diff --git a/src/sage/dynamics/finite_dynamical_system.py b/src/sage/dynamics/finite_dynamical_system.py index 8198872ab4a..5febefc74dd 100644 --- a/src/sage/dynamics/finite_dynamical_system.py +++ b/src/sage/dynamics/finite_dynamical_system.py @@ -72,7 +72,7 @@ knows the latter part of the Sage library well. """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2018 Darij Grinberg , # 2018 Tom Roby # @@ -85,18 +85,14 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** -#from sage.categories.enumerated_sets import EnumeratedSets -#from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets -#from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets -#from sage.sets.finite_enumerated_set import FiniteEnumeratedSet +# https://www.gnu.org/licenses/ +# **************************************************************************** from six import add_metaclass from sage.categories.sets_cat import Sets -from sage.misc.abstract_method import abstract_method from sage.structure.sage_object import SageObject from sage.misc.classcall_metaclass import ClasscallMetaclass, typecall + @add_metaclass(ClasscallMetaclass) class DiscreteDynamicalSystem(SageObject): r""" diff --git a/src/sage/env.py b/src/sage/env.py index d692e329045..738e95fa113 100644 --- a/src/sage/env.py +++ b/src/sage/env.py @@ -130,6 +130,12 @@ def var(key, *fallbacks, **kwds): value = None else: value = os.environ.get(key) + if value is None: + try: + import sage_conf + value = getattr(sage_conf, key, None) + except ImportError: + pass # Try all fallbacks in order as long as we don't have a value for f in fallbacks: if value is not None: @@ -186,6 +192,7 @@ def var(key, *fallbacks, **kwds): var('THREEJS_DIR', join(SAGE_SHARE, 'threejs')) var('SINGULARPATH', join(SAGE_SHARE, 'singular')) var('PPLPY_DOCS', join(SAGE_SHARE, 'doc', 'pplpy')) +var('MAXIMA', 'maxima') var('MAXIMA_FAS') var('SAGE_NAUTY_BINS_PREFIX', '') diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 6895b964258..0284e9c840e 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -4774,6 +4774,12 @@ def stack(self, face, position=None): Traceback (most recent call last): ... ValueError: the chosen position is too large + + Testing that :trac:`29057` is fixed:: + + sage: P = polytopes.cross_polytope(4) + sage: P.stack(P.faces(3)[0]) + A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 9 vertices """ from sage.geometry.polyhedron.face import PolyhedronFace if not isinstance(face, PolyhedronFace): @@ -4809,7 +4815,8 @@ def stack(self, face, position=None): locus_eqns = self.equations_list() locus_polyhedron = Polyhedron(ieqs=locus_ieqs, eqns=locus_eqns, - base_ring=self.parent().base_ring()) + base_ring=self.base_ring().fraction_field(), + backend=self.backend()) repr_point = locus_polyhedron.representative_point() new_vertex = (1-position)*barycenter + position*repr_point diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx index ca8ccba5b2a..7c2ec4e44ad 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx @@ -509,8 +509,8 @@ cdef class CombinatorialPolyhedron(SageObject): sage: C1 = loads(C.dumps()) sage: it = C.face_iter() sage: it1 = C1.face_iter() - sage: tup = tuple((face.Vrepr(), face.Hrepr()) for face in it) - sage: tup1 = tuple((face.Vrepr(), face.Hrepr()) for face in it1) + sage: tup = tuple((face.ambient_Vrepresentation(), face.ambient_Hrepresentation()) for face in it) + sage: tup1 = tuple((face.ambient_Vrepresentation(), face.ambient_Hrepresentation()) for face in it1) sage: tup == tup1 True @@ -519,8 +519,8 @@ cdef class CombinatorialPolyhedron(SageObject): sage: C1 = loads(C.dumps()) sage: it = C.face_iter() sage: it1 = C1.face_iter() - sage: tup = tuple((face.Vrepr(), face.Hrepr()) for face in it) - sage: tup1 = tuple((face.Vrepr(), face.Hrepr()) for face in it1) + sage: tup = tuple((face.ambient_Vrepresentation(), face.ambient_Hrepresentation()) for face in it) + sage: tup1 = tuple((face.ambient_Vrepresentation(), face.ambient_Hrepresentation()) for face in it1) sage: tup == tup1 True @@ -529,8 +529,8 @@ cdef class CombinatorialPolyhedron(SageObject): sage: C1 = loads(C.dumps()) sage: it = C.face_iter() sage: it1 = C1.face_iter() - sage: tup = tuple((face.Vrepr(), face.Hrepr()) for face in it) - sage: tup1 = tuple((face.Vrepr(), face.Hrepr()) for face in it1) + sage: tup = tuple((face.ambient_Vrepresentation(), face.ambient_Hrepresentation()) for face in it) + sage: tup1 = tuple((face.ambient_Vrepresentation(), face.ambient_Hrepresentation()) for face in it1) sage: tup == tup1 True @@ -540,8 +540,8 @@ cdef class CombinatorialPolyhedron(SageObject): sage: C1 = loads(C.dumps()) sage: it = C.face_iter() sage: it1 = C1.face_iter() - sage: tup = tuple((face.Vrepr(), face.Hrepr()) for face in it) - sage: tup1 = tuple((face.Vrepr(), face.Hrepr()) for face in it1) + sage: tup = tuple((face.ambient_Vrepresentation(), face.ambient_Hrepresentation()) for face in it) + sage: tup1 = tuple((face.ambient_Vrepresentation(), face.ambient_Hrepresentation()) for face in it1) sage: tup == tup1 True """ @@ -845,8 +845,11 @@ cdef class CombinatorialPolyhedron(SageObject): face_iter = self.face_iter(self.dimension() - 1, dual=False) facets = [None] * self.n_facets() for face in face_iter: - index = face.Hrepr(names=False)[0] - verts = face.Vrepr(names=names) + index = face.ambient_H_indices()[0] + if names: + verts = face.ambient_Vrepresentation() + else: + verts = face.ambient_V_indices() facets[index] = verts return tuple(facets) @@ -1077,7 +1080,7 @@ cdef class CombinatorialPolyhedron(SageObject): sage: C.ridges() () sage: it = C.face_iter(0) - sage: for face in it: face.Hrepr() + sage: for face in it: face.ambient_Hrepresentation() (An inequality (1, 0) x + 0 >= 0, An equation (0, 1) x + 0 == 0) TESTS: @@ -1172,7 +1175,10 @@ cdef class CombinatorialPolyhedron(SageObject): Graph on 10 vertices """ face_iter = self.face_iter(self.dimension() - 1, dual=False) - V = list(facet.Hrepr(names=names) for facet in face_iter) + if names: + V = list(facet.ambient_Hrepresentation() for facet in face_iter) + else: + V = list(facet.ambient_V_indices() for facet in face_iter) E = self.ridges(names=names, add_equalities=True) if not names: # If names is false, the ridges are given as tuple of indices, @@ -1409,34 +1415,34 @@ cdef class CombinatorialPolyhedron(SageObject): sage: it = C.face_iter(dimension=2) sage: face = next(it); face A 2-dimensional face of a 4-dimensional combinatorial polyhedron - sage: face.Vrepr() + sage: face.ambient_Vrepresentation() (A vertex at (4, 1, 5, 2, 3), A vertex at (4, 2, 5, 1, 3), A vertex at (5, 1, 4, 2, 3), A vertex at (5, 2, 4, 1, 3)) sage: face = next(it); face A 2-dimensional face of a 4-dimensional combinatorial polyhedron - sage: face.Vrepr() + sage: face.ambient_Vrepresentation() (A vertex at (4, 1, 5, 2, 3), A vertex at (4, 1, 5, 3, 2), A vertex at (5, 1, 4, 2, 3), A vertex at (5, 1, 4, 3, 2)) - sage: face.Hrepr() + sage: face.ambient_Hrepresentation() (An inequality (0, 1, 0, 0, 0) x - 1 >= 0, An inequality (0, 1, 0, 1, 1) x - 6 >= 0, An equation (1, 1, 1, 1, 1) x - 15 == 0) - sage: face.Hrepr(names=False) + sage: face.ambient_H_indices() (25, 29) sage: face = next(it); face A 2-dimensional face of a 4-dimensional combinatorial polyhedron - sage: face.Hrepr(names=False) + sage: face.ambient_H_indices() (12, 29) - sage: face.Vrepr(names=False) + sage: face.ambient_V_indices() (76, 77, 82, 83, 88, 89) sage: C = CombinatorialPolyhedron([[0,1,2],[0,1,3],[0,2,3],[1,2,3]]) sage: it = C.face_iter() - sage: for face in it: face.Vrepr() + sage: for face in it: face.ambient_Vrepresentation() (1, 2, 3) (0, 2, 3) (0, 1, 3) @@ -1455,7 +1461,7 @@ cdef class CombinatorialPolyhedron(SageObject): sage: P = Polyhedron(rays=[[1,0],[0,1]], vertices=[[1,0],[0,1]]) sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter(1) - sage: for face in it: face.Vrepr() + sage: for face in it: face.ambient_Vrepresentation() (A vertex at (0, 1), A vertex at (1, 0)) (A ray in the direction (1, 0), A vertex at (1, 0)) (A ray in the direction (0, 1), A vertex at (0, 1)) @@ -1668,7 +1674,7 @@ cdef class CombinatorialPolyhedron(SageObject): A 1-dimensional face of a 2-dimensional combinatorial polyhedron, A 2-dimensional face of a 2-dimensional combinatorial polyhedron) - sage: def f(i): return C.face_by_face_lattice_index(i).Vrepr(False) + sage: def f(i): return C.face_by_face_lattice_index(i).ambient_V_indices() sage: G = F.relabel(f) sage: G._elements ((), (0,), (0, 1), (0, 2), (0, 1, 2)) @@ -2225,30 +2231,30 @@ cdef class CombinatorialPolyhedron(SageObject): sage: P = polytopes.permutahedron(4) sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter() - sage: tup = tuple((face.Vrepr(),face.Hrepr()) for face in it) + sage: tup = tuple((face.ambient_Vrepresentation(),face.ambient_Hrepresentation()) for face in it) sage: rg = range(1,sum(C.f_vector()) - 1) - sage: tup2 = tuple((C.face_by_face_lattice_index(i).Vrepr(), - ....: C.face_by_face_lattice_index(i).Hrepr()) for i in rg) + sage: tup2 = tuple((C.face_by_face_lattice_index(i).ambient_Vrepresentation(), + ....: C.face_by_face_lattice_index(i).ambient_Hrepresentation()) for i in rg) sage: sorted(tup) == sorted(tup2) True sage: P = polytopes.cyclic_polytope(4,10) sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter() - sage: tup = tuple((face.Vrepr(),face.Hrepr()) for face in it) + sage: tup = tuple((face.ambient_Vrepresentation(),face.ambient_Hrepresentation()) for face in it) sage: rg = range(1,sum(C.f_vector()) - 1) - sage: tup2 = tuple((C.face_by_face_lattice_index(i).Vrepr(), - ....: C.face_by_face_lattice_index(i).Hrepr()) for i in rg) + sage: tup2 = tuple((C.face_by_face_lattice_index(i).ambient_Vrepresentation(), + ....: C.face_by_face_lattice_index(i).ambient_Hrepresentation()) for i in rg) sage: sorted(tup) == sorted(tup2) True sage: P = Polyhedron(rays=[[1,0,0], [-1,0,0], [0,-1,0]]) sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter() - sage: tup = tuple((face.Vrepr(),face.Hrepr()) for face in it) + sage: tup = tuple((face.ambient_Vrepresentation(),face.ambient_Hrepresentation()) for face in it) sage: rg = range(1,sum(C.f_vector()) - 1) - sage: tup2 = tuple((C.face_by_face_lattice_index(i).Vrepr(), - ....: C.face_by_face_lattice_index(i).Hrepr()) for i in rg) + sage: tup2 = tuple((C.face_by_face_lattice_index(i).ambient_Vrepresentation(), + ....: C.face_by_face_lattice_index(i).ambient_Hrepresentation()) for i in rg) sage: sorted(tup) == sorted(tup2) True @@ -2256,10 +2262,10 @@ cdef class CombinatorialPolyhedron(SageObject): ....: [0,-1,0], [0,1,0]]) sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter() - sage: tup = tuple((face.Vrepr(),face.Hrepr()) for face in it) + sage: tup = tuple((face.ambient_Vrepresentation(),face.ambient_Hrepresentation()) for face in it) sage: rg = range(1,sum(C.f_vector()) - 1) - sage: tup2 = tuple((C.face_by_face_lattice_index(i).Vrepr(), - ....: C.face_by_face_lattice_index(i).Hrepr()) for i in rg) + sage: tup2 = tuple((C.face_by_face_lattice_index(i).ambient_Vrepresentation(), + ....: C.face_by_face_lattice_index(i).ambient_Hrepresentation()) for i in rg) sage: sorted(tup) == sorted(tup2) True """ diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx index 6994a5f7195..8ee80ca7f71 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx @@ -34,11 +34,11 @@ Obtain further information regarding a face:: sage: it = C.face_iter(2) sage: face = next(it); face A 2-dimensional face of a 3-dimensional combinatorial polyhedron - sage: face.Vrepr() + sage: face.ambient_Vrepresentation() (A vertex at (0, 0, 1), A vertex at (0, 1, 0), A vertex at (1, 0, 0)) sage: face.n_ambient_Vrepresentation() 3 - sage: face.Hrepr(names=False) + sage: face.ambient_H_indices() (5,) sage: face.dimension() 2 @@ -112,16 +112,16 @@ cdef class CombinatorialFace(SageObject): The Vrepresentation:: - sage: face.Vrepr() + sage: face.ambient_Vrepresentation() (A vertex at (6, 36, 216, 1296, 7776),) - sage: face.Vrepr(names=False) + sage: face.ambient_V_indices() (6,) sage: face.n_ambient_Vrepresentation() 1 The Hrepresentation:: - sage: face.Hrepr() + sage: face.ambient_Hrepresentation() (An inequality (60, -112, 65, -14, 1) x + 0 >= 0, An inequality (180, -216, 91, -16, 1) x + 0 >= 0, An inequality (360, -342, 119, -18, 1) x + 0 >= 0, @@ -133,7 +133,7 @@ cdef class CombinatorialFace(SageObject): An inequality (-844, 567, -163, 21, -1) x + 420 >= 0, An inequality (84, -152, 83, -16, 1) x + 0 >= 0, An inequality (-210, 317, -125, 19, -1) x + 0 >= 0) - sage: face.Hrepr(names=False) + sage: face.ambient_H_indices() (3, 4, 5, 6, 7, 8, 9, 10, 11, 18, 19) sage: face.n_ambient_Hrepresentation() 11 @@ -311,17 +311,13 @@ cdef class CombinatorialFace(SageObject): """ return smallInteger(self._ambient_dimension) - def Vrepr(self, names=True): + def ambient_Vrepresentation(self): r""" - Return the vertex-representation of the current face. - - The vertex-representation consists of - the ``[vertices, rays, lines]`` that face contains. + Return the Vrepresentation objects of the ambient polyhedron + defining the face. - INPUT: - - - ``names`` -- if ``True`` returns the names of the ``[vertices, rays, lines]`` - as given on initialization of the :class:`~sage.geometry.polyhedron.combinatorial_polyhedron.base.CombinatorialPolyhedron` + It consists of the vertices/rays/lines + that face contains. EXAMPLES:: @@ -329,25 +325,74 @@ cdef class CombinatorialFace(SageObject): sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter(dimension=2) sage: face = next(it) - sage: face.Vrepr() + sage: face.ambient_Vrepresentation() (A vertex at (4, 1, 5, 2, 3), A vertex at (4, 2, 5, 1, 3), A vertex at (5, 1, 4, 2, 3), A vertex at (5, 2, 4, 1, 3)) sage: face = next(it) - sage: face.Vrepr() + sage: face.ambient_Vrepresentation() (A vertex at (4, 1, 5, 2, 3), A vertex at (4, 1, 5, 3, 2), A vertex at (5, 1, 4, 2, 3), A vertex at (5, 1, 4, 3, 2)) - sage: next(it).Vrepr(False) + + sage: C = CombinatorialPolyhedron([[0,1,2],[0,1,3],[0,2,3],[1,2,3]]) + sage: it = C.face_iter() + sage: for face in it: (face.dimension(), face.ambient_Vrepresentation()) + (2, (1, 2, 3)) + (2, (0, 2, 3)) + (2, (0, 1, 3)) + (2, (0, 1, 2)) + (1, (2, 3)) + (1, (1, 3)) + (1, (1, 2)) + (0, (3,)) + (0, (2,)) + (0, (1,)) + (1, (0, 3)) + (1, (0, 2)) + (0, (0,)) + (1, (0, 1)) + + .. SEEALSO:: + + :meth:`ambient_V_indices`. + """ + if not self._ambient_Vrep: + # There are no names, so we return indices instead. + return self.ambient_V_indices() + cdef size_t length + if self._dual: + # if dual, the Vrepresentation corresponds to the coatom-representation + length = self.set_coatom_repr() + return tuple(self._ambient_Vrep[self.coatom_repr[i]] + for i in range(length)) + else: + # if not dual, the Vrepresentation corresponds to the atom-representation + length = self.set_atom_repr() + return tuple(self._ambient_Vrep[self.atom_repr[i]] + for i in range(length)) + + def ambient_V_indices(self): + r""" + Return the indices of the Vrepresentation + objects of the ambient polyhedron defining the face. + + EXAMPLES:: + + sage: P = polytopes.permutahedron(5) + sage: C = CombinatorialPolyhedron(P) + sage: it = C.face_iter(dimension=2) + sage: face = next(it) + sage: next(it).ambient_V_indices() + (76, 77, 100, 101) + sage: next(it).ambient_V_indices() (76, 77, 82, 83, 88, 89) - sage: next(it).Vrepr(False) - (77, 83, 101, 107) sage: C = CombinatorialPolyhedron([[0,1,2],[0,1,3],[0,2,3],[1,2,3]]) sage: it = C.face_iter() - sage: for face in it: (face.dimension(), face.Vrepr()) + sage: for face in it: (face.dimension(), face.ambient_V_indices()) (2, (1, 2, 3)) (2, (0, 2, 3)) (2, (0, 1, 3)) @@ -362,39 +407,72 @@ cdef class CombinatorialFace(SageObject): (1, (0, 2)) (0, (0,)) (1, (0, 1)) + + .. SEEALSO:: + + :meth:`ambient_Vrepresentation`. """ cdef size_t length if self._dual: - # if dual, the Vrepresenation corresponds to the coatom-representation + # if dual, the Vrepresentation corresponds to the coatom-representation length = self.set_coatom_repr() - if names and self._ambient_Vrep: - return tuple(self._ambient_Vrep[self.coatom_repr[i]] - for i in range(length)) - else: - return tuple(smallInteger(self.coatom_repr[i]) - for i in range(length)) + return tuple(smallInteger(self.coatom_repr[i]) + for i in range(length)) else: - # if not dual, the Vrepresenation corresponds to the atom-representation + # if not dual, the Vrepresentation corresponds to the atom-representation length = self.set_atom_repr() - if names and self._ambient_Vrep: - return tuple(self._ambient_Vrep[self.atom_repr[i]] - for i in range(length)) - else: - return tuple(smallInteger(self.atom_repr[i]) - for i in range(length)) + return tuple(smallInteger(self.atom_repr[i]) + for i in range(length)) + + def Vrepr(self, names=True): + r""" + The method is deprecated. Use one of the following: + - :meth:`CombinatorialFace.ambient_Vrepresentation` + - :meth:`CombinatorialFace.ambient_V_indices` + + Return the vertex-representation of the current face. + + The vertex-representation consists of + the ``[vertices, rays, lines]`` that face contains. + + INPUT: + + - ``names`` -- if ``True`` returns the names of the ``[vertices, rays, lines]`` + as given on initialization of the :class:`~sage.geometry.polyhedron.combinatorial_polyhedron.base.CombinatorialPolyhedron` + + TESTS:: + + sage: P = polytopes.permutahedron(5) + sage: C = CombinatorialPolyhedron(P) + sage: it = C.face_iter(dimension=2) + sage: face = next(it) + sage: face.Vrepr() + doctest:...: DeprecationWarning: the method Vrepr of CombinatorialPolyhedron is deprecated; use ambient_V_indices or ambient_Vrepresentation + See https://trac.sagemath.org/28616 for details. + (A vertex at (4, 1, 5, 2, 3), + A vertex at (4, 2, 5, 1, 3), + A vertex at (5, 1, 4, 2, 3), + A vertex at (5, 2, 4, 1, 3)) + """ + from sage.misc.superseded import deprecation + deprecation(28616, "the method Vrepr of CombinatorialPolyhedron is deprecated; use ambient_V_indices or ambient_Vrepresentation", 3) + if names: + return self.ambient_Vrepresentation() + else: + return self.ambient_V_indices() def n_ambient_Vrepresentation(self): r""" - Return the length of the face. + Return the length of the :meth:`CombinatorialFace.ambient_V_indices`. - Might be faster than `len(self.Vrepr())`. + Might be faster than then using ``len``. EXAMPLES:: sage: P = polytopes.cube() sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter() - sage: all(face.n_ambient_Vrepresentation() == len(face.Vrepr()) for face in it) + sage: all(face.n_ambient_Vrepresentation() == len(face.ambient_Vrepresentation()) for face in it) True TESTS:: @@ -414,106 +492,164 @@ cdef class CombinatorialFace(SageObject): n_Vrepr = deprecated_function_alias(28614, n_ambient_Vrepresentation) - def Hrepr(self, names=True): + def ambient_Hrepresentation(self): r""" - Return the Hrepresentation of the face. - - If ``names`` is ``False`` this is just the indices - of the facets the face is contained in. - If ``names`` is ``True`` this the defining facets - and equations of the face. + Return the Hrepresentation objects of the ambient polyhedron + defining the face. - The facet-representation consists of the facets - that contain the face and of the equalities of the polyhedron. - - INPUT: - - - ``names`` -- if ``True`` returns the names of the ``[facets, equations]`` - as given on initialization of :class:`~sage.geometry.polyhedron.combinatorial_polyhedron.base.CombinatorialPolyhedron` + It consists of the facets/inequalities that contain the face + and the equalities defining the ambient polyhedron. EXAMPLES:: sage: P = polytopes.permutahedron(5) sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter(2) - sage: next(it).Hrepr() + sage: next(it).ambient_Hrepresentation() (An inequality (0, 1, 0, 1, 0) x - 3 >= 0, An inequality (0, 1, 0, 1, 1) x - 6 >= 0, An equation (1, 1, 1, 1, 1) x - 15 == 0) - sage: next(it).Hrepr() + sage: next(it).ambient_Hrepresentation() (An inequality (0, 1, 0, 0, 0) x - 1 >= 0, An inequality (0, 1, 0, 1, 1) x - 6 >= 0, An equation (1, 1, 1, 1, 1) x - 15 == 0) - sage: next(it).Hrepr(False) - (12, 29) - sage: next(it).Hrepr(False) - (6, 29) sage: P = polytopes.cyclic_polytope(4,6) sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter() - sage: next(it).Hrepr() + sage: next(it).ambient_Hrepresentation() (An inequality (-20, 29, -10, 1) x + 0 >= 0, An inequality (60, -47, 12, -1) x + 0 >= 0, An inequality (30, -31, 10, -1) x + 0 >= 0, An inequality (10, -17, 8, -1) x + 0 >= 0, An inequality (-154, 71, -14, 1) x + 120 >= 0, An inequality (-78, 49, -12, 1) x + 40 >= 0) - sage: next(it).Hrepr() + sage: next(it).ambient_Hrepresentation() (An inequality (-50, 35, -10, 1) x + 24 >= 0, An inequality (-12, 19, -8, 1) x + 0 >= 0, An inequality (-20, 29, -10, 1) x + 0 >= 0, An inequality (60, -47, 12, -1) x + 0 >= 0, An inequality (-154, 71, -14, 1) x + 120 >= 0, An inequality (-78, 49, -12, 1) x + 40 >= 0) - sage: next(it).Hrepr(False) + + .. SEEALSO:: + + :meth:`ambient_H_indices`. + """ + if not self._ambient_facets: + # There are no names, so we return indices instead. + return self.ambient_H_indices() + cdef size_t length + if not self._dual: + # if not dual, the facet-represention corresponds to the coatom-representation + length = self.set_coatom_repr() # fill self.coatom_rep_face + return tuple(self._ambient_facets[self.coatom_repr[i]] + for i in range(length)) + self._equalities + else: + # if dual, the facet-represention corresponds to the atom-representation + length = self.set_atom_repr() # fill self.atom_rep_face + return tuple(self._ambient_facets[self.atom_repr[i]] + for i in range(length)) + self._equalities + + def ambient_H_indices(self): + r""" + Return the indices of the Hrepresentation objects + of the ambient polyhedron defining the face. + + EXAMPLES:: + + sage: P = polytopes.permutahedron(5) + sage: C = CombinatorialPolyhedron(P) + sage: it = C.face_iter(2) + sage: next(it).ambient_H_indices() + (28, 29) + sage: next(it).ambient_H_indices() + (25, 29) + + sage: P = polytopes.cyclic_polytope(4,6) + sage: C = CombinatorialPolyhedron(P) + sage: it = C.face_iter() + sage: _ = next(it); _ = next(it) + sage: next(it).ambient_H_indices() (0, 1, 2, 4, 5, 7) - sage: next(it).Hrepr(False) + sage: next(it).ambient_H_indices() (0, 1, 5, 6, 7, 8) - sage: next(it).Hrepr(False) + sage: next(it).ambient_H_indices() (0, 1, 2, 3, 6, 8) sage: [next(it).dimension() for _ in range(2)] [0, 1] sage: face = next(it) - sage: face.Hrepr(False) + sage: face.ambient_H_indices() (4, 5, 7) - sage: face.Hrepr() - (An inequality (60, -47, 12, -1) x + 0 >= 0, - An inequality (30, -31, 10, -1) x + 0 >= 0, - An inequality (-154, 71, -14, 1) x + 120 >= 0) + + .. SEEALSO:: + + :meth:`ambient_Hrepresentation`. """ cdef size_t length if not self._dual: # if not dual, the facet-represention corresponds to the coatom-representation - length = self.set_coatom_repr() # fill self.coatom_repr_face - if names and self._ambient_facets: - return tuple(self._ambient_facets[self.coatom_repr[i]] - for i in range(length)) + self._equalities - else: - return tuple(smallInteger(self.coatom_repr[i]) - for i in range(length)) + length = self.set_coatom_repr() # fill self.coatom_rep_face + return tuple(smallInteger(self.coatom_repr[i]) + for i in range(length)) else: # if dual, the facet-represention corresponds to the atom-representation - length = self.set_atom_repr() # fill self.atom_repr_face - if names and self._ambient_facets: - return tuple(self._ambient_facets[self.atom_repr[i]] - for i in range(length)) + self._equalities - else: - return tuple(smallInteger(self.atom_repr[i]) - for i in range(length)) + length = self.set_atom_repr() # fill self.atom_rep_face + return tuple(smallInteger(self.atom_repr[i]) + for i in range(length)) + + def Hrepr(self, names=True): + r""" + The method is deprecated. Use one of the following: + - :meth:`CombinatorialFace.ambient_Hrepresentation` + - :meth:`CombinatorialFace.ambient_H_indices` + + Return the Hrepresentation of the face. + + If ``names`` is ``False`` this is just the indices + of the facets the face is contained in. + If ``names`` is ``True`` this the defining facets + and equations of the face. + + The facet-representation consists of the facets + that contain the face and of the equalities of the polyhedron. + + INPUT: + + - ``names`` -- if ``True`` returns the names of the ``[facets, equations]`` + as given on initialization of :class:`~sage.geometry.polyhedron.combinatorial_polyhedron.base.CombinatorialPolyhedron` + + TESTS:: + + sage: P = polytopes.permutahedron(5) + sage: C = CombinatorialPolyhedron(P) + sage: it = C.face_iter(2) + sage: next(it).Hrepr() + doctest:...: DeprecationWarning: the method Hrepr of CombinatorialPolyhedron is deprecated; use ambient_H_indices or ambient_Hrepresentation + See https://trac.sagemath.org/28616 for details. + (An inequality (0, 1, 0, 1, 0) x - 3 >= 0, + An inequality (0, 1, 0, 1, 1) x - 6 >= 0, + An equation (1, 1, 1, 1, 1) x - 15 == 0) + """ + from sage.misc.superseded import deprecation + deprecation(28616, "the method Hrepr of CombinatorialPolyhedron is deprecated; use ambient_H_indices or ambient_Hrepresentation", 3) + if names: + return self.ambient_Hrepresentation() + else: + return self.ambient_H_indices() def n_ambient_Hrepresentation(self): r""" - Returns the length of the :meth:`Hrepr`. + Return the length of the :meth:`CombinatorialFace.ambient_H_indices`. - Might be faster than ``len(self.Hrepr())``. + Might be faster than then using ``len``. EXAMPLES:: sage: P = polytopes.cube() sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter() - sage: all(face.n_ambient_Hrepresentation() == len(face.Hrepr()) for face in it) + sage: all(face.n_ambient_Hrepresentation() == len(face.ambient_Hrepresentation()) for face in it) True TESTS:: diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx index 2636111cca6..e2978baf132 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx @@ -69,7 +69,7 @@ Obtain the Vrepresentation:: sage: it = FaceIterator(C, False) sage: face = next(it) - sage: face.Vrepr() + sage: face.ambient_Vrepresentation() (A vertex at (0, -1, 0), A vertex at (0, 0, -1), A vertex at (1, 0, 0)) sage: face.n_ambient_Vrepresentation() 3 @@ -78,12 +78,12 @@ Obtain the facet-representation:: sage: it = FaceIterator(C, True) sage: face = next(it) - sage: face.Hrepr() + sage: face.ambient_Hrepresentation() (An inequality (-1, -1, 1) x + 1 >= 0, An inequality (-1, -1, -1) x + 1 >= 0, An inequality (-1, 1, -1) x + 1 >= 0, An inequality (-1, 1, 1) x + 1 >= 0) - sage: face.Hrepr(names=False) + sage: face.ambient_H_indices() (4, 5, 6, 7) sage: face.n_ambient_Hrepresentation() 4 @@ -92,10 +92,10 @@ In non-dual mode one can ignore all faces contained in the current face:: sage: it = FaceIterator(C, False) sage: face = next(it) - sage: face.Hrepr(names=False) + sage: face.ambient_H_indices() (7,) sage: it.ignore_subfaces() - sage: [face.Hrepr(names=False) for face in it] + sage: [face.ambient_H_indices() for face in it] [(6,), (5,), (4,), @@ -120,10 +120,10 @@ In dual mode one can ignore all faces that contain the current face:: sage: it = FaceIterator(C, True) sage: face = next(it) - sage: face.Vrepr(names=False) + sage: face.ambient_V_indices() (5,) sage: it.ignore_supfaces() - sage: [face.Vrepr(names=False) for face in it] + sage: [face.ambient_V_indices() for face in it] [(4,), (3,), (2,), @@ -210,7 +210,7 @@ cdef class FaceIterator(SageObject): sage: P = Polyhedron(rays=[[0,0,1], [0,1,0], [1,0,0]]) sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter() - sage: [face.Vrepr() for face in it] + sage: [face.ambient_Vrepresentation() for face in it] [(A vertex at (0, 0, 0), A ray in the direction (0, 1, 0), A ray in the direction (1, 0, 0)), @@ -248,10 +248,10 @@ cdef class FaceIterator(SageObject): sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter(dual=False) sage: face = next(it) - sage: face.Hrepr(names=False) + sage: face.ambient_H_indices() (5,) sage: it.ignore_subfaces() - sage: [face.Hrepr(names=False) for face in it] + sage: [face.ambient_H_indices() for face in it] [(4,), (3,), (2,), @@ -284,9 +284,9 @@ cdef class FaceIterator(SageObject): sage: next(it) A 0-dimensional face of a 3-dimensional combinatorial polyhedron sage: face = next(it) - sage: face.Vrepr(names=False) + sage: face.ambient_V_indices() (6,) - sage: [face.Vrepr(names=False) for face in it] + sage: [face.ambient_V_indices() for face in it] [(5,), (4,), (3,), diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx index 5dae2d5ce21..397ad5f2003 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx @@ -437,12 +437,12 @@ cdef class PolyhedronFaceLattice: sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter(dimension=1) sage: face = next(it) - sage: face_via_all_faces_from_iterator(it, C).Vrepr() + sage: face_via_all_faces_from_iterator(it, C).ambient_Vrepresentation() (A vertex at (3, 1, 4, 2), A vertex at (3, 2, 4, 1)) - sage: face.Vrepr() + sage: face.ambient_Vrepresentation() (A vertex at (3, 1, 4, 2), A vertex at (3, 2, 4, 1)) - sage: all(face_via_all_faces_from_iterator(it, C).Vrepr() == - ....: face.Vrepr() for face in it) + sage: all(face_via_all_faces_from_iterator(it, C).ambient_Vrepresentation() == + ....: face.ambient_Vrepresentation() for face in it) True sage: P = polytopes.twenty_four_cell() @@ -450,12 +450,12 @@ cdef class PolyhedronFaceLattice: sage: it = C.face_iter() sage: face = next(it) sage: while (face.dimension() == 3): face = next(it) - sage: face_via_all_faces_from_iterator(it, C).Vrepr() + sage: face_via_all_faces_from_iterator(it, C).ambient_Vrepresentation() (A vertex at (-1/2, 1/2, -1/2, -1/2), A vertex at (-1/2, 1/2, 1/2, -1/2), A vertex at (0, 0, 0, -1)) - sage: all(face_via_all_faces_from_iterator(it, C).Vrepr(False) == - ....: face.Vrepr(False) for face in it) + sage: all(face_via_all_faces_from_iterator(it, C).ambient_V_indices() == + ....: face.ambient_V_indices() for face in it) True """ cdef size_t length diff --git a/src/sage/geometry/polyhedron/face.py b/src/sage/geometry/polyhedron/face.py index 41df0cb3db3..1a84f4fb97d 100644 --- a/src/sage/geometry/polyhedron/face.py +++ b/src/sage/geometry/polyhedron/face.py @@ -714,18 +714,18 @@ def combinatorial_face_to_polyhedral_face(polyhedron, combinatorial_face): sage: polytopes.simplex(backend='polymake').equations()[0].index() # optional - polymake 4 """ - V_indices = combinatorial_face.Vrepr(names=False) + V_indices = combinatorial_face.ambient_V_indices() n_equations = polyhedron.n_equations() if polyhedron.backend() in ('ppl',): # Equations before inequalities in Hrep. H_indices = tuple(range(n_equations)) - H_indices += tuple(x+n_equations for x in combinatorial_face.Hrepr(names=False)) + H_indices += tuple(x+n_equations for x in combinatorial_face.ambient_H_indices()) elif polyhedron.backend() in ('normaliz', 'cdd', 'field', 'polymake'): # Equations after the inequalities in Hrep. n_ieqs = polyhedron.n_inequalities() H_indices = tuple(range(n_ieqs, n_ieqs + n_equations)) - H_indices += tuple(x for x in combinatorial_face.Hrepr(names=False)) + H_indices += tuple(x for x in combinatorial_face.ambient_H_indices()) else: raise NotImplementedError("unknown backend") diff --git a/src/sage/geometry/polyhedron/library.py b/src/sage/geometry/polyhedron/library.py index d66997fc95e..d4cce13d660 100644 --- a/src/sage/geometry/polyhedron/library.py +++ b/src/sage/geometry/polyhedron/library.py @@ -89,7 +89,6 @@ from sage.graphs.digraph import DiGraph from sage.combinat.root_system.associahedron import Associahedron - def zero_sum_projection(d, base_ring=RDF): r""" Return a matrix corresponding to the projection on the orthogonal of @@ -2715,21 +2714,36 @@ def one_hundred_twenty_cell(self, exact=True, backend=None, construction='coxete else: raise ValueError("construction (={}) must be either 'coxeter' or 'as_permutahedron' ".format(construction)) - def hypercube(self, dim, backend=None): + def hypercube(self, dim, intervals=None, backend=None): r""" - Return a hypercube in the given dimension. + Return a hypercube of the given dimension. - The `d` dimensional hypercube is the convex hull of the points `(\pm 1, - \pm 1, \ldots, \pm 1)` in `\RR^d`. For more information see - the :wikipedia:`Hypercube`. + The ``dim``-dimensional hypercube is by default the convex hull of the + `2^{\text{dim}}` `\pm 1` vectors of length ``dim``. Alternatively, + it is the product of ``dim`` line segments given in the ``intervals``. + For more information see the wikipedia article :wikipedia:`Hypercube`. INPUT: - - ``dim`` -- integer. The dimension of the cube. + - ``dim`` -- integer. The dimension of the hypercube. + + - ``intervals`` -- (default = None). It takes the following + possible inputs: + + - If ``None`` (the default), it returns the the `\pm 1`-cube of + dimension ``dim``. + + - ``'zero_one'`` -- (string). Return the `0/1`-cube. + + - a list of length ``dim``. Its elements are pairs of + numbers `(a,b)` with `a < b`. The cube will be the product of + these intervals. - ``backend`` -- the backend to use to create the polytope. - EXAMPLES:: + EXAMPLES: + + Create the `\pm 1`-hypercube of dimension 4:: sage: four_cube = polytopes.hypercube(4) sage: four_cube.is_simple() @@ -2741,20 +2755,71 @@ def hypercube(self, dim, backend=None): sage: four_cube.ehrhart_polynomial() # optional - latte_int 16*t^4 + 32*t^3 + 24*t^2 + 8*t + 1 + Return the `0/1`-hypercube of dimension 4:: + + sage: z_cube = polytopes.hypercube(4,intervals = 'zero_one') + sage: z_cube.vertices()[0] + A vertex at (0, 0, 0, 0) + sage: z_cube.is_simple() + True + sage: z_cube.base_ring() + Integer Ring + sage: z_cube.volume() + 1 + sage: z_cube.ehrhart_polynomial() # optional - latte_int + t^4 + 4*t^3 + 6*t^2 + 4*t + 1 + + Return the 4-dimensional combinatorial cube that is the product of + [0,3]^4:: + + sage: t_cube = polytopes.hypercube(4, intervals = [[0,3]]*4) + + Checking that t_cube is three times the previous `0/1`-cube:: + + sage: t_cube == 3 * z_cube + True + TESTS:: sage: fc = polytopes.hypercube(4,backend='normaliz') # optional - pynormaliz sage: TestSuite(fc).run(skip='_test_pickling') # optional - pynormaliz + + If the dimension ``dim`` is not equal to the length of intervals, an + error is raised:: + + sage: u_cube = polytopes.hypercube(2,intervals = [[0,1],[0,2],[0,3]]) + Traceback (most recent call last): + ... + ValueError: the dimension of the hypercube must match the number of intervals + + If a string besides 'zero_one' is passed to ``intervals``, return an + error:: + + sage: v_cube = polytopes.hypercube(3,intervals = 'a_string') + Traceback (most recent call last): + ... + ValueError: the only allowed string is 'zero_one' """ - return Polyhedron(vertices=list(itertools.product([1, -1], repeat=dim)), base_ring=ZZ, backend=backend) + if intervals is None: + cp = list(itertools.product([-1,1], repeat=dim)) + elif isinstance(intervals, str): + if intervals == 'zero_one': + cp = list(itertools.product([0,1], repeat=dim)) + else: + raise ValueError("the only allowed string is 'zero_one'") + elif len(intervals) == dim: + cp = list(itertools.product(*intervals)) + else: + raise ValueError("the dimension of the hypercube must match the number of intervals") + return Polyhedron(vertices=cp, backend=backend) - def cube(self, backend=None): + def cube(self, intervals=None, backend=None): r""" Return the cube. The cube is the Platonic solid that is obtained as the convex hull of - the points `(\pm 1, \pm 1, \pm 1)`. It generalizes into several - dimension into hypercubes. + the eight `\pm 1` vectors of length 3 (by default). Alternatively, the + cube is the product of three intervals from ``intervals``. .. SEEALSO:: @@ -2762,9 +2827,26 @@ def cube(self, backend=None): INPUT: + - ``intervals`` -- list (default=None). It takes the following + possible inputs: + + - If the input is ``None`` (the default), returns the convex hull of + the eight `\pm 1` vectors of length three. + + - ``'zero_one'`` -- (string). Return the `0/1`-cube. + + - a list of 3 lists of length 2. The cube will be a product of + these three intervals. + - ``backend`` -- the backend to use to create the polytope. - EXAMPLES:: + OUTPUT: + + A cube as a polyhedron object. + + EXAMPLES: + + Return the `\pm 1`-cube:: sage: c = polytopes.cube() sage: c @@ -2775,8 +2857,21 @@ def cube(self, backend=None): 8 sage: c.plot() Graphics3d Object + + Return the `0/1`-cube:: + + sage: cc = polytopes.cube(intervals ='zero_one') + sage: cc.vertices_list() + [[0, 0, 0], + [0, 0, 1], + [0, 1, 0], + [0, 1, 1], + [1, 0, 0], + [1, 0, 1], + [1, 1, 0], + [1, 1, 1]] """ - return self.hypercube(3, backend=backend) + return self.hypercube(3, backend=backend, intervals=intervals) def cross_polytope(self, dim, backend=None): r""" diff --git a/src/sage/geometry/polyhedron/parent.py b/src/sage/geometry/polyhedron/parent.py index 4e16b7225b4..da79995a642 100644 --- a/src/sage/geometry/polyhedron/parent.py +++ b/src/sage/geometry/polyhedron/parent.py @@ -456,9 +456,9 @@ def _element_constructor_(self, *args, **kwds): INPUT: - - ``Vrep`` -- a list `[vertices, rays, lines]`` or ``None``. + - ``Vrep`` -- a list ``[vertices, rays, lines]`` or ``None``. - - ``Hrep`` -- a list `[ieqs, eqns]`` or ``None``. + - ``Hrep`` -- a list ``[ieqs, eqns]`` or ``None``. - ``convert`` -- boolean keyword argument (default: ``True``). Whether to convert the coordinates into the base diff --git a/src/sage/groups/abelian_gps/abelian_aut.py b/src/sage/groups/abelian_gps/abelian_aut.py index faaff995506..f45738c6dbe 100644 --- a/src/sage/groups/abelian_gps/abelian_aut.py +++ b/src/sage/groups/abelian_gps/abelian_aut.py @@ -73,19 +73,19 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ # **************************************************************************** from sage.categories.groups import Groups from sage.groups.abelian_gps.abelian_group_gap import AbelianGroup_gap from sage.groups.group import Group from sage.groups.libgap_wrapper import ParentLibGAP, ElementLibGAP from sage.groups.libgap_mixin import GroupMixinLibGAP -from sage.libs.gap.element import GapElement from sage.libs.gap.libgap import libgap from sage.matrix.matrix_space import MatrixSpace from sage.rings.all import ZZ from sage.structure.unique_representation import UniqueRepresentation + class AbelianGroupAutomorphism(ElementLibGAP): """ Automorphisms of abelian groups with gap. diff --git a/src/sage/groups/abelian_gps/abelian_group_element.py b/src/sage/groups/abelian_gps/abelian_group_element.py index 75d3c18a14f..3859e6f9df6 100644 --- a/src/sage/groups/abelian_gps/abelian_group_element.py +++ b/src/sage/groups/abelian_gps/abelian_group_element.py @@ -41,15 +41,12 @@ # Copyright (C) 2012 Volker Braun # # Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ ########################################################################### - -from sage.rings.integer import Integer -from sage.rings.infinity import infinity -from sage.arith.all import LCM, GCD from sage.groups.abelian_gps.element_base import AbelianGroupElementBase + def is_AbelianGroupElement(x): """ Return true if x is an abelian group element, i.e., an element of @@ -112,7 +109,6 @@ def as_permutation(self): sage: ap in Gp True """ - from sage.groups.perm_gps.permgroup import PermutationGroup from sage.interfaces.all import gap G = self.parent() invs = list(G.gens_orders()) @@ -162,5 +158,5 @@ def word_problem(self, words): sage: prod([x^i for x,i in v]) == y*x True """ - from sage.groups.abelian_gps.abelian_group import AbelianGroup, word_problem - return word_problem(words,self) + from sage.groups.abelian_gps.abelian_group import word_problem + return word_problem(words, self) diff --git a/src/sage/groups/affine_gps/euclidean_group.py b/src/sage/groups/affine_gps/euclidean_group.py index 5f321928072..1ee458bf441 100644 --- a/src/sage/groups/affine_gps/euclidean_group.py +++ b/src/sage/groups/affine_gps/euclidean_group.py @@ -16,8 +16,6 @@ # https://www.gnu.org/licenses/ ############################################################################## -from sage.matrix.all import MatrixSpace -from sage.modules.all import FreeModule from sage.groups.affine_gps.affine_group import AffineGroup diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 0c6a016b14b..1642d481664 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -118,7 +118,7 @@ - Miguel Angel Marco Buzunariz """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2012 Miguel Angel Marco Buzunariz # # This program is free software: you can redistribute it and/or modify @@ -126,7 +126,7 @@ # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # https://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** from sage.groups.group import Group from sage.groups.libgap_wrapper import ParentLibGAP, ElementLibGAP @@ -134,13 +134,8 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.libs.gap.libgap import libgap from sage.libs.gap.element import GapElement -from sage.rings.integer import Integer -from sage.rings.integer_ring import IntegerRing from sage.misc.cachefunc import cached_method from sage.groups.free_group import FreeGroupElement - -from sage.structure.element import MultiplicativeGroupElement -from sage.interfaces.gap import gap from sage.functions.generalized import sign from sage.matrix.constructor import matrix from sage.categories.morphism import SetMorphism @@ -1395,7 +1390,6 @@ def simplification_isomorphism(self): Uses GAP. """ I = self.gap().IsomorphismSimplifiedFpGroup() - domain = self codomain = wrap_FpGroup(I.Range()) phi = lambda x: codomain(I.ImageElm(x.gap())) HS = self.Hom(codomain) diff --git a/src/sage/groups/indexed_free_group.py b/src/sage/groups/indexed_free_group.py index 0d8736f3411..c15c2e104fc 100644 --- a/src/sage/groups/indexed_free_group.py +++ b/src/sage/groups/indexed_free_group.py @@ -24,8 +24,7 @@ from sage.categories.poor_man_map import PoorManMap from sage.groups.group import Group, AbelianGroup from sage.monoids.indexed_free_monoid import (IndexedMonoid, - IndexedMonoidElement, IndexedFreeMonoidElement, - IndexedFreeAbelianMonoidElement) + IndexedFreeMonoidElement, IndexedFreeAbelianMonoidElement) from sage.misc.cachefunc import cached_method import sage.data_structures.blas_dict as blas from sage.rings.integer import Integer diff --git a/src/sage/groups/perm_gps/permgroup_element.pxd b/src/sage/groups/perm_gps/permgroup_element.pxd index 7178f174fc0..0f9a6e0ac9c 100644 --- a/src/sage/groups/perm_gps/permgroup_element.pxd +++ b/src/sage/groups/perm_gps/permgroup_element.pxd @@ -1,15 +1,17 @@ from sage.structure.element cimport MultiplicativeGroupElement, MonoidElement, Element from sage.structure.list_clone cimport ClonableIntArray +from sage.libs.gap.element cimport GapElement cdef class PermutationGroupElement(MultiplicativeGroupElement): cdef int* perm cdef int n cdef int perm_buf[15] # to avoid malloc for small elements - cdef Element _gap_element + cdef GapElement _libgap cdef PermutationGroupElement _new_c(self) cdef _alloc(self, int) cpdef _set_identity(self) cpdef _set_list_images(self, v, bint convert) + cpdef _set_libgap(self, GapElement p) cpdef _set_list_cycles(self, c, bint convert) cpdef _set_string(self, str s) cpdef _set_permutation_group_element(self, PermutationGroupElement p, bint convert) diff --git a/src/sage/groups/perm_gps/permgroup_element.pyx b/src/sage/groups/perm_gps/permgroup_element.pyx index 98996982268..90d014cb134 100644 --- a/src/sage/groups/perm_gps/permgroup_element.pyx +++ b/src/sage/groups/perm_gps/permgroup_element.pyx @@ -124,10 +124,14 @@ from sage.interfaces.gap import GapElement as PExpectGapElement from sage.interfaces.gp import GpElement from sage.libs.gap.libgap import libgap -from sage.libs.gap.element cimport GapElement, GapElement_List, GapElement_String, GapElement_Permutation +from sage.libs.gap.gap_includes cimport (UInt, UInt2, UInt4, T_PERM2, T_PERM4, + NEW_PERM2, NEW_PERM4, TNUM_OBJ, DEG_PERM2, DEG_PERM4, CONST_ADDR_PERM2, + CONST_ADDR_PERM4, ADDR_PERM2, ADDR_PERM4) +from sage.libs.gap.util cimport initialize +from sage.libs.gap.element cimport (GapElement, GapElement_List, + GapElement_String, GapElement_Permutation, make_GapElement_Permutation) from sage.libs.gap.gap_includes cimport Obj, INT_INTOBJ, ELM_LIST - import operator from sage.rings.fast_arith cimport arith_llong @@ -464,7 +468,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): self._set_list_images(g, convert) elif isinstance(g, GapElement): if isinstance(g, GapElement_Permutation): - self._set_list_images(g.ListPerm(), False) + self._set_libgap(g) elif isinstance(g, GapElement_String): self._set_string(g.sage()) elif isinstance(g, GapElement_List): @@ -555,6 +559,55 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): for i in range(vn, self.n): self.perm[i] = i + cpdef _set_libgap(self, GapElement p): + r""" + TESTS:: + + sage: S = SymmetricGroup(4) + sage: S(libgap.eval('(1,2)')) + (1,2) + sage: S(libgap.eval('(2,3)')) + (2,3) + sage: S(libgap.eval('(1,4)')) + (1,4) + sage: S(libgap.eval('(1,2,4)(3,5)') * libgap.eval('(3,5)')) + (1,2,4) + + sage: S(libgap.eval('(1,5)')) + Traceback (most recent call last): + ... + ValueError: invalid data to initialize a permutation + """ + cdef UInt2* p2 + cdef UInt4* p4 + cdef int i + cdef UInt d + + if TNUM_OBJ(p.value) == T_PERM2: + d = DEG_PERM2(p.value) + if d > self.n: + d = self.n + else: + for i in range(d, self.n): + self.perm[i] = i + p2 = CONST_ADDR_PERM2(p.value) + for i in range(d): + self.perm[i] = p2[i] + elif TNUM_OBJ(p.value) == T_PERM4: + d = DEG_PERM4(p.value) + if d > self.n: + d = self.n + else: + for i in range(d, self.n): + self.perm[i] = i + p4 = CONST_ADDR_PERM4(p.value) + for i in range(d): + self.perm[i] = p4[i] + else: + raise TypeError("not a gap permutation") + + self._libgap = p + cpdef _set_permutation_group_element(self, PermutationGroupElement p, bint convert): r""" TESTS:: @@ -768,58 +821,74 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): def _gap_(self, gap=None): """ - Returns - - EXAMPLES:: + TESTS:: sage: g = PermutationGroupElement([(1,2,3),(4,5)]); g (1,2,3)(4,5) - sage: a = g._gap_(); a + sage: g._gap_() (1,2,3)(4,5) - sage: g._gap_() is g._gap_() - True - - Note that only one GapElement is cached: - - sage: gap2 = Gap() - sage: b = g._gap_(gap2) - sage: c = g._gap_() - sage: a is c - False """ - if (self._gap_element is None or - (gap is not None and self._gap_element._parent is not gap)): - if gap is None: - from sage.interfaces.gap import gap - self._gap_element = gap(self._gap_init_()) - - return self._gap_element + if gap is None: + from sage.interfaces.gap import gap + return gap(self._gap_init_()) def _libgap_(self): - if (self._gap_element is None or - self._gap_element._parent is not libgap): - self._gap_element = libgap.eval(self._libgap_init_()) - - return self._gap_element - - # for compatibility with sage.groups.libgap_wrapper.ElementLibGAP - # see sage.groups.perm_gps.permgroup.PermutationGroup_generic.gap - def gap(self): - """ + r""" Returns self as a libgap element EXAMPLES:: + sage: S = SymmetricGroup(4) + sage: p = S('(2,4)') + sage: p_libgap = libgap(p) + sage: p_libgap.Order() + 2 + sage: S(p_libgap) == p + True + sage: P = PGU(8,2) sage: p, q = P.gens() sage: p_libgap = p.gap() + + TESTS:: + + sage: P = PGU(8,2) + sage: p, q = P.gens() sage: p_pexpect = gap(p) sage: p_libgap == p_pexpect True sage: type(p_libgap) == type(p_pexpect) False + + If the permutation element is built from a libgap element, it is cached + and returned by this function:: + + sage: S = SymmetricGroup(4) + sage: p = libgap.eval('(1,3)') + sage: libgap(S(p)) is p + True + + Test the empty permutation:: + + sage: p = SymmetricGroup(0).an_element() + sage: p._libgap_() + () """ - return libgap(self) + if self._libgap is not None: + return self._libgap + initialize() + + cdef Obj res = NEW_PERM2(self.n) + cdef UInt2* p = ADDR_PERM2(res) + cdef UInt i + for i in range(self.n): + p[i] = self.perm[i] + self._libgap = make_GapElement_Permutation(libgap, res) + return self._libgap + + # for compatibility with sage.groups.libgap_wrapper.ElementLibGAP + # see sage.groups.perm_gps.permgroup.PermutationGroup_generic.gap + gap = _libgap_ def _gap_init_(self): """ diff --git a/src/sage/interfaces/maxima.py b/src/sage/interfaces/maxima.py index eacbc4c3a22..d6d37fe3305 100644 --- a/src/sage/interfaces/maxima.py +++ b/src/sage/interfaces/maxima.py @@ -472,7 +472,7 @@ from random import randrange -from sage.env import DOT_SAGE, SAGE_LOCAL +from sage.env import DOT_SAGE, SAGE_LOCAL, MAXIMA from sage.misc.misc import ECL_TMP from .expect import (Expect, ExpectElement, gc_disabled) @@ -546,7 +546,7 @@ def __init__(self, script_subdirectory=None, logfile=None, server=None, Expect.__init__(self, name = 'maxima', prompt = r'\(\%i[0-9]+\) ', - command = 'maxima -p "{0}"'.format(STARTUP), + command = '"{0}" -p "{1}"'.format(MAXIMA, STARTUP), env = {'TMPDIR': str(ECL_TMP)}, script_subdirectory = script_subdirectory, restart_on_ctrlc = False, diff --git a/src/sage/interfaces/qepcad.py b/src/sage/interfaces/qepcad.py index 27954985561..508d1ef3f99 100644 --- a/src/sage/interfaces/qepcad.py +++ b/src/sage/interfaces/qepcad.py @@ -612,6 +612,7 @@ import re import sys +from sage.cpython.string import bytes_to_str from sage.misc.flatten import flatten from sage.misc.sage_eval import sage_eval from sage.repl.preparse import implicit_mul @@ -1067,7 +1068,7 @@ def phase(self): if match == pexpect.EOF: return 'EXITED' else: - return match.group(1) + return bytes_to_str(match.group(1)) def _parse_answer_stats(self): r""" @@ -1089,7 +1090,7 @@ def _parse_answer_stats(self): """ if self.phase() != 'EXITED': raise ValueError("QEPCAD is not finished yet") - final = self._qex.expect().before + final = bytes_to_str(self._qex.expect().before) match = re.search('\nAn equivalent quantifier-free formula:(.*)\n=+ The End =+\r\n\r\n(.*)$', final, re.DOTALL) if match: @@ -1717,7 +1718,7 @@ def qepcad_banner(): """ qex = Qepcad_expect() qex._start() - banner = qex.expect().before + banner = bytes_to_str(qex.expect().before) return AsciiArtString(banner) def qepcad_version(): diff --git a/src/sage/libs/gap/element.pxd b/src/sage/libs/gap/element.pxd index cc11f8f19e0..84dc49d8110 100644 --- a/src/sage/libs/gap/element.pxd +++ b/src/sage/libs/gap/element.pxd @@ -27,6 +27,7 @@ cdef GapElement_Rational make_GapElement_Rational(parent, Obj obj) cdef GapElement_String make_GapElement_String(parent, Obj obj) cdef GapElement_Boolean make_GapElement_Boolean(parent, Obj obj) cdef GapElement_Function make_GapElement_Function(parent, Obj obj) +cdef GapElement_Permutation make_GapElement_Permutation(parent, Obj obj) cdef char *capture_stdout(Obj, Obj) cdef char *gap_element_str(Obj) diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index 26fb45b45dd..0698798c97a 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -669,7 +669,7 @@ cdef class GapElement(RingElement): sage: x = libgap(1) sage: x._type_number() - (0L, 'T_INT (integer)') + (0, 'T_INT (integer)') """ n = TNUM_OBJ(self.value) global decode_type_number diff --git a/src/sage/libs/gap/gap_includes.pxd b/src/sage/libs/gap/gap_includes.pxd index f624ccd0b0e..5a9ab486f77 100644 --- a/src/sage/libs/gap/gap_includes.pxd +++ b/src/sage/libs/gap/gap_includes.pxd @@ -9,11 +9,16 @@ # http://www.gnu.org/licenses/ ############################################################################### +from libc.stdint cimport uintptr_t, uint8_t, uint16_t, uint32_t, uint64_t cdef extern from "gap/system.h" nogil: ctypedef char Char ctypedef int Int - ctypedef unsigned int UInt + ctypedef uintptr_t UInt + ctypedef uint8_t UInt1 + ctypedef uint16_t UInt2 + ctypedef uint32_t UInt4 + ctypedef uint64_t UInt8 ctypedef void* Obj @@ -146,6 +151,17 @@ cdef extern from "gap/objects.h" nogil: T_WPOBJ +cdef extern from "gap/permutat.h" nogil: + UInt DEG_PERM2(Obj) + UInt DEG_PERM4(Obj) + Obj NEW_PERM2(UInt) + Obj NEW_PERM4(UInt) + UInt2* ADDR_PERM2(Obj) + UInt4* ADDR_PERM4(Obj) + const UInt2* CONST_ADDR_PERM2(Obj) + const UInt4* CONST_ADDR_PERM4(Obj) + + cdef extern from "gap/precord.h" nogil: Obj NEW_PREC(int len) int LEN_PREC(Obj rec) diff --git a/src/sage/libs/singular/decl.pxd b/src/sage/libs/singular/decl.pxd index 76eb83a39c6..607c0cb12ae 100644 --- a/src/sage/libs/singular/decl.pxd +++ b/src/sage/libs/singular/decl.pxd @@ -685,6 +685,10 @@ cdef extern from "singular/Singular/libsingular.h": int p_IsUnit(poly *p, ring *r) + # TRUE if poly is one + + bint p_IsOne(const poly *p, const ring *r) + # substitute monomial for variable given by varidx in poly poly *pSubst(poly *p, int varidx, poly *value) @@ -749,6 +753,9 @@ cdef extern from "singular/Singular/libsingular.h": int n_IsUnit(number *n, const n_Procs_s *cf) number *n_Invers(number *n, const n_Procs_s *cf) + # Characteristic of coefficient domain + int n_GetChar(const ring* r) + # rational number from int number *nlInit(int) diff --git a/src/sage/misc/functional.py b/src/sage/misc/functional.py index 424ba0dd1a4..f27976bdd0d 100644 --- a/src/sage/misc/functional.py +++ b/src/sage/misc/functional.py @@ -859,7 +859,7 @@ def is_integrally_closed(x): return x.is_integrally_closed() -def is_field(x): +def is_field(x, proof=True): """ Return whether or not ``x`` is a field. @@ -872,7 +872,7 @@ def is_field(x): sage: is_field(F) True """ - return x.is_field() + return x.is_field(proof=proof) def is_odd(x): diff --git a/src/sage/quadratic_forms/binary_qf.py b/src/sage/quadratic_forms/binary_qf.py old mode 100644 new mode 100755 index f7ed55034eb..65f9f35e56a --- a/src/sage/quadratic_forms/binary_qf.py +++ b/src/sage/quadratic_forms/binary_qf.py @@ -6,7 +6,7 @@ EXAMPLES:: - sage: Q = BinaryQF([1,2,3]) + sage: Q = BinaryQF([1, 2, 3]) sage: Q x^2 + 2*x*y + 3*y^2 sage: Q.discriminant() @@ -61,7 +61,6 @@ from sage.matrix.constructor import Matrix from sage.misc.cachefunc import cached_method - @total_ordering class BinaryQF(SageObject): r""" @@ -83,16 +82,16 @@ class BinaryQF(SageObject): EXAMPLES:: - sage: b = BinaryQF([1,2,3]) + sage: b = BinaryQF([1, 2, 3]) sage: b.discriminant() -8 - sage: b1 = BinaryQF(1,2,3) + sage: b1 = BinaryQF(1, 2, 3) sage: b1 == b True sage: R. = ZZ[] sage: BinaryQF(x^2 + 2*x*y + 3*y^2) == b True - sage: BinaryQF(1,0,1) + sage: BinaryQF(1, 0, 1) x^2 + y^2 """ def __init__(self, a, b=None, c=None): @@ -111,9 +110,9 @@ def __init__(self, a, b=None, c=None): EXAMPLES:: - sage: Q = BinaryQF([1,2,3]); Q + sage: Q = BinaryQF([1, 2, 3]); Q x^2 + 2*x*y + 3*y^2 - sage: Q = BinaryQF([1,2]) + sage: Q = BinaryQF([1, 2]) Traceback (most recent call last): ... TypeError: binary quadratic form must be given by a quadratic homogeneous bivariate integer polynomial or its coefficients @@ -160,7 +159,7 @@ def _pari_init_(self): EXAMPLES:: - sage: f = BinaryQF([2,3,4]); f + sage: f = BinaryQF([2, 3, 4]); f 2*x^2 + 3*x*y + 4*y^2 sage: f._pari_init_() 'Qfb(2,3,4)' @@ -197,12 +196,12 @@ def __mul__(self, right): sage: (R[1] * R[1] * R[1]).reduced_form() x^2 + x*y + 6*y^2 sage: q1 = BinaryQF(1, 1, 4) - sage: M = Matrix(ZZ, [[1,3], [0,1]]) + sage: M = Matrix(ZZ, [[1, 3], [0, 1]]) sage: q1*M x^2 + 7*x*y + 16*y^2 sage: q1.matrix_action_right(M) x^2 + 7*x*y + 16*y^2 - sage: N = Matrix(ZZ, [[1,0], [1,0]]) + sage: N = Matrix(ZZ, [[1, 0], [1, 0]]) sage: q1*(M*N) == q1.matrix_action_right(M).matrix_action_right(N) True """ @@ -235,7 +234,7 @@ def __getitem__(self, n): EXAMPLES:: - sage: Q = BinaryQF([2,3,4]) + sage: Q = BinaryQF([2, 3, 4]) sage: Q[0] 2 sage: Q[2] @@ -283,11 +282,11 @@ def __hash__(self): r""" TESTS:: - sage: hash(BinaryQF([2,2,3])) + sage: hash(BinaryQF([2, 2, 3])) 802 - sage: hash(BinaryQF([2,3,2])) + sage: hash(BinaryQF([2, 3, 2])) 562 - sage: hash(BinaryQF([3,2,2])) + sage: hash(BinaryQF([3, 2, 2])) 547 """ return hash(self._a) ^ (hash(self._b) << 4) ^ (hash(self._c) << 8) @@ -300,9 +299,9 @@ def __eq__(self, right): EXAMPLES:: - sage: P = BinaryQF([2,2,3]) - sage: Q = BinaryQF([2,2,3]) - sage: R = BinaryQF([1,2,3]) + sage: P = BinaryQF([2, 2, 3]) + sage: Q = BinaryQF([2, 2, 3]) + sage: R = BinaryQF([1, 2, 3]) sage: P == Q # indirect doctest True sage: P == R # indirect doctest @@ -331,9 +330,9 @@ def __ne__(self, right): EXAMPLES:: - sage: P = BinaryQF([2,2,3]) - sage: Q = BinaryQF([2,2,3]) - sage: R = BinaryQF([1,2,3]) + sage: P = BinaryQF([2, 2, 3]) + sage: Q = BinaryQF([2, 2, 3]) + sage: R = BinaryQF([1, 2, 3]) sage: P != Q # indirect doctest False sage: P != R # indirect doctest @@ -349,8 +348,8 @@ def __lt__(self, right): EXAMPLES:: - sage: P = BinaryQF([2,2,3]) - sage: Q = BinaryQF([1,2,3]) + sage: P = BinaryQF([2, 2, 3]) + sage: Q = BinaryQF([1, 2, 3]) sage: P < Q False sage: Q < P @@ -372,18 +371,18 @@ def __add__(self, Q): EXAMPLES:: - sage: P = BinaryQF([2,2,3]); P + sage: P = BinaryQF([2, 2, 3]); P 2*x^2 + 2*x*y + 3*y^2 - sage: Q = BinaryQF([-1,2,2]); Q + sage: Q = BinaryQF([-1, 2, 2]); Q -x^2 + 2*x*y + 2*y^2 sage: P + Q x^2 + 4*x*y + 5*y^2 - sage: P + Q == BinaryQF([1,4,5]) # indirect doctest + sage: P + Q == BinaryQF([1, 4, 5]) # indirect doctest True TESTS:: - sage: Q + P == BinaryQF([1,4,5]) # indirect doctest + sage: Q + P == BinaryQF([1, 4, 5]) # indirect doctest True """ return BinaryQF([self._a + Q._a, self._b + Q._b, self._c + Q._c]) @@ -398,20 +397,20 @@ def __sub__(self, Q): EXAMPLES:: - sage: P = BinaryQF([2,2,3]); P + sage: P = BinaryQF([2, 2, 3]); P 2*x^2 + 2*x*y + 3*y^2 - sage: Q = BinaryQF([-1,2,2]); Q + sage: Q = BinaryQF([-1, 2, 2]); Q -x^2 + 2*x*y + 2*y^2 sage: P - Q 3*x^2 + y^2 - sage: P - Q == BinaryQF([3,0,1]) # indirect doctest + sage: P - Q == BinaryQF([3, 0, 1]) # indirect doctest True TESTS:: - sage: Q - P == BinaryQF([3,0,1]) # indirect doctest + sage: Q - P == BinaryQF([3, 0, 1]) # indirect doctest False - sage: Q - P != BinaryQF([3,0,1]) # indirect doctest + sage: Q - P != BinaryQF([3, 0, 1]) # indirect doctest True """ return BinaryQF([self._a - Q._a, self._b - Q._b, self._c - Q._c]) @@ -422,13 +421,13 @@ def _repr_(self): EXAMPLES:: - sage: Q = BinaryQF([1,2,3]); Q # indirect doctest + sage: Q = BinaryQF([1, 2, 3]); Q # indirect doctest x^2 + 2*x*y + 3*y^2 - sage: Q = BinaryQF([-1,2,3]); Q + sage: Q = BinaryQF([-1, 2, 3]); Q -x^2 + 2*x*y + 3*y^2 - sage: Q = BinaryQF([0,0,0]); Q + sage: Q = BinaryQF([0, 0, 0]); Q 0 """ return repr(self.polynomial()) @@ -439,7 +438,7 @@ def _latex_(self): EXAMPLES:: - sage: f = BinaryQF((778,1115,400)); f + sage: f = BinaryQF((778, 1115, 400)); f 778*x^2 + 1115*x*y + 400*y^2 sage: latex(f) # indirect doctest 778 x^{2} + 1115 x y + 400 y^{2} @@ -452,14 +451,14 @@ def content(self): EXAMPLES:: - sage: Q=BinaryQF(22,14,10) + sage: Q = BinaryQF(22, 14, 10) sage: Q.content() 2 - sage: Q=BinaryQF(4,4,-15) + sage: Q = BinaryQF(4, 4, -15) sage: Q.content() 1 """ - return gcd([self._a,self._b,self._c]) + return gcd([self._a, self._b, self._c]) def polynomial(self): """ @@ -467,15 +466,15 @@ def polynomial(self): EXAMPLES:: - sage: Q = BinaryQF([1,2,3]) + sage: Q = BinaryQF([1, 2, 3]) sage: Q.polynomial() x^2 + 2*x*y + 3*y^2 - sage: Q = BinaryQF([-1,-2,3]) + sage: Q = BinaryQF([-1, -2, 3]) sage: Q.polynomial() -x^2 - 2*x*y + 3*y^2 - sage: Q = BinaryQF([0,0,0]) + sage: Q = BinaryQF([0, 0, 0]) sage: Q.polynomial() 0 @@ -496,7 +495,7 @@ def discriminant(self): EXAMPLES:: - sage: Q = BinaryQF([1,2,3]) + sage: Q = BinaryQF([1, 2, 3]) sage: Q.discriminant() -8 """ @@ -566,15 +565,15 @@ def is_primitive(self): EXAMPLES:: - sage: Q = BinaryQF([6,3,9]) + sage: Q = BinaryQF([6, 3, 9]) sage: Q.is_primitive() False - sage: Q = BinaryQF([1,1,1]) + sage: Q = BinaryQF([1, 1, 1]) sage: Q.is_primitive() True - sage: Q = BinaryQF([2,2,2]) + sage: Q = BinaryQF([2, 2, 2]) sage: Q.is_primitive() False @@ -599,7 +598,7 @@ def is_primitive(self): 4*x^2 + x*y + 13*y^2, 8*x^2 + 7*x*y + 8*y^2] """ - return gcd([self._a, self._b, self._c])==1 + return gcd([self._a, self._b, self._c]) == 1 @cached_method def is_zero(self): @@ -608,10 +607,10 @@ def is_zero(self): EXAMPLES:: - sage: Q=BinaryQF(195751, 37615, 1807) + sage: Q = BinaryQF(195751, 37615, 1807) sage: Q.is_zero() False - sage: Q=BinaryQF(0, 0, 0) + sage: Q = BinaryQF(0, 0, 0) sage: Q.is_zero() True """ @@ -625,15 +624,15 @@ def is_weakly_reduced(self): EXAMPLES:: - sage: Q = BinaryQF([1,2,3]) + sage: Q = BinaryQF([1, 2, 3]) sage: Q.is_weakly_reduced() False - sage: Q = BinaryQF([2,1,3]) + sage: Q = BinaryQF([2, 1, 3]) sage: Q.is_weakly_reduced() True - sage: Q = BinaryQF([1,-1,1]) + sage: Q = BinaryQF([1, -1, 1]) sage: Q.is_weakly_reduced() True """ @@ -688,7 +687,7 @@ def _reduce_indef(self, transformation=False): True """ if transformation: - U = Matrix(ZZ, 2, 2, [1,0,0,1]) + U = Matrix(ZZ, 2, 2, [1, 0, 0, 1]) d = self.discriminant().sqrt(prec=53) Q = self while not Q.is_reduced(): @@ -750,7 +749,7 @@ def reduced_form(self, transformation=False, algorithm="default"): EXAMPLES:: - sage: a = BinaryQF([33,11,5]) + sage: a = BinaryQF([33, 11, 5]) sage: a.is_reduced() False sage: b = a.reduced_form(); b @@ -758,7 +757,7 @@ def reduced_form(self, transformation=False, algorithm="default"): sage: b.is_reduced() True - sage: a = BinaryQF([15,0,15]) + sage: a = BinaryQF([15, 0, 15]) sage: a.is_reduced() True sage: b = a.reduced_form(); b @@ -895,31 +894,48 @@ def cycle(self, proper=False): """ Return the cycle of reduced forms to which ``self`` belongs. - This is Algorithm 6.1 of [BUVO2007]_. + This is based on Algorithm 6.1 of [BUVO2007]_. INPUT: - ``self`` -- reduced, indefinite form of non-square discriminant - ``proper`` -- boolean (default: ``False``); if ``True``, return the - proper cycle (not implemented) - - This is used to test for equivalence between indefinite forms. - The cycle of a form `f` consists of all reduced, equivalent forms `g` - such that the `a`-coefficients of `f` and `g` have the same - sign. The proper cycle consists of all equivalent forms, and - is either the same as, or twice the size of, the cycle. In - the latter case, the cycle has odd length. + proper cycle + + The proper cycle of a form `f` consists of all reduced forms that are + properly equivalent to `f`. This is useful when testing for proper + equivalence (or equivalence) between indefinite forms. + + The cycle of `f` is a technical tool that is used when computing the proper + cycle. Our definition of the cycle is slightly different from the one + in [BUVO2007]_. In our definition, the cycle consists of all reduced + forms `g`, such that the `a`-coefficient of `g` has the same sign as the + `a`-coefficient of `f`, and `g` can be obtained from `f` by performing a + change of variables, and then multiplying by the determinant of the + change-of-variables matrix. It is important to note that `g` might not be + equivalent to `f` (because of multiplying by the determinant). However, + either 'g' or '-g' must be equivalent to `f`. Also note that the cycle + does contain `f`. (Under the definition in [BUVO2007]_, the cycle might + not contain `f`, because all forms in the cycle are required to have + positive `a`-coefficient, even if the `a`-coefficient of `f` is negative.) EXAMPLES:: - sage: Q = BinaryQF(14,17,-2) + sage: Q = BinaryQF(14, 17, -2) sage: Q.cycle() [14*x^2 + 17*x*y - 2*y^2, 2*x^2 + 19*x*y - 5*y^2, 5*x^2 + 11*x*y - 14*y^2] + sage: Q.cycle(proper=True) + [14*x^2 + 17*x*y - 2*y^2, + -2*x^2 + 19*x*y + 5*y^2, + 5*x^2 + 11*x*y - 14*y^2, + -14*x^2 + 17*x*y + 2*y^2, + 2*x^2 + 19*x*y - 5*y^2, + -5*x^2 + 11*x*y + 14*y^2] - sage: Q = BinaryQF(1,8,-3) + sage: Q = BinaryQF(1, 8, -3) sage: Q.cycle() [x^2 + 8*x*y - 3*y^2, 3*x^2 + 4*x*y - 5*y^2, @@ -927,8 +943,15 @@ def cycle(self, proper=False): 2*x^2 + 6*x*y - 5*y^2, 5*x^2 + 4*x*y - 3*y^2, 3*x^2 + 8*x*y - y^2] + sage: Q.cycle(proper=True) + [x^2 + 8*x*y - 3*y^2, + -3*x^2 + 4*x*y + 5*y^2, + 5*x^2 + 6*x*y - 2*y^2, + -2*x^2 + 6*x*y + 5*y^2, + 5*x^2 + 4*x*y - 3*y^2, + -3*x^2 + 8*x*y + y^2] - sage: Q=BinaryQF(1,7,-6) + sage: Q = BinaryQF(1, 7, -6) sage: Q.cycle() [x^2 + 7*x*y - 6*y^2, 6*x^2 + 5*x*y - 2*y^2, @@ -939,6 +962,83 @@ def cycle(self, proper=False): 3*x^2 + 7*x*y - 2*y^2, 2*x^2 + 5*x*y - 6*y^2, 6*x^2 + 7*x*y - y^2] + + TESTS: + + Check an example in :trac:`28989`:: + + sage: Q = BinaryQF(1, 1, -1) + sage: Q.cycle(proper=True) + [x^2 + x*y - y^2, -x^2 + x*y + y^2] + + This is Example 6.10.6 of [BUVO2007]_:: + + sage: Q = BinaryQF(1, 7, -6) + sage: Q.cycle() + [x^2 + 7*x*y - 6*y^2, + 6*x^2 + 5*x*y - 2*y^2, + 2*x^2 + 7*x*y - 3*y^2, + 3*x^2 + 5*x*y - 4*y^2, + 4*x^2 + 3*x*y - 4*y^2, + 4*x^2 + 5*x*y - 3*y^2, + 3*x^2 + 7*x*y - 2*y^2, + 2*x^2 + 5*x*y - 6*y^2, + 6*x^2 + 7*x*y - y^2] + sage: Q.cycle(proper=True) + [x^2 + 7*x*y - 6*y^2, + -6*x^2 + 5*x*y + 2*y^2, + 2*x^2 + 7*x*y - 3*y^2, + -3*x^2 + 5*x*y + 4*y^2, + 4*x^2 + 3*x*y - 4*y^2, + -4*x^2 + 5*x*y + 3*y^2, + 3*x^2 + 7*x*y - 2*y^2, + -2*x^2 + 5*x*y + 6*y^2, + 6*x^2 + 7*x*y - y^2, + -x^2 + 7*x*y + 6*y^2, + 6*x^2 + 5*x*y - 2*y^2, + -2*x^2 + 7*x*y + 3*y^2, + 3*x^2 + 5*x*y - 4*y^2, + -4*x^2 + 3*x*y + 4*y^2, + 4*x^2 + 5*x*y - 3*y^2, + -3*x^2 + 7*x*y + 2*y^2, + 2*x^2 + 5*x*y - 6*y^2, + -6*x^2 + 7*x*y + y^2] + + This is Example 6.10.7 of [BUVO2007]_:: + + sage: Q = BinaryQF(1, 8, -3) + sage: Q.cycle() + [x^2 + 8*x*y - 3*y^2, + 3*x^2 + 4*x*y - 5*y^2, + 5*x^2 + 6*x*y - 2*y^2, + 2*x^2 + 6*x*y - 5*y^2, + 5*x^2 + 4*x*y - 3*y^2, + 3*x^2 + 8*x*y - y^2] + sage: Q.cycle(proper=True) + [x^2 + 8*x*y - 3*y^2, + -3*x^2 + 4*x*y + 5*y^2, + 5*x^2 + 6*x*y - 2*y^2, + -2*x^2 + 6*x*y + 5*y^2, + 5*x^2 + 4*x*y - 3*y^2, + -3*x^2 + 8*x*y + y^2] + sage: Q.cycle(proper=True) # should be the same as the previous one + [x^2 + 8*x*y - 3*y^2, + -3*x^2 + 4*x*y + 5*y^2, + 5*x^2 + 6*x*y - 2*y^2, + -2*x^2 + 6*x*y + 5*y^2, + 5*x^2 + 4*x*y - 3*y^2, + -3*x^2 + 8*x*y + y^2] + + Try an example where a is negative:: + + sage: Q = BinaryQF(-1, 8, 3) + sage: Q.cycle(proper=True) + [-x^2 + 8*x*y + 3*y^2, + 3*x^2 + 4*x*y - 5*y^2, + -5*x^2 + 6*x*y + 2*y^2, + 2*x^2 + 6*x*y - 5*y^2, + -5*x^2 + 4*x*y + 3*y^2, + 3*x^2 + 8*x*y - y^2] """ if not (self.is_indef() and self.is_reduced()): raise ValueError("%s must be indefinite and reduced" % self) @@ -948,11 +1048,12 @@ def cycle(self, proper=False): 'implemented for non-square discriminants') if proper: # Prop 6.10.5 in Buchmann Vollmer - C = self.cycle(proper=False) + C = list(self.cycle(proper=False)) # make a copy so we can modify it if len(C) % 2: - return C - else: - return C[:1] + [q._Tau() for q in C[1:]] + C += C + for i in range(len(C)//2): + C[2*i+1] = C[2*i+1]._Tau() + return C if not hasattr(self, '_cycle_list'): C = [self] Q1 = self._RhoTau() @@ -969,10 +1070,10 @@ def is_positive_definite(self): EXAMPLES:: - sage: Q=BinaryQF(195751,37615,1807) + sage: Q = BinaryQF(195751, 37615, 1807) sage: Q.is_positive_definite() True - sage: Q=BinaryQF(195751,1212121,-1876411) + sage: Q = BinaryQF(195751, 1212121, -1876411) sage: Q.is_positive_definite() False """ @@ -987,7 +1088,7 @@ def is_negative_definite(self): EXAMPLES:: - sage: Q=BinaryQF(-1,3,-5) + sage: Q = BinaryQF(-1, 3, -5) sage: Q.is_positive_definite() False sage: Q.is_negative_definite() @@ -1003,7 +1104,7 @@ def is_indefinite(self): EXAMPLES:: - sage: Q=BinaryQF(1,3,-5) + sage: Q = BinaryQF(1, 3, -5) sage: Q.is_indef() True """ @@ -1017,10 +1118,10 @@ def is_singular(self): EXAMPLES:: - sage: Q=BinaryQF(1,3,-5) + sage: Q = BinaryQF(1, 3, -5) sage: Q.is_singular() False - sage: Q=BinaryQF(1,2,1) + sage: Q = BinaryQF(1, 2, 1) sage: Q.is_singular() True """ @@ -1032,10 +1133,10 @@ def is_nonsingular(self): EXAMPLES:: - sage: Q=BinaryQF(1,3,-5) + sage: Q = BinaryQF(1, 3, -5) sage: Q.is_nonsingular() True - sage: Q=BinaryQF(1,2,1) + sage: Q = BinaryQF(1, 2, 1) sage: Q.is_nonsingular() False """ @@ -1053,37 +1154,39 @@ def is_equivalent(self, other, proper=True): EXAMPLES:: - sage: Q3 = BinaryQF(4,4,15) - sage: Q2 = BinaryQF(4,-4,15) + sage: Q3 = BinaryQF(4, 4, 15) + sage: Q2 = BinaryQF(4, -4, 15) sage: Q2.is_equivalent(Q3) True - sage: a = BinaryQF([33,11,5]) + sage: a = BinaryQF([33, 11, 5]) sage: b = a.reduced_form(); b 5*x^2 - x*y + 27*y^2 sage: a.is_equivalent(b) True - sage: a.is_equivalent(BinaryQF((3,4,5))) + sage: a.is_equivalent(BinaryQF((3, 4, 5))) False Some indefinite examples:: - sage: Q1 = BinaryQF(3, 4, -2) - sage: Q2 = BinaryQF(-2, 4, 3) - sage: Q1.is_equivalent(Q2) + sage: Q1 = BinaryQF(9, 8, -7) + sage: Q2 = BinaryQF(9, -8, -7) + sage: Q1.is_equivalent(Q2, proper=True) False - sage: Q1.is_equivalent(Q2,proper=False) + sage: Q1.is_equivalent(Q2, proper=False) True TESTS: We check that :trac:`25888` is fixed:: - sage: Q1 = BinaryQF(3, 4, -2) + sage: Q1 = BinaryQF(3, 4, -2) sage: Q2 = BinaryQF(-2, 4, 3) - sage: Q1.is_equivalent(Q2, proper=False) + sage: Q1.is_equivalent(Q2) == Q2.is_equivalent(Q1) + True + sage: Q1.is_equivalent(Q2, proper=False) == Q2.is_equivalent(Q1, proper=False) + True + sage: Q1.is_equivalent(Q2, proper=True) True - sage: Q2.is_equivalent(Q1, proper=True) - False A test for rational forms:: @@ -1091,6 +1194,12 @@ def is_equivalent(self, other, proper=True): sage: Q2 = BinaryQF(2, 4, 0) sage: Q1.is_equivalent(Q2, proper=False) True + + Test another part of :trac:`28989`:: + + sage: Q1, Q2 = BinaryQF(1, 1, -1), BinaryQF(-1, 1, 1) + sage: Q1.is_equivalent(Q2, proper=True) + True """ if type(other) != type(self): raise TypeError("%s is not a BinaryQF" % other) @@ -1114,7 +1223,7 @@ def is_equivalent(self, other, proper=True): if proper: return (a-ao) % (2*b) == 0 else: - g = gcd(a,b) + g = gcd(a, b) return (a*ao - g**2) % (2*b*g) == 0 proper_cycle = otherred.cycle(proper=True) @@ -1124,11 +1233,12 @@ def is_equivalent(self, other, proper=True): return is_prop # note that our definition of improper equivalence # differs from that of Buchmann and Vollmer - # their action is det f * q(f(x,y)) - # ours is q(f(x,y)) + # their action is det f * q(f(x, y)) + # ours is q(f(x, y)) # an improper equivalence in our convention - selfred = BinaryQF(self._c, self._b, self._a) + selfred = BinaryQF(selfred._c, selfred._b, selfred._a) + assert selfred.is_reduced() return selfred in proper_cycle @@ -1169,19 +1279,19 @@ def is_reduced(self): EXAMPLES:: - sage: Q = BinaryQF([1,2,3]) + sage: Q = BinaryQF([1, 2, 3]) sage: Q.is_reduced() False - sage: Q = BinaryQF([2,1,3]) + sage: Q = BinaryQF([2, 1, 3]) sage: Q.is_reduced() True - sage: Q = BinaryQF([1,-1,1]) + sage: Q = BinaryQF([1, -1, 1]) sage: Q.is_reduced() False - sage: Q = BinaryQF([1,1,1]) + sage: Q = BinaryQF([1, 1, 1]) sage: Q.is_reduced() True @@ -1226,7 +1336,7 @@ def complex_point(self): EXAMPLES:: - sage: Q = BinaryQF([1,0,1]) + sage: Q = BinaryQF([1, 0, 1]) sage: Q.complex_point() 1.00000000000000*I """ @@ -1309,7 +1419,7 @@ def small_prime_value(self, Bmax=1000): from sage.arith.srange import xsrange B = 10 while True: - llist = list(Set([self(x,y) for x in xsrange(-B,B) for y in xsrange(B)])) + llist = list(Set([self(x, y) for x in xsrange(-B, B) for y in xsrange(B)])) llist = sorted([l for l in llist if l.is_prime()]) if llist: return llist[0] @@ -1319,7 +1429,7 @@ def small_prime_value(self, Bmax=1000): def solve_integer(self, n): r""" - Solve `Q(x,y) = n` in integers `x` and `y` where `Q` is this + Solve `Q(x, y) = n` in integers `x` and `y` where `Q` is this quadratic form. INPUT: @@ -1328,12 +1438,12 @@ def solve_integer(self, n): OUTPUT: - A tuple `(x,y)` of integers satisfying `Q(x,y) = n` or ``None`` + A tuple `(x, y)` of integers satisfying `Q(x, y) = n` or ``None`` if no such `x` and `y` exist. EXAMPLES:: - sage: Qs = BinaryQF_reduced_representatives(-23,primitive_only=True) + sage: Qs = BinaryQF_reduced_representatives(-23, primitive_only=True) sage: Qs [x^2 + x*y + 6*y^2, 2*x^2 - x*y + 3*y^2, 2*x^2 + x*y + 3*y^2] sage: [Q.solve_integer(3) for Q in Qs] @@ -1356,7 +1466,7 @@ def solve_integer(self, n): for z in z2.sqrt(extend=False, all=True): if a2.divides(z-b*y): x = (z-b*y)//a2 - return (x,y) + return (x, y) return None @@ -1407,7 +1517,7 @@ def BinaryQF_reduced_representatives(D, primitive_only=False, proper=True): 2 sage: QuadraticField(-13*4, 'a').class_number() 2 - sage: p=next_prime(2^20); p + sage: p = next_prime(2^20); p 1048583 sage: len(BinaryQF_reduced_representatives(-p)) 689 @@ -1435,22 +1545,31 @@ def BinaryQF_reduced_representatives(D, primitive_only=False, proper=True): TESTS:: sage: BinaryQF_reduced_representatives(73) - [-4*x^2 + 3*x*y + 4*y^2, - 4*x^2 + 3*x*y - 4*y^2] + [4*x^2 + 3*x*y - 4*y^2] sage: BinaryQF_reduced_representatives(76, primitive_only=True) [-3*x^2 + 4*x*y + 5*y^2, 3*x^2 + 4*x*y - 5*y^2] + sage: BinaryQF_reduced_representatives(136) + [-5*x^2 + 4*x*y + 6*y^2, + -2*x^2 + 8*x*y + 9*y^2, + 2*x^2 + 8*x*y - 9*y^2, + 5*x^2 + 4*x*y - 6*y^2] + sage: BinaryQF_reduced_representatives(136, proper=False) + [-2*x^2 + 8*x*y + 9*y^2, 2*x^2 + 8*x*y - 9*y^2, 5*x^2 + 4*x*y - 6*y^2] Check that the primitive_only keyword does something:: - sage: BinaryQF_reduced_representatives(4*5, primitive_only=True) - [-x^2 + 4*x*y + y^2, - x^2 + 4*x*y - y^2] - sage: BinaryQF_reduced_representatives(4*5, primitive_only=False) - [-2*x^2 + 2*x*y + 2*y^2, - -x^2 + 4*x*y + y^2, - x^2 + 4*x*y - y^2, - 2*x^2 + 2*x*y - 2*y^2] + sage: BinaryQF_reduced_representatives(148, proper=False, primitive_only=False) + [x^2 + 12*x*y - y^2, 4*x^2 + 6*x*y - 7*y^2, 6*x^2 + 2*x*y - 6*y^2] + sage: BinaryQF_reduced_representatives(148, proper=False, primitive_only=True) + [x^2 + 12*x*y - y^2, 4*x^2 + 6*x*y - 7*y^2] + sage: BinaryQF_reduced_representatives(148, proper=True, primitive_only=True) + [-7*x^2 + 6*x*y + 4*y^2, x^2 + 12*x*y - y^2, 4*x^2 + 6*x*y - 7*y^2] + sage: BinaryQF_reduced_representatives(148, proper=True, primitive_only=False) + [-7*x^2 + 6*x*y + 4*y^2, + x^2 + 12*x*y - y^2, + 4*x^2 + 6*x*y - 7*y^2, + 6*x^2 + 2*x*y - 6*y^2] """ D = ZZ(D) @@ -1488,7 +1607,7 @@ def BinaryQF_reduced_representatives(D, primitive_only=False, proper=True): continue c = -A/a if c in ZZ: - if (not primitive_only) or gcd([a, b, c])==1: + if (not primitive_only) or gcd([a, b, c]) == 1: Q = BinaryQF(a, b, c) Q1 = BinaryQF(-a, b, -c) form_list.append(Q) @@ -1501,19 +1620,19 @@ def BinaryQF_reduced_representatives(D, primitive_only=False, proper=True): else: # Definite # Only iterate over positive a and over b of the same # parity as D such that 4a^2 + D <= b^2 <= a^2 - for a in xsrange(1,1+((-D)//3).isqrt()): + for a in xsrange(1, 1+((-D)//3).isqrt()): a4 = 4*a s = D + a*a4 w = 1+(s-1).isqrt() if s > 0 else 0 if w%2 != D%2: w += 1 - for b in xsrange(w,a+1,2): + for b in xsrange(w, a+1, 2): t = b*b-D if t % a4 == 0: c = t // a4 - if (not primitive_only) or gcd([a,b,c])==1: + if (not primitive_only) or gcd([a, b, c]) == 1: if b>0 and a>b and c>a: - form_list.append(BinaryQF([a,-b,c])) - form_list.append(BinaryQF([a,b,c])) + form_list.append(BinaryQF([a, -b, c])) + form_list.append(BinaryQF([a, b, c])) if not proper or D > 0: # TODO: # instead of filtering, enumerate only improper classes to start with diff --git a/src/sage/repl/load.py b/src/sage/repl/load.py index 73b85444de1..936af57aa0e 100644 --- a/src/sage/repl/load.py +++ b/src/sage/repl/load.py @@ -154,7 +154,7 @@ def load(filename, globals, attach=False): We can load files using secure http (https):: - sage: sage.repl.load.load('https://raw.githubusercontent.com/sagemath/sage-patchbot/2.8.4/sage_patchbot/util.py', globals()) # optional - internet + sage: sage.repl.load.load('https://raw.githubusercontent.com/sagemath/sage-patchbot/3.0.0/sage_patchbot/util.py', globals()) # optional - internet We attach a file:: diff --git a/src/sage/repl/rich_output/backend_sagenb.py b/src/sage/repl/rich_output/backend_sagenb.py index dc3ed1519a1..8e71160a10f 100644 --- a/src/sage/repl/rich_output/backend_sagenb.py +++ b/src/sage/repl/rich_output/backend_sagenb.py @@ -61,7 +61,7 @@ # 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/ +# https://www.gnu.org/licenses/ #***************************************************************************** import io @@ -70,7 +70,6 @@ from sage.misc.cachefunc import cached_method from sage.misc.html import html from sage.misc.temporary_file import graphics_filename -from sage.doctest import DOCTEST_MODE from sage.repl.rich_output.backend_base import BackendBase from sage.repl.rich_output.output_catalog import * from sage.repl.rich_output.output_video import OutputVideoBase diff --git a/src/sage/repl/rich_output/pretty_print.py b/src/sage/repl/rich_output/pretty_print.py index f751fd3211e..7064eeba26a 100644 --- a/src/sage/repl/rich_output/pretty_print.py +++ b/src/sage/repl/rich_output/pretty_print.py @@ -20,20 +20,14 @@ sage: pretty_print(plot(sin)) """ - -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2015 Volker Braun # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** - - -import types -import collections - +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.structure.sage_object import SageObject from sage.repl.rich_output import get_display_manager diff --git a/src/sage/rings/all.py b/src/sage/rings/all.py index 286e61cafa0..b7ac48d3c5e 100644 --- a/src/sage/rings/all.py +++ b/src/sage/rings/all.py @@ -133,6 +133,10 @@ # Tate algebras from .tate_algebra import TateAlgebra +# Puiseux series ring +from .puiseux_series_ring import PuiseuxSeriesRing +from .puiseux_series_ring_element import PuiseuxSeries + # Pseudo-ring of PARI objects. from .pari_ring import PariRing, Pari diff --git a/src/sage/rings/big_oh.py b/src/sage/rings/big_oh.py index f4548a5bfbd..330bd18b9e6 100644 --- a/src/sage/rings/big_oh.py +++ b/src/sage/rings/big_oh.py @@ -13,6 +13,7 @@ import sage.arith.all as arith from . import laurent_series_ring_element +from sage.rings.puiseux_series_ring_element import PuiseuxSeries import sage.rings.padics.factory as padics_factory import sage.rings.padics.padic_generic_element as padic_generic_element from . import power_series_ring_element @@ -87,6 +88,15 @@ def O(*x, **kwds): sage: O(n) O(n) + Application with Puiseux series:: + + sage: P. = PuiseuxSeriesRing(ZZ) + sage: y^(1/5) + O(y^(1/3)) + y^(1/5) + O(y^(1/3)) + sage: y^(1/3) + O(y^(1/5)) + O(y^(1/5)) + + TESTS:: sage: var('x, y') @@ -133,6 +143,9 @@ def O(*x, **kwds): return laurent_series_ring_element.LaurentSeries(x.parent(), 0).\ add_bigoh(x.valuation(), **kwds) + elif isinstance(x, PuiseuxSeries): + return x.add_bigoh(x.valuation(), **kwds) + elif isinstance(x, integer_types + (integer.Integer, rational.Rational)): # p-adic number if x <= 0: diff --git a/src/sage/rings/function_field/order.py b/src/sage/rings/function_field/order.py index 9cb66141d35..24ad0b2defa 100644 --- a/src/sage/rings/function_field/order.py +++ b/src/sage/rings/function_field/order.py @@ -168,7 +168,7 @@ def __init__(self, field, ideal_class=FunctionFieldIdeal, category=None): self._ideal_class = ideal_class # element class for parent ideal monoid self._field = field - def is_field(self): + def is_field(self, proof=True): """ Return ``False`` since orders are never fields. diff --git a/src/sage/rings/laurent_series_ring_element.pyx b/src/sage/rings/laurent_series_ring_element.pyx index bb6f15703c0..d6a1e7e4f62 100644 --- a/src/sage/rings/laurent_series_ring_element.pyx +++ b/src/sage/rings/laurent_series_ring_element.pyx @@ -363,6 +363,66 @@ cdef class LaurentSeries(AlgebraElement): s += " + %s"%bigoh return s[1:] + def verschiebung(self, n): + r""" + Return the ``n``-th Verschiebung of ``self``. + + If `f = \sum a_m x^m` then this function returns `\sum a_m x^{mn}`. + + EXAMPLES:: + + sage: R. = LaurentSeriesRing(QQ) + sage: f = -1/x + 1 + 2*x^2 + 5*x^5 + sage: f.V(2) + -x^-2 + 1 + 2*x^4 + 5*x^10 + sage: f.V(-1) + 5*x^-5 + 2*x^-2 + 1 - x + sage: h = f.add_bigoh(7) + sage: h.V(2) + -x^-2 + 1 + 2*x^4 + 5*x^10 + O(x^14) + sage: h.V(-2) + Traceback (most recent call last): + ... + ValueError: For finite precision only positive arguments allowed + + TESTS:: + + sage: R. = LaurentSeriesRing(QQ) + sage: f = x + sage: f.V(3) + x^3 + sage: f.V(-3) + x^-3 + sage: g = 2*x^(-1) + 3 + 5*x + sage: g.V(-1) + 5*x^-1 + 3 + 2*x + """ + if n == 0: + raise ValueError('n must be non zero') + + if n < 0: + if not self.prec() is infinity: + raise ValueError('For finite precision only positive arguments allowed') + + exponents = [e * n for e in self.exponents()] + u = min(exponents) + exponents = [e - u for e in exponents] + coefficients = self.coefficients() + zero = self.base_ring().zero() + w = [zero] * (max(exponents) + 1) + for i in range(len(exponents)): + e = exponents[i] + c = coefficients[i] + w[e] = c + l = LaurentSeries(self._parent, w, u) + else: + __u = self.__u.V(n) + __n = self.__n * n + l = LaurentSeries(self._parent, __u, __n) + return l + + V = verschiebung + def _latex_(self): r""" EXAMPLES:: @@ -1307,7 +1367,7 @@ cdef class LaurentSeries(AlgebraElement): return self.prec() - self.valuation() def __copy__(self): - return type(self)(self._parent, self.__u.copy(), self.__n) + return type(self)(self._parent, self.__u.__copy__(), self.__n) def reverse(self, precision=None): """ diff --git a/src/sage/rings/morphism.pxd b/src/sage/rings/morphism.pxd index 62f1ee1f00e..c5587a99378 100644 --- a/src/sage/rings/morphism.pxd +++ b/src/sage/rings/morphism.pxd @@ -22,6 +22,9 @@ cdef class RingHomomorphism_im_gens(RingHomomorphism): cdef class RingHomomorphism_from_base(RingHomomorphism): cdef _underlying +cdef class RingHomomorphism_from_fraction_field(RingHomomorphism): + cdef _morphism + cdef class RingHomomorphism_cover(RingHomomorphism): pass diff --git a/src/sage/rings/morphism.pyx b/src/sage/rings/morphism.pyx index 66cec959e9d..c3c69164507 100644 --- a/src/sage/rings/morphism.pyx +++ b/src/sage/rings/morphism.pyx @@ -1665,6 +1665,104 @@ cdef class RingHomomorphism_from_base(RingHomomorphism): raise TypeError("invalid argument %s" % repr(x)) +cdef class RingHomomorphism_from_fraction_field(RingHomomorphism): + r""" + Morphisms between fraction fields. + + TESTS:: + + sage: S. = QQ[] + sage: f = S.hom([x^2]) + sage: g = f.extend_to_fraction_field() + sage: type(g) + + """ + def __init__(self, parent, morphism): + r""" + Initialize this morphism. + + TESTS:: + + sage: A. = ZZ.extension(x^2 - 2) + sage: f = A.coerce_map_from(ZZ) + sage: g = f.extend_to_fraction_field() # indirect doctest + sage: g + Ring morphism: + From: Rational Field + To: Number Field in a with defining polynomial x^2 - 2 + """ + RingHomomorphism.__init__(self, parent) + self._morphism = morphism + + def _repr_defn(self): + r""" + Return a string definition of this morphism. + + EXAMPLES:: + + sage: S. = QQ[] + sage: f = S.hom([x^2]).extend_to_fraction_field() + sage: f + Ring endomorphism of Fraction Field of Univariate Polynomial Ring in x over Rational Field + Defn: x |--> x^2 + sage: f._repr_defn() + 'x |--> x^2' + """ + return self._morphism._repr_defn() + + cpdef Element _call_(self, x): + r""" + Return the value of this morphism at ``x``. + + INPUT: + + - ``x`` -- an element in the domain of this morphism + + EXAMPLES:: + + sage: S. = QQ[] + sage: f = S.hom([x+1]).extend_to_fraction_field() + sage: f(1/x) + 1/(x + 1) + sage: f(1/(x-1)) + 1/x + """ + return self._morphism(x.numerator()) / self._morphism(x.denominator()) + + cdef _update_slots(self, dict _slots): + """ + Helper function for copying and pickling. + + TESTS:: + + sage: S. = QQ[] + sage: f = S.hom([x+1]).extend_to_fraction_field() + + sage: g = copy(f) # indirect doctest + sage: f == g + True + sage: f is g + False + """ + self._morphism = _slots['_morphism'] + RingHomomorphism._update_slots(self, _slots) + + cdef dict _extra_slots(self): + """ + Helper function for copying and pickling. + + TESTS:: + + sage: S. = QQ[] + sage: f = S.hom([x+1]).extend_to_fraction_field() + sage: loads(dumps(f)) == f + True + """ + slots = RingHomomorphism._extra_slots(self) + slots['_morphism'] = self._morphism + return slots + + cdef class RingHomomorphism_cover(RingHomomorphism): r""" A homomorphism induced by quotienting a ring out by an ideal. diff --git a/src/sage/rings/number_field/galois_group.py b/src/sage/rings/number_field/galois_group.py index cdc86042b05..0c857fa3f49 100644 --- a/src/sage/rings/number_field/galois_group.py +++ b/src/sage/rings/number_field/galois_group.py @@ -232,13 +232,22 @@ def __init__(self, number_field, names=None): # PARI computes all the elements of self anyway, so we might as well store them self._elts = sorted([self(x, check=False) for x in g[5]]) - def __call__(self, x, check=True): - r""" Create an element of self from x. Here x had better be one of: - -- the integer 1, denoting the identity of G - -- an element of G - -- a permutation of the right length which defines an element of G, or anything that - coerces into a permutation of the right length - -- an abstract automorphism of the underlying number field. + def _element_constructor_(self, x, check=True): + """ + Create an element of ``self`` from ``x``. + + INPUT: + + - ``x`` -- one of the following (`G` is this Galois group): + + - the integer 1, denoting the identity of `G`; + + - an element of `G`; + + - a permutation of the right length that defines an element + of `G`, or anything that coerces into such a permutation; + + - an automorphism of the underlying number field. EXAMPLES:: @@ -256,7 +265,6 @@ def __call__(self, x, check=True): if x == 1: return self.identity() - from sage.rings.number_field.morphism import NumberFieldHomomorphism_im_gens if isinstance(x, NumberFieldHomomorphism_im_gens) and x.parent() == self.number_field().Hom(self.number_field()): l = [g for g in self if g.as_hom() == x] if len(l) != 1: diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 8268f5f4a54..e39c347747e 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -2959,6 +2959,7 @@ def specified_complex_embedding(self): if embedding is not None and embedding.codomain()._is_numerical(): return embedding + @cached_method def gen_embedding(self): """ If an embedding has been specified, return the image of the diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx index efe147b5dc6..59bab07db08 100644 --- a/src/sage/rings/number_field/number_field_element.pyx +++ b/src/sage/rings/number_field/number_field_element.pyx @@ -3042,7 +3042,8 @@ cdef class NumberFieldElement(FieldElement): sage: g is f.polynomial() False """ - return QQ[var](self._coefficients()) + from sage.rings.polynomial.polynomial_ring_constructor import _single_variate as Pol + return Pol(QQ, var)(self._coefficients()) def __hash__(self): """ diff --git a/src/sage/rings/padics/eisenstein_extension_generic.py b/src/sage/rings/padics/eisenstein_extension_generic.py index e5701ba2f04..eab90b5359e 100644 --- a/src/sage/rings/padics/eisenstein_extension_generic.py +++ b/src/sage/rings/padics/eisenstein_extension_generic.py @@ -9,7 +9,7 @@ """ from __future__ import absolute_import -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2008 David Roe # William Stein # @@ -17,14 +17,12 @@ # 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/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from .padic_extension_generic import pAdicExtensionGeneric -from .misc import precprint from sage.rings.infinity import infinity -from sage.misc.latex import latex -from sage.rings.integer import Integer + class EisensteinExtensionGeneric(pAdicExtensionGeneric): def __init__(self, poly, prec, print_mode, names, element_class): diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index caaf1650e9b..fd03639df00 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -185,12 +185,12 @@ from sage.libs.singular.decl cimport (ring, poly, ideal, intvec, number, # singular functions from sage.libs.singular.decl cimport ( errorreported, - n_IsUnit, n_Invers, + n_IsUnit, n_Invers, n_GetChar, p_ISet, rChangeCurrRing, p_Copy, p_Init, p_SetCoeff, p_Setm, p_SetExp, p_Add_q, p_NSet, p_GetCoeff, p_Delete, p_GetExp, pNext, rRingVar, omAlloc0, omStrDup, omFree, p_Divide, p_SetCoeff0, n_Init, p_DivisibleBy, pLcm, p_LmDivisibleBy, pMDivide, p_MDivide, p_IsConstant, p_ExpVectorEqual, p_String, p_LmInit, n_Copy, - p_IsUnit, p_Series, p_Head, idInit, fast_map_common_subexp, id_Delete, + p_IsUnit, p_IsOne, p_Series, p_Head, idInit, fast_map_common_subexp, id_Delete, p_IsHomogeneous, p_Homogen, p_Totaldegree,pLDeg1_Totaldegree, singclap_pdivide, singclap_factorize, idLift, IDELEMS, On, Off, SW_USE_CHINREM_GCD, SW_USE_EZGCD, p_LmIsConstant, pTakeOutComp1, singclap_gcd, pp_Mult_qq, p_GetMaxExp, @@ -4075,23 +4075,23 @@ cdef class MPolynomial_libsingular(MPolynomial): True """ cdef MPolynomialRing_libsingular parent = self._parent + cdef MPolynomial_libsingular _right = right cdef ring *r = self._parent_ring cdef poly *quo cdef poly *temp cdef poly *p - if right.is_zero(): + if _right._poly == NULL: raise ZeroDivisionError - elif right.is_one(): + elif p_IsOne(_right._poly, r): return self - if self._parent._base.is_finite() and self._parent._base.characteristic() > 1<<29: + if n_GetChar(r) > 1<<29: raise NotImplementedError("Division of multivariate polynomials over prime fields with characteristic > 2^29 is not implemented.") - _right = right - if r.cf.type != n_unknown: - if _right.is_monomial(): + if (singular_polynomial_length_bounded(_right._poly, 2) == 1 + and r.cf.cfIsOne(p_GetCoeff(_right._poly, r), r.cf)): p = self._poly quo = p_ISet(0,r) while p: @@ -4402,9 +4402,8 @@ cdef class MPolynomial_libsingular(MPolynomial): except Exception: raise NotImplementedError("Factorization of multivariate polynomials over %s is not implemented."%self._parent._base) - if self._parent._base.is_finite(): - if self._parent._base.characteristic() > 1<<29: - raise NotImplementedError("Factorization of multivariate polynomials over prime fields with characteristic > 2^29 is not implemented.") + if n_GetChar(_ring) > 1<<29: + raise NotImplementedError("Factorization of multivariate polynomials over prime fields with characteristic > 2^29 is not implemented.") # I make a temporary copy of the poly in self because singclap_factorize appears to modify it's parameter ptemp = p_Copy(self._poly,_ring) @@ -4761,42 +4760,39 @@ cdef class MPolynomial_libsingular(MPolynomial): sage: (21^3*p^2*q).gcd(35^2*p*q^2) == -49*p*q True """ - cdef MPolynomial_libsingular _right cdef poly *_res cdef ring *_ring = self._parent_ring + cdef MPolynomial_libsingular _right = right - if algorithm is None: - algorithm = "modular" - - if algorithm == "ezgcd": - Off(SW_USE_CHINREM_GCD) - On(SW_USE_EZGCD) - elif algorithm == "modular": + if algorithm is None or algorithm == "modular": On(SW_USE_CHINREM_GCD) Off(SW_USE_EZGCD) + elif algorithm == "ezgcd": + Off(SW_USE_CHINREM_GCD) + On(SW_USE_EZGCD) else: raise TypeError("algorithm %s not supported" % algorithm) + if _right._poly == NULL: + return self + elif self._poly == NULL: + return right + elif p_IsOne(self._poly, _ring): + return self + elif p_IsOne(_right._poly, _ring): + return right + if _ring.cf.type != n_unknown: if _ring.cf.type == n_Znm or _ring.cf.type == n_Zn or _ring.cf.type == n_Z2m : raise NotImplementedError("GCD over rings not implemented.") - if self._parent._base.is_finite() and self._parent._base.characteristic() > 1<<29: + if n_GetChar(_ring) > 1<<29: raise NotImplementedError("GCD of multivariate polynomials over prime fields with characteristic > 2^29 is not implemented.") - if(_ring != currRing): rChangeCurrRing(_ring) - - if not (isinstance(right, MPolynomial_libsingular) \ - and (right)._parent is self._parent): - _right = self._parent._coerce_c(right) - else: - _right = right - cdef int count = singular_polynomial_length_bounded(self._poly,20) \ + singular_polynomial_length_bounded(_right._poly,20) if count >= 20: sig_on() - if _ring!=currRing: rChangeCurrRing(_ring) # singclap_gcd _res = singclap_gcd(p_Copy(self._poly, _ring), p_Copy(_right._poly, _ring), _ring ) if count >= 20: sig_off() @@ -4863,7 +4859,7 @@ cdef class MPolynomial_libsingular(MPolynomial): else: _g = g - if self._parent._base.is_finite() and self._parent._base.characteristic() > 1<<29: + if n_GetChar(_ring) > 1<<29: raise NotImplementedError("LCM of multivariate polynomials over prime fields with characteristic > 2^29 is not implemented.") cdef int count = singular_polynomial_length_bounded(self._poly,20) \ @@ -4943,7 +4939,7 @@ cdef class MPolynomial_libsingular(MPolynomial): py_rem = self - right*py_quo return py_quo, py_rem - if self._parent._base.is_finite() and self._parent._base.characteristic() > 1<<29: + if n_GetChar(r) > 1<<29: raise NotImplementedError("Division of multivariate polynomials over prime fields with characteristic > 2^29 is not implemented.") cdef int count = singular_polynomial_length_bounded(self._poly,15) @@ -5398,7 +5394,7 @@ cdef class MPolynomial_libsingular(MPolynomial): raise TypeError("second parameter needs to be an element of self.parent() or None") - if self._parent._base.is_finite() and self._parent._base.characteristic() > 1<<29: + if n_GetChar(_ring) > 1<<29: raise NotImplementedError("Resultants of multivariate polynomials over prime fields with characteristic > 2^29 is not implemented.") if is_IntegerRing(self._parent._base): diff --git a/src/sage/rings/polynomial/skew_polynomial_ring.py b/src/sage/rings/polynomial/skew_polynomial_ring.py index d2dfab55344..8ebbc2ef9e9 100644 --- a/src/sage/rings/polynomial/skew_polynomial_ring.py +++ b/src/sage/rings/polynomial/skew_polynomial_ring.py @@ -22,16 +22,15 @@ doctest errors """ - -############################################################################# +# *************************************************************************** # Copyright (C) 2012 Xavier Caruso # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#**************************************************************************** +# https://www.gnu.org/licenses/ +# *************************************************************************** from __future__ import print_function, absolute_import, division @@ -42,14 +41,12 @@ from sage.rings.ring import Algebra from sage.categories.rings import Rings from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ from sage.rings.ring import Field -from sage.structure.category_object import normalize_names -from sage.categories.morphism import Morphism -from sage.categories.morphism import IdentityMorphism -from sage.rings.polynomial.skew_polynomial_element import (SkewPolynomial, - SkewPolynomialBaseringInjection) +from sage.categories.morphism import Morphism, IdentityMorphism +from sage.rings.polynomial.skew_polynomial_element import SkewPolynomialBaseringInjection -######################################################################################### +############################################################################ def _base_ring_to_fraction_field(S): """ @@ -485,7 +482,7 @@ def _coerce_map_from_(self, P): return False if P.variable_name() == self.variable_name(): if (P.base_ring() is self.base_ring() - and self.base_ring() is ZZ_sage): + and self.base_ring() is ZZ): if self._implementation_names == ('NTL',): return False return self.base_ring().has_coerce_map_from(P.base_ring()) diff --git a/src/sage/rings/polynomial/skew_polynomial_ring_constructor.py b/src/sage/rings/polynomial/skew_polynomial_ring_constructor.py index ba0c9e77454..d1f913d2894 100644 --- a/src/sage/rings/polynomial/skew_polynomial_ring_constructor.py +++ b/src/sage/rings/polynomial/skew_polynomial_ring_constructor.py @@ -15,25 +15,22 @@ - Johan Rosenkilde (2016-08-03): changes to import format """ - -############################################################################# +# *************************************************************************** # Copyright (C) 2012 Xavier Caruso # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#**************************************************************************** +# https://www.gnu.org/licenses/ +# *************************************************************************** from __future__ import print_function, absolute_import, division from sage import categories -import cysignals -import sage.rings.ring as ring from sage.structure.category_object import normalize_names -from sage.rings.finite_rings.finite_field_base import is_FiniteField from sage.categories.morphism import Morphism, IdentityMorphism + def SkewPolynomialRing(base_ring, base_ring_automorphism=None, names=None, sparse=False): r""" Return the globally unique skew polynomial ring with the given properties diff --git a/src/sage/rings/power_series_ring.py b/src/sage/rings/power_series_ring.py index 06e5a1c5567..5632233bcd9 100644 --- a/src/sage/rings/power_series_ring.py +++ b/src/sage/rings/power_series_ring.py @@ -83,6 +83,15 @@ sage: S = parent(1/t); S Sparse Laurent Series Ring in t over Rational Field +Choose another implementation of the attached polynomial ring:: + + sage: R. = PowerSeriesRing(ZZ) + sage: type(t.polynomial()) + <... 'sage.rings.polynomial.polynomial_integer_dense_flint.Polynomial_integer_dense_flint'> + sage: S. = PowerSeriesRing(ZZ, implementation='NTL') + sage: type(s.polynomial()) + <... 'sage.rings.polynomial.polynomial_integer_dense_ntl.Polynomial_integer_dense_ntl'> + AUTHORS: - William Stein: the code @@ -134,7 +143,7 @@ from sage.interfaces.magma import MagmaElement from sage.rings.fraction_field_element import FractionFieldElement from sage.misc.sage_eval import sage_eval - + from sage.structure.unique_representation import UniqueRepresentation from sage.structure.category_object import normalize_names from sage.structure.element import parent @@ -474,8 +483,9 @@ def __init__(self, base_ring, name=None, default_prec=None, sparse=False, sparse - ``implementation`` -- either ``'poly'``, ``'mpoly'``, or - ``'pari'``. The default is ``'pari'`` if the base field is - a PARI finite field, and ``'poly'`` otherwise. + ``'pari'``. Other values (for example ``'NTL'``) are passed to + the attached polynomial ring. The default is ``'pari'`` if + the base field is a PARI finite field, and ``'poly'`` otherwise. If the base ring is a polynomial ring, then the option ``implementation='mpoly'`` causes computations to be done with @@ -524,8 +534,13 @@ def __init__(self, base_ring, name=None, default_prec=None, sparse=False, implementation = 'pari' else: implementation = 'poly' + R = PolynomialRing(base_ring, name, sparse=sparse) + elif implementation not in ['pari', 'mpoly']: # see :trac:`28996` + R = PolynomialRing(base_ring, name, sparse=sparse, implementation=implementation) + implementation = 'poly' + else: + R = PolynomialRing(base_ring, name, sparse=sparse) - R = PolynomialRing(base_ring, name, sparse=sparse) self.__poly_ring = R self.__is_sparse = sparse if default_prec is None: diff --git a/src/sage/rings/puiseux_series_ring.py b/src/sage/rings/puiseux_series_ring.py new file mode 100644 index 00000000000..0d267782cba --- /dev/null +++ b/src/sage/rings/puiseux_series_ring.py @@ -0,0 +1,441 @@ +# -*- coding: utf-8 -*- +r""" +Puiseux Series Ring + +The ring of Puiseux series. + +AUTHORS: + +- Chris Swierczewski 2016: initial version on https://github.com/abelfunctions/abelfunctions/tree/master/abelfunctions +- Frédéric Chapoton 2016: integration of code +- Travis Scrimshaw, Sebastian Oehms 2019-2020: basic improvements and completions + +REFERENCES: + +- :wikipedia:`Puiseux_series` +""" + + +# **************************************************************************** +# Copyright (c) 2016 Chris Swierczewski +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +# **************************************************************************** + + +from sage.misc.cachefunc import cached_method +from sage.rings.infinity import infinity +from sage.rings.puiseux_series_ring_element import PuiseuxSeries +from sage.structure.unique_representation import UniqueRepresentation +from sage.rings.ring import CommutativeRing +from sage.structure.element import parent +from sage.rings.laurent_series_ring import LaurentSeriesRing +from sage.rings.laurent_series_ring_element import LaurentSeries +from sage.rings.power_series_ring import is_PowerSeriesRing +from sage.rings.power_series_ring_element import PowerSeries + + +class PuiseuxSeriesRing(UniqueRepresentation, CommutativeRing): + """ + Rings of Puiseux series. + + EXAMPLES:: + + sage: P = PuiseuxSeriesRing(QQ, 'y') + sage: y = P.gen() + sage: f = y**(4/3) + y**(-5/6); f + y^(-5/6) + y^(4/3) + sage: f.add_bigoh(2) + y^(-5/6) + y^(4/3) + O(y^2) + sage: f.add_bigoh(1) + y^(-5/6) + O(y) + """ + @staticmethod + def __classcall__(cls, *args, **kwds): + r""" + TESTS:: + + sage: L = PuiseuxSeriesRing(QQ, 'q') + sage: L is PuiseuxSeriesRing(QQ, name='q') + True + sage: Lp. = PuiseuxSeriesRing(QQ) + sage: L is Lp + True + sage: loads(dumps(L)) is L + True + + sage: L.variable_names() + ('q',) + sage: L.variable_name() + 'q' + """ + if not kwds and len(args) == 1 and isinstance(args[0], LaurentSeriesRing): + laurent_series = args[0] + else: + laurent_series = LaurentSeriesRing(*args, **kwds) + + return super(PuiseuxSeriesRing, cls).__classcall__(cls, laurent_series) + + def __init__(self, laurent_series): + """ + Generic class for Puiseux series rings. + + EXAMPLES:: + + sage: P = PuiseuxSeriesRing(QQ, 'y') + sage: TestSuite(P).run() + + sage: P = PuiseuxSeriesRing(ZZ, 'x') + sage: TestSuite(P).run() + + sage: P = PuiseuxSeriesRing(ZZ['a,b'], 'x') + sage: TestSuite(P).run() + """ + base_ring = laurent_series.base_ring() + + # If self is R(( x^(1/e) )) then the corresponding Laurent series + # ring will be R(( x )) + self._laurent_series_ring = laurent_series + + CommutativeRing.__init__(self, base_ring, + names=laurent_series.variable_names(), + category=laurent_series.category()) + + def _repr_(self): + """ + String representation. + + EXAMPLES:: + + sage: PuiseuxSeriesRing(AA, 'y') + Puiseux Series Ring in y over Algebraic Real Field + """ + s = "Puiseux Series Ring in {} over {}".format(self.variable_name(), + self.base_ring()) + if self.is_sparse(): + s = 'Sparse ' + s + return s + + def base_extend(self, R): + """ + Extend the coefficients. + + INPUT: + + - ``R`` -- a ring + + EXAMPLES:: + + sage: A = PuiseuxSeriesRing(ZZ, 'y') + sage: A.base_extend(QQ) + Puiseux Series Ring in y over Rational Field + """ + return PuiseuxSeriesRing(self._laurent_series_ring.base_extend(R)) + + def change_ring(self, R): + """ + Return a Puiseux series ring over another ring. + + INPUT: + + - ``R`` -- a ring + + EXAMPLES:: + + sage: A = PuiseuxSeriesRing(ZZ, 'y') + sage: A.change_ring(QQ) + Puiseux Series Ring in y over Rational Field + """ + return PuiseuxSeriesRing(self._laurent_series_ring.change_ring(R)) + + def is_sparse(self): + """ + Return whether ``self`` is sparse. + + EXAMPLES:: + + sage: A = PuiseuxSeriesRing(ZZ, 'y') + sage: A.is_sparse() + False + """ + return self.laurent_series_ring().is_sparse() + + def is_dense(self): + """ + Return whether ``self`` is dense. + + EXAMPLES:: + + sage: A = PuiseuxSeriesRing(ZZ, 'y') + sage: A.is_dense() + True + """ + return self.laurent_series_ring().is_dense() + + def is_field(self, proof=True): + r""" + Return whether ``self`` is a field. + + A Puiseux series ring is a field if and only + its base ring is a field. + + EXAMPLES:: + + sage: A = PuiseuxSeriesRing(ZZ, 'y') + sage: A.is_field() + False + sage: A.change_ring(QQ).is_field() + True + """ + return self.base_ring().is_field(proof=proof) + + def fraction_field(self): + r""" + Return the fraction field of this ring of Laurent series. + + If the base ring is a field, then Puiseux series are already a field. + If the base ring is a domain, then the Puiseux series over its fraction + field is returned. Otherwise, raise a ``ValueError``. + + EXAMPLES:: + + sage: R = PuiseuxSeriesRing(ZZ, 't', 30).fraction_field() + sage: R + Puiseux Series Ring in t over Rational Field + sage: R.default_prec() + 30 + + sage: PuiseuxSeriesRing(Zmod(4), 't').fraction_field() + Traceback (most recent call last): + ... + ValueError: must be an integral domain + """ + from sage.categories.integral_domains import IntegralDomains + from sage.categories.fields import Fields + if self in Fields: + return self + elif self in IntegralDomains: + return PuiseuxSeriesRing(self._laurent_series_ring.fraction_field()) + else: + raise ValueError('must be an integral domain') + + def residue_field(self): + r""" + Return the residue field of this Puiseux series field + if it is a complete discrete valuation field (i.e. if + the base ring is a field, in which case it is also the + residue field). + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(GF(17)) + sage: R.residue_field() + Finite Field of size 17 + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: R.residue_field() + Traceback (most recent call last): + ... + TypeError: the base ring is not a field + """ + if not self.base_ring().is_field(): + raise TypeError("the base ring is not a field") + return self.base_ring() + + def uniformizer(self): + r""" + Return a uniformizer of this Puiseux series field if it is + a discrete valuation field (i.e. if the base ring is actually + a field). Otherwise, an error is raised. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: R.uniformizer() + t + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: R.uniformizer() + Traceback (most recent call last): + ... + TypeError: the base ring is not a field + """ + if not self.base_ring().is_field(): + raise TypeError("the base ring is not a field") + return self.gen() + + Element = PuiseuxSeries + + def _element_constructor_(self, x, e=1, prec=infinity): + r""" + Construct a Puiseux series from ``x``. + + INPUT: + + - ``x`` -- an object that can be converted into a Puiseux series + - ``e`` -- (default: ``1``) the ramification index of the series + - ``prec`` -- (default: ``infinity``) the precision of the series + as a rational number + + EXAMPLES:: + + sage: P = PuiseuxSeriesRing(QQ, 'y') + sage: y = P.gen() + sage: P([1,3,5,7]) + 1 + 3*y + 5*y^2 + 7*y^3 + sage: P(33/14) + 33/14 + + sage: Q = PowerSeriesRing(QQ, 'y') + sage: z = Q([1,2,4,5]).O(6); z + 1 + 2*y + 4*y^2 + 5*y^3 + O(y^6) + sage: P(z) + y**(1/2) + 1 + y^(1/2) + 2*y + 4*y^2 + 5*y^3 + O(y^6) + + sage: Q = LaurentSeriesRing(QQ, 'y') + sage: z = Q([3,2,1,2]).add_bigoh(5); z + 3 + 2*y + y^2 + 2*y^3 + O(y^5) + sage: P(z) + y**(1/2) + 3 + y^(1/2) + 2*y + y^2 + 2*y^3 + O(y^5) + + sage: from sage.modular.etaproducts import qexp_eta + sage: y^(1/24)*qexp_eta(P, prec=30) + y^(1/24) - y^(25/24) - y^(49/24) + y^(121/24) + y^(169/24) - y^(289/24) - y^(361/24) + y^(529/24) + y^(625/24) + O(y^(721/24)) + """ + P = parent(x) + + # 1. x is a Puiseux series belonging to this ring. + # This is short-circuited by the coercion framework. + #if isinstance(x, self.element_class) and P is self: + # return x + # 2. x is a Puiseux series but not an element of this ring. the laurent + # part should be coercible to the laurent series ring of self + if isinstance(x, self.element_class): + l = self._laurent_series_ring(x.laurent_part()) + e = x.ramification_index() + # 3. x is a member of the base ring then convert x to a laurent series + # and set the ramification index of the Puiseux series to 1. + elif P is self.base_ring(): + l = self._laurent_series_ring(x) + e = 1 + # 4. x is a Laurent or power series with the same base ring + elif (isinstance(x, (LaurentSeries, PowerSeries)) + and P is self.base_ring()): + l = self._laurent_series_ring(x) + # 5. everything else: try to coerce to laurent series ring + else: + l = self._laurent_series_ring(x) + + # finally, construct an instance of the element class and adding + # the precision afterwards (see also :trac:`28993`). + return self.element_class(self, l, e=e).add_bigoh(prec) + + def _coerce_map_from_(self, P): + r""" + Return a coercion map from `P` to `self`, or `True`, or `None`. + + The following rings admit a coercion map to the Puiseux series ring + `A((x-a)^(1/e))`: + + - any ring that admits a coercion map to `A` + + - any Laurent series ring, power series ring, or polynomial ring in the + variable `(x-a)` over a ring admitting a coercion map to `A` + + - any Puiseux series ring with the same center `a` and ramification + index equal to a multiple of `self`'s ramification index. For + example, Puiseux series in (x-a)^(1/2) can be interpreted as Puiseux + series in (x-a)^(1/4). + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: 5 in R, 1/5 in R # indirect doctests + (True, False) + + sage: p = x^(1/2) + x**3-x**(-1/4) + sage: p.laurent_part() in R # indirect doctests + True + + sage: Q. = PuiseuxSeriesRing(QQ) # indirect doctests + sage: p in Q + True + """ + # any ring that has a coercion map to A + A = self.base_ring() + if A is P: + return True + f = A.coerce_map_from(P) + if f is not None: + return self.coerce_map_from(A) * f + + # Laurent series rings, power series rings, and polynomial rings with + # the same variable name and the base rings are coercible + if ((isinstance(P, PuiseuxSeriesRing) or isinstance(P, LaurentSeriesRing) or + is_PowerSeriesRing(P)) and + P.variable_name() == self.variable_name() and + A.has_coerce_map_from(P.base_ring())): + return True + + # # other Puiseux series rings with the same variable name and + # # center. Puiseux series rings with difference ramification indices are + # # coercible to each other. + # if (is_PuiseuxSeriesRing(P) and + # P.variable_name() == self.variable_name()): + # return True + + @cached_method + def gen(self, n=0): + r""" + Return the generator of ``self``. + + EXAMPLES:: + + sage: A = PuiseuxSeriesRing(AA, 'z') + sage: A.gen() + z + """ + if n != 0: + raise IndexError("generator {} not defined".format(n)) + return self.element_class(self, [0, 1], e=1) + + def ngens(self): + r""" + Return the number of generators of ``self``, namely 1. + + EXAMPLES:: + + sage: A = PuiseuxSeriesRing(AA, 'z') + sage: A.ngens() + 1 + """ + return 1 + + def laurent_series_ring(self): + r""" + Return the underlying Laurent series ring. + + EXAMPLES:: + + sage: A = PuiseuxSeriesRing(AA, 'z') + sage: A.laurent_series_ring() + Laurent Series Ring in z over Algebraic Real Field + """ + return self._laurent_series_ring + + def default_prec(self): + """ + Return the default precision of ``self``. + + EXAMPLES:: + + sage: A = PuiseuxSeriesRing(AA, 'z') + sage: A.default_prec() + 20 + """ + return self.laurent_series_ring().default_prec() + diff --git a/src/sage/rings/puiseux_series_ring_element.pxd b/src/sage/rings/puiseux_series_ring_element.pxd new file mode 100644 index 00000000000..47371a4e77d --- /dev/null +++ b/src/sage/rings/puiseux_series_ring_element.pxd @@ -0,0 +1,6 @@ +from sage.structure.element cimport AlgebraElement, ModuleElement +from sage.rings.laurent_series_ring_element cimport LaurentSeries + +cdef class PuiseuxSeries(AlgebraElement): + cpdef LaurentSeries _l + cdef size_t _e diff --git a/src/sage/rings/puiseux_series_ring_element.pyx b/src/sage/rings/puiseux_series_ring_element.pyx new file mode 100644 index 00000000000..29be2de67af --- /dev/null +++ b/src/sage/rings/puiseux_series_ring_element.pyx @@ -0,0 +1,1050 @@ +# -*- coding: utf-8 -*- +r""" +Puiseux Series Ring Element + +A Puiseux series is a series of the form + +.. MATH:: + + p(x) = \sum_{n=N}^{\infty} a_n (x-a)^{n/e}, + +where the integer :math:`e` is called the *ramification index* of the series +and the number :math:`a` is the *center*. A Puiseux series is essentially a +Laurent series but with fractional exponents. + +EXAMPLES: + +We begin by constructing the ring of Puiseux series in `x` with coefficients +in the rationals:: + + sage: R. = PuiseuxSeriesRing(QQ) + +This command also defines ``x`` as the generator of this ring. + +When constructing a Puiseux series, the ramification index is automatically +determined from the greatest common divisor of the exponents:: + + sage: p = x^(1/2); p + x^(1/2) + sage: p.ramification_index() + 2 + sage: q = x^(1/2) + x**(1/3); q + x^(1/3) + x^(1/2) + sage: q.ramification_index() + 6 + +Other arithmetic can be performed with Puiseux Series:: + + sage: p + q + x^(1/3) + 2*x^(1/2) + sage: p - q + -x^(1/3) + sage: p * q + x^(5/6) + x + sage: (p / q).add_bigoh(4/3) + x^(1/6) - x^(1/3) + x^(1/2) - x^(2/3) + x^(5/6) - x + x^(7/6) + O(x^(4/3)) + +Mind the base ring. However, the base ring can be changed:: + + sage: I*q + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: 'Symbolic Ring' and 'Puiseux Series Ring in x over Rational Field' + sage: qz = q.change_ring(ZZ); qz + x^(1/3) + x^(1/2) + sage: qz.parent() + Puiseux Series Ring in x over Integer Ring + +Other properties of the Puiseux series can be easily obtained:: + + sage: r = (3*x^(-1/5) + 7*x^(2/5) + (1/2)*x).add_bigoh(6/5); r + 3*x^(-1/5) + 7*x^(2/5) + 1/2*x + O(x^(6/5)) + sage: r.valuation() + -1/5 + sage: r.prec() + 6/5 + sage: r.precision_absolute() + 6/5 + sage: r.precision_relative() + 7/5 + sage: r.exponents() + [-1/5, 2/5, 1] + sage: r.coefficients() + [3, 7, 1/2] + +Finally, Puiseux series are compatible with other objects in Sage. +For example, you can perform arithmetic with Laurent series:: + + sage: L. = LaurentSeriesRing(ZZ) + sage: l = 3*x^(-2) + x^(-1) + 2 + x**3 + sage: r + l + 3*x^-2 + x^-1 + 3*x^(-1/5) + 2 + 7*x^(2/5) + 1/2*x + O(x^(6/5)) + +AUTHORS: + +- Chris Swierczewski 2016: initial version on https://github.com/abelfunctions/abelfunctions/tree/master/abelfunctions +- Frédéric Chapoton 2016: integration of code +- Travis Scrimshaw, Sebastian Oehms 2019-2020: basic improvements and completions + +REFERENCES: + +- :wikipedia:`Puiseux_series` +""" + + +# **************************************************************************** +# Copyright (c) 2016 Chris Swierczewski +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.arith.functions import lcm +from sage.arith.misc import gcd +from sage.ext.fast_callable import fast_callable +from sage.rings.big_oh import O +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.rings.complex_field import ComplexField +from sage.rings.infinity import infinity +from sage.rings.laurent_series_ring_element cimport LaurentSeries +from sage.rings.laurent_series_ring import LaurentSeriesRing +from sage.rings.power_series_ring_element cimport PowerSeries +from sage.structure.element cimport (Element, ModuleElement, + RingElement, AlgebraElement) +from sage.structure.richcmp cimport richcmp + + +cdef class PuiseuxSeries(AlgebraElement): + r""" + A Puiseux series. + + .. MATH:: + + \sum_{n=-N}^\infty a_n x^{n/e} + + It is stored as a Laurent series: + + .. MATH:: + + \sum_{n=-N}^\infty a_n t^n + + where `t = x^{1/e}`. + + INPUT: + + - ``parent`` -- the parent ring + + - ``f`` -- one of the following types of inputs: + + * instance of :class:`PuiseuxSeries` + * instance that can be coerced into the Laurent sersies ring of the parent + + - ``e`` -- integer (default: 1) the ramification index + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + x**3; p + x^(1/2) + x^3 + sage: q = x**(1/2) - x**(-1/2) + sage: r = q.add_bigoh(7/2); r + -x^(-1/2) + x^(1/2) + O(x^(7/2)) + sage: r**2 + x^-1 - 2 + x + O(x^3) + """ + + def __init__(self, parent, f, e=1): + r""" + Initialize ``self``. + + TESTS:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + x**3 + sage: TestSuite(p).run() + """ + AlgebraElement.__init__(self, parent) + L = parent._laurent_series_ring + + if isinstance(f, PuiseuxSeries): + if (f)._l._parent is L: + l = (f)._l + e = (f)._e + else: + l = L((f)._l) + e = L((f)._e) + else: + l = L(f) + + # -------------------------------------------------------- + # choose a representative for this Puiseux series having + # minimal ramification index. This is neccessary because + # some methods need it as minimal as possible (for example + # :meth:`laurent_series' or :meth:`power_series`) + # -------------------------------------------------------- + exp_list = l.exponents() + prec = l.prec() + if prec == infinity: + d = gcd(exp_list +[e]) + else: + d = gcd(exp_list + [e] + [prec]) + if d > 1: + # ramification index can be reduced dividing by d + e = e / d + cf_ori = l.list() + if cf_ori: + cf = [cf_ori[d*i] for i in range((len(cf_ori)-1) / d + 1)] + else: + cf = cf_ori + val = l.valuation() / d + l = l.parent()(cf, n=val) + if prec != infinity: + l = l.add_bigoh(prec / d) + + self._l = l + self._e = long(abs(e)) + + def __reduce__(self): + """ + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: p = x^(1/2) + x**3-x**(-1/4) + sage: loads(dumps(p)) == p # indirect doctest + True + """ + return (self._parent, (self._l, self._e)) + + def _im_gens_(self, codomain, im_gens, base_map=None): + """ + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: p = x^(1/3) + x**3 + sage: t = p._im_gens_(QQbar, [2]) + sage: t in QQbar + True + sage: f = R.hom([QQbar(2)], check=False) + sage: t == f(p) + True + """ + return self(codomain(im_gens[0])) + + def _repr_(self): + """ + Return a string representation. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + x**3-x**(-1/4); p + -x^(-1/4) + x^(1/2) + x^3 + sage: R.zero() + 0 + + sage: S. = PuiseuxSeriesRing(Zp(5)) + sage: t**(1/2) + 5 * t^(1/3) + (5 + O(5^21))*t^(1/3) + (1 + O(5^20))*t^(1/2) + """ + laurent = self.laurent_part() + s = repr(laurent) + if self.ramification_index() == 1: + return s + + X = self._parent.variable_name() + + # find a temporary variable name (to avoid multiple transformations) + Xtemp = '?' + while Xtemp in s: Xtemp +='?' # if somebody uses '?' in variable_name + + # renaming and generalizing linear term + s = s.replace('%s' %X, '%s^1' %Xtemp) + s = s.replace('^1^', '^' ) + + # prepare exponent list + if laurent.prec() is infinity: + exponents = [ZZ(exp) for exp in set(laurent.exponents())] + else: + exponents = [ZZ(exp) for exp in set(laurent.exponents() + [laurent.prec()])] + + # sort exponents such that the largest will be replaced first + exp_pos = [exp for exp in exponents if exp >= 0]; exp_pos.sort(reverse=True) + exp_neg = [exp for exp in exponents if exp < 0]; exp_neg.sort() + exponents = exp_neg + exp_pos + + # replacing exponents + e = ZZ(self.ramification_index()) + for exp_l in exponents: + exp = exp_l/e + repl_str = '%s^%s' %(Xtemp, exp_l) + if exp.is_one(): + s = s.replace(repl_str, '%s' %X) + elif e.divides(exp_l): + s = s.replace(repl_str, '%s^%s' %(X, exp)) + else: + s = s.replace(repl_str, '%s^(%s)' %(X, exp)) + return s + + def __call__(self, x): + r""" + Evaluate this Puiseux series. + + INPUT: + + - ``x`` -- element of a ring + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + x**3-x**(-1/4) + sage: p(16) + 8199/2 + sage: p(pi.n()) + 32.0276049867404 + """ + # use x.nth_root since x**(1/self._e) returns oo when x = 0 + if isinstance(x, int): + x = ZZ(x) + elif isinstance(x, float): + x = ComplexField()(x) + t = x.nth_root(self._e) + p = self._l.__u.polynomial() + n = self._l.__n + return p(t)*t**n + + def _common_ramification_index(self, PuiseuxSeries right): + r""" + Return a ramification index common to ``self`` and ``right``. + + In order to perform arithmetic on Puiseux series it is useful to find a + common ramification index between two operands. That is, given Puiseux + series :math:`p` and :math:`q` of ramification indices :math:`e` and + :math:`f` we write both as series :math:`\tilde{f}` and + :math:`\tilde{g}` in :math:`(x-a)^(1/g)` such that, + + .. MATH:: + + f = \tilde{f}((x-a)^M), g = \tilde{g}((x-a)^N). + + INPUT: + + - ``right`` -- a Puiseux series + + OUTPUT: + + - ``g`` -- int; a ramification index common to self and right + - ``M, N`` -- int, int; scaling factors on self and right, respectively + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/3) + x**2-x**(-1/7) + sage: q = x^(-1/3) + x**2-x**(1/5) + sage: p._common_ramification_index(q) + (105, 5, 7) + sage: q._common_ramification_index(p) + (105, 7, 5) + """ + m = self._e + n = right._e + g = lcm(m, n) + m = g / m + n = g / n + return g, m, n + + cpdef _add_(self, right_m): + """ + Return the sum. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + 3/4 * x^(2/3) + sage: q = 2*x^(1/3) + 3/4 * x^(2/5) + sage: p + q # indirect doctest + 2*x^(1/3) + 3/4*x^(2/5) + x^(1/2) + 3/4*x^(2/3) + """ + cdef PuiseuxSeries right = right_m + cdef LaurentSeries l, l1, l2 + cdef size_t g, m, n + + g, m, n = self._common_ramification_index(right) + l1 = self._l.V(m) + l2 = right._l.V(n) + l = l1 + l2 + return type(self)(self._parent, l, g) + + cpdef _sub_(self, right_m): + """ + Return the difference. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + 3/4 * x^(2/3) + sage: q = 2*x^(1/3) + 3/4 * x^(2/5) + sage: p - q # indirect doctest + -2*x^(1/3) - 3/4*x^(2/5) + x^(1/2) + 3/4*x^(2/3) + """ + cdef PuiseuxSeries right = right_m + cdef LaurentSeries l, l1, l2 + cdef size_t g, m, n + + g, m, n = self._common_ramification_index(right) + l1 = self._l.V(m) + l2 = right._l.V(n) + l = l1 - l2 + return type(self)(self._parent, l, g) + + cpdef _mul_(self, right_r): + """ + Return the product. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + 3/4 * x^(2/3) + sage: q = 2*x^(1/3) + 3/4 * x^(2/5) + sage: p * q # indirect doctest + 2*x^(5/6) + 3/4*x^(9/10) + 3/2*x + 9/16*x^(16/15) + """ + cdef PuiseuxSeries right = right_r + cdef LaurentSeries l, l1, l2 + cdef size_t g, m, n + + g, m, n = self._common_ramification_index(right) + l1 = self._l.V(m) + l2 = right._l.V(n) + l = l1 * l2 + return type(self)(self._parent, l, g) + + cpdef _rmul_(self, Element c): + """ + Return the rigth scalar multiplication. + + EXAMPLES:: + + sage: P. = PuiseuxSeriesRing(ZZ) + sage: t = y^(-1/3) + O(y^(0)) + sage: 5*t # indirect doctest + 5*y^(-1/3) + O(1) + """ + return type(self)(self._parent, self._l._rmul_(c), self._e) + + cpdef _lmul_(self, Element c): + """ + Return the left scalar multiplication. + + EXAMPLES:: + + sage: P. = PuiseuxSeriesRing(Zp(3)) + sage: t = y^(2/5) + O(y) + sage: 5*t # indirect doctest + (2 + 3 + O(3^20))*y^(2/5) + O(y) + """ + return type(self)(self._parent, self._l._lmul_(c), self._e) + + cpdef _div_(self, right_r): + """ + Return the quotient. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + 3/4 * x^(2/3) + sage: q = 2*x^(1/3) + 3/4 * x^(2/5) + sage: p / q + 1/2*x^(1/6) - 3/16*x^(7/30) + 9/128*x^(3/10) + 3/8*x^(1/3) + - 27/1024*x^(11/30) - 9/64*x^(2/5) + 81/8192*x^(13/30) + + 27/512*x^(7/15) - 243/65536*x^(1/2) - 81/4096*x^(8/15) + + 729/524288*x^(17/30) + 243/32768*x^(3/5) - 2187/4194304*x^(19/30) + - 729/262144*x^(2/3) + 6561/33554432*x^(7/10) + + 2187/2097152*x^(11/15) - 19683/268435456*x^(23/30) + - 6561/16777216*x^(4/5) + O(x^(5/6)) + """ + cdef PuiseuxSeries right = right_r + cdef LaurentSeries l, l1, l2 + cdef size_t g, m, n + + g, m, n = self._common_ramification_index(right) + l1 = self._l.V(m) + l2 = right._l.V(n) + l = l1 / l2 + return type(self)(self._parent, l, g) + + def __pow__(_self, r, dummy): + """ + Return the power. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + 3/4 * x^(2/3) + sage: p ** 3 + x^(3/2) + 9/4*x^(5/3) + 27/16*x^(11/6) + 27/64*x^2 + """ + cdef PuiseuxSeries self = _self + cdef LaurentSeries l + cdef size_t e + + r = QQ(r) + numer = r.numerator() + denom = r.denominator() + + # if the exponent is integral then do normal exponentiation + if denom == 1: + l = self._l ** int(numer) + e = self._e + # otherwise, we only exponentiate by a rational number if there is a + # single term in the Puiseux series + # + # (I suppose we could use Taylor series expansions in the general case) + else: + if not self.is_monomial(): + raise ValueError('can only exponentiate single term by rational') + l = self._l.V(numer) + e = self._e * int(denom) + return type(self)(self._parent, l, e) + + cpdef _richcmp_(self, right_r, int op): + r""" + Comparison of ``self`` and ``right``. + + We say two approximate Puiseux series are equal, if they agree for + all coefficients up to the *minimum* of the precisions of each. + Comparison is done in dictionary order going from lowest degree + to highest degree coefficients with respect to the correspondig + Laurent series. That means that comparison is performed for + corresponding `LaurentSeries` instances obtained for the common + ramification index. + + See :meth:`power_series_ring_element._richcmp_` and + :meth:`_laurent_series_ring_element._richcmp_` for more + information. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + 3/4 * x^(2/3) + sage: q = 2*x^(1/3) + 3/4 * x^(2/5) + sage: (p < q, p >= q, p == 0, q != 0) + (True, False, False, True) + sage: p2 = x^(1/2) + 3/5 * x^(2/3) + sage: (p2 < p, p2 >= p) + (True, False) + sage: p3 = p2.add_bigoh(2/3); p3 + x^(1/2) + O(x^(2/3)) + sage: (p3 == p2, p3 == p, p3 < q, p3 > q) + (True, True, True, False) + """ + cdef PuiseuxSeries right = right_r + if self._e == right._e: + return richcmp(self._l, right._l, op) + + # If both have different ramification indices they must be different as + # Puiseux series (by the normalization performed in the python constructor). + # We use the ramification index to order them. + return richcmp(self._e, right._e, op) + + def __lshift__(self, r): + """ + Apply :meth:`shift` using the operator `<<`. + + EXAMPLES:: + + sage: P. = LaurentPolynomialRing(ZZ) + sage: R. = PuiseuxSeriesRing(P) + sage: p = y*x**(-1/3) + 2*y^(-2)*x**(1/2) + sage: p << 1/3 # indirect doctest + y + (2*y^-2)*x^(5/6) + """ + return self.shift(r) + + def __rshift__(self, r): + """ + Apply :meth:`shift` with negative argument using the operator `>>`. + + EXAMPLES:: + + sage: P. = LaurentPolynomialRing(ZZ) + sage: R. = PuiseuxSeriesRing(P) + sage: p = y*x**(-1/3) + 2*y^(-2)*x**(1/2) + sage: p >> 1/3 # indirect doctest + y*x^(-2/3) + (2*y^-2)*x^(1/6) + """ + return self.shift(-r) + + def __nonzero__(self): + """ + Return whether ``self`` is not zero. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + 3/4 * x^(2/3) + sage: p.is_zero() # indirect doctest + False + sage: R.zero() != 0 + False + """ + return bool(self._l) + + def __hash__(self): + """ + Return a hash of ``self``. + + EXAMPLES:: + + sage: from operator import xor + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(-7/2) + 3 + 5*x^(1/2) - 7*x**3 + sage: hash(p) == xor(hash(p.laurent_part()), 2) # indirect doctest + True + """ + return hash(self._l) ^ self._e + + def __getitem__(self, r): + r""" + Return the coefficient with exponent ``r``. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(-7/2) + 3 + 5*x^(1/2) - 7*x**3 + sage: p[-7/2] + 1 + sage: p[0] + 3 + sage: p[1/2] + 5 + sage: p[3] + -7 + sage: p[100] + 0 + """ + if isinstance(r, slice): + start, stop, step = r.start, r.stop, r.step + n = slice(start * self._e, stop * self._e, step * self._e) + return PuiseuxSeries(self._parent, self._l[start:stop:step], self._e) + else: + n = int(r * self._e) + return self._l[n] + + def __iter__(self): + """ + Return an iterator over the coefficients. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(-7/2) + 3 + 5*x^(1/2) - 7*x**3 + sage: list(p) + [1, 0, 0, 0, 0, 0, 0, 3, 5, 0, 0, 0, 0, -7] + """ + return iter(self._l) + + def __copy__(self): + """ + Since this is immutable, return ``self``. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: p = x^(3/4) + 2*x^(4/5) + 3* x^(5/6) + sage: p2 = copy(p); p2 + x^(3/4) + 2*x^(4/5) + 3*x^(5/6) + sage: p == p2 + True + sage: p is p2 + True + """ + return self + + def laurent_part(self): + """ + Return the underlying Laurent series. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + 3/4 * x^(2/3) + sage: p.laurent_part() + x^3 + 3/4*x^4 + """ + return self._l + + def ramification_index(self): + """ + Return the ramification index. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + 3/4 * x^(2/3) + sage: p.ramification_index() + 6 + """ + return self._e + + def valuation(self): + r""" + Return the valuation of ``self``. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(-7/2) + 3 + 5*x^(1/2) - 7*x**3 + sage: p.valuation() + -7/2 + """ + val = self._l.valuation() / QQ(self._e) + if val == infinity: + return 0 + return val + + def add_bigoh(self, prec): + r""" + Return the truncated series at chosen precision ``prec``. + + INPUT: + + - ``prec`` -- the precision of the series as a rational number + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(-7/2) + 3 + 5*x^(1/2) - 7*x**3 + sage: p.add_bigoh(2) + x^(-7/2) + 3 + 5*x^(1/2) + O(x^2) + sage: p.add_bigoh(0) + x^(-7/2) + O(1) + sage: p.add_bigoh(-1) + x^(-7/2) + O(x^-1) + + .. NOTE:: + + The precision passed to the method is adapted to the common + ramification index:: + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: p = x**(-1/3) + 2*x**(1/5) + sage: p.add_bigoh(1/2) + x^(-1/3) + 2*x^(1/5) + O(x^(7/15)) + """ + if prec == infinity or prec >= self.prec(): + return self + + l_prec = int(prec * self._e) + l = self._l.add_bigoh(l_prec) + return PuiseuxSeries(self._parent, l, self._e) + + def change_ring(self, R): + r""" + Return ``self`` over a the new ring ``R``. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: p = x^(-7/2) + 3 + 5*x^(1/2) - 7*x**3 + sage: q = p.change_ring(QQ); q + x^(-7/2) + 3 + 5*x^(1/2) - 7*x^3 + sage: q.parent() + Puiseux Series Ring in x over Rational Field + """ + return self._parent.change_ring(R)(self) + + def is_unit(self): + r""" + Return whether ``self`` is a unit. + + A Puiseux series is a unit if and only if its leading coefficient is. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: p = x^(-7/2) + 3 + 5*x^(1/2) - 7*x**3 + sage: p.is_unit() + True + sage: q = 4 * x^(-7/2) + 3 * x**4 + sage: q.is_unit() + False + """ + return self._l.is_unit() + + def is_zero(self): + """ + Return whether ``self`` is zero. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + 3/4 * x^(2/3) + sage: p.is_zero() + False + sage: R.zero().is_zero() + True + """ + return self._l.is_zero() + + def is_monomial(self): + r""" + Return whether ``self`` is a monomial. + + This is ``True`` if and only if ``self`` is `x^p` for + some rational `p`. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(1/2) + 3/4 * x^(2/3) + sage: p.is_monomial() + False + sage: q = x**(11/13) + sage: q.is_monomial() + True + sage: q = 4*x**(11/13) + sage: q.is_monomial() + False + """ + return self._l.is_monomial() + + def list(self): + r""" + Return the list of coefficients indexed by the exponents of the + the corresponding Laurent series. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: p = x^(3/4) + 2*x^(4/5) + 3* x^(5/6) + sage: p.list() + [1, 0, 0, 2, 0, 3] + """ + return self._l.list() + + def coefficients(self): + r""" + Return the list of coefficients. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: p = x^(3/4) + 2*x^(4/5) + 3* x^(5/6) + sage: p.coefficients() + [1, 2, 3] + """ + return self._l.coefficients() + + def exponents(self): + r""" + Return the list of exponents. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: p = x^(3/4) + 2*x^(4/5) + 3* x^(5/6) + sage: p.exponents() + [3/4, 4/5, 5/6] + """ + return [QQ(n) / self._e for n in self._l.exponents()] + + def __setitem__(self, n, value): + """ + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: f = t^2 + t^3 + O(t^10) + sage: f[2] = 5 + Traceback (most recent call last): + ... + IndexError: Puiseux series are immutable + """ + raise IndexError('Puiseux series are immutable') + + def degree(self): + r""" + Return the degree of ``self``. + + EXAMPLES:: + + sage: P. = PolynomialRing(GF(5)) + sage: R. = PuiseuxSeriesRing(P) + sage: p = 3*y*x**(-2/3) + 2*y**2*x**(1/5); p + 3*y*x^(-2/3) + 2*y^2*x^(1/5) + sage: p.degree() + 1/5 + """ + return self._l.degree() / self._e + + def shift(self, r): + r""" + Return this Puiseux series multiplied by `x^r`. + + EXAMPLES:: + + sage: P. = LaurentPolynomialRing(ZZ) + sage: R. = PuiseuxSeriesRing(P) + sage: p = y*x**(-1/3) + 2*y^(-2)*x**(1/2); p + y*x^(-1/3) + (2*y^-2)*x^(1/2) + sage: p.shift(3) + y*x^(8/3) + (2*y^-2)*x^(7/2) + """ + cdef LaurentSeries l = self._l.shift(r * self._e) + return PuiseuxSeries(self._parent, l, self._e) + + def truncate(self, r): + r""" + Return the Puiseux series of degree `< r`. + + This is equivalent to ``self`` modulo `x^r`. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: p = (x**(-1/3) + 2*x**3)**2; p + x^(-2/3) + 4*x^(8/3) + 4*x^6 + sage: q = p.truncate(5); q + x^(-2/3) + 4*x^(8/3) + sage: q == p.add_bigoh(5) + True + """ + l = self._l.truncate(r * self._e) + return PuiseuxSeries(self._parent, l, self._e) + + def prec(self): + r""" + Return the precision of ``self``. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: p = (x**(-1/3) + 2*x**3)**2; p + x^(-2/3) + 4*x^(8/3) + 4*x^6 + sage: q = p.add_bigoh(5); q + x^(-2/3) + 4*x^(8/3) + O(x^5) + sage: q.prec() + 5 + """ + if self._l.prec() == infinity: + return infinity + return self._l.prec() / self._e + + precision_absolute = prec + + def precision_relative(self): + r""" + Return the relative precision of the series. + + The relative precision of the Puiseux series is the difference + between its absolute precision and its valuation. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(GF(3)) + sage: p = (x**(-1/3) + 2*x**3)**2; p + x^(-2/3) + x^(8/3) + x^6 + sage: q = p.add_bigoh(7); q + x^(-2/3) + x^(8/3) + x^6 + O(x^7) + sage: q.precision_relative() + 23/3 + """ + if self.is_zero(): + return 0 + return self.prec() - self.valuation() + + def common_prec(self, p): + r""" + Return the minimum precision of `p` and ``self``. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: p = (x**(-1/3) + 2*x**3)**2 + sage: q5 = p.add_bigoh(5); q5 + x^(-2/3) + 4*x^(8/3) + O(x^5) + sage: q7 = p.add_bigoh(7); q7 + x^(-2/3) + 4*x^(8/3) + 4*x^6 + O(x^7) + sage: q5.common_prec(q7) + 5 + sage: q7.common_prec(q5) + 5 + """ + if self.prec() is infinity: + return p.prec() + elif p.prec() is infinity: + return self.prec() + return min(self.prec(), p.prec()) + + def variable(self): + r""" + Return the variable of ``self``. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(-7/2) + 3 + 5*x^(1/2) - 7*x**3 + sage: p.variable() + 'x' + """ + return self._parent.variable_name() + + def laurent_series(self): + r""" + If ``self`` is a Laurent series, return it as a Laurent series. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(ZZ) + sage: p = x**(1/2) - x**(-1/2) + sage: p.laurent_series() + Traceback (most recent call last): + ... + ArithmeticError: self is not a Laurent series + sage: q = p**2 + sage: q.laurent_series() + x^-1 - 2 + x + """ + if self._e != 1: + raise ArithmeticError('self is not a Laurent series') + return self._l + + def power_series(self): + r""" + If ``self`` is a power series, return it as a power series. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQbar) + sage: p = x**(3/2) - QQbar(I)*x**(1/2) + sage: p.power_series() + Traceback (most recent call last): + ... + ArithmeticError: self is not a power series + sage: q = p**2 + sage: q.power_series() + -x - 2*I*x^2 + x^3 + """ + try: + l = self.laurent_series() + return l.power_series() + except ArithmeticError: + raise ArithmeticError('self is not a power series') + + def inverse(self): + r""" + Return the inverse of ``self``. + + EXAMPLES:: + + sage: R. = PuiseuxSeriesRing(QQ) + sage: p = x^(-7/2) + 3 + 5*x^(1/2) - 7*x**3 + sage: 1/p + x^(7/2) - 3*x^7 - 5*x^(15/2) + 7*x^10 + 9*x^(21/2) + 30*x^11 + + 25*x^(23/2) + O(x^(27/2)) + """ + return self.__invert__() + diff --git a/src/sage/rings/ring.pyx b/src/sage/rings/ring.pyx index 0650103c2f0..c9dcae27e78 100644 --- a/src/sage/rings/ring.pyx +++ b/src/sage/rings/ring.pyx @@ -2476,7 +2476,7 @@ cdef class CommutativeAlgebra(CommutativeRing): """ Generic commutative algebra """ - def __init__(self, base_ring, names=None, normalize=True, category = None): + def __init__(self, base_ring, names=None, normalize=True, category=None): r""" Standard init function. This just checks that the base is a commutative ring and then passes the buck. diff --git a/src/sage/rings/ring_extension.pxd b/src/sage/rings/ring_extension.pxd new file mode 100644 index 00000000000..1607dd6fc2e --- /dev/null +++ b/src/sage/rings/ring_extension.pxd @@ -0,0 +1,41 @@ +from sage.categories.map cimport Map +from sage.rings.ring cimport CommutativeRing, CommutativeAlgebra +from sage.rings.ring_extension cimport RingExtension_generic + + +cdef class RingExtension_generic(CommutativeAlgebra): + cdef _type + cdef _backend + cdef _defining_morphism + cdef _backend_defining_morphism + cdef dict _print_options + cdef bint _import_methods + cdef bint _is_backend_exposed + # For division + cdef RingExtension_generic _fraction_field + cdef type _fraction_field_type + + cpdef is_defined_over(self, base) + cpdef CommutativeRing _check_base(self, CommutativeRing base) + cpdef _degree_over(self, CommutativeRing base) + cpdef _is_finite_over(self, CommutativeRing base) + cpdef _is_free_over(self, CommutativeRing base) + cdef Map _defining_morphism_fraction_field(self, bint extend_base) + + +cdef class RingExtensionFractionField(RingExtension_generic): + cdef _ring + + +cdef class RingExtensionWithBasis(RingExtension_generic): + cdef _basis + cdef _basis_names + cdef _basis_latex_names + + cpdef _basis_over(self, CommutativeRing base) + # cpdef _free_module(self, CommutativeRing base, bint map) + + +cdef class RingExtensionWithGen(RingExtensionWithBasis): + cdef _gen + cdef _name diff --git a/src/sage/rings/ring_extension.pyx b/src/sage/rings/ring_extension.pyx new file mode 100644 index 00000000000..754b8c2ca63 --- /dev/null +++ b/src/sage/rings/ring_extension.pyx @@ -0,0 +1,2688 @@ +r""" +Extension of rings + +Sage offers the possibility to work with ring extensions `L/K` as +actual parents and perform meaningful operations on them and their +elements. + +The simplest way to build an extension is to use the method +:meth:`sage.categories.commutative_rings.CommutativeRings.ParentMethods.over` on the top ring, +that is `L`. +For example, the following line constructs the extension of +finite fields `\mathbf{F}_{5^4}/\mathbf{F}_{5^2}`:: + + sage: GF(5^4).over(GF(5^2)) + Field in z4 with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base + +By default, Sage reuses the canonical generator of the top ring +(here `z_4 \in \mathbf{F}_{5^4}`), together with its name. However, +the user can customize them by passing in appropriate arguments:: + + sage: F = GF(5^2) + sage: k = GF(5^4) + sage: z4 = k.gen() + sage: K. = k.over(F, gen = 1-z4) + sage: K + Field in a with defining polynomial x^2 + z2*x + 4 over its base + +The base of the extension is available via the method :meth:`base` (or +equivalently :meth:`base_ring`):: + + sage: K.base() + Finite Field in z2 of size 5^2 + +It is also possible to build an extension on top of another extension, +obtaining this way a tower of extensions:: + + sage: L. = GF(5^8).over(K) + sage: L + Field in b with defining polynomial x^2 + (4*z2 + 3*a)*x + 1 - a over its base + sage: L.base() + Field in a with defining polynomial x^2 + z2*x + 4 over its base + sage: L.base().base() + Finite Field in z2 of size 5^2 + +The method :meth:`bases` gives access to the complete list of rings in +a tower:: + + sage: L.bases() + [Field in b with defining polynomial x^2 + (4*z2 + 3*a)*x + 1 - a over its base, + Field in a with defining polynomial x^2 + z2*x + 4 over its base, + Finite Field in z2 of size 5^2] + +Once we have constructed an extension (or a tower of extensions), we +have interesting methods attached to it. As a basic example, one can +compute a basis of the top ring over any base in the tower:: + + sage: L.basis_over(K) + [1, b] + sage: L.basis_over(F) + [1, a, b, a*b] + +When the base is omitted, the default is the natural base of the extension:: + + sage: L.basis_over() + [1, b] + +The method :meth:`sage.rings.ring_extension_element.RingExtensionWithBasis.vector` +computes the coordinates of an element according to the above basis:: + + sage: u = a + 2*b + 3*a*b + sage: u.vector() # over K + (a, 2 + 3*a) + sage: u.vector(F) + (0, 1, 2, 3) + +One can also compute traces and norms with respect to any base of the tower:: + + sage: u.trace() # over K + (2*z2 + 1) + (2*z2 + 1)*a + sage: u.trace(F) + z2 + 1 + sage: u.trace().trace() # over K, then over F + z2 + 1 + + sage: u.norm() # over K + (z2 + 1) + (4*z2 + 2)*a + sage: u.norm(F) + 2*z2 + 2 + +And minimal polynomials:: + + sage: u.minpoly() + x^2 + ((3*z2 + 4) + (3*z2 + 4)*a)*x + (z2 + 1) + (4*z2 + 2)*a + sage: u.minpoly(F) + x^4 + (4*z2 + 4)*x^3 + x^2 + (z2 + 1)*x + 2*z2 + 2 + + +AUTHOR: + +- Xavier Caruso (2019) +""" + +############################################################################# +# Copyright (C) 2019 Xavier Caruso +# +# This program is free softwGare: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#**************************************************************************** + + +from sage.misc.fast_methods cimport hash_by_id +from sage.misc.cachefunc import cached_method +from sage.cpython.getattr cimport AttributeErrorMessage +from sage.cpython.getattr import dir_with_other_class +from sage.misc.latex import latex, latex_variable_name + +from sage.structure.factory import UniqueFactory +from sage.structure.parent cimport Parent +from sage.structure.element cimport Element +from sage.structure.category_object import normalize_names +from sage.categories.map cimport Map +from sage.categories.commutative_rings import CommutativeRings +from sage.categories.commutative_algebras import CommutativeAlgebras +from sage.categories.fields import Fields +from sage.rings.ring cimport CommutativeRing, CommutativeAlgebra +from sage.rings.integer_ring import ZZ +from sage.rings.infinity import Infinity + +from sage.rings.ring_extension_element cimport ( + RingExtensionElement, RingExtensionFractionFieldElement, RingExtensionWithBasisElement) +from sage.rings.ring_extension_morphism cimport ( + RingExtensionHomomorphism, RingExtensionBackendIsomorphism, RingExtensionBackendReverseIsomorphism, + are_equal_morphisms, MapFreeModuleToRelativeRing, MapRelativeRingToFreeModule) +from sage.rings.ring_extension_conversion cimport ( + backend_parent, backend_morphism, to_backend, from_backend) + + +# Helper functions +################## + +def tower_bases(ring, degree): + r""" + Return the list of bases of ``ring`` (including itself); if + degree is ``True``, restrict to finite extensions and return + in addition the degree of ``ring`` over each base. + + INPUT: + + - ``ring`` -- a commutative ring + + - ``degree`` -- a boolean + + EXAMPLES:: + + sage: from sage.rings.ring_extension import tower_bases + sage: S. = QQ[] + sage: T. = S[] + sage: tower_bases(T, False) + ([Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field, + Univariate Polynomial Ring in x over Rational Field, + Rational Field], + []) + sage: tower_bases(T, True) + ([Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Rational Field], + [1]) + + sage: K. = Qq(5^2) + sage: L. = K.extension(x^3 - 5) + sage: tower_bases(L, True) + ([5-adic Eisenstein Extension Field in w defined by x^3 - 5 over its base field, + 5-adic Unramified Extension Field in a defined by x^2 + 4*x + 2, + 5-adic Field with capped relative precision 20], + [1, 3, 6]) + """ + bases = [ ] + degrees = [ ] + base = ring + deg = 1 + while True: + bases.append(base) + if degree: + degrees.append(deg) + try: + d = base.relative_degree() + except AttributeError: + try: + d = base.degree() + except AttributeError: + break + if d is Infinity: break + deg *= d + newbase = base._base + if newbase is base: break + base = newbase + return bases, degrees + + +def common_base(K, L, degree): + """ + Return a common base on which ``K`` and ``L`` are defined. + + INPUT: + + - ``K`` -- a commutative ring + + - ``L`` -- a commutative ring + + - ``degree`` -- a boolean; if true, return the degree of + ``K`` and ``L`` over their common base + + EXAMPLES:: + + sage: from sage.rings.ring_extension import common_base + + sage: common_base(GF(5^3), GF(5^7), False) + Finite Field of size 5 + sage: common_base(GF(5^3), GF(5^7), True) + (Finite Field of size 5, 3, 7) + + sage: common_base(GF(5^3), GF(7^5), False) + Traceback (most recent call last): + ... + NotImplementedError: unable to find a common base + + When ``degree`` is set to ``True``, we only look up for bases on + which both ``K`` and ``L`` are finite:: + + sage: S. = QQ[] + sage: common_base(S, QQ, False) + Rational Field + sage: common_base(S, QQ, True) + Traceback (most recent call last): + ... + NotImplementedError: unable to find a common base + + """ + bases_K, degrees_K = tower_bases(K, degree) + bases_L, degrees_L = tower_bases(L, degree) + base = None + for iL in range(len(bases_L)): + try: + iK = bases_K.index(bases_L[iL]) + base = bases_L[iL] + break + except ValueError: + pass + if base is None: + raise NotImplementedError("unable to find a common base") + if degree: + return base, degrees_K[iK], degrees_L[iL] + else: + return base + + +def generators(ring, base): + r""" + Return the generators of ``ring`` over ``base``. + + INPUT: + + - ``ring`` -- a commutative ring + + - ``base`` -- a commutative ring + + EXAMPLES:: + + sage: from sage.rings.ring_extension import generators + sage: S. = QQ[] + sage: T. = S[] + + sage: generators(T, S) + (y,) + sage: generators(T, QQ) + (y, x) + """ + gens = tuple() + while ring is not ring.base_ring() and (base is None or not base.has_coerce_map_from(ring)): + gens += tuple(ring.gens()) + ring = ring.base_ring() + if base is None: + return gens + else: + return tuple([x for x in gens if x not in base]) + + +def variable_names(ring, base): + r""" + Return the variable names of the generators of ``ring`` + over ``base``. + + INPUT: + + - ``ring`` -- a commutative ring + + - ``base`` -- a commutative ring + + EXAMPLES:: + + sage: from sage.rings.ring_extension import variable_names + sage: S. = QQ[] + sage: T. = S[] + + sage: variable_names(T, S) + ('y',) + sage: variable_names(T, QQ) + ('y', 'x') + """ + names = tuple() + while ring is not ring.base_ring() and (base is None or not base.has_coerce_map_from(ring)): + gens = ring.gens() + vars = ring.variable_names() + if len(gens) != len(vars): + raise NotImplementedError("cannot figure out the variable names") + if base is None: + names += tuple(vars) + else: + for gen, var in zip(gens, vars): + if gen not in base: + names += (var,) + ring = ring.base_ring() + return names + + +# Factory +######### + +class RingExtensionFactory(UniqueFactory): + """ + Factory for ring extensions. + + TESTS:: + + sage: E = QQ.over(ZZ) + sage: QQ.over(ZZ) is E + True + + sage: K. = QQ.extension(x^2 - 2) + sage: E = K.over(QQ) + sage: E + Field in a with defining polynomial x^2 - 2 over its base + + sage: E2. = K.over(QQ) + sage: E2 is E + False + """ + def create_key_and_extra_args(self, ring, defining_morphism=None, gens=None, names=None, constructors=None): + """ + Create a key and return it together with a list of constructors + of the object. + + INPUT: + + - ``ring`` -- a commutative ring + + - ``defining_morphism`` -- a ring homomorphism or a commutative + ring or ``None`` (default: ``None``); the defining morphism of + this extension or its base (if it coerces to ``ring``) + + - ``gens`` -- a list of generators of this extension (over its base) + or ``None`` (default: ``None``); + + - ``names`` -- a list or a tuple of variable names or ``None`` + (default: ``None``) + + - ``constructors`` -- a list of constructors; each constructor + is a pair `(class, arguments)` where `class` is the class + implementing the extension and `arguments` is the dictionary + of arguments to pass in to init function + + TESTS:: + + sage: from sage.rings.ring_extension import RingExtension + sage: RingExtension.create_key_and_extra_args(QQ, ZZ) + ((Ring morphism: + From: Integer Ring + To: Rational Field + Defn: 1 |--> 1, (), ()), + {'constructors': [(, + {'is_backend_exposed': True, + 'print_options': {'print_elements_as': None, 'print_parent_as': None}})]}) + + sage: RingExtension.create_key_and_extra_args(GF(5^4), GF(5^2), names=('a',)) + ((Ring morphism: + From: Finite Field in z2 of size 5^2 + To: Finite Field in z4 of size 5^4 + Defn: z2 |--> z4^3 + z4^2 + z4 + 3, (z4,), ('a',)), + {'constructors': [(, + {'gen': z4, 'is_backend_exposed': True, 'names': ('a',)})]}) + """ + use_generic_constructor = True + is_backend_exposed = True + print_as = None + + if defining_morphism is None: + base = ring.base_ring() + elif isinstance(defining_morphism, Map): + base = defining_morphism.domain() + elif defining_morphism in CommutativeRings(): + base = defining_morphism + defining_morphism = None + else: + raise TypeError("only commutative rings are accepted") + + # We compute the defining morphism + if defining_morphism is None: + if isinstance(base, RingExtension_generic): + backend_base = (base)._backend + if ring.has_coerce_map_from(backend_base): + defining_morphism = RingExtensionHomomorphism(base.Hom(ring), ring.coerce_map_from(backend_base)) + else: + if ring.has_coerce_map_from(base): + defining_morphism = ring.coerce_map_from(base) + if defining_morphism is None: + raise ValueError("No coercion map from %s to %s" % (base,ring)) + else: + if defining_morphism.domain() is not base: + defining_morphism = defining_morphism.extend_domain(base) + if defining_morphism.codomain() is not ring: + defining_morphism = defining_morphism.extend_codomain(ring) + if not isinstance(defining_morphism, RingExtensionHomomorphism): + defining_morphism = RingExtensionHomomorphism(defining_morphism.parent(), defining_morphism) + if isinstance(ring, RingExtension_generic): + defining_morphism = backend_morphism(defining_morphism, forget="codomain") + if (ring)._is_backend_exposed: + print_as = (ring)._backend + else: + use_generic_constructor = False + is_backend_exposed = False + + # We normalize other attributes + if gens is not None: + if not isinstance(gens, (list, tuple)): + raise TypeError("gens must be a list or a tuple") + gens = tuple(ring(g) for g in gens ) + if names is None: + raise TypeError("you must specify the names of the generators") + names = normalize_names(len(gens), names) + use_generic_constructor = False + else: + gens = generators(ring, base) + if names is None: + try: + names = variable_names(ring, base) + except NotImplementedError: + gens = names = None + else: + names = normalize_names(len(gens), names) + use_generic_constructor = False + + # We figure out what are the best constructors + if constructors is None: + constructors = [ ] + if gens is not None and len(gens) == 1: + constructors.append((RingExtensionWithGen, + {'gen': gens[0], 'names': names, + 'is_backend_exposed': is_backend_exposed})) + if use_generic_constructor: + constructors.append((RingExtension_generic, + {'print_options': {'print_parent_as': print_as, + 'print_elements_as': print_as}, + 'is_backend_exposed': is_backend_exposed})) + + # We build the key and return it + return (defining_morphism, gens, names), {'constructors': constructors} + + def create_object(self, version, key, **extra_args): + """ + Return the object associated to a given key. + + TESTS:: + + sage: from sage.rings.ring_extension import RingExtension + sage: key, extra_args = RingExtension.create_key_and_extra_args(QQ, ZZ) + sage: RingExtension.create_object((8,9,0), key, **extra_args) + Rational Field over its base + """ + defining_morphism, gens, names = key + constructors = extra_args['constructors'] + if len(constructors) == 0: + raise NotImplementedError("no constructor available for this extension") + for (constructor, kwargs) in constructors[:-1]: + try: + return constructor(defining_morphism, **kwargs) + except (NotImplementedError, ValueError, TypeError): + pass + (constructor, kwargs) = constructors[-1] + return constructor(defining_morphism, **kwargs) + + +RingExtension = RingExtensionFactory("sage.rings.ring_extension.RingExtension") + + +# General extensions +#################### + +cdef class RingExtension_generic(CommutativeAlgebra): + r""" + A generic class for all ring extensions. + + TESTS:: + + sage: Q = QQ.over(ZZ) # indirect doctest + sage: Q + Rational Field over its base + + sage: type(Q) + + + sage: TestSuite(Q).run() + + """ + Element = RingExtensionElement + + def __init__(self, defining_morphism, print_options={}, import_methods=True, is_backend_exposed=False, category=None): + r""" + Initialize this ring extension. + + INPUT: + + - ``defining_morphism`` -- a ring homomorphism + + - ``print_options`` -- a dictionary + + - ``import_methods`` -- a boolean (default: ``True``); whether this + parent (resp. its elements) import the methods of the backend + parent class (resp. element class) + + - ``is_backend_exposed`` -- a boolean (default: ``False``); whether + the backend ring can be exposed to the user + + - ``category`` -- the category for the resulting parent + (default: ``CommutativeRings()``) + + .. NOTE: + + The attribute `is_backend_exposed` is only used for printing; + when it is ``False``, printing an element like its backend is + disabled (and a ``RuntimeError`` is raised when it would occur). + + OUTPUT: + + The extension defined by ``defining_morphism`` + + EXAMPLES:: + + sage: QQ.over(ZZ) + Rational Field over its base + + sage: S. = QQ[] + sage: S.over() # over QQ + Univariate Polynomial Ring in x over Rational Field over its base + + TESTS:: + + sage: ZZ.over(NN) + Traceback (most recent call last): + ... + TypeError: only commutative rings are accepted + + sage: K = GF(5^3) + sage: K.over(K.frobenius_endomorphism()) + Traceback (most recent call last): + ... + ValueError: exotic defining morphism between two rings in the tower; consider using another variable name + + """ + cdef CommutativeRing base, ring + cdef CommutativeRing b, backend + cdef Map f + + base = defining_morphism.domain() + ring = defining_morphism.codomain() + if category is None: + # Another option would be to set category = CommutativeAlgebras(base) + # but CommutativeRings() seems safer, especially when dealing with + # morphisms which do not need to preserve the base + category = CommutativeRings() + CommutativeAlgebra.__init__(self, ZZ, category=category) + self._base = base + self._backend = ring + self._backend_defining_morphism = defining_morphism + self._defining_morphism = RingExtensionHomomorphism(self._base.Hom(self), defining_morphism) + self._print_options = print_options.copy() + if 'over' not in self._print_options: + self._print_options['over'] = ZZ(0) + self._import_methods = import_methods + self._is_backend_exposed = is_backend_exposed + self._type = "Ring" + if self._backend in Fields(): + self._type = "Field" + + # Some checks + if (base not in CommutativeRings() + or ring not in CommutativeRings() + or not defining_morphism.category_for().is_subcategory(CommutativeRings())): + raise TypeError("only commutative rings are accepted") + f = ring.Hom(ring).identity() + b = self + while isinstance(b, RingExtension_generic): + f *= backend_morphism((b)._backend_defining_morphism) + b = b._base + if isinstance(b, RingExtension_generic): + backend = (b)._backend + else: + backend = b + if ring.has_coerce_map_from(backend) and not are_equal_morphisms(f, None): + # TODO: find a better message + raise ValueError("exotic defining morphism between two rings in the tower; consider using another variable name") + + # We register coercion/conversion maps + self.register_coercion(self._defining_morphism.__copy__()) + self.register_coercion(RingExtensionBackendIsomorphism(ring.Hom(self))) + ring.register_conversion(RingExtensionBackendReverseIsomorphism(self.Hom(ring))) + + def __getattr__(self, name): + """ + If this extension was created with ``import_methods = True``, + return a wrapper to the corresponding method of the backend + parent (if it exists). + + EXAMPLES:: + + sage: K. = QQ.extension(x^2 - 2) + sage: E = K.over() # over QQ + + sage: hasattr(E, 'automorphisms') + True + sage: E.automorphisms() + [Ring endomorphism of Field in a with defining polynomial x^2 - 2 over its base + Defn: a |--> a, + Ring endomorphism of Field in a with defining polynomial x^2 - 2 over its base + Defn: a |--> -a] + """ + try: + return self.getattr_from_category(name) + except AttributeError: + pass + method = None + if self._import_methods and hasattr(self._backend, name): + method = getattr(self._backend, name) + if not callable(method): + raise AttributeError(AttributeErrorMessage(self, name)) + def wrapper(*args, **kwargs): + output = method(*to_backend(args), **to_backend(kwargs)) + return from_backend(output, self) + wrapper.__doc__ = method.__doc__ + return wrapper + + def __dir__(self): + """ + Return the list of all the attributes of this extension; + if the extension was created with ``import_methods = True``, + concatenate this list with the list of all the methods of + the backend parent. + + EXAMPLES:: + + sage: A. = QQ.extension(x^2 - 2) + sage: K. = A.over() + + sage: dir(K) + ['CartesianProduct', + 'Element', + 'Hom', + ... + 'zeta', + 'zeta_coefficients', + 'zeta_function', + 'zeta_order'] + """ + d = dir_with_other_class(self, self.category().parent_class) + if not self._import_methods: + return d + for name in dir(self._backend): + if name[0] == "_": continue + try: + attribute = getattr(self._backend, name) + if callable(attribute): + d.append(name) + except Exception: + pass + return sorted(set(d)) + + def __hash__(self): + """ + Return a hash of this extension. + + EXAMPLES: + + sage: E = GF(5^3).over() + sage: hash(E) # random + 140257667982632 + """ + return hash_by_id(self) + + def __reduce__(self): + """ + Return a tuple of a function and data that can be used to unpickle this + extension. + + TESTS:: + + sage: K = GF(7^3).over() + sage: type(K) + + sage: loads(dumps(K)) is K + True + """ + (defining_morphism, gens, names) = self._factory_data[2] + constructors = self._factory_data[3]['constructors'] + return RingExtension, (self._backend, defining_morphism, gens, names, constructors) + + def construction(self): + """ + Return the functorial construction of this extension, if defined. + + EXAMPLES:: + + sage: E = GF(5^3).over() + sage: E.construction() + + """ + # One could define a construction functor K' -> K' otimes_K L, but we leave this to another ticket + pass + + def from_base_ring(self, r): + r""" + Return the canonical embedding of ``r`` into this extension. + + INPUT: + + - ``r`` -- an element of the base of the ring of this extension + + EXAMPLES:: + + sage: k = GF(5) + sage: K. = GF(5^2).over(k) + sage: L. = GF(5^4).over(K) + + sage: x = L.from_base_ring(k(2)); x + 2 + sage: x.parent() + Field in v with defining polynomial x^2 + (3 - u)*x + u over its base + + sage: x = L.from_base_ring(u); x + u + sage: x.parent() + Field in v with defining polynomial x^2 + (3 - u)*x + u over its base + """ + if r not in self._base: + raise TypeError("%s is not an element of the base of %s (= %s)" % (r, self._backend, self._base)) + return self.element_class(self, r) + + def print_options(self, **options): + """ + Update the printing options of this extension. + + INPUT: + + - ``over`` -- an integer or ``Infinity`` (default: ``0``); the maximum + number of bases included in the printing of this extension + + - ``base`` -- a base over which this extension is finite free; + elements in this extension will be printed as a linear + combinaison of a basis of this extension over the given base + + EXAMPLES:: + + sage: A. = GF(5^2).over() # over GF(5) + sage: B. = GF(5^4).over(A) + sage: C. = GF(5^12).over(B) + sage: D. = GF(5^24).over(C) + + Observe what happens when we modify the option ``over``:: + + sage: D + Field in d with defining polynomial x^2 + ((1 - a) + ((1 + 2*a) - b)*c + ((2 + a) + (1 - a)*b)*c^2)*x + c over its base + + sage: D.print_options(over=2) + sage: D + Field in d with defining polynomial x^2 + ((1 - a) + ((1 + 2*a) - b)*c + ((2 + a) + (1 - a)*b)*c^2)*x + c over + Field in c with defining polynomial x^3 + (1 + (2 - a)*b)*x^2 + (2 + 2*b)*x - b over + Field in b with defining polynomial x^2 + (3 - a)*x + a over its base + + sage: D.print_options(over=Infinity) + sage: D + Field in d with defining polynomial x^2 + ((1 - a) + ((1 + 2*a) - b)*c + ((2 + a) + (1 - a)*b)*c^2)*x + c over + Field in c with defining polynomial x^3 + (1 + (2 - a)*b)*x^2 + (2 + 2*b)*x - b over + Field in b with defining polynomial x^2 + (3 - a)*x + a over + Field in a with defining polynomial x^2 + 4*x + 2 over + Finite Field of size 5 + + Now the option ``base``:: + + sage: d^2 + -c + ((-1 + a) + ((-1 + 3*a) + b)*c + ((3 - a) + (-1 + a)*b)*c^2)*d + + sage: D.basis_over(B) + [1, c, c^2, d, c*d, c^2*d] + sage: D.print_options(base=B) + sage: d^2 + -c + (-1 + a)*d + ((-1 + 3*a) + b)*c*d + ((3 - a) + (-1 + a)*b)*c^2*d + + sage: D.basis_over(A) + [1, b, c, b*c, c^2, b*c^2, d, b*d, c*d, b*c*d, c^2*d, b*c^2*d] + sage: D.print_options(base=A) + sage: d^2 + -c + (-1 + a)*d + (-1 + 3*a)*c*d + b*c*d + (3 - a)*c^2*d + (-1 + a)*b*c^2*d + """ + for (name, value) in options.items(): + method = None + if hasattr(self, '_print_option_' + name): + method = getattr(self, '_print_option_' + name) + if not callable(method): + raise ValueError("option '%s' does not exist" % name) + self._print_options[name] = method(value) + + def _print_option_over(self, over): + """ + Check and normalize the print option ``over`` + + INPUT: + + - ``over`` -- an integer or ``Infinity`` + + OUTPUT: + + The normalized value of ``over`` + + TESTS:: + + sage: E = QQ.over(ZZ) + sage: E.print_options(over=-2) + Traceback (most recent call last): + ... + ValueError: 'over' must be nonnegative + + sage: E.print_options(over=ZZ) + Traceback (most recent call last): + ... + TypeError: unable to coerce to an integer + """ + if over is not None and over is not Infinity: + over = ZZ(over) + if over < 0: + raise ValueError("'over' must be nonnegative") + return over + + def _repr_(self, **options): + r""" + Return a string representation of this extension. + + INPUT: + + - ``over`` -- an integer, ``Infinity`` or ``None``; the maximum + number of bases included in the print representation of + this extension; + if ``None``, use the print options of this extension. + + EXAMPLES:: + + sage: E = QQ.over(ZZ) + sage: E + Rational Field over its base + + sage: E._repr_() + 'Rational Field over its base' + sage: E._repr_(over=Infinity) + 'Rational Field over Integer Ring' + """ + if 'print_parent_as' in options: + print_as = options.pop('print_parent_as') + else: + print_as = self._print_options.get('print_parent_as') + if print_as is not None: + if isinstance(print_as, RingExtension_generic): + return print_as._repr_(**options) + else: + return str(print_as) + print_options = self._print_options.copy() + for (name, value) in options.items(): + method = None + if hasattr(self, '_print_option_' + name): + method = getattr(self, '_print_option_' + name) + if not callable(method): + raise ValueError("option '%s' does not exist" % name) + print_options[name] = method(value) + over = print_options.pop('over', None) + s = self._repr_topring(**print_options) + if over is None or over == 0: + s += " over its base" + else: + s += " over " + base = self._base + if isinstance(base, RingExtension_generic): + s += base._repr_(over=over-1) + else: + s += str(base) + return s + + def _repr_topring(self, **options): + r""" + Return a string representation of top ring of this extension. + + EXAMPLES:: + + sage: E = QQ.over(ZZ) + sage: E._repr_topring() + 'Rational Field' + """ + if not self._is_backend_exposed: + raise RuntimeError("backend is not exposed to the user; cannot print") + return str(self._backend) + + def _latex_(self, **options): + r""" + Return a LaTeX representation of this extension. + + - ``over`` -- an integer, ``Infinity`` or ``None``; the maximum + number of bases included in the LaTeX representation of + this extension; + if ``None``, use the print options of this extension. + + EXAMPLES:: + + sage: E = QQ.over(ZZ) + sage: latex(E) + \Bold{Q} + + sage: E._latex_() + \Bold{Q} + sage: E._latex_(over=Infinity) + \Bold{Q} / \Bold{Z} + """ + if 'print_parent_as' in options: + print_as = options.pop('print_parent_as') + else: + print_as = self._print_options.get('print_parent_as') + if print_as is not None: + if isinstance(print_as, RingExtension_generic): + return print_as._latex_(**options) + else: + return latex(print_as) + print_options = self._print_options.copy() + for (name, value) in options.items(): + method = None + if hasattr(self, '_print_option_' + name): + method = getattr(self, '_print_option_' + name) + if not callable(method): + raise ValueError("option '%s' does not exist" % name) + print_options[name] = method(value) + over = print_options.pop('over', None) + s = self._latex_topring(**print_options) + if over > 0: + s += " / " + base = self._base + if isinstance(base, RingExtension_generic): + s += base._latex_(over=over-1) + else: + s += latex(base) + return s + + def _latex_topring(self, **options): + r""" + Return a string representation of top ring of this extension. + + EXAMPLES:: + + sage: E = QQ.over(ZZ) + sage: E._latex_topring() + \Bold{Q} + """ + if not self._is_backend_exposed: + raise RuntimeError("backend is not exposed to the user; cannot print") + return latex(self._backend) + + cpdef _coerce_map_from_(self, other): + r""" + Return a coerce map from this extension to ``other`` if defined. + + COERCION MODEL + + If `L/K` is an extension, a coercion map `K \to (L/K)` + (acting through the defining morphism of `L/K`) is set. + + If ``L_1/K_1` and `L_2/K_2` are two extensions, a coercion + map `(L_1/K_1) \to (L_2/K_2)`` is set when `L_1` coerces to + `L_2` and `K_1` coerces to `K_2` in such a way that the + appropriate diagram commutes. + + These rules have the following consequence regarding iterated + extensions. + Given two iterated extensions `A = (A_n/\cdots/A_2/A_1)` and + `B = (B_m/\cdots/B_2/B_1)`, there is a coercion map `A \to B` + if there exists a strictly increasing function + `sigma : \{1,\ldots,n\} \to \{1,\ldots,m\}` and coercion maps + `A_i \to B_{\sigma(i)}` making all the appropriate diagrams + commutative. + + .. NOTE:: + + In order to avoid discrepancies, it is forbidden to create + an extension with exotic defining morphisms: + if (A_n/\cdots/A_2/A_1) is an iterated extension and `i + \leq j` are two indices such that `A_i` coerces to `A_j`, + then the composition defining morphism `A_i \to A_{i+1} + \to \cdots \to A_j` must agree with the coercion map. + + TESTS:: + + sage: E1 = GF(3^6).over(GF(3^3)) + sage: E1.coerce_map_from(GF(3^3)) # indirect doctest + Ring morphism: + From: Finite Field in z3 of size 3^3 + To: Field in z6 with defining polynomial x^2 + (2*z3 + 1)*x + z3 over its base + Defn: z3 |--> z3 + + sage: E1.coerce_map_from(GF(3)) # indirect doctest + Ring morphism: + From: Finite Field of size 3 + To: Field in z6 with defining polynomial x^2 + (2*z3 + 1)*x + z3 over its base + Defn: 1 |--> 1 + + sage: E2 = GF(3^18).over(GF(3^9)) + sage: E2.coerce_map_from(E1) # indirect doctest + Ring morphism: + From: Field in z6 with defining polynomial x^2 + (2*z3 + 1)*x + z3 over its base + To: Field in z18 with defining polynomial x^2 + (z9^8 + 2*z9^7 + z9^5 + 2*z9^4 + z9^2 + z9 + 1)*x + z9 over its base + Defn: z6 |--> (2*z9^7 + z9^6 + 2*z9^2 + 2*z9) + (z9^8 + 2*z9^7 + 2*z9^6 + z9^5 + z9^3 + z9 + 1)*z18 + + A test with iterated extensions:: + + sage: A = GF(3^18).over(GF(3^3)) # simple extension GF(3^3) -> GF(3^18) + sage: B = GF(3^18).over(E1) # iterated extension GF(3^3) -> GF(3^6) -> GF(3^18) + sage: A.has_coerce_map_from(B) + False + sage: B.has_coerce_map_from(A) + True + + """ + cdef RingExtension_generic right + if isinstance(other, RingExtension_generic): + right = other + if self._backend.has_coerce_map_from(right._backend) and self._base.has_coerce_map_from(right._base): + backend = self._backend.coerce_map_from(right._backend) + f = backend * backend_morphism(right._defining_morphism) + g = backend_morphism(self._defining_morphism * self._base.coerce_map_from(right._base)) + if are_equal_morphisms(f, g): + return RingExtensionHomomorphism(right.Hom(self), backend) + + def base(self): + r""" + Return the base of this extension. + + EXAMPLES:: + + sage: F = GF(5^2) + sage: K = GF(5^4).over(F) + sage: K.base() + Finite Field in z2 of size 5^2 + + In case of iterated extensions, the base is itself an extension:: + + sage: L = GF(5^8).over(K) + sage: L.base() + Field in z4 with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base + sage: L.base() is K + True + + .. SEEALSO:: + + :meth:`bases`, :meth:`absolute_base`, :meth:`is_defined_over` + """ + return self._base + + def bases(self): + r""" + Return the list of successive bases of this extension + (including itself). + + EXAMPLES:: + + sage: F = GF(5^2).over() # over GF(5) + sage: K = GF(5^4).over(F) + sage: L = GF(5^12).over(K) + + sage: F.bases() + [Field in z2 with defining polynomial x^2 + 4*x + 2 over its base, + Finite Field of size 5] + + sage: K.bases() + [Field in z4 with defining polynomial x^2 + (3 - z2)*x + z2 over its base, + Field in z2 with defining polynomial x^2 + 4*x + 2 over its base, + Finite Field of size 5] + + sage: L.bases() + [Field in z12 with defining polynomial x^3 + (1 + (2 - z2)*z4)*x^2 + (2 + 2*z4)*x - z4 over its base, + Field in z4 with defining polynomial x^2 + (3 - z2)*x + z2 over its base, + Field in z2 with defining polynomial x^2 + 4*x + 2 over its base, + Finite Field of size 5] + + .. SEEALSO:: + + :meth:`base`, :meth:`absolute_base`, :meth:`is_defined_over` + """ + L = [ self ] + base = self + while isinstance(base, RingExtension_generic): + base = base.base_ring() + L.append(base) + return L + + def absolute_base(self): + r""" + Return the absolute base of this extension. + + By definition, the absolute base of an iterated extension + `K_n/\cdots K_2/K_1` is the ring `K_1`. + + EXAMPLES:: + + sage: F = GF(5^2).over() # over GF(5) + sage: K = GF(5^4).over(F) + sage: L = GF(5^12).over(K) + + sage: F.absolute_base() + Finite Field of size 5 + sage: K.absolute_base() + Finite Field of size 5 + sage: L.absolute_base() + Finite Field of size 5 + + .. SEEALSO:: + + :meth:`base`, :meth:`bases`, :meth:`is_defined_over` + """ + return self.bases()[-1] + + cpdef is_defined_over(self, base): + r""" + Return whether or not ``base`` is one of the bases of this + extension. + + INPUT: + + - ``base`` -- a commutative ring, which might be itself an + extension + + EXAMPLES:: + + sage: A = GF(5^4).over(GF(5^2)) + sage: B = GF(5^12).over(A) + + sage: A.is_defined_over(GF(5^2)) + True + sage: A.is_defined_over(GF(5)) + False + + sage: B.is_defined_over(A) + True + sage: B.is_defined_over(GF(5^4)) + True + sage: B.is_defined_over(GF(5^2)) + True + sage: B.is_defined_over(GF(5)) + False + + Note that an extension is defined over itself:: + + sage: A.is_defined_over(A) + True + sage: A.is_defined_over(GF(5^4)) + True + + .. SEEALSO:: + + !meth:`base`, :meth:`bases`, :meth:`absolute_base` + """ + cdef CommutativeRing b + b = self + while isinstance(b, RingExtension_generic): + if b is base or (b)._backend is base: return True + b = (b)._base + return b is base + + cpdef CommutativeRing _check_base(self, CommutativeRing base): + r""" + Check if ``base`` is one of the successive bases of this + extension and, if it is, normalize it. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` + + OUTPUT: + + The base ``base`` normalized as a parent appearing in the + list of bases of this extension as returned by :meth:`bases`. + + EXAMPLES:: + + sage: F = GF(5^2) + sage: K = GF(5^4).over(F) + sage: L = GF(5^12).over(K) + sage: L.bases() + [Field in z12 with defining polynomial x^3 + (1 + (4*z2 + 2)*z4)*x^2 + (2 + 2*z4)*x - z4 over its base, + Field in z4 with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base, + Finite Field in z2 of size 5^2] + + sage: L._check_base(K) + Field in z4 with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base + sage: L._check_base(GF(5^4)) + Field in z4 with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base + sage: L._check_base(GF(5^4)) is K + True + + When ``base`` is ``None``, the base of the extension is returned:: + + sage: L._check_base(None) + Field in z4 with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base + sage: L._check_base(None) is L.base() + True + + """ + cdef CommutativeRing b + if base is None: + return self._base + b = self + while isinstance(b, RingExtension_generic): + if b is base or (b)._backend is base: return b + b = (b)._base + if b is base: + return b + raise ValueError("not (explicitly) defined over %s" % base) + + def defining_morphism(self, base=None): + r""" + Return the defining morphism of this extension over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` (default: ``None``) + + EXAMPLES:: + + sage: F = GF(5^2) + sage: K = GF(5^4).over(F) + sage: L = GF(5^12).over(K) + + sage: K.defining_morphism() + Ring morphism: + From: Finite Field in z2 of size 5^2 + To: Field in z4 with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base + Defn: z2 |--> z2 + + sage: L.defining_morphism() + Ring morphism: + From: Field in z4 with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base + To: Field in z12 with defining polynomial x^3 + (1 + (4*z2 + 2)*z4)*x^2 + (2 + 2*z4)*x - z4 over its base + Defn: z4 |--> z4 + + One can also pass in a base over which the extension is explicitly + defined (see also :meth:`is_defined_over`):: + + sage: L.defining_morphism(F) + Ring morphism: + From: Finite Field in z2 of size 5^2 + To: Field in z12 with defining polynomial x^3 + (1 + (4*z2 + 2)*z4)*x^2 + (2 + 2*z4)*x - z4 over its base + Defn: z2 |--> z2 + + sage: L.defining_morphism(GF(5)) + Traceback (most recent call last): + ... + ValueError: not (explicitly) defined over Finite Field of size 5 + """ + base = self._check_base(base) + return self.coerce_map_from(base) + + def _an_element_(self): + r""" + Return an element of this extension. + + TESTS:: + + sage: E = QQ.over(ZZ) + sage: x = E.an_element() # indirect doctest + sage: x + 1/2 + sage: x.parent() + Rational Field over its base + + """ + elt = self._backend.an_element() + return self.element_class(self, elt) + + def gens(self, base=None): + r""" + Return the generators of this extension over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` (default: ``None``); if omitted, + use the base of this extension + + EXAMPLES:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: K.gens() + (a,) + sage: L. = GF(5^4).over(K) + sage: L.gens() + (b,) + sage: L.gens(GF(5)) + (b, a) + + sage: S. = QQ[] + sage: T. = S[] + sage: T.over(S).gens() + (y,) + sage: T.over(QQ).gens() + (y, x) + """ + self._check_base(base) + return tuple([ self(x) for x in generators(self._backend, backend_parent(self._base)) ]) + + def ngens(self, base=None): + r""" + Return the number of generators of this extension over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` (default: ``None``) + + EXAMPLES:: + + sage: K = GF(5^2).over() # over GF(5) + sage: K.gens() + (z2,) + sage: K.ngens() + 1 + + sage: L = GF(5^4).over(K) + sage: L.gens(GF(5)) + (z4, z2) + sage: L.ngens(GF(5)) + 2 + """ + return len(self.gens(base)) + + def gen(self): + r""" + Return the first generator of this extension. + + EXAMPLES:: + + sage: K = GF(5^2).over() # over GF(5) + sage: x =K.gen(); x + z2 + + Observe that the generator lives in the extension:: + + sage: x.parent() + Field in z2 with defining polynomial x^2 + 4*x + 2 over its base + sage: x.parent() is K + True + """ + return self.gens()[0] + + def random_element(self): + r""" + Return a random element in this extension. + + EXAMPLES:: + + sage: K = GF(5^2).over() # over GF(5) + sage: x = K.random_element(); x # random + 3 + z2 + + sage: x.parent() + Field in z2 with defining polynomial x^2 + 4*x + 2 over its base + sage: x.parent() is K + True + """ + elt = self._backend.random_element() + return self.element_class(self, elt) + + def degree_over(self, base=None): + r""" + Return the degree of this extension over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` (default: ``None``) + + EXAMPLES:: + + sage: F = GF(5^2) + sage: K = GF(5^4).over(F) + sage: L = GF(5^12).over(K) + + sage: K.degree_over(F) + 2 + sage: L.degree_over(K) + 3 + sage: L.degree_over(F) + 6 + + If ``base`` is omitted, the degree is computed over the base + of the extension:: + + sage: K.degree_over() + 2 + sage: L.degree_over() + 3 + + Note that ``base`` must be an explicit base over which the + extension has been defined (as listed by the method :meth:`bases`):: + + sage: K.degree_over(GF(5)) + Traceback (most recent call last): + ... + ValueError: not (explicitly) defined over Finite Field of size 5 + """ + base = self._check_base(base) + return self._degree_over(base) + + cpdef _degree_over(self, CommutativeRing base): + r""" + Return the degree of this extension over ``base``. + + Should be implemented in subclasses. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` (default: ``None``) + + TESTS:: + + sage: A. = QQ.extension(x^2 - 2) + sage: B. = QQ.extension(x^6 - 2) + sage: f = A.hom([b^3]) + sage: E = B.over(f) + sage: E.degree_over() # indirect doctest + 3 + """ + if base is self: + return ZZ(1) + raise NotImplementedError("degree is not implemented (and maybe not defined) for this extension") + + def degree(self, base): + r""" + Return the degree of this extension over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) + + EXAMPLES:: + + sage: A = GF(5^4).over(GF(5^2)) + sage: B = GF(5^12).over(A) + + sage: A.degree(GF(5^2)) + 2 + sage: B.degree(A) + 3 + sage: B.degree(GF(5^2)) + 6 + + Note that ``base`` must be an explicit base over which the + extension has been defined (as listed by the method :meth:`bases`):: + + sage: A.degree(GF(5)) + Traceback (most recent call last): + ... + ValueError: not (explicitly) defined over Finite Field of size 5 + + .. SEEALSO:: + + :meth:`relative_degree`, :meth:`absolute_degree` + """ + return self.degree_over(base) + + def relative_degree(self): + r""" + Return the degree of this extension over its base + + EXAMPLES:: + + sage: A = GF(5^4).over(GF(5^2)) + sage: A.relative_degree() + 2 + + .. SEEALSO:: + + :meth:`degree`, :meth:`absolute_degree` + """ + return self._degree_over(self._base) + + def absolute_degree(self): + r""" + Return the degree of this extension over its absolute base + + EXAMPLES:: + + sage: A = GF(5^4).over(GF(5^2)) + sage: B = GF(5^12).over(A) + + sage: A.absolute_degree() + 2 + sage: B.absolute_degree() + 6 + + .. SEEALSO:: + + :meth:`degree`, :meth:`relative_degree` + """ + return self._degree_over(self.absolute_base()) + + def is_finite_over(self, base=None): + r""" + Return whether or not this extension is finite over ``base`` (as a module). + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` (default: ``None``) + + EXAMPLES:: + + sage: K = GF(5^2).over() # over GF(5) + sage: L = GF(5^4).over(K) + + sage: L.is_finite_over(K) + True + sage: L.is_finite_over(GF(5)) + True + + If ``base`` is omitted, it is set to its default which is the + base of the extension:: + + sage: L.is_finite_over() + True + """ + cdef CommutativeRing b + base = self._check_base(base) + if base is self: + return True + try: + return self._is_finite_over(base) + except NotImplementedError: + pass + b = self._base + while b is not base: + try: + if self._is_finite_over(b) and b.is_finite_over(base): + return True + except NotImplementedError: + pass + b = (b)._base + raise NotImplementedError + + cpdef _is_finite_over(self, CommutativeRing base): + r""" + Return whether or not this extension is finite over ``base``. + + Should be implemented in subclasses. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) + + TESTS:: + + sage: K = GF(5^2).over() # over GF(5) + sage: K.is_finite_over() # indirect doctest + True + """ + raise NotImplementedError + + def is_free_over(self, base=None): + r""" + Return ``True`` if this extension is free (as a module) + over ``base`` + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` (default: ``None``) + + EXAMPLES:: + + sage: K = GF(5^2).over() # over GF(5) + sage: L = GF(5^4).over(K) + + sage: L.is_free_over(K) + True + sage: L.is_free_over(GF(5)) + True + + If ``base`` is omitted, it is set to its default which is the + base of the extension:: + + sage: L.is_free_over() + True + """ + cdef CommutativeRing b + base = self._check_base(base) + if base is self or base.is_field(): + return True + try: + return self._is_free_over(base) + except NotImplementedError: + pass + b = self._base + while b is not base: + try: + if self._is_free_over(b) and b.is_free_over(base): + return True + except NotImplementedError: + pass + b = (b)._base + raise NotImplementedError + + cpdef _is_free_over(self, CommutativeRing base): + r""" + Return whether or not this extension is finite over ``base``. + + Should be implemented in subclasses. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) + + TESTS:: + + sage: K = GF(5^2).over() # over GF(5) + sage: K.is_free_over() # indirect doctest + True + """ + raise NotImplementedError + + def is_field(self, proof=True): + r""" + Return whether or not this extension is a field. + + INPUT: + + - ``proof`` -- a boolean (default: ``False``) + + EXAMPLES:: + + sage: K = GF(5^5).over() # over GF(5) + sage: K.is_field() + True + + sage: S. = QQ[] + sage: A = S.over(QQ) + sage: A.is_field() + False + + sage: B = A.fraction_field() + sage: B.is_field() + True + """ + return self._backend.is_field(proof=proof) + + @cached_method + def fraction_field(self, extend_base=False): + r""" + Return the fraction field of this extension. + + INPUT: + + - ``extend_base`` -- a boolean (default: ``False``); + + If ``extend_base`` is ``False``, the fraction field of the + extension `L/K` is defined as `\textrm{Frac}(L)/L/K`, except + if `L` is already a field in which base the fraction field + of `L/K` is `L/K` itself. + + If ``extend_base`` is ``True``, the fraction field of the + extension `L/K` is defined as `\textrm{Frac}(L)/\textrm{Frac}(K)` + (provided that the defining morphism extends to the fraction + fields, i.e. is injective). + + EXAMPLES:: + + sage: A. = ZZ.extension(x^2 - 5) + sage: OK = A.over() # over ZZ + sage: OK + Order in Number Field in a with defining polynomial x^2 - 5 over its base + + sage: K1 = OK.fraction_field() + sage: K1 + Fraction Field of Order in Number Field in a with defining polynomial x^2 - 5 over its base + sage: K1.bases() + [Fraction Field of Order in Number Field in a with defining polynomial x^2 - 5 over its base, + Order in Number Field in a with defining polynomial x^2 - 5 over its base, + Integer Ring] + + sage: K2 = OK.fraction_field(extend_base=True) + sage: K2 + Fraction Field of Order in Number Field in a with defining polynomial x^2 - 5 over its base + sage: K2.bases() + [Fraction Field of Order in Number Field in a with defining polynomial x^2 - 5 over its base, + Rational Field] + + Note that there is no coercion between `K_1` and `K_2`:: + + sage: K1.has_coerce_map_from(K2) + False + sage: K2.has_coerce_map_from(K1) + False + + We check that when the extension is a field, its fraction field does not change:: + + sage: K1.fraction_field() is K1 + True + sage: K2.fraction_field() is K2 + True + + TESTS:: + + sage: A = GF(5).over(ZZ) + sage: A.fraction_field(extend_base=True) + Traceback (most recent call last): + ... + ValueError: the morphism is not injective + """ + defining_morphism = self._defining_morphism_fraction_field(extend_base) + if defining_morphism is None: + return self + ring = defining_morphism.codomain() + constructor = RingExtensionFractionField, {'ring': self, 'is_backend_exposed': self._is_backend_exposed} + return RingExtension(ring, defining_morphism, constructors=[constructor]) + + cdef Map _defining_morphism_fraction_field(self, bint extend_base): + r""" + Return the defining morphism of the fraction field of this extension. + + This is an helper function. + + INPUT: + + - ``extend_base`` -- a boolean (default: ``False``); see + :meth:`fraction_field` for more informations + + TESTS:: + + sage: K = GF(5^2).over() + sage: K.fraction_field() # indirect doctest + Field in z2 with defining polynomial x^2 + 4*x + 2 over its base + + sage: K = QQ.over(ZZ) + sage: K.fraction_field(extend_base=True) # indirect doctest + Rational Field over its base + """ + if extend_base: + defining_morphism = backend_morphism(self._backend_defining_morphism) + defining_morphism = defining_morphism.extend_to_fraction_field() + if isinstance(self._base, RingExtension_generic): + base = self._base.fraction_field(extend_base) + ring = defining_morphism.codomain() + defining_morphism = RingExtensionHomomorphism(base.Hom(ring), defining_morphism) + else: + if self.is_field(): + defining_morphism = None + else: + ring = self._backend.fraction_field() + defining_morphism = RingExtensionHomomorphism(self.Hom(ring), ring.coerce_map_from(self._backend)) + return defining_morphism + + def _Hom_(self, codomain, category): + r""" + Return the homset from this extension of ``codomain`` is the category ``category``. + + INPUT: + + - ``codomain`` -- a parent + + - ``category`` -- a subcategory of the category of rings + + EXAMPLES:: + + sage: F = GF(5^2) + sage: K = GF(5^4).over(F) + sage: L = GF(5^12).over(F) + + sage: K.Hom(L) # indirect doctest + Set of Homomorphisms from Field in z4 with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base + to Field in z12 with defining polynomial x^6 + (4*z2 + 3)*x^5 + x^4 + (3*z2 + 1)*x^3 + x^2 + (4*z2 + 1)*x + z2 over its base + + sage: K.Hom(L, category=Sets()) + Set of Morphisms from Field in z4 with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base + to Field in z12 with defining polynomial x^6 + (4*z2 + 3)*x^5 + x^4 + (3*z2 + 1)*x^3 + x^2 + (4*z2 + 1)*x + z2 over its base + in Category of sets + + """ + from sage.rings.ring_extension_homset import RingExtensionHomset + if category.is_subcategory(CommutativeRings()): + return RingExtensionHomset(self, codomain, category) + raise TypeError("category must be a subcategory of rings") + + def hom(self, im_gens, codomain=None, base_map=None, category=None, check=True): + r""" + Return the unique homomorphism from this extension to + ``codomain`` that sends ``self.gens()`` to the entries + of ``im_gens`` and induces the map ``base_map`` on the + base ring. + + INPUT: + + - ``im_gens`` -- the images of the generators of this extension + + - ``codomain`` -- the codomain of the homomorphism; if omitted, it + is set to the smallest parent containing all the entries of ``im_gens`` + + - ``base_map`` -- a map from one of the bases of this extension into + something that coerces into the codomain; if omitted, coercion maps + are used + + - ``category`` -- the category of the resulting morphism + + - ``check`` -- a boolean (default: ``True``); whether to verify that the + images of generators extend to define a map (using only canonical coercions) + + EXAMPLES:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: L. = GF(5^6).over(K) + + We define (by hand) the relative Frobenius endomorphism of the extension `L/K`:: + + sage: L.hom([b^25]) + Ring endomorphism of Field in b with defining polynomial x^3 + (2 + 2*a)*x - a over its base + Defn: b |--> 2 + 2*a*b + (2 - a)*b^2 + + Defining the absolute Frobenius of `L` is a bit more complicated + because it is not a homomorphism of `K`-algebras. + For this reason, the construction ``L.hom([b^5])`` fails:: + + sage: L.hom([b^5]) + Traceback (most recent call last): + ... + ValueError: images do not define a valid homomorphism + + What we need is to specify a base map:: + + sage: FrobK = K.hom([a^5]) + sage: FrobL = L.hom([b^5], base_map=FrobK) + sage: FrobL + Ring endomorphism of Field in b with defining polynomial x^3 + (2 + 2*a)*x - a over its base + Defn: b |--> (-1 + a) + (1 + 2*a)*b + a*b^2 + with map on base ring: + a |--> 1 - a + + As a shortcut, we may use the following construction:: + + sage: phi = L.hom([b^5, a^5]) + sage: phi + Ring endomorphism of Field in b with defining polynomial x^3 + (2 + 2*a)*x - a over its base + Defn: b |--> (-1 + a) + (1 + 2*a)*b + a*b^2 + with map on base ring: + a |--> 1 - a + sage: phi == FrobL + True + """ + if codomain is None: + from sage.structure.sequence import Sequence + codomain = Sequence(im_gens).universe() + parent = self.Hom(codomain, category=category) + return RingExtensionHomomorphism(parent, im_gens, base_map, check) + + +# Fraction fields +################# + +cdef class RingExtensionFractionField(RingExtension_generic): + """ + A class for ring extensions of the form `\textrm{Frac}(A)/A`. + + TESTS:: + + sage: Z = ZZ.over() # over ZZ itself + sage: Q = Z.fraction_field() + sage: Q + Fraction Field of Integer Ring over its base + + sage: type(Q) + + + sage: TestSuite(Q).run() + + """ + Element = RingExtensionFractionFieldElement + + def __init__(self, defining_morphism, ring=None, **kwargs): + r""" + Initialize this ring extension. + + INPUT: + + - ``defining_morphism`` -- a ring homomorphism + + - ``ring`` -- the commutative ring whose fraction field is this + extension + + TESTS:: + + sage: A. = ZZ.extension(x^2 - 2) + sage: OK = A.over() + sage: K = OK.fraction_field() + sage: K + Fraction Field of Order in Number Field in a with defining polynomial x^2 - 2 over its base + + sage: TestSuite(K).run() + + """ + RingExtension_generic.__init__(self, defining_morphism, **kwargs) + if ring is None: + self._ring = self._base + else: + self._ring = ring + + def ring(self): + r""" + Return the ring whose fraction field is this extension. + + EXAMPLES:: + + sage: A. = ZZ.extension(x^2 - 2) + sage: OK = A.over() + sage: K = OK.fraction_field() + sage: K + Fraction Field of Order in Number Field in a with defining polynomial x^2 - 2 over its base + + sage: K.ring() + Order in Number Field in a with defining polynomial x^2 - 2 over its base + sage: K.ring() is OK + True + """ + return self._ring + + def _repr_topring(self, **options): + r""" + Return a string representation of top ring of this extension. + + EXAMPLES:: + + sage: A. = ZZ.extension(x^2 - 2) + sage: OK = A.over() + sage: K = OK.fraction_field() + + sage: K._repr_topring() + 'Fraction Field of Order in Number Field in a with defining polynomial x^2 - 2' + """ + if isinstance(self._ring, RingExtension_generic): + sr = self._ring._repr_topring(**options) + else: + sr = str(self._ring) + if self._ring in Fields(): + return sr + else: + return "Fraction Field of %s" % sr + + def _latex_topring(self, **options): + r""" + Return a LaTeX representation of top ring of this extension. + + EXAMPLES:: + + sage: Z = ZZ.over() + sage: Q = Z.fraction_field() + + sage: Q._latex_topring() + '\\mathrm{Frac}(\\Bold{Z})' + """ + if self._ring in Fields(): + return self._ring._latex_topring(**options) + else: + return "\\mathrm{Frac}(%s)" % latex(self._ring) + + +# Finite free extensions +######################## + +cdef class RingExtensionWithBasis(RingExtension_generic): + """ + A class for finite free ring extensions equipped + with a basis. + + TESTS:: + + sage: E = GF(5^4).over(GF(5^2)) + sage: E + Field in z4 with defining polynomial x^2 + (4*z2 + 3)*x + z2 over its base + + sage: TestSuite(E).run() + """ + Element = RingExtensionWithBasisElement + + def __init__(self, defining_morphism, basis, names=None, check=True, **kwargs): + r""" + Initialize this ring extension. + + INPUT: + + - ``defining_morphism`` -- a ring homomorphism + + - ``basis`` -- a tuple of elements in this extension + + - ``names`` -- a tuple of strings or ``None`` (default: ``None``); + the way the elements of the basis are printed + + - ``check`` -- a boolean (default: ``True``); whether to check if + ``basis`` is indeed a basis + + TESTS:: + + sage: K. = QQ.extension(x^3 - 2) + sage: E = K.over() + sage: E + Field in a with defining polynomial x^3 - 2 over its base + + sage: TestSuite(E).run() + """ + RingExtension_generic.__init__(self, defining_morphism, **kwargs) + self._basis = [ self(b) for b in basis ] + if names is None: + names = [ ] + for b in self._basis: + b = b._backend + if b == 1: + names.append("") + sb = str(b) + if b._is_atomic() or (sb[0] == "(" and sb[-1] == ")"): + names.append(sb) + else: + names.append("(" + sb + ")") + else: + if len(names) != len(self._basis): + raise ValueError("the number of names does not match the cardinality of the basis") + self._basis_names = names + self._basis_latex_names = [ latex_variable_name(name) for name in names ] + self._names = tuple(names) + if check: + try: + _ = self.free_module(map=True) + except (ZeroDivisionError, ArithmeticError): + raise ValueError("the given family is not a basis") + if 'base' not in self._print_options: + self._print_options['base'] = self._base + + def _print_option_base(self, base): + r""" + Return a normalized form of the print option ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` (default: ``None``) + + TESTS:: + + sage: F = GF(5) + sage: K = GF(5^2).over(F) + sage: L = GF(5^4).over(K) + + sage: L._print_option_base(F) is F + True + sage: L._print_option_base(K) is K + True + sage: L._print_option_base(GF(5^2)) is K + True + + sage: L._print_option_base(None) is K + True + + sage: L._print_option_base(L) + Traceback (most recent call last): + ... + ValueError: base must be strict + + sage: K._print_option_base(L) + Traceback (most recent call last): + ... + ValueError: not (explicitly) defined over Field in z4 with defining polynomial x^2 + (3 - z2)*x + z2 over its base + + """ + if 'print_elements_as' in self._print_options: + raise NotImplementedError("printing is handled by an external function or another parent") + base = self._check_base(base) + if base is self: + raise ValueError("base must be strict") + b = self._base + while b is not base: + if not isinstance(b, RingExtensionWithBasis): + raise NotImplementedError + b = b.base_ring() + return base + + cpdef _degree_over(self, CommutativeRing base): + r""" + Return the degree of this extension over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` (default: ``None``) + + TESTS:: + + sage: A. = QQ.extension(x^2 - 2) + sage: B. = QQ.extension(x^6 - 2) + sage: f = A.hom([b^3]) + sage: E = B.over(f) + sage: E.degree_over() # indirect doctest + 3 + """ + if base is self: + return ZZ(1) + elif base is self._base: + return len(self._basis) + else: + return len(self._basis) * self._base._degree_over(base) + + cpdef _is_finite_over(self, CommutativeRing base): + r""" + Return whether or not this extension is finite over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) + + TESTS:: + + sage: K = GF(5^2).over() # over GF(5) + sage: K.is_finite_over() # indirect doctest + True + """ + if base is self or base is self._base: + return True + return self._base._is_finite_over(base) + + cpdef _is_free_over(self, CommutativeRing base): + r""" + Return whether or not this extension is free over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) + + TESTS:: + + sage: K = GF(5^2).over() # over GF(5) + sage: K.is_free_over() # indirect doctest + True + """ + if base is self or base is self._base: + return True + return self._base._is_free_over(base) + + def basis_over(self, base=None): + r""" + Return a basis of this extension over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) + + EXAMPLES:: + + sage: F. = GF(5^2).over() # over GF(5) + sage: K. = GF(5^4).over(F) + sage: L. = GF(5^12).over(K) + + sage: L.basis_over(K) + [1, c, c^2] + + sage: L.basis_over(F) + [1, b, c, b*c, c^2, b*c^2] + + sage: L.basis_over(GF(5)) + [1, a, b, a*b, c, a*c, b*c, a*b*c, c^2, a*c^2, b*c^2, a*b*c^2] + + If ``base`` is omitted, it is set to its default which is the + base of the extension:: + + sage: L.basis_over() + [1, c, c^2] + + sage: K.basis_over() + [1, b] + + Note that ``base`` must be an explicit base over which the + extension has been defined (as listed by the method :meth:`bases`):: + + sage: L.degree_over(GF(5^6)) + Traceback (most recent call last): + ... + ValueError: not (explicitly) defined over Finite Field in z6 of size 5^6 + """ + base = self._check_base(base) + return self._basis_over(base) + + cpdef _basis_over(self, CommutativeRing base): + r""" + Return a basis of this extension over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) + + TESTS:: + + sage: A. = QQ.extension(x^3 - 2) + sage: K. = A.over() + sage: K.basis_over() + [1, u, u^2] + """ + if base is self: + return [ self.one() ] + elif base is self._base: + return self._basis[:] + else: + b = self._base._basis_over(base) + return [ x*y for x in self._basis for y in b ] + + def free_module(self, base=None, map=True): + r""" + Return a free module V over ``base`` which is isomorphic to + this ring + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` (default: ``None``) + + - ``map`` -- boolean (default ``True``); whether to return + isomorphisms between this ring and V + + OUTPUT: + + - A finite-rank free module V over ``base`` + + - The isomorphism from V to this ring corresponding to the + basis output by the method :meth:`basis_over` + (only included if ``map`` is ``True``) + + - The reverse isomorphism of the isomorphism above + (only included if ``map`` is ``True``) + + EXAMPLES:: + + sage: F = GF(11) + sage: K. = GF(11^2).over() + sage: L. = GF(11^6).over(K) + + Forgetting a part of the multiplicative structure, the field L + can be viewed as a vector space of dimension 3 over K, equipped + with a distinguished basis, namely `(1, b, b^2)`:: + + sage: V, i, j = L.free_module(K) + sage: V + Vector space of dimension 3 over Field in a with defining polynomial x^2 + 7*x + 2 over its base + sage: i + Generic map: + From: Vector space of dimension 3 over Field in a with defining polynomial x^2 + 7*x + 2 over its base + To: Field in b with defining polynomial x^3 + (7 + 2*a)*x^2 + (2 - a)*x - a over its base + sage: j + Generic map: + From: Field in b with defining polynomial x^3 + (7 + 2*a)*x^2 + (2 - a)*x - a over its base + To: Vector space of dimension 3 over Field in a with defining polynomial x^2 + 7*x + 2 over its base + + sage: j(b) + (0, 1, 0) + sage: i((1, a, a+1)) + 1 + a*b + (1 + a)*b^2 + + Similarly, one can view L as a F-vector space of dimension 6:: + + sage: V, i, j, = L.free_module(F) + sage: V + Vector space of dimension 6 over Finite Field of size 11 + + In this case, the isomorphisms between `V` and `L` are given by the + basis `(1, a, b, ab, b^2, ab^2)`: + + sage: j(a*b) + (0, 0, 0, 1, 0, 0) + sage: i((1,2,3,4,5,6)) + (1 + 2*a) + (3 + 4*a)*b + (5 + 6*a)*b^2 + + When ``base`` is omitted, the default is the base of this extension:: + + sage: L.free_module(map=False) + Vector space of dimension 3 over Field in a with defining polynomial x^2 + 7*x + 2 over its base + + Note that ``base`` must be an explicit base over which the + extension has been defined (as listed by the method :meth:`bases`):: + + sage: L.degree(GF(11^3)) + Traceback (most recent call last): + ... + ValueError: not (explicitly) defined over Finite Field in z3 of size 11^3 + + """ + base = self._check_base(base) + return self._free_module(base, map) + + @cached_method + def _free_module(self, base, map): + r""" + Return a free module V over ``base`` which is isomorphic to + this ring + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` (default: ``None``) + + - ``map`` -- boolean (default ``True``); whether to return + isomorphisms between this ring and V + + OUTPUT: + + - A finite-rank free module V over ``base`` + + - The isomorphism from V to this ring corresponding to the + basis output by the method :meth:`basis_over` + (only included if ``map`` is ``True``) + + - The reverse isomorphism of the isomorphism above + (only included if ``map`` is ``True``) + + TESTS:: + + sage: K = GF(7^5).over() + sage: L = GF(7^15).over(K) + sage: for base in L.bases(): + ....: V, i, j = L.free_module(base) + ....: assert([ i(v) for v in V.basis() ] == L.basis_over(base)) + ....: assert([ j(x) for x in L.basis_over(base) ] == V.basis()) + + """ + d = self._degree_over(base) + if map: + return base**d, MapFreeModuleToRelativeRing(self, base), MapRelativeRingToFreeModule(self, base) + else: + return base**d + + @cached_method + def fraction_field(self, extend_base=False): + r""" + Return the fraction field of this extension. + + INPUT: + + - ``extend_base`` -- a boolean (default: ``False``); + + If ``extend_base`` is ``False``, the fraction field of the + extension `L/K` is defined as `\textrm{Frac}(L)/L/K`, except + is `L` is already a field in which base the fraction field + of `L/K` is `L/K` itself. + + If ``extend_base`` is ``True``, the fraction field of the + extension `L/K` is defined as `\textrm{Frac}(L)/\textrm{Frac}(K)` + (provided that the defining morphism extends to the fraction + fields, i.e. is injective). + + EXAMPLES:: + + sage: A. = ZZ.extension(x^2 - 5) + sage: OK = A.over() # over ZZ + sage: OK + Order in Number Field in a with defining polynomial x^2 - 5 over its base + + sage: K1 = OK.fraction_field() + sage: K1 + Fraction Field of Order in Number Field in a with defining polynomial x^2 - 5 over its base + sage: K1.bases() + [Fraction Field of Order in Number Field in a with defining polynomial x^2 - 5 over its base, + Order in Number Field in a with defining polynomial x^2 - 5 over its base, + Integer Ring] + + sage: K2 = OK.fraction_field(extend_base=True) + sage: K2 + Fraction Field of Order in Number Field in a with defining polynomial x^2 - 5 over its base + sage: K2.bases() + [Fraction Field of Order in Number Field in a with defining polynomial x^2 - 5 over its base, + Rational Field] + + Note that there is no coercion map between `K_1` and `K_2`:: + + sage: K1.has_coerce_map_from(K2) + False + sage: K2.has_coerce_map_from(K1) + False + + We check that when the extension is a field, its fraction field does not change:: + + sage: K1.fraction_field() is K1 + True + sage: K2.fraction_field() is K2 + True + + TESTS:: + + sage: A = GF(5).over(ZZ) + sage: A.fraction_field(extend_base=True) + Traceback (most recent call last): + ... + ValueError: the morphism is not injective + """ + defining_morphism = self._defining_morphism_fraction_field(extend_base) + if defining_morphism is None: + return self + if extend_base: + basis = self._basis + names = self._basis_names + constructor = RingExtensionWithBasis + kwargs = { 'basis': basis, 'names': names, 'check': False } + else: + gen = names = None + constructor = RingExtensionFractionField + kwargs = { 'print_options': {'print_elements_as': self.fraction_field(extend_base=True)} } + kwargs['is_backend_exposed'] = self._is_backend_exposed + ring = defining_morphism.codomain() + return RingExtension(ring, defining_morphism, gen=gen, names=names, constructors=[(constructor, kwargs)]) + + +cdef class RingExtensionWithGen(RingExtensionWithBasis): + """ + A class for finite free ring extensions generated by + a single element + + TESTS:: + + sage: A. = QQ.extension(x^3 - 7) + sage: K = A.over() + + sage: type(K) + + + sage: TestSuite(K).run() + + """ + def __init__(self, defining_morphism, gen, names, check=True, **kwargs): + r""" + Initialize this ring extension. + + INPUT: + + - ``defining_morphism`` -- a ring homomorphism + + - ``gen`` -- a generator of this extension + + - ``names`` -- a tuple of strings or ``None`` (default: ``None``); + the way the elements of the basis are printed + + - ``check`` -- a boolean (default: ``True``); whether to check if + ``gen`` is indeed a generator + + TESTS:: + + sage: K. = QQ.extension(x^3 + 3*x + 1) + sage: E = K.over() + sage: E + Field in a with defining polynomial x^3 + 3*x + 1 over its base + + sage: TestSuite(E).run() + """ + self._name = names[0] + backend_base = backend_parent(defining_morphism.domain()) + _, deg_domain, deg_codomain = common_base(backend_base, defining_morphism.codomain(), True) + degree = deg_codomain // deg_domain + basis_names = [ "" ] + basis_latex_names = [ "" ] + if degree == 1: + self._name = None + else: + basis_names += [ self._name ] + [ "%s^%s" % (self._name, i) for i in range(2,degree) ] + latex_name = latex_variable_name(self._name) + basis_latex_names += [ latex_name ] + [ "%s^{%s}" % (latex_name, i) for i in range(2,degree) ] + basis = [ gen ** i for i in range(degree) ] + RingExtensionWithBasis.__init__(self, defining_morphism, basis, basis_names, check, **kwargs) + self._gen = self._backend(gen) + self._names = (self._name,) + self._latex_names = (latex_variable_name(self._name),) + self._basis_latex_names = basis_latex_names + + def _repr_topring(self, **options): + r""" + Return a string representation of top ring of this extension. + + EXAMPLES:: + + sage: K. = GF(5^3).over() + sage: K._repr_topring() + 'Field in a with defining polynomial x^3 + 3*x + 3' + + sage: L. = GF(5^9).over(K) + sage: L._repr_topring() + 'Field in b with defining polynomial x^3 + (1 + 3*a^2)*x^2 + (3 + 2*a + 2*a^2)*x - a' + """ + if self._name is None: + return RingExtension_generic._repr_topring(self, **options) + return "%s in %s with defining polynomial %s" % (self._type, self._name, self.modulus()) + + def _latex_topring(self): + r""" + Return a LaTeX representation of top ring of this extension. + + EXAMPLES:: + + sage: K. = GF(5^3).over() + sage: K._latex_topring() + '\\Bold{F}_{5}[a]' + + sage: L. = GF(5^9).over(K) + sage: L._latex_topring() + '\\Bold{F}_{5}[a][b]' + """ + if self._name is None: + return RingExtension_generic._latex_topring(self) + if isinstance(self._base, RingExtension_generic): + return "%s[%s]" % (self._base._latex_topring(), self.latex_variable_names()[0]) + else: + return "%s[%s]" % (latex(self._base), self.latex_variable_names()[0]) + + def modulus(self, var='x'): + r""" + Return the defining polynomial of this extension, that is the + minimal polynomial of the given generator of this extension. + + INPUT: + + - ``var`` -- a variable name (default: ``x``) + + EXAMPLES:: + + sage: K. = GF(7^10).over(GF(7^2)) + sage: K + Field in u with defining polynomial x^5 + (6*z2 + 4)*x^4 + (3*z2 + 5)*x^3 + (2*z2 + 2)*x^2 + 4*x + 6*z2 over its base + + sage: P = K.modulus(); P + x^5 + (6*z2 + 4)*x^4 + (3*z2 + 5)*x^3 + (2*z2 + 2)*x^2 + 4*x + 6*z2 + sage: P(u) + 0 + + We can use a different variable name:: + + sage: K.modulus('y') + y^5 + (6*z2 + 4)*y^4 + (3*z2 + 5)*y^3 + (2*z2 + 2)*y^2 + 4*y + 6*z2 + """ + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + _, _, j = self.free_module(map=True) + d = self.relative_degree() + coeffs = [ -c for c in j(self._gen**d) ] + [ 1 ] + S = PolynomialRing(self._base, name=var) + return S(coeffs) + + def gens(self, base=None): + r""" + Return the generators of this extension over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` (default: ``None``) + + EXAMPLES:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: K.gens() + (a,) + + sage: L. = GF(5^4).over(K) + sage: L.gens() + (b,) + sage: L.gens(GF(5)) + (b, a) + """ + if base is None: + return (self(self._gen),) + base = self._check_base(base) + gens = tuple([]) + b = self + while b is not base: + gens += b.gens() + b = b.base() + return gens + + @cached_method + def fraction_field(self, extend_base=False): + r""" + Return the fraction field of this extension. + + INPUT: + + - ``extend_base`` -- a boolean (default: ``False``); + + If ``extend_base`` is ``False``, the fraction field of the + extension `L/K` is defined as `\textrm{Frac}(L)/L/K`, except + is `L` is already a field in which base the fraction field + of `L/K` is `L/K` itself. + + If ``extend_base`` is ``True``, the fraction field of the + extension `L/K` is defined as `\textrm{Frac}(L)/\textrm{Frac}(K)` + (provided that the defining morphism extends to the fraction + fields, i.e. is injective). + + EXAMPLES:: + + sage: A. = ZZ.extension(x^2 - 5) + sage: OK = A.over() # over ZZ + sage: OK + Order in Number Field in a with defining polynomial x^2 - 5 over its base + + sage: K1 = OK.fraction_field() + sage: K1 + Fraction Field of Order in Number Field in a with defining polynomial x^2 - 5 over its base + sage: K1.bases() + [Fraction Field of Order in Number Field in a with defining polynomial x^2 - 5 over its base, + Order in Number Field in a with defining polynomial x^2 - 5 over its base, + Integer Ring] + + sage: K2 = OK.fraction_field(extend_base=True) + sage: K2 + Fraction Field of Order in Number Field in a with defining polynomial x^2 - 5 over its base + sage: K2.bases() + [Fraction Field of Order in Number Field in a with defining polynomial x^2 - 5 over its base, + Rational Field] + + Note that there is no coercion map between `K_1` and `K_2`:: + + sage: K1.has_coerce_map_from(K2) + False + sage: K2.has_coerce_map_from(K1) + False + + We check that when the extension is a field, its fraction field does not change:: + + sage: K1.fraction_field() is K1 + True + sage: K2.fraction_field() is K2 + True + + TESTS:: + + sage: A = GF(5).over(ZZ) + sage: A.fraction_field(extend_base=True) + Traceback (most recent call last): + ... + ValueError: the morphism is not injective + """ + defining_morphism = self._defining_morphism_fraction_field(extend_base) + if defining_morphism is None: + return self + if extend_base: + gen = self._gen + names = self._names + constructor = RingExtensionWithGen + kwargs = { 'gen': gen, 'names': names, 'check': False } + else: + gen = names = None + constructor = RingExtensionFractionField + kwargs = { 'print_options': {'print_elements_as': self.fraction_field(extend_base=True)} } + kwargs['is_backend_exposed'] = self._is_backend_exposed + ring = defining_morphism.codomain() + return RingExtension(ring, defining_morphism, gen=gen, names=names, constructors=[(constructor, kwargs)]) diff --git a/src/sage/rings/ring_extension_conversion.pxd b/src/sage/rings/ring_extension_conversion.pxd new file mode 100644 index 00000000000..e3815a411ba --- /dev/null +++ b/src/sage/rings/ring_extension_conversion.pxd @@ -0,0 +1,17 @@ +from sage.rings.ring_extension cimport RingExtension_generic + + +cpdef backend_parent(R) +cpdef from_backend_parent(R, RingExtension_generic E) + +cpdef backend_element(x) +cpdef from_backend_element(x, RingExtension_generic E) + +cdef _backend_morphism(f) +cpdef backend_morphism(f, forget=*) +cpdef from_backend_morphism(f, RingExtension_generic E) + +cpdef to_backend(arg) +cpdef from_backend(arg, E) + + diff --git a/src/sage/rings/ring_extension_conversion.pyx b/src/sage/rings/ring_extension_conversion.pyx new file mode 100644 index 00000000000..f432faf8bb2 --- /dev/null +++ b/src/sage/rings/ring_extension_conversion.pyx @@ -0,0 +1,459 @@ +############################################################################# +# Copyright (C) 2019 Xavier Caruso +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#**************************************************************************** + + +from sage.structure.parent cimport Parent +from sage.structure.element cimport Element +from sage.categories.pushout import construction_tower +from sage.categories.map cimport Map, FormalCompositeMap +from sage.categories.morphism import IdentityMorphism +from sage.rings.ring_extension cimport RingExtension_generic +from sage.rings.ring_extension_element cimport RingExtensionElement +from sage.rings.ring_extension_morphism cimport RingExtensionHomomorphism +from sage.rings.ring_extension_morphism cimport RingExtensionBackendIsomorphism +from sage.rings.ring_extension_morphism cimport RingExtensionBackendReverseIsomorphism + + +# For parents +############# + +cpdef backend_parent(R): + r""" + Return the backend parent of ``R``. + + INPUT: + + - ``R`` -- a parent + + EXAMPLES: + + sage: from sage.rings.ring_extension_conversion import backend_parent + + sage: K. = GF(5^2).over() # over GF(5) + sage: backend_parent(K) + Finite Field in z2 of size 5^2 + sage: backend_parent(K) is GF(5^2) + True + """ + if isinstance(R, RingExtension_generic): + return (R)._backend + else: + return R + +cpdef from_backend_parent(R, RingExtension_generic E): + r""" + Try to reconstruct a ring extension (somehow related to ``E``) + whose backend is ``R``. + + INPUT: + + - ``R`` -- a parent + + - ``E`` -- a ring extension + + EXAMPLES:: + + sage: from sage.rings.ring_extension_conversion import from_backend_parent + + sage: K. = GF(5^2).over() # over GF(5) + sage: L. = GF(5^4).over(K) + + sage: from_backend_parent(GF(5^2), K) + Field in a with defining polynomial x^2 + 4*x + 2 over its base + sage: from_backend_parent(GF(5^2), K) is K + True + + Bases are recognized:: + + sage: from_backend_parent(GF(5^2), L) + Field in a with defining polynomial x^2 + 4*x + 2 over its base + sage: from_backend_parent(GF(5^2), L) is K + True + + And also certain constructions:: + + sage: S. = GF(5^2)[] + sage: T = from_backend_parent(S, L) + sage: T + Univariate Polynomial Ring in x over Field in a with defining polynomial x^2 + 4*x + 2 over its base + sage: T.base_ring() is K + True + """ + tower = construction_tower(R) + bases_tower = [ parent for (_, parent) in tower ] + for base in E.bases(): + backend = backend_parent(base) + try: + s = bases_tower.index(backend) + ans = base + for i in range(s, 0, -1): + functor = tower[i][0] + ans = functor(ans) + return ans + except ValueError: + pass + return R + + +# For elements +############## + +cpdef backend_element(x): + r""" + Return the backend element of ``x``. + + INPUT: + + - ``x`` -- an element + + EXAMPLES: + + sage: from sage.rings.ring_extension_conversion import backend_element + + sage: K. = GF(5^2).over() # over GF(5) + sage: backend_element(a) + z2 + sage: backend_element(a) in GF(5^2) + True + """ + if isinstance(x, RingExtensionElement): + return (x)._backend + else: + return x + +cpdef from_backend_element(x, RingExtension_generic E): + r""" + Try to reconstruct an element in a ring extension (somehow + related to ``E``) whose backend is ``x``. + + INPUT: + + - ``x`` -- an element + + - ``E`` -- a ring extension + + EXAMPLES:: + + sage: from sage.rings.ring_extension_conversion import from_backend_element + + sage: K. = GF(5^2).over() # over GF(5) + sage: L. = GF(5^4).over(K) + sage: z2 = GF(5^2).gen() + + sage: from_backend_element(z2, K) + a + sage: from_backend_element(z2, K).parent() is K + True + + Bases are recognized:: + + sage: from_backend_element(z2, L) + a + sage: from_backend_element(z2, L).parent() is K + True + + And also certain constructions:: + + sage: S. = GF(5^2)[] + sage: u = from_backend_element(x + z2, L); u + x + a + sage: u.parent() + Univariate Polynomial Ring in x over Field in a with defining polynomial x^2 + 4*x + 2 over its base + sage: u.base_ring() is K + True + """ + parent = from_backend_parent(x.parent(),E) + if parent is None: + return x + else: + return parent(x) + + +# For morphisms +############### + +cdef _backend_morphism(f): + r""" + Return the backend morphism of ``f``. + + INPUT: + + - ``f`` -- a map + + TESTS:: + + sage: from sage.rings.ring_extension_conversion import backend_morphism + + sage: Frob = GF(7^3).frobenius_endomorphism() + sage: backend_morphism(Frob) is Frob # indirect doctest + True + + sage: K. = GF(7^3).over() + sage: f = End(K)(Frob) + sage: type(f) + + sage: backend_morphism(f) == Frob # indirect doctest + True + + sage: L. = GF(7^6).over(K) + sage: g = f.extend_codomain(L) + sage: bg = backend_morphism(g); bg + Composite map: + From: Finite Field in z3 of size 7^3 + To: Finite Field in z6 of size 7^6 + Defn: Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 + then + Ring morphism: + From: Finite Field in z3 of size 7^3 + To: Finite Field in z6 of size 7^6 + Defn: z3 |--> 2*z6^4 + 6*z6^3 + 2*z6^2 + 3*z6 + 2 + sage: backend_morphism(bg) == bg + True + + sage: iota = End(K).identity() + sage: type(iota) + + sage: backend_morphism(iota) + Identity endomorphism of Finite Field in z3 of size 7^3 + """ + domain = f.domain() + if not isinstance(f.domain(), RingExtension_generic) and not isinstance(f.codomain(), RingExtension_generic): + return f + elif isinstance(f, RingExtensionHomomorphism): + return (f)._backend + elif isinstance(f, FormalCompositeMap): + return _backend_morphism(f.then()) * _backend_morphism(f.first()) + elif isinstance(f, IdentityMorphism): + ring = backend_parent(domain) + return ring.Hom(ring).identity() + elif domain is domain.base_ring(): + ring = f.codomain() + if isinstance(ring, RingExtension_generic): + ring = ring._backend + if ring.has_coerce_map_from(domain): + return ring.coerce_map_from(domain) + raise NotImplementedError + +cpdef backend_morphism(f, forget="all"): + r""" + Return the backend morphism of ``f``. + + INPUT: + + - ``f`` -- a map + + - ``forget`` -- a string, either ``all`` or ``domain`` or ``codomain`` + (default: ``all``); whether to switch to the backend for the domain, + the codomain or both of them. + + EXAMPLES:: + + sage: from sage.rings.ring_extension_conversion import backend_morphism + + sage: K. = GF(7^3).over() # over GF(7) + sage: f = K.hom([a^7]) + sage: f + Ring endomorphism of Field in a with defining polynomial x^3 + 6*x^2 + 4 over its base + Defn: a |--> 5*a + 3*a^2 + + sage: backend_morphism(f) + Ring endomorphism of Finite Field in z3 of size 7^3 + Defn: z3 |--> 3*z3^2 + 5*z3 + + sage: backend_morphism(f, forget="domain") + Ring morphism: + From: Finite Field in z3 of size 7^3 + To: Field in a with defining polynomial x^3 + 6*x^2 + 4 over its base + Defn: z3 |--> 5*a + 3*a^2 + + sage: backend_morphism(f, forget="codomain") + Ring morphism: + From: Field in a with defining polynomial x^3 + 6*x^2 + 4 over its base + To: Finite Field in z3 of size 7^3 + Defn: a |--> 3*z3^2 + 5*z3 + """ + try: + g = _backend_morphism(f) + if forget is None and (isinstance(f.domain(), RingExtension_generic) or isinstance(f.codomain(), RingExtension_generic)): + g = RingExtensionHomomorphism(f.domain().Hom(f.codomain()), g) + if forget == "domain" and isinstance(f.codomain(), RingExtension_generic): + g = RingExtensionHomomorphism(g.domain().Hom(f.codomain()), g) + if forget == "codomain" and isinstance(f.domain(), RingExtension_generic): + g = RingExtensionHomomorphism(f.domain().Hom(g.codomain()), g) + except NotImplementedError: + g = f + if (forget == "all" or forget == "domain") and isinstance(f.domain(), RingExtension_generic): + ring = f.domain()._backend + g = g * RingExtensionBackendIsomorphism(ring.Hom(f.domain())) + if (forget == "all" or forget == "codomain") and isinstance(f.codomain(), RingExtension_generic): + ring = f.codomain()._backend + g = RingExtensionBackendReverseIsomorphism(f.codomain().Hom(ring)) * g + return g + +cpdef from_backend_morphism(f, RingExtension_generic E): + r""" + Try to reconstruct a morphism between ring extensions + (somehow related to ``E``) whose backend is ``f``. + + INPUT: + + - ``x`` -- a morphism + + - ``E`` -- a ring extension + + EXAMPLES:: + + sage: from sage.rings.ring_extension_conversion import from_backend_morphism + + sage: K. = GF(5^2).over() # over GF(5) + sage: L. = GF(5^6).over(K) + + sage: Frob = GF(5^2).frobenius_endomorphism() + sage: from_backend_morphism(Frob, K) + Ring endomorphism of Field in a with defining polynomial x^2 + 4*x + 2 over its base + Defn: a |--> 1 - a + + Bases are recognized:: + + sage: from_backend_morphism(Frob, L) + Ring endomorphism of Field in a with defining polynomial x^2 + 4*x + 2 over its base + Defn: a |--> 1 - a + """ + cdef domain = from_backend_parent(f.domain(), E) + cdef codomain = from_backend_parent(f.codomain(), E) + return RingExtensionHomomorphism(domain.Hom(codomain), f) + + +# Generic +######### + +cpdef to_backend(arg): + r""" + Return the backend of ``arg``. + + EXAMPLES:: + + sage: from sage.rings.ring_extension_conversion import to_backend + + This function accepts parents:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: to_backend(K) + Finite Field in z2 of size 5^2 + + elements:: + + sage: to_backend(a) + z2 + + morphisms:: + + sage: f = K.hom([a^5]) + sage: to_backend(f) + Ring endomorphism of Finite Field in z2 of size 5^2 + Defn: z2 |--> 4*z2 + 1 + + list/tuple of them:: + + sage: to_backend(([K, a], f)) + ([Finite Field in z2 of size 5^2, z2], + Ring endomorphism of Finite Field in z2 of size 5^2 + Defn: z2 |--> 4*z2 + 1) + + and dictionaries:: + + sage: to_backend({a: K}) + {z2: Finite Field in z2 of size 5^2} + + .. SEEALSO:: + + :meth:`to_backend_parent`, :meth:`to_backend_element`, :meth:`to_backend_morphism` + """ + if isinstance(arg, list): + return [ to_backend(x) for x in arg ] + elif isinstance(arg, tuple): + return tuple([ to_backend(x) for x in arg ]) + elif isinstance(arg, dict): + return { to_backend(key): to_backend(value) for (key, value) in arg.items() } + elif isinstance(arg, RingExtension_generic): + return (arg)._backend + elif isinstance(arg, Map): + return backend_morphism(arg) + elif isinstance(arg, RingExtensionElement): + return (arg)._backend + return arg + +cpdef from_backend(arg, E): + r""" + Try to reconstruct something (somehow related to ``E``) + whose backend is ``arg``. + + INPUT: + + - ``arg`` -- any argument + + - ``E`` -- a ring extension + + EXAMPLES:: + + sage: from sage.rings.ring_extension_conversion import from_backend + + This function accepts parents:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: from_backend(GF(5^2), K) + Field in a with defining polynomial x^2 + 4*x + 2 over its base + + elements:: + + sage: z2 = GF(5^2).gen() + sage: from_backend(z2, K) + a + + morphisms:: + + sage: f = GF(5^2).frobenius_endomorphism() + sage: from_backend(f, K) + Ring endomorphism of Field in a with defining polynomial x^2 + 4*x + 2 over its base + Defn: a |--> 1 - a + + list/tuple of them:: + + sage: from_backend(([K, a], f), K) + ([Field in a with defining polynomial x^2 + 4*x + 2 over its base, a], + Ring endomorphism of Field in a with defining polynomial x^2 + 4*x + 2 over its base + Defn: a |--> 1 - a) + + and dictionaries:: + + sage: from_backend({a: K}, K) + {a: Field in a with defining polynomial x^2 + 4*x + 2 over its base} + + .. SEEALSO:: + + :meth:`from_backend_parent`, :meth:`from_backend_element`, :meth:`from_backend_morphism` + """ + ans = None + if isinstance(arg, list): + ans = [ from_backend(x,E) for x in arg ] + elif isinstance(arg, tuple): + ans = tuple([ from_backend(x,E) for x in arg ]) + elif isinstance(arg, dict): + ans = { from_backend(key,E): from_backend(value,E) for (key, value) in arg.items() } + elif isinstance(arg, Parent): + ans = from_backend_parent(arg,E) + elif isinstance(arg, Map): + ans = from_backend_morphism(arg,E) + elif isinstance(arg, Element): + ans = from_backend_element(arg,E) + if ans is None: + return arg + else: + return ans diff --git a/src/sage/rings/ring_extension_element.pxd b/src/sage/rings/ring_extension_element.pxd new file mode 100644 index 00000000000..6b62ad58c06 --- /dev/null +++ b/src/sage/rings/ring_extension_element.pxd @@ -0,0 +1,22 @@ +from sage.rings.ring cimport CommutativeRing +from sage.structure.element cimport Element +from sage.structure.element cimport CommutativeAlgebraElement +from sage.rings.ring_extension cimport RingExtension_generic +from sage.rings.ring_extension cimport RingExtensionFractionField +from sage.rings.ring_extension cimport RingExtensionWithBasis + + +cdef class RingExtensionElement(CommutativeAlgebraElement): + cdef Element _backend + +cdef class RingExtensionFractionFieldElement(RingExtensionElement): + pass + +cdef class RingExtensionWithBasisElement(RingExtensionElement): + cdef _vector(self, CommutativeRing base) + cdef _matrix(self, CommutativeRing base) + cdef _trace(self, CommutativeRing base) + cdef _norm(self, CommutativeRing base) + cpdef minpoly(self, base=*, var=*) + + diff --git a/src/sage/rings/ring_extension_element.pyx b/src/sage/rings/ring_extension_element.pyx new file mode 100644 index 00000000000..1f34ba48078 --- /dev/null +++ b/src/sage/rings/ring_extension_element.pyx @@ -0,0 +1,1485 @@ +r""" +Elements lying in extension of rings + +AUTHOR: + +- Xavier Caruso (2019) +""" + +############################################################################# +# Copyright (C) 2019 Xavier Caruso +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#**************************************************************************** + + +from sage.ext.stdsage cimport PY_NEW +from sage.misc.cachefunc import cached_method +from sage.cpython.getattr cimport AttributeErrorMessage +from sage.cpython.getattr import dir_with_other_class +from sage.misc.latex import latex + +from sage.structure.category_object import normalize_names +from sage.structure.element cimport CommutativeAlgebraElement +from sage.structure.element cimport Element +from sage.rings.integer_ring import ZZ +from sage.categories.fields import Fields +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + +from sage.rings.ring_extension cimport RingExtension_generic, RingExtensionWithGen, RingExtensionFractionField +from sage.rings.ring_extension_morphism cimport MapRelativeRingToFreeModule +from sage.rings.ring_extension_conversion cimport backend_parent, backend_element +from sage.rings.ring_extension_conversion cimport to_backend, from_backend + + +# Classes +######### + +cdef class RingExtensionElement(CommutativeAlgebraElement): + r""" + Generic class for elements lying in ring extensions. + + TESTS:: + + sage: K = GF(5^4).over() + sage: x = K.random_element() + sage: TestSuite(x).run() + + """ + def __init__(self, RingExtension_generic parent, x, *args, **kwds): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + - ``x`` -- some data to construct this element + + TESTS:: + + sage: Q = QQ.over(ZZ) + sage: x = Q(1/2) + sage: x + 1/2 + """ + if not isinstance(parent, RingExtension_generic): + raise TypeError("%s is not a ring extension" % parent) + x = backend_element(x) + try: + parentx = x.parent() + if parent._base.has_coerce_map_from(parentx): + x = parent._base.coerce_map_from(parentx)(x) + x = parent._backend_defining_morphism(x) + except AttributeError: + pass + CommutativeAlgebraElement.__init__(self, parent) + ring = parent._backend + self._backend = ring(x, *args, **kwds) + + def __reduce__(self): + """ + Return a tuple of a function and data that can be used to unpickle this + element. + + TESTS:: + + sage: K = GF(5^3).over() + sage: x = K.random_element() + sage: type(x) + + sage: loads(dumps(x)) == x + True + """ + return self._parent, (self._backend,) + + def __getattr__(self, name): + """ + If the parent of this element was created with ``import_methods = True``, + return a wrapper to the corresponding method of the backend element + (if it exists). + + EXAMPLES:: + + sage: A. = QQ.extension(x^2 - 2) + sage: K. = A.over() # over QQ + + sage: hasattr(a, 'continued_fraction') + True + sage: a.continued_fraction() + [1; (2)*] + """ + try: + return self.getattr_from_category(name) + except AttributeError: + pass + method = None + if (self._parent)._import_methods and hasattr(self._backend, name): + method = getattr(self._backend, name) + if not callable(method): + raise AttributeError(AttributeErrorMessage(self, name)) + def wrapper(*args, **kwargs): + output = method(*to_backend(args), **to_backend(kwargs)) + return from_backend(output, self._parent) + wrapper.__doc__ = method.__doc__ + return wrapper + + def __dir__(self): + """ + Return the list of all the attributes of this element; + if the parent of this element was created with ``import_methods = True``, + concatenate this list with the list of all the methods of the backend + element. + + EXAMPLES:: + + sage: A. = QQ.extension(x^2 - 2) + sage: K. = A.over() + + sage: dir(a) + ['__abs__', + '__add__', + ... + 'complex_embeddings', + 'conjugate', + 'continued_fraction', + 'continued_fraction_list', + ... + 'trace', + 'valuation', + 'vector', + 'xgcd'] + """ + d = dir_with_other_class(self, self._parent.category().element_class) + if not (self._parent)._import_methods: + return d + for name in dir(self._backend): + try: + attribute = getattr(self._backend, name) + if callable(attribute): + d.append(name) + except: + pass + return sorted(set(d)) + + def __hash__(self): + """ + Return a hash of this element. + + EXAMPLES: + + sage: E. = GF(5^3).over() + sage: hash(a) + 5 + """ + return hash(self._backend) + + def _repr_(self, **options): + r""" + Return a string representation of this element. + + Do not override this method in subclasses; + instead override the method :meth:`_repr_extension`. + + TESTS:: + + sage: K. = GF(5^2).over() + sage: L. = GF(5^4).over(K) + sage: b._repr_() + 'b' + """ + cdef RingExtension_generic parent = self._parent + if 'print_elements_as' in options: + print_as = options.pop('print_elements_as') + else: + print_as = parent._print_options.get('print_elements_as') + if print_as is not None: + return print_as(self._backend)._repr_(**options) + print_options = parent._print_options.copy() + for (name, value) in options.items(): + method = None + if hasattr(parent, '_print_option_' + name): + method = getattr(parent, '_print_option_' + name) + if not callable(method): + raise ValueError("option '%s' does not exist" % name) + print_options[name] = method(value) + return self._repr_extension(**print_options) + + def _repr_extension(self, **options): + r""" + Return a string representation of this element. + + TESTS:: + + sage: K = QQ.over(ZZ) + sage: x = K(1/2) + sage: x._repr_extension() + '1/2' + """ + return str(self._backend) + + def _latex_(self, **options): + r""" + Return a LaTeX representation of this element. + + Do not override this method in subclasses; + instead override the method :meth:`_latex_extension`. + + TESTS:: + + sage: K. = GF(5^2).over() + sage: L. = GF(5^4).over(K) + sage: b._latex_() + 'b' + """ + cdef RingExtension_generic parent = self._parent + if 'print_elements_as' in options: + print_as = options.pop('print_elements_as') + else: + print_as = parent._print_options.get('print_elements_as') + if print_as is not None: + return print_as(self._backend)._latex_(**options) + print_options = parent._print_options.copy() + for (name, value) in options.items(): + method = None + if hasattr(parent, '_print_option_' + name): + method = getattr(parent, '_print_option_' + name) + if not callable(method): + raise ValueError("option '%s' does not exist" % name) + print_options[name] = method(value) + return self._latex_extension(**print_options) + + def _latex_extension(self, **options): + r""" + Return a LaTeX representation of this element. + + TESTS:: + + sage: K = QQ.over(ZZ) + sage: x = K(1/2) + sage: x._latex_extension() + \frac{1}{2} + """ + return latex(self._backend) + + cpdef _richcmp_(left, right, int op): + r""" + Compare this element with ``right`` according to + the rich comparison operator ``op``. + + The comparison is performed by comparing the backend + elements. + + INPUT: + + - ``right`` -- an element in the same parent + + - ``op`` -- the comparison operator + + EXAMPLES:: + + sage: K. = GF(5^2).over() + sage: x = K.random_element() + sage: x == x + True + sage: x == x + 1 + False + sage: x == x^25 + True + """ + return left._backend._richcmp_(backend_element(right), op) + + cpdef _add_(self,other): + r""" + Return the sum of this element and ``other``. + + TESTS:: + + sage: K = GF(5^4).over(GF(5^2)) + sage: x = K.random_element() + sage: y = K.random_element() + + sage: (x+y).parent() is K + True + sage: x + y == y + x + True + """ + cdef RingExtensionElement ans = PY_NEW(type(self)) + ans._parent = self._parent + ans._backend = self._backend + (other)._backend + return ans + + cpdef _neg_(self): + r""" + Return the opposite of this element. + + TESTS:: + + sage: K = GF(5^4).over(GF(5^2)) + sage: x = K.random_element() + + sage: y = -x + sage: y.parent() is K + True + sage: x + y == 0 + True + """ + cdef RingExtensionElement ans = PY_NEW(type(self)) + ans._parent = self._parent + ans._backend = -self._backend + return ans + + cpdef _sub_(self,other): + r""" + Return the difference of this element and ``other``. + + TESTS:: + + sage: K = GF(5^4).over(GF(5^2)) + sage: x = K.random_element() + sage: y = K.random_element() + + sage: (x-y).parent() is K + True + sage: x - y == x + (-y) + True + """ + cdef RingExtensionElement ans = PY_NEW(type(self)) + ans._parent = self._parent + ans._backend = self._backend - (other)._backend + return ans + + cpdef _mul_(self,other): + r""" + Return the product of this element and ``other``. + + TESTS:: + + sage: K = GF(5^4).over(GF(5^2)) + sage: x = K.random_element() + sage: y = K.random_element() + + sage: (x*y).parent() is K + True + sage: x * y == y * x + True + """ + cdef RingExtensionElement ans = PY_NEW(type(self)) + ans._parent = self._parent + ans._backend = self._backend * (other)._backend + return ans + + cpdef _div_(self,other): + r""" + Return the quotient of this element by ``other``, + considered as an element of the fraction field. + + TESTS:: + + sage: A. = ZZ.extension(x^2 - 2) + sage: OK = A.over() + sage: a = OK(a) + + sage: b = 1/a; b + a/2 + sage: b.parent() + Fraction Field of Order in Number Field in a with defining polynomial x^2 - 2 over its base + sage: a*b + 1 + """ + cdef RingExtensionElement ans + cdef RingExtension_generic parent = self._parent + if parent._fraction_field is None: + parent._fraction_field = parent.fraction_field() + parent._fraction_field_type = parent._fraction_field.element_class + ans = PY_NEW(parent._fraction_field_type) + ans._parent = parent._fraction_field + ans._backend = self._backend / (other)._backend + return ans + + def additive_order(self): + r""" + Return the additive order of this element. + + EXAMPLES:: + + sage: K. = GF(5^4).over(GF(5^2)) + sage: a.additive_order() + 5 + """ + return self._backend.additive_order() + + def multiplicative_order(self): + r""" + Return the multiplicite order of this element. + + EXAMPLES:: + + sage: K. = GF(5^4).over(GF(5^2)) + sage: a.multiplicative_order() + 624 + """ + return self._backend.multiplicative_order() + + def is_unit(self): + r""" + Return whether if this element is a unit in this ring. + + EXAMPLES:: + + sage: A. = PolynomialRing(QQ) + sage: E = A.over(QQ) + sage: E(4).is_unit() + True + sage: E(x).is_unit() + False + """ + return self._backend.is_unit() + + def is_nilpotent(self): + r""" + Return whether if this element is nilpotent in this ring. + + EXAMPLES:: + + sage: A. = PolynomialRing(QQ) + sage: E = A.over(QQ) + sage: E(0).is_nilpotent() + True + sage: E(x).is_nilpotent() + False + """ + return self._backend.is_nilpotent() + + def is_prime(self): + r""" + Return whether this element is a prime element in this ring. + + EXAMPLES:: + + sage: A. = PolynomialRing(QQ) + sage: E = A.over(QQ) + sage: E(x^2+1).is_prime() + True + sage: E(x^2-1).is_prime() + False + """ + return self._backend.is_prime() + + def is_square(self, root=False): + r""" + Return whether this element is a square in this ring. + + INPUT: + + - ``root`` -- a boolean (default: ``False``); if ``True``, + return also a square root + + EXAMPLES:: + + sage: K. = GF(5^3).over() + sage: a.is_square() + False + sage: a.is_square(root=True) + (False, None) + + sage: b = a + 1 + sage: b.is_square() + True + sage: b.is_square(root=True) + (True, 2 + 3*a + a^2) + """ + is_sq = self._backend.is_square() + sq = None + if root and is_sq: + sq = self.sqrt(extend=False, all=False) + if root: + return is_sq, sq + else: + return is_sq + + def sqrt(self, extend=True, all=False, name=None): + r""" + Return a square root or all square roots of this element. + + INPUT: + + - ``extend`` -- a boolean (default: ``True``); if "True", + return a square root in an extension ring, if necessary. + Otherwise, raise a ``ValueError`` if the root is not in + the ring + + - ``all`` -- a boolean (default: ``False``); if ``True``, + return all square roots of this element, instead of just one. + + - ``name`` -- Required when ``extend=True`` and ``self`` is not a + square. This will be the name of the generator extension. + + .. NOTE:: + + The option `extend=True` is often not implemented. + + EXAMPLES:: + + sage: K. = GF(5^3).over() + sage: b = a + 1 + sage: b.sqrt() + 2 + 3*a + a^2 + sage: b.sqrt(all=True) + [2 + 3*a + a^2, 3 + 2*a - a^2] + """ + sq = self._backend.sqrt(extend=extend, all=all) + if all: + gen = sq[0] + else: + gen = sq + parent = self._parent + backend_parent = gen.parent() + if backend_parent is not (parent)._backend: + from sage.rings.ring_extension import RingExtension + if name is None: + raise ValueError("you must specify a variable name") + names = normalize_names(1, name) + constructor = (RingExtensionWithGen, + {'gen': gen, 'name': names[0], 'is_backend_exposed': False}) + parent = RingExtension(backend_parent, parent, (gen,), names, constructors=[constructor]) + if all: + return [ parent(s) for s in sq ] + else: + return parent(sq) + + +# Fraction fields +################# + +cdef class RingExtensionFractionFieldElement(RingExtensionElement): + r""" + A class for elements lying in fraction fields of ring extensions. + + TESTS:: + + sage: Z = ZZ.over() + sage: Q = Z.fraction_field() + sage: x = Q.random_element() + sage: type(x) + + sage: TestSuite(x).run() + """ + def __hash__(self): + """ + Return a hash of this element. + + EXAMPLES: + + sage: E. = GF(5^3).over() + sage: hash(a) + 5 + """ + return hash(self._backend) + + def _repr_extension(self, **options): + r""" + Return a string representation of this element. + + TESTS:: + + sage: Z = ZZ.over() + sage: Q = Z.fraction_field() + sage: x = Q(1/2) + sage: x._repr_extension() + '1/2' + sage: R = QQ['x'].over() + sage: K = R.fraction_field() + sage: x = R.gen() + sage: (x^2 + 1) / (x^2 - 1) + (x^2 + 1)/(x^2 - 1) + sage: x / (x + 1) + x/(x + 1) + sage: (x + 1)/(-x) + (-x - 1)/x + """ + num = self.numerator() + denom = self.denominator() + if denom == 1: + sd = "" + elif denom == -1: + num = -num + sd = "" + elif denom._is_atomic(): + sd = "/%s" % denom + elif (-denom)._is_atomic(): + sd = "/%s" % (-denom) + num = -num + else: + sd = "/(%s)" % denom + if num._is_atomic(): + return "%s%s" % (num, sd) + else: + return "(%s)%s" % (num, sd) + + def _latex_extension(self, **options): + r""" + Return a LaTeX representation of this element. + + TESTS:: + + sage: Z = ZZ.over() + sage: Q = Z.fraction_field() + sage: x = Q(1/2) + sage: x._latex_extension() + '\\frac{1}{2}' + """ + num = self.numerator() + denom = self.denominator() + if denom == -1: + denom = 1 + num = -num + if isinstance((self._parent)._ring, RingExtension_generic): + snum = num._latex_(**options) + sdenom = denom._latex_(**options) + else: + snum = latex(num) + sdenom = latex(denom) + if denom == 1: + return snum + else: + return "\\frac{%s}{%s}" % (snum, sdenom) + + def numerator(self): + r""" + Return the numerator of this element. + + EXAMPLES:: + + sage: A. = ZZ.extension(x^2 - 2) + sage: OK = A.over() # over ZZ + sage: K = OK.fraction_field() + sage: K + Fraction Field of Order in Number Field in a with defining polynomial x^2 - 2 over its base + + sage: x = K(1/a); x + a/2 + sage: num = x.numerator(); num + a + + The numerator is an element of the ring which was used + to construct the fraction field:: + + sage: num.parent() + Order in Number Field in a with defining polynomial x^2 - 2 over its base + sage: num.parent() is OK + True + + TESTS:: + + sage: x = K.random_element() + sage: x == x.numerator() / x.denominator() + True + """ + ring = (self._parent)._ring + num = self._backend.numerator() + return ring(num) + + def denominator(self): + r""" + Return the denominator of this element. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: A. = ZZ.extension(x^2 - 2) + sage: OK = A.over() # over ZZ + sage: K = OK.fraction_field() + sage: K + Fraction Field of Order in Number Field in a with defining polynomial x^2 - 2 over its base + + sage: x = K(1/a); x + a/2 + sage: denom = x.denominator(); denom + 2 + + The denominator is an element of the ring which was used + to construct the fraction field:: + + sage: denom.parent() + Order in Number Field in a with defining polynomial x^2 - 2 over its base + sage: denom.parent() is OK + True + + TESTS:: + + sage: x = K.random_element() + sage: x == x.numerator() / x.denominator() + True + """ + ring = (self._parent)._ring + denom = self._backend.denominator() + return ring(denom) + + +# Finite free extensions +######################## + +cdef class RingExtensionWithBasisElement(RingExtensionElement): + r""" + A class for elements lying in finite free extensions. + + TESTS:: + + sage: K. = GF(5^3).over() + sage: L. = GF(5^9).over(K) + sage: type(b) + + sage: TestSuite(b).run() + """ + def __hash__(self): + """ + Return a hash of this element. + + EXAMPLES: + + sage: E. = GF(5^3).over() + sage: hash(a) + 5 + """ + return hash(self._backend) + + def _repr_extension(self, base, **options): + r""" + Return a string representation of this element written as + a linear combination over ``base`` in the basis provided by + the method :meth:`basis_over`. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` + + EXAMPLES:: + + sage: K. = GF(5^3).over() + sage: L. = GF(5^9).over(K) + sage: u = 1/(a+b) + + sage: u._repr_extension(base=K) + '(2 + 2*a) + (-1 + a - a^2)*b + (2 + 3*a + 3*a^2)*b^2' + sage: u._repr_extension(base=GF(5)) + '2 + 2*a - b + a*b - a^2*b + 2*b^2 + 3*a*b^2 + 3*a^2*b^2' + """ + cdef RingExtensionWithBasis parent = self._parent + coeffs = self._vector(base) + names = parent._basis_names + b = parent._base + while b is not base: + new_names = [ ] + for y in names: + for x in (b)._basis_names: + if x == "": + new_names.append(y) + elif y == "": + new_names.append(x) + else: + new_names.append(x + "*" + y) + names = new_names + b = (b)._base + s = "" + for i in range(len(names)): + c = coeffs[i] + if c.is_zero(): + continue + sign = 1 + ss = "" + if c == -1: + sign = -1 + elif c != 1: + atomic = c._is_atomic() + if not atomic and (-c)._is_atomic(): + c = -c + sign = -sign + atomic = True + sc = str(c) + if atomic: + ss += sc + else: + ss += "(" + sc + ")" + if names[i] != "": + ss += "*" + if ss and ss[0] == "-": + ss = ss[1:] + sign *= -1 + if s == "": + if sign == -1: + s = "-" + else: + s += " + " if sign == 1 else " - " + ss += names[i] + if ss == "": + ss += "1" + s += ss + if s == "": + return "0" + if s[0] == "(" and s[-1] == ")": + s = s[1:-1] + return s + + def _latex_extension(self, base, **options): + r""" + Return a LaTeX representation of this element written as + a linear combination over ``base`` in the basis provided by + the method :meth:`basis_over`. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` + + EXAMPLES:: + + sage: K. = GF(5^3).over() + sage: L. = GF(5^9).over(K) + sage: u = 1/(a+b) + + sage: u._latex_extension(base=K) + \left( 2 + 2 a \right) + \left( -1 + a - a^{2} \right) b + \left( 2 + 3 a + 3 a^{2} \right) b^{2} + sage: u._latex_extension(base=GF(5)) + 2 + 2 a - b + ab - a^{2}b + 2 b^{2} + 3 ab^{2} + 3 a^{2}b^{2} + """ + cdef RingExtensionWithBasis parent = self._parent + coeffs = self._vector(base) + names = parent._basis_latex_names + b = parent._base + while b is not base: + names = [ x + y for y in names for x in (b)._basis_latex_names ] + b = (b)._base + s = "" + for i in range(len(names)): + c = coeffs[i] + if c.is_zero(): + continue + sign = 1 + ss = "" + if c == -1: + sign = -1 + elif c != 1: + atomic = c._is_atomic() + if not atomic and (-c)._is_atomic(): + c = -c + sign = -sign + atomic = True + sc = latex(c) + if atomic: + ss += sc + else: + ss += r"\left(" + sc + r"\right)" + if ss != "" and ss[0] == "-": + ss = ss[1:] + sign *= -1 + if s == "": + if sign == -1: + s = "-" + else: + s += " + " if sign == 1 else " - " + ss += names[i] + if ss == "": + ss += "1" + s += ss + if s == "": + return "0" + if s[:6] == r"\left(" and s[-7] == r"\right)": + s = s[6:-7] + return s + + def vector(self, base=None): + r""" + Return the vector of coordinates of this element over ``base`` + (in the basis output by the method :meth:`basis_over`). + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` + + EXAMPLES:: + + sage: F = GF(5) + sage: K. = GF(5^2).over() # over F + sage: L. = GF(5^6).over(K) + sage: x = (a+b)^4; x + (-1 + a) + (3 + a)*b + (1 - a)*b^2 + + sage: x.vector(K) # basis is (1, b, b^2) + (-1 + a, 3 + a, 1 - a) + + sage: x.vector(F) # basis is (1, a, b, a*b, b^2, a*b^2) + (4, 1, 3, 1, 1, 4) + + If ``base`` is omitted, it is set to its default which is the + base of the extension:: + + sage: x.vector() + (-1 + a, 3 + a, 1 - a) + + Note that ``base`` must be an explicit base over which the + extension has been defined (as listed by the method :meth:`bases`):: + + sage: x.vector(GF(5^3)) + Traceback (most recent call last): + ... + ValueError: not (explicitly) defined over Finite Field in z3 of size 5^3 + """ + base = (self._parent)._check_base(base) + return self._vector(base) + + cdef _vector(self, CommutativeRing base): + r""" + Return the vector of coordinates of this element over ``base`` + (in the basis output by the method :meth:`basis_over`). + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` + + TESTS:: + + sage: K = GF(11^10).over(GF(11^2)) + sage: x = K.random_element() + sage: coeffs = x.vector() + sage: basis = K.basis_over() + sage: x == sum(coeffs[i]*basis[i] for i in range(5)) + True + """ + _, _, j = (self._parent)._free_module(base, map=True) + return j(self) + + def polynomial(self, base=None, var='x'): + r""" + Return a polynomial (in one or more variables) over ``base`` + whose evaluation at the generators of the parent equals this + element. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` + + EXAMPLES:: + + sage: F. = GF(5^2).over() # over GF(5) + sage: K. = GF(5^4).over(F) + sage: L. = GF(5^12).over(K) + sage: u = 1/(a + b + c); u + (2 + (-1 - a)*b) + ((2 + 3*a) + (1 - a)*b)*c + ((-1 - a) - a*b)*c^2 + + sage: P = u.polynomial(K); P + ((-1 - a) - a*b)*x^2 + ((2 + 3*a) + (1 - a)*b)*x + 2 + (-1 - a)*b + sage: P.base_ring() is K + True + sage: P(c) == u + True + + When the base is `F`, we obtain a bivariate polynomial:: + + sage: P = u.polynomial(F); P + (-a)*x0^2*x1 + (-1 - a)*x0^2 + (1 - a)*x0*x1 + (2 + 3*a)*x0 + (-1 - a)*x1 + 2 + + We check that its value at the generators is the element we started with:: + + sage: L.gens(F) + (c, b) + sage: P(c, b) == u + True + + Similarly, when the base is ``GF(5)``, we get a trivariate polynomial: + + sage: P = u.polynomial(GF(5)); P + -x0^2*x1*x2 - x0^2*x2 - x0*x1*x2 - x0^2 + x0*x1 - 2*x0*x2 - x1*x2 + 2*x0 - x1 + 2 + sage: P(c, b, a) == u + True + + Different variable names can be specified:: + + sage: u.polynomial(GF(5), var='y') + -y0^2*y1*y2 - y0^2*y2 - y0*y1*y2 - y0^2 + y0*y1 - 2*y0*y2 - y1*y2 + 2*y0 - y1 + 2 + sage: u.polynomial(GF(5), var=['x','y','z']) + -x^2*y*z - x^2*z - x*y*z - x^2 + x*y - 2*x*z - y*z + 2*x - y + 2 + + If ``base`` is omitted, it is set to its default which is the + base of the extension:: + + sage: u.polynomial() + ((-1 - a) - a*b)*x^2 + ((2 + 3*a) + (1 - a)*b)*x + 2 + (-1 - a)*b + + Note that ``base`` must be an explicit base over which the + extension has been defined (as listed by the method :meth:`bases`):: + + sage: u.polynomial(GF(5^3)) + Traceback (most recent call last): + ... + ValueError: not (explicitly) defined over Finite Field in z3 of size 5^3 + """ + base = self._parent._check_base(base) + degrees = [ ] + b = self._parent + degree = 1 + while b is not base: + if not isinstance(b, RingExtensionWithGen): + raise NotImplementedError + reldeg = b.relative_degree() + degree *= reldeg + degrees.append(reldeg) + b = b.base_ring() + degrees.reverse() + coeffs = { } + v = self._vector(base) + S = PolynomialRing(base, len(degrees), names=var) + for i in range(degree): + ii = ZZ(i) + exponents = [ ] + for d in degrees: + ii, exponent = ii.quo_rem(d) + exponents.append(exponent) + coeffs[tuple(reversed(exponents))] = v[i] + return S(coeffs) + + def matrix(self, base=None): + r""" + Return the matrix of the multiplication by this element (in + the basis output by :meth:`basis_over`). + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` + + EXAMPLES:: + + sage: K. = GF(5^3).over() # over GF(5) + sage: L. = GF(5^6).over(K) + sage: u = a/(1+b) + + sage: u + (2 + a + 3*a^2) + (3 + 3*a + a^2)*b + sage: b*u + (3 + 2*a^2) + (2 + 2*a - a^2)*b + sage: u.matrix(K) + [2 + a + 3*a^2 3 + 3*a + a^2] + [ 3 + 2*a^2 2 + 2*a - a^2] + + sage: u.matrix(GF(5)) + [2 1 3 3 3 1] + [1 3 1 2 0 3] + [2 3 3 1 3 0] + [3 0 2 2 2 4] + [4 2 0 3 0 2] + [0 4 2 4 2 0] + + If ``base`` is omitted, it is set to its default which is the + base of the extension:: + + sage: u.matrix() + [2 + a + 3*a^2 3 + 3*a + a^2] + [ 3 + 2*a^2 2 + 2*a - a^2] + + Note that ``base`` must be an explicit base over which the + extension has been defined (as listed by the method :meth:`bases`):: + + sage: u.matrix(GF(5^2)) + Traceback (most recent call last): + ... + ValueError: not (explicitly) defined over Finite Field in z2 of size 5^2 + """ + cdef RingExtension_generic parent = self._parent + base = parent._check_base(base) + if not (parent._is_finite_over(base) and parent._is_free_over(base)): + raise ValueError("the extension is not finite free") + return self._matrix(base) + + cdef _matrix(self, CommutativeRing base): + r""" + Return the matrix of the multiplication by this element (in + the basis output by :meth:`basis_over`). + + This method does not check its input. + Do not call it directly; use :meth:`matrix` instead. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) + + TESTS:: + + sage: F = GF(11^2) + sage: K = GF(11^6).over(F) + sage: L = GF(11^18).over(K) + + sage: for base in L.bases(): + ....: x = L.random_element() + ....: y = L.random_element() + ....: assert((x+y).matrix(base) == x.matrix(base) + y.matrix(base)) + ....: assert((x*y).matrix(base) == x.matrix(base) * y.matrix(base)) + """ + from sage.matrix.matrix_space import MatrixSpace + cdef RingExtensionWithBasis parent = self._parent + _, _, j = parent._free_module(base, map=True) + x = self._backend + M = [ j(x * (b)._backend) for b in parent._basis_over(base) ] + return MatrixSpace(base, len(M))(M) + + def trace(self, base=None): + r""" + Return the trace of this element over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` + + EXAMPLES:: + + sage: F = GF(5) + sage: K. = GF(5^3).over(F) + sage: L. = GF(5^6).over(K) + sage: u = a/(1+b) + + sage: tr = u.trace(K); tr + -1 + 3*a + 2*a^2 + + We check that the trace lives in the base ring:: + + sage: tr.parent() + Field in a with defining polynomial x^3 + 3*x + 3 over its base + sage: tr.parent() is K + True + + Similarly, one can compute the trace over F:: + + sage: u.trace(F) + 0 + + We check the transitivity of the trace:: + + sage: u.trace(F) == tr.trace(F) + True + + If ``base`` is omitted, it is set to its default which is the + base of the extension:: + + sage: u.trace() + -1 + 3*a + 2*a^2 + + Note that ``base`` must be an explicit base over which the + extension has been defined (as listed by the method :meth:`bases`):: + + sage: u.trace(GF(5^2)) + Traceback (most recent call last): + ... + ValueError: not (explicitly) defined over Finite Field in z2 of size 5^2 + """ + cdef RingExtension_generic parent = self._parent + base = parent._check_base(base) + if not (parent._is_finite_over(base) and parent._is_free_over(base)): + raise ValueError("the extension is not finite free") + return self._trace(base) + + cdef _trace(self, CommutativeRing base): + r""" + Return the trace of this element over ``base``. + + This method does not check its input. + Do not call it directly; use :meth:`trace` instead. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) + + TESTS:: + + sage: F = GF(11^2) + sage: K = GF(11^6).over(F) + sage: L = GF(11^18).over(K) + + sage: x = L.random_element() + sage: x.trace(F) == x.trace().trace() + True + + sage: for base in L.bases(): + ....: x = L.random_element() + ....: y = L.random_element() + ....: assert(x.trace(base) == x.matrix(base).trace()) + ....: assert((x+y).trace(base) == x.trace(base) + y.trace(base)) + """ + cdef RingExtensionWithBasis parent = self._parent + cdef CommutativeRing b + if base is parent: + return self + b = parent._base + t = self._matrix(b).trace() + if base is b: + return t + return (t)._trace(base) + + def norm(self, base=None): + r""" + Return the norm of this element over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` + + EXAMPLES:: + + sage: F = GF(5) + sage: K. = GF(5^3).over(F) + sage: L. = GF(5^6).over(K) + sage: u = a/(1+b) + + sage: nr = u.norm(K); nr + 3 + 2*a^2 + + We check that the norm lives in the base ring:: + + sage: nr.parent() + Field in a with defining polynomial x^3 + 3*x + 3 over its base + sage: nr.parent() is K + True + + Similarly, one can compute the norm over F:: + + sage: u.norm(F) + 4 + + We check the transitivity of the norm:: + + sage: u.norm(F) == nr.norm(F) + True + + If ``base`` is omitted, it is set to its default which is the + base of the extension:: + + sage: u.norm() + 3 + 2*a^2 + + Note that ``base`` must be an explicit base over which the + extension has been defined (as listed by the method :meth:`bases`):: + + sage: u.norm(GF(5^2)) + Traceback (most recent call last): + ... + ValueError: not (explicitly) defined over Finite Field in z2 of size 5^2 + """ + cdef RingExtension_generic parent = self._parent + base = parent._check_base(base) + if not (parent._is_finite_over(base) and parent._is_free_over(base)): + raise ValueError("the extension is not finite free") + return self._norm(base) + + cdef _norm(self, CommutativeRing base): + r""" + Return the norm of this element over ``base``. + + This method does not check its input. + Do not call it directly; use :meth:`norm` instead. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) + + TESTS:: + + sage: F = GF(11^2) + sage: K = GF(11^6).over(F) + sage: L = GF(11^18).over(K) + + sage: x = L.random_element() + sage: x.norm(F) == x.norm().norm() + True + + sage: for base in L.bases(): + ....: x = L.random_element() + ....: y = L.random_element() + ....: assert(x.norm(base) == x.matrix(base).determinant()) + ....: assert((x*y).norm(base) == x.norm(base) * y.norm(base)) + """ + cdef RingExtensionWithBasis parent = self._parent + cdef CommutativeRing b + if base is parent: + return self + b = parent._base + n = self._matrix(b).determinant() + if base is b: + return n + return (n)._norm(base) + + def charpoly(self, base=None, var='x'): + r""" + Return the characteristic polynomial of this element over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` + + EXAMPLES:: + + sage: F = GF(5) + sage: K. = GF(5^3).over(F) + sage: L. = GF(5^6).over(K) + sage: u = a/(1+b) + + sage: chi = u.charpoly(K); chi + x^2 + (1 + 2*a + 3*a^2)*x + 3 + 2*a^2 + + We check that the charpoly has coefficients in the base ring:: + + sage: chi.base_ring() + Field in a with defining polynomial x^3 + 3*x + 3 over its base + sage: chi.base_ring() is K + True + + and that it annihilates u:: + + sage: chi(u) + 0 + + Similarly, one can compute the characteristic polynomial over F:: + + sage: u.charpoly(F) + x^6 + x^4 + 2*x^3 + 3*x + 4 + + A different variable name can be specified:: + + sage: u.charpoly(F, var='t') + t^6 + t^4 + 2*t^3 + 3*t + 4 + + If ``base`` is omitted, it is set to its default which is the + base of the extension:: + + sage: u.charpoly() + x^2 + (1 + 2*a + 3*a^2)*x + 3 + 2*a^2 + + Note that ``base`` must be an explicit base over which the + extension has been defined (as listed by the method :meth:`bases`):: + + sage: u.charpoly(GF(5^2)) + Traceback (most recent call last): + ... + ValueError: not (explicitly) defined over Finite Field in z2 of size 5^2 + + TESTS: + + We check that the characteristic polynomial of an element in the base + ring is a power of a polynomial of degree 1:: + + sage: S. = K[] + sage: u = K.random_element() + sage: L(u).charpoly() == (x - u)^2 + True + """ + return self.matrix(base).charpoly(var) + + cpdef minpoly(self, base=None, var='x'): + r""" + Return the minimal polynomial of this element over ``base``. + + INPUT: + + - ``base`` -- a commutative ring (which might be itself an + extension) or ``None`` + + EXAMPLES:: + + sage: F = GF(5) + sage: K. = GF(5^3).over(F) + sage: L. = GF(5^6).over(K) + sage: u = 1 / (a+b) + + sage: chi = u.minpoly(K); chi + x^2 + (2*a + a^2)*x - 1 + a + + We check that the minimal polynomial has coefficients in the base ring:: + + sage: chi.base_ring() + Field in a with defining polynomial x^3 + 3*x + 3 over its base + sage: chi.base_ring() is K + True + + and that it annihilates u:: + + sage: chi(u) + 0 + + Similarly, one can compute the minimal polynomial over F:: + + sage: u.minpoly(F) + x^6 + 4*x^5 + x^4 + 2*x^2 + 3 + + A different variable name can be specified:: + + sage: u.minpoly(F, var='t') + t^6 + 4*t^5 + t^4 + 2*t^2 + 3 + + If ``base`` is omitted, it is set to its default which is the + base of the extension:: + + sage: u.minpoly() + x^2 + (2*a + a^2)*x - 1 + a + + Note that ``base`` must be an explicit base over which the + extension has been defined (as listed by the method :meth:`bases`):: + + sage: u.minpoly(GF(5^2)) + Traceback (most recent call last): + ... + ValueError: not (explicitly) defined over Finite Field in z2 of size 5^2 + + TESTS: + + We check that the minimal polynomial of an element in the base + ring has degree 1:: + + sage: S. = K[] + sage: u = K.random_element() + sage: L(u).minpoly() == x - u + True + + In a similar fashion, the minimal polynomial over `F` of an element + of `K` should have degree 1 or 3:: + + sage: L(u).minpoly(F).degree() in [ 1, 3 ] + True + """ + from sage.modules.free_module import FreeModule + cdef RingExtensionWithBasis parent = self._parent + cdef MapRelativeRingToFreeModule j + + base = parent._check_base(base) + if not (parent._is_finite_over(base) and parent._is_free_over(base)): + raise ValueError("the extension is not finite free") + if not base in Fields(): + raise NotImplementedError("minpoly is only implemented when the base is a field") + K = backend_parent(base) + degree = parent._degree_over(base) + _, _, j = parent._free_module(base, map=True) + V = FreeModule(K, degree) + vector = [K(1)] + (degree-1)*[K(0)] + vectors = [vector] + W = V.span(vectors) + elt = self + while True: + vector = V(j.backend_coefficients(elt)) + if vector in W: break + vectors.append(vector) + W += V.span([vector]) + elt *= self + W = V.span_of_basis(vectors) + coeffs = [ -c for c in W.coordinate_vector(vector) ] + [K(1)] + return PolynomialRing(base, name=var)(coeffs) diff --git a/src/sage/rings/ring_extension_homset.py b/src/sage/rings/ring_extension_homset.py new file mode 100644 index 00000000000..28b2b736dd8 --- /dev/null +++ b/src/sage/rings/ring_extension_homset.py @@ -0,0 +1,58 @@ +r""" +Homset between extensions of rings + +AUTHOR: + +- Xavier Caruso (2019) +""" + +############################################################################# +# Copyright (C) 2019 Xavier Caruso +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#**************************************************************************** + +from sage.rings.homset import RingHomset_generic +from sage.rings.ring_extension_morphism import RingExtensionHomomorphism + +class RingExtensionHomset(RingHomset_generic): + r""" + A generic class for homsets between ring extensions. + + TESTS:: + + sage: K = GF(5^2).over() + sage: L = GF(5^8).over(K) + sage: H = Hom(K,L) + sage: H + Set of Homomorphisms from Field in z2 with defining polynomial x^2 + 4*x + 2 over its base to Field in z8 with defining polynomial x^4 + (3 - z2)*x + z2 over its base + + sage: type(H) + <... 'sage.rings.ring_extension_homset.RingExtensionHomset_with_category'> + """ + def __call__(self, *args, **kwargs): + r""" + Return the morphism in this parent defined by the + given parameters. + + TESTS:: + + sage: K. = GF(5^2).over() + sage: L. = GF(5^4).over(K) + sage: Hom(L,L)([b^5, a^5]) + Ring endomorphism of Field in b with defining polynomial x^2 + (3 - a)*x + a over its base + Defn: b |--> (2 + a) + 2*b + with map on base ring: + a |--> 1 - a + + sage: Hom(K,L)(GF(5^4).coerce_map_from(GF(5^2))) + Ring morphism: + From: Field in a with defining polynomial x^2 + 4*x + 2 over its base + To: Field in b with defining polynomial x^2 + (3 - a)*x + a over its base + Defn: a |--> a + """ + return RingExtensionHomomorphism(self, *args, **kwargs) diff --git a/src/sage/rings/ring_extension_morphism.pxd b/src/sage/rings/ring_extension_morphism.pxd new file mode 100644 index 00000000000..a02aff31a50 --- /dev/null +++ b/src/sage/rings/ring_extension_morphism.pxd @@ -0,0 +1,34 @@ +from sage.structure.element cimport Element +from sage.categories.map cimport Map +from sage.rings.morphism cimport RingMap +from sage.rings.ring_extension_element cimport RingExtensionElement + + +cdef are_equal_morphisms(f, g) + + +cdef class RingExtensionHomomorphism(RingMap): + cdef _backend + cdef _im_gens + cdef _base_map_construction + +cdef class RingExtensionBackendIsomorphism(RingExtensionHomomorphism): + pass + +cdef class RingExtensionBackendReverseIsomorphism(RingExtensionHomomorphism): + pass + +cdef class MapFreeModuleToRelativeRing(Map): + cdef _degree + cdef list _basis + cdef Map _f + +cdef class MapRelativeRingToFreeModule(Map): + cdef _degree + cdef list _basis + cdef _dimK + cdef Map _iK + cdef Map _jL + cdef _matrix + + cdef list backend_coefficients(self, RingExtensionElement x) diff --git a/src/sage/rings/ring_extension_morphism.pyx b/src/sage/rings/ring_extension_morphism.pyx new file mode 100644 index 00000000000..463a07276a6 --- /dev/null +++ b/src/sage/rings/ring_extension_morphism.pyx @@ -0,0 +1,930 @@ +r""" +Morphisms between extension of rings + +AUTHOR: + +- Xavier Caruso (2019) +""" + +############################################################################# +# Copyright (C) 2019 Xavier Caruso +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#**************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.structure.richcmp import op_EQ, op_NE + +from sage.structure.element cimport Element +from sage.categories.map import Map +from sage.rings.ring cimport CommutativeRing +from sage.rings.morphism cimport RingMap +from sage.rings.ring_extension cimport RingExtension_generic, RingExtensionWithBasis +from sage.rings.ring_extension_element cimport RingExtensionElement +from sage.rings.ring_extension_conversion cimport backend_parent, backend_element, backend_morphism + + +# I don't trust the operator == +cdef are_equal_morphisms(f, g): + r""" + Return ``True`` if ``f`` and ``g`` coincide on the + generators of the domain, ``False`` otherwise. + + INPUT: + + - ``f`` - a ring homomorphism or ``None``; if ``None``, + we consider that ``f`` is a coercion map + + - ``g`` - a ring homomorphism or ``None`` + + TESTS:: + + sage: S. = QQ[] + sage: T. = S[] + sage: TT = T.over(QQ) + sage: H = End(TT) + + sage: cc = S.hom([-x]) + sage: f = T.hom([x^2 + y^2], base_map=cc) + sage: g = T.hom([x^2 + y^2]) + + sage: H(f) == H(g) # indirect doctest + False + sage: H(f^2) == H(g^2) # indirect doctest + True + """ + cdef CommutativeRing b + cdef tuple gens + if f is None and g is None: + return True + if f is None: + f, g = g, f + gens = tuple() + b = f.domain() + while b is not b._base: + gens += b.gens() + b = b._base + if g is None: + for x in gens: + if f(x) != x: return False + else: + for x in gens: + if f(x) != g(x): return False + return True + + +cdef class RingExtensionHomomorphism(RingMap): + r""" + A class for ring homomorphisms between extensions. + + TESTS:: + + sage: K. = GF(5^2).over() + sage: L. = GF(5^4).over(K) + sage: phi = L.hom([b^5, a^5]) + sage: phi + Ring endomorphism of Field in b with defining polynomial x^2 + (3 - a)*x + a over its base + Defn: b |--> (2 + a) + 2*b + with map on base ring: + a |--> 1 - a + + sage: type(phi) + + + sage: TestSuite(phi).run() + + """ + def __init__(self, parent, defn, base_map=None, check=True): + r""" + Initialize this morphism. + + INPUT: + + - ``defn`` -- the definition of the morphism (either a map or images of generators) + + - ``base_map`` -- a ring homomorphism or ``None`` (default: ``None``); + the action of this morphism on one of the bases of the domain; + if ``None``, a coercion map is used + + - ``check`` -- a boolean (default: ``True``); whether to check if + the given data define a valid homomorphism + + TESTS:: + + sage: S. = QQ[] + sage: T. = QQ[] + sage: f = T.hom([x^2, y^2]) + sage: f + Ring endomorphism of Multivariate Polynomial Ring in x, y over Rational Field + Defn: x |--> x^2 + y |--> y^2 + + sage: TT = T.over(QQ) + sage: End(TT)(f) + Ring endomorphism of Multivariate Polynomial Ring in x, y over Rational Field over its base + Defn: x |--> x^2 + y |--> y^2 + + sage: TT = T.over(S) + sage: End(TT)(f) + Ring endomorphism of Multivariate Polynomial Ring in x, y over Rational Field over its base + Defn: y |--> y^2 + with map on base ring: + x |--> x^2 + """ + RingMap.__init__(self, parent) + domain = self.domain() + backend_domain = backend_parent(domain) + codomain = self.codomain() + backend_codomain = backend_parent(codomain) + # We construct the backend morphism + if isinstance(defn, Map): + if base_map is not None: + raise ValueError("base_map cannot be set when passing in the backend morphism") + backend = backend_morphism(defn) + if backend.domain() is not backend_domain: + raise TypeError("the domain of the backend morphism is not correct") + if backend.codomain() is not backend_codomain: + raise TypeError("the codomain of the backend morphism is not correct") + self._backend = backend + self._im_gens = None + self._base_map_construction = False + elif isinstance(defn, (list, tuple)): + # We figure out what is the base + if base_map is not None: + base = base_map.domain() + gens = domain.gens(base) + else: + base = domain + gens = tuple([]) + while True: + if len(gens) == len(defn): + break + if len(gens) > len(defn) or base is base.base_ring(): + raise ValueError("the number of images does not match the number of generators") + gens += base.gens() + base = base.base_ring() + # We construct the backend morphism + im_gens = [ codomain(x) for x in defn ] + backend_bases = [ backend_domain ] + b = backend_domain.base_ring() + while b is not b.base_ring(): + backend_bases.append(b) + b = b.base_ring() + backend_bases.reverse() + current_morphism = None + for current_domain in backend_bases: + current_im_gens = [ ] + for x in current_domain.gens(): + pol = domain(backend_domain(x)).polynomial(base) + if base_map is not None: + pol = pol.map_coefficients(base_map) + y = pol(im_gens) + current_im_gens.append(backend_element(y)) + current_morphism = current_domain.hom(current_im_gens, base_map=current_morphism, check=check) + # We check that everything went well + if check: + for i in range(len(gens)): + x = backend_element(domain(gens[i])) + y = backend_element(im_gens[i]) + if current_morphism(x) != y: + raise ValueError("images do not define a valid homomorphism") + coercion_morphism = backend_morphism(domain.defining_morphism(base)) + if base_map is None: + backend_base_map = coercion_morphism + else: + backend_base_map = backend_morphism(base_map) + restriction_current_morphism = current_morphism * coercion_morphism + if not are_equal_morphisms(restriction_current_morphism, backend_base_map): + raise ValueError("images do not define a valid homomorphism") + self._backend = current_morphism + self._im_gens = im_gens[:domain.ngens()] + if base is domain.base_ring(): + self._base_map_construction = base_map + else: + self._base_map_construction = { + 'im_gens': defn[domain.ngens():], + 'base_map': base_map, + 'check': False + } + else: + raise TypeError + + def _repr_type(self): + r""" + Return a string that describes the type of this morphism. + + EXAMPLES:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: f = K.hom([a^5]) + sage: f + Ring endomorphism of Field in a with defining polynomial x^2 + 4*x + 2 over its base + Defn: a |--> 1 - a + + sage: f._repr_type() + 'Ring' + """ + return "Ring" + + cpdef Element _call_(self, x): + r""" + Return the image of ``x`` under this morphism. + + INPUT: + + - ``x`` -- an element in the domain of this morphism + + EXAMPLES:: + + sage: A. = QQ.extension(x^2 - 2) + sage: K. = A.over() + sage: f = K.hom([-sqrt2]) + sage: f + Ring endomorphism of Field in sqrt2 with defining polynomial x^2 - 2 over its base + Defn: sqrt2 |--> -sqrt2 + sage: f(sqrt2) + -sqrt2 + + TESTS:: + + sage: a = QQ.random_element() + sage: b = QQ.random_element() + sage: f(a + b*sqrt2) == a - b*sqrt2 + True + """ + y = self._backend(backend_element(x)) + if isinstance(self.codomain(), RingExtension_generic): + y = self._codomain(y) + return y + + @cached_method + def base_map(self): + r""" + Return the base map of this morphism + or just ``None`` if the base map is a coercion map. + + EXAMPLES:: + + sage: F = GF(5) + sage: K. = GF(5^2).over(F) + sage: L. = GF(5^6).over(K) + + We define the absolute Frobenius of L:: + + sage: FrobL = L.hom([b^5, a^5]) + sage: FrobL + Ring endomorphism of Field in b with defining polynomial x^3 + (2 + 2*a)*x - a over its base + Defn: b |--> (-1 + a) + (1 + 2*a)*b + a*b^2 + with map on base ring: + a |--> 1 - a + sage: FrobL.base_map() + Ring morphism: + From: Field in a with defining polynomial x^2 + 4*x + 2 over its base + To: Field in b with defining polynomial x^3 + (2 + 2*a)*x - a over its base + Defn: a |--> 1 - a + + The square of ``FrobL`` acts trivially on K; in other words, it has + a trivial base map:: + + sage: phi = FrobL^2 + sage: phi + Ring endomorphism of Field in b with defining polynomial x^3 + (2 + 2*a)*x - a over its base + Defn: b |--> 2 + 2*a*b + (2 - a)*b^2 + sage: phi.base_map() + + """ + domain = self.domain() + codomain = self.codomain() + base = domain.base_ring() + if base is base.base_ring(): + return None + base_map = self._base_map_construction + if base_map is False: + if base is domain: + base_map = None + else: + base_map = self * domain.coerce_map_from(base) + elif isinstance(base_map, dict): + base_map = base.hom(**self._base_map_construction) + if base_map is None: + return None + if (codomain.has_coerce_map_from(base) and + are_equal_morphisms(backend_morphism(base_map), + backend_morphism(codomain.coerce_map_from(base)))): + return None + if base_map.codomain() is not self.codomain(): + base_map = base_map.extend_codomain(self.codomain()) + return base_map + + cpdef _richcmp_(self, other, int op): + r""" + Compare this element with ``other`` according to + the rich comparison operator ``op``. + + INPUT: + + - ``other`` -- a morphism with the same codomain and codomain + + - ``op`` -- the comparison operator + + TESTS:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: L. = GF(5^6).over(K) + + sage: FrobK = K.hom([a^5]) + sage: FrobL = L.hom([b^5], base_map=FrobK) + + sage: FrobK^2 == End(K).identity() + True + sage: FrobL^6 == End(L).identity() + True + """ + eq = are_equal_morphisms(self._backend, backend_morphism(other)) + if op == op_EQ: + return eq + if op == op_NE: + return not eq + return NotImplemented + + def is_identity(self): + r""" + Return whether this morphism is the identity. + + EXAMPLES:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: FrobK = K.hom([a^5]) + sage: FrobK.is_identity() + False + sage: (FrobK^2).is_identity() + True + + Coercion maps are not considered as identity morphisms:: + + sage: L. = GF(5^6).over(K) + sage: iota = L.defining_morphism() + sage: iota + Ring morphism: + From: Field in a with defining polynomial x^2 + 4*x + 2 over its base + To: Field in b with defining polynomial x^3 + (2 + 2*a)*x - a over its base + Defn: a |--> a + sage: iota.is_identity() + False + """ + if self.domain() is not self.codomain(): + return False + return are_equal_morphisms(self._backend, None) + + def is_injective(self): + r""" + Return whether this morphism is injective. + + EXAMPLES:: + + sage: K = GF(5^10).over(GF(5^5)) + sage: iota = K.defining_morphism() + sage: iota + Ring morphism: + From: Finite Field in z5 of size 5^5 + To: Field in z10 with defining polynomial x^2 + (2*z5^3 + 2*z5^2 + 4*z5 + 4)*x + z5 over its base + Defn: z5 |--> z5 + sage: iota.is_injective() + True + + sage: K = GF(7).over(ZZ) + sage: iota = K.defining_morphism() + sage: iota + Ring morphism: + From: Integer Ring + To: Finite Field of size 7 over its base + Defn: 1 |--> 1 + sage: iota.is_injective() + False + """ + return self._backend.is_injective() + + def is_surjective(self): + r""" + Return whether this morphism is surjective. + + EXAMPLES:: + + sage: K = GF(5^10).over(GF(5^5)) + sage: iota = K.defining_morphism() + sage: iota + Ring morphism: + From: Finite Field in z5 of size 5^5 + To: Field in z10 with defining polynomial x^2 + (2*z5^3 + 2*z5^2 + 4*z5 + 4)*x + z5 over its base + Defn: z5 |--> z5 + sage: iota.is_surjective() + False + + sage: K = GF(7).over(ZZ) + sage: iota = K.defining_morphism() + sage: iota + Ring morphism: + From: Integer Ring + To: Finite Field of size 7 over its base + Defn: 1 |--> 1 + sage: iota.is_surjective() + True + """ + return self._backend.is_surjective() + + def _repr_defn(self): + r""" + Return a string definition of this morphism. + + By default, we show the action of the morphism on the + generators of the domain. + + EXAMPLES:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: L. = GF(5^6).over(K) + sage: FrobL = L.hom([b^5, a^5]) # absolute Frobenius + + sage: print(FrobL._repr_defn()) + b |--> (-1 + a) + (1 + 2*a)*b + a*b^2 + with map on base ring: + a |--> 1 - a + """ + import re + s = "" + gens = self.domain().gens() + if self._im_gens is None: + self._im_gens = [ self(x) for x in gens ] + for i in range(len(gens)): + s += "%s |--> %s\n" % (gens[i], self._im_gens[i]) + if self.base_map() is not None: + s += "with map on base ring" + ss = self.base_map()._repr_defn() + ss = re.sub('\nwith map on base ring:?$', '', ss, 0, re.MULTILINE) + if ss != "": s += ":\n" + ss + if s != "" and s[-1] == "\n": + s = s[:-1] + return s + + def _composition(self, right): + r""" + Return the composite ``self o right``. + + TESTS:: + + sage: A. = QQ.extension(x^2 - 5) + sage: K. = A.over() + sage: f = K.hom([-sqrt5]) + sage: f + Ring endomorphism of Field in sqrt5 with defining polynomial x^2 - 5 over its base + Defn: sqrt5 |--> -sqrt5 + + sage: f^2 # indirect doctest + Ring endomorphism of Field in sqrt5 with defining polynomial x^2 - 5 over its base + Defn: sqrt5 |--> sqrt5 + """ + domain = right.domain() + codomain = self.codomain() + backend_right = backend_morphism(right) + backend = self._backend * backend_right + if isinstance(domain, RingExtension_generic) or isinstance(codomain, RingExtension_generic): + return RingExtensionHomomorphism(domain.Hom(codomain), backend) + else: + return backend + + cdef _update_slots(self, dict _slots): + """ + Helper function for copying and pickling. + + TESTS:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: f = K.hom([a^5]) + + sage: g = copy(f) # indirect doctest + sage: f == g + True + sage: f is g + False + """ + self._backend = _slots['_backend'] + RingMap._update_slots(self, _slots) + + cdef dict _extra_slots(self): + """ + Helper function for copying and pickling. + + TESTS:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: f = K.hom([a^5]) + sage: loads(dumps(f)) == f + True + """ + slots = RingMap._extra_slots(self) + slots['_backend'] = self._backend + return slots + + +cdef class RingExtensionBackendIsomorphism(RingExtensionHomomorphism): + r""" + A class for implementating isomorphisms taking an element of the + backend to its ring extension. + + TESTS:: + + sage: K = GF(11^9).over(GF(11^3)) + sage: f = K.coerce_map_from(GF(11^9)) + sage: f + Coercion morphism: + From: Finite Field in z9 of size 11^9 + To: Field in z9 with defining polynomial x^3 + (9*z3^2 + 5*z3 + 1)*x^2 + (4*z3 + 3)*x + 10*z3 over its base + + sage: type(f) + + + sage: TestSuite(f).run() + """ + def __init__(self, parent): + r""" + Initialize this morphism. + + TESTS:: + + sage: A. = QQ.extension(x^2 - 5) + sage: K = A.over() + sage: K.coerce_map_from(A) + Coercion morphism: + From: Number Field in a with defining polynomial x^2 - 5 + To: Field in a with defining polynomial x^2 - 5 over its base + """ + RingMap.__init__(self, parent) + domain = self.domain() + self._backend = domain.Hom(domain).identity() + + def _repr_type(self): + r""" + Return a string that describes the type of this morphism. + + EXAMPLES:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: f = K.coerce_map_from(GF(5^2)) + sage: f + Coercion morphism: + From: Finite Field in z2 of size 5^2 + To: Field in a with defining polynomial x^2 + 4*x + 2 over its base + + sage: f._repr_type() + 'Coercion' + """ + return "Coercion" + + def _repr_defn(self): + r""" + Return the empty string since this morphism is canonical. + + EXAMPLES:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: f = K.coerce_map_from(GF(5^2)) + sage: f + Coercion morphism: + From: Finite Field in z2 of size 5^2 + To: Field in a with defining polynomial x^2 + 4*x + 2 over its base + + sage: f._repr_defn() + '' + """ + return "" + + cpdef Element _call_(self, x): + r""" + Return the image of ``x`` under this morphism. + + INPUT: + + - ``x`` -- an element in the domain of this morphism + + EXAMPLES:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: f = K.coerce_map_from(GF(5^2)) + sage: f(GF(5^2).gen()) + a + """ + codomain = self.codomain() + return codomain.element_class(codomain, x) + + +cdef class RingExtensionBackendReverseIsomorphism(RingExtensionHomomorphism): + r""" + A class for implementating isomorphisms from a ring extension to + its backend. + + TESTS:: + + sage: K = GF(11^9).over(GF(11^3)) + sage: f = GF(11^9).convert_map_from(K) + sage: f + Canonical morphism: + From: Field in z9 with defining polynomial x^3 + (9*z3^2 + 5*z3 + 1)*x^2 + (4*z3 + 3)*x + 10*z3 over its base + To: Finite Field in z9 of size 11^9 + + sage: type(f) + + + sage: TestSuite(f).run() + + """ + def __init__(self, parent): + r""" + Initialize this morphism. + + TESTS:: + + sage: A. = QQ.extension(x^2 - 5) + sage: K = A.over() + sage: A.convert_map_from(K) + Canonical morphism: + From: Field in a with defining polynomial x^2 - 5 over its base + To: Number Field in a with defining polynomial x^2 - 5 + """ + RingMap.__init__(self, parent) + codomain = self.codomain() + self._backend = codomain.Hom(codomain).identity() + + def _repr_type(self): + r""" + Return a string that describes the type of this morphism. + + EXAMPLES:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: f = GF(5^2).convert_map_from(K) + sage: f + Canonical morphism: + From: Field in a with defining polynomial x^2 + 4*x + 2 over its base + To: Finite Field in z2 of size 5^2 + + sage: f._repr_type() + 'Canonical' + """ + return "Canonical" + + def _repr_defn(self): + r""" + Return the empty string since this morphism is canonical. + + EXAMPLES:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: f = GF(5^2).convert_map_from(K) + sage: f + Canonical morphism: + From: Field in a with defining polynomial x^2 + 4*x + 2 over its base + To: Finite Field in z2 of size 5^2 + + sage: f._repr_defn() + '' + """ + return "" + + cpdef Element _call_(self, x): + r""" + Return the image of ``x`` under this morphism. + + INPUT: + + - ``x`` -- an element in the domain of this morphism + + EXAMPLES:: + + sage: K. = GF(5^2).over() # over GF(5) + sage: f = GF(5^2).convert_map_from(K) + sage: f(a) + z2 + """ + return (x)._backend + + +cdef class MapFreeModuleToRelativeRing(Map): + """ + Base class of the module isomorphism between a ring extension + and a free module over one of its bases. + + TESTS:: + + sage: K = GF(5^2).over() + sage: V, i, j = K.free_module() + sage: type(i) + + + """ + def __init__(self, E, K): + r""" + Initialize this morphism. + + INPUT: + + - ``E`` -- a ring extension + + - ``K`` -- a commutative ring; one base of ``E`` + + TESTS:: + + sage: K = GF(11^6).over(GF(11^3)) + sage: V, i, j = K.free_module() + sage: i + Generic map: + From: Vector space of dimension 2 over Finite Field in z3 of size 11^3 + To: Field in z6 with defining polynomial x^2 + (10*z3^2 + z3 + 6)*x + z3 over its base + """ + self._degree = E.degree(K) + self._basis = [ (x)._backend for x in E.basis_over(K) ] + self._f = backend_morphism(E.defining_morphism(K), forget="codomain") + domain = K ** self._degree + parent = domain.Hom(E) + Map.__init__(self, parent) + + def is_injective(self): + r""" + Return whether this morphism is injective. + + EXAMPLES:: + + sage: K = GF(11^6).over(GF(11^3)) + sage: V, i, j = K.free_module() + sage: i.is_injective() + True + """ + return True + + def is_surjective(self): + r""" + Return whether this morphism is surjective. + + EXAMPLES:: + + sage: K = GF(11^6).over(GF(11^3)) + sage: V, i, j = K.free_module() + sage: i.is_surjective() + True + """ + return True + + cpdef Element _call_(self, v): + r""" + Return the image of ``x`` under this morphism. + + INPUT: + + - ``x`` -- an element in the domain of this morphism + + EXAMPLES:: + + sage: K. = GF(11^6).over(GF(11^3)) + sage: V, i, j = K.free_module() + sage: i((0,1)) + a + """ + cdef Element elt + elt = self._f(v[0]) * self._basis[0] + for i in range(1, self._degree): + elt += self._f(v[i]) * self._basis[i] + return self.codomain()(elt) + + +cdef class MapRelativeRingToFreeModule(Map): + """ + Base class of the module isomorphism between a ring extension + and a free module over one of its bases. + + TESTS:: + + sage: K = GF(5^2).over() + sage: V, i, j = K.free_module() + sage: type(j) + + + """ + def __init__(self, E, K): + r""" + Initialize this morphism. + + INPUT: + + - ``E`` -- a ring extension + + - ``K`` -- a commutative ring; one base of ``E`` + + TESTS:: + + sage: K = GF(11^6).over(GF(11^3)) + sage: V, i, j = K.free_module() + sage: j + Generic map: + From: Field in z6 with defining polynomial x^2 + (10*z3^2 + z3 + 6)*x + z3 over its base + To: Vector space of dimension 2 over Finite Field in z3 of size 11^3 + """ + cdef CommutativeRing L, base + + self._degree = (E)._degree_over(K) + self._basis = [ (x)._backend for x in E.basis_over(K) ] + f = backend_morphism(E.defining_morphism(K), forget="codomain") + codomain = K ** self._degree + Map.__init__(self, E.Hom(codomain)) + + K = backend_parent(K) + L = (E)._backend + + # We compute the matrix of our isomorphism (over base) + from sage.rings.ring_extension import common_base + base = common_base(K, L, False) + EK, iK, jK = K.free_module(base, map=True) + EL, iL, jL = L.free_module(base, map=True) + + self._dimK = EK.dimension() + self._iK = iK + self._jL = jL + + M = [ ] + for x in self._basis: + for v in EK.basis(): + y = x * f(iK(v)) + M.append(jL(y)) + from sage.matrix.matrix_space import MatrixSpace + self._matrix = MatrixSpace(base,len(M))(M).inverse_of_unit() + + def is_injective(self): + r""" + Return whether this morphism is injective. + + EXAMPLES:: + + sage: K = GF(11^6).over(GF(11^3)) + sage: V, i, j = K.free_module() + sage: j.is_injective() + True + """ + return True + + def is_surjective(self): + r""" + Return whether this morphism is injective. + + EXAMPLES:: + + sage: K = GF(11^6).over(GF(11^3)) + sage: V, i, j = K.free_module() + sage: j.is_surjective() + True + """ + return True + + cpdef Element _call_(self, x): + r""" + Return the image of ``x`` under this morphism. + + INPUT: + + - ``x`` -- an element in the domain of this morphism + + EXAMPLES:: + + sage: K. = GF(11^6).over(GF(11^3)) + sage: V, i, j = K.free_module() + sage: j(a) + (0, 1) + """ + coeffs = self.backend_coefficients(x) + return self.codomain()(coeffs) + + cdef list backend_coefficients(self, RingExtensionElement x): + r""" + Return the coordinates of the image of ``x`` + as elements of the backend ring. + + INPUT: + + - ``x`` -- an element in the domain of this morphism + + TESTS:: + + sage: K. = GF(11^9).over(GF(11^3)) + sage: V, i, j = K.free_module() + sage: j(a + 2*a^2) # indirect doctest + (0, 1, 2) + """ + cdef list coeffs = [ ] + dK = self._dimK + w = (self._jL(x._backend) * self._matrix).list() + for i in range(self._degree): + coeff = self._iK(w[i*dK:(i+1)*dK]) + coeffs.append(coeff) + return coeffs diff --git a/src/sage/schemes/product_projective/rational_point.py b/src/sage/schemes/product_projective/rational_point.py index efa271a9dbf..2a47b8ab165 100644 --- a/src/sage/schemes/product_projective/rational_point.py +++ b/src/sage/schemes/product_projective/rational_point.py @@ -59,13 +59,14 @@ from sage.schemes.product_projective.space import is_ProductProjectiveSpaces from sage.misc.mrange import xmrange from sage.misc.all import prod -from sage.arith.all import gcd, srange, next_prime, previous_prime, crt +from sage.arith.all import next_prime, previous_prime, crt from sage.rings.all import ZZ, RR from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.parallel.ncpus import ncpus from sage.parallel.use_fork import p_iter_fork from sage.matrix.constructor import matrix + def enum_product_projective_rational_field(X, B): r""" Enumerate projective, rational points on scheme ``X`` of height up to diff --git a/src/sage/tensor/modules/tensor_with_indices.py b/src/sage/tensor/modules/tensor_with_indices.py index d0c6f9416ad..0b87ff828bb 100644 --- a/src/sage/tensor/modules/tensor_with_indices.py +++ b/src/sage/tensor/modules/tensor_with_indices.py @@ -19,7 +19,9 @@ #****************************************************************************** from sage.structure.sage_object import SageObject +from sage.groups.perm_gps.permgroup import PermutationGroup import re +from itertools import combinations class TensorWithIndices(SageObject): r""" @@ -153,7 +155,36 @@ class TensorWithIndices(SageObject): sage: s == a.contract(0,1, b, 0,1) True - Some minimal arithmetics:: + The square bracket operator acts in a similar way on :class:`TensorWithIndices`:: + + sage: b = +a["ij"] ; b._tensor.set_name("b") # create a copy of a["ij"] + sage: b + b^ij + sage: b[:] + [1 2 3] + [4 5 6] + [7 8 9] + sage: b[0,0] == 1 + True + sage: b["ji"] + b^ji + sage: b["(ij)"][:] + [1 3 5] + [3 5 7] + [5 7 9] + sage: b["(ij)"] == b["(ij)"]["ij"] + True + + However, it keeps track of indices:: + + sage: b["ij"] = a["ji"] + sage: b[:] == a[:] + False + sage: b[:] == a[:].transpose() + True + + + Arithmetics:: sage: 2*a['^ij'] X^ij @@ -169,6 +200,19 @@ class TensorWithIndices(SageObject): -a^ij sage: -t['ij_kl'] -t^ij_kl + sage: a["^(..)"]["ij"] == 1/2*(a["^ij"] + a["^ji"]) + True + + The output indices are the ones of the left term of the addition:: + + sage: a["^(..)"]["ji"] == 1/2*(a["^ij"] + a["^ji"]) + False + sage: (a*a)["^..(ij)"]["abij"] == 1/2*((a*a)["^abij"] + (a*a)["^abji"]) + True + sage: c = 1/2*((a*a)["^abij"] + (a*a)["^ijab"]) + sage: from itertools import product + sage: all(c[i,j,k,l] == c[k,l,i,j] for i,j,k,l in product(range(3),repeat=4)) + True Conventions are checked and non acceptable indices raise ``ValueError``, for instance:: @@ -195,6 +239,130 @@ class TensorWithIndices(SageObject): ValueError: index conventions not satisfied """ + + @staticmethod + def _parse_indices(indices, tensor_type=None, allow_contraction=True, + allow_symmetries=True): + r""" + Parse index notation for tensors, enforces conventions and return + indices. + + Parse ``indices`` checking usual conventions on repeating indices, + wildcard, balanced parentheses/brackets and raises a ValueError if not. + Return a couple contravariant/covariant indices. + + INPUT: + + - ``indices`` -- a string of index notation + - ``tensor_type`` -- (default : ``None``) a valid tensor type + (a couple of non-negative integers). If not ``None``, the indices + are checked to have the correct type. + - ``allow_contraction`` -- (default : ``True``) Determines if + repeated indices are allowed in the index notation. + - ``allow_symmetries`` -- (default : ``True``) Determines if + symmetries ()/[] are allowed in the index notation. + + OUTPUT: + + - A couple of string corresponding to the contravariant and the + covariant part + + TESTS:: + + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices + sage: TensorWithIndices._parse_indices('([..])') # nested symmetries + Traceback (most recent call last): + ... + ValueError: index conventions not satisfied + sage: TensorWithIndices._parse_indices('(..') # unbalanced parenthis + Traceback (most recent call last): + ... + ValueError: index conventions not satisfied + sage: TensorWithIndices._parse_indices('ii') # repeated indices of the same type + Traceback (most recent call last): + ... + ValueError: index conventions not satisfied: repeated indices of same type + sage: TensorWithIndices._parse_indices('^(ij)^(kl)') # multiple indices group of the same type + Traceback (most recent call last): + ... + ValueError: index conventions not satisfied + sage: TensorWithIndices._parse_indices("^éa") # accentuated index name + Traceback (most recent call last): + ... + ValueError: index conventions not satisfied + sage: TensorWithIndices._parse_indices('^ij_kl') + ('ij', 'kl') + sage: TensorWithIndices._parse_indices('_kl^ij') + ('ij', 'kl') + sage: TensorWithIndices._parse_indices("(ij)_ik",tensor_type=(2,2)) + ('(ij)', 'ik') + sage: TensorWithIndices._parse_indices("(ij)_ik",tensor_type=(2,0)) + Traceback (most recent call last): + ... + IndexError: number of covavariant indices not compatible with the tensor type + sage: TensorWithIndices._parse_indices("(ij)_ik", allow_contraction=False) + Traceback (most recent call last): + ... + IndexError: no contraction allowed + sage: TensorWithIndices._parse_indices("(ij)_ik", allow_symmetries=False) + Traceback (most recent call last): + ... + IndexError: no symmetry allowed + + """ + # Suppress all '{' and '}' coming from LaTeX notations: + indices = indices.replace('{','').replace('}','') + + # Check index notation conventions and parse indices + allowed_pattern = r"(\([a-zA-Z.]{2,}\)|\[[a-zA-Z.]{2,}\]|[a-zA-Z.]+)*" + con_then_cov = r"^(\^|)" + allowed_pattern + r"(\_" + allowed_pattern + r"|)$" + cov_then_con = r"^\_" + allowed_pattern + r"(\^" + allowed_pattern + r"|)$" + if (re.match(con_then_cov,indices) is None + and re.match(cov_then_con,indices) is None): + raise ValueError("index conventions not satisfied") + elif re.match(con_then_cov,indices): + try: + con,cov = indices.replace("^","").split("_") + except ValueError: + con = indices.replace("^","") + cov = "" + else: + try: + cov,con = indices.replace("_","").split("^") + except ValueError: + cov = indices.replace("_","") + con = "" + if not allow_contraction: + for ind in con: + if ind != '.' and ind in cov: + raise IndexError("no contraction allowed") + con_without_sym = (con.replace("(","").replace(")","").replace("[","").replace("]","")) + cov_without_sym = (cov.replace("(","").replace(")","").replace("[","").replace("]","")) + if allow_symmetries: + if len(con_without_sym) != len(set(con_without_sym)) \ + + max(con_without_sym.count(".")-1, 0): + raise ValueError("index conventions not satisfied: " + "repeated indices of same type") + if len(cov_without_sym) != len(set(cov_without_sym)) \ + + max(cov_without_sym.count(".")-1, 0): + raise ValueError("index conventions not satisfied: " + "repeated indices of same type") + else: + if re.search(r"[()\[\]]",con) is not None: + raise IndexError("no symmetry allowed") + if re.search(r"[()\[\]]",cov) is not None: + raise IndexError("no symmetry allowed") + if tensor_type is not None: + # Check number of (co/contra)variant indices + if len(con_without_sym) != tensor_type[0]: + raise IndexError("number of contravariant indices not compatible " + "with the tensor type") + if len(cov_without_sym) != tensor_type[1]: + raise IndexError("number of covavariant indices not compatible " + "with the tensor type") + return con,cov + + def __init__(self, tensor, indices): r""" TESTS:: @@ -209,6 +377,8 @@ def __init__(self, tensor, indices): sage: TestSuite(ti).run(skip="_test_pickling") + :: + sage: e = M.basis('e') sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], ....: [[10,-11,12], [13,14,-15], [16,17,18]], @@ -232,47 +402,10 @@ def __init__(self, tensor, indices): # "^{ijkl}_{ib(cd)}" # For now authorized symbol list only includes a-z and A-Z - # Suppress all '{' and '}' coming from LaTeX notations: - indices = indices.replace('{','').replace('}','') - - # Check index notation conventions and parse indices - allowed_pattern = r"(\([a-zA-Z.]{2,}\)|\[[a-zA-Z.]{2,}\]|[a-zA-Z.]+)*" - con_then_cov = r"^(\^|)" + allowed_pattern + r"(\_" + allowed_pattern + r"|)$" - cov_then_con = r"^\_" + allowed_pattern + r"(\^" + allowed_pattern + r"|)$" - if (re.match(con_then_cov,indices) is None - and re.match(cov_then_con,indices) is None): - raise ValueError("index conventions not satisfied") - elif re.match(con_then_cov,indices): - try: - con,cov = indices.replace("^","").split("_") - except ValueError: - con = indices.replace("^","") - cov = "" - else: - try: - cov,con = indices.replace("_","").split("^") - except ValueError: - cov = indices.replace("_","") - con = "" - - con_without_sym = (con.replace("(","").replace(")","").replace("[","").replace("]","")) - cov_without_sym = (cov.replace("(","").replace(")","").replace("[","").replace("]","")) - if len(con_without_sym) != len(set(con_without_sym)) \ - + max(con_without_sym.count(".")-1, 0): - raise ValueError("index conventions not satisfied: " - "repeated indices of same type") - if len(cov_without_sym) != len(set(cov_without_sym)) \ - + max(cov_without_sym.count(".")-1, 0): - raise ValueError("index conventions not satisfied: " - "repeated indices of same type") - - # Check number of (co/contra)variant indices - if len(con_without_sym) != tensor._tensor_type[0]: - raise IndexError("number of contravariant indices not compatible " - "with the tensor type") - if len(cov_without_sym) != tensor._tensor_type[1]: - raise IndexError("number of covavariant indices not compatible " - "with the tensor type") + con,cov = self._parse_indices( + indices, + tensor_type = self._tensor.tensor_type() + ) # Apply (anti)symmetrizations on contravariant indices first_sym_regex = r"(\(|\[)[a-zA-Z.]*[)\]]" @@ -398,7 +531,7 @@ def update(self): return self def __eq__(self, other): - """ + r""" Check equality. TESTS:: @@ -416,6 +549,7 @@ def __eq__(self, other): Traceback (most recent call last): ... ValueError: no common basis for the comparison + """ if not isinstance(other, TensorWithIndices): return False @@ -424,7 +558,7 @@ def __eq__(self, other): and self._cov == other._cov) def __ne__(self, other): - """ + r""" Check not equals. TESTS:: @@ -437,6 +571,7 @@ def __ne__(self, other): False sage: ti != TensorWithIndices(t, 'ac_b') True + """ return not self == other @@ -525,6 +660,335 @@ def __rmul__(self, other): return TensorWithIndices(other*self._tensor, self._con + '_' + self._cov) + def __add__(self, other): + r""" + Addition between tensors with indices. + + The underlying tensor of the ouput is the sum of the underlying tensor + of ``self`` with the underlying tensor of ``other`` whose entries have + be permuted to respect Einstein summation usual conventions. The + indices names of the output are those of self. + + + TESTS:: + + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: b = M.tensor((0,2), name='b') + sage: b[:] = [[-1,2,-3], [-4,5,6], [7,-8,9]] + sage: T = a*a*b*b + sage: 1/4*(T["ijkl_abcd"] + T["jikl_abcd"] + T["ijkl_abdc"]\ + + T["jikl_abdc"]) == T["(..).._..(..)"]["ijkl_abcd"] + True + + """ + # Check tensor types are compatible + if self._tensor.tensor_type() != other._tensor.tensor_type(): + raise ValueError("Tensors are not of the same type") + # Check the set of indices are compatible + if set(self._cov) != set(other._cov): + raise ValueError("The covariant Indices sets are not identical") + if set(self._con) != set(other._con): + raise ValueError("The contravariant Indices sets are not identical") + self_wild_card_indices = [match.span()[0] for match in re.finditer(r"\.", self._con)] + other_wild_card_indices = [match.span()[0] for match in re.finditer(r"\.", self._cov)] + if self_wild_card_indices != other_wild_card_indices: + raise ValueError("Ambiguous wildcard notation") + + # Permutation of the components of self + # ------------------------------------- + + permutation = list(range(other._tensor.tensor_rank())) + for other_index in range(other._tensor.tensor_type()[0]): + if other._con[other_index] == self._con[other_index]: + permutation[other_index] = other_index + else: + permutation[other_index] = self._con.index(other._con[other_index]) + for other_index in range(other._tensor.tensor_type()[1]): + if other._cov[other_index] == self._cov[other_index]: + permutation[other._tensor.tensor_type()[0] + other_index]\ + = other._tensor.tensor_type()[0] + other_index + else: + permutation[other._tensor.tensor_type()[0] + other_index]\ + = other._tensor.tensor_type()[0] + self._cov.index(other._cov[other_index]) + + result = self.__pos__() + result._tensor = result._tensor + other.permute_indices(permutation)._tensor + return result + + + def __sub__(self, other): + r""" + Substraction between tensors with indices. + + The underlying tensor of the ouput is the underlying tensor of + ``self`` minus the underlying tensor of ``other`` whose entries have + be permuted to respect Einstein summation usual conventions. The + indices names of the output are those of self. + + EXAMPLES:: + + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: b = M.tensor((0,2), name='b') + sage: b[:] = [[-1,2,-3], [-4,5,6], [7,-8,9]] + sage: a["^[..]"]["ij"] == 1/2*(a["^ij"]-a["^ji"]) + True + sage: (a*a)["^..[ij]"]["abij"] == 1/2*((a*a)["^abij"]-(a*a)["^abji"]) + True + sage: Riem = a*a + sage: Riem = Riem["[ij][kl]"] + sage: Riem = 1/2*(Riem["ijkl"]+Riem["klij"]) + sage: O = M.tensor((4,0), name='O') + sage: O[0,0,0,0] = 0 + sage: (Riem["ijkl"]+Riem["iklj"]+Riem["iljk"]) == O["ijkl"] + True + + TESTS:: + + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: b = M.tensor((0,2), name='b') + sage: b[:] = [[-1,2,-3], [-4,5,6], [7,-8,9]] + sage: T = a*a*b*b + sage: 1/4*(T["ijkl_abcd"]-T["jikl_abcd"] - T["ijkl_abdc"]\ + + T["jikl_abdc"] ) == T["[..].._..[..]"]["ijkl_abcd"] + True + + """ + return self + (-other) + + def __getitem__(self, args): + r""" + Return a component of the underlying tensor w.r.t. some basis. + + NB: if ``args`` is a string, this method acts as a shortcut for + tensor contractions and symmetrizations, the string containing + abstract indices. + + INPUT: + + - ``args`` -- list of indices defining the component; if ``[:]`` is + provided, all the components are returned. The basis can be passed + as the first item of ``args``; if not, the free module's default + basis is assumed. + if ``args`` is a string, this method acts as a shortcut for + tensor contractions and symmetrizations, the string containing + abstract indices. + + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: b = a["ij"] + sage: b + a^ij + sage: b[:] + [1 2 3] + [4 5 6] + [7 8 9] + sage: b[0,0] == 1 + True + sage: b["ji"] + a^ji + sage: b["(ij)"][:] + [1 3 5] + [3 5 7] + [5 7 9] + + """ + if isinstance(args, str): + result = +self + result.__init__(self._tensor, args) + return result + else: + return self._tensor[args] + + def __setitem__(self, args, value): + r""" + Set a component w.r.t. some basis. + + INPUT: + + - ``args`` -- list of indices defining the component; if ``[:]`` is + provided, all the components are set. The basis can be passed + as the first item of ``args``; if not, the free module's default + basis is assumed if ``args`` is a string and value is a tensor + with indices, this method permutes the coefficients of ``value`` + before assigning the underlying tensor of ``value`` to ``self``. + + - ``value`` -- the value to be set or a list of values if + ``args = [:]`` or a tensor with indices + + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a')["ij"] + sage: b = M.tensor((2,0), name='b')["ij"] + sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: b["ij"] = a["ji"] + sage: b[:] == a[:].transpose() + True + + """ + if isinstance(args, str): + if not isinstance(value,TensorWithIndices): + raise ValueError("The tensor provided should be with indices") + elif self._tensor.tensor_type() != value._tensor.tensor_type(): + raise ValueError("The tensors are not of the same type") + else: + con,cov = self._parse_indices( + args, + tensor_type=self._tensor.tensor_type(), + allow_symmetries=False, + allow_contraction=False + ) + + permutation = list(range(value._tensor.tensor_rank())) + for value_index in range(value._tensor.tensor_type()[0]): + if value._con[value_index] == self._con[value_index]: + permutation[value_index] = value_index + else: + permutation[value_index] = self._con.index(value._con[value_index]) + for value_index in range(value._tensor.tensor_type()[1]): + if value._cov[value_index] == self._cov[value_index]: + permutation[value._tensor.tensor_type()[0] + value_index]\ + = value._tensor.tensor_type()[0] + value_index + else: + permutation[value._tensor.tensor_type()[0] + value_index]\ + = value._tensor.tensor_type()[0] + self._cov.index(value._cov[value_index]) + self._tensor[:] = value.permute_indices(permutation)[:] + + else: + self._tensor.__setitem__(args,value) + + def permute_indices(self, permutation): + r""" + Return a tensor with indices with permuted indices. + + INPUT: + + - ``permutation`` -- permutation that has to be applied to the indices + the input should be a ``list`` containing the second line of the permutation + in Cauchy notation. + + OUTPUT: + + - an instance of ``TensorWithIndices`` whose indices names and place + are those of ``self`` but whose components have been permuted with + ``permutation``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: b = M.tensor((2,0), name='b') + sage: b[:] = [[-1,2,-3], [-4,5,6], [7,-8,9]] + sage: identity = [0,1] + sage: transposition = [1,0] + sage: a["ij"].permute_indices(identity) == a["ij"] + True + sage: a["ij"].permute_indices(transposition)[:] == a[:].transpose() + True + sage: cycle = [1,2,3,0] # the cyclic permutation sending 0 to 1 + sage: (a*b)[0,1,2,0] == (a*b)["ijkl"].permute_indices(cycle)[1,2,0,0] + True + + TESTS:: + + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: identity = [0,1] + sage: transposition = [1,0] + sage: a["ij"].permute_indices(identity) == a["ij"] + True + sage: a["ij"].permute_indices(transposition)[:] == a[:].transpose() + True + sage: (a*a)["ijkl"].permute_indices([1,2,3,0])[0,1,2,1] == (a*a)[1,2,1,0] + True + """ + # Decomposition of the permutation of the components of self + # into product of swaps given by the method + # sage.tensor.modules.comp.Components.swap_adjacent_indices + + # A swap is determined by 3 distinct integers + swap_params = list(combinations(range(self._tensor.tensor_rank()+1), 3)) + + # The associated permutation is as follows + def swap(param,N): + i,j,k = param + L = list(range(1,N+1)) + L = L[:i] + L[j:k] + L[i:j] + L[k:] + return L + + # Construction of the permutation group generated by swaps + perm_group = PermutationGroup( + [swap(param, self._tensor.tensor_rank()) for param in swap_params], + canonicalize = False + ) + # Compute a decomposition of the permutation as a product of swaps + decomposition_as_string = perm_group([x+1 for x in permutation]).word_problem( + perm_group.gens(), + display=False + )[0] + + if decomposition_as_string != "": + decomposition_as_string = [ + # Two cases wether the term appear with an exponent or not + ("^" in term)*term.split("^") + ("^" not in term)*(term.split("^")+['1']) + for term in decomposition_as_string.replace("x","").split("*") + ] + decomposition = [(swap_params[int(x)-1], int(y)) for x,y in decomposition_as_string] + decomposition.reverse() # /!\ The symetric group acts on the right by default /!\. + else: + decomposition = [] + # Choice of a basis + basis = self._tensor._fmodule._def_basis + + # Swap of components + + swaped_components = self._tensor.comp(basis) + for swap_param,exponent in decomposition: + if exponent > 0: + for i in range(exponent): + # Apply the swap given by swap_param + swaped_components = swaped_components\ + .swap_adjacent_indices(*swap_param) + elif exponent < 0: + for i in range(-exponent): + # Apply the opposite of the swap given by swap_param + swaped_components = swaped_components\ + .swap_adjacent_indices( + swap_param[0], + swap_param[0] + swap_param[2] - swap_param[1], + swap_param[2] + ) + else: + pass + result = self.__pos__() + result._tensor = self._tensor._fmodule.tensor_from_comp( + self._tensor.tensor_type(), + swaped_components + ) + + return result + def __pos__(self): r""" Unary plus operator. diff --git a/src/sage/tests/cmdline.py b/src/sage/tests/cmdline.py index c88e7315020..38388664b1b 100644 --- a/src/sage/tests/cmdline.py +++ b/src/sage/tests/cmdline.py @@ -57,12 +57,13 @@ """ from subprocess import Popen, PIPE import os +import sys import select import six -def test_executable(args, input="", timeout=100.0, **kwds): +def test_executable(args, input="", timeout=100.0, pydebug_ignore_warnings=False, **kwds): r""" Run the program defined by ``args`` using the string ``input`` on the standard input. @@ -78,6 +79,13 @@ def test_executable(args, input="", timeout=100.0, **kwds): - ``timeout`` -- if the program produces no output for ``timeout`` seconds, a RuntimeError is raised. + - ``pydebug_ignore_warnings`` -- boolean. Set the PYTHONWARNINGS environment variable to ignore + Python warnings when on a Python debug build (`--with-pydebug`, e.g. from building with + `SAGE_DEBUG=yes`). Debug builds do not install the default warning filters, which can break + some doctests. Unfortunately the environment variable does not support regex message filters, + so the filter will catch a bit more than the default filters. Hence we only enable it on debug + builds. + - ``**kwds`` -- Additional keyword arguments passed to the :class:`Popen` constructor. @@ -107,7 +115,7 @@ def test_executable(args, input="", timeout=100.0, **kwds): Run Sage itself with various options:: - sage: (out, err, ret) = test_executable(["sage"]) + sage: (out, err, ret) = test_executable(["sage"], pydebug_ignore_warnings=True) sage: out.find(version()) >= 0 True sage: err @@ -115,7 +123,7 @@ def test_executable(args, input="", timeout=100.0, **kwds): sage: ret 0 - sage: (out, err, ret) = test_executable(["sage"], "3^33\n") + sage: (out, err, ret) = test_executable(["sage"], "3^33\n", pydebug_ignore_warnings=True) sage: out.find(version()) >= 0 True sage: out.find("5559060566555523") >= 0 @@ -125,7 +133,7 @@ def test_executable(args, input="", timeout=100.0, **kwds): sage: ret 0 - sage: (out, err, ret) = test_executable(["sage", "-q"], "3^33\n") + sage: (out, err, ret) = test_executable(["sage", "-q"], "3^33\n", pydebug_ignore_warnings=True) sage: out.find(version()) >= 0 False sage: out.find("5559060566555523") >= 0 @@ -285,14 +293,14 @@ def test_executable(args, input="", timeout=100.0, **kwds): sage: F = open(fullname, 'w') sage: _ = F.write("from cysignals.signals cimport *\nfrom sage.rings.integer cimport Integer\ncdef long i, s = 0\nsig_on()\nfor i in range(1000): s += i\nsig_off()\nprint(Integer(s))") sage: F.close() - sage: (out, err, ret) = test_executable(["sage", fullname]) + sage: (out, err, ret) = test_executable(["sage", fullname], pydebug_ignore_warnings=True) sage: print(out) 499500 sage: err 'Compiling ...spyx...' sage: ret 0 - sage: (out, err, ret) = test_executable(["sage", name], cwd=dir) + sage: (out, err, ret) = test_executable(["sage", name], cwd=dir, pydebug_ignore_warnings=True) sage: print(out) 499500 sage: err @@ -477,7 +485,7 @@ def test_executable(args, input="", timeout=100.0, **kwds): sage: ret 42 - sage: (out, err, ret) = test_executable(["sage", "--ipython"], "\n3**33\n") + sage: (out, err, ret) = test_executable(["sage", "--ipython"], "\n3**33\n", pydebug_ignore_warnings=True) sage: out.find("5559060566555523") >= 0 True sage: err @@ -850,6 +858,12 @@ def test_executable(args, input="", timeout=100.0, **kwds): except KeyError: pass + __with_pydebug = hasattr(sys, 'gettotalrefcount') # This is a Python debug build (--with-pydebug) + if __with_pydebug and pydebug_ignore_warnings: + pexpect_env['PYTHONWARNINGS'] = ','.join([ + 'ignore::DeprecationWarning', + ]) + encoding = kwds.pop('encoding', 'utf-8') errors = kwds.pop('errors', None) diff --git a/src/sage/version.py b/src/sage/version.py index 48c223a1d1d..81338b3758c 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 = '9.1.beta1' -date = '2020-01-21' -banner = 'SageMath version 9.1.beta1, Release Date: 2020-01-21' +version = '9.1.beta2' +date = '2020-01-26' +banner = 'SageMath version 9.1.beta2, Release Date: 2020-01-26' diff --git a/src/sage_setup/docbuild/ext/inventory_builder.py b/src/sage_setup/docbuild/ext/inventory_builder.py index 09520372733..c3b58cca354 100644 --- a/src/sage_setup/docbuild/ext/inventory_builder.py +++ b/src/sage_setup/docbuild/ext/inventory_builder.py @@ -7,13 +7,10 @@ inventory files and pickle files. The documentation files are not written. """ from sphinx.builders.html import StandaloneHTMLBuilder -from sphinx.util.console import bold from os import path - -from six import iteritems, text_type - import shutil + class InventoryBuilder(StandaloneHTMLBuilder): """ A customized HTML builder which only generates intersphinx "object.inv" diff --git a/src/sage_setup/docbuild/ext/multidocs.py b/src/sage_setup/docbuild/ext/multidocs.py index bc5ca9e23c5..71a08cd9379 100644 --- a/src/sage_setup/docbuild/ext/multidocs.py +++ b/src/sage_setup/docbuild/ext/multidocs.py @@ -23,10 +23,7 @@ from six import text_type import os -import sys import shutil -import re -import tempfile import sphinx from sphinx.util.console import bold from sage.env import SAGE_DOC