diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..fb91e2c6ec0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Force LF normalization +* text=auto eol=lf +# except for Windows batch files +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf \ No newline at end of file diff --git a/.github/workflows/ci-cygwin-minimal.yml b/.github/workflows/ci-cygwin-minimal.yml index c41e2617bd3..21b798552a0 100644 --- a/.github/workflows/ci-cygwin-minimal.yml +++ b/.github/workflows/ci-cygwin-minimal.yml @@ -11,7 +11,7 @@ env: MAKE: make -j8 SAGE_NUM_THREADS: 3 SAGE_CHECK: warn - SAGE_CHECK_PACKAGES: "!cython,!r,!python3,!python2,!nose,!pathpy,!gap,!cysignals,!linbox,!git,!ppl" + SAGE_CHECK_PACKAGES: "!cython,!r,!python3,!nose,!pathpy,!gap,!cysignals,!linbox,!git,!ppl" CYGWIN: winsymlinks:native CONFIGURE_ARGS: --enable-experimental-packages --enable-download-from-upstream-url SAGE_FAT_BINARY: yes diff --git a/.github/workflows/ci-cygwin-standard.yml b/.github/workflows/ci-cygwin-standard.yml index 9f7414961c2..02ae9cdf41a 100644 --- a/.github/workflows/ci-cygwin-standard.yml +++ b/.github/workflows/ci-cygwin-standard.yml @@ -11,7 +11,7 @@ env: MAKE: make -j8 SAGE_NUM_THREADS: 3 SAGE_CHECK: warn - SAGE_CHECK_PACKAGES: "!cython,!r,!python3,!python2,!nose,!pathpy,!gap,!cysignals,!linbox,!git,!ppl" + SAGE_CHECK_PACKAGES: "!cython,!r,!python3,!nose,!pathpy,!gap,!cysignals,!linbox,!git,!ppl" CYGWIN: winsymlinks:native CONFIGURE_ARGS: --enable-experimental-packages --enable-download-from-upstream-url SAGE_FAT_BINARY: yes diff --git a/README.md b/README.md index 8621c29cbc8..380c2a45706 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ Guide](https://doc.sagemath.org/html/en/installation). - [Git] Alternatively, clone the Sage git repository: - $ git clone --branch master git://trac.sagemath.org/sage.git + $ git clone -c core.symlinks=true --branch master git://trac.sagemath.org/sage.git This will create the subdirectory `sage`. @@ -183,12 +183,9 @@ Guide](https://doc.sagemath.org/html/en/installation). Therefore it is crucial that you unpack the source tree from the Cygwin (or WSL) `bash` using the Cygwin (or WSL) `tar` utility - and not using other Windows tools (including mingw). Likewise, - when using `git`, it is crucial that you use the Cygwin (or WSL) - version of `git`, and that you configure it as follows first: - - $ git config --global core.autocrlf false - $ git config --global core.symlinks true + and not using other Windows tools (including mingw). Likewise, + when using `git`, it is recommended (but not necessary) to use the Cygwin (or WSL) + version of `git`. 3. `cd` into the source/build directory: @@ -271,8 +268,8 @@ Guide](https://doc.sagemath.org/html/en/installation). guide](https://doc.sagemath.org/html/en/installation/source.html#environment-variables). 11. Type `./configure`, followed by any options that you wish to use. - For example, to build a Python2-based Sage (which was the default - before Sage 9.0), use `./configure --with-python=2`. + For example, to build Sage with `gf2x` package supplied by Sage, + use `./configure --with-system-gf2x=no`. At the end of a successful `./configure` run, you may see messages recommending to install extra system packages using your package @@ -422,7 +419,7 @@ through to another Makefile under `build/make/Makefile`. The latter `build/make/Makefile` *is* generated by an autoconf-generated `configure` script, using the template in `build/make/Makefile.in`. This includes rules for building the Sage library itself (`make sagelib`), and for -building and installing each of Sage's dependencies (e.g. `make python2`). +building and installing each of Sage's dependencies (e.g. `make gf2x`). The `configure` script itself, if it is not already built, can be generated by running the `bootstrap` script (the latter requires _GNU autotools_ being installed). diff --git a/VERSION.txt b/VERSION.txt index 0f4841857d2..3edca6f98e2 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 9.2.beta3, Release Date: 2020-07-04 +SageMath version 9.2.beta4, Release Date: 2020-07-08 diff --git a/build/bin/sage-site b/build/bin/sage-site index 566ae033010..4b2c770797c 100755 --- a/build/bin/sage-site +++ b/build/bin/sage-site @@ -158,6 +158,7 @@ if [ "$1" = "-docbuild" -o "$1" = "--docbuild" ]; then # tends to ask interactive questions if something goes wrong. These # cause the build to hang. If stdin is /dev/null, TeX just aborts. shift + export LANG=C # to ensure it is possible to scrape out non-EN locale warnings exec sage-python -m sage_setup.docbuild "$@" = 2.4], [], [sage_spkg_install_freetype=yes]) - fi + SAGE_SPKG_DEPCHECK([libpng], [ + dnl freetype versions are libtool's ones, cf trac #30014 + PKG_CHECK_MODULES([FREETYPE], [freetype2 >= 16.1], [], [sage_spkg_install_freetype=yes]) + ]) +], [], [], [ if test x$sage_spkg_install_freetype = xyes; then AC_SUBST(SAGE_FREETYPE_PREFIX, ['$SAGE_LOCAL']) else diff --git a/build/pkgs/sagelib/package-version.txt b/build/pkgs/sagelib/package-version.txt index f083251bf12..57e44d9c48e 100644 --- a/build/pkgs/sagelib/package-version.txt +++ b/build/pkgs/sagelib/package-version.txt @@ -1 +1 @@ -9.2.beta3 +9.2.beta4 diff --git a/build/pkgs/tox/requirements.txt b/build/pkgs/tox/requirements.txt new file mode 100644 index 00000000000..053148f8486 --- /dev/null +++ b/build/pkgs/tox/requirements.txt @@ -0,0 +1 @@ +tox diff --git a/build/pkgs/tox/type b/build/pkgs/tox/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/tox/type @@ -0,0 +1 @@ +optional diff --git a/build/sage_bootstrap/app.py b/build/sage_bootstrap/app.py index 30a4d90c917..a3191b05e2b 100644 --- a/build/sage_bootstrap/app.py +++ b/build/sage_bootstrap/app.py @@ -14,6 +14,7 @@ # https://www.gnu.org/licenses/ # **************************************************************************** + import os import logging log = logging.getLogger() @@ -22,6 +23,9 @@ from sage_bootstrap.tarball import Tarball from sage_bootstrap.updater import ChecksumUpdater, PackageUpdater from sage_bootstrap.creator import PackageCreator +from sage_bootstrap.pypi import PyPiVersion, PyPiNotFound +from sage_bootstrap.fileserver import FileServer +from sage_bootstrap.expand_class import PackageClass class Application(object): @@ -39,7 +43,7 @@ def config(self): from sage_bootstrap.config import Configuration print(Configuration()) - def list(self): + def list_cls(self, package_class): """ Print a list of all available packages @@ -52,8 +56,9 @@ def list(self): zn_poly """ log.debug('Listing packages') - for pkg in Package.all(): - print(pkg.name) + pc = PackageClass(package_class) + for pkg_name in pc.names: + print(pkg_name) def name(self, tarball_filename): """ @@ -113,6 +118,32 @@ def update(self, package_name, new_version, url=None): update.download_upstream(url) update.fix_checksum() + def update_latest(self, package_name): + """ + Update a package to the latest version. This modifies the Sage sources. + """ + try: + pypi = PyPiVersion(package_name) + except PyPiNotFound: + log.debug('%s is not a pypi package', package_name) + return + else: + pypi.update() + + def update_latest_all(self): + log.debug('Attempting to update all packages') + exclude = [ + 'atlas', 'flint', 'bzip2', 'ecm', 'freetype', 'gap', 'glpk', 'graphs', + 'iconv', 'patch', 'r', 'configure', 'bliss', 'readline', 'decorator', + 'igraph', 'rw', 'planarity', 'gambit', + ] + pc = PackageClass(':standard:') + for package_name in pc.names: + if package_name in exclude: + log.debug('skipping %s because of pypi name collision', package_name) + continue + self.update_latest(package_name) + def download(self, package_name, allow_upstream=False): """ Download a package @@ -126,6 +157,31 @@ def download(self, package_name, allow_upstream=False): package.tarball.download(allow_upstream=allow_upstream) print(package.tarball.upstream_fqn) + def download_cls(self, package_name_or_class): + pc = PackageClass(package_name_or_class) + pc.apply(self.download) + + def upload(self, package_name): + """ + Upload a package to the Sage mirror network + + $ sage --package upload pari + Uploading /home/vbraun/Code/sage.git/upstream/pari-2.8-2044-g89b0f1e.tar.gz + """ + package = Package(package_name) + if not os.path.exists(package.tarball.upstream_fqn): + log.debug('Skipping %s because there is no local tarbal', package_name) + return + log.info('Uploading %s', package.tarball.upstream_fqn) + fs = FileServer() + fs.upload(package) + + def upload_cls(self, package_name_or_class): + pc = PackageClass(package_name_or_class) + pc.apply(self.upload) + fs = FileServer() + fs.publish() + def fix_all_checksums(self): """ Fix the checksum of a package diff --git a/build/sage_bootstrap/cmdline.py b/build/sage_bootstrap/cmdline.py index 48dc0d9c3fe..117d02c79a3 100644 --- a/build/sage_bootstrap/cmdline.py +++ b/build/sage_bootstrap/cmdline.py @@ -69,6 +69,13 @@ autotools [...] zn_poly + + $ sage --package list :standard: | sort + arb + atlas + backports_ssl_match_hostname + [...] + zn_poly """ @@ -115,6 +122,16 @@ """ +epilog_update_latest = \ +""" +Update a package to the latest version. This modifies the Sage sources. + +EXAMPLE: + + $ sage --package update-latest ipython +""" + + epilog_download = \ """ Download the tarball for a package and print the filename to stdout @@ -127,6 +144,17 @@ """ +epilog_upload = \ +""" +Upload the tarball to the Sage mirror network (requires ssh key authentication) + +EXAMPLE: + + $ sage --package upload pari + Uploading /home/vbraun/Code/sage.git/upstream/pari-2.8-2044-g89b0f1e.tar.gz +""" + + epilog_fix_checksum = \ """ Fix the checksum of a package @@ -170,6 +198,10 @@ def make_parser(): 'list', epilog=epilog_list, formatter_class=argparse.RawDescriptionHelpFormatter, help='Print a list of all available packages') + parser_list.add_argument( + 'package_class', + type=str, default=':all:', nargs='?', + help='Package class like :all: (default) or :standard:') parser_name = subparsers.add_parser( 'name', epilog=epilog_name, @@ -202,15 +234,29 @@ def make_parser(): parser_update.add_argument( '--url', type=str, default=None, help='Download URL') + parser_update_latest = subparsers.add_parser( + 'update-latest', epilog=epilog_update_latest, + formatter_class=argparse.RawDescriptionHelpFormatter, + help='Update a package to the latest version. This modifies the Sage sources.') + parser_update_latest.add_argument( + 'package_name', type=str, help='Package name (:all: for all packages)') + parser_download = subparsers.add_parser( 'download', epilog=epilog_download, formatter_class=argparse.RawDescriptionHelpFormatter, help='Download tarball') parser_download.add_argument( - 'package_name', type=str, help='Package name') + 'package_name', type=str, help='Package name or :type:') parser_download.add_argument( '--allow-upstream', action="store_true", help='Whether to fall back to downloading from the upstream URL') + + parser_upload = subparsers.add_parser( + 'upload', epilog=epilog_upload, + formatter_class=argparse.RawDescriptionHelpFormatter, + help='Upload tarball to Sage mirrors') + parser_upload.add_argument( + 'package_name', type=str, help='Package name or :type:') parser_fix_checksum = subparsers.add_parser( 'fix-checksum', epilog=epilog_fix_checksum, @@ -254,7 +300,7 @@ def run(): if args.subcommand == 'config': app.config() elif args.subcommand == 'list': - app.list() + app.list_cls(args.package_class) elif args.subcommand == 'name': app.name(args.tarball_filename) elif args.subcommand == 'tarball': @@ -263,10 +309,17 @@ def run(): app.apropos(args.incorrect_name) elif args.subcommand == 'update': app.update(args.package_name, args.new_version, url=args.url) + elif args.subcommand == 'update-latest': + if args.package_name == ':all:': + app.update_latest_all() + else: + app.update_latest(args.package_name) elif args.subcommand == 'download': - app.download(args.package_name, args.allow_upstream) + app.download_cls(args.package_name, args.allow_upstream) elif args.subcommand == 'create': app.create(args.package_name, args.version, args.tarball, args.type, args.url) + elif args.subcommand == 'upload': + app.upload_cls(args.package_name) elif args.subcommand == 'fix-checksum': if args.package_name is None: app.fix_all_checksums() diff --git a/build/sage_bootstrap/expand_class.py b/build/sage_bootstrap/expand_class.py new file mode 100644 index 00000000000..a666ec7ae93 --- /dev/null +++ b/build/sage_bootstrap/expand_class.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +""" +Utility to specify classes of packages like `:all:` +""" + + +#***************************************************************************** +# 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/ +#***************************************************************************** + + +import os +import sys +import logging +log = logging.getLogger() + +from sage_bootstrap.package import Package + + +class PackageClass(object): + + def __init__(self, package_name_or_class): + if package_name_or_class == ':all:': + self._init_all() + elif package_name_or_class == ':standard:': + self._init_standard() + elif package_name_or_class == ':optional:': + self._init_optional() + elif package_name_or_class == ':experimental:': + self._init_experimental() + elif package_name_or_class == ':huge:': + self._init_huge() + else: + if package_name_or_class.startswith(':'): + raise ValueError('Package name cannot start with ":", got %s', package_name_or_class) + if package_name_or_class.endswith(':'): + raise ValueError('Package name cannot end with ":", got %s', package_name_or_class) + self.names = [package_name_or_class] + + def _init_all(self): + self.names = [pkg.name for pkg in Package.all()] + + def _init_standard(self): + self.names = [pkg.name for pkg in Package.all() if pkg.type == 'standard'] + + def _init_optional(self): + self.names = [pkg.name for pkg in Package.all() if pkg.type == 'optional'] + + def _init_experimental(self): + self.names = [pkg.name for pkg in Package.all() if pkg.type == 'experimental'] + + def _init_huge(self): + self.names = [pkg.name for pkg in Package.all() if pkg.type == 'huge'] + + def apply(self, function, *args, **kwds): + for package_name in self.names: + function(package_name, *args, **kwds) diff --git a/build/sage_bootstrap/fileserver.py b/build/sage_bootstrap/fileserver.py new file mode 100644 index 00000000000..df6c20ee35e --- /dev/null +++ b/build/sage_bootstrap/fileserver.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +""" +Interface to the Sage fileserver +""" + + +#***************************************************************************** +# 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/ +#***************************************************************************** + + + +import os +import subprocess + + + +class FileServer(object): + + def __init__(self): + self.user = 'sagemath' + self.hostname = 'fileserver.sagemath.org' + + def upstream_directory(self, package): + """ + Return the directory where the tarball resides on the server + """ + return os.path.join( + '/', 'data', 'files', 'spkg', 'upstream', package.name, + ) + + def upload(self, package): + """ + Upload the current tarball of package + """ + subprocess.check_call([ + 'ssh', 'sagemath@fileserver.sagemath.org', + 'mkdir -p {0} && touch {0}/index.html'.format(self.upstream_directory(package)) + ]) + subprocess.check_call([ + 'rsync', '-av', '--checksum', '-e', 'ssh -l sagemath', + package.tarball.upstream_fqn, + 'fileserver.sagemath.org:{0}'.format(self.upstream_directory(package)) + ]) + + def publish(self): + """ + Publish the files + """ + subprocess.check_call([ + 'ssh', 'sagemath@fileserver.sagemath.org', './publish-files.sh' + ]) diff --git a/build/sage_bootstrap/package.py b/build/sage_bootstrap/package.py index 9d103db7331..bbd04fab36a 100644 --- a/build/sage_bootstrap/package.py +++ b/build/sage_bootstrap/package.py @@ -45,6 +45,7 @@ def __init__(self, package_name): self.__tarball = None self._init_checksum() self._init_version() + self._init_type() def __repr__(self): return 'Package {0}'.format(self.name) @@ -217,6 +218,13 @@ def patchlevel(self): """ return self.__patchlevel + @property + def type(self): + """ + Return the package type + """ + return self.__type + def __eq__(self, other): return self.tarball == other.tarball @@ -227,10 +235,15 @@ def all(cls): """ base = os.path.join(SAGE_ROOT, 'build', 'pkgs') for subdir in os.listdir(base): - path = os.path.join(base, subdir) + path = os.path.join(base, subdir) if not os.path.isfile(os.path.join(path, "checksums.ini")): + log.debug('%s has no checksums.ini', subdir) continue - yield cls(subdir) + try: + yield cls(subdir) + except BaseException: + log.error('Failed to open %s', subdir) + raise @property def path(self): @@ -274,4 +287,10 @@ def _init_version(self): self.__version = match.group('version') self.__patchlevel = int(match.group('patchlevel')) - + def _init_type(self): + with open(os.path.join(self.path, 'type')) as f: + package_type = f.read().strip() + assert package_type in [ + 'base', 'standard', 'optional', 'experimental', 'script', 'pip' + ] + self.__type = package_type diff --git a/build/sage_bootstrap/pypi.py b/build/sage_bootstrap/pypi.py new file mode 100644 index 00000000000..7b17df3a58e --- /dev/null +++ b/build/sage_bootstrap/pypi.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +""" +PyPi Version Information +""" + + +#***************************************************************************** +# 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/ +#***************************************************************************** + +import logging +log = logging.getLogger() + +import json + +from sage_bootstrap.package import Package +from sage_bootstrap.updater import PackageUpdater +from sage_bootstrap.compat import urllib + + +class PyPiNotFound(Exception): + pass + + +class PyPiError(Exception): + pass + + +class PyPiVersion(object): + + def __init__(self, package_name): + self.name = package_name + self.json = self._get_json() + + def _get_json(self): + response = urllib.urlopen(self.json_url) + if response.getcode() != 200: + raise PyPiNotFound('%s not on pypi', self.name) + data = response.read() + text = data.decode('utf-8') + return json.loads(text) + + @property + def json_url(self): + return 'https://pypi.python.org/pypi/{0}/json'.format(self.name) + + @property + def version(self): + """ + Return the current version + """ + return self.json['info']['version'] + + @property + def url(self): + """ + Return the source url + """ + for download in self.json['urls']: + if download['python_version'] == 'source': + return download['url'] + raise PyPiError('No source url for %s found', self.name) + + def update(self): + package = Package(self.name) + if package.version == self.version: + log.info('%s is already at the latest version', self.name) + return + log.info('Updating %s: %s -> %s', self.name, package.version, self.version) + update = PackageUpdater(self.name, self.version) + update.download_upstream(self.url) + update.fix_checksum() diff --git a/src/bin/sage-runtests b/src/bin/sage-runtests index 3248f09b0dc..ff75a49e436 100755 --- a/src/bin/sage-runtests +++ b/src/bin/sage-runtests @@ -50,7 +50,6 @@ if __name__ == "__main__": 'skipped if no limit is set (default: 3300 MB)') parser.add_option("-a", "--all", action="store_true", default=False, help="test all files in the Sage library") parser.add_option("--logfile", metavar="FILE", help="log all output to FILE") - parser.add_option("--sagenb", action="store_true", default=False, help="test all files from the Sage notebook sources") parser.add_option("-l", "--long", action="store_true", default=False, help="include lines with the phrase 'long time'") parser.add_option("-s", "--short", dest="target_walltime", default=None, action="callback", @@ -130,7 +129,7 @@ if __name__ == "__main__": options, args = parser.parse_args() - if not args and not (options.all or options.sagenb or options.new): + if not args and not (options.all or options.new): parser.print_help() sys.exit(2) diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 8158c10f174..eda56fa1fdc 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.2.beta3' -SAGE_RELEASE_DATE='2020-07-04' -SAGE_VERSION_BANNER='SageMath version 9.2.beta3, Release Date: 2020-07-04' +SAGE_VERSION='9.2.beta4' +SAGE_RELEASE_DATE='2020-07-08' +SAGE_VERSION_BANNER='SageMath version 9.2.beta4, Release Date: 2020-07-08' diff --git a/src/doc/en/developer/coding_in_python.rst b/src/doc/en/developer/coding_in_python.rst index d2850268b2c..7036ac8d69d 100644 --- a/src/doc/en/developer/coding_in_python.rst +++ b/src/doc/en/developer/coding_in_python.rst @@ -310,23 +310,25 @@ The __hash__ Special Method Here is the definition of ``__hash__`` from the Python reference manual: - Called by built-in function ``hash()`` and for operations on members of - hashed collections including set, frozenset, and dict. ``__hash__()`` - should return an integer. The only required property is that objects which - compare equal have the same hash value; it is advised to somehow mix - together (e.g. using exclusive or) the hash values for the components of - the object that also play a part in comparison of objects. If a class does - not define a - ``__cmp__()`` method it should not define a - ``__hash__()`` operation either; if it defines - ``__cmp__()`` or ``__eq__()`` but not - ``__hash__()``, its instances will not be usable as - dictionary keys. If a class defines mutable objects and implements - a ``__cmp__()`` or ``__eq__()`` method, it - should not implement ``__hash__()``, since the dictionary - implementation requires that a key's hash value is immutable (if - the object's hash value changes, it will be in the wrong hash - bucket). + Called by built-in function ``hash()`` and for operations on members + of hashed collections including ``set``, ``frozenset``, and + ``dict``. ``__hash__()`` should return an integer. The only required + property is that objects which compare equal have the same hash + value; it is advised to mix together the hash values of the + components of the object that also play a part in comparison of + objects by packing them into a tuple and hashing the tuple. + + If a class does not define an ``__eq__()`` method it should not define + a ``__hash__()`` operation either; if it defines ``__eq__()`` but not + ``__hash__()``, its instances will not be usable as items in hashable + collections. If a class defines mutable objects and implements an + ``__eq__()`` method, it should not implement ``__hash__()``, since the + implementation of hashable collections requires that a key’s hash + value is immutable (if the object’s hash value changes, it will be + in the wrong hash bucket). + +See https://docs.python.org/3/reference/datamodel.html#object.__hash__ for more +information on the subject. Notice the phrase, "The only required property is that objects which compare equal have the same hash value." This is an assumption made by diff --git a/src/doc/en/developer/portability_testing.rst b/src/doc/en/developer/portability_testing.rst index f550d1d3e07..d4c625c5961 100644 --- a/src/doc/en/developer/portability_testing.rst +++ b/src/doc/en/developer/portability_testing.rst @@ -346,10 +346,8 @@ Then, to bootstrap and configure...:: ADD src/doc/bootstrap src/doc/bootstrap ADD m4 ./m4 ADD build ./build - ADD src/bin/sage-version.sh src/bin/sage-version.sh RUN ./bootstrap ADD src/bin src/bin - ADD src/Makefile.in src/Makefile.in ARG EXTRA_CONFIGURE_ARGS="" RUN ./configure --enable-build-as-root ${EXTRA_CONFIGURE_ARGS} || (cat config.log; exit 1) diff --git a/src/doc/en/faq/faq-contribute.rst b/src/doc/en/faq/faq-contribute.rst index 69ea2aa400c..bc70ab6c145 100644 --- a/src/doc/en/faq/faq-contribute.rst +++ b/src/doc/en/faq/faq-contribute.rst @@ -7,10 +7,9 @@ FAQ: Contributing to Sage How can I start contributing to Sage? """"""""""""""""""""""""""""""""""""" -The first step -is to use Sage and encourage your friends to use Sage. If you find -bugs or confusing documentation along the way, please report your -problems! +The first step is to use Sage and encourage your friends to use +Sage. If you find bugs or confusing documentation along the way, +please report your problems! Two popular ways to contribute to Sage are to write code and to create documentation or tutorials. Some steps in each direction @@ -20,12 +19,12 @@ I want to contribute code to Sage. How do I get started? """""""""""""""""""""""""""""""""""""""""""""""""""""""" Take a look at the -`official development guide `_ +`official development guide `_ for Sage. At a minimum, the first chapter in that guide is required reading for any Sage developer. Also pay special attention to the -`trac guidelines `_. +`trac guidelines `_. You can also join the -`sage-devel `_ +`sage-devel `_ mailing list or hang around on the ``#sage-devel`` IRC channel on `freenode `_. While you are getting to know @@ -35,7 +34,7 @@ source and familiarize yourself with the The best way to become familiar with the Sage development process is to choose a ticket from the -`trac server `_ +`trac server `_ and review the proposed changes contained in that ticket. If you want to implement something, it is a good practice to discuss your ideas on the ``sage-devel`` mailing list first, so that other developers have a @@ -43,7 +42,7 @@ chance to comment on your ideas/proposals. They are pretty open to new ideas, too, as all mathematicians should be. Sage's main programming language is -`Python `_. +`Python `_. Some parts of Sage may be written in other languages, especially the components that do the heavy number crunching, but most native functionality is done using Python, including "glue code". One of the @@ -62,11 +61,11 @@ evil." If you do not know Python, you should start learning that language. A good place to start is the -`Python Official Tutorial `_ +`Python Official Tutorial `_ and other documents in the -`Python standard documentation `_. +`Python standard documentation `_. Another good place to take a look at is -`Dive Into Python `_ +`Dive Into Python `_ by Mark Pilgrim, which may be pretty helpful on some specific topics such as test-driven development. The book `Building Skills in Python `_ @@ -82,17 +81,8 @@ programming concepts are explained more thoroughly in Python-centered resources than in Sage-centered resources; in the latter, mathematics is usually the priority. -Can I contribute to Sage using SageMathCloud? -""""""""""""""""""""""""""""""""""""""""""""" - -Absolutely! If you want to write code for Sage or update the -official documentation, -you will need your own installation of Sage on `SageMathCloud `_. -You can find more information about the details of installation in -`the SageMathCloud FAQ `_. - -I'm not a programmer. Is there another way I can help out? -"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +I am not a programmer. Is there another way I can help out? +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" Yes. As with any free open source software project, there are numerous ways in which you could help out within the Sage community, and @@ -109,23 +99,20 @@ has written some which we highly recommend. For the graphic designers or the artistically creative, you can -help out with improving the design of the Sage website. Or you can -cast your critical artistic eyes over the interfaces of SageMathCloud -or the Sage notebook, and find out where they need improvement. +help out with improving the design of the Sage website. If you can speak, read, and write in another (natural) language, there are many ways in which your contribution would be very valuable to the whole Sage community. Say you know Italian. Then you can write a Sage tutorial in Italian, or help out with translating the official Sage tutorial to -Italian. +Italian. -The above is a very short -list. There are many, many more ways in which you can help out. Feel -free to send an email to the -`sage-devel `_ -mailing list to ask about possible ways in which you could help out, -or to suggest a project idea. +The above is a very short list. There are many, many more ways in +which you can help out. Feel free to send an email to the `sage-devel +`_ mailing list to ask +about possible ways in which you could help out, or to suggest a +project idea. Where can I find resources on Python or Cython? @@ -136,31 +123,26 @@ resources can be found by a web search. **General resources** -* `Cython `_ +* `Cython `_ * `pep8 `_ * `py2depgraph `_ * `pycallgraph `_ * `PyChecker `_ * `PyFlakes `_ * `Pylint `_ -* `Python `_ home page and the - `Python standard documentation `_ +* `Python `_ home page and the + `Python standard documentation `_ * `Snakefood `_ * `Sphinx `_ * `XDot `_ **Tutorials and books** -* `Building Skills in Python `_ - by Steven F. Lott * `Cython Tutorial `_ by Stefan Behnel, Robert W. Bradshaw, and Dag Sverre Seljebotn -* `Dive into Python `_ by Mark Pilgrim * `Dive Into Python 3 `_ by Mark Pilgrim * `Fast Numerical Computations with Cython `_ by Dag Sverre Seljebotn -* `How to Think Like a Computer Scientist `_ - by Jeffrey Elkner, Allen B. Downey, and Chris Meyers * `Official Python Tutorial `_ **Articles and HOWTOs** @@ -173,9 +155,6 @@ resources can be found by a web search. * `Regular Expression HOWTO `_ by A. M. Kuchling * `reStructuredText `_ -* `Static Code Analizers for Python `_ - by Doug Hellmann - Are there any coding conventions I need to follow? """""""""""""""""""""""""""""""""""""""""""""""""" @@ -183,7 +162,7 @@ Are there any coding conventions I need to follow? You should follow the standard Python conventions as documented at :pep:`8` and :pep:`257`. Also consult the Sage Developer's Guide, especially the chapter -`Conventions for Coding in Sage `_. +`Conventions for Coding in Sage `_. I submitted a bug fix to the trac server several weeks ago. Why are you ignoring my patch? @@ -268,7 +247,12 @@ necessity to import what you need. from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - You can ask Sage where to find ``PolynomialRing`` using:: + You can use ``import_statements`` to get the exact necessary line:: + + sage: import_statements(PolynomialRing) + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + + If this fails, you can ask Sage where to find ``PolynomialRing`` using:: sage: PolynomialRing.__module__ 'sage.rings.polynomial.polynomial_ring_constructor' diff --git a/src/doc/en/faq/faq-usage.rst b/src/doc/en/faq/faq-usage.rst index 0308e1d4673..44db03050d6 100644 --- a/src/doc/en/faq/faq-usage.rst +++ b/src/doc/en/faq/faq-usage.rst @@ -12,7 +12,7 @@ You can try out Sage without downloading anything: * **CoCalc™:** Go to https://cocalc.com and set up a free account. - If you log in, you will gain access to the latest version of Sage and to + If you log in, you will gain access to the latest version of Sage and to many other programs. Note that this website is an independent commercial service. @@ -419,7 +419,7 @@ How can I wrote multiplication implicitly as in Mathematica? Sage has a function that enables this:: sage: implicit_multiplication(True) - sage: x 2 x # Not tested + sage: x 2 x # not tested 2*x^2 sage: implicit_multiplication(False) @@ -435,7 +435,7 @@ complicated situation. To see what the preparser does:: See https://wiki.sagemath.org/sage_mathematica for more information about Mathematica vs. SageMath. - + Can I make Sage automatically execute commands on startup? """""""""""""""""""""""""""""""""""""""""""""""""""""""""" @@ -684,14 +684,19 @@ How do I plot the cube root (or other odd roots) for negative input? This is one of the most frequently asked questions. There are several methods mentioned in the plot documentation, but this one is easiest:: - sage: plot(sign(x)*abs(x)^(1/3),-1,1) + sage: plot(real_nth_root(x, 3), (x, -1, 1)) Graphics object consisting of 1 graphics primitive -The *reason* this is necessary is that Sage returns complex numbers -for odd roots of negative numbers when numerically approximated, which -is a `standard convention `_. +On the other hand, note that the straightforward :: + + sage: plot(x^(1/3), (x, -1, 1)) # not tested + +produces the expected plot only for positive `x`. The *reason* is that Sage +returns complex numbers for odd roots of negative numbers when numerically +approximated, which is a `standard convention +`_. :: - sage: N((-1)^(1/3)) + sage: numerical_approx( (-1)^(1/3) ) 0.500000000000000 + 0.866025403784439*I How do I use the bitwise XOR operator in Sage? @@ -799,7 +804,7 @@ on the IPython command line with the ``??`` shortcut:: sage: plot?? # not tested Signature: plot(*args, **kwds) - Source: + Source: ... Objects that are built into Python or IPython are compiled and will diff --git a/src/doc/en/installation/conda.rst b/src/doc/en/installation/conda.rst index f7ce7cf0a91..0b090cb2089 100644 --- a/src/doc/en/installation/conda.rst +++ b/src/doc/en/installation/conda.rst @@ -11,7 +11,7 @@ then type in the following commands in a terminal: * Add the conda-forge channel: ``conda config --add channels conda-forge`` * Create a new environment containing SageMath: ``conda create -n sage sage python=X``, where - ``X`` is version of Python, e.g. ``2.7`` + ``X`` is version of Python, e.g. ``3.7`` * Enter the new environment: ``conda activate sage`` * Start SageMath: ``sage`` diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index 2ae166b6fac..cd29a527a0a 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -282,6 +282,7 @@ Comprehensive Module list sage/combinat/root_system/weight_lattice_realizations sage/combinat/root_system/weight_space sage/combinat/root_system/weyl_characters + sage/combinat/root_system/fusion_ring sage/combinat/root_system/weyl_group sage/combinat/rooted_tree sage/combinat/rsk diff --git a/src/doc/en/reference/misc/index.rst b/src/doc/en/reference/misc/index.rst index 4e8eb49bc15..20b9285f836 100644 --- a/src/doc/en/reference/misc/index.rst +++ b/src/doc/en/reference/misc/index.rst @@ -167,6 +167,7 @@ Formatted Output sage/typeset/character_art_factory sage/typeset/ascii_art sage/typeset/unicode_art + sage/misc/repr sage/misc/sage_input sage/misc/table diff --git a/src/doc/en/reference/plot3d/threejs.rst b/src/doc/en/reference/plot3d/threejs.rst index 82ddf609f71..3d33c8fa78a 100644 --- a/src/doc/en/reference/plot3d/threejs.rst +++ b/src/doc/en/reference/plot3d/threejs.rst @@ -59,6 +59,10 @@ Options currently supported by the viewer: - ``thickness`` -- (default: 1) numeric value for thickness of lines +- ``viewpoint`` -- (default: None) list or tuple of the form [[x,y,z],angle] setting the initial + viewpoint of the scene, where angle is in degrees; can be determined using the 'Get Viewpoint' + option of the information menu + Clicking on the information icon in the lower right-hand corner of the viewer opens a menu of available actions. These include saving the three-dimensional scene as a static PNG image or as complete HTML source code. diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index b7383e7e113..13b8d738738 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -347,6 +347,9 @@ REFERENCES: **B** +.. [BaKi2001] Bakalov and Kirillov, *Lectures on tensor categories and modular functors*, + AMS (2001). + .. [Ba1994] Kaushik Basu. *The Traveler's Dilemma: Paradoxes of Rationality in Game Theory*. The American Economic Review (1994): 391-395. @@ -1984,6 +1987,9 @@ REFERENCES: 1981, Pages 108-125, ISSN 0097-3165,:doi:`10.1016/0097-3165(81)90007-8`. (http://www.sciencedirect.com/science/article/pii/0097316581900078) +.. [EGNO2015] Pavel Etingof, Shlomo Gelaki, Dmitri Nikshych and Victor Ostrik, + *Tensor Categories*, AMS Mathematical Surveys and Monographs 205 (2015). + .. [EGHLSVY] Pavel Etingof, Oleg Golberg, Sebastian Hensel, Tiankai Liu, Alex Schwendner, Dmitry Vaintrob, Elena Yudovina, "Introduction to representation theory", @@ -4123,6 +4129,10 @@ REFERENCES: **N** +.. [NaiRow2011] Naidu and Rowell, A finiteness property for braided fusion + categories. Algebr. Represent. Theory 14 (2011), no. 5, 837–855. + :arXiv:`0903.4157`. + .. [Nas1950] John Nash. *Equilibrium points in n-person games.* Proceedings of the National Academy of Sciences 36.1 (1950): 48-49. @@ -4612,6 +4622,13 @@ REFERENCES: .. [Rot2006] Ron Roth, Introduction to Coding Theory, Cambridge University Press, 2006 +.. [Row2006] Eric Rowell, *From quantum groups to unitary modular tensor categories*. + In Representations of algebraic groups, quantum groups, and Lie algebras, + Contemp. Math., **413**, Amer. Math. Soc., Providence, RI, 2006. + :arXiv:`math/0503226`. + +.. [RoStWa2009] Eric Rowell, Richard Stong and Zhenghan Wang, *On classification + of modular tensor categories*, Comm. Math. Phys. 292, 343--389, 2009. .. [RR1997] Arun Ram and Jeffrey Remmel. *Applications of the Frobenius formulas and the characters of the symmetric group and the diff --git a/src/doc/en/thematic_tutorials/sandpile.rst b/src/doc/en/thematic_tutorials/sandpile.rst index 7ee8f86d080..8339a314f3a 100644 --- a/src/doc/en/thematic_tutorials/sandpile.rst +++ b/src/doc/en/thematic_tutorials/sandpile.rst @@ -3995,6 +3995,7 @@ OUTPUT: SandpileDivisor EXAMPLES:: + sage: S = sandpiles.Cycle(3) sage: D = SandpileDivisor(S, [1,2,3]) sage: D.dualize() diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index e3c7b1ea99d..770dd68b442 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -1063,7 +1063,7 @@ def mutate(self, direction, **kwargs): raise ValueError('cannot mutate in direction ' + str(k)) # store new mutation path - if to_mutate._path != [] and to_mutate._path[-1] == k: + if to_mutate._path and to_mutate._path[-1] == k: to_mutate._path.pop() else: to_mutate._path.append(k) diff --git a/src/sage/algebras/free_algebra_element.py b/src/sage/algebras/free_algebra_element.py index 456732d6ced..da91eda2cf8 100644 --- a/src/sage/algebras/free_algebra_element.py +++ b/src/sage/algebras/free_algebra_element.py @@ -33,9 +33,8 @@ # # http://www.gnu.org/licenses/ #***************************************************************************** -from __future__ import print_function -from sage.misc.misc import repr_lincomb +from sage.misc.repr import repr_lincomb from sage.monoids.free_monoid_element import FreeMonoidElement from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement from sage.structure.element import AlgebraElement diff --git a/src/sage/algebras/free_algebra_quotient_element.py b/src/sage/algebras/free_algebra_quotient_element.py index f5216b525d4..9e291010fa2 100644 --- a/src/sage/algebras/free_algebra_quotient_element.py +++ b/src/sage/algebras/free_algebra_quotient_element.py @@ -22,7 +22,7 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.misc.misc import repr_lincomb +from sage.misc.repr import repr_lincomb from sage.structure.element import RingElement, AlgebraElement from sage.structure.parent_gens import localvars from sage.structure.richcmp import richcmp diff --git a/src/sage/algebras/letterplace/free_algebra_element_letterplace.pyx b/src/sage/algebras/letterplace/free_algebra_element_letterplace.pyx index ba7518818e0..ad863ea0a34 100644 --- a/src/sage/algebras/letterplace/free_algebra_element_letterplace.pyx +++ b/src/sage/algebras/letterplace/free_algebra_element_letterplace.pyx @@ -18,7 +18,7 @@ AUTHOR: # **************************************************************************** from sage.libs.singular.function import lib, singular_function -from sage.misc.misc import repr_lincomb +from sage.misc.repr import repr_lincomb from sage.rings.polynomial.multi_polynomial_ideal import MPolynomialIdeal from cpython.object cimport PyObject_RichCompare diff --git a/src/sage/algebras/lie_algebras/lie_algebra_element.pyx b/src/sage/algebras/lie_algebras/lie_algebra_element.pyx index d519d37b618..644eef7cc42 100644 --- a/src/sage/algebras/lie_algebras/lie_algebra_element.pyx +++ b/src/sage/algebras/lie_algebras/lie_algebra_element.pyx @@ -20,7 +20,7 @@ AUTHORS: from copy import copy from cpython.object cimport Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE -from sage.misc.misc import repr_lincomb +from sage.misc.repr import repr_lincomb from sage.combinat.free_module import CombinatorialFreeModule from sage.structure.element cimport have_same_parent, parent from sage.structure.coerce cimport coercion_model @@ -403,7 +403,7 @@ cdef class LieAlgebraElementWrapper(ElementWrapper): right = ( right).lift() return left * right - def __div__(self, x): + def __truediv__(self, x): """ Division by coefficients. diff --git a/src/sage/algebras/weyl_algebra.py b/src/sage/algebras/weyl_algebra.py index 67965111f61..3ebff737431 100644 --- a/src/sage/algebras/weyl_algebra.py +++ b/src/sage/algebras/weyl_algebra.py @@ -18,10 +18,12 @@ from sage.misc.cachefunc import cached_method from sage.misc.latex import latex, LatexExpr +from sage.misc.lazy_attribute import lazy_attribute from sage.misc.misc_c import prod from sage.structure.richcmp import richcmp from sage.structure.element import AlgebraElement from sage.structure.unique_representation import UniqueRepresentation +from sage.categories.action import Action from sage.categories.rings import Rings from sage.categories.algebras_with_basis import AlgebrasWithBasis from sage.sets.family import Family @@ -618,6 +620,33 @@ def factor_differentials(self): ret[dx] += c * prod(g**e for e, g in zip(x, gens)) return ret + def diff(self, p): + """ + Apply this differential operator to a polynomial. + + INPUT: + + - ``p`` -- polynomial of the underlying polynomial ring + + OUTPUT: + + The result of the left action of the Weyl algebra on the polynomial + ring via differentiation. + + EXAMPLES:: + + sage: R. = QQ[] + sage: W = R.weyl_algebra() + sage: dx, dy = W.differentials() + sage: dx.diff(x^3) + 3*x^2 + sage: (dx*dy).diff(W(x^3*y^3)) + 9*x^2*y^2 + sage: (x*dx + dy + 1).diff(x^4*y^4 + 1) + 5*x^4*y^4 + 4*x^4*y^3 + 1 + """ + return self.parent().diff_action(self, p) + class DifferentialWeylAlgebra(Algebra, UniqueRepresentation): r""" @@ -1047,5 +1076,95 @@ def zero(self): """ return self.element_class(self, {}) + @lazy_attribute + def diff_action(self): + """ + Left action of this Weyl algebra on the underlying polynomial ring by + differentiation. + + EXAMPLES:: + + sage: R. = QQ[] + sage: W = R.weyl_algebra() + sage: dx, dy = W.differentials() + sage: W.diff_action + Left action by Differential Weyl algebra of polynomials in x, y + over Rational Field on Multivariate Polynomial Ring in x, y over + Rational Field + sage: W.diff_action(dx^2 + dy + 1, x^3*y^3) + x^3*y^3 + 3*x^3*y^2 + 6*x*y^3 + """ + return DifferentialWeylAlgebraAction(self) + Element = DifferentialWeylAlgebraElement + +class DifferentialWeylAlgebraAction(Action): + """ + Left action of a Weyl algebra on its underlying polynomial ring by + differentiation. + + EXAMPLES:: + + sage: R. = QQ[] + sage: W = R.weyl_algebra() + sage: dx, dy = W.differentials() + sage: W.diff_action + Left action by Differential Weyl algebra of polynomials in x, y + over Rational Field on Multivariate Polynomial Ring in x, y over + Rational Field + + :: + + sage: g = dx^2 + x*dy + sage: p = x^5 + x^3 + y^2*x^2 + 1 + sage: W.diff_action(g, p) + 2*x^3*y + 20*x^3 + 2*y^2 + 6*x + + The action is a left action:: + + sage: h = dx*x + x*y + sage: W.diff_action(h, W.diff_action(g, p)) == W.diff_action(h*g, p) + True + + The action endomorphism of a differential operator:: + + sage: dg = W.diff_action(g); dg + Action of dx^2 + x*dy on Multivariate Polynomial Ring in x, y over + Rational Field under Left action by Differential Weyl algebra... + sage: dg(p) == W.diff_action(g, p) == g.diff(p) + True + """ + + def __init__(self, G): + """ + INPUT: + + - ``G`` -- Weyl algebra + + EXAMPLES:: + + sage: from sage.algebras.weyl_algebra import DifferentialWeylAlgebraAction + sage: W. = DifferentialWeylAlgebra(QQ) + sage: DifferentialWeylAlgebraAction(W) + Left action by Differential Weyl algebra of polynomials in x, y + over Rational Field on Multivariate Polynomial Ring in x, y over + Rational Field + """ + super().__init__(G, G.polynomial_ring(), is_left=True) + + def _act_(self, g, x): + """ + Apply a differential operator to a polynomial. + + EXAMPLES:: + + sage: W. = DifferentialWeylAlgebra(QQ) + sage: dx, dy = W.differentials() + sage: W.diff_action(dx^3 + dx, x^3*y^3 + x*y) + 3*x^2*y^3 + 6*y^3 + y + """ + f = g * x + D = {y: c for (y, dy), c in f.monomial_coefficients(copy=False).items() + if all(dyi == 0 for dyi in dy)} + return self.right_domain()(D) diff --git a/src/sage/calculus/functional.py b/src/sage/calculus/functional.py index feef8446e16..0864be59891 100644 --- a/src/sage/calculus/functional.py +++ b/src/sage/calculus/functional.py @@ -126,6 +126,26 @@ def derivative(f, *args, **kwds): 80*u^3*v^3 sage: derivative(f, [u, v, v]) 80*u^3*v^3 + + We differentiate a scalar field on a manifold:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: f = M.scalar_field(x^2*y, name='f') + sage: derivative(f) + 1-form df on the 2-dimensional differentiable manifold M + sage: derivative(f).display() + df = 2*x*y dx + x^2 dy + + We differentiate a differentiable form, getting its exterior derivative:: + + sage: a = M.one_form(-y, x, name='a'); a.display() + a = -y dx + x dy + sage: derivative(a) + 2-form da on the 2-dimensional differentiable manifold M + sage: derivative(a).display() + da = 2 dx/\dy + """ try: return f.derivative(*args, **kwds) diff --git a/src/sage/calculus/integration.pyx b/src/sage/calculus/integration.pyx index d0a5983eb60..1571cf873d7 100644 --- a/src/sage/calculus/integration.pyx +++ b/src/sage/calculus/integration.pyx @@ -70,7 +70,7 @@ def numerical_integral(func, a, b=None, algorithm='qag', max_points=87, params=[], eps_abs=1e-6, eps_rel=1e-6, rule=6): - r""" + r""" Return the numerical integral of the function on the interval from a to b and an error bound. @@ -241,40 +241,52 @@ def numerical_integral(func, a, b=None, Traceback (most recent call last): ... TypeError: unable to simplify to float approximation - """ - cdef double abs_err # step size - cdef double result - cdef int i - cdef int j - cdef double _a, _b - cdef PyFunctionWrapper wrapper # struct to pass information into GSL C function + Check for :trac:`15496`:: - if b is None or isinstance(a, (list, tuple)): - b = a[1] - a = a[0] + sage: f = x^2/exp(-1/(x^2+1))/(x^2+1) + sage: D = integrate(f,(x,-infinity,infinity),hold=True) + sage: D.n() + Traceback (most recent call last): + ... + ValueError: integral does not converge at -infinity + """ + cdef double abs_err # step size + cdef double result + cdef int i + cdef int j + cdef double _a, _b + cdef PyFunctionWrapper wrapper # struct to pass information into GSL C function + + if b is None or isinstance(a, (list, tuple)): + b = a[1] + a = a[0] - # The integral over a point is always zero - if a == b: - return (0.0, 0.0) + # The integral over a point is always zero + if a == b: + return (0.0, 0.0) - if not callable(func): + if not callable(func): # handle the constant case return (((b - a) * func), 0.0) - cdef gsl_function F - cdef gsl_integration_workspace* W - W=NULL + cdef gsl_function F + cdef gsl_integration_workspace* W + W = NULL - if not isinstance(func, FastDoubleFunc): + if not isinstance(func, FastDoubleFunc): + from sage.rings.infinity import Infinity try: if hasattr(func, 'arguments'): vars = func.arguments() else: vars = func.variables() - if len(vars) == 0: - # handle the constant case - return (((b - a) * func), 0.0) + except (AttributeError): + pass + else: + if not vars: + # handle the constant case + return (((b - a) * func), 0.0) if len(vars) != 1: if len(params) + 1 != len(vars): raise ValueError(("The function to be integrated depends on " @@ -285,70 +297,85 @@ def numerical_integral(func, a, b=None, to_sub = dict(zip(vars[1:], params)) func = func.subs(to_sub) - func = func._fast_float_(str(vars[0])) - except (AttributeError): - pass - if isinstance(func, FastDoubleFunc): + # sanity checks for integration up to infinity + v = str(vars[0]) + if a is -Infinity: + try: + ell = func.limit(**{v: -Infinity}) + except (AttributeError, ValueError): + pass + else: + if ell.is_numeric() and not ell.is_zero(): + raise ValueError('integral does not converge at -infinity') + if b is Infinity: + try: + ell = func.limit(**{v: Infinity}) + except (AttributeError, ValueError): + pass + else: + if ell.is_numeric() and not ell.is_zero(): + raise ValueError('integral does not converge at infinity') + func = func._fast_float_(v) + + if isinstance(func, FastDoubleFunc): F.function = c_ff F.params = func - elif not isinstance(func, compiled_integrand): + elif not isinstance(func, compiled_integrand): wrapper = PyFunctionWrapper() if not func is None: wrapper.the_function = func else: raise ValueError("No integrand defined") try: - if params == [] and len(sage_getargspec(wrapper.the_function)[0]) == 1: - wrapper.the_parameters=[] - elif params == [] and len(sage_getargspec(wrapper.the_function)[0]) > 1: + if not params and len(sage_getargspec(wrapper.the_function)[0]) == 1: + wrapper.the_parameters = [] + elif not params and len(sage_getargspec(wrapper.the_function)[0]) > 1: raise ValueError("Integrand has parameters but no parameters specified") - elif params!=[]: + elif params: wrapper.the_parameters = params except TypeError: - wrapper.the_function = eval("lambda x: func(x)", {'func':func}) + wrapper.the_function = eval("lambda x: func(x)", {'func': func}) wrapper.the_parameters = [] F.function = c_f F.params = wrapper + cdef size_t n + n = max_points - cdef size_t n - n = max_points + gsl_set_error_handler_off() - gsl_set_error_handler_off() - - if algorithm == "qng": + if algorithm == "qng": _a=a _b=b sig_on() gsl_integration_qng(&F, _a, _b, eps_abs, eps_rel, &result, &abs_err, &n) sig_off() - elif algorithm == "qag": - from sage.rings.infinity import Infinity - if a is -Infinity and b is +Infinity: + elif algorithm == "qag": + if a is -Infinity and b is +Infinity: W = gsl_integration_workspace_alloc(n) sig_on() gsl_integration_qagi(&F, eps_abs, eps_rel, n, W, &result, &abs_err) sig_off() - elif a is -Infinity: + elif a is -Infinity: _b = b W = gsl_integration_workspace_alloc(n) sig_on() gsl_integration_qagil(&F, _b, eps_abs, eps_rel, n, W, &result, &abs_err) sig_off() - elif b is +Infinity: + elif b is +Infinity: _a = a W = gsl_integration_workspace_alloc(n) sig_on() gsl_integration_qagiu(&F, _a, eps_abs, eps_rel, n, W, &result, &abs_err) sig_off() - else: + else: _a = a _b = b W = gsl_integration_workspace_alloc(n) @@ -357,7 +384,7 @@ def numerical_integral(func, a, b=None, sig_off() - elif algorithm == "qags": + elif algorithm == "qags": W = gsl_integration_workspace_alloc(n) sig_on() @@ -366,13 +393,14 @@ def numerical_integral(func, a, b=None, gsl_integration_qags(&F, _a, _b, eps_abs, eps_rel, n, W, &result, &abs_err) sig_off() - else: + else: raise TypeError("invalid integration algorithm") - if W != NULL: + if W != NULL: gsl_integration_workspace_free(W) - return result, abs_err + return result, abs_err + cdef double c_monte_carlo_f(double *t, size_t dim, void *params): cdef double value @@ -393,6 +421,7 @@ cdef double c_monte_carlo_f(double *t, size_t dim, void *params): return value + cdef double c_monte_carlo_ff(double *x, size_t dim, void *params): cdef double result ( params).call_c(x, &result) @@ -598,11 +627,11 @@ def monte_carlo_integral(func, xl, xu, size_t calls, algorithm='plain', wrapper = PyFunctionWrapper() wrapper.the_function = func - if params == [] and len(sage_getargspec(wrapper.the_function)[0]) == dim: + if not params and len(sage_getargspec(wrapper.the_function)[0]) == dim: wrapper.the_parameters = [] - elif params == [] and len(sage_getargspec(wrapper.the_function)[0]) > dim: + elif not params and len(sage_getargspec(wrapper.the_function)[0]) > dim: raise ValueError("Integrand has parameters but no parameters specified") - elif params != []: + elif params: wrapper.the_parameters = params wrapper.lx = [None] * dim diff --git a/src/sage/categories/action.pyx b/src/sage/categories/action.pyx index fb39aaafa0a..7ec46f80a98 100644 --- a/src/sage/categories/action.pyx +++ b/src/sage/categories/action.pyx @@ -71,8 +71,8 @@ cdef inline category(x): try: return x.category() except AttributeError: - import sage.categories.all - return sage.categories.all.Objects() + from sage.categories.objects import Objects + return Objects() cdef class Action(Functor): diff --git a/src/sage/categories/all.py b/src/sage/categories/all.py index a60c45eba95..62ff402d522 100644 --- a/src/sage/categories/all.py +++ b/src/sage/categories/all.py @@ -3,16 +3,15 @@ from .category import Category -from .category_types import( - Elements, - ChainComplexes, -) +from .category_types import Elements -from sage.categories.simplicial_complexes import SimplicialComplexes +from .chain_complexes import ChainComplexes -from sage.categories.tensor import tensor -from sage.categories.signed_tensor import tensor_signed -from sage.categories.cartesian_product import cartesian_product +from .simplicial_complexes import SimplicialComplexes + +from .tensor import tensor +from .signed_tensor import tensor_signed +from .cartesian_product import cartesian_product from .functor import (ForgetfulFunctor, IdentityFunctor) diff --git a/src/sage/categories/category_types.py b/src/sage/categories/category_types.py index ed1c7d5cce8..0ec5cce262a 100644 --- a/src/sage/categories/category_types.py +++ b/src/sage/categories/category_types.py @@ -21,6 +21,8 @@ lazy_import('sage.categories.objects', 'Objects') lazy_import('sage.misc.latex', 'latex') +lazy_import('sage.categories.chain_complexes', 'ChainComplexes', + deprecation=29917) #################################################################### # Different types of categories @@ -614,38 +616,3 @@ def __call__(self, v): if v in self: return v return self.ring().ideal(v) - -# TODO: make this into a better category -############################################################# -# ChainComplex -############################################################# -class ChainComplexes(Category_module): - """ - The category of all chain complexes over a base ring. - - EXAMPLES:: - - sage: ChainComplexes(RationalField()) - Category of chain complexes over Rational Field - - sage: ChainComplexes(Integers(9)) - Category of chain complexes over Ring of integers modulo 9 - - TESTS:: - - sage: TestSuite(ChainComplexes(RationalField())).run() - """ - - def super_categories(self): - """ - EXAMPLES:: - - sage: ChainComplexes(Integers(9)).super_categories() - [Category of modules over Ring of integers modulo 9] - """ - from sage.categories.all import Fields, Modules, VectorSpaces - base_ring = self.base_ring() - if base_ring in Fields(): - return [VectorSpaces(base_ring)] - return [Modules(base_ring)] - diff --git a/src/sage/categories/chain_complexes.py b/src/sage/categories/chain_complexes.py new file mode 100644 index 00000000000..72a4763b3b8 --- /dev/null +++ b/src/sage/categories/chain_complexes.py @@ -0,0 +1,49 @@ +""" +Category of chain complexes +""" + +#***************************************************************************** +# Copyright (C) 2007 Robert Bradshaw +# 2009 Mike Hansen +# 2013 Volker Braun +# 2013, 2015 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.categories.category_types import Category_module + +# TODO: make this into a better category +############################################################# +# ChainComplex +############################################################# +class ChainComplexes(Category_module): + """ + The category of all chain complexes over a base ring. + + EXAMPLES:: + + sage: ChainComplexes(RationalField()) + Category of chain complexes over Rational Field + + sage: ChainComplexes(Integers(9)) + Category of chain complexes over Ring of integers modulo 9 + + TESTS:: + + sage: TestSuite(ChainComplexes(RationalField())).run() + """ + + def super_categories(self): + """ + EXAMPLES:: + + sage: ChainComplexes(Integers(9)).super_categories() + [Category of modules over Ring of integers modulo 9] + """ + from sage.categories.all import Fields, Modules, VectorSpaces + base_ring = self.base_ring() + if base_ring in Fields(): + return [VectorSpaces(base_ring)] + return [Modules(base_ring)] diff --git a/src/sage/categories/coxeter_groups.py b/src/sage/categories/coxeter_groups.py index e01e5b66277..9dbbc7b9467 100644 --- a/src/sage/categories/coxeter_groups.py +++ b/src/sage/categories/coxeter_groups.py @@ -799,7 +799,7 @@ def bruhat_interval(self, x, y): if not x.bruhat_le(y): return ret ret.append([y]) - while ret[-1] != []: + while ret[-1]: nextlayer = [] for z in ret[-1]: for t in z.bruhat_lower_covers(): diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index efb906f3098..e62dd3e4cb7 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -178,8 +178,11 @@ def is_injective(self): if K is self.codomain(): return True - if self.domain().cardinality() > self.codomain().cardinality(): - return False + try: + if self.domain().cardinality() > self.codomain().cardinality(): + return False + except AttributeError: + pass raise NotImplementedError @@ -1063,7 +1066,7 @@ def normalize_arg(arg): # 1. If arg is a list, try to return a power series ring. if isinstance(arg, list): - if arg == []: + if not arg: raise TypeError("power series rings must have at least one variable") elif len(arg) == 1: # R[["a,b"]], R[[(a,b)]]... diff --git a/src/sage/combinat/combinatorial_algebra.py b/src/sage/combinat/combinatorial_algebra.py index a473c23813e..c9e4d0675d1 100644 --- a/src/sage/combinat/combinatorial_algebra.py +++ b/src/sage/combinat/combinatorial_algebra.py @@ -60,7 +60,7 @@ #***************************************************************************** from sage.combinat.free_module import CombinatorialFreeModule -from sage.misc.misc import repr_lincomb +from sage.misc.repr import repr_lincomb from sage.misc.cachefunc import cached_method from sage.categories.all import AlgebrasWithBasis from sage.structure.element import Element diff --git a/src/sage/combinat/designs/bibd.py b/src/sage/combinat/designs/bibd.py index b1545bd9a53..d4e9832911c 100644 --- a/src/sage/combinat/designs/bibd.py +++ b/src/sage/combinat/designs/bibd.py @@ -4,7 +4,7 @@ This module gathers everything related to Balanced Incomplete Block Designs. One can build a BIBD (or check that it can be built) with :func:`balanced_incomplete_block_design`:: - sage: BIBD = designs.balanced_incomplete_block_design(7,3) + sage: BIBD = designs.balanced_incomplete_block_design(7,3,1) In particular, Sage can build a `(v,k,1)`-BIBD when one exists for all `k\leq 5`. The following functions are available: @@ -15,7 +15,7 @@ :widths: 30, 70 :delim: | - :func:`balanced_incomplete_block_design` | Return a BIBD of parameters `v,k`. + :func:`balanced_incomplete_block_design` | Return a BIBD of parameters `v,k,\lambda`. :func:`BIBD_from_TD` | Return a BIBD through TD-based constructions. :func:`BIBD_from_difference_family` | Return the BIBD associated to the difference family ``D`` on the group ``G``. :func:`BIBD_from_PBD` | Return a `(v,k,1)`-BIBD from a `(r,K)`-PBD where `r=(v-1)/(k-1)`. @@ -56,29 +56,25 @@ from sage.categories.sets_cat import EmptySetError from sage.misc.unknown import Unknown from .design_catalog import transversal_design -from .block_design import BlockDesign from sage.arith.all import binomial, is_prime_power from .group_divisible_designs import GroupDivisibleDesign from .designs_pyx import is_pairwise_balanced_design -def balanced_incomplete_block_design(v, k, existence=False, use_LJCR=False): +def balanced_incomplete_block_design(v, k, lambd=1, existence=False, use_LJCR=False): r""" - Return a BIBD of parameters `v,k`. + Return a BIBD of parameters `v,k, \lambda`. - A Balanced Incomplete Block Design of parameters `v,k` is a collection + A Balanced Incomplete Block Design of parameters `v,k,\lambda` is a collection `\mathcal C` of `k`-subsets of `V=\{0,\dots,v-1\}` such that for any two - distinct elements `x,y\in V` there is a unique element `S\in \mathcal C` + distinct elements `x,y\in V` there are `\lambda` elements `S\in \mathcal C` such that `x,y\in S`. - More general definitions sometimes involve a `\lambda` parameter, and we - assume here that `\lambda=1`. - For more information on BIBD, see the :wikipedia:`corresponding Wikipedia entry `. INPUT: - - ``v,k`` (integers) + - ``v,k,lambd`` (integers) - ``existence`` (boolean) -- instead of building the design, return: @@ -107,16 +103,16 @@ def balanced_incomplete_block_design(v, k, existence=False, use_LJCR=False): EXAMPLES:: - sage: designs.balanced_incomplete_block_design(7, 3).blocks() + sage: designs.balanced_incomplete_block_design(7, 3, 1).blocks() [[0, 1, 3], [0, 2, 4], [0, 5, 6], [1, 2, 6], [1, 4, 5], [2, 3, 5], [3, 4, 6]] - sage: B = designs.balanced_incomplete_block_design(66, 6, use_LJCR=True) # optional - internet + sage: B = designs.balanced_incomplete_block_design(66, 6, 1, use_LJCR=True) # optional - internet sage: B # optional - internet Incidence structure with 66 points and 143 blocks sage: B.blocks() # optional - internet [[0, 1, 2, 3, 4, 65], [0, 5, 22, 32, 38, 58], [0, 6, 21, 30, 43, 48], ... - sage: designs.balanced_incomplete_block_design(66, 6, use_LJCR=True) # optional - internet + sage: designs.balanced_incomplete_block_design(66, 6, 1, use_LJCR=True) # optional - internet Incidence structure with 66 points and 143 blocks - sage: designs.balanced_incomplete_block_design(216, 6) + sage: designs.balanced_incomplete_block_design(216, 6, 1) Traceback (most recent call last): ... NotImplementedError: I don't know how to build a (216,6,1)-BIBD! @@ -165,12 +161,22 @@ def balanced_incomplete_block_design(v, k, existence=False, use_LJCR=False): sage: designs.balanced_incomplete_block_design(1171,10) (1171,10,1)-Balanced Incomplete Block Design - And we know some inexistence results:: + And we know some nonexistence results:: sage: designs.balanced_incomplete_block_design(21,6,existence=True) False + + Some BIBDs with `\lambda \neq 1`:: + + sage: designs.balanced_incomplete_block_design(176, 50, 14, existence=True) + True + sage: designs.balanced_incomplete_block_design(64,28,12) + (64,28,12)-Balanced Incomplete Block Design + sage: designs.balanced_incomplete_block_design(37,9,8) + (37,9,8)-Balanced Incomplete Block Design + sage: designs.balanced_incomplete_block_design(15,7,3) + (15,7,3)-Balanced Incomplete Block Design """ - lmbd = 1 # Trivial BIBD if v == 1: @@ -181,13 +187,13 @@ def balanced_incomplete_block_design(v, k, existence=False, use_LJCR=False): if k == v: if existence: return True - return BalancedIncompleteBlockDesign(v, [list(range(v))], check=False, copy=False) + return BalancedIncompleteBlockDesign(v, [list(range(v)) for _ in range(lambd)],lambd=lambd, check=False, copy=False) # Non-existence of BIBD if (v < k or k < 2 or - (v-1) % (k-1) != 0 or - (v*(v-1)) % (k*(k-1)) != 0 or + (lambd*(v-1)) % (k-1) != 0 or + (lambd*v*(v-1)) % (k*(k-1)) != 0 or # From the Handbook of combinatorial designs: # # With lambda>1 other exceptions are @@ -195,25 +201,24 @@ def balanced_incomplete_block_design(v, k, existence=False, use_LJCR=False): (k==6 and v in [36,46]) or (k==7 and v == 43) or # Fisher's inequality - (v*(v-1))/(k*(k-1)) < v): + (lambd*v*(v-1))/(k*(k-1)) < v): if existence: return False - raise EmptySetError("There exists no ({},{},{})-BIBD".format(v,k,lmbd)) + raise EmptySetError("There exists no ({},{},{})-BIBD".format(v, k, lambd)) if k == 2: if existence: return True - from itertools import combinations - return BalancedIncompleteBlockDesign(v, combinations(list(range(v)),2), check=False, copy=True) - if k == 3: + return BalancedIncompleteBlockDesign(v, [[x, y] for _ in range(lambd) for x in range(v) for y in range(x+1, v) if x != y], lambd=lambd, check=False, copy=True) + if k == 3 and lambd == 1: if existence: return v%6 == 1 or v%6 == 3 return steiner_triple_system(v) - if k == 4: + if k == 4 and lambd == 1: if existence: return v%12 == 1 or v%12 == 4 return BalancedIncompleteBlockDesign(v, v_4_1_BIBD(v), copy=False) - if k == 5: + if k == 5 and lambd == 1: if existence: return v%20 == 1 or v%20 == 5 return BalancedIncompleteBlockDesign(v, v_5_1_BIBD(v), copy=False) @@ -221,39 +226,39 @@ def balanced_incomplete_block_design(v, k, existence=False, use_LJCR=False): from .difference_family import difference_family from .database import BIBD_constructions - if (v,k,1) in BIBD_constructions: + if (v, k, lambd) in BIBD_constructions: if existence: return True - return BlockDesign(v,BIBD_constructions[(v,k,1)](), copy=False) - if BIBD_from_arc_in_desarguesian_projective_plane(v,k,existence=True): + return BalancedIncompleteBlockDesign(v,BIBD_constructions[(v, k, lambd)](), lambd=lambd, copy=False) + if lambd == 1 and BIBD_from_arc_in_desarguesian_projective_plane(v, k, existence=True): if existence: return True - B = BIBD_from_arc_in_desarguesian_projective_plane(v,k) + B = BIBD_from_arc_in_desarguesian_projective_plane(v, k) return BalancedIncompleteBlockDesign(v, B, copy=False) - if BIBD_from_TD(v,k,existence=True) is True: + if lambd == 1 and BIBD_from_TD(v, k, existence=True) is True: if existence: return True - return BalancedIncompleteBlockDesign(v, BIBD_from_TD(v,k), copy=False) - if v == (k-1)**2+k and is_prime_power(k-1): + return BalancedIncompleteBlockDesign(v, BIBD_from_TD(v, k), copy=False) + if lambd == 1 and v == (k-1)**2+k and is_prime_power(k-1): if existence: return True from .block_design import projective_plane return BalancedIncompleteBlockDesign(v, projective_plane(k-1),copy=False) - if difference_family(v,k,existence=True) is True: + if difference_family(v, k, l=lambd, existence=True) is True: if existence: return True - G,D = difference_family(v,k) - return BalancedIncompleteBlockDesign(v, BIBD_from_difference_family(G,D,check=False), copy=False) - if use_LJCR: + G, D = difference_family(v, k, l=lambd) + return BalancedIncompleteBlockDesign(v, BIBD_from_difference_family(G, D, check=False), lambd=lambd, copy=False) + if lambd == 1 and use_LJCR: from .covering_design import best_known_covering_design_www - B = best_known_covering_design_www(v,k,2) + B = best_known_covering_design_www(v, k, 2) # Is it a BIBD or just a good covering ? - expected_n_of_blocks = binomial(v,2)//binomial(k,2) + expected_n_of_blocks = binomial(v, 2)//binomial(k, 2) if B.low_bd() > expected_n_of_blocks: if existence: return False - raise EmptySetError("There exists no ({},{},{})-BIBD".format(v,k,lmbd)) + raise EmptySetError("There exists no ({},{},{})-BIBD".format(v, k, lambd)) B = B.incidence_structure() if B.num_blocks() == expected_n_of_blocks: if existence: @@ -264,7 +269,7 @@ def balanced_incomplete_block_design(v, k, existence=False, use_LJCR=False): if existence: return Unknown else: - raise NotImplementedError("I don't know how to build a ({},{},1)-BIBD!".format(v,k)) + raise NotImplementedError("I don't know how to build a ({},{},{})-BIBD!".format(v, k, lambd)) def steiner_triple_system(n): r""" @@ -432,8 +437,8 @@ def BIBD_from_TD(v,k,existence=False): """ # First construction if (v%k == 0 and - balanced_incomplete_block_design(v//k,k,existence=True) is True and - transversal_design(k,v//k,existence=True) is True): + balanced_incomplete_block_design(v//k, k, existence=True) is True and + transversal_design(k, v//k, existence=True) is True): if existence: return True @@ -448,8 +453,8 @@ def BIBD_from_TD(v,k,existence=False): # Second construction elif ((v-1)%k == 0 and - balanced_incomplete_block_design((v-1)//k+1,k,existence=True) and - transversal_design(k,(v-1)//k,existence=True)): + balanced_incomplete_block_design((v-1)//k+1,k,existence=True) is True and + transversal_design(k,(v-1)//k,existence=True)) is True: if existence: return True @@ -563,7 +568,8 @@ def BIBD_from_difference_family(G, D, lambd=None, check=True): GG = Gset.copy() while GG: g = GG.pop() - if S: GG.difference_update(mul(s,g) for s in S) + if S: + GG.difference_update(mul(s,g) for s in S) bibd.append([p_to_i[mul(i,g)] for i in b]) if check: diff --git a/src/sage/combinat/designs/database.py b/src/sage/combinat/designs/database.py index 22b453120e3..4f1343a57c6 100644 --- a/src/sage/combinat/designs/database.py +++ b/src/sage/combinat/designs/database.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" Database of small combinatorial designs @@ -43,15 +44,22 @@ Chapman & Hall/CRC 2012 +.. [Aschbacher71] \M. Aschbacher, + On collineation groups of symmetric block designs. + J. Combinatorial Theory Ser. A 11 (1971), pp. 272–281. + +.. [Hall71] \M. Hall, Jr., + Combinatorial designs and groups. + Actes du Congrès International des Mathématiciens (Nice, 1970), + v.3, pp. 217–222. Gauthier-Villars, Paris, 1971. + Functions --------- """ from __future__ import print_function, absolute_import from sage.combinat.designs.orthogonal_arrays import (OA_from_quasi_difference_matrix, - OA_from_Vmt, QDM_from_Vmt, - OA_from_wider_OA, OA_from_PBD, OA_n_times_2_pow_c_from_matrix, orthogonal_array) @@ -771,7 +779,6 @@ def OA_9_120(): sage: designs.orthogonal_arrays.is_available(9,120) True """ - from .incidence_structures import IncidenceStructure RBIBD_120 = RBIBD_120_8_1() equiv = [RBIBD_120[i*15:(i+1)*15] for i in range(17)] @@ -3151,7 +3158,6 @@ def DM_12_6_1(): :doi:`10.1016/0012-365X(75)90040-0`, Discrete Mathematics, Volume 11, Issue 3, 1975, Pages 255-369. """ - from sage.groups.additive_abelian.additive_abelian_group import AdditiveAbelianGroup from sage.rings.finite_rings.integer_mod_ring import IntegerModRing as AdditiveCyclic G = AdditiveCyclic(2).cartesian_product(AdditiveCyclic(6)) M = [[(0,0),(0,0),(0,0),(0,0),(0,0),(0,0)], @@ -3689,7 +3695,6 @@ def DM_52_6_1(): sage: _ = designs.difference_matrix(52,6) """ - from sage.rings.finite_rings.integer_mod_ring import IntegerModRing as AdditiveCyclic from sage.rings.finite_rings.finite_field_constructor import FiniteField F4 = FiniteField(4,'z') G13 = FiniteField(13) @@ -3721,7 +3726,6 @@ def DM_52_6_1(): Mb=[[(0,0)]*6] from itertools import product - p = lambda x,y : G(tuple([x*yy for yy in G(y)])) def t1(i,R): if i > 1: @@ -4565,6 +4569,127 @@ def BIBD_201_6_1(): bibd = RecursivelyEnumeratedSet([frozenset(e) for e in bibd], successors=gens) return IncidenceStructure(bibd)._blocks +def BIBD_79_13_2(): + r""" + Return a symmetric `(79,13,2)`-BIBD. + + The construction implemented is the one described in [Aschbacher71]_. + A typo in that paper was corrected in [Hall71]_. + + .. NOTE:: + + A symmetric `(v,k,\lambda)` BIBD is a `(v,k,\lambda)` BIBD with `v` blocks. + + EXAMPLES: + + sage: from sage.combinat.designs.database import BIBD_79_13_2 + sage: D = IncidenceStructure(BIBD_79_13_2()) + sage: D.is_t_design(t=2, v=79, k=13, l=2) + True + """ + from sage.libs.gap.libgap import libgap + + g11 = libgap.Z(11) # generator for GF(11) + one = g11**0 + zero = 0*g11 + + X = libgap([[one, one], [zero, one]]) + Y = libgap([[5*one, zero], [zero, 9*one]]) + Z = libgap([[-one, zero], [zero, one]]) + + G = libgap.Group(X, Y, Z) + H1 = libgap.Group(X, Y) + H23 = libgap.Group(Y, Z) + H4 = libgap.Group(Z) + + P1Action = G.FactorCosetAction(H1) + P23Action = G.FactorCosetAction(H23) + P4Action = G.FactorCosetAction(H4) + + libgap.set_global("p1Act", P1Action) + libgap.set_global("p23Act", P23Action) + libgap.set_global("p4Act", P4Action) + + action = libgap.function_factory("""function(pair, g) + local i, C, homs; + i := pair[1]; + C := pair[2]; + homs := [p1Act, p23Act, p23Act, p4Act]; + return [i, C^(ImageElm(homs[i],g))]; + end;""") + + p1 = (1,1) + p2 = (2,1) + p3 = (3,1) + p4 = (4,1) + + B1 = list(libgap.Orbit(H4, p1, action)) + list(libgap.Orbit(G, p2, action)) + B2 = list(libgap.Orbit(H4, p1, action)) + list(libgap.Orbit(G, p3, action)) + B3 = list(libgap([p1, p2, p3])) + list(libgap.Orbit(libgap.Group(Y), action(p4, X), action)) + list(libgap.Orbit(libgap.Group(Y), action(p4, X**4), action)) + B4 = [action(p2, X**2), action(p2, X**-2), action(p3, X**5), action(p3, X**-5), p4, + action(p4, X * Y**2), action(p4, X**-1 * Y**2), action(p4, X*Y), action(p4, X**-1 * Y), + action(p4, X**5 * Y), action(p4, X**-5 * Y), action(p4, X**5 * Y**4), action(p4, X**-5 * Y**4)] + + points = [] + for i in range(1,5): + points += list(libgap.Orbit(G, (i,1), action)) + + permAction = libgap.Action(G, points, action) + + baseBlocks = [libgap.Set(list(map(lambda p: libgap.Position(points, p), B))) for B in [B1, B2, B3, B4]] + + B3Orbit = libgap.Orbit(permAction, baseBlocks[2], libgap.OnSets) + B4Orbit = libgap.Orbit(permAction, baseBlocks[3], libgap.OnSets) + blocks = baseBlocks[0:2] + list(B3Orbit) + list(B4Orbit) + + # clear gap variables + libgap.unset_global("p1Act") + libgap.unset_global("p23Act") + libgap.unset_global("p4Act") + return [[int(t)-1 for t in y] for y in blocks] + +def BIBD_56_11_2(): + r""" + Return a symmetric `(56,11,2)`-BIBD. + + The construction implemented is given in [Hall71]_. + + .. NOTE:: + + A symmetric `(v,k,\lambda)` BIBD is a `(v,k,\lambda)` BIBD with `v` blocks. + + EXAMPLES: + + sage: from sage.combinat.designs.database import BIBD_56_11_2 + sage: D = IncidenceStructure(BIBD_56_11_2()) + sage: D.is_t_design(t=2, v=56, k=11, l=2) + True + """ + from sage.libs.gap.libgap import libgap + from .incidence_structures import IncidenceStructure + + a = list(range(2,57)) + [50] + a[6] = 1 + a[13] = 8 + a[20] = 15 + a[27] = 22 + a[34] = 29 + a[41] = 36 + a[48] = 43 + + b = [1,8,27,36,20,14,42,41,29,52,24,30,55,22,26,21,10,40,23,53, + 56,6,49,46,50,32,28,3,34,48,4,15,13,9,18,31,51,39,43,35, + 2,54,38,25,45,11,37,12,19,44,47,17,5,7,33,16] + + a = libgap.PermList(a) + b = libgap.PermList(b) + G = libgap.Group(a,b) + + B = libgap.Set([1,12,19,23,30,37,45,47,48,49,51]) + + D = IncidenceStructure(libgap.Orbit(G, B, libgap.OnSets)) + return D._blocks + # Index of the BIBD constructions # # Associates to triple (v,k,lambda) a function that return a @@ -4574,8 +4699,10 @@ def BIBD_201_6_1(): # Note that the values are a list of blocks and not a design object BIBD_constructions = { ( 45,9,8): BIBD_45_9_8, + (56,11,2): BIBD_56_11_2, ( 66,6,1): BIBD_66_6_1, ( 76,6,1): BIBD_76_6_1, + (79,13,2): BIBD_79_13_2, ( 96,6,1): BIBD_96_6_1, (120,8,1): RBIBD_120_8_1, (106,6,1): BIBD_106_6_1, diff --git a/src/sage/combinat/dyck_word.py b/src/sage/combinat/dyck_word.py index a87cc7b2001..438b68ef939 100644 --- a/src/sage/combinat/dyck_word.py +++ b/src/sage/combinat/dyck_word.py @@ -1580,7 +1580,7 @@ def to_standard_tableau(self): else: close_positions.append(i + 1) from sage.combinat.tableau import StandardTableau - return StandardTableau([x for x in [open_positions, close_positions] if x != []]) + return StandardTableau([x for x in [open_positions, close_positions] if x]) def to_tamari_sorting_tuple(self): """ diff --git a/src/sage/combinat/gray_codes.py b/src/sage/combinat/gray_codes.py index d9c6da78127..87282c08a8e 100644 --- a/src/sage/combinat/gray_codes.py +++ b/src/sage/combinat/gray_codes.py @@ -3,11 +3,8 @@ REFERENCES: -.. [Knuth-TAOCP2A] \D. Knuth "The art of computer programming", fascicules 2A, - "generating all n-tuples" - -.. [Knuth-TAOCP3A] \D. Knuth "The art of computer programming", fascicule 3A - "generating all combinations" +.. [Knuth-TAOCP4A] \D. Knuth *The Art of Computer Programming. Volume 4A. + Combinatorial Algorithms, Part 1.* Functions --------- @@ -25,8 +22,8 @@ def product(m): apply the increment ``i`` at the position ``p``. By construction, the increment is either ``+1`` or ``-1``. - This is algorithm H in [Knuth-TAOCP2A]_: loopless reflected mixed-radix Gray - generation. + This is algorithm H in [Knuth-TAOCP4A]_ Section 7.2.1.1, "Generating All + `n`-Tuples": loopless reflected mixed-radix Gray generation. INPUT: @@ -123,7 +120,7 @@ def combinations(n,t): The ground set is always `\{0, 1, ..., n-1\}`. Note that ``n`` can be infinity in that algorithm. - See [Knuth-TAOCP3A]_. + See [Knuth-TAOCP4A]_ Section 7.2.1.3, "Generating All Combinations". INPUT: @@ -229,7 +226,7 @@ def _revolving_door_odd(n,t): sage: sum(1 for _ in _revolving_door_odd(10,5)) == binomial(10,5) - 1 True """ - # note: the numerotation of the steps below follows Kunth TAOCP + # note: the numbering of the steps below follows Knuth TAOCP c = list(range(t)) + [n] # the combination (ordered list of numbers of length t+1) while True: @@ -274,7 +271,7 @@ def _revolving_door_even(n,t): sage: sum(1 for _ in _revolving_door_even(12,6)) == binomial(12,6) - 1 True """ - # note: the numerotation of the setps below follows Kunth TAOCP + # note: the numbering of the steps below follows Knuth TAOCP c = list(range(t)) + [n] # the combination (ordered list of numbers of length t+1) diff --git a/src/sage/combinat/matrices/dancing_links.pyx b/src/sage/combinat/matrices/dancing_links.pyx index bfbc01819c9..c62780c5d74 100644 --- a/src/sage/combinat/matrices/dancing_links.pyx +++ b/src/sage/combinat/matrices/dancing_links.pyx @@ -922,7 +922,7 @@ cdef class dancing_linksWrapper: Using some optional SAT solvers:: - sage: x.to_sat_solver('cryptominisat') # optional cryptominisat + sage: x.to_sat_solver('cryptominisat') # optional - cryptominisat CryptoMiniSat solver: 4 variables, 7 clauses. """ @@ -982,8 +982,8 @@ cdef class dancing_linksWrapper: Using optional solvers:: - sage: s = d.one_solution_using_sat_solver('glucose') # optional glucose - sage: s in solutions # optional glucose + sage: s = d.one_solution_using_sat_solver('glucose') # optional - glucose + sage: s in solutions # optional - glucose True When no solution is found:: @@ -1000,6 +1000,139 @@ cdef class dancing_linksWrapper: return None return [key for (key,val) in enumerate(solution, start=-1) if val] + @cached_method + def to_milp(self, solver=None): + r""" + Return the mixed integer linear program (MILP) representing an + equivalent problem. + + See also :mod:`sage.numerical.mip.MixedIntegerLinearProgram`. + + INPUT: + + - ``solver`` -- string or ``None`` (default: ``None``), possible + values include ``'GLPK'``, ``'GLPK/exact'``, ``'Coin'``, + ``'CPLEX'``, ``'Gurobi'``, ``'CVXOPT'``, ``'PPL'``, + ``'InteractiveLP'``. + + OUTPUT: + + - MixedIntegerLinearProgram instance + - MIPVariable of dimension 1 + + EXAMPLES:: + + sage: from sage.combinat.matrices.dancing_links import dlx_solver + sage: rows = [[0,1,2], [0,2], [1], [3]] + sage: d = dlx_solver(rows) + sage: p,x = d.to_milp() + sage: p + Boolean Program (no objective, 4 variables, 4 constraints) + sage: x + MIPVariable of dimension 1 + + In the reduction, the boolean variable x_i is True if and only if + the i-th row is in the solution:: + + sage: p.show() + Maximization: + + + Constraints: + one 1 in 0-th column: 1.0 <= x_0 + x_1 <= 1.0 + one 1 in 1-th column: 1.0 <= x_0 + x_2 <= 1.0 + one 1 in 2-th column: 1.0 <= x_0 + x_1 <= 1.0 + one 1 in 3-th column: 1.0 <= x_3 <= 1.0 + Variables: + x_0 is a boolean variable (min=0.0, max=1.0) + x_1 is a boolean variable (min=0.0, max=1.0) + x_2 is a boolean variable (min=0.0, max=1.0) + x_3 is a boolean variable (min=0.0, max=1.0) + + Using some optional MILP solvers:: + + sage: d.to_milp('gurobi') # optional - gurobi sage_numerical_backends_gurobi + (Boolean Program (no objective, 4 variables, 4 constraints), + MIPVariable of dimension 1) + + """ + from sage.numerical.mip import MixedIntegerLinearProgram + p = MixedIntegerLinearProgram(solver=solver) + + # x[i] == True iff i-th dlx row is in the solution + x = p.new_variable(binary=True, indices=range(self.nrows())) + + # Construction of the columns (transpose of the rows) + columns = [[] for _ in range(self.ncols())] + for i,row in enumerate(self.rows()): + for a in row: + columns[a].append(i) + + # Constraints: exactly one 1 in each column + for j,column in enumerate(columns): + S = p.sum(x[a] for a in column) + name = "one 1 in {}-th column".format(j) + p.add_constraint(S==1, name=name) + + return p,x + + def one_solution_using_milp_solver(self, solver=None): + r""" + Return a solution found using a MILP solver. + + INPUT: + + - ``solver`` -- string or ``None`` (default: ``None``), possible + values include ``'GLPK'``, ``'GLPK/exact'``, ``'Coin'``, + ``'CPLEX'``, ``'Gurobi'``, ``'CVXOPT'``, ``'PPL'``, + ``'InteractiveLP'``. + + OUTPUT: + + list of rows or ``None`` if no solution is found + + .. NOTE:: + + When comparing the time taken by method `one_solution`, have in + mind that `one_solution_using_milp_solver` first creates (and + caches) the MILP solver instance from the dancing links solver. + This copy of data may take many seconds depending on the size + of the problem. + + EXAMPLES:: + + sage: from sage.combinat.matrices.dancing_links import dlx_solver + sage: rows = [[0,1,2], [3,4,5], [0,1], [2,3,4,5], [0], [1,2,3,4,5]] + sage: d = dlx_solver(rows) + sage: solutions = [[0,1], [2,3], [4,5]] + sage: d.one_solution_using_milp_solver() in solutions + True + + Using optional solvers:: + + sage: s = d.one_solution_using_milp_solver('gurobi') # optional - gurobi sage_numerical_backends_gurobi + sage: s in solutions # optional - gurobi sage_numerical_backends_gurobi + True + + When no solution is found:: + + sage: rows = [[0,1,2], [2,3,4,5], [0,1,2,3]] + sage: d = dlx_solver(rows) + sage: d.one_solution_using_milp_solver() is None + True + + """ + from sage.numerical.mip import MIPSolverException + p,x = self.to_milp(solver) + try: + p.solve() + except MIPSolverException: + return None + else: + soln = p.get_values(x) + support = sorted(key for key in soln if soln[key]) + return support + def dlx_solver(rows): """ Internal-use wrapper for the dancing links C++ code. diff --git a/src/sage/combinat/partition_tuple.py b/src/sage/combinat/partition_tuple.py index 45b75b582e6..e65ed33ef68 100644 --- a/src/sage/combinat/partition_tuple.py +++ b/src/sage/combinat/partition_tuple.py @@ -1771,7 +1771,7 @@ def defect(self, e, multicharge): class PartitionTuples(UniqueRepresentation, Parent): - """ + r""" Class of all partition tuples. For more information about partition tuples, see :class:`PartitionTuple`. @@ -1785,8 +1785,16 @@ class PartitionTuples(UniqueRepresentation, Parent): - ``size`` -- the total number of cells - - ``regular`` -- the highest multiplicity an entry may have in a - component plus `1` + - ``regular`` -- a positive integer or a tuple of non-negative + integers; if an integer, the highest multiplicity an entry may + have in a component plus `1` + + If a level `k` is specified and ``regular`` is a tuple of integers + `\ell_1, \ldots, \ell_k`, then this specifies partition tuples `\mu` + such that `\mu_i` is `\ell_i`-regular, where `0` here + represents `\infty`-regular partitions (equivalently, partitions + without restrictions). If ``regular`` is an integer `\ell`, then + we set `\ell_i = \ell` for all `i`. TESTS:: @@ -1796,6 +1804,10 @@ class PartitionTuples(UniqueRepresentation, Parent): True sage: ( [] ) in PartitionTuples() True + sage: PartitionTuples(level=1, regular=(0,)) + Partitions + sage: PartitionTuples(level=1, size=3, regular=(0,)) + Partitions of the integer 3 Check that :trac:`14145` has been fixed:: @@ -1818,15 +1830,28 @@ def __classcall_private__(klass, level=None, size=None, regular=None): Partition tuples of size 3 sage: PartitionTuples(3,8) Partition tuples of level 3 and size 8 + sage: PartitionTuples(level=3, regular=(0,2,4)) + (0, 2, 4)-Regular partition tuples of level 3 + sage: PartitionTuples(level=1,regular=(4,)) is PartitionTuples(level=1, regular=4) + True """ - # sanity testing - if level is not None and (not isinstance(level,(int,Integer)) or level<1): + if level is not None and (not isinstance(level, (int, Integer)) or level < 1): raise ValueError('the level must be a positive integer') - if size is not None and (not isinstance(size,(int,Integer)) or size<0): + if size is not None and (not isinstance(size, (int, Integer)) or size < 0): raise ValueError('the size must be a non-negative integer') + if isinstance(regular, (list, tuple)): + if level is None: + raise ValueError("When no level is specified, regular must be " + "a positive integer") + if len(regular) != level: + raise ValueError("regular must be a list of length {}, got {}".format( + level, regular)) + if regular == 0: + raise ValueError("regular must be a positive integer or a tuple " + "of non-negative integers") if level is None: if size is None: if regular is None: @@ -1838,16 +1863,23 @@ def __classcall_private__(klass, level=None, size=None, regular=None): return RegularPartitionTuples_size(size, regular) elif level == 1: + if isinstance(regular, (list, tuple)): + regular = regular[0] if size is None: - if regular is None: + if regular is None or regular == 0: return _Partitions return RegularPartitions_all(regular) - if regular is None: + if regular is None or regular == 0: return Partitions_n(size) return RegularPartitions_n(size, regular) # Higher level + if regular is not None: + if not isinstance(regular, (list, tuple)): + regular = (regular,) * level + else: + regular = tuple(regular) if size is None: if regular is None: return PartitionTuples_level(level) @@ -1857,11 +1889,11 @@ def __classcall_private__(klass, level=None, size=None, regular=None): return RegularPartitionTuples_level_size(level, size, regular) Element = PartitionTuple - options=Partitions.options + options = Partitions.options # default for level - _level=None - _size=None + _level = None + _size = None def _element_constructor_(self, mu): r""" @@ -2107,8 +2139,7 @@ class PartitionTuples_level(PartitionTuples): Class of partition tuples of a fixed level, but summing to an arbitrary integer. """ - - def __init__(self, level): + def __init__(self, level, category=None): r""" Initializes this class. @@ -2122,8 +2153,10 @@ def __init__(self, level): """ if level not in NN: raise ValueError('level must be a non-negative integer') - super(PartitionTuples_level, self).__init__(category=InfiniteEnumeratedSets()) - self._level=level + if category is None: + category = InfiniteEnumeratedSets() + super(PartitionTuples_level, self).__init__(category=category) + self._level = level def _repr_(self): """ @@ -2523,7 +2556,7 @@ def __contains__(self, mu): return max(mu.to_exp() + [0]) < self._ell if isinstance(mu, PartitionTuple): return all(max(nu.to_exp() + [0]) < self._ell for nu in mu) - if len(mu) == 0: + if not mu: return True if mu in _Partitions: return all(mu.count(i) < self._ell for i in set(mu) if i > 0) @@ -2610,27 +2643,85 @@ def __iter__(self): yield self.element_class(self, list(mu)) -class RegularPartitionTuples_level(RegularPartitionTuples): +class RegularPartitionTuples_level(PartitionTuples_level): r""" - Class of `\ell`-regular partition tuples with a fixed level. + Regular Partition tuples of a fixed level. + + INPUT: + + - ``level`` -- a non-negative Integer; the level + - ``regular`` -- a positive integer or a tuple of non-negative + integers; if an integer, the highest multiplicity an entry may + have in a component plus `1` with `0` representing `\infty`-regular + (equivalently, partitions without restrictions) + + ``regular`` is a tuple of integers `(\ell_1, \ldots, \ell_k)` that + specifies partition tuples `\mu` such that `\mu_i` is `\ell_i`-regular. + If ``regular`` is an integer `\ell`, then we set `\ell_i = \ell` for + all `i`. + + EXAMPLES:: + + sage: RPT = PartitionTuples(level=4, regular=(2,3,0,2)) + sage: RPT[:24] + [([], [], [], []), + ([1], [], [], []), + ([], [1], [], []), + ([], [], [1], []), + ([], [], [], [1]), + ([2], [], [], []), + ([1], [1], [], []), + ([1], [], [1], []), + ([1], [], [], [1]), + ([], [2], [], []), + ([], [1, 1], [], []), + ([], [1], [1], []), + ([], [1], [], [1]), + ([], [], [2], []), + ([], [], [1, 1], []), + ([], [], [1], [1]), + ([], [], [], [2]), + ([3], [], [], []), + ([2, 1], [], [], []), + ([2], [1], [], []), + ([2], [], [1], []), + ([2], [], [], [1]), + ([1], [2], [], []), + ([1], [1, 1], [], [])] + sage: [[1,1],[3],[5,5,5],[7,2]] in RPT + False + sage: [[3,1],[3],[5,5,5],[7,2]] in RPT + True + sage: [[3,1],[3],[5,5,5]] in RPT + False """ def __init__(self, level, regular): r""" Initialize ``self``. - EXAMPLES:: + TESTS:: + sage: RPT = PartitionTuples(level=2, regular=(0,0)) + sage: RPT.category() + Category of infinite enumerated sets sage: RPT = PartitionTuples(level=4, regular=3) sage: TestSuite(RPT).run() """ - if level not in ZZ or level <= 0: - raise ValueError('level must be a positive integer') - if regular > 1: + if not level in NN: + raise ValueError('level must be a non-negative integer') + if not isinstance(regular, tuple): + # This should not happen if called from RegularPartitionTuples + regular = (regular,) * level + if any (r != 1 for r in regular): category = InfiniteEnumeratedSets() else: category = FiniteEnumeratedSets() - RegularPartitionTuples.__init__(self, regular, category=category) - self._level = level + if any (r not in NN for r in regular): + raise ValueError('regular must be a tuple of non-negative integers') + if len(regular) != level: + raise ValueError("regular must be a tuple with length {}".format(level)) + PartitionTuples_level.__init__(self, level, category=category) + self._ell = regular def _repr_(self): """ @@ -2640,8 +2731,14 @@ def _repr_(self): sage: PartitionTuples(level=4, regular=3) 3-Regular partition tuples of level 4 + sage: PartitionTuples(level=4, regular=(2,3,0,2)) + (2, 3, 0, 2)-Regular partition tuples of level 4 """ - return '{}-Regular partition tuples of level {}'.format(self._ell, self._level) + if self._ell[1:] == self._ell[:-1]: + return '{}-Regular partition tuples of level {}'.format(self._ell[0], + self._level) + return '{}-Regular partition tuples of level {}'.format(self._ell, + self._level) def __contains__(self, mu): r""" @@ -2658,11 +2755,51 @@ def __contains__(self, mu): False sage: [4, 3, 2] in RPT False + + sage: RPT = PartitionTuples(level=3, regular=(2,1,4)) + sage: [[4], [2], [5]] in RPT + False + sage: [[4], [], [5]] in RPT + True + sage: [[4,3], [], [5]] in RPT + True + sage: [[4,4], [], [5]] in RPT + False + sage: [[4,3], [5]] in RPT + False + sage: [5, 4, 3] in RPT + False + sage: [] in RPT + False + sage: [[], [], []] in RPT + True + sage: [[], [], [], [2]] in RPT + False + + sage: from sage.combinat.partition_tuple import RegularPartitionTuples_level + sage: RPT = RegularPartitionTuples_level(1, (3,)); RPT + 3-Regular partition tuples of level 1 + sage: [[2,2]] in RPT + True + sage: [[2,2,2]] in RPT + False """ if self._level == 1: - if mu[0] in ZZ: - return mu in RegularPartitions_all(self._ell) - return RegularPartitionTuples.__contains__(self, mu) and self._level == len(mu) + try: + if mu[0] in ZZ: + return mu in RegularPartitions_all(self._ell[0]) + except (TypeError, ValueError): + return False + return mu[0] in RegularPartitions_all(self._ell[0]) + if mu not in PartitionTuples_level(self._level): + return False + if isinstance(mu, Partition): # it is level 1 + return False + if isinstance(mu, PartitionTuple): + return all(max(p.to_exp() + [0]) < ell for p, ell in zip(mu, self._ell) + if ell > 0) + return all(p in RegularPartitions_all(ell) for p, ell in zip(mu, self._ell) + if ell > 0) def __iter__(self): r""" @@ -2671,6 +2808,31 @@ def __iter__(self): EXAMPLES:: + sage: PartitionTuples(level=3, regular=(2,1,4))[:24] + [([], [], []), + ([1], [], []), + ([], [], [1]), + ([2], [], []), + ([1], [], [1]), + ([], [], [2]), + ([], [], [1, 1]), + ([3], [], []), + ([2, 1], [], []), + ([2], [], [1]), + ([1], [], [2]), + ([1], [], [1, 1]), + ([], [], [3]), + ([], [], [2, 1]), + ([], [], [1, 1, 1]), + ([4], [], []), + ([3, 1], [], []), + ([3], [], [1]), + ([2, 1], [], [1]), + ([2], [], [2]), + ([2], [], [1, 1]), + ([1], [], [3]), + ([1], [], [2, 1]), + ([1], [], [1, 1, 1])] sage: PartitionTuples(level=4, regular=2)[:20] [([], [], [], []), ([1], [], [], []), @@ -2784,16 +2946,58 @@ def __iter__(self): yield self.element_class(self, list(mu)) -class RegularPartitionTuples_level_size(RegularPartitionTuples): +class RegularPartitionTuples_level_size(PartitionTuples_level_size): r""" - Class of `\ell`-regular partition tuples with a fixed level and a fixed - size. + Class of `\ell`-regular partition tuples with a fixed level and + a fixed size. + + INPUT: + + - ``level`` -- a non-negative Integer; the level + - ``size`` -- a non-negative Integer; the size + - ``regular`` -- a positive integer or a tuple of non-negative + integers; if an integer, the highest multiplicity an entry may + have in a component plus `1` with `0` representing `\infty`-regular + (equivalently, partitions without restrictions) + + ``regular`` is a tuple of integers `(\ell_1, \ldots, \ell_k)` that + specifies partition tuples `\mu` such that `\mu_i` is `\ell_i`-regular. + If ``regular`` is an integer `\ell`, then we set `\ell_i = \ell` for + all `i`. + + EXAMPLES:: + + sage: PartitionTuples(level=3, size=7, regular=(2,1,3))[0:24] + [([7], [], []), + ([6, 1], [], []), + ([5, 2], [], []), + ([4, 3], [], []), + ([4, 2, 1], [], []), + ([6], [], [1]), + ([5, 1], [], [1]), + ([4, 2], [], [1]), + ([3, 2, 1], [], [1]), + ([5], [], [2]), + ([5], [], [1, 1]), + ([4, 1], [], [2]), + ([4, 1], [], [1, 1]), + ([3, 2], [], [2]), + ([3, 2], [], [1, 1]), + ([4], [], [3]), + ([4], [], [2, 1]), + ([3, 1], [], [3]), + ([3, 1], [], [2, 1]), + ([3], [], [4]), + ([3], [], [3, 1]), + ([3], [], [2, 2]), + ([3], [], [2, 1, 1]), + ([2, 1], [], [4])] """ def __init__(self, level, size, regular): r""" Initialize ``self``. - EXAMPLES:: + TESTS:: sage: RPT = PartitionTuples(4,2,3) sage: TestSuite(RPT).run() @@ -2802,9 +3006,15 @@ def __init__(self, level, size, regular): raise ValueError('size must be a non-negative integer') if not (level in ZZ and level > 0): raise ValueError('level must be a positive integer') - RegularPartitionTuples.__init__(self, regular, category=FiniteEnumeratedSets()) - self._level = level - self._size = size + if not isinstance(regular, tuple): + #This should not happen if called from RegularPartitionTuples + regular = (regular,)*level + if len(regular) != level: + raise ValueError('regular must be a list with length {}'.format(level)) + if any (i not in NN for i in regular): + raise ValueError('regular must be a list of non-negative integers') + PartitionTuples_level_size.__init__(self, level, size) + self._ell = regular def _repr_(self): """ @@ -2812,12 +3022,18 @@ def _repr_(self): EXAMPLES:: + sage: PartitionTuples(level=3, size=7, regular=(2,1,4)) + (2, 1, 4)-Regular partition tuples of level 3 and size 7 sage: PartitionTuples(4,2,3) 3-Regular partition tuples of level 4 and size 2 sage: PartitionTuples(size=2,level=4,regular=3) 3-Regular partition tuples of level 4 and size 2 """ - return '{}-Regular partition tuples of level {} and size {}'.format(self._ell, self._level, self._size) + if self._ell[1:] == self._ell[:-1]: + return '{}-Regular partition tuples of level {} and size {}'.format( + self._ell[0], self._level, self._size) + return '{}-Regular partition tuples of level {} and size {}'.format( + self._ell, self._level, self._size) def __contains__(self, mu): r""" @@ -2825,6 +3041,17 @@ def __contains__(self, mu): TESTS:: + sage: RPT = PartitionTuples(level=3, size=7, regular=(2,1,4)) + sage: RPT + (2, 1, 4)-Regular partition tuples of level 3 and size 7 + sage: [[3,1],[],[3]] in RPT + True + sage: [[3],[1],[3]] in RPT + False + sage: [[3,2],[],[3]] in RPT + False + sage: [[3,3],[],[1]] in RPT + False sage: RPT = PartitionTuples(4,3,2) sage: [[], [], [2], [1]] in RPT True @@ -2836,11 +3063,9 @@ def __contains__(self, mu): sage: [4, 3, 2] in RPT False """ - if self._level == 1 and mu[0] in ZZ: - return mu in RegularPartitions_all(self._ell) and self._size == sum(mu) - return (RegularPartitionTuples.__contains__(self, mu) - and self._level == len(mu) - and self._size == sum(map(sum,mu))) + if mu not in RegularPartitionTuples_level(self._level, self._ell): + return False + return self._size == sum(map(sum, mu)) def __iter__(self): r""" @@ -2864,9 +3089,10 @@ def __iter__(self): ([], [], [3]), ([], [], [2, 1])] """ - p = [RegularPartitions_n(i, self._ell) for i in range(self._size+1)] for iv in IntegerVectors(self._size, self._level): - for cp in itertools.product(*[p[i] for i in iv]): + p = [RegularPartitions_n(v, ell) if ell > 0 else Partitions_n(v) + for v,ell in zip(iv,self._ell)] + for cp in itertools.product(*[p[i] for i in range(self._level)]): yield self._element_constructor_(cp) def _an_element_(self): @@ -2881,8 +3107,9 @@ def _an_element_(self): mu = [[] for l in range(self._level)] if self._size > 0: if self._level == 1: - mu = [self._size-1,1] + mu = [[self._size-1,1]] else: mu[0] = [1] mu[-1] = [self._size-1] return self.element_class(self, mu) + diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index bcefcbc24a8..67866f91508 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -6409,7 +6409,7 @@ def rank(self, p=None): return self.n - 1 if p in self: return Permutation(p).rank() - raise ValueError("x not in self") + raise ValueError("p not in self") def random_element(self): """ diff --git a/src/sage/combinat/posets/hasse_cython.pyx b/src/sage/combinat/posets/hasse_cython.pyx new file mode 100644 index 00000000000..4d4ba3abe3c --- /dev/null +++ b/src/sage/combinat/posets/hasse_cython.pyx @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +r""" +Some fast computations for finite posets +""" +# **************************************************************************** +# Copyright (C) 2020 Frédéric Chapoton +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** +from cysignals.signals cimport sig_check +from cysignals.memory cimport sig_malloc, sig_free + +from sage.libs.flint.fmpz cimport * +from sage.libs.flint.fmpz_mat cimport * + +from sage.matrix.matrix_integer_dense cimport Matrix_integer_dense + +from sage.rings.integer_ring import ZZ +from sage.matrix.matrix_space import MatrixSpace + + +cpdef Matrix_integer_dense moebius_matrix_fast(list positions): + """ + Compute the Möbius matrix of a poset by a specific triangular inversion. + + INPUT: + + a list of sets describing the poset, as given by the + lazy attribute ``_leq_storage`` of Hasse diagrams. + + OUTPUT: + + a dense matrix + + EXAMPLES:: + + sage: from sage.combinat.posets.hasse_cython import moebius_matrix_fast + sage: D = [{0,1},{1}] + sage: moebius_matrix_fast(D) + [ 1 -1] + [ 0 1] + sage: P = posets.TamariLattice(5) + sage: H = P._hasse_diagram + sage: D = H._leq_storage + sage: moebius_matrix_fast(D) + 42 x 42 dense matrix over Integer Ring (...) + """ + cdef Matrix_integer_dense A + cdef Py_ssize_t n = len(positions) + cdef Py_ssize_t i + cdef int j, k + A = Matrix_integer_dense.__new__(Matrix_integer_dense, + MatrixSpace(ZZ, n, n), None, None, None) + fmpz_mat_one(A._matrix) + cdef Py_ssize_t *pos_lens = sig_malloc(n*sizeof(Py_ssize_t)) + cdef int **pos_array = sig_malloc(n*sizeof(int*)) + cdef Py_ssize_t jind, kind + for i in range(n): + pos_lens[i] = len(positions[i]) + pos_array[i] = sig_malloc(pos_lens[i]*sizeof(int)) + for jind,j in enumerate(positions[i]): + pos_array[i][jind] = j + + for i in range(n - 1, -1, -1): + sig_check() + for jind in range(pos_lens[i]): + j = pos_array[i][jind] + if j != i: + for kind in range(pos_lens[j]): + k = pos_array[j][kind] + fmpz_sub(fmpz_mat_entry(A._matrix, i, k), + fmpz_mat_entry(A._matrix, i, k), + fmpz_mat_entry(A._matrix, j, k)) + for i in range(n): + sig_free(pos_array[i]) + sig_free(pos_array) + sig_free(pos_lens) + return A + + +cpdef Matrix_integer_dense coxeter_matrix_fast(list positions): + """ + Compute the Coxeter matrix of a poset by a specific algorithm. + + INPUT: + + a list of sets describing the poset, as given by the + lazy attribute ``_leq_storage`` of Hasse diagrams. + + OUTPUT: + + a dense matrix + + EXAMPLES:: + + sage: from sage.combinat.posets.hasse_cython import coxeter_matrix_fast + sage: D = [{0,1},{1}] + sage: coxeter_matrix_fast(D) + [ 0 -1] + [ 1 -1] + sage: P = posets.TamariLattice(5) + sage: H = P._hasse_diagram + sage: D = H._leq_storage + sage: coxeter_matrix_fast(D) + 42 x 42 dense matrix over Integer Ring (...) + """ + cdef Matrix_integer_dense A + cdef Py_ssize_t n = len(positions) + cdef Py_ssize_t i + cdef int j, k + A = Matrix_integer_dense.__new__(Matrix_integer_dense, + MatrixSpace(ZZ, n, n), None, None, None) + fmpz_mat_one(A._matrix) + cdef Py_ssize_t *pos_lens = sig_malloc(n*sizeof(Py_ssize_t)) + cdef int **pos_array = sig_malloc(n*sizeof(int*)) + cdef Py_ssize_t jind, kind + for i in range(n): + pos_lens[i] = len(positions[i]) + pos_array[i] = sig_malloc(pos_lens[i]*sizeof(int)) + for jind,j in enumerate(positions[i]): + pos_array[i][jind] = j + + for i in range(n - 1, -1, -1): + sig_check() + for jind in range(pos_lens[i]): + j = pos_array[i][jind] + if j != i: + for kind in range(pos_lens[j]): + k = pos_array[j][kind] + fmpz_sub(fmpz_mat_entry(A._matrix, k, i), + fmpz_mat_entry(A._matrix, k, i), + fmpz_mat_entry(A._matrix, k, j)) + for i in range(n): + sig_check() + for jind in range(pos_lens[i]): + j = pos_array[i][jind] + if j != i: + for k in range(n): + fmpz_add(fmpz_mat_entry(A._matrix, i, k), + fmpz_mat_entry(A._matrix, i, k), + fmpz_mat_entry(A._matrix, j, k)) + for i in range(n): + sig_free(pos_array[i]) + sig_free(pos_array) + sig_free(pos_lens) + fmpz_mat_scalar_mul_si(A._matrix, A._matrix, -1) + return A + diff --git a/src/sage/combinat/posets/hasse_diagram.py b/src/sage/combinat/posets/hasse_diagram.py index 184c0a1a7b3..7cdc90eccde 100644 --- a/src/sage/combinat/posets/hasse_diagram.py +++ b/src/sage/combinat/posets/hasse_diagram.py @@ -23,6 +23,8 @@ from sage.misc.cachefunc import cached_method from sage.functions.other import binomial from sage.misc.rest_index_of_methods import gen_rest_table_index +from sage.combinat.posets.hasse_cython import (moebius_matrix_fast, + coxeter_matrix_fast) class LatticeError(ValueError): @@ -943,31 +945,29 @@ def moebius_function(self, i, j): # dumb algorithm self._moebius_function_values[(i, j)] = -sum(self.moebius_function(i, k) for k in ci[:-1]) return self._moebius_function_values[(i, j)] - def moebius_function_matrix(self, algorithm='recursive'): + def moebius_function_matrix(self, algorithm='cython'): r""" Return the matrix of the Möbius function of this poset. - This returns the sparse matrix over `\ZZ` whose ``(x, y)`` entry + This returns the matrix over `\ZZ` whose ``(x, y)`` entry is the value of the Möbius function of ``self`` evaluated on - ``x`` and ``y``, and redefines :meth:`moebius_function` to use - it. + ``x`` and ``y``, and redefines :meth:`moebius_function` to use it. INPUT: - - ``algorithm`` -- optional, ``'recursive'`` (default) or ``'matrix'`` + - ``algorithm`` -- optional, ``'recursive'``, ``'matrix'`` + or ``'cython'`` (default) - This uses either the recursive formula or one matrix inversion. + This uses either the recursive formula, a generic matrix inversion + or a specific matrix inversion coded in Cython. - .. NOTE:: + OUTPUT: - The result is cached in :meth:`_moebius_function_matrix`. + a dense matrix for the algorithm ``cython``, a sparse matrix otherwise - .. TODO:: + .. NOTE:: - Try to make a specific multimodular matrix inversion - algorithm for this kind of sparse triangular matrices - where the non-zero entries of the inverse are in known - positions. + The result is cached in :meth:`_moebius_function_matrix`. .. SEEALSO:: :meth:`lequal_matrix`, :meth:`coxeter_transformation` @@ -996,12 +996,23 @@ def moebius_function_matrix(self, algorithm='recursive'): True sage: H = posets.TamariLattice(3)._hasse_diagram - sage: H.moebius_function_matrix('matrix') + sage: M = H.moebius_function_matrix('matrix'); M [ 1 -1 -1 0 1] [ 0 1 0 0 -1] [ 0 0 1 -1 0] [ 0 0 0 1 -1] [ 0 0 0 0 1] + sage: _ = H.__dict__.pop('_moebius_function_matrix') + sage: H.moebius_function_matrix('cython') == M + True + sage: _ = H.__dict__.pop('_moebius_function_matrix') + sage: H.moebius_function_matrix('recursive') == M + True + sage: _ = H.__dict__.pop('_moebius_function_matrix') + sage: H.moebius_function_matrix('banana') + Traceback (most recent call last): + ... + ValueError: unknown algorithm """ if not hasattr(self, '_moebius_function_matrix'): if algorithm == 'recursive': @@ -1019,8 +1030,12 @@ def moebius_function_matrix(self, algorithm='recursive'): for j in available if k in greater_than[j]) M = matrix(ZZ, n, n, m, sparse=True) - else: + elif algorithm == "matrix": M = self.lequal_matrix().inverse_of_unit() + elif algorithm == "cython": + M = moebius_matrix_fast(self._leq_storage) + else: + raise ValueError("unknown algorithm") self._moebius_function_matrix = M self._moebius_function_matrix.set_immutable() self.moebius_function = self._moebius_function_from_matrix @@ -1048,30 +1063,51 @@ def _moebius_function_from_matrix(self, i, j): return self._moebius_function_matrix[i, j] @cached_method - def coxeter_transformation(self): + def coxeter_transformation(self, algorithm='cython'): r""" Return the matrix of the Auslander-Reiten translation acting on the Grothendieck group of the derived category of modules on the poset, in the basis of simple modules. + INPUT: + + - ``algorithm`` -- optional, ``'cython'`` (default) or ``'matrix'`` + + This uses either a specific matrix code in Cython, or generic matrices. + .. SEEALSO:: :meth:`lequal_matrix`, :meth:`moebius_function_matrix` EXAMPLES:: - sage: M = posets.PentagonPoset()._hasse_diagram.coxeter_transformation(); M + sage: P = posets.PentagonPoset()._hasse_diagram + sage: M = P.coxeter_transformation(); M [ 0 0 0 0 -1] [ 0 0 0 1 -1] [ 0 1 0 0 -1] [-1 1 1 0 -1] [-1 1 0 1 -1] + sage: P.__dict__['coxeter_transformation'].clear_cache() + sage: P.coxeter_transformation(algorithm="matrix") == M + True TESTS:: - sage: M = posets.PentagonPoset()._hasse_diagram.coxeter_transformation() + sage: P = posets.PentagonPoset()._hasse_diagram + sage: M = P.coxeter_transformation() sage: M**8 == 1 True + sage: P.__dict__['coxeter_transformation'].clear_cache() + sage: P.coxeter_transformation(algorithm="banana") + Traceback (most recent call last): + ... + ValueError: unknown algorithm """ - return - self.lequal_matrix() * self.moebius_function_matrix().transpose() + if algorithm == 'matrix': + return - self.lequal_matrix() * self.moebius_function_matrix().transpose() + elif algorithm == 'cython': + return coxeter_matrix_fast(self._leq_storage) + else: + raise ValueError("unknown algorithm") def order_filter(self, elements): r""" @@ -1213,12 +1249,9 @@ def _leq_matrix(self): Integer Ring """ n = self.order() - one = ZZ.one() greater_than = self._leq_storage - D = {(i, j): one for i in range(n) for j in greater_than[i]} - M = matrix(ZZ, n, n, D, sparse=True) - M.set_immutable() - return M + D = {(i, j): 1 for i in range(n) for j in greater_than[i]} + return matrix(ZZ, n, n, D, sparse=True, immutable=True) def lequal_matrix(self, boolean=False): r""" diff --git a/src/sage/combinat/quickref.py b/src/sage/combinat/quickref.py index f37950b34c6..a62944138ad 100644 --- a/src/sage/combinat/quickref.py +++ b/src/sage/combinat/quickref.py @@ -7,7 +7,10 @@ sage: s = oeis([1,3,19,211]); s # optional - internet 0: A000275: Coefficients of a Bessel function (reciprocal of J_0(z)); also pairs of permutations with rise/rise forbidden. sage: s[0].programs() # optional - internet - 0: (PARI) {a(n) = if( n<0, 0, n!^2 * 4^n * polcoeff( 1 / besselj(0, x + x * O(x^(2*n))), 2*n))}; /* _Michael Somos_, May 17 2004 */ + [('maple', ...), + ('mathematica', ...), + ('pari', + 0: {a(n) = if( n<0, 0, n!^2 * 4^n * polcoeff( 1 / besselj(0, x + x * O(x^(2*n))), 2*n))}; /* _Michael Somos_, May 17 2004 */)] Combinatorial objects:: diff --git a/src/sage/combinat/root_system/__init__.py b/src/sage/combinat/root_system/__init__.py index fa932ab28ef..b04c503fad1 100644 --- a/src/sage/combinat/root_system/__init__.py +++ b/src/sage/combinat/root_system/__init__.py @@ -77,6 +77,7 @@ --------------------- - :ref:`sage.combinat.root_system.weyl_characters` +- :ref:`sage.combinat.root_system.fusion_ring` - :ref:`sage.combinat.root_system.integrable_representations` - :ref:`sage.combinat.root_system.branching_rules` - :ref:`sage.combinat.root_system.hecke_algebra_representation` diff --git a/src/sage/combinat/root_system/all.py b/src/sage/combinat/root_system/all.py index 8ed7e596a17..ccb40064690 100644 --- a/src/sage/combinat/root_system/all.py +++ b/src/sage/combinat/root_system/all.py @@ -19,8 +19,8 @@ 'ExtendedAffineWeylGroup') lazy_import('sage.combinat.root_system.coxeter_group', 'CoxeterGroup') lazy_import('sage.combinat.root_system.weyl_characters', ['WeylCharacterRing', - 'WeightRing', - 'FusionRing']) + 'WeightRing']) +lazy_import('sage.combinat.root_system.fusion_ring', ['FusionRing']) from .branching_rules import BranchingRule, branching_rule_from_plethysm, branching_rule lazy_import('sage.combinat.root_system.non_symmetric_macdonald_polynomials', 'NonSymmetricMacdonaldPolynomials') diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py new file mode 100644 index 00000000000..4f457e0d8e7 --- /dev/null +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -0,0 +1,933 @@ +""" +Fusion Rings +""" +# **************************************************************************** +# Copyright (C) 2019 Daniel Bump +# Nicolas Thiery +# Guillermo Aboumrad +# +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.combinat.root_system.weyl_characters import WeylCharacterRing +from sage.combinat.q_analogues import q_int +from sage.matrix.special import diagonal_matrix +from sage.matrix.constructor import matrix +from sage.misc.misc import inject_variable +from sage.rings.integer_ring import ZZ +from sage.rings.number_field.number_field import CyclotomicField +from sage.misc.cachefunc import cached_method + +class FusionRing(WeylCharacterRing): + r""" + Return the Fusion Ring (Verlinde Algebra) of level ``k``. + + INPUT: + + - ``ct`` -- the Cartan type of a simple (finite-dimensional) Lie algebra + - ``k`` -- a nonnegative integer + - ``conjugate`` -- (default ``False``) set ``True`` to obtain + the complex conjugate ring + - ``cyclotomic_order`` -- (default computed depending on ``ct`` and ``k``) + + The cyclotomic order is an integer `N` such that all computations + will return elements of the cyclotomic field of `N`-th roots of unity. + Normally you will never need to change this but consider changing it + if :meth:`root_of_unity` ever returns ``None``. + + This algebra has a basis (sometimes called *primary fields* but here + called *simple objects*) indexed by the weights of level `\leq k`. + These arise as the fusion algebras of Wess-Zumino-Witten (WZW) conformal + field theories, or as Grothendieck groups of tilting modules for quantum + groups at roots of unity. The :class:`FusionRing` class is implemented as + a variant of the :class:`WeylCharacterRing`. + + REFERENCES: + + - [BaKi2001]_ Chapter 3 + - [DFMS1996]_ Chapter 16 + - [EGNO2015]_ Chapter 8 + - [Feingold2004]_ + - [Fuchs1994]_ + - [Row2006]_ + - [Walton1990]_ + + EXAMPLES:: + + sage: A22 = FusionRing("A2",2) + sage: [f1, f2] = A22.fundamental_weights() + sage: M = [A22(x) for x in [0*f1, 2*f1, 2*f2, f1+f2, f2, f1]] + sage: [M[3] * x for x in M] + [A22(1,1), + A22(0,1), + A22(1,0), + A22(0,0) + A22(1,1), + A22(0,1) + A22(2,0), + A22(1,0) + A22(0,2)] + + You may assign your own labels to the basis elements. In the next + example, we create the `SO(5)` fusion ring of level `2`, check the + weights of the basis elements, then assign new labels to them while + injecting them into the global namespace:: + + sage: B22 = FusionRing("B2", 2) + sage: b = [B22(x) for x in B22.get_order()]; b + [B22(0,0), B22(1,0), B22(0,1), B22(2,0), B22(1,1), B22(0,2)] + sage: [x.weight() for x in b] + [(0, 0), (1, 0), (1/2, 1/2), (2, 0), (3/2, 1/2), (1, 1)] + sage: B22.fusion_labels(['I0','Y1','X','Z','Xp','Y2'], inject_variables=True) + sage: b = [B22(x) for x in B22.get_order()]; b + [I0, Y1, X, Z, Xp, Y2] + sage: [(x, x.weight()) for x in b] + [(I0, (0, 0)), + (Y1, (1, 0)), + (X, (1/2, 1/2)), + (Z, (2, 0)), + (Xp, (3/2, 1/2)), + (Y2, (1, 1))] + sage: X * Y1 + X + Xp + sage: Z * Z + I0 + + A fixed order of the basis keys is available with :meth:`get_order`. + This is the order used by methods such as :meth:`s_matrix`. You may + use :meth:`CombinatorialFreeModule.set_order` to reorder the basis:: + + sage: B22.set_order([x.weight() for x in [I0,Y1,Y2,X,Xp,Z]]) + sage: [B22(x) for x in B22.get_order()] + [I0, Y1, Y2, X, Xp, Z] + + To reset the labels, you may run :meth:`fusion_labels` with no parameter:: + + sage: B22.fusion_labels() + sage: [B22(x) for x in B22.get_order()] + [B22(0,0), B22(1,0), B22(0,2), B22(0,1), B22(1,1), B22(2,0)] + + To reset the order to the default, simply set it to the list of basis + element keys:: + + sage: B22.set_order(B22.basis().keys().list()) + sage: [B22(x) for x in B22.get_order()] + [B22(0,0), B22(1,0), B22(0,1), B22(2,0), B22(1,1), B22(0,2)] + + The fusion ring has a number of methods that reflect its role + as the Grothendieck ring of a *modular tensor category*. These + include a twist method :meth:`Element.twist` for its elements related + to the ribbon structure, and the S-matrix :meth:`s_ij`. + + There are two natural normalizations of the S-matrix. Both + are explained in Chapter 3 of [BaKi2001]_. The one that is computed + by the method :meth:`s_matrix`, or whose individual entries + are computed by :meth:`s_ij` is denoted `\tilde{s}` in + [BaKi2001]_. It is not unitary. + + The unitary S-matrix is `s=D^{-1/2}\tilde{s}` where + + .. MATH:: + + D = \sum_V d_i(V)^2. + + The sum is over all simple objects `V` with + `d_i(V)` the *quantum dimension*. We will call quantity `D` + the *global quantum dimension* and `\sqrt{D}` the + *total quantum order*. They are computed by :meth:`global_q_dimension` + and :meth:`total_q_order`. The unitary S-matrix `s` may be obtained + using :meth:`s_matrix` with the option ``unitary=True``. + + Let us check the Verlinde formula, which is [DFMS1996]_ (16.3). This + famous identity states that + + .. MATH:: + + N^k_{ij} = \sum_l \frac{s(i,\ell)\,s(j,\ell)\,\overline{s(k,\ell)}}{s(I,\ell)}, + + where `N^k_{ij}` are the fusion coefficients, i.e. the structure + constants of the fusion ring, and ``I`` is the unit object. + The S-matrix has the property that if `i*` denotes the dual + object of `i`, implemented in Sage as ``i.dual()``, then + + .. MATH:: + + s(i*,j) = s(i,j*) = \overline{s(i,j)}. + + This is equation (16.5) in [DFMS1996]_. Thus with `N_{ijk}=N^{k*}_{ij}` + the Verlinde formula is equivalent to + + .. MATH:: + + N_{ijk} = \sum_l \frac{s(i,\ell)\,s(j,\ell)\,s(k,\ell)}{s(I,\ell)}, + + In this formula `s` is the normalized unitary S-matrix + denoted `s` in [BaKi2001]_. We may define a function that + corresponds to the right-hand side, except using + `\tilde{s}` instead of `s`:: + + sage: def V(i,j,k): + ....: R = i.parent() + ....: return sum(R.s_ij(i,l) * R.s_ij(j,l) * R.s_ij(k,l) / R.s_ij(R.one(),l) + ....: for l in R.basis()) + + This does not produce ``self.N_ijk(i,j,k)`` exactly, because of the + missing normalization factor. The following code to check the + Verlinde formula takes this into account:: + + sage: def test_verlinde(R): + ....: b0 = R.one() + ....: c = R.global_q_dimension() + ....: return all(V(i,j,k) == c * R.N_ijk(i,j,k) for i in R.basis() + ....: for j in R.basis() for k in R.basis()) + + Every fusion ring should pass this test:: + + sage: test_verlinde(FusionRing("A2",1)) + True + sage: test_verlinde(FusionRing("B4",2)) # long time (.56s) + True + + As an exercise, the reader may verify the examples in + Section 5.3 of [RoStWa2009]_. Here we check the example + of the Ising modular tensor category, which is related + to the BPZ minimal model `M(4,3)` or to an `E_8` coset + model. See [DFMS1996]_ Sections 7.4.2 and 18.4.1. + [RoStWa2009]_ Example 5.3.4 tells us how to + construct it as the conjugate of the `E_8` level 2 + :class:`FusionRing`:: + + sage: I = FusionRing("E8",2,conjugate=True) + sage: I.fusion_labels(["i0","p","s"],inject_variables=True) + sage: b = I.basis().list(); b + [i0, p, s] + sage: [[x*y for x in b] for y in b] + [[i0, p, s], [p, i0, s], [s, s, i0 + p]] + sage: [x.twist() for x in b] + [0, 1, 1/8] + sage: I.global_q_dimension() + 4 + sage: I.total_q_order() + 2 + sage: [x.q_dimension()^2 for x in b] + [1, 1, 2] + sage: I.s_matrix() + [ 1 1 -zeta128^48 + zeta128^16] + [ 1 1 zeta128^48 - zeta128^16] + [-zeta128^48 + zeta128^16 zeta128^48 - zeta128^16 0] + sage: I.s_matrix().apply_map(lambda x:x^2) + [1 1 2] + [1 1 2] + [2 2 0] + + The term *modular tensor category* refers to the fact that associated + with the category there is a projective representation of the modular + group `SL(2,\ZZ)`. We recall that this group is generated by + + .. MATH:: + + S = \begin{pmatrix} & -1\\1\end{pmatrix},\qquad + T = \begin{pmatrix} 1 & 1\\ &1 \end{pmatrix} + + subject to the relations `(ST)^3 = S^2`, `S^2T = TS^2`, and `S^4 = I`. + Let `s` be the normalized S-matrix, and + `t` the diagonal matrix whose entries are the twists of the simple + objects. Let `s` the unitary S-matrix and `t` the matrix of twists, + and `C` the conjugation matrix :meth:`conj_matrix`. Let + + .. MATH:: + + D_+ = \sum_i d_i^2 \theta_i, \qquad D_- = d_i^2 \theta_i^{-1}, + + where `d_i` and `\theta_i` are the quantum dimensions and twists of the + simple objects. Let `c` be the Virasoro central charge, a rational number + that is computed in :meth:`virasoro_central_charge`. It is known that + + .. MATH:: + + \sqrt{\frac{D_+}{D_-}} = e^{i\pi c/4}. + + It is proved in [BaKi2001]_ Equation (3.1.17) that + + .. MATH:: + + (st)^3 = e^{i\pi c/4} s^2, \qquad + s^2 = C, \qquad C^2 = 1, \qquad Ct = tC. + + Therefore `S \mapsto s, T \mapsto t` is a projective representation + of `SL(2, \ZZ)`. Let us confirm these identities for the Fibonacci MTC + ``FusionRing("G2", 1)``:: + + sage: R = FusionRing("G2",1) + sage: S = R.s_matrix(unitary=True) + sage: T = R.twists_matrix() + sage: C = R.conj_matrix() + sage: c = R.virasoro_central_charge(); c + 14/5 + sage: (S*T)^3 == R.root_of_unity(c/4) * S^2 + True + sage: S^2 == C + True + sage: C*T == T*C + True + """ + @staticmethod + def __classcall__(cls, ct, k, base_ring=ZZ, prefix=None, style="coroots", conjugate=False, cyclotomic_order=None): + """ + Normalize input to ensure a unique representation. + + TESTS:: + + sage: F1 = FusionRing('B3', 2) + sage: F2 = FusionRing(CartanType('B3'), QQ(2), ZZ) + sage: F3 = FusionRing(CartanType('B3'), int(2), style="coroots") + sage: F1 is F2 and F2 is F3 + True + + sage: A23 = FusionRing('A2', 3) + sage: TestSuite(A23).run() + + sage: B22 = FusionRing('B2', 2) + sage: TestSuite(B22).run() + + sage: C31 = FusionRing('C3', 1) + sage: TestSuite(C31).run() + + sage: D41 = FusionRing('D4', 1) + sage: TestSuite(D41).run() + + sage: G22 = FusionRing('G2', 2) + sage: TestSuite(G22).run() + + sage: F41 = FusionRing('F4', 1) + sage: TestSuite(F41).run() + + sage: E61 = FusionRing('E6', 1) + sage: TestSuite(E61).run() + + sage: E71 = FusionRing('E7', 1) + sage: TestSuite(E71).run() + + sage: E81 = FusionRing('E8', 1) + sage: TestSuite(E81).run() + """ + return super(FusionRing, cls).__classcall__(cls, ct, base_ring=base_ring, + prefix=prefix, style=style, k=k, + conjugate=conjugate, + cyclotomic_order=cyclotomic_order) + + def _test_verlinde(self, **options): + """ + Check the Verlinde formula for this :class:`FusionRing` instance. + + EXAMPLES:: + + sage: G22 = FusionRing("G2",2) + sage: G22._test_verlinde() + """ + tester = self._tester(**options) + c = self.global_q_dimension() + i0 = self.one() + from sage.misc.misc import some_tuples + B = self.basis() + for x,y,z in some_tuples(B, 3, tester._max_runs): + v = sum(self.s_ij(x,w) * self.s_ij(y,w) * self.s_ij(z,w) / self.s_ij(i0,w) for w in B) + tester.assertEqual(v, c * self.N_ijk(x,y,z)) + + def _test_total_q_order(self, **options): + r""" + Check that the total quantum order is real and positive. + + The total quantum order is the positive square root + of the global quantum dimension. This indirectly test the + Virasoro central charge. + + EXAMPLES:: + + sage: G22 = FusionRing("G2",2) + sage: G22._test_total_q_order() + """ + tester = self._tester(**options) + tqo = self.total_q_order() + tester.assertTrue(tqo.is_real_positive()) + tester.assertTrue(tqo**2 == self.global_q_dimension()) + + def fusion_labels(self, labels=None, inject_variables=False): + r""" + Set the labels of the basis. + + INPUT: + + - ``labels`` -- (default: ``None``) a list of strings or string + - ``inject_variables`` -- (default: ``False``) if ``True``, then + inject the variable names into the global namespace; note that + this could override objects already defined + + If ``labels`` is a list, the length of the list must equal the + number of basis elements. These become the names of + the basis elements. + + If ``labels`` is a string, this is treated as a prefix and a + list of names is generated. + + If ``labels`` is ``None``, then this resets the labels to the default. + + EXAMPLES:: + + sage: A13 = FusionRing("A1", 3) + sage: A13.fusion_labels("x") + sage: fb = list(A13.basis()); fb + [x0, x1, x2, x3] + sage: Matrix([[x*y for y in A13.basis()] for x in A13.basis()]) + [ x0 x1 x2 x3] + [ x1 x0 + x2 x1 + x3 x2] + [ x2 x1 + x3 x0 + x2 x1] + [ x3 x2 x1 x0] + + We give an example where the variables are injected into the + global namepsace:: + + sage: A13.fusion_labels("y", inject_variables=True) + sage: y0 + y0 + sage: y0.parent() is A13 + True + + We reset the labels to the default:: + + sage: A13.fusion_labels() + sage: fb + [A13(0), A13(1), A13(2), A13(3)] + sage: y0 + A13(0) + """ + if labels is None: + # Remove the fusion labels + self._fusion_labels = None + return + + B = self.basis() + if isinstance(labels, str): + labels = [labels + str(k) for k in range(len(B))] + elif len(labels) != len(B): + raise ValueError('invalid data') + + d = {} + ac = self.simple_coroots() + for j, b in enumerate(self.get_order()): + t = tuple([b.inner_product(x) for x in ac]) + d[t] = labels[j] + if inject_variables: + inject_variable(labels[j], B[b]) + self._fusion_labels = d + + @cached_method + def field(self): + r""" + Return a cyclotomic field large enough to + contain the `2 \ell`-th roots of unity, as well as + all the S-matrix entries. + + EXAMPLES:: + + sage: FusionRing("A2",2).field() + Cyclotomic Field of order 60 and degree 16 + sage: FusionRing("B2",2).field() + Cyclotomic Field of order 40 and degree 16 + """ + return CyclotomicField(4 * self._cyclotomic_order) + + def root_of_unity(self, r): + r""" + Return `e^{i\pi r}` as an element of ``self.field()`` if possible. + + INPUT: + + - ``r`` -- a rational number + + EXAMPLES:: + + sage: A11 = FusionRing("A1",1) + sage: A11.field() + Cyclotomic Field of order 24 and degree 8 + sage: [A11.root_of_unity(2/x) for x in [1..7]] + [1, -1, zeta24^4 - 1, zeta24^6, None, zeta24^4, None] + """ + n = 2 * r * self._cyclotomic_order + if n in ZZ: + return self.field().gen() ** n + else: + return None + + def get_order(self): + r""" + Return the weights of the basis vectors in a fixed order. + + You may change the order of the basis using :meth:`CombinatorialFreeModule.set_order` + + EXAMPLES:: + + sage: A14 = FusionRing("A1",4) + sage: w = A14.get_order(); w + [(0, 0), (1/2, -1/2), (1, -1), (3/2, -3/2), (2, -2)] + sage: A14.set_order([w[k] for k in [0,4,1,3,2]]) + sage: [A14(x) for x in A14.get_order()] + [A14(0), A14(4), A14(1), A14(3), A14(2)] + + .. WARNING:: + + This duplicates :meth:`get_order` from + :class:`CombinatorialFreeModule` except the result + is *not* cached. Caching of + :meth:`CombinatorialFreeModule.get_order` causes inconsistent + results after calling :meth:`CombinatorialFreeModule.set_order`. + + """ + if self._order is None: + self.set_order(self.basis().keys().list()) + return self._order + + def some_elements(self): + """ + Return some elements of ``self``. + + EXAMPLES:: + + sage: D41 = FusionRing('D4', 1) + sage: D41.some_elements() + [D41(1,0,0,0), D41(0,0,1,0), D41(0,0,0,1)] + """ + return [self.monomial(x) for x in self.fundamental_weights() + if self.level(x) <= self._k] + + def fusion_level(self): + r""" + Return the level `k` of ``self``. + + EXAMPLES:: + + sage: B22 = FusionRing('B2',2) + sage: B22.fusion_level() + 2 + """ + return self._k + + def fusion_l(self): + r""" + Return the product `\ell = m_g(k + h^\vee)`, where `m_g` denotes the + square of the ratio of the lengths of long to short roots of + the underlying Lie algebra, `k` denotes the level of the FusionRing, + and `h^\vee` denotes the dual Coxeter number of the underlying Lie + algebra. + + This value is used to define the associated root `2\ell`-th + of unity `q = e^{i\pi/\ell}`. + + EXAMPLES:: + + sage: B22 = FusionRing('B2',2) + sage: B22.fusion_l() + 10 + sage: D52 = FusionRing('D5',2) + sage: D52.fusion_l() + 10 + """ + return self._l + + def virasoro_central_charge(self): + r""" + Return the Virasoro central charge of the WZW conformal + field theory associated with the Fusion Ring. + + If `\mathfrak{g}` is the corresponding semisimple Lie algebra, this is + + .. MATH:: + + \frac{k\dim\mathfrak{g}}{k+h^\vee}, + + where `k` is the level and `h^\vee` is the dual Coxeter number. + See [DFMS1996]_ Equation (15.61). + + Let `d_i` and `theta_i` be the quantum dimensions and + twists of the simple objects. By Proposition 2.3 in [RoStWa2009]_, + there exists a rational number `c` such that + `D_+ / \sqrt{D} = e^{i\pi c/4}`, where `D_+ = \sum d_i^2 \theta_i` + is computed in :meth:`D_plus` and `D = \sum d_i^2 > 0` is computed + by :meth:`global_q_dimension`. Squaring this identity and + remembering that `D_+ D_- = D` gives + + .. MATH:: + + D_+ / D_- = e^{i\pi c/2}. + + EXAMPLES:: + + sage: R = FusionRing("A1", 2) + sage: c = R.virasoro_central_charge(); c + 3/2 + sage: Dp = R.D_plus(); Dp + 2*zeta32^6 + sage: Dm = R.D_minus(); Dm + -2*zeta32^10 + sage: Dp / Dm == R.root_of_unity(c/2) + True + """ + dim_g = len(self.space().roots()) + self.cartan_type().rank() + return self._conj * self._k * dim_g / (self._k + self._h_check) + + def conj_matrix(self): + r""" + Return the conjugation matrix, which is the permutation matrix + for the conjugation (dual) operation on basis elements. + + EXAMPLES:: + + sage: FusionRing("A2",1).conj_matrix() + [1 0 0] + [0 0 1] + [0 1 0] + """ + b = self.basis().list() + return matrix(ZZ, [[i == j.dual() for i in b] for j in b]) + + def twists_matrix(self): + r""" + Return a diagonal matrix describing the twist corresponding to + each simple object in the ``FusionRing``. + + EXAMPLES:: + + sage: B21=FusionRing("B2",1) + sage: [x.twist() for x in B21.basis().list()] + [0, 1, 5/8] + sage: [B21.root_of_unity(x.twist()) for x in B21.basis().list()] + [1, -1, zeta32^10] + sage: B21.twists_matrix() + [ 1 0 0] + [ 0 -1 0] + [ 0 0 zeta32^10] + """ + B = self.basis() + return diagonal_matrix(self.root_of_unity(B[x].twist()) for x in self.get_order()) + + @cached_method + def N_ijk(self, elt_i, elt_j, elt_k): + r""" + Return the symmetric fusion coefficient `N_{ijk}`. + + INPUT: + + - ``elt_i``, ``elt_j``, ``elt_k`` -- elements of the fusion basis + + This is the same as `N_{ij}^{k\ast}`, where `N_{ij}^k` are + the structure coefficients of the ring (see :meth:`Nk_ij`), + and `k\ast`` denotes the dual element. The coefficient `N_{ijk}` + is unchanged under permutations of the three basis vectors. + + EXAMPLES:: + + sage: G23 = FusionRing("G2", 3) + sage: G23.fusion_labels("g") + sage: b = G23.basis().list(); b + [g0, g1, g2, g3, g4, g5] + sage: [(x,y,z) for x in b for y in b for z in b if G23.N_ijk(x,y,z) > 1] + [(g3, g3, g3), (g3, g3, g4), (g3, g4, g3), (g4, g3, g3)] + sage: all(G23.N_ijk(x,y,z)==G23.N_ijk(y,z,x) for x in b for y in b for z in b) + True + sage: all(G23.N_ijk(x,y,z)==G23.N_ijk(y,x,z) for x in b for y in b for z in b) + True + """ + return (elt_i * elt_j).monomial_coefficients().get(elt_k.dual().weight(), 0) + + @cached_method + def Nk_ij(self, elt_i, elt_j, elt_k): + r""" + Return the fusion coefficient `N^k_{ij}`. + + These are the structure coefficients of the fusion ring, so + + .. MATH:: + + i * j = \sum_{k} N_{ij}^k k. + + EXAMPLES:: + + sage: A22 = FusionRing("A2", 2) + sage: b = A22.basis().list() + sage: all(x*y == sum(A22.Nk_ij(x,y,k)*k for k in b) for x in b for y in b) + True + """ + return (elt_i * elt_j).monomial_coefficients(copy=False).get(elt_k.weight(), 0) + + @cached_method + def s_ij(self, elt_i, elt_j): + r""" + Return the element of the S-matrix of this fusion ring corresponding to + the given elements. + + This is computed using the formula + + .. MATH:: + + s_{i,j} = \frac{1}{\theta_i\theta_j} \sum_k N_{ik}^j d_k \theta_k, + + where `\theta_k` is the twist and `d_k` is the quantum + dimension. See [Row2006]_ Equation (2.2) or [EGNO2015]_ + Proposition 8.13.8. + + INPUT: + + - ``elt_i``, ``elt_j`` -- elements of the fusion basis + + EXAMPLES:: + + sage: G21 = FusionRing("G2", 1) + sage: b = G21.basis() + sage: [G21.s_ij(x, y) for x in b for y in b] + [1, -zeta60^14 + zeta60^6 + zeta60^4, -zeta60^14 + zeta60^6 + zeta60^4, -1] + """ + ijtwist = elt_i.twist() + elt_j.twist() + return sum(k.q_dimension() * self.Nk_ij(elt_i, k, elt_j) + * self.root_of_unity(k.twist() - ijtwist) + for k in self.basis()) + + def s_matrix(self, unitary=False): + r""" + Return the S-matrix of this fusion ring. + + OPTIONAL: + + - ``unitary`` -- (default: ``False``) set to ``True`` to obtain + the unitary S-matrix + + Without the ``unitary`` parameter, this is the matrix denoted + `\widetilde{s}` in [BaKi2001]_. + + EXAMPLES:: + + sage: D91 = FusionRing("D9", 1) + sage: D91.s_matrix() + [ 1 1 1 1] + [ 1 1 -1 -1] + [ 1 -1 -zeta136^34 zeta136^34] + [ 1 -1 zeta136^34 -zeta136^34] + sage: S = D91.s_matrix(unitary=True); S + [ 1/2 1/2 1/2 1/2] + [ 1/2 1/2 -1/2 -1/2] + [ 1/2 -1/2 -1/2*zeta136^34 1/2*zeta136^34] + [ 1/2 -1/2 1/2*zeta136^34 -1/2*zeta136^34] + sage: S*S.conjugate() + [1 0 0 0] + [0 1 0 0] + [0 0 1 0] + [0 0 0 1] + """ + b = self.basis() + S = matrix([[self.s_ij(b[x], b[y]) for x in self.get_order()] for y in self.get_order()]) + if unitary: + return S / self.total_q_order() + else: + return S + + def global_q_dimension(self): + r""" + Return `\sum d_i^2`, where the sum is over all simple objects + and `d_i` is the quantum dimension. It is a positive real number. + + EXAMPLES:: + + sage: FusionRing("E6",1).global_q_dimension() + 3 + """ + return sum(x.q_dimension()**2 for x in self.basis()) + + def total_q_order(self): + r""" + Return the positive square root of ``self.global_q_dimension()`` + as an element of ``self.field()``. + + EXAMPLES:: + + sage: F = FusionRing("G2",1) + sage: tqo=F.total_q_order(); tqo + zeta60^15 - zeta60^11 - zeta60^9 + 2*zeta60^3 + zeta60 + sage: tqo.is_real_positive() + True + sage: tqo^2 == F.global_q_dimension() + True + """ + c = self.virasoro_central_charge() + return self.D_plus() * self.root_of_unity(-c/4) + + def D_plus(self): + r""" + Return `\sum d_i^2\theta_i` where `i` runs through the simple objects, + `d_i` is the quantum dimension and `\theta_i` is the twist. + + This is denoted `p_+` in [BaKi2001]_ Chapter 3. + + EXAMPLES:: + + sage: B31 = FusionRing("B3",1) + sage: Dp = B31.D_plus(); Dp + 2*zeta48^13 - 2*zeta48^5 + sage: Dm = B31.D_minus(); Dm + -2*zeta48^3 + sage: Dp*Dm == B31.global_q_dimension() + True + sage: c = B31.virasoro_central_charge(); c + 7/2 + sage: Dp/Dm == B31.root_of_unity(c/2) + True + """ + return sum((x.q_dimension())**2 * self.root_of_unity(x.twist()) for x in self.basis()) + + def D_minus(self): + r""" + Return `\sum d_i^2\theta_i^{-1}` where `i` runs through the simple + objects, `d_i` is the quantum dimension and `\theta_i` is the twist. + + This is denoted `p_-` in [BaKi2001]_ Chapter 3. + + EXAMPLES:: + + sage: E83 = FusionRing("E8",3,conjugate=True) + sage: [Dp,Dm] = [E83.D_plus(), E83.D_minus()] + sage: Dp*Dm == E83.global_q_dimension() + True + sage: c = E83.virasoro_central_charge(); c + -248/11 + sage: Dp*Dm == E83.global_q_dimension() + True + """ + return sum((x.q_dimension())**2 * self.root_of_unity(-x.twist()) for x in self.basis()) + + class Element(WeylCharacterRing.Element): + """ + A class for FusionRing elements. + """ + def is_simple_object(self): + r""" + Determine whether ``self`` is a simple object of the fusion ring. + + EXAMPLES:: + + sage: A22 = FusionRing("A2", 2) + sage: x = A22(1,0); x + A22(1,0) + sage: x.is_simple_object() + True + sage: x^2 + A22(0,1) + A22(2,0) + sage: (x^2).is_simple_object() + False + """ + return self.parent()._k is not None and len(self._monomial_coefficients) == 1 + + def weight(self): + r""" + Return the parametrizing dominant weight in the level `k` alcove. + + This method is only available for basis elements. + + EXAMPLES:: + + sage: A21 = FusionRing("A2",1) + sage: [x.weight() for x in A21.basis().list()] + [(0, 0, 0), (2/3, -1/3, -1/3), (1/3, 1/3, -2/3)] + """ + if len(self._monomial_coefficients) != 1: + raise ValueError("fusion weight is valid for basis elements only") + return next(iter(self._monomial_coefficients)) + + def twist(self): + r""" + Return a rational number `h` such that `\theta = e^{i \pi h}` + is the twist of ``self``. + + This method is only available for simple objects. If + `\lambda` is the weight of the object, then + `h = \langle \lambda, \lambda+2\rho \rangle`, where + `\rho` is half the sum of the positive roots. + As in [Row2006]_, this requires normalizing + the invariant bilinear form so that + `\langle \alpha, \alpha \rangle = 2` for short roots. + + EXAMPLES:: + + sage: G21 = FusionRing("G2", 1) + sage: [x.twist() for x in G21.basis()] + [0, 4/5] + sage: [G21.root_of_unity(x.twist()) for x in G21.basis()] + [1, zeta60^14 - zeta60^4] + sage: zeta60 = G21.field().gen() + sage: zeta60^((4/5)*(60/2)) + zeta60^14 - zeta60^4 + + sage: F42 = FusionRing("F4", 2) + sage: [x.twist() for x in F42.basis()] + [0, 18/11, 2/11, 12/11, 4/11] + + sage: E62 = FusionRing("E6", 2) + sage: [x.twist() for x in E62.basis()] + [0, 26/21, 12/7, 8/21, 8/21, 26/21, 2/3, 4/7, 2/3] + """ + if not self.is_simple_object(): + raise ValueError("quantum twist is only available for simple objects of a FusionRing") + P = self.parent() + rho = P.space().rho() + # We copy self.weight() to skip the test (which was already done + # by self.is_simple_object()). + lam = next(iter(self._monomial_coefficients)) + inner = lam.inner_product(lam + 2*rho) + twist = P._conj * P._nf * inner / P.fusion_l() + # Reduce to canonical form + f = twist.floor() + twist -= f + return twist + (f % 2) + + @cached_method + def q_dimension(self): + r""" + Return the quantum dimension as an element of the cyclotomic + field of the `2\ell`-th roots of unity, where `l = m (k+h^\vee)` + with `m=1,2,3` depending on whether type is simply, doubly or + triply laced, `k` is the level and `h^\vee` is the dual + Coxeter number. + + EXAMPLES:: + + sage: B22 = FusionRing("B2",2) + sage: [(b.q_dimension())^2 for b in B22.basis()] + [1, 4, 5, 1, 5, 4] + """ + if not self.is_simple_object(): + raise ValueError("quantum dimension is only available for simple objects of a FusionRing") + P = self.parent() + lam = self.weight() + space = P.space() + rho = space.rho() + powers = {} + for alpha in space.positive_roots(): + val = alpha.inner_product(lam + rho) + if val in powers: + powers[val] += 1 + else: + powers[val] = 1 + val = alpha.inner_product(rho) + if val in powers: + powers[val] -= 1 + else: + powers[val] = -1 + R = ZZ['q'] + q = R.gen() + expr = R.fraction_field().one() + for val in powers: + exp = powers[val] + if exp > 0: + expr *= q_int(P._nf * val, q)**exp + elif exp < 0: + expr /= q_int(P._nf * val, q)**(-exp) + expr = R(expr) + expr = expr.substitute(q=q**4) / (q**(2*expr.degree())) + zet = P.field().gen() ** P._fg + return expr.substitute(q=zet) + diff --git a/src/sage/combinat/root_system/type_dual.py b/src/sage/combinat/root_system/type_dual.py index cdcc1601c9c..ae1d25776d9 100644 --- a/src/sage/combinat/root_system/type_dual.py +++ b/src/sage/combinat/root_system/type_dual.py @@ -596,14 +596,13 @@ def _repr_(self, compact=False): sage: CartanType(['F', 4, 1]).dual()._repr_(compact = True) 'F4~*' """ - dual_str = self.options.dual_str if self.options.notation == "Kac": if self._type.type() == 'B': if compact: return 'A%s^2'%(self.classical().rank()*2-1) return "['A', %s, 2]"%(self.classical().rank()*2-1) elif self._type.type() == 'BC': - dual_str = '+' # UNUSED ? + pass elif self._type.type() == 'C': if compact: return 'D%s^2'%(self.rank()) diff --git a/src/sage/combinat/root_system/weyl_characters.py b/src/sage/combinat/root_system/weyl_characters.py index 744fcda226e..a07b06f9e43 100644 --- a/src/sage/combinat/root_system/weyl_characters.py +++ b/src/sage/combinat/root_system/weyl_characters.py @@ -19,10 +19,8 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet from sage.misc.functional import is_even -from sage.misc.misc import inject_variable from sage.rings.all import ZZ - class WeylCharacterRing(CombinatorialFreeModule): r""" A class for rings of Weyl characters. @@ -92,7 +90,7 @@ class WeylCharacterRing(CombinatorialFreeModule): https://doc.sagemath.org/html/en/thematic_tutorials/lie.html """ @staticmethod - def __classcall__(cls, ct, base_ring=ZZ, prefix=None, style="lattice", k=None): + def __classcall__(cls, ct, base_ring=ZZ, prefix=None, style="lattice", k=None, conjugate=False, cyclotomic_order=None): """ TESTS:: @@ -108,9 +106,9 @@ def __classcall__(cls, ct, base_ring=ZZ, prefix=None, style="lattice", k=None): prefix = ct[0]+str(ct[1]) else: prefix = repr(ct) - return super(WeylCharacterRing, cls).__classcall__(cls, ct, base_ring=base_ring, prefix=prefix, style=style, k=k) + return super(WeylCharacterRing, cls).__classcall__(cls, ct, base_ring=base_ring, prefix=prefix, style=style, k=k, conjugate=conjugate, cyclotomic_order=cyclotomic_order) - def __init__(self, ct, base_ring=ZZ, prefix=None, style="lattice", k=None): + def __init__(self, ct, base_ring=ZZ, prefix=None, style="lattice", k=None, conjugate=False, cyclotomic_order=None): """ EXAMPLES:: @@ -146,7 +144,7 @@ def __init__(self, ct, base_ring=ZZ, prefix=None, style="lattice", k=None): def next_level(wt): return [wt + la for la in fw if self.level(wt + la) <= k] B = list(RecursivelyEnumeratedSet([self._space.zero()], next_level)) - B = [self._space.from_vector_notation(wt, style=style) for wt in B] + B = [self._space.from_vector_notation(wt, style="coroots") for wt in B] else: B = self._space @@ -162,6 +160,42 @@ def next_level(wt): # Register the partial inverse as a conversion self.register_conversion(self.retract) + # Record properties of the FusionRing + # mg = square of long to short root lengths + # nf = normalizing factor for the inner product + # fg = order of the fundamental group (except for Type B) + if k is not None: + if ct[0] in ['A', 'D', 'E']: + self._m_g = 1 + elif ct[0] in ['B', 'C', 'F']: + self._m_g = 2 + else: + self._m_g = 3 + if ct[0] in ['B','F']: + self._nf = 2 + else: + self._nf = 1 + self._h_check = ct.dual_coxeter_number() + self._l = self._m_g * (self._k + self._h_check) + if conjugate: + self._conj = -1 + else: + self._conj = 1 + if ct[0] == 'A': + self._fg = ct[1] + 1 + elif ct[0] == 'E' and ct[1] == 6: + self._fg = 3 + elif ct[0] == 'E' and ct[1] == 7: + self._fg = 2 + elif ct[0] == 'D': + self._fg = 2 + else: + self._fg = 1 + if cyclotomic_order is None: + self._cyclotomic_order = self._fg * self._l + else: + self._cyclotomic_order = cyclotomic_order + @cached_method def ambient(self): """ @@ -195,7 +229,7 @@ def lift_on_basis(self, irr): sage: A2.lift_on_basis(v) 2*a2(1,1,1) + a2(1,2,0) + a2(1,0,2) + a2(2,1,0) + a2(2,0,1) + a2(0,1,2) + a2(0,2,1) - This is consistent with the analogous calculation with symmetric + This is consistent with the analoguous calculation with symmetric Schur functions:: sage: s = SymmetricFunctions(QQ).s() @@ -1221,20 +1255,15 @@ def dual(self): for k in d) def highest_weight(self): - r""" - Return the parametrizing dominant weight of an irreducible - character or simple element of a FusionRing. - - This method is only available for basis elements. + """ + This method is only available for basis elements. Returns the + parametrizing dominant weight of an irreducible character. EXAMPLES:: sage: G2 = WeylCharacterRing("G2", style="coroots") sage: [x.highest_weight() for x in [G2(1,0),G2(0,1)]] [(1, 0, -1), (2, -1, -1)] - sage: A21 = FusionRing("A2",1) - sage: sorted([x.highest_weight() for x in A21.basis()]) - [(0, 0, 0), (1/3, 1/3, -2/3), (2/3, -1/3, -1/3)] """ if len(self.monomial_coefficients()) != 1: raise ValueError("fusion weight is valid for basis elements only") @@ -1598,7 +1627,6 @@ def multiplicity(self, other): raise ValueError("{} is not irreducible".format(other)) return self.coefficient(other.support()[0]) - def irreducible_character_freudenthal(hwv, debug=False): r""" Return the dictionary of multiplicities for the irreducible @@ -2189,161 +2217,3 @@ def demazure_lusztig(self, i, v): return self.demazure_lusztig(i.reduced_word(), v) except Exception: raise ValueError("unknown index {}".format(i)) - - -class FusionRing(WeylCharacterRing): - r""" - Return the Fusion Ring (Verlinde Algebra) of level ``k``. - - INPUT: - - - ``ct`` -- the Cartan type of a simple (finite-dimensional) Lie algebra - - ``k`` -- a nonnegative integer - - This algebra has a basis indexed by the weights of level `\leq k`. - It is implemented as a variant of the :class:`WeylCharacterRing`. - - EXAMPLES:: - - sage: A22 = FusionRing("A2",2) - sage: [f1, f2] = A22.fundamental_weights() - sage: M = [A22(x) for x in [0*f1, 2*f1, 2*f2, f1+f2, f2, f1]] - sage: [M[3] * x for x in M] - [A22(1,1), - A22(0,1), - A22(1,0), - A22(0,0) + A22(1,1), - A22(0,1) + A22(2,0), - A22(1,0) + A22(0,2)] - - You may assign your own labels to the basis elements. In the next - example, we create the `SO(5)` fusion ring of level `2`, check the - weights of the basis elements, then assign new labels to them:: - - sage: B22 = FusionRing("B2", 2) - sage: basis = sorted(B22.basis(), key=str) - sage: basis - [B22(0,0), B22(0,1), B22(0,2), B22(1,0), B22(1,1), B22(2,0)] - sage: [x.highest_weight() for x in basis] - [(0, 0), (1/2, 1/2), (1, 1), (1, 0), (3/2, 1/2), (2, 0)] - sage: B22.fusion_labels(['1','X','Y2','Y1','Xp','Z']) - sage: relabeled_basis = sorted(B22.basis(), key=str) - sage: relabeled_basis - [1, X, Xp, Y1, Y2, Z] - sage: [(x, x.highest_weight()) for x in relabeled_basis] - [(1, (0, 0)), - (X, (1/2, 1/2)), - (Xp, (3/2, 1/2)), - (Y1, (1, 0)), - (Y2, (1, 1)), - (Z, (2, 0))] - sage: X*Y1 - X + Xp - sage: Z*Z - 1 - - sage: C22 = FusionRing("C2", 2) - sage: sorted(C22.basis(), key=str) - [C22(0,0), C22(0,1), C22(0,2), C22(1,0), C22(1,1), C22(2,0)] - - REFERENCES: - - - [DFMS1996]_ Chapter 16 - - [Feingold2004]_ - - [Fuchs1994]_ - - [Walton1990]_ - """ - @staticmethod - def __classcall__(cls, ct, k, base_ring=ZZ, prefix=None, style="coroots"): - """ - Normalize input to ensure a unique representation. - - TESTS:: - - sage: F1 = FusionRing('B3', 2) - sage: F2 = FusionRing(CartanType('B3'), QQ(2), ZZ) - sage: F3 = FusionRing(CartanType('B3'), int(2), style="coroots") - sage: F1 is F2 and F2 is F3 - True - - sage: A23 = FusionRing('A2', 3) - sage: TestSuite(A23).run() - - sage: B22 = FusionRing('B2', 2) - sage: TestSuite(B22).run() - - sage: C31 = FusionRing('C3', 1) - sage: TestSuite(C31).run() - - sage: D41 = FusionRing('D4', 1) - sage: TestSuite(D41).run() - """ - return super(FusionRing, cls).__classcall__(cls, ct, base_ring=base_ring, - prefix=prefix, style=style, k=k) - - def some_elements(self): - """ - Return some elements of ``self``. - - EXAMPLES:: - - sage: D41 = FusionRing('D4', 1) - sage: D41.some_elements() - [D41(1,0,0,0), D41(0,0,1,0), D41(0,0,0,1)] - """ - return [self.monomial(x) for x in self.fundamental_weights() - if self.level(x) <= self._k] - - def fusion_labels(self, labels=None, key=str): - r""" - Set the labels of the basis. - - INPUT: - - - ``labels`` -- (default: ``None``) a list of strings - - ``key`` -- (default: ``str``) key to use to sort basis - - The length of the list ``labels`` must equal the - number of basis elements. These become the names of - the basis elements. If ``labels`` is ``None``, then - this resets the labels to the default. - - Note that the basis is stored as unsorted data, so to obtain - consistent results, it should be sorted when applying - labels. The argument ``key`` (default ``str``) specifies how - to sort the basis. If you call this with ``key=None``, then no - sorting is done. This may lead to random results, at least - with Python 3. - - EXAMPLES:: - - sage: A13 = FusionRing("A1", 3) - sage: A13.fusion_labels(['x0','x1','x2','x3']) - sage: fb = list(A13.basis()); fb - [x0, x1, x2, x3] - sage: Matrix([[x*y for y in A13.basis()] for x in A13.basis()]) - [ x0 x1 x2 x3] - [ x1 x0 + x2 x1 + x3 x2] - [ x2 x1 + x3 x0 + x2 x1] - [ x3 x2 x1 x0] - - We reset the labels to the default:: - - sage: A13.fusion_labels() - sage: fb - [A13(0), A13(1), A13(2), A13(3)] - """ - if labels is None: - self._fusion_labels = None - return - d = {} - if key: - fb = sorted(self.basis(), key=key) - else: - fb = list(self.basis()) - for j, b in enumerate(fb): - wt = b.highest_weight() - t = tuple([wt.inner_product(x) for x in self.simple_coroots()]) - d[t] = labels[j] - inject_variable(labels[j], b) - self._fusion_labels = d diff --git a/src/sage/combinat/species/series.py b/src/sage/combinat/species/series.py index 1163aebcda4..ead9daae563 100644 --- a/src/sage/combinat/species/series.py +++ b/src/sage/combinat/species/series.py @@ -37,7 +37,8 @@ from sage.rings.all import Integer from sage.misc.all import prod from functools import partial -from sage.misc.misc import repr_lincomb, is_iterator +from sage.misc.misc import is_iterator +from sage.misc.repr import repr_lincomb from sage.misc.cachefunc import cached_method from sage.algebras.algebra import Algebra diff --git a/src/sage/combinat/words/finite_word.py b/src/sage/combinat/words/finite_word.py index 9406087ff8e..91d00310f52 100644 --- a/src/sage/combinat/words/finite_word.py +++ b/src/sage/combinat/words/finite_word.py @@ -4747,8 +4747,8 @@ def is_quasiperiodic(self): return False for i in range(1, l - 1): return_lengths = [x.length() for x in self.return_words(self[:i])] - if return_lengths != []: - if (max(return_lengths) <= i and self[l-i:l] == self[:i]): + if return_lengths: + if max(return_lengths) <= i and self[l-i:l] == self[:i]: return True return False diff --git a/src/sage/combinat/words/suffix_trees.py b/src/sage/combinat/words/suffix_trees.py index daaa592cb73..9a61e4e4b36 100644 --- a/src/sage/combinat/words/suffix_trees.py +++ b/src/sage/combinat/words/suffix_trees.py @@ -833,8 +833,8 @@ def to_digraph(self, word_labels=False): sage: t.to_digraph() Digraph on 8 vertices """ - if self._letters == []: - d = {0:{}} + if not self._letters: + d = {0: {}} return DiGraph(d) d = self.transition_function_dictionary() for u in d: diff --git a/src/sage/databases/oeis.py b/src/sage/databases/oeis.py index 370a444652f..e24ab411252 100644 --- a/src/sage/databases/oeis.py +++ b/src/sage/databases/oeis.py @@ -475,25 +475,23 @@ def find_by_description(self, description, max_results=3, first_result=0): EXAMPLES:: sage: oeis.find_by_description('prime gap factorization') # optional -- internet - 0: A073491: Numbers having no prime gaps in their factorization. - 1: A073485: Product of any number of consecutive primes; squarefree numbers with no gaps in their prime factorization. - 2: A073490: Number of prime gaps in factorization of n. + 0: A...: ... + 1: A...: ... + 2: A...: ... sage: prime_gaps = _[2] ; prime_gaps # optional -- internet A073490: Number of prime gaps in factorization of n. - :: - sage: oeis('beaver') # optional -- internet - 0: A028444: Busy Beaver sequence, or Rado's sigma function: ... - 1: A060843: Busy Beaver problem: a(n) = maximal number of steps ... - 2: A131956: Busy Beaver variation: maximum number of steps for ... + 0: A...: ...eaver... + 1: A...: ...eaver... + 2: A...: ...eaver... sage: oeis('beaver', max_results=4, first_result=2) # optional -- internet - 0: A131956: Busy Beaver variation: maximum number of steps for ... - 1: A141475: Number of Turing machines with n states following ... - 2: A131957: Busy Beaver sigma variation: maximum number of 1's ... - 3: A...: ... + 0: A...: ...eaver... + 1: A...: ...eaver... + 2: A...: ...eaver... + 3: A...: ...eaver... """ options = {'q': description, 'n': str(max_results), diff --git a/src/sage/databases/stein_watkins.py b/src/sage/databases/stein_watkins.py index 45a45c8472d..00fe7181c82 100644 --- a/src/sage/databases/stein_watkins.py +++ b/src/sage/databases/stein_watkins.py @@ -297,11 +297,11 @@ def iter_levels(self): try: E = next(it) except StopIteration: - if C != []: + if C: yield C return if E.conductor != N: - if C != []: + if C: yield C C = [E] N = E.conductor diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index a90b45981bb..cd5ab598458 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -94,7 +94,6 @@ def __init__(self, **kwds): self.memlimit = 0 self.all = False self.logfile = None - self.sagenb = False self.long = False self.warn_long = None self.randorder = None @@ -326,7 +325,7 @@ def __init__(self, options, args): options.timeout *= 2 if options.nthreads == 0: options.nthreads = int(os.getenv('SAGE_NUM_THREADS_PARALLEL',1)) - if options.failed and not (args or options.new or options.sagenb): + if options.failed and not (args or options.new): # If the user doesn't specify any files then we rerun all failed files. options.all = True if options.global_iterations == 0: @@ -656,7 +655,7 @@ def create_run_id(self): def add_files(self): r""" - Checks for the flags '--all', '--new' and '--sagenb'. + Checks for the flags '--all' and '--new'. For each one present, this function adds the appropriate directories and files to the todo list. @@ -679,15 +678,6 @@ def add_files(self): sage: DC = DocTestController(DD, []) sage: DC.add_files() Doctesting ... - - :: - - sage: DD = DocTestDefaults(sagenb = True) - sage: DC = DocTestController(DD, []) - sage: DC.add_files() # py2 # optional - sagenb - Doctesting the Sage notebook. - sage: DC.files[0][-6:] # py2 # optional - sagenb - 'sagenb' """ opj = os.path.join from sage.env import SAGE_SRC, SAGE_DOC_SRC, SAGE_ROOT, SAGE_ROOT_GIT @@ -706,7 +696,6 @@ def all_files(): if have_git: self.files.append(opj(SAGE_SRC, 'sage_setup')) self.files.append(SAGE_DOC_SRC) - self.options.sagenb = True if self.options.all or (self.options.new and not have_git): self.log("Doctesting entire Sage library.") @@ -732,18 +721,6 @@ def all_files(): filename.endswith(".pyx") or filename.endswith(".rst"))): self.files.append(os.path.relpath(opj(SAGE_ROOT,filename))) - if self.options.sagenb: - if not PythonModule('sagenb').is_present(): - if not self.options.all: - self.log("Skipping doctesting of the Sage notebook: " - "not installed on Python 3") - return - - if not self.options.all: - self.log("Doctesting the Sage notebook.") - from pkg_resources import Requirement, working_set - sagenb_loc = working_set.find(Requirement.parse('sagenb')).location - self.files.append(opj(sagenb_loc, 'sagenb')) def expand_files_into_sources(self): r""" @@ -1025,10 +1002,9 @@ def _assemble_cmd(self): """ cmd = "sage-runtests --serial " opt = dict_difference(self.options.__dict__, DocTestDefaults().__dict__) - for o in ("all", "sagenb"): - if o in opt: - raise ValueError("You cannot run gdb/valgrind on the whole sage%s library"%("" if o == "all" else "nb")) - for o in ("all", "sagenb", "long", "force_lib", "verbose", "failed", "new"): + if "all" in opt: + raise ValueError("You cannot run gdb/valgrind on the whole sage library") + for o in ("all", "long", "force_lib", "verbose", "failed", "new"): if o in opt: cmd += "--%s "%o for o in ("timeout", "memlimit", "randorder", "stats_path"): diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 861b77ba9b3..75559667d15 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -2532,12 +2532,9 @@ def _run(self, runner, options, results): """ Actually run the doctests with the right set of globals """ - if self.source.basename.startswith("sagenb."): - import sage.all_notebook as sage_all - else: - # Import Jupyter globals to doctest the Jupyter - # implementation of widgets and interacts - import sage.repl.ipython_kernel.all_jupyter as sage_all + # Import Jupyter globals to doctest the Jupyter + # implementation of widgets and interacts + import sage.repl.ipython_kernel.all_jupyter as sage_all dict_all = sage_all.__dict__ # Remove '__package__' item from the globals since it is not # always in the globals in an actual Sage session. diff --git a/src/sage/dynamics/arithmetic_dynamics/endPN_automorphism_group.py b/src/sage/dynamics/arithmetic_dynamics/endPN_automorphism_group.py index 5f855cd915f..4584510aeb4 100644 --- a/src/sage/dynamics/arithmetic_dynamics/endPN_automorphism_group.py +++ b/src/sage/dynamics/arithmetic_dynamics/endPN_automorphism_group.py @@ -523,21 +523,20 @@ def valid_automorphisms(automorphisms_CRT, rational_function, ht_bound, M, # multiply lift by appropriate scalar matrices and adjust (mod M) # to find an element of minimal height. These will have # coefficients in [-M/2, M/2) - for scalar in range(1, M): - if gcd(scalar, M) == 1: - new_lift = [scalar*x - (scalar*x/M).round()*M - for x in init_lift] - g = gcd(new_lift) - new_lift = [x // g for x in new_lift] - if all(abs(x) <= ht_bound for x in new_lift): - a, b, c, d = new_lift - f = (a*z + b) / (c*z + d) - if rational_function(f(z)) == f(rational_function(z)): - if return_functions: - valid_auto.append(f) - else: - valid_auto.append(matrix(ZZ,2,2,new_lift)) - break + for scalar in M.coprime_integers(M): + new_lift = [scalar*x - (scalar*x/M).round()*M + for x in init_lift] + g = gcd(new_lift) + new_lift = [x // g for x in new_lift] + if all(abs(x) <= ht_bound for x in new_lift): + a, b, c, d = new_lift + f = (a*z + b) / (c*z + d) + if rational_function(f(z)) == f(rational_function(z)): + if return_functions: + valid_auto.append(f) + else: + valid_auto.append(matrix(ZZ,2,2,new_lift)) + break return valid_auto @@ -1390,14 +1389,18 @@ def order_p_automorphisms(rational_function, pre_image): u = F(1) / (z - pt[0]) u_inv = pt[0] + F(1)/z for i in range(1,m): - if M[0] == [F(1),F(0)]: uy1 = 0 - else: uy1 = u(M[0][0]) - if M[i] == [F(1),F(0)]: uy2 = 0 - else: uy2 = u(M[i][0]) + if M[0] == [F(1),F(0)]: + uy1 = 0 + else: + uy1 = u(M[0][0]) + if M[i] == [F(1),F(0)]: + uy2 = 0 + else: + uy2 = u(M[i][0]) s = u_inv( u(z) + uy2 - uy1 ) if s(phi(z)) == phi(s(z)): automorphisms_p.append(s) - elif T==[]: + elif not T: # create the extension field generated by pre-images of the unique fixed point T_poly = pre_image[0][2] e = lcm([x[0].degree() for x in T_poly.factor()])*F.degree() diff --git a/src/sage/ext/fast_callable.pyx b/src/sage/ext/fast_callable.pyx index 86033d983be..580f115692b 100644 --- a/src/sage/ext/fast_callable.pyx +++ b/src/sage/ext/fast_callable.pyx @@ -422,39 +422,6 @@ def fast_callable(x, domain=None, vars=None, Traceback (most recent call last): ... TypeError: unable to simplify to float approximation - - Check :trac:`24805`--if a fast_callable expression involves division - on a Python object, it will always prefer Python 3 semantics (e.g. - ``x / y`` will try ``x.__truediv__`` instead of ``x.__div__``, as if - ``from __future__ import division`` is in effect). However, for - classes that implement ``__div__`` but not ``__truediv__`` it will still - fall back on ``__div__`` for backwards-compatibility, but reliance on - this functionality is deprecated:: - - sage: from sage.ext.fast_callable import ExpressionTreeBuilder - sage: etb = ExpressionTreeBuilder('x') - sage: x = etb.var('x') - sage: class One(object): - ....: def __div__(self, other): - ....: if not isinstance(other, Integer): - ....: return NotImplemented - ....: return 1 / other - sage: expr = One() / x - sage: f = fast_callable(expr, vars=[x]) - sage: f(2) # py2 - doctest:warning...: - DeprecationWarning: use of __truediv__ should be preferred over __div__ - See https://trac.sagemath.org/24805 for details. - 1/2 - sage: class ModernOne(One): - ....: def __truediv__(self, other): - ....: if not isinstance(other, Integer): - ....: return NotImplemented - ....: return 1 / other - sage: expr = ModernOne() / x - sage: f = fast_callable(expr, vars=[x]) - sage: f(2) - 1/2 """ cdef Expression et if isinstance(x, Expression): @@ -960,28 +927,6 @@ cdef class Expression: """ return _expression_binop_helper(s, o, op_truediv) - def __div__(s, o): - r""" - Compute a quotient of two Expressions. - - EXAMPLES:: - - sage: from sage.ext.fast_callable import ExpressionTreeBuilder - sage: etb = ExpressionTreeBuilder(vars=(x,)) - sage: x = etb(x) - sage: x/x - div(v_0, v_0) - sage: x/1 - div(v_0, 1) - sage: 1/x - div(1, v_0) - sage: x.__div__(1) # py2 - div(v_0, 1) - sage: x.__rdiv__(1) # py2 - div(1, v_0) - """ - return _expression_binop_helper(s, o, op_div) - def __floordiv__(s, o): r""" Compute the floordiv (the floor of the quotient) of two Expressions. diff --git a/src/sage/ext/fast_eval.pyx b/src/sage/ext/fast_eval.pyx index 1d2c1bf4470..d4e736b83ec 100644 --- a/src/sage/ext/fast_eval.pyx +++ b/src/sage/ext/fast_eval.pyx @@ -797,17 +797,6 @@ cdef class FastDoubleFunc: """ return binop(left, right, DIV) - def __div__(left, right): - """ - EXAMPLES:: - - sage: from sage.ext.fast_eval import fast_float_arg - sage: f = fast_float_arg(0) / 7 - sage: f(14) - 2.0 - """ - return binop(left, right, DIV) - def __pow__(FastDoubleFunc left, right, dummy): """ EXAMPLES:: diff --git a/src/sage/ext_data/images/corner.png b/src/sage/ext_data/images/corner.png deleted file mode 100644 index f4b96d28f6f..00000000000 Binary files a/src/sage/ext_data/images/corner.png and /dev/null differ diff --git a/src/sage/ext_data/images/evaluate.png b/src/sage/ext_data/images/evaluate.png deleted file mode 100644 index a3436fa983e..00000000000 Binary files a/src/sage/ext_data/images/evaluate.png and /dev/null differ diff --git a/src/sage/ext_data/images/evaluate_over.png b/src/sage/ext_data/images/evaluate_over.png deleted file mode 100644 index 46fc67204d7..00000000000 Binary files a/src/sage/ext_data/images/evaluate_over.png and /dev/null differ diff --git a/src/sage/ext_data/images/favicon.ico b/src/sage/ext_data/images/favicon.ico deleted file mode 100644 index 3cefb468d16..00000000000 Binary files a/src/sage/ext_data/images/favicon.ico and /dev/null differ diff --git a/src/sage/ext_data/images/sagelogo.png b/src/sage/ext_data/images/sagelogo.png deleted file mode 100644 index 5b9fad9a657..00000000000 Binary files a/src/sage/ext_data/images/sagelogo.png and /dev/null differ diff --git a/src/sage/ext_data/threejs/threejs_template.html b/src/sage/ext_data/threejs/threejs_template.html index 68595d3b9a8..5f48fc8b981 100644 --- a/src/sage/ext_data/threejs/threejs_template.html +++ b/src/sage/ext_data/threejs/threejs_template.html @@ -146,7 +146,23 @@ var camera = createCamera(); camera.up.set( 0, 0, 1 ); - camera.position.set( a[0]*(xMid+xRange), a[1]*(yMid+yRange), a[2]*(zMid+zRange) ); + camera.position.set( a[0]*xMid, a[1]*yMid, a[2]*zMid ); + + var offset = new THREE.Vector3( a[0]*xRange, a[1]*yRange, a[2]*zRange ); + + if ( options.viewpoint ) { + + var aa = options.viewpoint; + var axis = new THREE.Vector3( aa[0][0], aa[0][1], aa[0][2] ).normalize(); + var angle = aa[1] * Math.PI / 180; + var q = new THREE.Quaternion().setFromAxisAngle( axis, angle ).inverse(); + + offset.set( 0, 0, offset.length() ); + offset.applyQuaternion( q ); + + } + + camera.position.add( offset ); function createCamera() { diff --git a/src/sage/functions/all.py b/src/sage/functions/all.py index b84db9e7a57..0c449236e67 100644 --- a/src/sage/functions/all.py +++ b/src/sage/functions/all.py @@ -20,13 +20,13 @@ reciprocal_trig_functions = {'sec': cos, 'csc': sin, 'cot': tan, 'sech': cosh, 'csch': sinh, 'coth': tanh} -from .other import ( ceil, floor, abs_symbolic, sqrt, +from .other import ( ceil, floor, abs_symbolic, sqrt, real_nth_root, arg, real_part, real, frac, factorial, binomial, imag_part, imag, imaginary, conjugate, cases, complex_root_of) -from .log import (exp, exp_polar, log, ln, polylog, dilog, lambert_w, harmonic_number) +from .log import (exp, exp_polar, log, ln, polylog, dilog, lambert_w, harmonic_number) from .transcendental import (zeta, zetaderiv, zeta_symmetric, hurwitz_zeta, dickman_rho, stieltjes) diff --git a/src/sage/functions/log.py b/src/sage/functions/log.py index e54c7f1b1b3..d7a4c921eda 100644 --- a/src/sage/functions/log.py +++ b/src/sage/functions/log.py @@ -153,7 +153,7 @@ def __init__(self): e^x """ GinacFunction.__init__(self, "exp", latex_name=r"\exp", - conversions=dict(maxima='exp', fricas='exp')) + conversions=dict(maxima='exp', fricas='exp')) exp = Function_exp() @@ -263,8 +263,8 @@ def __init__(self): log """ GinacFunction.__init__(self, 'log', ginac_name='logb', nargs=2, - latex_name=r'\log', - conversions=dict(maxima='log')) + latex_name=r'\log', + conversions=dict(maxima='log')) logb = Function_log2() @@ -409,7 +409,7 @@ def log(*args, **kwds): -I*log(3)/pi sage: log(int(8),2) 3 - sage: log(8,int(2)) # known bug, see #21518 + sage: log(8,int(2)) 3 sage: log(8,2) 3 @@ -417,7 +417,7 @@ def log(*args, **kwds): -3 sage: log(1/8,1/2) 3 - sage: log(8,1/2) # known bug, see #21517 + sage: log(8,1/2) -3 sage: log(1000, 10, base=5) diff --git a/src/sage/functions/other.py b/src/sage/functions/other.py index e39e424c31f..93dc8bd3f43 100644 --- a/src/sage/functions/other.py +++ b/src/sage/functions/other.py @@ -33,6 +33,7 @@ from sage.structure.all import parent as s_parent from sage.functions.trig import arctan2 + from sage.arith.all import binomial as arith_binomial one_half = SR.one() / SR(2) @@ -617,6 +618,7 @@ def _eval_(self, x): floor = Function_floor() + class Function_Order(GinacFunction): def __init__(self): r""" @@ -677,7 +679,6 @@ def _sympy_(self, arg): import sympy return sympy.O(*sympy.sympify(arg, evaluate=False)) - Order = Function_Order() @@ -751,6 +752,7 @@ def _eval_(self, x): frac = Function_frac() + def _do_sqrt(x, prec=None, extend=True, all=False): r""" Used internally to compute the square root of x. @@ -909,6 +911,162 @@ def sqrt(x, *args, **kwds): {'__call__': staticmethod(sqrt), '__setstate__': lambda x, y: None}) + +class Function_real_nth_root(BuiltinFunction): + r""" + Real `n`-th root function `x^\frac{1}{n}`. + + The function assumes positive integer `n` and real number `x`. + + EXAMPLES:: + + sage: v = real_nth_root(2, 3) + sage: v + real_nth_root(2, 3) + sage: v^3 + 2 + sage: v = real_nth_root(-2, 3) + sage: v + real_nth_root(-2, 3) + sage: v^3 + -2 + sage: real_nth_root(8, 3) + 2 + sage: real_nth_root(-8, 3) + -2 + + For numeric input, it gives a numerical approximation. :: + + sage: real_nth_root(2., 3) + 1.25992104989487 + sage: real_nth_root(-2., 3) + -1.25992104989487 + + Some symbolic calculus:: + + sage: f = real_nth_root(x, 5)^3 + sage: f + real_nth_root(x^3, 5) + sage: f.diff() + 3/5*x^2*real_nth_root(x^(-12), 5) + sage: f.integrate(x) + integrate((abs(x)^3)^(1/5)*sgn(x^3), x) + sage: _.diff() + (abs(x)^3)^(1/5)*sgn(x^3) + + """ + def __init__(self): + r""" + Initialize. + + TESTS:: + + sage: cube_root = real_nth_root(x, 3) + sage: loads(dumps(cube_root)) + real_nth_root(x, 3) + + :: + + sage: f = real_nth_root(x, 3) + sage: f._sympy_() + Piecewise((Abs(x)**(1/3)*sign(x), Eq(im(x), 0)), (x**(1/3), True)) + + """ + BuiltinFunction.__init__(self, "real_nth_root", nargs=2, + conversions=dict(sympy='real_root', + mathematica='Surd', + maple='surd')) + + def _print_latex_(self, base, exp): + r""" + TESTS:: + + sage: latex(real_nth_root(x, 3)) + x^{\frac{1}{3}} + sage: latex(real_nth_root(x^2 + x, 3)) + {\left(x^{2} + x\right)}^{\frac{1}{3}} + """ + return latex(base**(1/exp)) + + def _evalf_(self, base, exp, parent=None): + """ + TESTS:: + + sage: real_nth_root(RIF(2), 3) + 1.259921049894873? + sage: real_nth_root(RBF(2), 3) + [1.259921049894873 +/- 3.92e-16] + sage: real_nth_root(-2, 4) + Traceback (most recent call last): + ... + ValueError: no real nth root of negative real number with even n + """ + negative = base < 0 + + if negative: + if exp % 2 == 0: + raise ValueError('no real nth root of negative real number with even n') + base = -base + + r = base**(1/exp) + + if negative: + return -r + else: + return r + + def _eval_(self, base, exp): + """ + TESTS:: + + sage: real_nth_root(x, 1) + x + sage: real_nth_root(x, 3) + real_nth_root(x, 3) + """ + if not isinstance(base, Expression) and not isinstance(exp, Expression): + if isinstance(base, Integer): + try: + return base.nth_root(exp) + except ValueError: + pass + self._evalf_(base, exp, parent=s_parent(base)) + + if isinstance(exp, Integer) and exp.is_one(): + return base + + def _power_(self, base, exp, power_param=None): + """ + TESTS:: + + sage: f = real_nth_root(x, 3) + sage: f^5 + real_nth_root(x^5, 3) + """ + return self(base**power_param, exp) + + def _derivative_(self, base, exp, diff_param=None): + """ + TESTS:: + + sage: f = real_nth_root(x, 3) + sage: f.diff() + 1/3*real_nth_root(x^(-2), 3) + sage: f = real_nth_root(-x, 3) + sage: f.diff() + -1/3*real_nth_root(x^(-2), 3) + sage: f = real_nth_root(x, 4) + sage: f.diff() + 1/4*real_nth_root(x^(-3), 4) + sage: f = real_nth_root(-x, 4) + sage: f.diff() + -1/4*real_nth_root(-1/x^3, 4) + """ + return 1/exp * self(base, exp)**(1-exp) + +real_nth_root = Function_real_nth_root() + + class Function_arg(BuiltinFunction): def __init__(self): r""" @@ -1152,6 +1310,7 @@ def __call__(self, x, **kwargs): real = real_part = Function_real_part() + class Function_imag_part(GinacFunction): def __init__(self): r""" @@ -1298,6 +1457,7 @@ def __init__(self): conjugate = Function_conjugate() + class Function_factorial(GinacFunction): def __init__(self): r""" @@ -1488,6 +1648,7 @@ def _eval_(self, x): factorial = Function_factorial() + class Function_binomial(GinacFunction): def __init__(self): r""" diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index d84e23ca706..242929ce9fd 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -113,6 +113,31 @@ class Polyhedron_base(Element): sage: p = Polyhedron() sage: TestSuite(p).run() + + :: + + sage: p = Polyhedron(vertices=[(1,0), (0,1)], rays=[(1,1)], base_ring=ZZ) + sage: TestSuite(p).run() + + :: + + sage: p=polytopes.flow_polytope(digraphs.DeBruijn(3,2)) + sage: TestSuite(p).run() + + :: + + sage: TestSuite(Polyhedron([[]])).run() + sage: TestSuite(Polyhedron([[0]])).run() + + :: + + sage: P = polytopes.permutahedron(3) * Polyhedron(rays=[[0,0,1],[0,1,1],[1,2,3]]) + sage: TestSuite(P).run() + + :: + + sage: P = polytopes.permutahedron(3)*Polyhedron(rays=[[0,0,1],[0,1,1]], lines=[[1,0,0]]) + sage: TestSuite(P).run() """ def __init__(self, parent, Vrep, Hrep, Vrep_minimal=None, Hrep_minimal=None, pref_rep=None, **kwds): @@ -2053,30 +2078,6 @@ def an_affine_basis(self): Traceback (most recent call last): ... NotImplementedError: this function is not implemented for unbounded polyhedra - - TESTS: - - Checking for various inputs, that this actually works:: - - sage: def test_affine_basis(P): - ....: b = P.an_affine_basis() - ....: m = Matrix(b).transpose().stack(Matrix([[1]*len(b)])) - ....: assert m.rank() == P.dim() + 1 - ....: - sage: test_affine_basis(polytopes.permutahedron(5)) - sage: test_affine_basis(polytopes.Birkhoff_polytope(4)) - sage: test_affine_basis(polytopes.hypercube(6)) - sage: test_affine_basis(polytopes.dodecahedron()) - sage: test_affine_basis(polytopes.cross_polytope(5)) - - Small-dimensional cases: - - sage: Polyhedron([[1]]).an_affine_basis() - [A vertex at (1)] - sage: Polyhedron([[]]).an_affine_basis() - [A vertex at ()] - sage: Polyhedron().an_affine_basis() - [] """ if not self.is_compact(): raise NotImplementedError("this function is not implemented for unbounded polyhedra") @@ -2107,6 +2108,23 @@ def an_affine_basis(self): return [self.Vrepresentation()[i] for i in basis_indices] + def _test_an_affine_basis(self, tester=None, **options): + """ + Run tests on the method :meth:`.an_affine_basis` + + TESTS:: + + sage: polytopes.cross_polytope(3)._test_an_affine_basis() + """ + if tester is None: + tester = self._tester(**options) + if self.is_compact(): + b = self.an_affine_basis() + m = matrix([1] + list(v) for v in b) + tester.assertEqual(m.rank(), self.dim() + 1) + for v in b: + tester.assertIn(v, self.vertices()) + def ray_generator(self): """ Return a generator for the rays of the polyhedron. diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx index 21d3a272376..fbe253de5fc 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx @@ -589,6 +589,7 @@ cdef class FaceIterator(SageObject): Return the next face. EXAMPLES:: + sage: P = polytopes.cube() sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter() diff --git a/src/sage/geometry/polyhedron/library.py b/src/sage/geometry/polyhedron/library.py index 6fc5d4d4fa9..8f5aa10b241 100644 --- a/src/sage/geometry/polyhedron/library.py +++ b/src/sage/geometry/polyhedron/library.py @@ -564,6 +564,8 @@ def regular_polygon(self, n, exact=True, base_ring=None, backend=None): 8 sage: octagon.volume() # optional - pynormaliz 2*a + sage: TestSuite(octagon).run() + sage: TestSuite(polytopes.regular_polygon(5, exact=False)).run() """ n = ZZ(n) if n <= 2: @@ -628,6 +630,7 @@ def Birkhoff_polytope(self, n, backend=None): sage: b4norm = polytopes.Birkhoff_polytope(4,backend='normaliz') # optional - pynormaliz sage: TestSuite(b4norm).run() # optional - pynormaliz + sage: TestSuite(polytopes.Birkhoff_polytope(3)).run() """ from itertools import permutations verts = [] @@ -702,6 +705,7 @@ def simplex(self, dim=3, project=False, base_ring=None, backend=None): sage: s6norm = polytopes.simplex(6,backend='normaliz') # optional - pynormaliz sage: TestSuite(s6norm).run() # optional - pynormaliz + sage: TestSuite(polytopes.simplex(5)).run() """ verts = list((ZZ**(dim + 1)).basis()) if project: @@ -768,6 +772,9 @@ def icosahedron(self, exact=True, base_ring=None, backend=None): (1, 12, 30, 20, 1) sage: ico.volume() # optional - pynormaliz 5/12*sqrt5 + 5/4 + sage: TestSuite(ico).run() # optional - pynormaliz + sage: ico = polytopes.icosahedron(exact=False) + sage: TestSuite(ico).run() """ if base_ring is None and exact: @@ -833,6 +840,7 @@ def dodecahedron(self, exact=True, base_ring=None, backend=None): sage: d12 = polytopes.dodecahedron(backend='normaliz') # optional - pynormaliz sage: d12.f_vector() # optional - pynormaliz (1, 20, 30, 12, 1) + sage: TestSuite(d12).run() # optional - pynormaliz """ return self.icosahedron(exact=exact, base_ring=base_ring, backend=backend).polar() @@ -887,6 +895,7 @@ def small_rhombicuboctahedron(self, exact=True, base_ring=None, backend=None): (1, 24, 48, 26, 1) sage: sr.volume() # optional - pynormaliz 80/3*sqrt2 + 32 + sage: TestSuite(sr).run() # optional - pynormaliz """ if base_ring is None and exact: from sage.rings.number_field.number_field import QuadraticField @@ -1103,6 +1112,7 @@ def truncated_cube(self, exact=True, base_ring=None, backend=None): sage: co = polytopes.truncated_cube(backend='normaliz') # optional - pynormaliz sage: co.f_vector() # optional - pynormaliz (1, 24, 36, 14, 1) + sage: TestSuite(co).run() # optional - pynormaliz """ if base_ring is None and exact: @@ -1480,14 +1490,16 @@ def icosidodecahedron(self, exact=True, backend=None): TESTS:: - sage: polytopes.icosidodecahedron(exact=False) + sage: id = polytopes.icosidodecahedron(exact=False); id A 3-dimensional polyhedron in RDF^3 defined as the convex hull of 30 vertices + sage: TestSuite(id).run() sage: id = polytopes.icosidodecahedron(backend='normaliz') # optional - pynormaliz sage: id.f_vector() # optional - pynormaliz (1, 30, 60, 32, 1) sage: id.base_ring() # optional - pynormaliz Number Field in sqrt5 with defining polynomial x^2 - 5 with sqrt5 = 2.236067977499790? + sage: TestSuite(id).run() # optional - pynormaliz """ from sage.rings.number_field.number_field import QuadraticField from itertools import product @@ -1560,7 +1572,7 @@ def icosidodecahedron_V2(self, exact=True, base_ring=None, backend=None): (1, 30, 60, 32, 1) sage: id.base_ring() # optional - pynormaliz Number Field in sqrt5 with defining polynomial x^2 - 5 with sqrt5 = 2.236067977499790? - + sage: TestSuite(id).run() # optional - pynormaliz """ if base_ring is None and exact: from sage.rings.number_field.number_field import QuadraticField @@ -2244,7 +2256,7 @@ def six_hundred_cell(self, exact=False, backend=None): TESTS:: sage: p600 = polytopes.six_hundred_cell(exact=True, backend='normaliz') # optional - pynormaliz - sage: len(list(p600.bounded_edges())) # optional - pynormaliz + sage: len(list(p600.bounded_edges())) # optional - pynormaliz, long time 720 """ if exact: @@ -2383,7 +2395,7 @@ def Gosset_3_21(self, backend=None): TESTS:: sage: G321 = polytopes.Gosset_3_21(backend='normaliz') # optional - pynormaliz - sage: TestSuite(G321).run() # optional - pynormaliz + sage: TestSuite(G321).run() # optional - pynormaliz, long time """ from itertools import combinations verts = [] @@ -2464,12 +2476,14 @@ def hypersimplex(self, dim, k, project=False, backend=None): (1, 6, 12, 8, 1) sage: h_4_2.ehrhart_polynomial() # optional - latte_int 2/3*t^3 + 2*t^2 + 7/3*t + 1 + sage: TestSuite(h_4_2).run() sage: h_7_3 = polytopes.hypersimplex(7, 3, project=True) sage: h_7_3 A 6-dimensional polyhedron in RDF^6 defined as the convex hull of 35 vertices sage: h_7_3.f_vector() (1, 35, 210, 350, 245, 84, 14, 1) + sage: TestSuite(h_7_3).run() """ verts = Permutations([0] * (dim - k) + [1] * k).list() if project: @@ -2611,8 +2625,8 @@ def generalized_permutahedron(self, coxeter_type, point=None, exact=True, regula A vertex at (0.500000000000000?, 0.866025403784439?)) sage: perm_a2_reg.is_inscribed() True - sage: perm_a3_reg = polytopes.generalized_permutahedron(['A',3],regular=True) - sage: perm_a3_reg.is_inscribed() + sage: perm_a3_reg = polytopes.generalized_permutahedron(['A',3],regular=True) # long time + sage: perm_a3_reg.is_inscribed() # long time True The same is possible with vertices in ``RDF``:: @@ -2637,7 +2651,7 @@ def generalized_permutahedron(self, coxeter_type, point=None, exact=True, regula It works also with types with non-rational coordinates:: - sage: perm_b3 = polytopes.generalized_permutahedron(['B',3]); perm_b3 + sage: perm_b3 = polytopes.generalized_permutahedron(['B',3]); perm_b3 # long time A 3-dimensional polyhedron in (Number Field in a with defining polynomial x^2 - 2 with a = 1.414213562373095?)^3 defined as the convex hull of 48 vertices sage: perm_b3_reg = polytopes.generalized_permutahedron(['B',3],regular=True); perm_b3_reg # not tested - long time (12sec on 64 bits). @@ -2655,8 +2669,8 @@ def generalized_permutahedron(self, coxeter_type, point=None, exact=True, regula sage: perm_h3 = polytopes.generalized_permutahedron(['H',3],backend='normaliz') # optional - pynormaliz sage: perm_h3 # optional - pynormaliz A 3-dimensional polyhedron in (Number Field in a with defining polynomial x^2 - 5 with a = 2.236067977499790?)^3 defined as the convex hull of 120 vertices - sage: perm_f4 = polytopes.generalized_permutahedron(['F',4],backend='normaliz') # optional - pynormaliz - sage: perm_f4 # optional - pynormaliz + sage: perm_f4 = polytopes.generalized_permutahedron(['F',4],backend='normaliz') # optional - pynormaliz, long time + sage: perm_f4 # optional - pynormaliz, long time A 4-dimensional polyhedron in (Number Field in a with defining polynomial x^2 - 2 with a = 1.414213562373095?)^4 defined as the convex hull of 1152 vertices .. SEEALSO:: @@ -2978,7 +2992,7 @@ def one_hundred_twenty_cell(self, exact=True, backend=None, construction='coxete The ``'normaliz'`` is faster:: - sage: polytopes.one_hundred_twenty_cell(backend='normaliz') # optional - pynormaliz + sage: P = polytopes.one_hundred_twenty_cell(backend='normaliz'); P # optional - pynormaliz A 4-dimensional polyhedron in (Number Field in sqrt5 with defining polynomial x^2 - 5 with sqrt5 = 2.236067977499790?)^4 defined as the convex hull of 600 vertices @@ -2987,6 +3001,10 @@ def one_hundred_twenty_cell(self, exact=True, backend=None, construction='coxete sage: polytopes.one_hundred_twenty_cell(backend='normaliz',construction='as_permutahedron') # not tested - long time A 4-dimensional polyhedron in AA^4 defined as the convex hull of 600 vertices + + TESTS:: + + sage: TestSuite(P).run() # optional - pynormaliz, long time """ if construction == 'coxeter': if not exact: @@ -3047,7 +3065,7 @@ def hypercube(self, dim, intervals=None, backend=None): - ``intervals`` -- (default = None). It takes the following possible inputs: - - If ``None`` (the default), it returns the the `\pm 1`-cube of + - If ``None`` (the default), it returns the `\pm 1`-cube of dimension ``dim``. - ``'zero_one'`` -- (string). Return the `0/1`-cube. @@ -3101,6 +3119,13 @@ def hypercube(self, dim, intervals=None, backend=None): sage: fc = polytopes.hypercube(4,backend='normaliz') # optional - pynormaliz sage: TestSuite(fc).run() # optional - pynormaliz + :: + + sage: ls = [randint(-100,100) for _ in range(4)] + sage: intervals = [[x, x+randint(1,50)] for x in ls] + sage: P = polytopes.hypercube(4, intervals, backend='field') + sage: TestSuite(P).run() + Check that :trac:`29904` is fixed:: sage: intervals = [[-2,2]] @@ -3291,6 +3316,11 @@ def cross_polytope(self, dim, backend=None): sage: cp = polytopes.cross_polytope(4,backend='normaliz') # optional - pynormaliz sage: TestSuite(cp).run() # optional - pynormaliz + :: + + sage: P = polytopes.cross_polytope(6, backend='field') + sage: TestSuite(P).run() + Check that double description is set up correctly:: sage: P = polytopes.cross_polytope(6, backend='ppl') @@ -3326,10 +3356,14 @@ def parallelotope(self, generators, backend=None): sage: K = QuadraticField(2, 'sqrt2') sage: sqrt2 = K.gen() - sage: polytopes.parallelotope([ (1,sqrt2), (1,-1) ]) + sage: P = polytopes.parallelotope([ (1,sqrt2), (1,-1) ]); P A 2-dimensional polyhedron in (Number Field in sqrt2 with defining polynomial x^2 - 2 with sqrt2 = 1.414213562373095?)^2 defined as the convex hull of 4 vertices + + TESTS:: + + sage: TestSuite(P).run() """ from sage.modules.free_module_element import vector from sage.structure.sequence import Sequence diff --git a/src/sage/graphs/base/boost_graph.pyx b/src/sage/graphs/base/boost_graph.pyx index 1e99247025e..e5584d0c335 100644 --- a/src/sage/graphs/base/boost_graph.pyx +++ b/src/sage/graphs/base/boost_graph.pyx @@ -1607,6 +1607,194 @@ cpdef min_cycle_basis(g_sage, weight_function=None, by_weight=False): orth_set[j] = orth_set[j] ^ base return cycle_basis + +cpdef eccentricity_DHV(g, vertex_list=None, weight_function=None, check_weight=True): + r""" + Return the vector of eccentricities using the algorithm of [Dragan2018]_. + + The array returned is of length `n`, and by default its `i`-th component is + the eccentricity of the `i`-th vertex in ``g.vertices()``, + if ``vertex_list is None``, otherwise ``ecc[i]`` is the eccentricity of + vertex ``vertex_list[i]``. + + The algorithm proposed in [Dragan2018]_ is based on the observation that for + all nodes `v,w\in V`, we have `\max(ecc[v]-d(v,w), d(v,w))\leq ecc[w] \leq + ecc[v] + d(v,w)`. Also the algorithm iteratively improves upper and lower + bounds on the eccentricity of each vertex until no further improvements can + be done. + + INPUT: + + - ``g`` -- the input Sage graph. + + - ``vertex_list`` -- list (default: ``None``); a list of `n` vertices + specifying a mapping from `(0, \ldots, n-1)` to vertex labels in `g`. When + set, ``ecc[i]`` is the eccentricity of vertex ``vertex_list[i]``. When + ``vertex_list`` is ``None``, ``ecc[i]`` is the eccentricity of vertex + ``g.vertices()[i]``. + + - ``weight_function`` -- function (default: ``None``); a function that + associates a weight to each edge. If ``None`` (default), the weights of + ``g`` are used, if ``g.weighted()==True``, otherwise all edges have + weight 1. + + - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check + that the ``weight_function`` outputs a number for each edge + + EXAMPLES:: + + sage: from sage.graphs.base.boost_graph import eccentricity_DHV + sage: G = graphs.BullGraph() + sage: eccentricity_DHV(G) + [2.0, 2.0, 2.0, 3.0, 3.0] + + TESTS: + + sage: G = Graph(2) + sage: eccentricity_DHV(G) + [+Infinity, +Infinity] + sage: G = graphs.RandomGNP(20, 0.7) + sage: eccentricity_DHV(G) == G.eccentricity() + True + sage: G = Graph([(0,1,-1)], weighted=True) + sage: eccentricity_DHV(G) + Traceback (most recent call last): + ... + ValueError: graph contains negative edge weights, use Johnson_Boost instead + """ + if g.is_directed(): + raise TypeError("the 'DHV' algorithm only works on undirected graphs") + + cdef int n = g.order() + if not n: + return [] + if n == 1: + return [0] + + if weight_function and check_weight: + g._check_weight_function(weight_function) + + if weight_function is not None: + for e in g.edge_iterator(): + if float(weight_function(e)) < 0: + raise ValueError("graph contains negative edge weights, use Johnson_Boost instead") + elif g.weighted(): + for _,_,w in g.edge_iterator(): + if w and float(w) < 0: + raise ValueError("graph contains negative edge weights, use Johnson_Boost instead") + + if vertex_list is None: + vertex_list = g.vertices() + elif not len(vertex_list) == n or not set(vertex_list) == set(g): + raise ValueError("parameter vertex_list is incorrect for this graph") + + # These variables are automatically deleted when the function terminates. + cdef dict v_to_int = {vv: vi for vi, vv in enumerate(vertex_list)} + cdef BoostVecWeightedGraph g_boost + boost_weighted_graph_from_sage_graph(&g_boost, g, v_to_int, weight_function) + + import sys + cdef v_index u, antipode, v + cdef double ecc_u, ecc_antipode, tmp + cdef size_t i, idx + + cdef list active = list(range(n)) + cdef vector[double] ecc_lower_bound + cdef vector[double] ecc_upper_bound + cdef vector[double] distances + + ecc_lower_bound.assign(n, 0) + ecc_upper_bound.assign(n, sys.float_info.max) + + # Algorithm + while active: + # Select vertex with minimum eccentricity in active and update + # eccentricity upper bounds. + # For this, we select u with minimum eccentricity lower bound in active + # if ecc_u == ecc_lb[u], we are done. Otherwise, we update eccentricity + # lower bounds and repeat + + tmp = sys.float_info.max + for i, v in enumerate(active): + if ecc_lower_bound[v] < tmp: + tmp = ecc_lower_bound[v] + idx = i + active[idx], active[-1] = active[-1], active[idx] + u = active.pop() + + # compute distances from u + sig_on() + distances = g_boost.dijkstra_shortest_paths(u).distances + sig_off() + + # Compute eccentricity of u + ecc_u = 0 + for v in range(n): + if ecc_u < distances[v]: + ecc_u = distances[v] + antipode = v + ecc_upper_bound[u] = ecc_u + + if ecc_u == sys.float_info.max: # Disconnected graph + break + + if ecc_u == ecc_lower_bound[u]: + # We found the good vertex. + # Update eccentricity upper bounds and remove from active those + # vertices for which gap is closed + i = 0 + while i < len(active): + v = active[i] + ecc_upper_bound[v] = min(ecc_upper_bound[v], distances[v] + ecc_u) + if ecc_upper_bound[v] == ecc_lower_bound[v]: + active[i] = active[-1] + active.pop() + else: + i += 1 + + else: + # u was not a good choice. + # We use its antipode to update eccentricity lower bounds. + # Observe that this antipode might have already been seen. + for i, v in enumerate(active): + if v == antipode: + active[i] = active[-1] + active.pop() + break + + # Compute distances from antipode + sig_on() + distances = g_boost.dijkstra_shortest_paths(antipode).distances + sig_off() + + # Compute eccentricity of antipode + ecc_antipode = 0 + for v in range(n): + ecc_antipode = max(ecc_antipode, distances[v]) + ecc_upper_bound[antipode] = ecc_antipode + + # Update eccentricity lower bounds and remove from active those + # vertices for which the gap is closed + i = 0 + while i < len(active): + v = active[i] + ecc_lower_bound[v] = max(ecc_lower_bound[v], distances[v]) + if ecc_upper_bound[v] == ecc_lower_bound[v]: + active[i] = active[-1] + active.pop() + else: + i += 1 + + from sage.rings.infinity import Infinity + cdef list eccentricity = [] + for i in range(n): + if ecc_upper_bound[i] != sys.float_info.max: + eccentricity.append(ecc_upper_bound[i]) + else: + eccentricity.append(+Infinity) + + return eccentricity + cpdef radius_DHV(g, weight_function=None, check_weight=True): r""" Return the radius of weighted graph `g`. diff --git a/src/sage/graphs/bipartite_graph.py b/src/sage/graphs/bipartite_graph.py index 520c4e408a9..b63ca4ad02d 100644 --- a/src/sage/graphs/bipartite_graph.py +++ b/src/sage/graphs/bipartite_graph.py @@ -1553,14 +1553,14 @@ def matching(self, value_only=False, algorithm=None, sage: B = BipartiteGraph() sage: algorithms = ["Hopcroft-Karp", "Eppstein", "Edmonds", "LP"] - sage: all(B.matching(algorithm=algo) == [] for algo in algorithms) + sage: not any(B.matching(algorithm=algo) for algo in algorithms) True sage: all(B.matching(algorithm=algo, value_only=True) == 0 for algo in algorithms) True sage: B.add_vertex(1, left=True) sage: B.add_vertex(2, left=True) sage: B.add_vertex(3, right=True) - sage: all(B.matching(algorithm=algo) == [] for algo in algorithms) + sage: not any(B.matching(algorithm=algo) for algo in algorithms) True sage: all(B.matching(algorithm=algo, value_only=True) == 0 for algo in algorithms) True diff --git a/src/sage/graphs/comparability.pyx b/src/sage/graphs/comparability.pyx index fc19042f23c..44247d26ab0 100644 --- a/src/sage/graphs/comparability.pyx +++ b/src/sage/graphs/comparability.pyx @@ -752,7 +752,7 @@ def is_transitive(g, certificate=False): sage: cert = D.is_transitive(certificate=True) sage: D.has_edge(*cert) False - sage: D.shortest_path(*cert) != [] + sage: bool(D.shortest_path(*cert)) True sage: digraphs.RandomDirectedGNP(20,.2).transitive_closure().is_transitive() True diff --git a/src/sage/graphs/digraph_generators.py b/src/sage/graphs/digraph_generators.py index d00fea61a0a..2947c3c312b 100644 --- a/src/sage/graphs/digraph_generators.py +++ b/src/sage/graphs/digraph_generators.py @@ -577,14 +577,14 @@ def nauty_directg(self, graphs, options="", debug=False): - ``options`` (str) -- a string passed to directg as if it was run at a system command line. Available options from directg --help:: - -e# | -e#:# specify a value or range of the total number of arcs - -o orient each edge in only one direction, never both - -f# Use only the subgroup that fixes the first # vertices setwise - -V only output graphs with nontrivial groups (including exchange of - isolated vertices). The -f option is respected. - -s#/# Make only a fraction of the orientations: The first integer is - the part number (first is 0) and the second is the number of - parts. Splitting is done per input graph independently. + -e | -e: specify a value or range of the total number of arcs + -o orient each edge in only one direction, never both + -f Use only the subgroup that fixes the first vertices setwise + -V only output graphs with nontrivial groups (including exchange of + isolated vertices). The -f option is respected. + -s/ Make only a fraction of the orientations: The first integer is + the part number (first is 0) and the second is the number of + parts. Splitting is done per input graph independently. - ``debug`` (boolean) -- default: ``False`` - if ``True`` directg standard error and standard output are displayed. diff --git a/src/sage/graphs/distances_all_pairs.pyx b/src/sage/graphs/distances_all_pairs.pyx index 0d595571e00..e0529dbb10f 100644 --- a/src/sage/graphs/distances_all_pairs.pyx +++ b/src/sage/graphs/distances_all_pairs.pyx @@ -801,6 +801,138 @@ cdef uint32_t * c_eccentricity_bounding(G, vertex_list=None) except NULL: return LB +cdef uint32_t * c_eccentricity_DHV(G, vertex_list=None): + r""" + Return the vector of eccentricities using the algorithm of [Dragan2018]_. + + The array returned is of length `n`, and by default its `i`-th component is + the eccentricity of the `i`-th vertex in ``G.vertices()``. + + Optional parameter ``vertex_list`` is a list of `n` vertices specifying a + mapping from `(0, \ldots, n-1)` to vertex labels in `G`. When set, + ``ecc[i]`` is the eccentricity of vertex ``vertex_list[i]``. + + The algorithm proposed in [Dragan2018]_ is an improvement of the algorithm + proposed in [TK2013]_. It is also based on the observation that for all + nodes `v,w\in V`, we have `\max(ecc[v]-d(v,w), d(v,w))\leq ecc[w] \leq + ecc[v] + d(v,w)`. Also the algorithms iteratively improves upper and lower + bounds on the eccentricity of each vertex until no further improvements can + be done. The difference with [TK2013]_ is in the order in which improvements + are done. + + EXAMPLES:: + + sage: from sage.graphs.distances_all_pairs import eccentricity + sage: G = graphs.PathGraph(5) + sage: eccentricity(G, algorithm='DHV') + [4, 3, 2, 3, 4] + + TESTS: + + sage: G = graphs.RandomBarabasiAlbert(50, 2) + sage: eccentricity(G, algorithm='bounds') == eccentricity(G, algorithm='DHV') + True + """ + if G.is_directed(): + raise ValueError("the 'DHV' algorithm only works on undirected graphs") + + cdef uint32_t n = G.order() + if not n: + return NULL + + cdef short_digraph sd + init_short_digraph(sd, G, edge_labelled=False, vertex_list=vertex_list) + + cdef MemoryAllocator mem = MemoryAllocator() + cdef uint32_t * distances = mem.malloc(3 * n * sizeof(uint32_t)) + # For storing upper bounds on eccentricity of nodes + cdef uint32_t * ecc_upper_bound = sig_calloc(n, sizeof(uint32_t)) + if not distances or not ecc_upper_bound: + sig_free(ecc_upper_bound) + free_short_digraph(sd) + raise MemoryError() + + cdef uint32_t * waiting_list = distances + n + # For storing lower bounds on eccentricity of nodes + cdef uint32_t * ecc_lower_bound = distances + 2 * n + memset(ecc_upper_bound, -1, n * sizeof(uint32_t)) + memset(ecc_lower_bound, 0, n * sizeof(uint32_t)) + + cdef uint32_t u, ecc_u + cdef uint32_t antipode, ecc_antipode + cdef uint32_t v, tmp + cdef size_t i, idx + cdef bitset_t seen + bitset_init(seen,n) + + cdef list active = list(range(n)) + + # Algorithm + while active: + # Select vertex with minimum eccentricity in active and update + # eccentricity upper bounds. + # For this, we select u with minimum eccentricity lower bound in active + # if ecc_u == ecc_lb[u], we are done. Otherwise, we update eccentricity + # lower bounds and repeat + + tmp = UINT32_MAX + for i, v in enumerate(active): + if ecc_lower_bound[v] < tmp: + tmp = ecc_lower_bound[v] + idx = i + active[idx], active[-1] = active[-1], active[idx] + u = active.pop() + ecc_u = simple_BFS(sd, u, distances, NULL, waiting_list, seen) + ecc_upper_bound[u] = ecc_u + + if ecc_u == UINT32_MAX: # Disconnected graph + break + + if ecc_u == ecc_lower_bound[u]: + # We found the good vertex. + # Update eccentricity upper bounds and remove from active those + # vertices for which gap is closed + i = 0 + while i < len(active): + v = active[i] + ecc_upper_bound[v] = min(ecc_upper_bound[v], distances[v] + ecc_u) + if ecc_upper_bound[v] == ecc_lower_bound[v]: + active[i] = active[-1] + active.pop() + else: + i += 1 + + else: + # u was not a good choice. + # We use its antipode to update eccentricity lower bounds. + # Observe that this antipode might have already been seen. + antipode = waiting_list[n-1] + for i, v in enumerate(active): + if v == antipode: + active[i] = active[-1] + active.pop() + break + + ecc_antipode = simple_BFS(sd, antipode, distances, NULL, waiting_list, seen) + ecc_upper_bound[antipode] = ecc_antipode + + # Update eccentricity lower bounds and remove from active those + # vertices for which the gap is closed + i = 0 + while i < len(active): + v = active[i] + ecc_lower_bound[v] = max(ecc_lower_bound[v], distances[v]) + if ecc_upper_bound[v] == ecc_lower_bound[v]: + active[i] = active[-1] + active.pop() + else: + i += 1 + + free_short_digraph(sd) + bitset_free(seen) + + return ecc_upper_bound + def eccentricity(G, algorithm="standard", vertex_list=None): r""" Return the vector of eccentricities in G. @@ -813,9 +945,16 @@ def eccentricity(G, algorithm="standard", vertex_list=None): - ``G`` -- a Graph or a DiGraph. - ``algorithm`` -- string (default: ``'standard'``); name of the method used - to compute the eccentricity of the vertices. Available algorithms are - ``'standard'`` which performs a BFS from each vertex and ``'bounds'`` - which uses the fast algorithm proposed in [TK2013]_ for undirected graphs. + to compute the eccentricity of the vertices. + + - ``'standard'`` -- Computes eccentricity by performing a BFS from each + vertex. + + - ``'bounds'`` -- Computes eccentricity using the fast algorithm proposed + in [TK2013]_ for undirected graphs. + + - ``'DHV'`` -- Computes all eccentricities of undirected graph using the + algorithm proposed in [Dragan2018]_. - ``vertex_list`` -- list (default: ``None``); a list of `n` vertices specifying a mapping from `(0, \ldots, n-1)` to vertex labels in `G`. When @@ -842,7 +981,10 @@ def eccentricity(G, algorithm="standard", vertex_list=None): sage: from sage.graphs.distances_all_pairs import eccentricity sage: g = graphs.RandomGNP(50, .1) - sage: eccentricity(g, algorithm='standard') == eccentricity(g, algorithm='bounds') + sage: ecc = eccentricity(g, algorithm='standard') + sage: ecc == eccentricity(g, algorithm='bounds') + True + sage: ecc == eccentricity(g, algorithm='DHV') True Case of not (strongly) connected (directed) graph:: @@ -906,6 +1048,8 @@ def eccentricity(G, algorithm="standard", vertex_list=None): ecc = c_eccentricity_bounding(G, vertex_list=int_to_vertex) elif algorithm == "standard": ecc = c_eccentricity(G, vertex_list=int_to_vertex) + elif algorithm == "DHV": + ecc = c_eccentricity_DHV(G, vertex_list=int_to_vertex) else: raise ValueError("unknown algorithm '{}', please contribute".format(algorithm)) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index d22626b92a7..b7743a80b01 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -284,6 +284,7 @@ :meth:`~GenericGraph.layout_extend_randomly` | Extend randomly a partial layout :meth:`~GenericGraph.layout_circular` | Return a circular layout for this graph :meth:`~GenericGraph.layout_tree` | Return an ordered tree layout for this graph + :meth:`~GenericGraph.layout_forest` | Return an ordered forest layout for this graph :meth:`~GenericGraph.layout_graphviz` | Call ``graphviz`` to compute a layout of the vertices of this graph. :meth:`~GenericGraph._circle_embedding` | Set some vertices on a circle in the embedding of this graph. :meth:`~GenericGraph._line_embedding` | Set some vertices on a line in the embedding of this graph. @@ -18570,8 +18571,8 @@ def layout(self, layout=None, pos=None, dim=2, save_pos=False, **options): - ``layout`` -- string (default: ``None``); specifies a layout algorithm among ``"acyclic"``, ``"acyclic_dummy"``, ``"circular"``, - ``"ranked"``, ``"graphviz"``, ``"planar"``, ``"spring"``, or - ``"tree"`` + ``"ranked"``, ``"graphviz"``, ``"planar"``, ``"spring"``, + ``"forest"`` or ``"tree"`` - ``pos`` -- dictionary (default: ``None``); a dictionary of positions @@ -18633,6 +18634,7 @@ def layout(self, layout=None, pos=None, dim=2, save_pos=False, **options): ....: print("option {} : {}".format(key, value)) option by_component : Whether to do the spring layout by connected component -- a boolean. option dim : The dimension of the layout -- 2 or 3. + option forest_roots : An iterable specifying which vertices to use as roots for the ``layout='forest'`` option. If no root is specified for a tree, then one is chosen close to the center of the tree. Ignored unless ``layout='forest'``. option heights : A dictionary mapping heights to the list of vertices at this height. option iterations : The number of times to execute the spring layout algorithm. option layout : A layout algorithm -- one of : "acyclic", "circular" (plots the graph with vertices evenly distributed on a circle), "ranked", "graphviz", "planar", "spring" (traditional spring layout, using the graph's current positions as initial positions), or "tree" (the tree will be plotted in levels, depending on minimum distance for the root). @@ -18640,7 +18642,7 @@ def layout(self, layout=None, pos=None, dim=2, save_pos=False, **options): option save_pos : Whether or not to save the computed position for the graph. option spring : Use spring layout to finalize the current layout. option tree_orientation : The direction of tree branches -- 'up', 'down', 'left' or 'right'. - option tree_root : A vertex designation for drawing trees. A vertex of the tree to be used as the root for the ``layout='tree'`` option. If no root is specified, then one is chosen close to the center of the tree. Ignored unless ``layout='tree'`` + option tree_root : A vertex designation for drawing trees. A vertex of the tree to be used as the root for the ``layout='tree'`` option. If no root is specified, then one is chosen close to the center of the tree. Ignored unless ``layout='tree'``. Some of them only apply to certain layout algorithms. For details, see :meth:`.layout_acyclic`, :meth:`.layout_planar`, @@ -18701,12 +18703,12 @@ def layout_spring(self, by_component=True, **options): sage: g = graphs.LadderGraph(3) #TODO!!!! sage: g.layout_spring() - {0: [0.73..., -0.29...], - 1: [1.37..., 0.30...], - 2: [2.08..., 0.89...], - 3: [1.23..., -0.83...], - 4: [1.88..., -0.30...], - 5: [2.53..., 0.22...]} + {0: [1.0, -0.29...], + 1: [1.64..., 0.30...], + 2: [2.34..., 0.89...], + 3: [1.49..., -0.83...], + 4: [2.14..., -0.30...], + 5: [2.80..., 0.22...]} sage: g = graphs.LadderGraph(7) sage: g.plot(layout="spring") Graphics object consisting of 34 graphics primitives @@ -18885,12 +18887,78 @@ def layout_circular(self, dim=2, center=(0, 0), radius=1, shift=0, angle=0, **op return self._circle_embedding(self.vertices(), center=(0, 0), radius=1, shift=0, angle=pi/2, return_dict=True) + def layout_forest(self, tree_orientation="down", forest_roots=None, + **options): + """ + Return an ordered forest layout for this graph. + + The function relies on :meth:`~GenericGraph.layout_tree` to deal with + each connected component. + + INPUT: + + - ``forest_roots`` -- an iterable of vertices (default: ``None``); + the root vertices of the trees in the forest; a vertex is chosen + close to the center of each component for which no root is specified + in ``forest_roots`` or if ``forest_roots`` is ``None`` + + - ``tree_orientation`` -- string (default: ``'down'``); the direction in + which the tree is growing, can be ``'up'``, ``'down'``, ``'left'`` or + ``'right'`` + + - ``**options`` -- other parameters ignored here + + EXAMPLES:: + + sage: G = graphs.RandomTree(4) + graphs.RandomTree(5) + graphs.RandomTree(6) + sage: p = G.layout_forest() + sage: G.plot(pos=p) # random + Graphics object consisting of 28 graphics primitives + + sage: H = graphs.PathGraph(5) + graphs.PathGraph(5) + graphs.BalancedTree(2,2) + sage: p = H.layout_forest(forest_roots=[14,3]) + sage: H.plot(pos=p) + Graphics object consisting of 32 graphics primitives + + TESTS:: + + sage: G = Graph(0) + sage: G.plot(layout='forest') + Graphics object consisting of 0 graphics primitives + + Works for forests that are trees:: + + sage: g = graphs.StarGraph(4) + sage: p = g.layout_forest(forest_roots=[1]) + sage: sorted(p.items()) + [(0, [2.0, -1]), (1, [2.0, 0]), (2, [3.0, -2]), (3, [2.0, -2]), (4, [1.0, -2])] + + The parameter ``forest_roots`` should be an iterable (or ``None``):: + + sage: H = graphs.PathGraph(5) + sage: p = H.layout_forest(forest_roots=3) + Traceback (most recent call last): + ... + TypeError: forest_roots should be an iterable of vertices + """ + if not self: + return dict() + else: + # Compute the layout component by component + return layout_split(self.__class__.layout_tree, + self, + tree_orientation=tree_orientation, + forest_roots=forest_roots, + **options) + def layout_tree(self, tree_orientation="down", tree_root=None, dim=2, **options): r""" Return an ordered tree layout for this graph. - The graph must be a tree (no non-oriented cycles). + The graph must be a tree (no non-oriented cycles). In case of doubt + whether the graph is connected or not, prefer + :meth:`~GenericGraph.layout_forest`. INPUT: @@ -18929,13 +18997,13 @@ def layout_tree(self, tree_orientation="down", tree_root=None, sage: G = graphs.BalancedTree(2, 2) sage: G.layout_tree(tree_root=0) - {0: (1.5, 0), - 1: (2.5, -1), - 2: (0.5, -1), - 3: (3.0, -2), - 4: (2.0, -2), - 5: (1.0, -2), - 6: (0.0, -2)} + {0: [1.5, 0], + 1: [2.5, -1], + 2: [0.5, -1], + 3: [3.0, -2], + 4: [2.0, -2], + 5: [1.0, -2], + 6: [0.0, -2]} sage: G = graphs.BalancedTree(2, 4) sage: G.plot(layout="tree", tree_root=0, tree_orientation="up") @@ -18947,29 +19015,45 @@ def layout_tree(self, tree_orientation="down", tree_root=None, sage: T.set_embedding({0: [1, 6, 3], 1: [2, 5, 0], 2: [1], 3: [4, 7, 8, 0], ....: 4: [3], 5: [1], 6: [0], 7: [3], 8: [3]}) sage: T.layout_tree() - {0: (2.166..., 0), - 1: (3.5, -1), - 2: (4.0, -2), - 3: (1.0, -1), - 4: (2.0, -2), - 5: (3.0, -2), - 6: (2.0, -1), - 7: (1.0, -2), - 8: (0.0, -2)} + {0: [2.166..., 0], + 1: [3.5, -1], + 2: [4.0, -2], + 3: [1.0, -1], + 4: [2.0, -2], + 5: [3.0, -2], + 6: [2.0, -1], + 7: [1.0, -2], + 8: [0.0, -2]} sage: T.plot(layout="tree", tree_root=3) Graphics object consisting of 18 graphics primitives TESTS:: + sage: G = graphs.BalancedTree(2, 2) + sage: G.layout_tree(tree_root=0, tree_orientation='left') + {0: [0, 1.5], + 1: [-1, 2.5], + 2: [-1, 0.5], + 3: [-2, 3.0], + 4: [-2, 2.0], + 5: [-2, 1.0], + 6: [-2, 0.0]} + sage: G = graphs.CycleGraph(3) sage: G.plot(layout='tree') Traceback (most recent call last): ... RuntimeError: cannot use tree layout on this graph: self.is_tree() returns False + sage: G = Graph(0) + sage: G.plot(layout='tree') + Graphics object consisting of 0 graphics primitives """ if dim != 2: raise ValueError('only implemented in 2D') + if not self: + return dict() + from sage.graphs.all import Graph if not Graph(self).is_tree(): raise RuntimeError("cannot use tree layout on this graph: " @@ -19026,7 +19110,7 @@ def slide(v, dx): x, y = pos[u] x += dx obstruction[y] = max(x + 1, obstruction[y]) - pos[u] = x, y + pos[u] = [x, y] nextlevel += children[u] level = nextlevel @@ -19045,12 +19129,12 @@ def slide(v, dx): # If p has no children, we draw it at the leftmost position # which has not been forbidden x = obstruction[y] - pos[p] = x, y + pos[p] = [x, y] else: # If p has children, we put v on a vertical line going # through the barycenter of its children x = sum(pos[c][0] for c in cp) / len(cp) - pos[p] = x, y + pos[p] = [x, y] ox = obstruction[y] if x < ox: slide(p, ox - x) @@ -19082,7 +19166,7 @@ def slide(v, dx): stick.append(t) if tree_orientation in ['right', 'left']: - return {p: (py, px) for p, (px, py) in pos.items()} + return {p: [py, px] for p, [px, py] in pos.items()} return pos diff --git a/src/sage/graphs/generic_graph_pyx.pyx b/src/sage/graphs/generic_graph_pyx.pyx index 0611477abd8..3ec3373edfe 100644 --- a/src/sage/graphs/generic_graph_pyx.pyx +++ b/src/sage/graphs/generic_graph_pyx.pyx @@ -79,25 +79,39 @@ def layout_split(layout_function, G, **options): left = 0 buffer = 1/sqrt(len(G)) + on_embedding = options.get('on_embedding', None) + forest_roots = options.get('forest_roots', None) + try: + forest_roots = list(forest_roots) if forest_roots else None + except TypeError: + raise TypeError('forest_roots should be an iterable of vertices') + + if forest_roots or on_embedding: + options = copy(options) + options.pop('forest_roots', None) + options.pop('on_embedding', None) + for g in Gs: - if options.get('on_embedding', None): - em = options['on_embedding'] - options_g = copy(options) - # Restriction of `on_embedding` to `g` - options_g['on_embedding'] = {v: em[v] for v in g} - cur_pos = layout_function(g, **options_g) + if on_embedding: + # Restrict ``on_embedding`` to ``g`` + embedding_g = {v: on_embedding[v] for v in g} + cur_pos = layout_function(g, on_embedding=embedding_g, **options) + elif forest_roots: + # Find a root for ``g`` (if any) + tree_root = next((v for v in forest_roots if v in g), None) + cur_pos = layout_function(g, tree_root=tree_root, **options) else: cur_pos = layout_function(g, **options) + xmin = min(x[0] for x in cur_pos.values()) xmax = max(x[0] for x in cur_pos.values()) if len(g) > 1: - buffer = (xmax - xmin)/sqrt(len(g)) + buffer = max(1, (xmax - xmin)/sqrt(len(g))) for v, loc in cur_pos.items(): loc[0] += left - xmin + buffer pos[v] = loc left += xmax - xmin + buffer - if options.get('set_embedding', None): embedding = dict() for g in Gs: diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index d0885c377cb..182a3aa45f8 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -5236,14 +5236,21 @@ def eccentricity(self, v=None, by_weight=False, algorithm=None, - ``'BFS'`` - the computation is done through a BFS centered on each vertex successively. Works only if ``by_weight==False``. + - ``'DHV'`` - the computation is done using the algorithm proposed in + [Dragan2018]_. Works only if ``self`` has non-negative edge weights + and ``v is None`` or ``v`` should contain all vertices of ``self``. + For more information see method + :func:`sage.graphs.distances_all_pairs.eccentricity` and + :func:`sage.graphs.base.boost_graph.eccentricity_DHV`. + - ``'Floyd-Warshall-Cython'`` - a Cython implementation of the Floyd-Warshall algorithm. Works only if ``by_weight==False`` and - ``v is None``. + ``v is None`` or ``v`` should contain all vertices of ``self``. - ``'Floyd-Warshall-Python'`` - a Python implementation of the Floyd-Warshall algorithm. Works also with weighted graphs, even with negative weights (but no negative cycle is allowed). However, ``v`` - must be ``None``. + must be ``None`` or ``v`` should contain all vertices of ``self``. - ``'Dijkstra_NetworkX'`` - the Dijkstra algorithm, implemented in NetworkX. It works with weighted graphs, but no negative weight is @@ -5254,7 +5261,8 @@ def eccentricity(self, v=None, by_weight=False, algorithm=None, - ``'Johnson_Boost'`` - the Johnson algorithm, implemented in Boost (works also with negative weights, if there is no negative - cycle). + cycle). Works only if ``v is None`` or ``v`` should contain all + vertices of ``self``. - ``'From_Dictionary'`` - uses the (already computed) distances, that are provided by input variable ``dist_dict``. @@ -5316,6 +5324,10 @@ def eccentricity(self, v=None, by_weight=False, algorithm=None, [2, 1, 2] sage: G.eccentricity(dist_dict = G.shortest_path_all_pairs(by_weight = True)[0]) [2, 1, 2] + sage: G.eccentricity(by_weight = False, algorithm = 'DHV') + [1, 1, 1] + sage: G.eccentricity(by_weight = True, algorithm = 'DHV') + [2.0, 1.0, 2.0] TESTS: @@ -5352,6 +5364,10 @@ def eccentricity(self, v=None, by_weight=False, algorithm=None, Traceback (most recent call last): ... ValueError: algorithm 'Johnson_Boost' works only if all eccentricities are needed + sage: G.eccentricity(0, algorithm = 'DHV') + Traceback (most recent call last): + ... + ValueError: algorithm 'DHV' works only if all eccentricities are needed """ if weight_function is not None: by_weight = True @@ -5380,6 +5396,8 @@ def weight_function(e): v = [v] if v is None or all(u in v for u in self): + if v is None: + v = list(self) # If we want to use BFS, we use the Cython routine if algorithm == 'BFS': if by_weight: @@ -5387,10 +5405,28 @@ def weight_function(e): from sage.graphs.distances_all_pairs import eccentricity algo = 'bounds' if with_labels: - vertex_list = list(self) - return dict(zip(vertex_list, eccentricity(self, algorithm=algo, vertex_list=vertex_list))) + return dict(zip(v, eccentricity(self, algorithm=algo, vertex_list=v))) else: - return eccentricity(self, algorithm=algo) + return eccentricity(self, algorithm=algo,vertex_list=v) + + if algorithm == 'DHV': + if by_weight: + from sage.graphs.base.boost_graph import eccentricity_DHV + if with_labels: + return dict(zip(v, eccentricity_DHV(self, vertex_list=v, + weight_function=weight_function, + check_weight=check_weight))) + else: + return eccentricity_DHV(self, vertex_list=v, + weight_function=weight_function, + check_weight=check_weight) + else: + from sage.graphs.distances_all_pairs import eccentricity + if with_labels: + return dict(zip(v, eccentricity(self, algorithm=algorithm, + vertex_list=v))) + else: + return eccentricity(self, algorithm=algorithm, vertex_list=v) if algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost']: dist_dict = self.shortest_path_all_pairs(by_weight, algorithm, @@ -5398,9 +5434,7 @@ def weight_function(e): check_weight)[0] algorithm = 'From_Dictionary' - v = self.vertices() - - elif algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost']: + elif algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost','DHV']: raise ValueError("algorithm '" + algorithm + "' works only if all" + " eccentricities are needed") diff --git a/src/sage/graphs/graph_decompositions/tdlib.pyx b/src/sage/graphs/graph_decompositions/tdlib.pyx index 5ffc28685a2..b8885299177 100644 --- a/src/sage/graphs/graph_decompositions/tdlib.pyx +++ b/src/sage/graphs/graph_decompositions/tdlib.pyx @@ -152,6 +152,7 @@ def treedecomposition_exact(G, lb=-1): return T + def get_width(T): """ Return the width of a given tree decomposition. @@ -167,6 +168,7 @@ def get_width(T): - The width of ``T`` EXAMPLES:: + sage: import sage.graphs.graph_decompositions.tdlib as tdlib # optional - tdlib sage: G = graphs.PetersenGraph() # optional - tdlib sage: T = tdlib.treedecomposition_exact(G) # optional - tdlib diff --git a/src/sage/graphs/graph_generators.py b/src/sage/graphs/graph_generators.py index 283d9f5fc4a..e052476a52a 100644 --- a/src/sage/graphs/graph_generators.py +++ b/src/sage/graphs/graph_generators.py @@ -824,25 +824,25 @@ def nauty_geng(self, options="", debug=False): The possible options, obtained as output of ``geng --help``:: - n : the number of vertices - mine:maxe : a range for the number of edges - #:0 means '# or more' except in the case 0:0 + n : the number of vertices + mine:maxe : : a range for the number of edges + :0 means ' or more' except in the case 0:0 res/mod : only generate subset res out of subsets 0..mod-1 - -c : only write connected graphs - -C : only write biconnected graphs - -t : only generate triangle-free graphs - -f : only generate 4-cycle-free graphs - -b : only generate bipartite graphs - (-t, -f and -b can be used in any combination) - -m : save memory at the expense of time (only makes a - difference in the absence of -b, -t, -f and n <= 28). - -d# : a lower bound for the minimum degree - -D# : a upper bound for the maximum degree - -v : display counts by number of edges - -l : canonically label output graphs - - -q : suppress auxiliary output (except from -v) + -c : only write connected graphs + -C : only write biconnected graphs + -t : only generate triangle-free graphs + -f : only generate 4-cycle-free graphs + -b : only generate bipartite graphs + (-t, -f and -b can be used in any combination) + -m : save memory at the expense of time (only makes a + difference in the absence of -b, -t, -f and n <= 28). + -d : a lower bound for the minimum degree + -D : a upper bound for the maximum degree + -v : display counts by number of edges + -l : canonically label output graphs + + -q : suppress auxiliary output (except from -v) Options which cause ``geng`` to use an output format different than the graph6 format are not listed above (-u, -g, -s, -y, -h) as they will @@ -891,6 +891,12 @@ def nauty_geng(self, options="", debug=False): sage: len(list(gen)) 853 + A list of connected degree exactly 2 graphs on 5 vertices. :: + + sage: gen = graphs.nauty_geng("5 -c -d2 -D2") + sage: len(list(gen)) + 1 + The ``debug`` switch can be used to examine ``geng``'s reaction to the input in the ``options`` string. We illustrate success. (A failure will be a string beginning with ">E".) Passing the "-q" switch to diff --git a/src/sage/graphs/graph_plot.py b/src/sage/graphs/graph_plot.py index 453e3325b3c..d2d353e4920 100644 --- a/src/sage/graphs/graph_plot.py +++ b/src/sage/graphs/graph_plot.py @@ -109,17 +109,31 @@ """ layout_options = { - 'layout': 'A layout algorithm -- one of : "acyclic", "circular" (plots the graph with vertices evenly distributed on a circle), "ranked", "graphviz", "planar", "spring" (traditional spring layout, using the graph\'s current positions as initial positions), or "tree" (the tree will be plotted in levels, depending on minimum distance for the root).', - 'iterations': 'The number of times to execute the spring layout algorithm.', - 'heights': 'A dictionary mapping heights to the list of vertices at this height.', - 'spring': 'Use spring layout to finalize the current layout.', - 'tree_root': 'A vertex designation for drawing trees. A vertex of the tree to be used as the root for the ``layout=\'tree\'`` option. If no root is specified, then one is chosen close to the center of the tree. Ignored unless ``layout=\'tree\'``', - 'tree_orientation': 'The direction of tree branches -- \'up\', \'down\', \'left\' or \'right\'.', - 'save_pos': 'Whether or not to save the computed position for the graph.', - 'dim': 'The dimension of the layout -- 2 or 3.', - 'prog': 'Which graphviz layout program to use -- one of "circo", "dot", "fdp", "neato", or "twopi".', - 'by_component': 'Whether to do the spring layout by connected component -- a boolean.', - } + 'layout': 'A layout algorithm -- one of : "acyclic", "circular" (plots the ' + 'graph with vertices evenly distributed on a circle), "ranked", ' + '"graphviz", "planar", "spring" (traditional spring layout, using the ' + 'graph\'s current positions as initial positions), or "tree" (the tree ' + 'will be plotted in levels, depending on minimum distance for the root).', + 'iterations': 'The number of times to execute the spring layout algorithm.', + 'heights': 'A dictionary mapping heights to the list of vertices at this height.', + 'spring': 'Use spring layout to finalize the current layout.', + 'tree_root': 'A vertex designation for drawing trees. A vertex of the tree ' + 'to be used as the root for the ``layout=\'tree\'`` option. If no root ' + 'is specified, then one is chosen close to the center of the tree. ' + 'Ignored unless ``layout=\'tree\'``.', + 'forest_roots': 'An iterable specifying which vertices to use as roots for ' + 'the ``layout=\'forest\'`` option. If no root is specified for a tree, ' + 'then one is chosen close to the center of the tree. ' + 'Ignored unless ``layout=\'forest\'``.', + 'tree_orientation': 'The direction of tree branches -- \'up\', \'down\', ' + '\'left\' or \'right\'.', + 'save_pos': 'Whether or not to save the computed position for the graph.', + 'dim': 'The dimension of the layout -- 2 or 3.', + 'prog': 'Which graphviz layout program to use -- one of "circo", "dot", ' + '"fdp", "neato", or "twopi".', + 'by_component': 'Whether to do the spring layout by connected component ' + '-- a boolean.', + } graphplot_options = layout_options.copy() diff --git a/src/sage/graphs/independent_sets.pyx b/src/sage/graphs/independent_sets.pyx index 63cf7b7ac97..35fb6562fe8 100644 --- a/src/sage/graphs/independent_sets.pyx +++ b/src/sage/graphs/independent_sets.pyx @@ -370,7 +370,7 @@ cdef class IndependentSets: True """ if not self.n: - return S == [] + return not S cdef int i # Set of vertices as a bitset diff --git a/src/sage/graphs/isgci.py b/src/sage/graphs/isgci.py index ce6211aa104..b1023c093cb 100644 --- a/src/sage/graphs/isgci.py +++ b/src/sage/graphs/isgci.py @@ -500,9 +500,8 @@ def __ge__(self, other): sage: graph_classes.Chordal >= graph_classes.Tree True """ - inclusion_digraph = GraphClasses().inclusion_digraph() - if inclusion_digraph.shortest_path(self._gc_id,other._gc_id) != []: + if inclusion_digraph.shortest_path(self._gc_id,other._gc_id): return True else: return Unknown diff --git a/src/sage/graphs/line_graph.pyx b/src/sage/graphs/line_graph.pyx index 5dc94cfdc23..262b293c529 100644 --- a/src/sage/graphs/line_graph.pyx +++ b/src/sage/graphs/line_graph.pyx @@ -206,6 +206,7 @@ def is_line_graph(g, certificate=False): True Verify that :trac:`29740` is fixed:: + sage: g = Graph('O{e[{}^~z`MDZBZBkXzE^') sage: g.is_line_graph() False diff --git a/src/sage/groups/lie_gps/nilpotent_lie_group.py b/src/sage/groups/lie_gps/nilpotent_lie_group.py index 78001d2e9be..5b6541a96fb 100644 --- a/src/sage/groups/lie_gps/nilpotent_lie_group.py +++ b/src/sage/groups/lie_gps/nilpotent_lie_group.py @@ -24,7 +24,7 @@ from sage.manifolds.structure import(DifferentialStructure, RealDifferentialStructure) from sage.misc.cachefunc import cached_method -from sage.misc.misc import repr_lincomb +from sage.misc.repr import repr_lincomb from sage.modules.free_module_element import vector from sage.rings.real_mpfr import RealField_class from sage.structure.element import MultiplicativeGroupElement diff --git a/src/sage/groups/perm_gps/permgroup_element.pyx b/src/sage/groups/perm_gps/permgroup_element.pyx index 3393e7e902f..9f843d84f2f 100644 --- a/src/sage/groups/perm_gps/permgroup_element.pyx +++ b/src/sage/groups/perm_gps/permgroup_element.pyx @@ -1177,22 +1177,23 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): cpdef _act_on_(self, x, bint self_on_left): """ - Return the right action of self on left. + Return the result of the action of ``self`` on ``x``. - For example, if f=left is a polynomial, then this function returns - f(sigma\*x), which is image of f under the right action of sigma on + For example, if ``x=f(z)`` is a polynomial, then this function returns + f(sigma\*z), which is the image of f under the right action of sigma on the indeterminates. This is a right action since the image of - f(sigma\*x) under tau is f(sigma\*tau\*x). + f(sigma\*z) under tau is f(sigma\*tau\*z). - Additionally, if ``left`` is a matrix, then sigma acts on the matrix - by permuting the rows. + Additionally, if ``x`` is a matrix, then sigma acts on the matrix + by permuting the columns when acting from the right and by permuting + the rows when acting from the left. INPUT: + - ``x`` -- element of space on which permutations act - - ``left`` - element of space on which permutations - act from the right - + - ``self_on_left`` -- if ``True``, this permutation acts on ``x`` from + the left, otherwise from the right EXAMPLES:: @@ -1210,13 +1211,18 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): 2*x^2 - y^2 + z^2 + u^2 sage: M = matrix(ZZ,[[1,0,0,0,0],[0,2,0,0,0],[0,0,3,0,0],[0,0,0,4,0],[0,0,0,0,5]]) - sage: M*sigma + sage: sigma * M [0 2 0 0 0] [0 0 3 0 0] [1 0 0 0 0] [0 0 0 0 5] [0 0 0 4 0] - + sage: (M * sigma) * tau == M * (sigma * tau) + True + sage: (M * sigma) * tau == (M * sigma.matrix()) * tau.matrix() + True + sage: (tau * sigma) * M == tau * (sigma * M) + True """ if not self_on_left: left = x @@ -1235,7 +1241,11 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): left.parent())) return left(tuple(sigma_x)) elif is_Matrix(left): - return left.with_permuted_rows(self) + return left.with_permuted_columns(~self) + else: + if is_Matrix(x): + return x.with_permuted_rows(self) + def __mul__(left, right): r""" diff --git a/src/sage/homology/chain_complex.py b/src/sage/homology/chain_complex.py index efc2ee6d809..234878e9d54 100644 --- a/src/sage/homology/chain_complex.py +++ b/src/sage/homology/chain_complex.py @@ -681,7 +681,7 @@ def __init__(self, grading_group, degree_of_differential, base_ring, differentia self._degree_of_differential = degree_of_differential self._diff = differentials - from sage.categories.all import ChainComplexes + from sage.categories.chain_complexes import ChainComplexes category = ChainComplexes(base_ring) super(ChainComplex_class, self).__init__(base=base_ring, category=category) diff --git a/src/sage/homology/chain_complex_morphism.py b/src/sage/homology/chain_complex_morphism.py index 885777d276e..fea32a9f6bf 100644 --- a/src/sage/homology/chain_complex_morphism.py +++ b/src/sage/homology/chain_complex_morphism.py @@ -54,7 +54,7 @@ from sage.matrix.constructor import block_diagonal_matrix, zero_matrix from sage.categories.morphism import Morphism from sage.categories.homset import Hom -from sage.categories.category_types import ChainComplexes +from sage.categories.chain_complexes import ChainComplexes def is_ChainComplexMorphism(x): diff --git a/src/sage/homology/hochschild_complex.py b/src/sage/homology/hochschild_complex.py index 29048a8ae26..78f38739dc5 100644 --- a/src/sage/homology/hochschild_complex.py +++ b/src/sage/homology/hochschild_complex.py @@ -17,7 +17,7 @@ from sage.structure.parent import Parent from sage.structure.element import ModuleElement, parent from sage.structure.richcmp import richcmp -from sage.categories.category_types import ChainComplexes +from sage.categories.chain_complexes import ChainComplexes from sage.categories.tensor import tensor from sage.combinat.free_module import CombinatorialFreeModule from sage.homology.chain_complex import ChainComplex, Chain_class diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 52ceaf4253b..9ab236bd7c3 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -423,14 +423,14 @@ def arcs(self, presentation='pd'): elif presentation == 'gauss_code': res = [] for comp in self.gauss_code(): - if not any(i<0 for i in comp): + if not any(i < 0 for i in comp): res.append(comp) else: rescom = [] par = [] for i in comp: par.append(i) - if i<0: + if i < 0: rescom.append(copy(par)) par = [i] rescom[0] = par + rescom[0] @@ -795,7 +795,7 @@ def _directions_of_edges(self): tails[C[2]] = C a = C[2] D = C - while not a in heads: + while a not in heads: next_crossing = [x for x in pd_code if a in x and x != D] if not next_crossing: heads[a] = D @@ -819,7 +819,7 @@ def _directions_of_edges(self): if a in x: D = x break - while not a in heads: + while a not in heads: tails[a] = D for x in pd_code: if a in x and x != D: @@ -1021,12 +1021,14 @@ def _enhanced_states(self): G = Graph() for j, cr in enumerate(crossings): n = nmax + j - if not v[j]: # For negative crossings, we go from undercrossings to the left + if not v[j]: + # For negative crossings, we go from undercrossings to the left G.add_edge((cr[3], cr[0], n), cr[0]) G.add_edge((cr[3], cr[0], n), cr[3]) G.add_edge((cr[1], cr[2], n), cr[2]) G.add_edge((cr[1], cr[2], n), cr[1]) - else: # positive crossings, from undercrossing to the right + else: + # positive crossings, from undercrossing to the right G.add_edge((cr[0], cr[1], n), cr[0]) G.add_edge((cr[0], cr[1], n), cr[1]) G.add_edge((cr[2], cr[3], n), cr[2]) @@ -1037,10 +1039,10 @@ def _enhanced_states(self): jmin = writhe + iindex - len(sm) jmax = writhe + iindex + len(sm) smoothings.append((tuple(v), sm, iindex, jmin, jmax)) - states = [] # we got all the smoothings, now find all the states + states = [] # we got all the smoothings, now find all the states for sm in smoothings: for k in range(len(sm[1])+1): - for circpos in combinations(sorted(sm[1]), k): # Add each state + for circpos in combinations(sorted(sm[1]), k): # Add each state circneg = sm[1].difference(circpos) j = writhe + sm[2] + len(circpos) - len(circneg) states.append((sm[0], tuple(sorted(circneg)), tuple(circpos), sm[2], j)) @@ -1082,7 +1084,7 @@ def _khovanov_homology_cached(self, height, ring=ZZ): ncross = len(crossings) states = [(_0, set(_1), set(_2), _3, _4) for (_0, _1, _2, _3, _4) in self._enhanced_states()] - bases = {} # arrange them by (i,j) + bases = {} # arrange them by (i,j) for st in states: i, j = st[3], st[4] if j == height: @@ -1102,7 +1104,7 @@ def _khovanov_homology_cached(self, height, ring=ZZ): difs = [index for index,value in enumerate(V1[0]) if value != V20[index]] if len(difs) == 1 and not (V2[2].intersection(V1[1]) or V2[1].intersection(V1[2])): m[ii,jj] = (-1)**sum(V2[0][x] for x in range(difs[0]+1, ncross)) - #Here we have the matrix constructed, now we have to put it in the dictionary of complexes + # Here we have the matrix constructed, now we have to put it in the dictionary of complexes else: m = matrix(ring, len(bases[(i,j)]), 0) complexes[i] = m.transpose() @@ -1570,39 +1572,25 @@ def seifert_matrix(self): """ x = self._braid_word_components_vector() h = self._homology_generators() - hl = len(h) - A = matrix(ZZ, hl, hl) indices = [i for i, hi in enumerate(h) if hi] - for i in indices: + N = len(indices) + A = matrix(ZZ, N, N, 0) + for ni, i in enumerate(indices): hi = h[i] - for j in range(i, hl): - if i == j: - A[i, j] = -(x[i] + x[hi]).sign() - elif hi > h[j]: - A[i, j] = 0 - A[j, i] = 0 - elif hi < j: - A[i, j] = 0 - A[j, i] = 0 - elif hi == j: + A[ni, ni] = -(x[i] + x[hi]).sign() + for nj in range(ni + 1, N): + j = indices[nj] + if hi > h[j] or hi < j: + continue + if hi == j: if x[j] > 0: - A[i, j] = 0 - A[j, i] = 1 + A[nj, ni] = 1 else: - A[i, j] = -1 - A[j, i] = 0 - elif abs(abs(x[i]) - abs(x[j])) > 1: - A[i, j] = 0 + A[ni, nj] = -1 elif abs(x[i]) - abs(x[j]) == 1: - A[i, j] = 0 - A[j, i] = -1 + A[nj, ni] = -1 elif abs(x[j]) - abs(x[i]) == 1: - A[i, j] = 1 - A[j, i] = 0 - else: # for debugging - A[i, j] = 2 - A[j, i] = 2 - A = A.matrix_from_rows_and_columns(indices, indices) + A[ni, nj] = 1 A.set_immutable() return A @@ -1851,6 +1839,7 @@ def alexander_polynomial(self, var='t'): t = R.gen() seifert_matrix = self.seifert_matrix() f = (seifert_matrix - t * seifert_matrix.transpose()).determinant() + # could we use a charpoly here ? or faster determinant ? if f != 0: exp = f.exponents() return t ** ((-max(exp) - min(exp)) // 2) * f @@ -1998,7 +1987,7 @@ def seifert_circles(self): result = [] # detect looped segments. They must be their own seifert circles for a in available_segments: - if any(C.count(a)>1 for C in self.pd_code()): + if any(C.count(a) > 1 for C in self.pd_code()): result.append([a]) # remove the looped segments from the available for a in result: @@ -2011,7 +2000,7 @@ def seifert_circles(self): else: C = heads[a] par = [] - while not a in par: + while a not in par: par.append(a) posnext = C[(C.index(a) + 1) % 4] if tails[posnext] == C and not [posnext] in result: @@ -2099,7 +2088,7 @@ def regions(self): while available_edges: edge = available_edges.pop() region = [] - while not edge in region: + while edge not in region: region.append(edge) if edge > 0: cros = heads[edge] @@ -3054,7 +3043,7 @@ def plot(self, gap=0.1, component_gap=0.5, solver=None, rev = segments[-e][1:] rev.reverse() sig = sign(s[edges.index(-e)]) - nregion+=[[a, -sig] for a in rev] + nregion += [[a, -sig] for a in rev] nregion.append([segments[-e][0], 1]) nregions.append(nregion) N = max(segments) + 1 @@ -3182,8 +3171,8 @@ def plot(self, gap=0.1, component_gap=0.5, solver=None, y1 = y0 elif direction == 3: x1 = x0 - y1 = y0 -l - im.append(([[x0,y0],[x1,y1]], l, direction)) + y1 = y0 - l + im.append(([[x0, y0], [x1, y1]], l, direction)) direction = (direction + turn) % 4 x0 = x1 y0 = y1 diff --git a/src/sage/libs/eclib/mat.pyx b/src/sage/libs/eclib/mat.pyx index b061982110e..9cd06f9b344 100644 --- a/src/sage/libs/eclib/mat.pyx +++ b/src/sage/libs/eclib/mat.pyx @@ -131,7 +131,8 @@ cdef class Matrix: ## """ ## Return the rank of this matrix. -## EXAMPLES: +## EXAMPLES:: +## ## sage: M = CremonaModularSymbols(389) ## sage: t = M.hecke_matrix(2) ## sage: t.rank() diff --git a/src/sage/libs/flint/flint_wrap.h b/src/sage/libs/flint/flint_wrap.h index b68c93ce810..9c0ca4e88b1 100644 --- a/src/sage/libs/flint/flint_wrap.h +++ b/src/sage/libs/flint/flint_wrap.h @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include diff --git a/src/sage/libs/flint/fmpz_poly_mat.pxd b/src/sage/libs/flint/fmpz_poly_mat.pxd new file mode 100644 index 00000000000..d045c745de3 --- /dev/null +++ b/src/sage/libs/flint/fmpz_poly_mat.pxd @@ -0,0 +1,27 @@ +# distutils: libraries = flint +# distutils: depends = flint/fmpz_poly_mat.h + +from sage.libs.flint.types cimport fmpz_poly_mat_t, fmpz_poly_t, fmpz_t, slong, fmpz_mat_t + +# flint/fmpz_poly_mat.h +cdef extern from "flint_wrap.h": + + void fmpz_poly_mat_init(fmpz_poly_mat_t mat, slong rows, slong cols) + void fmpz_poly_mat_init_set(fmpz_poly_mat_t mat, const fmpz_poly_mat_t src) + void fmpz_poly_mat_clear(fmpz_poly_mat_t mat) + fmpz_poly_t fmpz_poly_mat_entry(fmpz_poly_mat_t mat, long i, long j) + slong fmpz_poly_mat_nrows(const fmpz_poly_mat_t mat) + slong fmpz_poly_mat_ncols(const fmpz_poly_mat_t mat) + + void fmpz_poly_mat_set(fmpz_poly_mat_t mat1, const fmpz_poly_mat_t mat2) + + void fmpz_poly_mat_swap(fmpz_poly_mat_t mat1, fmpz_poly_mat_t mat2) + + void fmpz_poly_mat_transpose(fmpz_poly_mat_t B, const fmpz_poly_mat_t A) + + void fmpz_poly_mat_evaluate_fmpz(fmpz_mat_t B, const fmpz_poly_mat_t A, + const fmpz_t x) + + void fmpz_poly_mat_trace(fmpz_poly_t trace, const fmpz_poly_mat_t mat) + + void fmpz_poly_mat_det(fmpz_poly_t det, const fmpz_poly_mat_t A) diff --git a/src/sage/libs/flint/types.pxd b/src/sage/libs/flint/types.pxd index 92173a78156..3e0d595ee9f 100644 --- a/src/sage/libs/flint/types.pxd +++ b/src/sage/libs/flint/types.pxd @@ -79,6 +79,13 @@ cdef extern from "flint_wrap.h": ctypedef fmpq_mat_struct fmpq_mat_t[1] +# flint/fmpz_poly_mat.h: +cdef extern from "flint_wrap.h": + ctypedef struct fmpz_poly_mat_struct: + pass + + ctypedef fmpz_poly_mat_struct fmpz_poly_mat_t[1] + # flint/fmpz_mod_poly.h: cdef extern from "flint_wrap.h": ctypedef struct fmpz_mod_poly_struct: diff --git a/src/sage/libs/mpmath/ext_main.pyx b/src/sage/libs/mpmath/ext_main.pyx index ac89834fee6..694cc089e7c 100644 --- a/src/sage/libs/mpmath/ext_main.pyx +++ b/src/sage/libs/mpmath/ext_main.pyx @@ -1586,19 +1586,6 @@ cdef class mpnumber: """ return binop(OP_MUL, self, other, global_opts) - def __div__(self, other): - """ - Division of mpmath numbers. Compatible numerical types - are automatically converted to mpmath numbers :: - - sage: from mpmath import mpf, mpc - sage: mpf(10) / mpc(5) - mpc(real='2.0', imag='0.0') - sage: float(9) / mpf(3) - mpf('3.0') - """ - return binop(OP_DIV, self, other, global_opts) - def __truediv__(self, other): """ Division of mpmath numbers. Compatible numerical types diff --git a/src/sage/libs/ntl/ntl_GF2.pyx b/src/sage/libs/ntl/ntl_GF2.pyx index 21f69956b7c..1a03ed5ebd7 100644 --- a/src/sage/libs/ntl/ntl_GF2.pyx +++ b/src/sage/libs/ntl/ntl_GF2.pyx @@ -153,9 +153,6 @@ cdef class ntl_GF2(object): GF2_div(r.x, (self).x, (other).x) return r - def __div__(self, other): - return self / other - def __sub__(self, other): """ sage: o = ntl.GF2(1) diff --git a/src/sage/libs/ntl/ntl_GF2E.pyx b/src/sage/libs/ntl/ntl_GF2E.pyx index f45ad616a94..f9072cbc612 100644 --- a/src/sage/libs/ntl/ntl_GF2E.pyx +++ b/src/sage/libs/ntl/ntl_GF2E.pyx @@ -284,9 +284,6 @@ cdef class ntl_GF2E(object): GF2E_div(r.x, self.x, (other).x) return r - def __div__(self, other): - return self / other - def __neg__(ntl_GF2E self): """ EXAMPLES:: diff --git a/src/sage/libs/ntl/ntl_GF2X.pyx b/src/sage/libs/ntl/ntl_GF2X.pyx index f3817f9cac2..b80766b4ec1 100644 --- a/src/sage/libs/ntl/ntl_GF2X.pyx +++ b/src/sage/libs/ntl/ntl_GF2X.pyx @@ -224,9 +224,6 @@ cdef class ntl_GF2X(object): raise ArithmeticError("self (=%s) is not divisible by b (=%s)" % (self, b)) return q - def __div__(self, other): - return self / other - def DivRem(ntl_GF2X self, b): """ EXAMPLES:: diff --git a/src/sage/libs/ntl/ntl_ZZX.pyx b/src/sage/libs/ntl/ntl_ZZX.pyx index 22e945814ae..4d602df9032 100644 --- a/src/sage/libs/ntl/ntl_ZZX.pyx +++ b/src/sage/libs/ntl/ntl_ZZX.pyx @@ -360,9 +360,6 @@ cdef class ntl_ZZX(object): result = make_ZZX_sig_off(q) return result - def __div__(self, other): - return self / other - def __mod__(ntl_ZZX self, ntl_ZZX other): """ Given polynomials a, b in ZZ[X], there exist polynomials q, r diff --git a/src/sage/libs/ntl/ntl_ZZ_pEX.pyx b/src/sage/libs/ntl/ntl_ZZ_pEX.pyx index b8f07db7c74..58939074d82 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pEX.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_pEX.pyx @@ -141,13 +141,14 @@ cdef class ntl_ZZ_pEX(object): def __reduce__(self): """ - TESTS: - sage: c=ntl.ZZ_pEContext(ntl.ZZ_pX([1,1,1], 7)) - sage: a = ntl.ZZ_pE([3,2], c) - sage: b = ntl.ZZ_pE([1,2], c) - sage: f = ntl.ZZ_pEX([a, b, b]) - sage: loads(dumps(f)) == f - True + TESTS:: + + sage: c=ntl.ZZ_pEContext(ntl.ZZ_pX([1,1,1], 7)) + sage: a = ntl.ZZ_pE([3,2], c) + sage: b = ntl.ZZ_pE([1,2], c) + sage: f = ntl.ZZ_pEX([a, b, b]) + sage: loads(dumps(f)) == f + True """ return make_ZZ_pEX, (self.list(), self.get_modulus_context()) @@ -155,13 +156,14 @@ cdef class ntl_ZZ_pEX(object): """ Returns a string representation of self. - TESTS: - sage: c=ntl.ZZ_pEContext(ntl.ZZ_pX([1,1,1], 7)) - sage: a = ntl.ZZ_pE([3,2], c) - sage: b = ntl.ZZ_pE([1,2], c) - sage: f = ntl.ZZ_pEX([a, b, b]) - sage: f - [[3 2] [1 2] [1 2]] + TESTS:: + + sage: c=ntl.ZZ_pEContext(ntl.ZZ_pX([1,1,1], 7)) + sage: a = ntl.ZZ_pE([3,2], c) + sage: b = ntl.ZZ_pE([1,2], c) + sage: f = ntl.ZZ_pEX([a, b, b]) + sage: f + [[3 2] [1 2] [1 2]] """ self.c.restore_c() return ccrepr(self.x) @@ -170,20 +172,21 @@ cdef class ntl_ZZ_pEX(object): """ Return a copy of self. - TESTS: - sage: c=ntl.ZZ_pEContext(ntl.ZZ_pX([1,1,1], 7)) - sage: a = ntl.ZZ_pE([3,2], c) - sage: b = ntl.ZZ_pE([1,2], c) - sage: f = ntl.ZZ_pEX([a, b, b]) - sage: f - [[3 2] [1 2] [1 2]] - sage: y = copy(f) - sage: y == f - True - sage: y is f - False - sage: f[0] = 0; y - [[3 2] [1 2] [1 2]] + TESTS:: + + sage: c=ntl.ZZ_pEContext(ntl.ZZ_pX([1,1,1], 7)) + sage: a = ntl.ZZ_pE([3,2], c) + sage: b = ntl.ZZ_pE([1,2], c) + sage: f = ntl.ZZ_pEX([a, b, b]) + sage: f + [[3 2] [1 2] [1 2]] + sage: y = copy(f) + sage: y == f + True + sage: y is f + False + sage: f[0] = 0; y + [[3 2] [1 2] [1 2]] """ cdef ntl_ZZ_pEX r = self._new() #self.c.restore_c() ## _new() restores @@ -379,9 +382,6 @@ cdef class ntl_ZZ_pEX(object): raise ArithmeticError("self (=%s) is not divisible by other (=%s)" % (self, other)) return r - def __div__(self, other): - return self / other - def __mod__(ntl_ZZ_pEX self, ntl_ZZ_pEX other): """ Given polynomials a, b in ZZ_pE[X], if p is prime and the defining modulus irreducible, diff --git a/src/sage/libs/ntl/ntl_ZZ_pX.pyx b/src/sage/libs/ntl/ntl_ZZ_pX.pyx index a31f9a4c807..0d18d06c4fa 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pX.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_pX.pyx @@ -407,9 +407,6 @@ cdef class ntl_ZZ_pX(object): raise ArithmeticError("self (=%s) is not divisible by other (=%s)" % (self, other)) return r - def __div__(self, other): - return self / other - def __mod__(ntl_ZZ_pX self, ntl_ZZ_pX other): """ Given polynomials a, b in ZZ_p[X], if p is prime, then there exist polynomials q, r diff --git a/src/sage/libs/ntl/ntl_lzz_p.pyx b/src/sage/libs/ntl/ntl_lzz_p.pyx index 582e6f088f4..0aef08c3601 100644 --- a/src/sage/libs/ntl/ntl_lzz_p.pyx +++ b/src/sage/libs/ntl/ntl_lzz_p.pyx @@ -165,7 +165,8 @@ cdef class ntl_zz_p(object): """ For pickling. - TESTS: + TESTS:: + sage: f = ntl.zz_p(16,244) sage: loads(dumps(f)) == f True @@ -253,9 +254,6 @@ cdef class ntl_zz_p(object): sig_off() return q - def __div__(self, other): - return self / other - def __pow__(ntl_zz_p self, long n, ignored): """ Return the n-th nonnegative power of self. @@ -423,11 +421,13 @@ cdef class ntl_zz_p(object): self.c.restore_c() zz_p_clear(self.x) + def make_zz_p(val, context): """ For unpickling. - TESTS: + TESTS:: + sage: f = ntl.zz_p(1, 12) sage: loads(dumps(f)) == f True diff --git a/src/sage/libs/ntl/ntl_lzz_pX.pyx b/src/sage/libs/ntl/ntl_lzz_pX.pyx index d953a55248f..c08ad28491a 100644 --- a/src/sage/libs/ntl/ntl_lzz_pX.pyx +++ b/src/sage/libs/ntl/ntl_lzz_pX.pyx @@ -165,7 +165,8 @@ cdef class ntl_zz_pX(object): def __reduce__(self): """ - TESTS: + TESTS:: + sage: f = ntl.zz_pX([10,10^30+1], 20) sage: f == loads(dumps(f)) True @@ -352,9 +353,6 @@ cdef class ntl_zz_pX(object): raise ArithmeticError("self (=%s) is not divisible by other (=%s)" % (self, other)) return q - def __div__(self, other): - return self / other - def __mod__(ntl_zz_pX self, other): """ Given polynomials a, b in ZZ[X], there exist polynomials q, r @@ -894,7 +892,8 @@ def make_zz_pX(L, context): """ For unpickling. - TESTS: + TESTS:: + sage: f = ntl.zz_pX(range(16), 12) sage: loads(dumps(f)) == f True diff --git a/src/sage/libs/ntl/ntl_mat_GF2.pyx b/src/sage/libs/ntl/ntl_mat_GF2.pyx index 5ac36f96285..111010f8d14 100644 --- a/src/sage/libs/ntl/ntl_mat_GF2.pyx +++ b/src/sage/libs/ntl/ntl_mat_GF2.pyx @@ -40,18 +40,20 @@ from .ntl_GF2 cimport ntl_GF2 from sage.rings.integer cimport Integer from sage.libs.ntl.ntl_ZZ import unpickle_class_args + cdef class ntl_mat_GF2(object): r""" The \class{mat_GF2} class implements arithmetic with matrices over $F_2$. """ def __init__(self, nrows=0, ncols=0, v=None): """ - Constructs a matrix over ntl.GF2. + Construct a matrix over ntl.GF2. INPUT: - nrows -- number of rows - ncols -- number of columns - v -- either a list or a matrix over GF(2^x) + + - nrows -- number of rows + - ncols -- number of columns + - v -- either a list or a matrix over GF(2^x) EXAMPLES:: @@ -412,7 +414,7 @@ cdef class ntl_mat_GF2(object): def determinant(self): """ - Returns the determinant. + Return the determinant. EXAMPLES:: @@ -438,9 +440,11 @@ cdef class ntl_mat_GF2(object): the rank of the first ncols columns). INPUT: - ncols -- number of columns to process (default: all) + + ncols -- number of columns to process (default: all) EXAMPLES:: + sage: A = random_matrix(GF(2), 10, 10) sage: Abar = ntl.mat_GF2(A) sage: A.echelon_form() @@ -479,9 +483,10 @@ cdef class ntl_mat_GF2(object): def list(self): """ - Returns a list of the entries in this matrix + Return a list of the entries in this matrix. EXAMPLES:: + sage: A = random_matrix(GF(2), 4, 4) sage: Abar = ntl.mat_GF2(A) sage: A.list() @@ -499,6 +504,7 @@ cdef class ntl_mat_GF2(object): Return \code{True} if this matrix contains only zeroes, and \code{False} otherwise. EXAMPLES:: + sage: A = random_matrix(GF(2), 10, 10) sage: Abar = ntl.mat_GF2(A) sage: Abar.IsZero() @@ -515,7 +521,7 @@ cdef class ntl_mat_GF2(object): def _sage_(ntl_mat_GF2 self): r""" - Returns a \class{Matrix} over GF(2). + Return a \class{Matrix} over GF(2). EXAMPLES:: @@ -557,9 +563,10 @@ cdef class ntl_mat_GF2(object): def transpose(ntl_mat_GF2 self): """ - Returns the transposed matrix of this matrix. + Return the transposed matrix of this matrix. EXAMPLES:: + sage: A = random_matrix(GF(2), 10, 10) sage: Abar = ntl.mat_GF2(A); Abar [[0 1 0 1 1 0 0 0 1 1] @@ -598,6 +605,7 @@ cdef class ntl_mat_GF2(object): Return $X = A^{-1}$; an error is raised if A is singular. EXAMPLES:: + sage: l = [0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, \ 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, \ 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, \ @@ -618,6 +626,7 @@ cdef class ntl_mat_GF2(object): test if this matrix is the n x n identity matrix. EXAMPLES:: + sage: A = ntl.mat_GF2(4,4) sage: A[0,0] = 1 sage: A[1,1] = 1 @@ -637,6 +646,7 @@ cdef class ntl_mat_GF2(object): test if X is an n x n diagonal matrix with d on diagonal. EXAMPLES:: + sage: A = ntl.mat_GF2(4,4) sage: A[0,0] = 1 sage: A[1,1] = 1 @@ -656,6 +666,7 @@ cdef class ntl_mat_GF2(object): is in row echelon form. EXAMPLES:: + sage: A = random_matrix(GF(2),10,10) sage: Abar = ntl.mat_GF2(A) sage: A.image() diff --git a/src/sage/manifolds/chart.py b/src/sage/manifolds/chart.py index f1a8cb3f58d..07e3fa94aee 100644 --- a/src/sage/manifolds/chart.py +++ b/src/sage/manifolds/chart.py @@ -1649,8 +1649,10 @@ def _init_coordinates(self, coord_list): coord_symb = coord_properties[0].strip() # the coordinate symbol # default values, possibly redefined below: coord_latex = None - xmin = -Infinity; xmin_included = False - xmax = +Infinity; xmax_included = False + xmin = -Infinity + xmin_included = False + xmax = +Infinity + xmax_included = False # scan of the properties other than the symbol: is_periodic = False for prop in coord_properties[1:]: @@ -2912,7 +2914,7 @@ def _plot_xx_list(xx_list, rem_coords, ranges, steps, number_values): first_invalid = False # next invalid point will not # be the first one xc += dx - if curve != []: + if curve: resu += line(curve, color=color_c, linestyle=style_c, thickness=thickness_c) diff --git a/src/sage/manifolds/chart_func.py b/src/sage/manifolds/chart_func.py index 9874cbdc9ca..97eaff61f8d 100644 --- a/src/sage/manifolds/chart_func.py +++ b/src/sage/manifolds/chart_func.py @@ -912,7 +912,7 @@ def copy(self): resu._order = self._order return resu - def diff(self, coord): + def derivative(self, coord): r""" Partial derivative with respect to a coordinate. @@ -937,28 +937,38 @@ def diff(self, coord): sage: X. = M.chart(calc_method='SR') sage: f = X.function(x^2+3*y+1); f x^2 + 3*y + 1 - sage: f.diff(x) + sage: f.derivative(x) 2*x - sage: f.diff(y) + sage: f.derivative(y) 3 - Each partial derivatives is itself a chart function:: + An alias is ``diff``:: + + sage: f.diff(x) + 2*x + + Each partial derivative is itself a chart function:: sage: type(f.diff(x)) + The same result is returned by the function ``diff``:: + + sage: diff(f, x) + 2*x + An index can be used instead of the coordinate symbol:: sage: f.diff(0) 2*x - sage: f.diff(1) + sage: diff(f, 1) 3 The index range depends on the convention used on the chart's domain:: sage: M = Manifold(2, 'M', structure='topological', start_index=1) - sage: X. = M.chart(calc_method='sympy') - sage: f = X.function(x**2+3*y+1) + sage: X. = M.chart() + sage: f = X.function(x^2+3*y+1) sage: f.diff(0) Traceback (most recent call last): ... @@ -1007,6 +1017,8 @@ def diff(self, coord): else: return self._der[self._chart[:].index(coord)] + diff = derivative + def __eq__(self, other): r""" Comparison (equality) operator. diff --git a/src/sage/manifolds/differentiable/affine_connection.py b/src/sage/manifolds/differentiable/affine_connection.py index 3cb22403aa3..5cb8d5a6f96 100644 --- a/src/sage/manifolds/differentiable/affine_connection.py +++ b/src/sage/manifolds/differentiable/affine_connection.py @@ -1450,7 +1450,7 @@ def _derive_paral(self, tensor): # Component computation in the common frame: tc = tensor._components[frame] gam = self._coefficients[frame] - if tensor._sym == [] and tensor._antisym == []: + if not tensor._sym and not tensor._antisym: resc = Components(tdom.scalar_field_algebra(), frame, tensor._tensor_rank+1, start_index=self._domain._sindex, diff --git a/src/sage/manifolds/differentiable/characteristic_class.py b/src/sage/manifolds/differentiable/characteristic_class.py index 34f5754a8d4..da2e2299430 100644 --- a/src/sage/manifolds/differentiable/characteristic_class.py +++ b/src/sage/manifolds/differentiable/characteristic_class.py @@ -255,7 +255,7 @@ sage: g[eV,1,1], g[eV,2,2] = 4/(1+u^2+v^2)^2, 4/(1+u^2+v^2)^2 sage: nab = g.connection() -In case of the the Euler class, skew-symmetric curvature matrices are needed +In case of the Euler class, skew-symmetric curvature matrices are needed for the Pfaffian. For this, we need to define the curvature matrices by hand:: diff --git a/src/sage/manifolds/differentiable/diff_form.py b/src/sage/manifolds/differentiable/diff_form.py index 56a11581cd1..417183adf62 100644 --- a/src/sage/manifolds/differentiable/diff_form.py +++ b/src/sage/manifolds/differentiable/diff_form.py @@ -163,6 +163,12 @@ class DiffForm(TensorField): sage: da.display(eV) da = -du/\dv + The exterior derivative can also be obtained by applying the function + ``diff`` to a differentiable form:: + + sage: diff(a) is a.exterior_derivative() + True + Another 1-form defined by its components in ``eU``:: sage: b = M.one_form(1+x*y, x^2, frame=eU, name='b') @@ -422,19 +428,16 @@ def exterior_derivative(self): True Instead of invoking the method :meth:`exterior_derivative`, one may - use the global function - :func:`~sage.manifolds.utilities.exterior_derivative` - or its alias :func:`~sage.manifolds.utilities.xder`:: + use the global function ``diff``:: - sage: from sage.manifolds.utilities import xder - sage: xder(a) is a.exterior_derivative() + sage: diff(a) is a.exterior_derivative() True Let us check Cartan's identity:: sage: v = M.vector_field({e_xy: [-y, x]}, name='v') sage: v.add_comp_by_continuation(e_uv, U.intersection(V), c_uv) - sage: a.lie_der(v) == v.contract(xder(a)) + xder(a(v)) # long time + sage: a.lie_der(v) == v.contract(diff(a)) + diff(a(v)) # long time True """ @@ -449,6 +452,9 @@ def exterior_derivative(self): resu._restrictions[dom] = rst.exterior_derivative() return resu + derivative = exterior_derivative # allows one to use functional notation, + # e.g. diff(a) for a.exterior_derivative() + def wedge(self, other): r""" Exterior product with another differential form. @@ -1046,7 +1052,7 @@ class DiffFormParal(FreeModuleAltForm, TensorFieldParal): no symmetry; antisymmetry: (0, 1) The exterior derivative of a differential form is obtained by means - of the :meth:`exterior_derivative`:: + of the method :meth:`exterior_derivative`:: sage: da = a.exterior_derivative() ; da 2-form dA on the 3-dimensional differentiable manifold R3 @@ -1059,6 +1065,11 @@ class DiffFormParal(FreeModuleAltForm, TensorFieldParal): sage: dab = ab.exterior_derivative() ; dab 3-form d(A/\B) on the 3-dimensional differentiable manifold R3 + or by applying the function ``diff`` to the differential form:: + + sage: diff(a) is a.exterior_derivative() + True + As a 3-form over a 3-dimensional manifold, ``d(A/\B)`` is necessarily proportional to the volume 3-form:: @@ -1319,12 +1330,9 @@ def exterior_derivative(self): True Instead of invoking the method :meth:`exterior_derivative`, one may - use the global function - :func:`~sage.manifolds.utilities.exterior_derivative` - or its alias :func:`~sage.manifolds.utilities.xder`:: + use the global function ``diff``:: - sage: from sage.manifolds.utilities import xder - sage: xder(a) is a.exterior_derivative() + sage: diff(a) is a.exterior_derivative() True The exterior derivative is nilpotent:: @@ -1339,7 +1347,7 @@ def exterior_derivative(self): Let us check Cartan's identity:: sage: v = M.vector_field(-y, x, t, z, name='v') - sage: a.lie_der(v) == v.contract(xder(a)) + xder(a(v)) # long time + sage: a.lie_der(v) == v.contract(diff(a)) + diff(a(v)) # long time True """ @@ -1399,6 +1407,9 @@ def exterior_derivative(self): resu._components[frame] = dc return resu + derivative = exterior_derivative # allows one to use functional notation, + # e.g. diff(a) for a.exterior_derivative() + def wedge(self, other): r""" Exterior product of ``self`` with another differential form. diff --git a/src/sage/manifolds/differentiable/scalarfield.py b/src/sage/manifolds/differentiable/scalarfield.py index 3116731150e..e8f7ae76a19 100644 --- a/src/sage/manifolds/differentiable/scalarfield.py +++ b/src/sage/manifolds/differentiable/scalarfield.py @@ -739,6 +739,12 @@ def differential(self): sage: f.differential() is df True + Instead of invoking the method :meth:`differential`, one may apply the + function ``diff`` to the scalar field:: + + sage: diff(f) is f.differential() + True + Since the exterior derivative of a scalar field (considered a 0-form) is nothing but its differential, ``exterior_derivative()`` is an alias of ``differential()``:: @@ -750,15 +756,6 @@ def differential(self): sage: latex(df) \mathrm{d}f - One may also use the function - :func:`~sage.manifolds.utilities.exterior_derivative` - or its alias :func:`~sage.manifolds.utilities.xder` instead - of the method ``exterior_derivative()``:: - - sage: from sage.manifolds.utilities import xder - sage: xder(f) is f.exterior_derivative() - True - Differential computed on a chart that is not the default one:: sage: c_uvw. = M.chart() @@ -808,7 +805,10 @@ def differential(self): diff_func[i, chart] = func.diff(i) return self._differential - exterior_derivative = differential + exterior_derivative = differential # a scalar field being a 0-form + derivative = differential # allows one to use functional notation, + # e.g. diff(f) for f.differential() + def lie_derivative(self, vector): r""" diff --git a/src/sage/manifolds/differentiable/vector_bundle.py b/src/sage/manifolds/differentiable/vector_bundle.py index a06bdfa987f..b0b83c41e21 100644 --- a/src/sage/manifolds/differentiable/vector_bundle.py +++ b/src/sage/manifolds/differentiable/vector_bundle.py @@ -1461,7 +1461,7 @@ def ambient_domain(self): def destination_map(self): r""" - Return the the destination map. + Return the destination map. OUTPUT: diff --git a/src/sage/manifolds/scalarfield.py b/src/sage/manifolds/scalarfield.py index 6cf8a987cd6..edb52ecd954 100644 --- a/src/sage/manifolds/scalarfield.py +++ b/src/sage/manifolds/scalarfield.py @@ -2262,7 +2262,7 @@ def common_charts(self, other): if (chart2, chart1) in coord_changes: self.coord_function(chart2, from_chart=chart1) resu.append(chart2) - if resu == []: + if not resu: return None else: return resu @@ -2689,7 +2689,7 @@ def _lmul_(self, number): var_not_in_chart = [s for s in var if not s in chart_coords] any_in_other_chart = False - if var_not_in_chart != []: + if var_not_in_chart: for other_chart in self._domain.atlas(): other_chart_coords = other_chart[:] for s in var_not_in_chart: diff --git a/src/sage/matrix/matrix0.pyx b/src/sage/matrix/matrix0.pyx index b1c0ed38ee6..82ed0f86085 100644 --- a/src/sage/matrix/matrix0.pyx +++ b/src/sage/matrix/matrix0.pyx @@ -566,7 +566,8 @@ cdef class Matrix(sage.structure.element.Matrix): ## This function it can very easily !! SEG FAULT !! if you call ## it with invalid input. Use with *extreme* caution. -## EXAMPLES: +## EXAMPLES:: +## ## sage: a = matrix(ZZ,2,range(4)) ## sage: a._get_very_unsafe(0,1) ## 1 diff --git a/src/sage/matrix/matrix_cyclo_dense.pyx b/src/sage/matrix/matrix_cyclo_dense.pyx index f930c329751..b255f8bcc7e 100644 --- a/src/sage/matrix/matrix_cyclo_dense.pyx +++ b/src/sage/matrix/matrix_cyclo_dense.pyx @@ -758,15 +758,16 @@ cdef class Matrix_cyclo_dense(Matrix_dense): EXAMPLES: - We create a cyclotomic matrix.:: + We create a cyclotomic matrix:: sage: W. = CyclotomicField(5) sage: A = matrix(W, 2, 2, [1,2/3*z+z^2,-z,1+z/2]) - We make a copy of A.:: + We make a copy of A:: + sage: C = A.__copy__() - We make another reference to A.:: + We make another reference to A:: sage: B = A @@ -776,7 +777,7 @@ cdef class Matrix_cyclo_dense(Matrix_dense): sage: A[0,0] 10 - Changing the copy does not change A.:: + Changing the copy does not change A:: sage: C[0,0] = 20 sage: C[0,0] @@ -1508,6 +1509,7 @@ cdef class Matrix_cyclo_dense(Matrix_dense): [4, 9, 1, 3] The reduction matrix is cached:: + sage: w._reduction_matrix(7) is w._reduction_matrix(7) True """ diff --git a/src/sage/matrix/matrix_gfpn_dense.pyx b/src/sage/matrix/matrix_gfpn_dense.pyx index 0735177b1bb..b62dc4c685c 100644 --- a/src/sage/matrix/matrix_gfpn_dense.pyx +++ b/src/sage/matrix/matrix_gfpn_dense.pyx @@ -1408,7 +1408,7 @@ cdef class Matrix_gfpn_dense(Matrix_dense): sig_off() return new_mtx(mat, self) - def __div__(Matrix_gfpn_dense self, p): + def __truediv__(Matrix_gfpn_dense self, p): """ Divide a matrix by a scalar. diff --git a/src/sage/matrix/matrix_modn_sparse.pyx b/src/sage/matrix/matrix_modn_sparse.pyx index 52e846c4e94..34c72d7418b 100644 --- a/src/sage/matrix/matrix_modn_sparse.pyx +++ b/src/sage/matrix/matrix_modn_sparse.pyx @@ -482,17 +482,17 @@ cdef class Matrix_modn_sparse(matrix_sparse.Matrix_sparse): def _nonzero_positions_by_row(self, copy=True): """ - Returns the list of pairs (i,j) such that self[i,j] != 0. + Return the list of pairs (i,j) such that self[i,j] != 0. It is safe to change the resulting list (unless you give the option copy=False). EXAMPLES:: + sage: M = Matrix(GF(7), [[0,0,0,1,0,0,0,0],[0,1,0,0,0,0,1,0]], sparse=True); M [0 0 0 1 0 0 0 0] [0 1 0 0 0 0 1 0] sage: M.nonzero_positions() [(0, 3), (1, 1), (1, 6)] - """ x = self.fetch('nonzero_positions') if not x is None: diff --git a/src/sage/matrix/matrix_rational_dense.pyx b/src/sage/matrix/matrix_rational_dense.pyx index 9296e1b8698..0b74bde1e6a 100644 --- a/src/sage/matrix/matrix_rational_dense.pyx +++ b/src/sage/matrix/matrix_rational_dense.pyx @@ -2669,6 +2669,7 @@ cdef class Matrix_rational_dense(Matrix_dense): Return the determinant of this matrix computed using pari. EXAMPLES:: + sage: matrix(QQ,3,[1..9])._det_pari() 0 sage: matrix(QQ,3,[1..9])._det_pari(1) diff --git a/src/sage/matrix/matrix_rational_sparse.pyx b/src/sage/matrix/matrix_rational_sparse.pyx index 23f1e147626..79e626b6923 100644 --- a/src/sage/matrix/matrix_rational_sparse.pyx +++ b/src/sage/matrix/matrix_rational_sparse.pyx @@ -285,7 +285,8 @@ cdef class Matrix_rational_sparse(Matrix_sparse): # TODO ## cpdef _lmul_(self, Element right): ## """ -## EXAMPLES: +## EXAMPLES:: +## ## sage: a = matrix(QQ,2,range(6)) ## sage: (3/4) * a ## [ 0 3/4 3/2] diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index d274d9eec53..6c8e736e80b 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -3784,18 +3784,6 @@ cdef class Matroid(SageObject): """ return self.minor(contractions=X) - def __div__(self, X): - r""" - Shorthand for ``self.contract(X)``. - - EXAMPLES:: - - sage: M = matroids.CompleteGraphic(4) - sage: M.contract(1) == M / 1 # indirect doctest - True - """ - return self.contract(X) - def __truediv__(self, X): r""" Shorthand for ``self.contract(X)``. diff --git a/src/sage/misc/all.py b/src/sage/misc/all.py index 1eb98169cdc..61090c25111 100644 --- a/src/sage/misc/all.py +++ b/src/sage/misc/all.py @@ -8,7 +8,6 @@ union, uniq, powerset, subsets, exists, forall, is_iterator, random_sublist, walltime, - repr_lincomb, pad_zeros, SAGE_DB, SAGE_TMP, newton_method_sizes, compose, @@ -28,6 +27,8 @@ from .html import html +from .repr import repr_lincomb + from .table import table from .sage_timeit_class import timeit diff --git a/src/sage/misc/call.py b/src/sage/misc/call.py index 5b259b8f5a2..ddb05e44819 100644 --- a/src/sage/misc/call.py +++ b/src/sage/misc/call.py @@ -141,17 +141,17 @@ def __hash__(self): def attrcall(name, *args, **kwds): """ - Returns a callable which takes in an object, gets the method named + Return a callable which takes in an object, gets the method named name from that object, and calls it with the specified arguments and keywords. INPUT: - - ``name`` - a string of the name of the method you - want to call + - ``name`` - a string of the name of the method you + want to call - - ``args, kwds`` - arguments and keywords to be passed - to the method + - ``args, kwds`` - arguments and keywords to be passed + to the method EXAMPLES:: diff --git a/src/sage/misc/functional.py b/src/sage/misc/functional.py index cd190ab53fd..8eb93e7c743 100644 --- a/src/sage/misc/functional.py +++ b/src/sage/misc/functional.py @@ -127,8 +127,8 @@ def category(x): try: return x.category() except AttributeError: - import sage.categories.all - return sage.categories.all.Objects() + from sage.categories.objects import Objects + return Objects() def characteristic_polynomial(x, var='x'): diff --git a/src/sage/misc/lazy_import.pyx b/src/sage/misc/lazy_import.pyx index 6e2c6624760..f759d953d98 100644 --- a/src/sage/misc/lazy_import.pyx +++ b/src/sage/misc/lazy_import.pyx @@ -640,19 +640,6 @@ cdef class LazyImport(object): """ return obj(left) @ obj(right) - def __div__(left, right): - """ - TESTS:: - - sage: sage.all.foo = 10 - sage: lazy_import('sage.all', 'foo') - sage: type(foo) - - sage: foo / 2 - 5 - """ - return obj(left) / obj(right) - def __floordiv__(left, right): """ TESTS:: diff --git a/src/sage/misc/misc.py b/src/sage/misc/misc.py index 1583f4d8626..0b979bcf7de 100644 --- a/src/sage/misc/misc.py +++ b/src/sage/misc/misc.py @@ -54,6 +54,9 @@ lazy_import("sage.misc.call", ["AttrCallObject", "attrcall", "call_method"], deprecation=29869) +lazy_import("sage.misc.repr", ["coeff_repr", "repr_lincomb"], + deprecation=29892) + from sage.env import DOT_SAGE, HOSTNAME LOCAL_IDENTIFIER = '%s.%s' % (HOSTNAME, os.getpid()) @@ -775,173 +778,6 @@ def exactly_one_is_true(iterable): return any(it) and not any(it) -def coeff_repr(c, is_latex=False): - if not is_latex: - try: - return c._coeff_repr() - except AttributeError: - pass - if isinstance(c, (int, float)): - return str(c) - if is_latex and hasattr(c, '_latex_'): - s = c._latex_() - else: - s = str(c).replace(' ', '') - if s.find("+") != -1 or s.find("-") != -1: - if is_latex: - return "\\left(%s\\right)" % s - else: - return "(%s)" % s - return s - - -def repr_lincomb(terms, is_latex=False, scalar_mult="*", strip_one=False, - repr_monomial=None, latex_scalar_mult=None): - """ - Compute a string representation of a linear combination of some - formal symbols. - - INPUT: - - - ``terms`` -- list of terms, as pairs (support, coefficient) - - ``is_latex`` -- whether to produce latex (default: ``False``) - - ``scalar_mult`` -- string representing the multiplication (default:``'*'``) - - ``latex_scalar_mult`` -- latex string representing the multiplication - (default: ``''`` if ``scalar_mult`` is ``'*'``; otherwise ``scalar_mult``) - - ``coeffs`` -- for backward compatibility - - OUTPUT: - - - ``str`` - a string - - EXAMPLES:: - - sage: repr_lincomb([('a',1), ('b',-2), ('c',3)]) - 'a - 2*b + 3*c' - sage: repr_lincomb([('a',0), ('b',-2), ('c',3)]) - '-2*b + 3*c' - sage: repr_lincomb([('a',0), ('b',2), ('c',3)]) - '2*b + 3*c' - sage: repr_lincomb([('a',1), ('b',0), ('c',3)]) - 'a + 3*c' - sage: repr_lincomb([('a',-1), ('b','2+3*x'), ('c',3)]) - '-a + (2+3*x)*b + 3*c' - sage: repr_lincomb([('a', '1+x^2'), ('b', '2+3*x'), ('c', 3)]) - '(1+x^2)*a + (2+3*x)*b + 3*c' - sage: repr_lincomb([('a', '1+x^2'), ('b', '-2+3*x'), ('c', 3)]) - '(1+x^2)*a + (-2+3*x)*b + 3*c' - sage: repr_lincomb([('a', 1), ('b', -2), ('c', -3)]) - 'a - 2*b - 3*c' - sage: t = PolynomialRing(RationalField(),'t').gen() - sage: repr_lincomb([('a', -t), ('s', t - 2), ('', t^2 + 2)]) - '-t*a + (t-2)*s + (t^2+2)' - - Examples for ``scalar_mult``:: - - sage: repr_lincomb([('a',1), ('b',2), ('c',3)], scalar_mult='*') - 'a + 2*b + 3*c' - sage: repr_lincomb([('a',2), ('b',0), ('c',-3)], scalar_mult='**') - '2**a - 3**c' - sage: repr_lincomb([('a',-1), ('b',2), ('c',3)], scalar_mult='**') - '-a + 2**b + 3**c' - - Examples for ``scalar_mult`` and ``is_latex``:: - - sage: repr_lincomb([('a',-1), ('b',2), ('c',3)], is_latex=True) - '-a + 2b + 3c' - sage: repr_lincomb([('a',-1), ('b',-1), ('c',3)], is_latex=True, scalar_mult='*') - '-a - b + 3c' - sage: repr_lincomb([('a',-1), ('b',2), ('c',-3)], is_latex=True, scalar_mult='**') - '-a + 2**b - 3**c' - sage: repr_lincomb([('a',-2), ('b',-1), ('c',-3)], is_latex=True, latex_scalar_mult='*') - '-2*a - b - 3*c' - - Examples for ``strip_one``:: - - sage: repr_lincomb([ ('a',1), (1,-2), ('3',3) ]) - 'a - 2*1 + 3*3' - sage: repr_lincomb([ ('a',-1), (1,1), ('3',3) ]) - '-a + 1 + 3*3' - sage: repr_lincomb([ ('a',1), (1,-2), ('3',3) ], strip_one = True) - 'a - 2 + 3*3' - sage: repr_lincomb([ ('a',-1), (1,1), ('3',3) ], strip_one = True) - '-a + 1 + 3*3' - sage: repr_lincomb([ ('a',1), (1,-1), ('3',3) ], strip_one = True) - 'a - 1 + 3*3' - - Examples for ``repr_monomial``:: - - sage: repr_lincomb([('a',1), ('b',2), ('c',3)], repr_monomial = lambda s: s+"1") - 'a1 + 2*b1 + 3*c1' - """ - # Setting scalar_mult: symbol used for scalar multiplication - if is_latex: - if latex_scalar_mult is not None: - scalar_mult = latex_scalar_mult - elif scalar_mult == "*": - scalar_mult = "" - - if repr_monomial is None: - if is_latex: - - def repr_monomial(monomial): - return monomial._latex_() if hasattr(monomial, '_latex_') else str(monomial) - else: - repr_monomial = str - - s = "" - first = True - - if scalar_mult is None: - scalar_mult = "" if is_latex else "*" - - for (monomial, c) in terms: - if c != 0: - coeff = coeff_repr(c) - negative = False - if len(coeff) and coeff[0] == "-": - negative = True - try: - if c < 0: - negative = True - except (NotImplementedError, TypeError): - # comparisons may not be implemented for some coefficients - pass - if negative: - coeff = coeff_repr(-c, is_latex) - else: - coeff = coeff_repr(c, is_latex) - if coeff == "1": - coeff = "" - if coeff != "0": - if negative: - if first: - sign = "-" # add trailing space? - else: - sign = " - " - else: - if first: - sign = "" - else: - sign = " + " - b = repr_monomial(monomial) - if len(b): - if coeff != "": - if b == "1" and strip_one: - b = "" - else: - b = scalar_mult + b - s += "%s%s%s" % (sign, coeff, b) - first = False - if first: - return "0" - # this can happen only if are only terms with coeff_repr(c) == "0" - # elif s == "": - # return "1" # is empty string representation invalid? - else: - return s - - def strunc(s, n=60): """ Truncate at first space after position n, adding '...' if diff --git a/src/sage/misc/nested_class_test.py b/src/sage/misc/nested_class_test.py index 27273ad5c69..61e29f46d46 100644 --- a/src/sage/misc/nested_class_test.py +++ b/src/sage/misc/nested_class_test.py @@ -63,7 +63,7 @@ def __init__(self): sage: sage.misc.nested_class_test.TestParent1() """ - from sage.categories.all import Sets + from sage.categories.sets_cat import Sets Parent.__init__(self, category = Sets()) class Element(ElementWrapper): @@ -80,7 +80,7 @@ def __init__(self): ... TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases """ - from sage.categories.all import Sets + from sage.categories.sets_cat import Sets Parent.__init__(self, category = Sets()) class Element(ElementWrapper): @@ -96,7 +96,7 @@ def __init__(self): sage: sage.misc.nested_class_test.TestParent3() """ - from sage.categories.all import Sets + from sage.categories.sets_cat import Sets Parent.__init__(self, category = Sets()) class Element(ElementWrapper): @@ -111,7 +111,7 @@ def __init__(self): sage: sage.misc.nested_class_test.TestParent4() """ - from sage.categories.all import Sets + from sage.categories.sets_cat import Sets Parent.__init__(self, category=Sets()) def __eq__(self, other): diff --git a/src/sage/misc/repr.py b/src/sage/misc/repr.py new file mode 100644 index 00000000000..a65fd7d9269 --- /dev/null +++ b/src/sage/misc/repr.py @@ -0,0 +1,193 @@ +""" +Repr formatting support +""" + + +def coeff_repr(c, is_latex=False): + r""" + String representing coefficients in a linear combination. + + INPUT: + + - ``c`` -- a coefficient (i.e., an element of a ring) + + OUTPUT: + + A string + + EXAMPLES:: + + sage: from sage.misc.repr import coeff_repr + sage: coeff_repr(QQ(1/2)) + '1/2' + sage: coeff_repr(-x^2) + '(-x^2)' + sage: coeff_repr(QQ(1/2), is_latex=True) + '\\frac{1}{2}' + sage: coeff_repr(-x^2, is_latex=True) + '\\left(-x^{2}\\right)' + """ + if not is_latex: + try: + return c._coeff_repr() + except AttributeError: + pass + if isinstance(c, (int, float)): + return str(c) + if is_latex and hasattr(c, '_latex_'): + s = c._latex_() + else: + s = str(c).replace(' ', '') + if s.find("+") != -1 or s.find("-") != -1: + if is_latex: + return "\\left(%s\\right)" % s + else: + return "(%s)" % s + return s + + +def repr_lincomb(terms, is_latex=False, scalar_mult="*", strip_one=False, + repr_monomial=None, latex_scalar_mult=None): + """ + Compute a string representation of a linear combination of some + formal symbols. + + INPUT: + + - ``terms`` -- list of terms, as pairs (support, coefficient) + - ``is_latex`` -- whether to produce latex (default: ``False``) + - ``scalar_mult`` -- string representing the multiplication (default:``'*'``) + - ``latex_scalar_mult`` -- latex string representing the multiplication + (default: ``''`` if ``scalar_mult`` is ``'*'``; otherwise ``scalar_mult``) + - ``coeffs`` -- for backward compatibility + + OUTPUT: + + - ``str`` - a string + + EXAMPLES:: + + sage: repr_lincomb([('a',1), ('b',-2), ('c',3)]) + 'a - 2*b + 3*c' + sage: repr_lincomb([('a',0), ('b',-2), ('c',3)]) + '-2*b + 3*c' + sage: repr_lincomb([('a',0), ('b',2), ('c',3)]) + '2*b + 3*c' + sage: repr_lincomb([('a',1), ('b',0), ('c',3)]) + 'a + 3*c' + sage: repr_lincomb([('a',-1), ('b','2+3*x'), ('c',3)]) + '-a + (2+3*x)*b + 3*c' + sage: repr_lincomb([('a', '1+x^2'), ('b', '2+3*x'), ('c', 3)]) + '(1+x^2)*a + (2+3*x)*b + 3*c' + sage: repr_lincomb([('a', '1+x^2'), ('b', '-2+3*x'), ('c', 3)]) + '(1+x^2)*a + (-2+3*x)*b + 3*c' + sage: repr_lincomb([('a', 1), ('b', -2), ('c', -3)]) + 'a - 2*b - 3*c' + sage: t = PolynomialRing(RationalField(),'t').gen() + sage: repr_lincomb([('a', -t), ('s', t - 2), ('', t^2 + 2)]) + '-t*a + (t-2)*s + (t^2+2)' + + Examples for ``scalar_mult``:: + + sage: repr_lincomb([('a',1), ('b',2), ('c',3)], scalar_mult='*') + 'a + 2*b + 3*c' + sage: repr_lincomb([('a',2), ('b',0), ('c',-3)], scalar_mult='**') + '2**a - 3**c' + sage: repr_lincomb([('a',-1), ('b',2), ('c',3)], scalar_mult='**') + '-a + 2**b + 3**c' + + Examples for ``scalar_mult`` and ``is_latex``:: + + sage: repr_lincomb([('a',-1), ('b',2), ('c',3)], is_latex=True) + '-a + 2b + 3c' + sage: repr_lincomb([('a',-1), ('b',-1), ('c',3)], is_latex=True, scalar_mult='*') + '-a - b + 3c' + sage: repr_lincomb([('a',-1), ('b',2), ('c',-3)], is_latex=True, scalar_mult='**') + '-a + 2**b - 3**c' + sage: repr_lincomb([('a',-2), ('b',-1), ('c',-3)], is_latex=True, latex_scalar_mult='*') + '-2*a - b - 3*c' + + Examples for ``strip_one``:: + + sage: repr_lincomb([ ('a',1), (1,-2), ('3',3) ]) + 'a - 2*1 + 3*3' + sage: repr_lincomb([ ('a',-1), (1,1), ('3',3) ]) + '-a + 1 + 3*3' + sage: repr_lincomb([ ('a',1), (1,-2), ('3',3) ], strip_one = True) + 'a - 2 + 3*3' + sage: repr_lincomb([ ('a',-1), (1,1), ('3',3) ], strip_one = True) + '-a + 1 + 3*3' + sage: repr_lincomb([ ('a',1), (1,-1), ('3',3) ], strip_one = True) + 'a - 1 + 3*3' + + Examples for ``repr_monomial``:: + + sage: repr_lincomb([('a',1), ('b',2), ('c',3)], repr_monomial = lambda s: s+"1") + 'a1 + 2*b1 + 3*c1' + """ + # Setting scalar_mult: symbol used for scalar multiplication + if is_latex: + if latex_scalar_mult is not None: + scalar_mult = latex_scalar_mult + elif scalar_mult == "*": + scalar_mult = "" + + if repr_monomial is None: + if is_latex: + + def repr_monomial(monomial): + return monomial._latex_() if hasattr(monomial, '_latex_') else str(monomial) + else: + repr_monomial = str + + s = "" + first = True + + if scalar_mult is None: + scalar_mult = "" if is_latex else "*" + + for (monomial, c) in terms: + if c != 0: + coeff = coeff_repr(c) + negative = False + if len(coeff) and coeff[0] == "-": + negative = True + try: + if c < 0: + negative = True + except (NotImplementedError, TypeError): + # comparisons may not be implemented for some coefficients + pass + if negative: + coeff = coeff_repr(-c, is_latex) + else: + coeff = coeff_repr(c, is_latex) + if coeff == "1": + coeff = "" + if coeff != "0": + if negative: + if first: + sign = "-" # add trailing space? + else: + sign = " - " + else: + if first: + sign = "" + else: + sign = " + " + b = repr_monomial(monomial) + if len(b): + if coeff != "": + if b == "1" and strip_one: + b = "" + else: + b = scalar_mult + b + s += "%s%s%s" % (sign, coeff, b) + first = False + if first: + return "0" + # this can happen only if are only terms with coeff_repr(c) == "0" + # elif s == "": + # return "1" # is empty string representation invalid? + else: + return s diff --git a/src/sage/modular/dirichlet.py b/src/sage/modular/dirichlet.py index 293f439d002..0313f303357 100644 --- a/src/sage/modular/dirichlet.py +++ b/src/sage/modular/dirichlet.py @@ -1342,34 +1342,49 @@ def kloosterman_sum(self, a=1, b=0): sage: e.kloosterman_sum(3,5) -2*zeta6 + 1 sage: G = DirichletGroup(20) - sage: e = G([1 for u in G.unit_gens()]) + sage: e = G([1 for u in G.unit_gens()]) sage: e.kloosterman_sum(7,17) -2*zeta20^6 + 2*zeta20^4 + 4 + TESTS:: + + sage: G = DirichletGroup(20, UniversalCyclotomicField()) + sage: e = G([1 for u in G.unit_gens()]) + sage: e.kloosterman_sum(7,17) + -2*E(5) - 4*E(5)^2 - 4*E(5)^3 - 2*E(5)^4 + + sage: G = DirichletGroup(12, QQbar) + sage: e = G.gens()[0] + sage: e.kloosterman_sum(5,11) + Traceback (most recent call last): + ... + NotImplementedError: Kloosterman sums not implemented over this ring """ G = self.parent() - K = G.base_ring() - if not (number_field.is_CyclotomicField(K) or is_RationalField(K)): - raise NotImplementedError("Kloosterman sums only currently implemented when the base ring is a cyclotomic field or QQ.") - g = 0 + zo = G.zeta_order() m = G.modulus() - L = rings.CyclotomicField(lcm(m,G.zeta_order())) + g = 0 + L = rings.CyclotomicField(m.lcm(zo)) zeta = L.gen(0) + try: + self(1) * zeta**(a+b) + except TypeError: + raise NotImplementedError('Kloosterman sums not implemented ' + 'over this ring') n = zeta.multiplicative_order() - zeta = zeta ** (n // m) - for c in range(1,m): - if gcd(c,m)==1: - e = rings.Mod(c,m) - z = zeta ** int(a*e + b*(e**(-1))) - g += self(c)*z + zeta = zeta**(n // m) + for c in m.coprime_integers(m): + e = rings.Mod(c, m) + g += self(c) * zeta**int(a*e + b*e**(-1)) return g - def kloosterman_sum_numerical(self, prec=53, a=1,b=0): + def kloosterman_sum_numerical(self, prec=53, a=1, b=0): r""" Return the Kloosterman sum associated to this Dirichlet character as - an approximate complex number with prec bits of precision. See also - :meth:`.kloosterman_sum`, which calculates the sum exactly (which is - generally slower). + an approximate complex number with prec bits of precision. + + See also :meth:`.kloosterman_sum`, which calculates the sum + exactly (which is generally slower). INPUT: @@ -1403,12 +1418,10 @@ def kloosterman_sum_numerical(self, prec=53, a=1,b=0): g = 0 m = G.modulus() zeta = CC.zeta(m) - - for c in range(1,m): - if gcd(c,m)==1: - e = rings.Mod(c,m) - z = zeta ** int(a*e + b*(e**(-1))) - g += phi(self(c))*z + for c in m.coprime_integers(m): + e = rings.Mod(c, m) + z = zeta ** int(a*e + b*(e**(-1))) + g += phi(self(c))*z return g @cached_method diff --git a/src/sage/modular/hypergeometric_misc.pxd b/src/sage/modular/hypergeometric_misc.pxd index 00bf9a97e9a..d031601666f 100644 --- a/src/sage/modular/hypergeometric_misc.pxd +++ b/src/sage/modular/hypergeometric_misc.pxd @@ -1,2 +1,3 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, gtable, int gtable_prec, bint use_longs) + diff --git a/src/sage/modular/hypergeometric_misc.pyx b/src/sage/modular/hypergeometric_misc.pyx index 93e2eb34257..0faa55e3701 100644 --- a/src/sage/modular/hypergeometric_misc.pyx +++ b/src/sage/modular/hypergeometric_misc.pyx @@ -3,6 +3,7 @@ Some utility routines for the hypergeometric motives package that benefit significantly from Cythonization. """ from cpython cimport array +from cysignals.signals cimport sig_check cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, gtable, int gtable_prec, bint use_longs): @@ -14,16 +15,14 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, TESTS:: sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp - sage: import array sage: from sage.modular.hypergeometric_misc import hgm_coeffs sage: H = Hyp(cyclotomic=([3],[4])) sage: H.euler_factor(2, 7, cache_p=True) 7*T^2 - 3*T + 1 sage: gamma = H.gamma_array() sage: prec, gtable = H.gauss_table(7, 1, 2) - sage: m = array.array('i', [0]*6) sage: D = 1 - sage: hgm_coeffs(7, 1, 2, gamma, m, D, gtable, prec, False) + sage: hgm_coeffs(7, 1, 2, gamma, [0]*6, D, gtable, prec, False) [7, 2*7, 6*7, 7, 6, 4*7] """ from sage.rings.padics.factory import Zp @@ -36,7 +35,7 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, gl = len(gamma) cdef array.array gamma_array1 = array.array('i', gamma.keys()) cdef array.array gamma_array2 = array.array('i', gamma.values()) - cdef array.array r_array = array.array('i', [0]) * gl + r_array = [0] * gl cdef array.array digit_count = array.array('i', [0]) * q1 cdef array.array gtab2 @@ -62,13 +61,19 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, else: for r in range(q1): r1 = r - digit_count[r] = 0 + w = 0 for i in range(f): - digit_count[r] += r1 % p + w += r1 % p r1 //= p + digit_count[r] = w Rz = R.zero() + ans = [None] * q1 for r in range(q1): - # First determine whether this term is forced to be zero + sig_check() + # Skip this coefficient if we already have it by symmetry. + if ans[r] is not None: + continue + # Determine whether this term is forced to be zero # for divisibility reasons. If so, skip the p-adic arithmetic. i = 0 for k in range(gl): @@ -80,34 +85,54 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, i //= (p - 1) l = i + f * (D + m[0] - m[r]) if l >= prec: - ans.append(Rz) - continue - # Keep numerator and denominator separate for efficiency. - if use_longs: - w = 1 - w1 = 1 + ans[r] = Rz else: - u = R.one() - u1 = R.one() - for k in range(gl): - gv = gamma_array2[k] - r1 = r_array[k] - if flip: - gv = -gv + # Keep numerator and denominator separate for efficiency. if use_longs: - w2 = gtab2[r1] # cast to long long to avoid overflow - if gv > 0: - for j in range(gv): w = w * w2 % q2 - else: - for j in range(-gv): w1 = w1 * w2 % q2 + w = 1 + w1 = 1 else: - if gv > 0: - for j in range(gv): u *= gtable[r1] + u = R.one() + u1 = R.one() + for k in range(gl): + gv = gamma_array2[k] + r1 = r_array[k] + if flip: + gv = -gv + if use_longs: + w2 = gtab2[r1] # cast to long long to avoid overflow + if gv > 0: + for j in range(gv): + w = w * w2 % q2 + else: + for j in range(-gv): + w1 = w1 * w2 % q2 else: - for j in range(-gv): u1 *= gtable[r1] - if use_longs: - u = R(w) - u1 = R(w1) - if i % 2: u = -u - ans.append((u / u1) << l) - return ans + w2 = gtable[r1] + if gv > 0: + for j in range(gv): + u *= w2 + else: + for j in range(-gv): + u1 *= w2 + if use_longs: + u = R(w) + u1 = R(w1) + if i % 2: + u = -u + ans[r] = (u / u1) << l + if f > 1: + r1 = r + for j in range(f-1): + r1 = r1 * p % q1 + if ans[r1] is not None: + break + ans[r1] = ans[r] + if f == 1: + return ans + # Consolidate down to p-1 terms. + ans2 = [Rz] * (p-1) + for r1 in range(p-1): + for r in range(r1, q1, p-1): + ans2[r1] += ans[r] + return ans2 diff --git a/src/sage/modular/hypergeometric_motive.py b/src/sage/modular/hypergeometric_motive.py index b15d149cb42..2bca0d85f87 100644 --- a/src/sage/modular/hypergeometric_motive.py +++ b/src/sage/modular/hypergeometric_motive.py @@ -1291,10 +1291,10 @@ def padic_H_value(self, p, f, t, prec=None, cache_p=False): else: gtab = gauss_table(p, f, prec, use_longs) trcoeffs = hgm_coeffs(p, f, prec, gamma, m, D, gtab, prec, use_longs) - sigma = trcoeffs[q-2] + sigma = trcoeffs[p-2] p_ring = sigma.parent() teich = p_ring.teichmuller(M/t) - for i in range(q-3, -1, -1): + for i in range(p-3, -1, -1): sigma = sigma * teich + trcoeffs[i] resu = ZZ(-1) ** m[0] * sigma / (1 - q) return IntegerModRing(p**prec)(resu).lift_centered() diff --git a/src/sage/modular/modform/find_generators.py b/src/sage/modular/modform/find_generators.py index c593213bc04..5d510776a38 100644 --- a/src/sage/modular/modform/find_generators.py +++ b/src/sage/modular/modform/find_generators.py @@ -111,7 +111,8 @@ def _span_of_forms_in_weight(forms, weight, prec, stop_dim=None, use_random=Fals for c in range(N): w = V(prod(shortforms[i]**wts[c][i] for i in range(n)).padded_list(prec)) - if w in W: continue + if w in W: + continue W = V.span(list(W.gens()) + [w]) if stop_dim and W.rank() == stop_dim: if R != ZZ or W.index_in_saturation() == 1: @@ -541,7 +542,8 @@ def _find_generators(self, maxweight, start_gens, start_weight): G.append((k, f, F)) k = start_weight - if increment == 2 and (k % 2) == 1: k += 1 + if increment == 2 and (k % 2) == 1: + k += 1 while k <= maxweight: @@ -549,12 +551,12 @@ def _find_generators(self, maxweight, start_gens, start_weight): k += increment continue - verbose('Looking at k = %s'%k) + verbose('Looking at k = %s' % k) M = self.modular_forms_of_weight(k) # 1. Multiply together all forms in G that give an element # of M. - if G != []: + if G: F = _span_of_forms_in_weight(G, k, M.sturm_bound(), None, False) else: F = (self.base_ring() ** M.sturm_bound()).zero_submodule() @@ -574,7 +576,8 @@ def _find_generators(self, maxweight, start_gens, start_weight): # try adding basis elements of M into G. verbose("Known generators span a subspace of dimension %s of space of dimension %s" % (F.dimension(), M.dimension())) - if self.base_ring() == ZZ: verbose("saturation index is %s" % F.index_in_saturation()) + if self.base_ring() == ZZ: + verbose("saturation index is %s" % F.index_in_saturation()) t = verbose("Computing more modular forms at weight %s" % k) kprec = M.sturm_bound() @@ -650,7 +653,8 @@ def q_expansion_basis(self, weight, prec=None, use_random=True): [1 + O(q^5), q + O(q^5), q^2 + O(q^5), q^3 + O(q^5), q^4 + O(q^5), O(q^5), O(q^5), O(q^5), O(q^5), O(q^5)] """ d = self.modular_forms_of_weight(weight).dimension() - if d == 0: return [] + if d == 0: + return [] if prec is None: prec=self.modular_forms_of_weight(weight).sturm_bound() @@ -786,7 +790,8 @@ def cuspidal_submodule_q_expansion_basis(self, weight, prec=None): True """ d = self.modular_forms_of_weight(weight).cuspidal_submodule().dimension() - if d == 0: return [] + if d == 0: + return [] minprec = self.modular_forms_of_weight(weight).sturm_bound() if prec is None: diff --git a/src/sage/modular/modsym/ambient.py b/src/sage/modular/modsym/ambient.py index ef0db033ab6..aad77cd7f73 100644 --- a/src/sage/modular/modsym/ambient.py +++ b/src/sage/modular/modsym/ambient.py @@ -70,7 +70,7 @@ class ``ModularSymbolsAmbient``, derived from # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ ################################################################################ # Sage packages import sage.misc.latex as latex @@ -84,7 +84,7 @@ class ``ModularSymbolsAmbient``, derived from import sage.modular.dirichlet as dirichlet import sage.modular.hecke.all as hecke from sage.rings.all import Integer, QQ, ZZ, Ring -from sage.arith.all import is_prime, gcd, divisors, number_of_divisors, crt +from sage.arith.all import is_prime, divisors, number_of_divisors, crt import sage.rings.polynomial.multi_polynomial_element import sage.structure.formal_sum as formal_sum import sage.categories.all as cat @@ -2181,21 +2181,15 @@ def twisted_winding_element(self, i, eps): sage: M = ModularSymbols(37,2,0,K) sage: M.twisted_winding_element(0,eps) 2*(1,23) - 2*(1,32) + 2*(1,34) - """ - if not dirichlet.is_DirichletCharacter(eps): raise TypeError("eps must be a Dirichlet character.") - if (i < 0) or (i > self.weight()-2): + if (i < 0) or (i > self.weight() - 2): raise ValueError("i must be between 0 and k-2.") m = eps.modulus() - s = self(0) - - for a in ([ x for x in range(1,m) if gcd(x, m) == 1 ]): - s += eps(a) * self.modular_symbol([i, Cusp(0), Cusp(a/m)]) - - return s + return self.sum(eps(a) * self.modular_symbol([i, Cusp(0), Cusp(a / m)]) + for a in m.coprime_integers(m)) ###################################################################### # Z-module of integral modular symbols. diff --git a/src/sage/modular/modsym/boundary.py b/src/sage/modular/modsym/boundary.py index ca0f9b6c6eb..736d4fd0e50 100644 --- a/src/sage/modular/modsym/boundary.py +++ b/src/sage/modular/modsym/boundary.py @@ -90,7 +90,7 @@ from __future__ import absolute_import -from sage.misc.misc import repr_lincomb +from sage.misc.repr import repr_lincomb from sage.structure.richcmp import richcmp_method, richcmp import sage.modules.free_module as free_module diff --git a/src/sage/modular/modsym/element.py b/src/sage/modular/modsym/element.py index fd7504a6a3b..bfeffc20ead 100644 --- a/src/sage/modular/modsym/element.py +++ b/src/sage/modular/modsym/element.py @@ -22,7 +22,7 @@ import sage.modules.free_module_element -import sage.misc.misc as misc +from sage.misc.repr import repr_lincomb import sage.structure.formal_sum as formal_sum import sage.modular.hecke.all as hecke import sage.misc.latex as latex @@ -144,7 +144,7 @@ def _repr_(self): m = self.manin_symbol_rep() elif _print_mode == "modular": m = self.modular_symbol_rep() - return misc.repr_lincomb([(t,c) for c,t in m]) + return repr_lincomb([(t,c) for c,t in m]) def _latex_(self): r""" diff --git a/src/sage/modular/modsym/space.py b/src/sage/modular/modsym/space.py index 912c1ceb5a7..e93d967b6e0 100644 --- a/src/sage/modular/modsym/space.py +++ b/src/sage/modular/modsym/space.py @@ -7,7 +7,7 @@ """ from __future__ import absolute_import -#***************************************************************************** +# **************************************************************************** # Sage: System for Algebra and Geometry Experimentation # # Copyright (C) 2005 William Stein @@ -21,32 +21,34 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** +from sage.categories.fields import Fields import sage.modules.free_module as free_module import sage.matrix.matrix_space as matrix_space -from sage.modules.free_module_element import is_FreeModuleElement +from sage.modules.free_module_element import FreeModuleElement from sage.modules.free_module import EchelonMatrixKey from sage.misc.all import verbose, prod import sage.modular.hecke.all as hecke -import sage.arith.all as arith -import sage.rings.fast_arith as fast_arith -from sage.rings.all import PowerSeriesRing, Integer, O, QQ, ZZ, infinity, Zmod +from sage.arith.all import divisors, next_prime +from sage.rings.fast_arith import prime_range +from sage.rings.all import PowerSeriesRing, Integer, QQ, ZZ, infinity, Zmod from sage.rings.number_field.number_field_base import is_NumberField -from sage.structure.all import Sequence, SageObject +from sage.structure.all import Sequence, SageObject from sage.structure.richcmp import (richcmp_method, richcmp, rich_to_bool, richcmp_not_equal) -from sage.modular.arithgroup.all import Gamma0, is_Gamma0 # for Sturm bound given a character +from sage.modular.arithgroup.all import Gamma0, is_Gamma0 # for Sturm bound given a character from sage.modular.modsym.element import ModularSymbolsElement from . import hecke_operator from sage.misc.cachefunc import cached_method + def is_ModularSymbolsSpace(x): r""" - Return True if x is a space of modular symbols. + Return ``True`` if ``x`` is a space of modular symbols. EXAMPLES:: @@ -85,7 +87,7 @@ def __init__(self, group, weight, character, sign, base_ring, category=None): def __richcmp__(self, other, op): """ - Compare self and other. + Compare ``self`` and ``other``. When spaces are in a common ambient space, we order lexicographically by the sequence of traces of Hecke operators @@ -153,7 +155,7 @@ def __richcmp__(self, other, op): # distinguish using Hecke operators, if possible. try: - for p in fast_arith.prime_range(self.hecke_bound()): + for p in prime_range(self.hecke_bound()): ap = self.hecke_matrix(p).trace() bp = other.hecke_matrix(p).trace() if ap != bp: @@ -166,7 +168,7 @@ def __richcmp__(self, other, op): def _hecke_operator_class(self): """ Return the class to be used for instantiating Hecke operators - acting on self. + acting on ``self``. EXAMPLES:: @@ -178,16 +180,17 @@ def _hecke_operator_class(self): def compact_system_of_eigenvalues(self, v, names='alpha', nz=None): r""" Return a compact system of eigenvalues `a_n` for - `n\in v`. This should only be called on simple factors of - modular symbols spaces. + `n\in v`. + + This should only be called on simple factors of modular + symbols spaces. INPUT: - ``v`` - a list of positive integers - - ``nz`` - (default: None); if given specifies a column index + - ``nz`` - (default: ``None``); if given specifies a column index such that the dual module has that column nonzero. - OUTPUT: - ``E`` - matrix such that E\*v is a vector with components @@ -236,7 +239,7 @@ def compact_system_of_eigenvalues(self, v, names='alpha', nz=None): def character(self): """ - Return the character associated to self. + Return the character associated to ``self``. EXAMPLES:: @@ -249,11 +252,11 @@ def character(self): def cuspidal_submodule(self): """ - Return the cuspidal submodule of self. + Return the cuspidal submodule of ``self``. - .. note:: + .. NOTE:: - This should be overridden by all derived classes. + This should be overridden by all derived classes. EXAMPLES:: @@ -353,9 +356,9 @@ def dual_star_involution_matrix(self): Return the matrix of the dual star involution, which is induced by complex conjugation on the linear dual of modular symbols. - .. note:: + .. NOTE:: - This should be overridden in all derived classes. + This should be overridden in all derived classes. EXAMPLES:: @@ -372,21 +375,18 @@ def dual_star_involution_matrix(self): def group(self): """ - Returns the group of this modular symbols space. + Return the group of this modular symbols space. INPUT: - - ``ModularSymbols self`` - an arbitrary space of modular symbols - OUTPUT: - ``CongruenceSubgroup`` - the congruence subgroup that this is a space of modular symbols for. - ALGORITHM: The group is recorded when this space is created. EXAMPLES:: @@ -399,7 +399,7 @@ def group(self): def is_ambient(self): """ - Return True if self is an ambient space of modular symbols. + Return ``True`` if ``self`` is an ambient space of modular symbols. EXAMPLES:: @@ -413,11 +413,11 @@ def is_ambient(self): def is_cuspidal(self): """ - Return True if self is a cuspidal space of modular symbols. + Return ``True`` if ``self`` is a cuspidal space of modular symbols. - .. note:: + .. NOTE:: - This should be overridden in all derived classes. + This should be overridden in all derived classes. EXAMPLES:: @@ -491,11 +491,11 @@ def multiplicity(self, S, check_simple=True): d = C.rank() n = S.rank() assert d % n == 0, "the dimension of intersection must be a multiple of dimension of simple space. bug!" - return d//n + return d // n def ngens(self): """ - The number of generators of self. + Return the number of generators of ``self``. INPUT: @@ -506,7 +506,6 @@ def ngens(self): - ``int`` - the number of generators, which is the same as the dimension of self. - ALGORITHM: Call the dimension function. EXAMPLES:: @@ -605,13 +604,13 @@ def set_precision(self, prec): def q_expansion_basis(self, prec=None, algorithm='default'): r""" - Returns a basis of q-expansions (as power series) to precision prec - of the space of modular forms associated to self. The q-expansions - are defined over the same base ring as self, and a put in echelon - form. + Return a basis of q-expansions (as power series) to precision prec + of the space of modular forms associated to ``self``. - INPUT: + The q-expansions are defined over the same base ring as ``self``, + and a put in echelon form. + INPUT: - ``self`` - a space of CUSPIDAL modular symbols @@ -713,7 +712,7 @@ def q_expansion_basis(self, prec=None, algorithm='default'): prec = Integer(prec) if prec < 1: - raise ValueError("prec (=%s) must be >= 1"%prec) + raise ValueError("prec (=%s) must be >= 1" % prec) if not self.is_cuspidal(): raise ArithmeticError("space must be cuspidal") @@ -735,18 +734,19 @@ def q_expansion_basis(self, prec=None, algorithm='default'): B1 = self._q_expansion_basis_hecke_dual(prec) B2 = self._q_expansion_basis_eigen(prec, 'alpha') if B1 != B2: - raise RuntimeError("There is a bug in q_expansion_basis -- basis computed differently with two algorithms:\n%s\n%s\n"%(B1, B2,)) + raise RuntimeError("There is a bug in q_expansion_basis -- basis computed differently with two algorithms:\n%s\n%s\n" % (B1, B2,)) return Sequence(B1, cr=True) else: - raise ValueError("no algorithm '%s'"%algorithm) + raise ValueError("no algorithm '%s'" % algorithm) - def q_expansion_module(self, prec = None, R=None): + def q_expansion_module(self, prec=None, R=None): r""" Return a basis over R for the space spanned by the coefficient - vectors of the `q`-expansions corresponding to self. If R - is not the base ring of self, returns the restriction of scalars - down to R (for this, self must have base ring `\QQ` - or a number field). + vectors of the `q`-expansions corresponding to ``self``. + + If R is not the base ring of ``self``, this returns the + restriction of scalars down to R (for this, self must have + base ring `\QQ` or a number field). INPUT: @@ -760,9 +760,11 @@ def q_expansion_module(self, prec = None, R=None): OUTPUT: A free module over R. - TODO - extend to more general R (though that is fairly easy for the - user to get by just doing base_extend or change_ring on the - output of this function). + .. TODO:: + + extend to more general R (though that is fairly easy for the + user to get by just doing base_extend or change_ring on the + output of this function). Note that the prec needed to distinguish elements of the restricted-down-to-R basis may be bigger than ``self.hecke_bound()``, @@ -885,7 +887,7 @@ def q_expansion_module(self, prec = None, R=None): EXAMPLES WITH SIGN 0 and R=QQ: - TODO - this doesn't work yet as it's not implemented!! + .. TODO:: This doesn't work yet as it's not implemented!! :: @@ -916,16 +918,17 @@ def q_expansion_module(self, prec = None, R=None): elif R == QQ: return self._q_expansion_module_rational(prec) elif R is None or R == self.base_ring(): - ## names is never used in this case + # names is never used in this case return self._q_expansion_module(prec) else: raise NotImplementedError("R must be ZZ, QQ, or the base ring of the modular symbols space.") def _q_eigenform_images(self, A, prec, names): """ - Return list of images in space corresponding to self of eigenform - corresponding to A under the degeneracy maps. This is mainly a - helper function for other internal functions. + Return list of images in space corresponding to ``self`` of eigenform + corresponding to A under the degeneracy maps. + + This is mainly a helper function for other internal functions. INPUT: @@ -936,7 +939,6 @@ def _q_eigenform_images(self, A, prec, names): - ``prec`` - a positive integer - EXAMPLES:: sage: M = ModularSymbols(33,2,sign=1) @@ -948,16 +950,17 @@ def _q_eigenform_images(self, A, prec, names): f = A.q_eigenform(prec, names) if A.level() == self.level(): return [f] - D = arith.divisors(self.level() // A.level()) + D = divisors(self.level() // A.level()) q = f.parent().gen() return [f] + [f(q**d) for d in D if d > 1] def _q_expansion_module(self, prec, algorithm='hecke'): """ - Return module spanned by the `q`-expansions corresponding to self. See - the docstring for ``q_expansion_module`` (without underscore) for - further details. Note that this will not work if ``algorithm=eigen`` - and the sign is 0. + Return module spanned by the `q`-expansions corresponding to ``self``. + + See the docstring for ``q_expansion_module`` (without + underscore) for further details. Note that this will not work + if ``algorithm=eigen`` and the sign is 0. EXAMPLES:: @@ -969,12 +972,11 @@ def _q_expansion_module(self, prec, algorithm='hecke'): Vector space of degree 4 and dimension 1 over Number Field in b with defining polynomial x^2 + 7 with b = 2.645751311064591?*I Basis matrix: [ 0 1 -2 -1] - """ if not self.is_cuspidal(): raise ValueError("self must be cuspidal") R = self.base_ring() - if not R.is_field(): + if R not in Fields(): if R == ZZ: return self._q_expansion_module_integral(prec) raise NotImplementedError("base ring must be a field (or ZZ).") @@ -984,10 +986,11 @@ def _q_expansion_module(self, prec, algorithm='hecke'): return V.span([f.padded_list(prec) for f in self.q_expansion_basis(prec, algorithm)]) if algorithm != 'eigen': - raise ValueError("unknown algorithm '%s'"%algorithm) + raise ValueError("unknown algorithm '%s'" % algorithm) def q_eigen_gens(d, f): - r""" Temporary function for internal use. + r""" + Temporary function for internal use. EXAMPLES:: @@ -1019,12 +1022,12 @@ def q_eigen_gens(d, f): def _q_expansion_module_rational(self, prec): r""" Return a vector space over `\QQ` for the space spanned by the - `q`-expansions corresponding to self. + `q`-expansions corresponding to ``self``. - The base ring of self must be - `\QQ` or a number field, and self must be cuspidal. The returned space - is a `\QQ`-vector space, where the coordinates are the coefficients of - `q`-expansions. + The base ring of ``self`` must be `\QQ` or a number field, and + ``self`` must be cuspidal. The returned space is a + `\QQ`-vector space, where the coordinates are the coefficients + of `q`-expansions. INPUT: @@ -1072,7 +1075,8 @@ def q_eigen_gens(d, f): # This looks like it might be really slow -- though # perhaps it's nothing compared to the time taken by # whatever computed this in the first place. - return [[(X[i].list())[j][k] for i in range(prec)] for j in range(d) for k in range(n)] + return [[(X[i].list())[j][k] for i in range(prec)] + for j in range(d) for k in range(n)] if self.sign() == 0: X = self.plus_submodule(compute_dual=True) else: @@ -1087,11 +1091,11 @@ def q_eigen_gens(d, f): def _q_expansion_module_integral(self, prec): r""" Return module over `\ZZ` for the space spanned by - the `q`-expansions corresponding to self. The base ring of - self must be `\QQ` or a number field, and self must - be cuspidal. The returned space is a `\ZZ`-module, - where the coordinates are the coefficients of - `q`-expansions. + the `q`-expansions corresponding to ``self``. + + The base ring of ``self`` must be `\QQ` or a number field, and + self must be cuspidal. The returned space is a `\ZZ`-module, + where the coordinates are the coefficients of `q`-expansions. EXAMPLES:: @@ -1109,11 +1113,10 @@ def _q_expansion_module_integral(self, prec): V = self.q_expansion_module(prec, QQ) return free_module.FreeModule(ZZ, V.degree()).span(V.basis()).saturation() - def congruence_number(self, other, prec=None): r""" Given two cuspidal spaces of modular symbols, compute the - congruence number, using prec terms of the `q`-expansions. + congruence number, using ``prec`` terms of the `q`-expansions. The congruence number is defined as follows. If `V` is the submodule of integral cusp forms corresponding to self (saturated in @@ -1122,7 +1125,7 @@ def congruence_number(self, other, prec=None): the congruence number is the index of `V+W` in its saturation in `\ZZ[[q]]`. - If prec is not given it is set equal to the max of the + If ``prec`` is not given it is set equal to the max of the ``hecke_bound`` function called on each space. EXAMPLES:: @@ -1139,9 +1142,9 @@ def congruence_number(self, other, prec=None): prec = max(self.hecke_bound(), other.hecke_bound()) prec = int(prec) - V = self.q_expansion_module(prec, ZZ) + V = self.q_expansion_module(prec, ZZ) W = other.q_expansion_module(prec, ZZ) - K = V+W + K = V + W return K.index_in_saturation() ######################################################################### @@ -1219,13 +1222,16 @@ def q_eigenform_character(self, names=None): G = DirichletGroup(self.level(), K) M = self.ambient_module() # act on right since v is a in the dual - b = [(M.diamond_bracket_matrix(u)*v)[i] / v[i] for u in G.unit_gens()] + b = [(M.diamond_bracket_matrix(u) * v)[i] / v[i] + for u in G.unit_gens()] return G(b) def q_eigenform(self, prec, names=None): """ - Returns the q-expansion to precision prec of a new eigenform - associated to self, where self must be new, cuspidal, and simple. + Return the q-expansion to precision ``prec`` of a new eigenform + associated to ``self``. + + Here ``self`` must be new, cuspidal, and simple. EXAMPLES:: @@ -1255,11 +1261,11 @@ def q_eigenform(self, prec, names=None): a2 = self.eigenvalue(2, names) R = PowerSeriesRing(a2.parent(), "q") q = R.gen(0) - f = q + a2*q**2 + O(q**3) + f = (q + a2 * q**2).O(3) if f.prec() < prec: R = f.parent() - ext = [self.eigenvalue(n, names) for n in range(f.prec(),prec)] + ext = [self.eigenvalue(n, names) for n in range(f.prec(), prec)] f = R(f.padded_list(f.prec()) + ext) self._q_expansion_dict[names] = f.add_bigoh(prec) return self._q_expansion_dict[names] @@ -1280,12 +1286,12 @@ def _q_expansion_basis_eigen(self, prec, names): # should we perhaps check at this point if self is new? f = self.q_eigenform(prec, names) R = PowerSeriesRing(self.base_ring(), 'q') - B = [R([f[i][j] for i in range(prec)],prec) for j in range(self.rank())] + B = [R([f[i][j] for i in range(prec)], prec) + for j in range(self.rank())] return B else: raise NotImplementedError - ######################################################################### # # Computation of a basis using linear functionals on the Hecke algebra. @@ -1294,12 +1300,12 @@ def _q_expansion_basis_eigen(self, prec, names): def q_expansion_cuspforms(self, prec=None): r""" - Returns a function f(i,j) such that each value f(i,j) is the + Return a function f(i,j) such that each value f(i,j) is the q-expansion, to the given precision, of an element of the - corresponding space `S` of cusp forms. Together these - functions span `S`. Here `i,j` are integers with - `0\leq i,j < d`, where `d` is the dimension of - self. + corresponding space `S` of cusp forms. + + Together these functions span `S`. Here `i,j` are integers + with `0\leq i,j < d`, where `d` is the dimension of ``self``. For a reduced echelon basis, use the function ``q_expansion_basis`` instead. @@ -1307,7 +1313,7 @@ def q_expansion_cuspforms(self, prec=None): More precisely, this function returns the `q`-expansions obtained by taking the `ij` entry of the matrices of the Hecke operators `T_n` acting on the subspace of the linear - dual of modular symbols corresponding to self. + dual of modular symbols corresponding to ``self``. EXAMPLES:: @@ -1347,9 +1353,9 @@ def q_expansion_cuspforms(self, prec=None): prec = self.default_prec() if not self.is_cuspidal(): raise ArithmeticError("self must be cuspidal") - T = [self.dual_hecke_matrix(n) for n in range(1,prec)] + T = [self.dual_hecke_matrix(n) for n in range(1, prec)] R = PowerSeriesRing(self.base_ring(), 'q') - return lambda i, j: R([0] + [t[i,j] for t in T], prec) + return lambda i, j: R([0] + [t[i, j] for t in T], prec) def _q_expansion_basis_hecke_dual(self, prec): r""" @@ -1368,35 +1374,34 @@ def _q_expansion_basis_hecke_dual(self, prec): d = self.dimension_of_associated_cuspform_space() prec = Integer(prec) if prec < 1: - raise ValueError("prec (=%s) must be >= 1"%prec) - if d >= prec-1: - d = prec-1 + raise ValueError("prec (=%s) must be >= 1" % prec) + if d >= prec - 1: + d = prec - 1 K = self.base_ring() - A = free_module.VectorSpace(K, prec-1) - M = matrix_space.MatrixSpace(K, prec-1, self.dimension()) + A = free_module.VectorSpace(K, prec - 1) + M = matrix_space.MatrixSpace(K, prec - 1, self.dimension()) V = A.zero_submodule() - i = self.dimension()-1 + i = self.dimension() - 1 j = 0 - t = verbose('computing basis to precision %s'%prec) + t = verbose('computing basis to precision %s' % prec) while V.dimension() < d and i >= 0: - v = [self.dual_hecke_matrix(n).column(i) for n in range(1,prec)] - t = verbose('iteration: %s'%j,t) + v = [self.dual_hecke_matrix(n).column(i) for n in range(1, prec)] + t = verbose('iteration: %s' % j, t) X = M(v).transpose() V += X.row_space() - t = verbose('addition of row space: %s'%j,t) + t = verbose('addition of row space: %s' % j, t) i -= 1 j += 1 R = PowerSeriesRing(K, 'q') B = V.basis() if len(B) < d: - B += [V(0)] * (d-len(B)) + B += [V(0)] * (d - len(B)) return [R([0] + b.list(), prec) for b in B] - ######################################################################### # # Decomposition of spaces @@ -1405,7 +1410,7 @@ def _q_expansion_basis_hecke_dual(self, prec): # def factorization(self): # """ -# Returns a list of pairs `(S,e)` where `S` is simple +# Return a list of pairs `(S,e)` where `S` is simple # spaces of modular symbols and self is isomorphic to the direct sum # of the `S^e` as a module over the *anemic* Hecke algebra # adjoin the star involution. @@ -1430,7 +1435,7 @@ def hecke_module_of_level(self, level): def sign(self): r""" - Return the sign of self. + Return the sign of ``self``. For efficiency reasons, it is often useful to compute in the (largest) quotient of modular symbols where the \* involution acts @@ -1438,15 +1443,12 @@ def sign(self): INPUT: - - ``ModularSymbols self`` - arbitrary space of modular symbols. - OUTPUT: - - - ``int`` - the sign of self, either -1, 0, or 1. + - ``int`` - the sign of ``self``, either -1, 0, or 1. - ``-1`` - if this is factor of quotient where \* acts as -1, @@ -1457,7 +1459,6 @@ def sign(self): - ``0`` - if this is full space of modular symbols (no quotient). - EXAMPLES:: sage: m = ModularSymbols(33) @@ -1480,10 +1481,11 @@ def sign(self): def simple_factors(self): """ - Returns a list modular symbols spaces `S` where `S` + Return a list modular symbols spaces `S` where `S` is simple spaces of modular symbols (for the anemic Hecke algebra) - and self is isomorphic to the direct sum of the `S` with + and ``self`` is isomorphic to the direct sum of the `S` with some multiplicities, as a module over the *anemic* Hecke algebra. + For the multiplicities use factorization() instead. ASSUMPTION: self is a module over the anemic Hecke algebra. @@ -1497,11 +1499,11 @@ def simple_factors(self): Modular Symbols subspace of dimension 1 of Modular Symbols space of dimension 3 for Gamma_0(1) of weight 16 with sign 0 over Finite Field of size 5, Modular Symbols subspace of dimension 1 of Modular Symbols space of dimension 3 for Gamma_0(1) of weight 16 with sign 0 over Finite Field of size 5] """ - return [S for S,_ in self.factorization()] + return [S for S, _ in self.factorization()] def star_eigenvalues(self): """ - Returns the eigenvalues of the star involution acting on self. + Return the eigenvalues of the star involution acting on ``self``. EXAMPLES:: @@ -1536,7 +1538,7 @@ def star_eigenvalues(self): def star_decomposition(self): r""" - Decompose self into subspaces which are eigenspaces for the star + Decompose ``self`` into subspaces which are eigenspaces for the star involution. EXAMPLES:: @@ -1590,14 +1592,16 @@ def integral_structure(self): def intersection_number(self, M): """ - Given modular symbols spaces self and M in some common ambient - space, returns the intersection number of these two spaces. This is - the index in their saturation of the sum of their underlying - integral structures. + Given modular symbols spaces ``self`` and ``M`` in some common ambient + space, returns the intersection number of these two spaces. + + This is the index in their saturation of the sum of their + underlying integral structures. - If self and M are of weight two and defined over QQ, and correspond - to newforms f and g, then this number equals the order of the - intersection of the modular abelian varieties attached to f and g. + If ``self`` and ``M`` are of weight two and defined over QQ, + and correspond to newforms f and g, then this number equals + the order of the intersection of the modular abelian varieties + attached to f and g. EXAMPLES:: @@ -1617,7 +1621,7 @@ def intersection_number(self, M): raise ValueError("self and M must be in the same ambient space.") A = self.integral_structure() B = M.integral_structure() - return (A+B).index_in_saturation() + return (A + B).index_in_saturation() def integral_basis(self): r""" @@ -1681,13 +1685,14 @@ def integral_basis(self): self.__integral_basis = tuple([self(b) for b in B]) return self.__integral_basis - def integral_hecke_matrix(self, n): r""" Return the matrix of the `n`th Hecke operator acting on the integral - structure on self (as returned by ``self.integral_structure()``). This - is often (but not always) different from the matrix returned by - ``self.hecke_matrix``, even if the latter has integral entries. + structure on ``self`` (as returned by ``self.integral_structure()``). + + This is often (but not always) different from the matrix + returned by ``self.hecke_matrix``, even if the latter has + integral entries. EXAMPLES:: @@ -1714,7 +1719,6 @@ def integral_hecke_matrix(self, n): self.__integral_hecke_matrix = {} except KeyError: pass - #raise NotImplementedError, "code past this point is broken / not done" # todo A = self.ambient_hecke_module() T = A.hecke_matrix(n) S = T.restrict(self.integral_structure()).change_ring(ZZ) @@ -1723,7 +1727,7 @@ def integral_hecke_matrix(self, n): def sturm_bound(self): r""" - Returns the Sturm bound for this space of modular symbols. + Return the Sturm bound for this space of modular symbols. Type ``sturm_bound?`` for more details. @@ -1752,19 +1756,15 @@ def sturm_bound(self): self.__sturm_bound = self.group().sturm_bound(self.weight()) return self.__sturm_bound - def plus_submodule(self, compute_dual=True): """ - Return the subspace of self on which the star involution acts as - +1. + Return the subspace of ``self`` on which the star involution acts as +1. INPUT: - - - ``compute_dual`` - bool (default: True) also + - ``compute_dual`` - bool (default: ``True``) also compute dual subspace. This are useful for many algorithms. - OUTPUT: subspace of modular symbols EXAMPLES:: @@ -1778,12 +1778,10 @@ def plus_submodule(self, compute_dual=True): def minus_submodule(self, compute_dual=True): """ - Return the subspace of self on which the star involution acts as - -1. + Return the subspace of ``self`` on which the star involution acts as -1. INPUT: - - ``compute_dual`` - bool (default: True) also compute dual subspace. This are useful for many algorithms. @@ -1800,15 +1798,16 @@ def minus_submodule(self, compute_dual=True): def _compute_sign_submodule(self, sign, compute_dual=True): r""" - Compute the submodule of self which is an eigenspace for the star involution with the given sign. + Compute the submodule of ``self`` which is an eigenspace for the star involution with the given sign. INPUT: - sign (integer): 1 or -1 - - compute_dual (True or False, default True): also compute the dual submodule (useful for some algorithms) + - compute_dual (boolean, default ``True``): also compute the dual + submodule (useful for some algorithms) - OUTPUT: a submodule of self + OUTPUT: a submodule of ``self`` EXAMPLES:: @@ -1852,24 +1851,21 @@ def _set_sign(self, sign): sage: M._set_sign(0) """ sign = int(sign) - if not (sign in [-1,0,1]): - raise ValueError("sign (=%s) must be -1, 0, or 1"%sign) + if sign not in [-1, 0, 1]: + raise ValueError("sign (=%s) must be -1, 0, or 1" % sign) self.__sign = sign def sign_submodule(self, sign, compute_dual=True): """ - Return the subspace of self that is fixed under the star - involution. + Return the subspace of ``self`` that is fixed under the star involution. INPUT: - - ``sign`` - int (either -1, 0 or +1) - - ``compute_dual`` - bool (default: True) also + - ``compute_dual`` - bool (default: ``True``) also compute dual subspace. This are useful for many algorithms. - OUTPUT: subspace of modular symbols EXAMPLES:: @@ -1883,7 +1879,7 @@ def sign_submodule(self, sign, compute_dual=True): -1 """ sign = int(sign) - if not sign in [-1, 0, 1]: + if sign not in [-1, 0, 1]: raise ValueError("sign must be -1, 0 or 1") if self.sign() == sign: # an easy case return self @@ -1901,14 +1897,15 @@ def sign_submodule(self, sign, compute_dual=True): pass P = self._compute_sign_submodule(sign, compute_dual) P.__star_eigenvalue = sign - self.__plus_submodule[(sign,compute_dual)] = P + self.__plus_submodule[(sign, compute_dual)] = P return P def star_involution(self): """ - Return the star involution on self, which is induced by complex - conjugation on modular symbols. Not implemented in this abstract base - class. + Return the star involution on ``self``, which is induced by complex + conjugation on modular symbols. + + Not implemented in this abstract base class. EXAMPLES:: @@ -1925,11 +1922,9 @@ def abelian_variety(self): INPUT: - - ``self`` - modular symbols space of weight 2 for a congruence subgroup such as Gamma0, Gamma1 or GammaH. - EXAMPLES:: sage: ModularSymbols(Gamma0(11)).cuspidal_submodule().abelian_variety() @@ -1964,13 +1959,13 @@ def abelian_variety(self): def rational_period_mapping(self): r""" - Return the rational period mapping associated to self. + Return the rational period mapping associated to ``self``. - This is a homomorphism to a vector space whose kernel is the same as - the kernel of the period mapping associated to self. For this to exist, - self must be Hecke equivariant. + This is a homomorphism to a vector space whose kernel is the + same as the kernel of the period mapping associated to + ``self``. For this to exist, self must be Hecke equivariant. - Use ``integral_period_mapping`` to obtain a homomorphism to a + Use :meth:`integral_period_mapping` to obtain a homomorphism to a `\ZZ`-module, normalized so the image of integral modular symbols is exactly `\ZZ^n`. @@ -2012,15 +2007,13 @@ def rational_period_mapping(self): self.__rational_period_mapping = R return R - def integral_period_mapping(self): r""" - Return the integral period mapping associated to self. + Return the integral period mapping associated to ``self``. This is a homomorphism to a vector space whose kernel is the same - as the kernel of the period mapping associated to self, normalized - so the image of integral modular symbols is exactly - `\ZZ^n`. + as the kernel of the period mapping associated to ``self``, normalized + so the image of integral modular symbols is exactly `\ZZ^n`. EXAMPLES:: @@ -2084,13 +2077,12 @@ def integral_period_mapping(self): @cached_method def modular_symbols_of_sign(self, sign, bound=None): """ - Returns a space of modular symbols with the same defining + Return a space of modular symbols with the same defining properties (weight, level, etc.) and Hecke eigenvalues as this space except with given sign. INPUT: - - ``self`` - a cuspidal space of modular symbols - ``sign`` - an integer, one of -1, 0, or 1 @@ -2098,7 +2090,6 @@ def modular_symbols_of_sign(self, sign, bound=None): - ``bound`` - integer (default: None); if specified only use Hecke operators up to the given bound. - EXAMPLES:: sage: S = ModularSymbols(Gamma0(11),2,sign=0).cuspidal_subspace() @@ -2148,22 +2139,22 @@ def modular_symbols_of_sign(self, sign, bound=None): return self if sign != 0: if self.sign() == 0: - d = d//2 + d = d // 2 elif sign == 0: # self has nonzero sign - d = 2*d + d = 2 * d B = self.ambient_module().modular_symbols_of_sign(sign) p = 2 if bound is None: bound = self.hecke_bound() while B.dimension() > d and p <= bound: while self.level() % p == 0: - p = arith.next_prime(p) + p = next_prime(p) f = self.hecke_polynomial(p) - g = prod(g for g,_ in f.factor()) # square free part + g = prod(g for g, _ in f.factor()) # square free part t = B.hecke_operator(p) s = g(t) B = s.kernel() - p = arith.next_prime(p) + p = next_prime(p) return B ######################################################### @@ -2177,8 +2168,8 @@ def abvarquo_cuspidal_subgroup(self): the relevant modular Jacobian attached to this modular symbols space. - We assume that self is defined over QQ and has weight 2. If - the sign of self is not 0, then the power of 2 may be wrong. + We assume that ``self`` is defined over QQ and has weight 2. If + the sign of ``self`` is not 0, then the power of 2 may be wrong. EXAMPLES:: @@ -2194,8 +2185,10 @@ def abvarquo_cuspidal_subgroup(self): sage: [A.abvarquo_cuspidal_subgroup().invariants() for A in D] [(), (), ()] """ - try: return self.__abvarquo_cuspidal_subgroup - except AttributeError: pass + try: + return self.__abvarquo_cuspidal_subgroup + except AttributeError: + pass if self.base_ring() != QQ: raise ValueError("base ring must be QQ") if self.weight() != 2: @@ -2208,14 +2201,14 @@ def abvarquo_cuspidal_subgroup(self): # Compute the images of the cusp classes (c)-(oo) in the # rational homology of the quotient modular abelian variety. - ims = [phi(M([c,infinity])) for c in P] + ims = [phi(M([c, infinity])) for c in P] # Take the span of the ims over ZZ A = phi.codomain().span(ims, ZZ) # The cuspidal subgroup is then the quotient of that module + # H_1(A) by H_1(A) - C = (A.ambient_module() + A)/A.ambient_module() + C = (A.ambient_module() + A) / A.ambient_module() self.__abvarquo_cuspidal_subgroup = C return C @@ -2225,12 +2218,14 @@ def abvarquo_rational_cuspidal_subgroup(self): Compute the rational subgroup of the cuspidal subgroup (as an abstract abelian group) of the abelian variety quotient A of the relevant modular Jacobian attached to this modular symbols - space. If C is the subgroup of A generated by differences of + space. + + If C is the subgroup of A generated by differences of cusps, then C is equipped with an action of Gal(Qbar/Q), and this function computes the fixed subgroup, i.e., C(Q). - We assume that self is defined over QQ and has weight 2. If - the sign of self is not 0, then the power of 2 may be wrong. + We assume that ``self`` is defined over QQ and has weight 2. If + the sign of ``self`` is not 0, then the power of 2 may be wrong. EXAMPLES: @@ -2281,8 +2276,10 @@ def abvarquo_rational_cuspidal_subgroup(self): sage: [A.abelian_variety().rational_torsion_subgroup().multiple_of_order() for A in D] [1, 5, 5] """ - try: return self.__abvarquo_rational_cuspidal_subgroup - except AttributeError: pass + try: + return self.__abvarquo_rational_cuspidal_subgroup + except AttributeError: + pass if self.base_ring() != QQ: raise ValueError("base ring must be QQ") if self.weight() != 2: @@ -2294,7 +2291,7 @@ def abvarquo_rational_cuspidal_subgroup(self): if N.is_squarefree(): return self.abvarquo_cuspidal_subgroup() - M = self.ambient_module() + M = self.ambient_module() phi = self.integral_period_mapping() # Make a list of all the finite cusps. @@ -2307,17 +2304,17 @@ def abvarquo_rational_cuspidal_subgroup(self): # Compute the images of the cusp classes (c)-(oo) in the # rational homology of the quotient modular abelian variety. - ims = [phi(M([c,infinity])) for c in P] + ims = [phi(M([c, infinity])) for c in P] # Take the span of the ims over ZZ A = phi.codomain().span(ims, ZZ) # The cuspidal subgroup is then the quotient of that module + # H_1(A) by H_1(A) - C = (A.ambient_module() + A)/A.ambient_module() + C = (A.ambient_module() + A) / A.ambient_module() # Make fgp module version of V. - D = V/V.zero_submodule() + D = V / V.zero_submodule() psi = D.hom([C(x) for x in ims]) # The rational cuspidal subgroup is got by intersecting kernels @@ -2326,7 +2323,8 @@ def abvarquo_rational_cuspidal_subgroup(self): CQ = C for t in G: T = self._matrix_of_galois_action(t, P) - 1 - if not T: continue + if not T: + continue im_gens = [psi(psi.lift(g).lift() * T) for g in CQ.gens()] h = CQ.hom(im_gens) CQ = h.kernel() @@ -2340,9 +2338,10 @@ def _matrix_of_galois_action(self, t, P): """ Compute the matrix of the action of the element of the cyclotomic Galois group defined by t on the set of cusps in P - (which is the set of finite cusps). This function is used - internally by the (rational) cuspidal subgroup and quotient - functions. + (which is the set of finite cusps). + + This function is used internally by the (rational) cuspidal + subgroup and quotient functions. INPUT: @@ -2385,15 +2384,18 @@ def _matrix_of_galois_action(self, t, P): d = c.galois_action(t, N) for j, e in enumerate(P): if d.is_gamma0_equiv(e, N, False): - A[i,j] = 1 + A[i, j] = 1 A.set_immutable() return A + class PeriodMapping(SageObject): r""" Base class for representing a period mapping attached to a space of modular - symbols. To be used via the derived classes RationalPeriodMapping and - IntegralPeriodMapping. + symbols. + + To be used via the derived classes :class:`RationalPeriodMapping` and + :class:`IntegralPeriodMapping`. """ def __init__(self, modsym, A): r""" @@ -2437,11 +2439,11 @@ def __call__(self, x): sage: M(vector([1,0,2])) (0, 9/4) """ - if is_FreeModuleElement(x): + if isinstance(x, FreeModuleElement): v = x else: v = self.__domain(x).element() - return v*self.__A + return v * self.__A def matrix(self): r""" @@ -2485,27 +2487,28 @@ def codomain(self): """ return self.__A.row_module() + class RationalPeriodMapping(PeriodMapping): def _repr_(self): """ - Return the string representation of self. + Return the string representation of ``self``. EXAMPLES:: sage: ModularSymbols(40,2).rational_period_mapping()._repr_() 'Rational period mapping associated to Modular Symbols space of dimension 13 for Gamma_0(40) of weight 2 with sign 0 over Rational Field' """ - return "Rational period mapping associated to %s"%self.modular_symbols_space() + return "Rational period mapping associated to %s" % self.modular_symbols_space() class IntegralPeriodMapping(PeriodMapping): def _repr_(self): """ - Return the string representation of self. + Return the string representation of ``self``. EXAMPLES:: sage: ModularSymbols(40,2).cuspidal_submodule().integral_period_mapping()._repr_() 'Integral period mapping associated to Modular Symbols subspace of dimension 6 of Modular Symbols space of dimension 13 for Gamma_0(40) of weight 2 with sign 0 over Rational Field' """ - return "Integral period mapping associated to %s"%self.modular_symbols_space() + return "Integral period mapping associated to %s" % self.modular_symbols_space() diff --git a/src/sage/modules/vector_mod2_dense.pyx b/src/sage/modules/vector_mod2_dense.pyx index 026beb08a21..d01fd24b5a9 100644 --- a/src/sage/modules/vector_mod2_dense.pyx +++ b/src/sage/modules/vector_mod2_dense.pyx @@ -348,6 +348,7 @@ cdef class Vector_mod2_dense(free_module_element.FreeModuleElement): cpdef _dot_product_(self, Vector right): """ EXAMPLES:: + sage: VS = VectorSpace(GF(2),3) sage: v = VS([1,1,1]); w = VS([0,0,0]) sage: v * w, w * v #indirect doctest diff --git a/src/sage/modules/with_basis/indexed_element.pyx b/src/sage/modules/with_basis/indexed_element.pyx index 4026f1947b5..58f0b04e6cd 100644 --- a/src/sage/modules/with_basis/indexed_element.pyx +++ b/src/sage/modules/with_basis/indexed_element.pyx @@ -21,7 +21,7 @@ from sage.structure.element cimport parent from sage.structure.richcmp cimport richcmp, rich_to_bool from cpython.object cimport Py_NE, Py_EQ -from sage.misc.misc import repr_lincomb +from sage.misc.repr import repr_lincomb from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute from sage.typeset.ascii_art import AsciiArt, empty_ascii_art, ascii_art @@ -307,7 +307,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): sage: ascii_art(M.zero()) 0 """ - from sage.misc.misc import coeff_repr + from sage.misc.repr import coeff_repr terms = self._sorted_items_for_printing() scalar_mult = self._parent._print_options['scalar_mult'] repr_monomial = self._parent._ascii_art_term @@ -376,7 +376,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): sage: unicode_art([M.zero()]) # indirect doctest [ 0 ] """ - from sage.misc.misc import coeff_repr + from sage.misc.repr import coeff_repr terms = self._sorted_items_for_printing() scalar_mult = self._parent._print_options['scalar_mult'] repr_monomial = self._parent._unicode_art_term @@ -886,20 +886,6 @@ cdef class IndexedFreeModuleElement(ModuleElement): x_inv = B(x) ** -1 return type(self)(F, scal(x_inv, D)) - def __div__(left, right): - """ - Forward old-style division to true division. - - EXAMPLES:: - - sage: F = CombinatorialFreeModule(QQ, [1,2,3]) - sage: x = F._from_dict({1:2, 2:3}) - sage: x/2 - B[1] + 3/2*B[2] - """ - return left / right - - def _unpickle_element(C, d): """ Unpickle an element in ``C`` given by ``d``. diff --git a/src/sage/plot/plot.py b/src/sage/plot/plot.py index 8e2c7e65430..6d518629bd5 100644 --- a/src/sage/plot/plot.py +++ b/src/sage/plot/plot.py @@ -1459,17 +1459,17 @@ def h2(x): return -abs(sqrt(x**3 - 1)) .. PLOT:: - g = plot(sin(x), (x,0,10), plot_points=20, linestyle='', marker='.') + g = plot(sin(x), (x, 0, 10), plot_points=20, linestyle='', marker='.') sphinx_plot(g) The marker can be a TeX symbol as well:: - sage: plot(sin(x), (x,0,10), plot_points=20, linestyle='', marker=r'$\checkmark$') + sage: plot(sin(x), (x, 0, 10), plot_points=20, linestyle='', marker=r'$\checkmark$') Graphics object consisting of 1 graphics primitive .. PLOT:: - g = plot(sin(x), (x,0,10), plot_points=20, linestyle='', marker=r'$\checkmark$') + g = plot(sin(x), (x, 0, 10), plot_points=20, linestyle='', marker=r'$\checkmark$') sphinx_plot(g) Sage currently ignores points that cannot be evaluated @@ -1477,7 +1477,7 @@ def h2(x): return -abs(sqrt(x**3 - 1)) :: sage: set_verbose(-1) - sage: plot(-x*log(x), (x,0,1)) # this works fine since the failed endpoint is just skipped. + sage: plot(-x*log(x), (x, 0, 1)) # this works fine since the failed endpoint is just skipped. Graphics object consisting of 1 graphics primitive sage: set_verbose(0) @@ -1487,42 +1487,42 @@ def h2(x): return -abs(sqrt(x**3 - 1)) :: sage: set_verbose(-1) - sage: plot(x^(1/3), (x,-1,1)) + sage: plot(x^(1/3), (x, -1, 1)) Graphics object consisting of 1 graphics primitive sage: set_verbose(0) .. PLOT:: set_verbose(-1) - g = plot(x**(1.0/3.0), (x,-1,1)) + g = plot(x**(1.0/3.0), (x, -1, 1)) sphinx_plot(g) set_verbose(0) - Plotting the real cube root function for negative input - requires avoiding the complex numbers one would usually get. - The easiest way is to use absolute value:: + Plotting the real cube root function for negative input requires avoiding + the complex numbers one would usually get. The easiest way is to use + :class:`real_nth_root(x, n)` :: - sage: plot(sign(x)*abs(x)^(1/3), (x,-1,1)) + sage: plot(real_nth_root(x, 3), (x, -1, 1)) Graphics object consisting of 1 graphics primitive .. PLOT:: - g = plot(sign(x)*abs(x)**(1.0/3.0), (x,-1,1)) + g = plot(real_nth_root(x, 3), (x, -1, 1)) sphinx_plot(g) - We can also use the following:: + We can also get the same plot in the following way:: - sage: plot(sign(x)*(x*sign(x))^(1/3), (x,-4,4)) + sage: plot(sign(x)*abs(x)^(1/3), (x, -1, 1)) Graphics object consisting of 1 graphics primitive .. PLOT:: - g = plot(sign(x)*(x*sign(x))**(1.0/3.0), (x,-4,4)) + g = plot(sign(x)*abs(x)**(1./3.), (x, -1, 1)) sphinx_plot(g) - A way that points to how to plot other functions without - symbolic variants is using lambda functions:: + A way to plot other functions without symbolic variants is to use lambda + functions:: sage: plot(lambda x : RR(x).nth_root(3), (x,-1, 1)) Graphics object consisting of 1 graphics primitive @@ -2277,7 +2277,7 @@ def golden_rainbow(i,lightness=0.4): for x in excluded_points], []) else: initial_points = None - + # If we are a log scale plot on the x axis, do a change of variables # so we sample the range in log scale is_log_scale = ('scale' in options.keys() and @@ -2306,7 +2306,7 @@ def golden_rainbow(i,lightness=0.4): # add an exclusion point. if abs(data[i+1][0] - data[i][0]) > 2*average_distance_between_points: excluded_points.append((data[i][0] + data[i+1][0])/2) - + # If we did a change in variables, undo it now if is_log_scale: for i,(a,fa) in enumerate(data): diff --git a/src/sage/plot/plot3d/base.pyx b/src/sage/plot/plot3d/base.pyx index 2d53ec23945..a2740add101 100644 --- a/src/sage/plot/plot3d/base.pyx +++ b/src/sage/plot/plot3d/base.pyx @@ -374,6 +374,7 @@ cdef class Graphics3d(SageObject): js_options['decimals'] = options.get('decimals', 2) js_options['frame'] = options.get('frame', True) js_options['projection'] = options.get('projection', 'perspective') + js_options['viewpoint'] = options.get('viewpoint', False) if js_options['projection'] not in ['perspective', 'orthographic']: import warnings @@ -384,6 +385,19 @@ cdef class Graphics3d(SageObject): js_options['aspectRatio'] = [float(i) for i in js_options['aspectRatio']] js_options['decimals'] = int(js_options['decimals']) + if js_options['viewpoint']: + if len(js_options['viewpoint']) != 2 or len(js_options['viewpoint'][0]) != 3: + import warnings + warnings.warn('viewpoint must be of the form [[x,y,z],angle]') + js_options['viewpoint'] = False + else: + if type(js_options['viewpoint']) is tuple: + js_options['viewpoint'] = list(js_options['viewpoint']) + if type(js_options['viewpoint'][0]) is tuple: + js_options['viewpoint'][0] = list(js_options['viewpoint'][0]) + js_options['viewpoint'][0] = [float(i) for i in js_options['viewpoint'][0]] + js_options['viewpoint'][1] = float(js_options['viewpoint'][1]) + if not js_options['frame']: js_options['axesLabels'] = False diff --git a/src/sage/plot/plot3d/index_face_set.pyx b/src/sage/plot/plot3d/index_face_set.pyx index 1f6b5422240..41a47560a07 100644 --- a/src/sage/plot/plot3d/index_face_set.pyx +++ b/src/sage/plot/plot3d/index_face_set.pyx @@ -1799,25 +1799,6 @@ cdef class VertexIter: self.set.vs[self.i-1].z) -def len3d(v): - """ - Return the norm of a vector in three dimensions. - - This is deprecated since :trac:`27450` . - - EXAMPLES:: - - sage: from sage.plot.plot3d.index_face_set import len3d - sage: len3d((1,2,3)) - doctest:warning...: - DeprecationWarning: len3d is deprecated, use point_c_len instead - See https://trac.sagemath.org/27450 for details. - 3.7416573867739413 - """ - deprecation(27450, "len3d is deprecated, use point_c_len instead") - return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) - - def sticker(face, width, hover): """ Return a sticker over the given face. diff --git a/src/sage/quivers/algebra_elements.pyx b/src/sage/quivers/algebra_elements.pyx index 25968539fcf..55d1f0986ab 100644 --- a/src/sage/quivers/algebra_elements.pyx +++ b/src/sage/quivers/algebra_elements.pyx @@ -18,7 +18,7 @@ AUTHORS: include "algebra_elements.pxi" from sage.misc.cachefunc import cached_method -from sage.misc.misc import repr_lincomb +from sage.misc.repr import repr_lincomb from sage.structure.richcmp cimport richcmp_not_equal, rich_to_bool @@ -1301,9 +1301,6 @@ cdef class PathAlgebraElement(RingElement): return sample._new_(homog_poly_scale((self).data, x)) raise TypeError("Don't know how to divide {} by {}".format(x, self)) - def __div__(self, x): - return self / x - ## Multiplication in the algebra cpdef _mul_(self, other): diff --git a/src/sage/repl/display/jsmol_iframe.py b/src/sage/repl/display/jsmol_iframe.py index 82b897fc11c..d8de42f7bb7 100644 --- a/src/sage/repl/display/jsmol_iframe.py +++ b/src/sage/repl/display/jsmol_iframe.py @@ -47,6 +47,7 @@