diff --git a/.ci/create-changes-html.sh b/.ci/create-changes-html.sh index cce6c45acac..cdae3a49df8 100755 --- a/.ci/create-changes-html.sh +++ b/.ci/create-changes-html.sh @@ -62,10 +62,12 @@ for block in diff_blocks: match = re.search(r'^diff --git a/(.*) b/\1', block, flags=re.MULTILINE) if match: doc = match.group(1) - path = 'html/' + doc file_path = os.path.join('$DOC_REPOSITORY', doc) - with open(file_path, 'r') as file: - content = file.readlines() + try: + with open(file_path, 'r') as file: + content = file.readlines() + except FileNotFoundError: + content = [] count = 0 for line in block.splitlines(): if line.startswith('@@ -'): @@ -77,8 +79,10 @@ for block in diff_blocks: count += 1 content[i] = f'' + content[i] break - with open(file_path, 'w') as file: - file.writelines(content) + if content: + with open(file_path, 'w') as file: + file.writelines(content) + path = 'html/' + doc hunks = ' '.join(f'#{i + 1}' for i in range(count)) out_blocks.append(f'

{doc} ' + hunks + ' 

' + '\n
'
diff --git a/.github/sync_labels.py b/.github/sync_labels.py
index 97ddf039a16..dbd8566c517 100755
--- a/.github/sync_labels.py
+++ b/.github/sync_labels.py
@@ -141,6 +141,7 @@ def __init__(self, url, actor):
         self._commits = None
         self._commit_date = None
         self._bot_login = None
+        self._gh_version = None
 
         s = url.split('/')
         self._owner = s[3]
@@ -235,13 +236,30 @@ def bot_login(self):
         """
         if self._bot_login:
             return self._bot_login
-        cmd = 'gh auth status'
         from subprocess import run
+        cmd = 'gh version'
+        capt = run(cmd, shell=True, capture_output=True)
+        self._gh_version = str(capt.stdout).split('\\n')[0]
+        info('version: %s' % self._gh_version)
+        cmd = 'gh auth status'
         capt = run(cmd, shell=True, capture_output=True)
-        l = str(capt.stderr).split()
-        if not 'as' in l:
-            l = str(capt.stdout).split()
-        self._bot_login = l[l.index('as')+1]
+        errtxt = str(capt.stderr)
+        outtxt = str(capt.stdout)
+        debug('auth status err: %s' % errtxt)
+        debug('auth status out: %s' % outtxt)
+        def read_login(txt, position_mark):
+            for t in txt:
+                for p in position_mark:
+                    # the output text has changed from as to account
+                    # around version 2.40.0
+                    l = t.split()
+                    if p in l:
+                        return l[l.index(p)+1]
+        self._bot_login = read_login([errtxt, outtxt], ['account', 'as'])
+        if not self._bot_login:
+            self._bot_login = default_bot
+            warning('Bot is unknown')
+            return self._bot_login
         if self._bot_login.endswith('[bot]'):
             self._bot_login = self._bot_login.split('[bot]')[0]
         info('Bot is %s' % self._bot_login)
diff --git a/.github/workflows/ci-conda-known-test-failures.json b/.github/workflows/ci-conda-known-test-failures.json
new file mode 100644
index 00000000000..2d828ac98fb
--- /dev/null
+++ b/.github/workflows/ci-conda-known-test-failures.json
@@ -0,0 +1,53 @@
+{
+    "sage_setup.clean": {
+        "failed": true
+    },
+    "sage.combinat.cluster_algebra_quiver.quiver": {
+        "failed": true
+    },
+    "sage.geometry.cone": {
+        "failed": true
+    },
+    "sage.groups.matrix_gps.finitely_generated_gap": {
+        "failed": true
+    },
+    "sage.interfaces.expect": {
+        "failed": true
+    },
+    "sage.libs.gap.element": {
+        "failed": true
+    },
+    "sage.libs.singular.singular": {
+        "failed": true
+    },
+    "sage.matrix.matrix2": {
+        "failed": true
+    },
+    "sage.matrix.matrix_integer_sparse": {
+        "failed": true
+    },
+    "sage.misc.lazy_import": {
+        "failed": true
+    },
+    "sage.misc.weak_dict": {
+        "failed": true
+    },
+    "sage.modular.modform.l_series_gross_zagier": {
+        "failed": true
+    },
+    "sage.rings.function_field.drinfeld_modules.morphism": {
+        "failed": true
+    },
+    "sage.rings.polynomial.multi_polynomial_ideal": {
+        "failed": true
+    },
+    "sage.rings.polynomial.multi_polynomial_libsingular": {
+        "failed": true
+    },
+    "sage.rings.polynomial.skew_polynomial_finite_field": {
+        "failed": true
+    },
+    "sage.tests.gap_packages": {
+        "failed": true
+    }
+}
diff --git a/.github/workflows/ci-conda.yml b/.github/workflows/ci-conda.yml
index 3b928fef58d..6432b7a0a6a 100644
--- a/.github/workflows/ci-conda.yml
+++ b/.github/workflows/ci-conda.yml
@@ -92,7 +92,7 @@ jobs:
       - name: Test
         if: success() || failure()
         shell: bash -l {0}
-        run: ./sage -t --all -p0
+        run: ./sage -t --all --baseline-stats-path=.github/workflows/ci-conda-known-test-failures.json -p0
 
       - name: Print logs
         if: always()
diff --git a/CITATION.cff b/CITATION.cff
index d05315fa584..cc293723139 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -4,8 +4,8 @@ title: SageMath
 abstract: SageMath is a free open-source mathematics software system.
 authors:
 - name: "The SageMath Developers"
-version: 10.3.beta2
+version: 10.3.beta3
 doi: 10.5281/zenodo.593563
-date-released: 2023-12-13
+date-released: 2023-12-18
 repository-code: "https://github.com/sagemath/sage"
 url: "https://www.sagemath.org/"
diff --git a/README.md b/README.md
index 4720c2b87ab..422582292af 100644
--- a/README.md
+++ b/README.md
@@ -424,8 +424,8 @@ For installation of `sage` in python using `pip` you need to install `sagemath-s
 
             $ python3 -m pip install sage_conf
             $ ls $(sage-config SAGE_SPKG_WHEELS)
-            $ python3 -m pip install $(sage-config SAGE_SPKG_WHEELS)/*.whl
-            $ python3 -m pip install sagemath-standard
+            $ python3 -m pip install $(sage-config SAGE_SPKG_WHEELS)/*.whl sage_setup
+            $ python3 -m pip install --no-build-isolation sagemath-standard
 
 You need to install `sage_conf`, a wheelhouse of various python packages. You can list the wheels using `ls $(sage-config SAGE_SPKG_WHEELS)`. After manual installation of these wheels, you can install the sage library, `sagemath-standard`.
 
diff --git a/VERSION.txt b/VERSION.txt
index a6d9f480c1e..ed9ec729cc0 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1 +1 @@
-SageMath version 10.3.beta2, Release Date: 2023-12-13
+SageMath version 10.3.beta3, Release Date: 2023-12-18
diff --git a/build/bin/sage-dist-helpers b/build/bin/sage-dist-helpers
index 2dc56b62466..9def7350a37 100644
--- a/build/bin/sage-dist-helpers
+++ b/build/bin/sage-dist-helpers
@@ -289,8 +289,6 @@ sdh_pip_install() {
 
 sdh_pip_editable_install() {
     echo "Installing $PKG_NAME (editable mode)"
-    # Until https://github.com/sagemath/sage/issues/34209 switches us to PEP 660 editable wheels
-    export SETUPTOOLS_ENABLE_FEATURES=legacy-editable
     python3 -m pip install --verbose --no-deps --no-index --no-build-isolation --isolated --editable "$@" || \
         sdh_die "Error installing $PKG_NAME"
 }
diff --git a/build/bin/sage-spkg b/build/bin/sage-spkg
index ed2e9090cf8..70256370549 100755
--- a/build/bin/sage-spkg
+++ b/build/bin/sage-spkg
@@ -341,7 +341,7 @@ fi
 PKG_BASE_VER=`echo $PKG_VER | sed 's/\.p[0-9][0-9]*$//'`
 if [ -f "$PKG_SCRIPTS/checksums.ini" ]; then
     # Normal/wheel package
-    PKG_NAME_UPSTREAM=`lookup_param tarball "$PKG_SCRIPTS/checksums.ini" | sed "s/VERSION/$PKG_BASE_VER/"`
+    PKG_NAME_UPSTREAM=`lookup_param tarball "$PKG_SCRIPTS/checksums.ini"`
 fi
 
 # Set the $SAGE_DESTDIR variable to be passed to the spkg-install
@@ -396,11 +396,11 @@ ensure_pkg_src() { ###############################################
 if [ ! -f "$PKG_SRC" ]; then
     if [ -n "$PKG_NAME_UPSTREAM" ]; then
         # Normal or wheel package
-        if ! sage-download-file $SAGE_DOWNLOAD_FILE_OPTIONS "$PKG_NAME_UPSTREAM"; then
-            error_msg "Error downloading $PKG_NAME_UPSTREAM"
+        PKG_SRC=$(sage-package download $SAGE_DOWNLOAD_FILE_OPTIONS $PKG_BASE)
+        if [ $? != 0 ]; then
+            error_msg "Error downloading tarball of $PKG_BASE"
             exit 1
         fi
-        PKG_SRC="$SAGE_DISTFILES/$PKG_NAME_UPSTREAM"
         # Do a final check that PKG_SRC is a file with an absolute path
         cd /
         if [ ! -f "$PKG_SRC" ]; then
diff --git a/build/pkgs/4ti2/checksums.ini b/build/pkgs/4ti2/checksums.ini
index 275cf157a6a..047f39ece1d 100644
--- a/build/pkgs/4ti2/checksums.ini
+++ b/build/pkgs/4ti2/checksums.ini
@@ -1,5 +1,5 @@
-tarball=4ti2-VERSION.tar.gz
+tarball=4ti2-${VERSION}.tar.gz
 sha1=3d41f30ea3ef94c293eae30c087494269fc1a6b9
 md5=1215872325ddfc561865ecb22b2bccb2
 cksum=2439180289
-upstream_url=https://github.com/4ti2/4ti2/releases/download/Release_1_6_10/4ti2-1.6.10.tar.gz
+upstream_url=https://github.com/4ti2/4ti2/releases/download/Release_${VERSION_MAJOR}_${VERSION_MINOR}_${VERSION_MICRO}/4ti2-${VERSION}.tar.gz
diff --git a/build/pkgs/boost_cropped/spkg-legacy-uninstall b/build/pkgs/boost_cropped/spkg-legacy-uninstall
deleted file mode 100755
index ef170323b6a..00000000000
--- a/build/pkgs/boost_cropped/spkg-legacy-uninstall
+++ /dev/null
@@ -1,2 +0,0 @@
-#! /usr/bin/env bash
-rm -rf "${SAGE_LOCAL}"/include/boost
diff --git a/build/pkgs/brial/spkg-legacy-uninstall b/build/pkgs/brial/spkg-legacy-uninstall
deleted file mode 100755
index 59e01958685..00000000000
--- a/build/pkgs/brial/spkg-legacy-uninstall
+++ /dev/null
@@ -1,5 +0,0 @@
-#! /usr/bin/env bash
-echo "Cleaning out old PolyBoRi and BRiAl installations"
-rm -f "$SAGE_LOCAL"/lib/lib{polybori,brial}*
-rm -rf "$SAGE_LOCAL"/include/polybori*
-rm -rf "$SAGE_LOCAL"/share/polybori
diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini
index deadbf67dc3..bdd96db7026 100644
--- a/build/pkgs/configure/checksums.ini
+++ b/build/pkgs/configure/checksums.ini
@@ -1,4 +1,4 @@
 tarball=configure-VERSION.tar.gz
-sha1=7f2fe8137f5998559f8d3a0a7f965adf6d5ebc78
-md5=586738771174a2d26d29ce7ba571a95d
-cksum=1156937644
+sha1=2389d2b093493c568deda190ffc326ff2b835169
+md5=545e80b50deb4efa46f14d0a543ba98f
+cksum=169905223
diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt
index 1596e721179..033bf42aaae 100644
--- a/build/pkgs/configure/package-version.txt
+++ b/build/pkgs/configure/package-version.txt
@@ -1 +1 @@
-2395f7bb45fd7da537a953055df5e0a70dd96c6a
+73e52a419812253c3c3ce72bab7f1a5ddf4c0461
diff --git a/build/pkgs/cunningham_tables/checksums.ini b/build/pkgs/cunningham_tables/checksums.ini
index d6de22c0850..360d9b647b7 100644
--- a/build/pkgs/cunningham_tables/checksums.ini
+++ b/build/pkgs/cunningham_tables/checksums.ini
@@ -2,4 +2,4 @@ tarball=cunningham_tables-VERSION.tar.gz
 sha1=8bea1a113d85bb9c37d8f213dd19525d9d026f22
 md5=e71b32f12e9a46c1c86e275e8441a06b
 cksum=1990403877
-upstream_url=http://users.ox.ac.uk/~coml0531/sage/cunningham_tables-1.0.tar.gz
+upstream_url=http://users.ox.ac.uk/~coml0531/sage/cunningham_tables-VERSION.tar.gz
diff --git a/build/pkgs/cvxopt/spkg-install.in b/build/pkgs/cvxopt/spkg-install.in
index 0081c40f819..8179125fc9a 100644
--- a/build/pkgs/cvxopt/spkg-install.in
+++ b/build/pkgs/cvxopt/spkg-install.in
@@ -82,10 +82,6 @@ sdh_pip_install .
 
 if [ "x$SAGE_SPKG_INSTALL_DOCS" = xyes ] ; then
    cd doc
-   # checking to see if there is previously installed documentation.
-   if [ -d $SAGE_LOCAL/share/doc/cvxopt/html ] ; then
-      rm -rf $SAGE_LOCAL/share/doc/cvxopt/html
-   fi
    mkdir -p "${SAGE_DESTDIR}${SAGE_LOCAL}/share/doc/cvxopt/html"
    cp -r html/* "${SAGE_DESTDIR}${SAGE_LOCAL}/share/doc/cvxopt/html/"
 fi
diff --git a/build/pkgs/ecl/spkg-legacy-uninstall b/build/pkgs/ecl/spkg-legacy-uninstall
deleted file mode 100755
index 375d99ebcf1..00000000000
--- a/build/pkgs/ecl/spkg-legacy-uninstall
+++ /dev/null
@@ -1,6 +0,0 @@
-#! /usr/bin/env bash
-rm -f "$SAGE_LOCAL"/bin/ecl
-rm -rf "$SAGE_LOCAL"/include/ecl
-rm -rf "$SAGE_LOCAL"/lib/ecl
-rm -rf "$SAGE_LOCAL"/lib/ecl-*
-rm -rf "$SAGE_LOCAL"/lib/libecl.*
diff --git a/build/pkgs/eclib/spkg-install.in b/build/pkgs/eclib/spkg-install.in
index 2a83bdb5235..4998cff1676 100644
--- a/build/pkgs/eclib/spkg-install.in
+++ b/build/pkgs/eclib/spkg-install.in
@@ -3,17 +3,6 @@ CXXFLAGS="$CXXFLAGS_O3_NON_NATIVE"
 
 export CFLAGS CXXFLAGS
 
-
-echo "Deleting old versions of eclib libraries, which"
-echo "would interfere with new builds..."
-# Delete any pre-autotools libraries:
-rm -f "$SAGE_LOCAL"/lib/lib{curvesntl,g0nntl,jcntl,rankntl,mwrank}.*
-# Delete autotools libraries:
-rm -f "$SAGE_LOCAL"/lib/lib{e,j}c.*
-echo "Deleting old include directory..."
-rm -rf "$SAGE_LOCAL"/include/eclib/
-
-
 cd src/
 
 #############################################################
diff --git a/build/pkgs/ecm/spkg-install.in b/build/pkgs/ecm/spkg-install.in
index edc6bb2df5f..03c6c109d7d 100644
--- a/build/pkgs/ecm/spkg-install.in
+++ b/build/pkgs/ecm/spkg-install.in
@@ -232,17 +232,6 @@ sdh_configure $SAGE_CONFIGURE_GMP $ECM_CONFIGURE
 
 sdh_make
 
-###############################################################################
-# Remove old executable/header/libraries/manpage:
-###############################################################################
-
-echo
-echo "Build succeeded.  Now removing old binary, header file, manual page and libraries..."
-rm -f "$SAGE_LOCAL"/bin/ecm
-rm -f "$SAGE_LOCAL"/lib/libecm.*
-rm -f "$SAGE_LOCAL"/include/ecm.h
-rm -f "$SAGE_LOCAL"/share/man/man1/ecm.1
-
 ###############################################################################
 # Now install ECM:
 ###############################################################################
diff --git a/build/pkgs/fplll/spkg-install.in b/build/pkgs/fplll/spkg-install.in
index 68c0cb70ad2..d898db82687 100644
--- a/build/pkgs/fplll/spkg-install.in
+++ b/build/pkgs/fplll/spkg-install.in
@@ -22,7 +22,3 @@ export CXX="$CXX"
 sdh_configure $CONFIGUREFLAGS
 sdh_make
 sdh_make_install
-
-# Pretend that the "libfplll" package is not installed. This is needed to
-# support renaming libfplll -> fplll done on Trac #24042
-rm -f "$SAGE_SPKG_INST/"libfplll-*
diff --git a/build/pkgs/freetype/spkg-install.in b/build/pkgs/freetype/spkg-install.in
index c1615626e72..9d8c0075ec0 100644
--- a/build/pkgs/freetype/spkg-install.in
+++ b/build/pkgs/freetype/spkg-install.in
@@ -4,9 +4,3 @@ cd src
 GNUMAKE=${MAKE} sdh_configure --enable-freetype-config $FREETYPE_CONFIGURE
 sdh_make
 sdh_make_install
-
-# The following file may be present from old builds of freetype, and
-# its presence can break the matplotlib build. So remove it. (The
-# current version is $SAGE_LOCAL/include/freetype2/ftbuild.h.)
-
-rm -f "$SAGE_LOCAL/include/ft2build.h"
diff --git a/build/pkgs/frobby/spkg-install.in b/build/pkgs/frobby/spkg-install.in
index 1a97cc6b682..60ed2c67207 100644
--- a/build/pkgs/frobby/spkg-install.in
+++ b/build/pkgs/frobby/spkg-install.in
@@ -1,4 +1,3 @@
-rm -rf "$SAGE_LOCAL/bin/frobby"
 
 GMP_INC_DIR="$SAGE_LOCAL/include"; export GMP_INC_DIR
 ldflags="-Wl,-rpath,$SAGE_LOCAL/lib -L$SAGE_LOCAL/lib/ -lgmpxx -lgmp"; export ldflags
@@ -12,14 +11,4 @@ if [ $? -ne 0 ]; then
    exit 1
 fi
 
-if [ ! -f bin/release/frobby ]; then
-   echo "Frobby executable not found."
-   exit 1
-fi
-
-$CP bin/release/frobby "$SAGE_LOCAL/bin/"
-
-if [ ! -f "$SAGE_LOCAL/bin/frobby" ]; then
-   echo "Frobby executable not copied."
-   exit 1
-fi
+sdh_install bin/release/frobby "$SAGE_LOCAL/bin"
diff --git a/build/pkgs/frobby/spkg-legacy-uninstall b/build/pkgs/frobby/spkg-legacy-uninstall
new file mode 100755
index 00000000000..a262741b2a9
--- /dev/null
+++ b/build/pkgs/frobby/spkg-legacy-uninstall
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+rm -f "$SAGE_LOCAL/bin/frobby"
diff --git a/build/pkgs/gap/distros/debian.txt b/build/pkgs/gap/distros/debian.txt
index 883a55b9c20..66efa2824a6 100644
--- a/build/pkgs/gap/distros/debian.txt
+++ b/build/pkgs/gap/distros/debian.txt
@@ -1 +1,2 @@
+gap
 libgap-dev
diff --git a/build/pkgs/gap/distros/fedora.txt b/build/pkgs/gap/distros/fedora.txt
new file mode 100644
index 00000000000..6c561683bf4
--- /dev/null
+++ b/build/pkgs/gap/distros/fedora.txt
@@ -0,0 +1,5 @@
+gap
+gap-core
+gap-devel
+gap-libs
+libgap
diff --git a/build/pkgs/gap/spkg-configure.m4 b/build/pkgs/gap/spkg-configure.m4
new file mode 100644
index 00000000000..72a0a999da7
--- /dev/null
+++ b/build/pkgs/gap/spkg-configure.m4
@@ -0,0 +1,96 @@
+SAGE_SPKG_CONFIGURE([gap], [
+  # Default to installing the SPKG, if the check is run at all.
+  sage_spkg_install_gap=yes
+
+  m4_pushdef([GAP_MINVER],["4.12.2"])
+
+  SAGE_SPKG_DEPCHECK([ncurses readline zlib], [
+    AC_PATH_PROG(GAP, gap)
+    AS_IF([test -n "${GAP}"], [
+      AC_MSG_CHECKING([for gap version GAP_MINVER or newer])
+
+      # GAP will later add the "user" path to the list of root paths
+      # so long as we don't initialize GAP with -r in Sage. But we
+      # don't want to include it in the hard-coded list.
+      GAPRUN="${GAP} -r -q --bare --nointeract -c"
+      _cmd='Display(GAPInfo.KernelInfo.KERNEL_VERSION);'
+      GAP_VERSION=$( ${GAPRUN} "${_cmd}" 2>/dev/null )
+      AX_COMPARE_VERSION(["${GAP_VERSION}"], [ge], [GAP_MINVER], [
+        AC_MSG_RESULT([yes])
+        AC_MSG_CHECKING([for gap root paths])
+        _cmd='Display(JoinStringsWithSeparator(GAPInfo.RootPaths,";"));'
+        SYS_GAP_ROOT_PATHS=$( ${GAPRUN} "${_cmd}" 2>/dev/null )
+        AC_MSG_RESULT([$SYS_GAP_ROOT_PATHS])
+        AS_IF([test -n "${SYS_GAP_ROOT_PATHS}"], [
+          AC_MSG_CHECKING([for the PrimGrp, SmallGrp, and TransGrp packages])
+          # Check for a very minimal set of packages without which the
+          # sage test suite will fail. The crazy thing below is a
+          # "quadrigraph" for a square bracket.
+          _cmd="Display(@<:@"
+          _cmd="${_cmd} TestPackageAvailability(\"PrimGrp\"),"
+          _cmd="${_cmd} TestPackageAvailability(\"SmallGrp\"),"
+          _cmd="${_cmd} TestPackageAvailability(\"TransGrp\")"
+          _cmd="${_cmd} @:>@);"
+          _output=$( ${GAPRUN} "${_cmd}" 2>/dev/null )
+          AS_IF([test $? -eq 0], [
+            AS_CASE([$_output],
+              [*fail*],[AC_MSG_RESULT([no (at least one package missing)])],[
+                # default case, i.e. no "fail"
+                AC_MSG_RESULT([yes])
+
+                AC_MSG_CHECKING([if we can link against libgap])
+                # That was all for the CLI. Now we check for libgap,
+                # too. There's a long list of headers we need in
+                # src/sage/libs/gap/gap_includes.pxd, but libgap-api.h
+                # combined with the version test above should be
+                # sufficient even on systems where the headers are
+                # packaged separately.
+                _old_libs=$LIBS
+                LIBS="${LIBS} -lgap"
+                AC_LANG_PUSH([C])
+                AC_LINK_IFELSE([
+                  AC_LANG_PROGRAM(
+                    [[#include ]],
+                    [[
+                      int main(int argc, char** argv) {
+                        GAP_Initialize(0, 0, 0, 0, 0);
+                        return 0;
+                      }
+                    ]])
+                ],[
+                  AC_MSG_RESULT([yes])
+                  sage_spkg_install_gap=no
+                ],[
+                  AC_MSG_RESULT([no])
+                ])
+                AC_LANG_POP
+                LIBS="${_old_libs}"
+            ])
+          ], [
+            # The gap command itself failed
+            AC_MSG_RESULT([no (package check command failed)])
+          ])
+        ])
+      ],[
+        # Version too old
+        AC_MSG_RESULT([no])
+      ])
+    ])
+  ])
+
+  m4_popdef([GAP_MINVER])
+],[],[],[
+  # This is the post-check phase, where we make sage-conf
+  # substitutions, in this case of GAP_ROOT_PATHS. We begin with the
+  # two root paths used by the sage distribution. The '${prefix}' is
+  # a magic string that sage-conf will replace.
+  GAP_ROOT_PATHS='${prefix}/lib/gap;${prefix}/share/gap';
+
+  AS_IF([test "${sage_spkg_install_gap}" = "no"],[
+    # If we're using the system GAP, append the system root
+    # paths to the existing two sage paths.
+    GAP_ROOT_PATHS="${GAP_ROOT_PATHS};${SYS_GAP_ROOT_PATHS}"
+  ])
+
+  AC_SUBST(GAP_ROOT_PATHS, "${GAP_ROOT_PATHS}")
+])
diff --git a/build/pkgs/gap/spkg-install.in b/build/pkgs/gap/spkg-install.in
index 47a7a8700ac..7d207a47a48 100644
--- a/build/pkgs/gap/spkg-install.in
+++ b/build/pkgs/gap/spkg-install.in
@@ -16,46 +16,43 @@ if [ "$SAGE_DEBUG" = yes ] ; then
     export CFLAGS="-O0 -g3 -DDEBUG_MASTERPOINTERS -DDEBUG_GLOBAL_BAGS -DDEBUG_FUNCTIONS_BAGS $CFLAGS"
 fi
 
-# LDFLAGS hack below needed by Semigroups package
-sdh_configure $SAGE_CONFIGURE_GMP LDFLAGS="-pthread" --prefix=$SAGE_LOCAL
+sdh_configure $SAGE_CONFIGURE_GMP --prefix=$SAGE_LOCAL
 sdh_make
-
 sdh_make_install
-# sdh_make install-headers install-libgap
-# The 'packagemanager' package expects this https://github.com/gap-packages/PackageManager/issues/105
-mkdir -p "$SAGE_LOCAL/lib/gap/bin"
 
 # Install only the minimal packages GAP needs to run
 sdh_install pkg/gapdoc pkg/primgrp pkg/smallgrp pkg/transgrp "$GAP_ROOT"/pkg
 
-# Install additional packages that are not strictly required, but that are
-# typically "expected" to be loaded: These are the default packages that are
-# autoloaded at GAP startup (via the PackagesToLoad UserPreference) with an
-# out-of-the-box GAP installation; see
-# https://github.com/sagemath/sage/issues/22626#comment:393 for discussion on this
-#
-# Also include atlasrep which is a dependency of tomlib
+# Install additional packages that are automatically loaded in the
+# default GAP configuration. The list can be found in lib/package.gi
+# as part of the "PackagesToLoad" user preference. Also include
+# atlasrep because it is a dependency of tomlib.
 sdh_install \
+    pkg/alnuth \
     pkg/atlasrep \
-    pkg/autodoc \
     pkg/autpgrp \
-    pkg/alnuth \
     pkg/crisp \
     pkg/ctbllib \
     pkg/factint \
     pkg/fga \
     pkg/irredsol \
     pkg/laguna \
-    pkg/packagemanager \
     pkg/polenta \
     pkg/polycyclic \
-    pkg/radiroot \
     pkg/resclasses \
     pkg/sophus \
     pkg/tomlib \
-    pkg/utils \
     "$GAP_ROOT"/pkg
 
+# Finally, install packagemanager for the people who reject both
+# sage's and their system's package managers. We have to create
+# the local bin directory first:
+#
+#   https://github.com/gap-packages/PackageManager/issues/105
+#
+mkdir -p "$SAGE_LOCAL/lib/gap/bin"
+sdh_install pkg/packagemanager "$GAP_ROOT"/pkg
+
 # TODO: This seems unnecessary--we are already installing all of doc/ to
 # GAP_ROOT, which is necessary for some functionality in GAP to work.  Do
 # we need this?  Maybe doc/gap could just be a symlink to gap/doc??
diff --git a/build/pkgs/gap/spkg-legacy-uninstall b/build/pkgs/gap/spkg-legacy-uninstall
deleted file mode 100755
index a8e5c59e1fb..00000000000
--- a/build/pkgs/gap/spkg-legacy-uninstall
+++ /dev/null
@@ -1,11 +0,0 @@
-#! /usr/bin/env bash
-# Remove existing GAP 4.x install(s)
-rm -rf "$SAGE_LOCAL/gap/gap-4."*
-rm -rf "$SAGE_SHARE/gap"
-rm -f "$SAGE_LOCAL/gap/latest"
-rm -f "$SAGE_LOCAL/bin/gap"
-rm -f "$SAGE_LOCAL/bin/gac"
-
-# Remove old libgap headers and library
-rm -rf "$SAGE_LOCAL/include/gap"
-rm -rf "$SAGE_LOCAL/lib/gap"
diff --git a/build/pkgs/gap_packages/SPKG.rst b/build/pkgs/gap_packages/SPKG.rst
index 67d3256637a..96238eef804 100644
--- a/build/pkgs/gap_packages/SPKG.rst
+++ b/build/pkgs/gap_packages/SPKG.rst
@@ -7,6 +7,20 @@ Description
 Several "official" and "undeposited" GAP packages available from
 https://www.gap-system.org/Packages/packages.html
 
+Installing this SPKG will install the corresponding GAP packages, but
+before you can use them in Sage, they still have to be loaded into
+either the GAP interface or libgap::
+
+  sage: gap.eval('LoadPackage("Grape")')  # optional - gap_packages
+  'true'
+  sage: libgap.LoadPackage("Grape")       # optional - gap_packages
+  true
+
+Those correspond to::
+
+  gap> LoadPackage("Grape");
+
+within the GAP interface and libgap, respectively.
 
 Upstream Contact
 ----------------
diff --git a/build/pkgs/gap_packages/spkg-install.in b/build/pkgs/gap_packages/spkg-install.in
index 6dff182a48f..7005cc3d322 100644
--- a/build/pkgs/gap_packages/spkg-install.in
+++ b/build/pkgs/gap_packages/spkg-install.in
@@ -1,5 +1,34 @@
-GAP_ROOT="$SAGE_LOCAL/lib/gap"
-PKG_DIR="$GAP_ROOT/pkg"
+# Ask GAP for the directory where sysinfo.gap lives. This is to
+# support system GAP installations. This root-path gathering
+# command is borrowed from gap's spkg-configure.m4 and modified
+# to separate the paths with spaces.
+GAPRUN="gap -r -q --bare --nointeract -c"
+_cmd='Display(JoinStringsWithSeparator(GAPInfo.RootPaths," "));'
+GAP_ROOT_PATHS=$(${GAPRUN} "${_cmd}")
+
+# Loop though GAP_ROOT_PATHS looking for sysinfo.gap
+GAP_ROOT=""
+for grp in $GAP_ROOT_PATHS; do
+    if [ -f "${grp}/sysinfo.gap" ]; then
+        GAP_ROOT=$grp
+        echo "found GAP root $GAP_ROOT"
+        break
+    fi
+done
+
+# Try the old sage default if nothing else worked.
+if [ -z "$GAP_ROOT" ]; then
+    GAP_ROOT="$SAGE_LOCAL/lib/gap"
+    echo "falling back to GAP root $GAP_ROOT"
+fi
+
+# And finally, throw an error ASAP if the build is going to fail anyway.
+if [ ! -f "${GAP_ROOT}/sysinfo.gap" ]; then
+    sdh_die "no sysinfo.gap in your gap root"
+fi
+
+# Where to install these packages
+PKG_DIR="$SAGE_LOCAL/lib/gap/pkg"
 
 PKG_SRC_DIR="$(pwd)/src/pkg"
 cd "$PKG_SRC_DIR"
@@ -12,6 +41,7 @@ cd "$PKG_SRC_DIR"
 
 sdh_install \
     aclib \
+    autodoc \
     corelg \
     crime \
     cryst \
@@ -31,11 +61,13 @@ sdh_install \
     polymaking \
     qpa \
     quagroup \
+    radiroot \
     repsn \
     singular \
     sla \
     sonata \
     toric \
+    utils \
     "$PKG_DIR"
 
 install_compiled_pkg()
diff --git a/build/pkgs/gf2x/checksums.ini b/build/pkgs/gf2x/checksums.ini
index 10a93c2c1bc..0eaf7657e40 100644
--- a/build/pkgs/gf2x/checksums.ini
+++ b/build/pkgs/gf2x/checksums.ini
@@ -2,4 +2,4 @@ tarball=gf2x-VERSION.tar.gz
 sha1=1b9c7e14031afc5488b9aa27f5501f78c90f00b4
 md5=842f087ce423c279dced26b85b0fd1d0
 cksum=3368093312
-upstream_url=https://gitlab.inria.fr/gf2x/gf2x/uploads/c46b1047ba841c20d1225ae73ad6e4cd/gf2x-1.3.0.tar.gz
+upstream_url=https://gitlab.inria.fr/gf2x/gf2x/uploads/c46b1047ba841c20d1225ae73ad6e4cd/gf2x-VERSION.tar.gz
diff --git a/build/pkgs/gf2x/spkg-install.in b/build/pkgs/gf2x/spkg-install.in
index 057be0a5953..c8e4c83e6d2 100644
--- a/build/pkgs/gf2x/spkg-install.in
+++ b/build/pkgs/gf2x/spkg-install.in
@@ -1,7 +1,3 @@
-echo "Deleting old gf2x files."
-rm -f "$SAGE_LOCAL"/lib/libgf2x*
-rm -rf "$SAGE_LOCAL"/include/gf2x*
-
 cd src
 
 # Use newer version of config.guess and config.sub (see Trac #19727)
diff --git a/build/pkgs/gfan/spkg-legacy-uninstall b/build/pkgs/gfan/spkg-legacy-uninstall
deleted file mode 100755
index 74552b0b71c..00000000000
--- a/build/pkgs/gfan/spkg-legacy-uninstall
+++ /dev/null
@@ -1,3 +0,0 @@
-#! /usr/bin/env bash
-echo "Removing old version of gfan (if any)..."
-rm -f "$SAGE_LOCAL"/bin/gfan*
diff --git a/build/pkgs/giac/spkg-install.in b/build/pkgs/giac/spkg-install.in
index e3eb86e6034..ceed3899be6 100644
--- a/build/pkgs/giac/spkg-install.in
+++ b/build/pkgs/giac/spkg-install.in
@@ -60,24 +60,6 @@ sdh_configure --disable-gui --disable-ao "$DISABLENLS" --enable-png=no --disable
 
 sdh_make
 
-#############################################################
-#   Clean old install
-#############################################################
-echo "Cleaning giac..."
-rm -f ${SAGE_LOCAL}/lib/libgiac*
-rm -f ${SAGE_LOCAL}/bin/icas
-rm -f ${SAGE_LOCAL}/bin/xcas
-rm -f ${SAGE_LOCAL}/bin/cas_help
-rm -f ${SAGE_LOCAL}/bin/pgiac
-rm -f ${SAGE_LOCAL}/bin/en_cas_help
-rm -f ${SAGE_LOCAL}/bin/es_cas_help
-rm -f ${SAGE_LOCAL}/bin/fr_cas_help
-rm -f ${SAGE_LOCAL}/bin/giac
-rm -f ${SAGE_LOCAL}/bin/xcasnew
-rm -rf ${SAGE_LOCAL}/share/giac
-rm -rf ${SAGE_LOCAL}/share/doc/giac
-rm -rf ${SAGE_LOCAL}/include/giac
-
 #############################################################
 #   Install
 #############################################################
diff --git a/build/pkgs/glpk/spkg-install.in b/build/pkgs/glpk/spkg-install.in
index a911c46f024..f592a562148 100644
--- a/build/pkgs/glpk/spkg-install.in
+++ b/build/pkgs/glpk/spkg-install.in
@@ -25,8 +25,4 @@ export CFLAGS LDFLAGS CPPFLAGS
 sdh_configure --with-gmp --disable-static
 sdh_make
 
-# Remove old libraries to make sure we can downgrade it if needed.
-# See https://github.com/sagemath/sage/issues/23596#comment:4 and later.
-rm -f "$SAGE_LOCAL"/lib/libglpk.*
-
 sdh_make_install
diff --git a/build/pkgs/graphs/checksums.ini b/build/pkgs/graphs/checksums.ini
index 285778ef3a7..7ec4a8a6ada 100644
--- a/build/pkgs/graphs/checksums.ini
+++ b/build/pkgs/graphs/checksums.ini
@@ -2,4 +2,4 @@ tarball=graphs-VERSION.tar.bz2
 sha1=c3b9fcbc92482efd6b7f6f3a33df5a78e1256aa1
 md5=4357919410e8ac2611c9fe643976c8ff
 cksum=2340933149
-upstream_url=http://users.ox.ac.uk/~coml0531/sage/graphs-20210214.tar.bz2
+upstream_url=http://users.ox.ac.uk/~coml0531/sage/graphs-VERSION.tar.bz2
diff --git a/build/pkgs/gsl/spkg-legacy-uninstall b/build/pkgs/gsl/spkg-legacy-uninstall
deleted file mode 100755
index 6c42f90b665..00000000000
--- a/build/pkgs/gsl/spkg-legacy-uninstall
+++ /dev/null
@@ -1,3 +0,0 @@
-#! /usr/bin/env bash
-rm -rf "$SAGE_LOCAL"/include/gsl
-rm -rf "$SAGE_LOCAL"/lib/libgsl*
diff --git a/build/pkgs/ipython/spkg-install.in b/build/pkgs/ipython/spkg-install.in
index 5096a907a95..058b1344dc2 100644
--- a/build/pkgs/ipython/spkg-install.in
+++ b/build/pkgs/ipython/spkg-install.in
@@ -1,7 +1,3 @@
-# Old installations of ipython can leave a symlink which can interfere
-# with proper installation.
-rm -f "$SAGE_LOCAL"/bin/ipython
-
 cd src
 
 sdh_pip_install .
diff --git a/build/pkgs/jmol/spkg-legacy-uninstall b/build/pkgs/jmol/spkg-legacy-uninstall
deleted file mode 100755
index 6c9bd1a2c08..00000000000
--- a/build/pkgs/jmol/spkg-legacy-uninstall
+++ /dev/null
@@ -1,5 +0,0 @@
-#! /usr/bin/env bash
-# Cleanup of previous installation
-rm -rf "${SAGE_SHARE}/jsmol/"
-rm -rf "${SAGE_SHARE}/jmol/"
-rm -f "${SAGE_LOCAL}/bin/jmol"
diff --git a/build/pkgs/kenzo/checksums.ini b/build/pkgs/kenzo/checksums.ini
index ad2a7c4cc60..7fff82ca271 100644
--- a/build/pkgs/kenzo/checksums.ini
+++ b/build/pkgs/kenzo/checksums.ini
@@ -1,5 +1,5 @@
-tarball=kenzo-1.1.10.tar.gz
-upstream_url=https://github.com/miguelmarco/kenzo/releases/download/1.1.10/kenzo-1.1.10.tar.gz
+tarball=kenzo-VERSION.tar.gz
+upstream_url=https://github.com/miguelmarco/kenzo/releases/download/VERSION/kenzo-VERSION.tar.gz
 sha1=76115aae9972090d5d51fee18592fc7a79461474
 md5=3a3d5350fb17304f03e614713e585ed4
 cksum=2981306888
diff --git a/build/pkgs/lcalc/spkg-legacy-uninstall b/build/pkgs/lcalc/spkg-legacy-uninstall
deleted file mode 100755
index 4cacdfd9367..00000000000
--- a/build/pkgs/lcalc/spkg-legacy-uninstall
+++ /dev/null
@@ -1,2 +0,0 @@
-#! /usr/bin/env bash
-rm -fr "$SAGE_LOCAL"/include/libLfunction
diff --git a/build/pkgs/libbraiding/checksums.ini b/build/pkgs/libbraiding/checksums.ini
index bb76fa5b604..466c33c66a5 100644
--- a/build/pkgs/libbraiding/checksums.ini
+++ b/build/pkgs/libbraiding/checksums.ini
@@ -1,5 +1,5 @@
-tarball=libbraiding-VERSION.tar.gz
-sha1=06610e47eb243b27aea0ad399b41614fcdb179c9
-md5=5466605026b90bdca7ca20852f88b5c5
-cksum=704753563
-upstream_url=https://github.com/miguelmarco/libbraiding/releases/download/1.1/libbraiding-1.1.tar.gz
+tarball=libbraiding-VERSION-actually-VERSION.tar.gz
+sha1=b7e13778784fe1e36e7c0cbd7a4c234a090cd1b2
+md5=0513967c81b783ea66336b7ad0562534
+cksum=3619705925
+upstream_url=https://github.com/miguelmarco/libbraiding/releases/download/VERSION/libbraiding-VERSION.tar.gz
diff --git a/build/pkgs/libgd/spkg-legacy-uninstall b/build/pkgs/libgd/spkg-legacy-uninstall
deleted file mode 100755
index c67f4d2167d..00000000000
--- a/build/pkgs/libgd/spkg-legacy-uninstall
+++ /dev/null
@@ -1,5 +0,0 @@
-#! /usr/bin/env bash
-# Critical to get rid of old versions, since they will break the install, since
-# at some point one of the libraries accidently links against what's in SAGE_LOCAL,
-# instead of what is in the build directory!
-rm "$SAGE_LOCAL"/lib/libgd.*
diff --git a/build/pkgs/m4ri/spkg-install.in b/build/pkgs/m4ri/spkg-install.in
index c3ae2270406..0af3234ed16 100644
--- a/build/pkgs/m4ri/spkg-install.in
+++ b/build/pkgs/m4ri/spkg-install.in
@@ -24,9 +24,6 @@ else
     DISABLE_SSE2=""
 fi
 
-# otherwise we might run into problems with old headers
-rm -rf "$SAGE_LOCAL/include/m4ri"
-
 cd src
 
 # Configure and build M4RI
diff --git a/build/pkgs/m4rie/spkg-install.in b/build/pkgs/m4rie/spkg-install.in
index 925706f0581..bc0c13ffe1e 100644
--- a/build/pkgs/m4rie/spkg-install.in
+++ b/build/pkgs/m4rie/spkg-install.in
@@ -27,12 +27,6 @@ export CFLAGS
 export CPPFLAGS
 export LDFLAGS
 
-# otherwise we might run into problems with old headers
-
-if [ -d "$SAGE_LOCAL/include/m4rie" ]; then
-    rm -rf "$SAGE_LOCAL/include/m4rie"
-fi
-
 # build M4RIE
 
 cd $ROOT_DIR/src/
diff --git a/build/pkgs/mathjax/spkg-install.in b/build/pkgs/mathjax/spkg-install.in
index 31cdb426992..2eeaf6e84b4 100644
--- a/build/pkgs/mathjax/spkg-install.in
+++ b/build/pkgs/mathjax/spkg-install.in
@@ -1,4 +1,2 @@
 TARGET="${SAGE_SHARE}/mathjax"
-# Cleanup installed version
-rm -rf "${TARGET}"
 sdh_install src/* "${TARGET}"
diff --git a/build/pkgs/meataxe/spkg-install.in b/build/pkgs/meataxe/spkg-install.in
index 433e5ba7c86..231519fac23 100644
--- a/build/pkgs/meataxe/spkg-install.in
+++ b/build/pkgs/meataxe/spkg-install.in
@@ -1,16 +1,3 @@
-# Delete old (Shared)MeatAxe libraries. This ensures a sane state if
-# installation of this package fails: the mtx library should exist if
-# and only if meataxe is installed. In detail: the build-time check in
-# src/setup.py checks whether or not the "meataxe" package is
-# marked as installed but the run-time check for the matrix_gfpn_dense
-# module checks whether it can be imported. We need to ensure that these
-# two conditions are equivalent, otherwise strange things can happen.
-# See also https://github.com/sagemath/sage/issues/24359#comment:154
-#
-# This also deletes the static library left behind from the installation
-# of MeatAxe (as opposed to SharedMeatAxe).
-rm -f "$SAGE_LOCAL"/lib/libmtx.*
-
 # Directory where executables are installed.
 export MTXBIN="$SAGE_LOCAL"/bin
 
@@ -34,4 +21,3 @@ sdh_make_install
 if [ "$SAGE_SPKG_INSTALL_DOCS" = yes ] ; then
     $MAKE doc || sdh_die "Error documenting SharedMeatAxe"
 fi
-
diff --git a/build/pkgs/mpc/spkg-install.in b/build/pkgs/mpc/spkg-install.in
index b260b531f17..835bcc53ab2 100644
--- a/build/pkgs/mpc/spkg-install.in
+++ b/build/pkgs/mpc/spkg-install.in
@@ -12,13 +12,5 @@ EXTRA=""
 sdh_configure $SAGE_CONFIGURE_GMP $SAGE_CONFIGURE_MPFR $EXTRA
 sdh_make
 
-# Cleaning
-echo "Deleting old headers"
-rm -f "$SAGE_LOCAL"/include/mpc.h
-# Do not delete old libraries as this causes gcc to break during
-# parallel builds.
-# echo "Deleting old libraries"
-# rm -f "$SAGE_LOCAL"/lib/libmpc.*
-
 # Installing
 sdh_make_install
diff --git a/build/pkgs/mpfr/spkg-install.in b/build/pkgs/mpfr/spkg-install.in
index a0ecd15ec43..b377e0a93b6 100644
--- a/build/pkgs/mpfr/spkg-install.in
+++ b/build/pkgs/mpfr/spkg-install.in
@@ -145,14 +145,6 @@ mpfr_build()
 {
     mpfr_configure
     sdh_make
-
-    echo
-    echo "Building MPFR succeeded.  Now deleting old headers..."
-    rm -f "$SAGE_LOCAL"/include/*mpfr*
-    # Do not delete old libraries as this causes gcc to break during
-    # parallel builds.
-    # rm -f "$SAGE_LOCAL"/lib/libmpfr.*
-
     sdh_make_install
 }
 
diff --git a/build/pkgs/mpfrcx/spkg-legacy-uninstall b/build/pkgs/mpfrcx/spkg-legacy-uninstall
deleted file mode 100755
index 3538ccc54e4..00000000000
--- a/build/pkgs/mpfrcx/spkg-legacy-uninstall
+++ /dev/null
@@ -1,4 +0,0 @@
-#! /usr/bin/env bash
-echo "Deleting old headers"
-rm -f "$SAGE_LOCAL"/include/mpfrcx.h
-rm -f "$SAGE_LOCAL"/lib/libmpfrcx.*
diff --git a/build/pkgs/nauty/checksums.ini b/build/pkgs/nauty/checksums.ini
index a9dd1246e91..f699a05e9fc 100644
--- a/build/pkgs/nauty/checksums.ini
+++ b/build/pkgs/nauty/checksums.ini
@@ -1,5 +1,5 @@
-tarball=nautyVERSION.tar.gz
-sha1=10c39117c55c69c18c6a107110e7c08f3d873652
-md5=7a82f4209f5d552da3078c67e5af872e
-cksum=2164796643
-upstream_url=https://pallini.di.uniroma1.it/nauty2_8_6.tar.gz
+tarball=nauty${VERSION}.tar.gz
+sha1=672e9fc9dfd07201af37ee65807a9b493331ed92
+md5=16c6edc1a8747c9281041b7c7092135f
+cksum=2663136901
+upstream_url=https://pallini.di.uniroma1.it/nauty${VERSION_MAJOR}_${VERSION_MINOR}_${VERSION_MICRO}.tar.gz
diff --git a/build/pkgs/nauty/package-version.txt b/build/pkgs/nauty/package-version.txt
index 04e60eaa37a..80803faf1b9 100644
--- a/build/pkgs/nauty/package-version.txt
+++ b/build/pkgs/nauty/package-version.txt
@@ -1 +1 @@
-2.8.6.p1
+2.8.8
diff --git a/build/pkgs/nauty/patches/nauty-2.8.6-gentreeg-gentourng.patch b/build/pkgs/nauty/patches/nauty-2.8.6-gentreeg-gentourng.patch
deleted file mode 100644
index 322b25326ee..00000000000
--- a/build/pkgs/nauty/patches/nauty-2.8.6-gentreeg-gentourng.patch
+++ /dev/null
@@ -1,144 +0,0 @@
-From edb0474a4db8e69f971e4eebe18716309f5a7bb3 Mon Sep 17 00:00:00 2001
-From: Michael Orlitzky 
-Date: Tue, 17 Jan 2023 19:44:49 -0500
-Subject: [PATCH 1/1] Upstream fixes for gentreeg and gentourng.
-
-https://mailman.anu.edu.au/pipermail/nauty/2023-January/000903.html
----
- gentourng.c |  2 +-
- gentreeg.c  | 95 ++++++++++++++++++++++++++++-------------------------
- 2 files changed, 51 insertions(+), 46 deletions(-)
-
-diff --git a/gentourng.c b/gentourng.c
-index 634e5e8..5c7ffff 100644
---- a/gentourng.c
-+++ b/gentourng.c
-@@ -1408,7 +1408,7 @@ PLUGIN_INIT
-                 (*outproc)(outfile,g,1);
-             }
-         }
--        else
-+        else if (!connec || maxn != 2)
-         {
-             makeleveldata();
- 
-diff --git a/gentreeg.c b/gentreeg.c
-index 946d5f8..15bf87b 100644
---- a/gentreeg.c
-+++ b/gentreeg.c
-@@ -1,4 +1,4 @@
--/* gentree version 1.3; Brendan McKay Oct 2022 */
-+/* gentree version 1.4; Brendan McKay Dec 2022 */
- /* This program is a wrapper for the program FreeTrees.c written
-  * by Gang Li & Frank Ruskey.  See below for their original
-  * comments. */
-@@ -32,49 +32,54 @@ Counts for n=1..45:
-  1: 1
-  2: 1
-  3: 1
-- 4: 1
-- 5: 2
-- 6: 3
-- 7: 6
-- 8: 11
-- 9: 23
--10: 47
--11: 106
--12: 235
--13: 551
--14: 1301
--15: 3159
--16: 7741
--17: 19320
--18: 48629
--19: 123867
--20: 317955
--21: 823065
--22: 2144505
--23: 5623756
--24: 14828074
--25: 39299897
--26: 104636890
--27: 279793450
--28: 751065460
--29: 2023443032
--30: 5469566585
--31: 14830871802
--32: 40330829030
--33: 109972410221
--34: 300628862480
--35: 823779631721
--36: 2262366343746
--37: 6226306037178
--38: 17169677490714
--39: 47436313524262
--40: 131290543779126
--41: 363990257783343
--42: 1010748076717151
--43: 2810986483493475
--44: 7828986221515605
--45: 21835027912963086
--********************************/
-+ 4: 2
-+ 5: 3
-+ 6: 6
-+ 7: 11
-+ 8: 23
-+ 9: 47
-+10: 106
-+11: 235
-+12: 551
-+13: 1301
-+14: 3159
-+15: 7741
-+16: 19320
-+17: 48629
-+18: 123867
-+19: 317955
-+20: 823065
-+21: 2144505
-+22: 5623756
-+23: 14828074
-+24: 39299897
-+25: 104636890
-+26: 279793450
-+27: 751065460
-+28: 2023443032
-+29: 5469566585
-+30: 14830871802
-+31: 40330829030
-+32: 109972410221
-+33: 300628862480
-+34: 823779631721
-+35: 2262366343746
-+36: 6226306037178
-+37: 17169677490714
-+38: 47436313524262
-+39: 131290543779126
-+40: 363990257783343
-+41: 1010748076717151
-+42: 2810986483493475
-+43: 7828986221515605
-+44: 21835027912963086
-+45: 60978390985918906
-+46: 170508699155987862
-+47: 477355090753926460
-+48: 1337946100045842285
-+49: 3754194185716399992
-+50: 10545233702911509534
-+*******************************/
- 
- /* Comments on original program by original authors */
- /*==============================================================*/
-@@ -676,7 +681,7 @@ PLUGIN_INIT
-         }
-         else if (nv == 2)
-         {
--            if (res == 0 && maxdeg >= 1 && mindiam <= 1 && maxdiam >= 2)
-+            if (res == 0 && maxdeg >= 1 && mindiam <= 1 && maxdiam >= 1)
-             {
-                 par[1] = 0;
-                 par[2] = 1;
--- 
-2.38.2
-
diff --git a/build/pkgs/ntl/spkg-install.in b/build/pkgs/ntl/spkg-install.in
index a26c3b5026e..d82ddf370e5 100644
--- a/build/pkgs/ntl/spkg-install.in
+++ b/build/pkgs/ntl/spkg-install.in
@@ -112,11 +112,6 @@ ntl_build()
 
 ntl_install()
 {
-    echo
-    echo "Removing old NTL files."
-    rm -rf "$SAGE_LOCAL"/lib/libntl*
-    rm -rf "$SAGE_LOCAL"/include/NTL
-
     echo
     echo "Installing NTL."
 
diff --git a/build/pkgs/numpy/spkg-legacy-uninstall b/build/pkgs/numpy/spkg-legacy-uninstall
deleted file mode 100755
index 9cb6a63a5a3..00000000000
--- a/build/pkgs/numpy/spkg-legacy-uninstall
+++ /dev/null
@@ -1,2 +0,0 @@
-#! /usr/bin/env bash
-rm -rf "$SAGE_LOCAL"/lib/python*/site-packages/numpy
diff --git a/build/pkgs/openblas/spkg-install.in b/build/pkgs/openblas/spkg-install.in
index 2f8aea512bc..dbfcb6c1a75 100644
--- a/build/pkgs/openblas/spkg-install.in
+++ b/build/pkgs/openblas/spkg-install.in
@@ -59,9 +59,6 @@ if ! (sdh_make libs $OPENBLAS_CONFIGURE && sdh_make netlib $OPENBLAS_CONFIGURE &
     fi
 fi
 
-# See https://github.com/sagemath/sage/issues/30335
-rm -f "$SAGE_LOCAL/lib/pkgconfig/cblas.pc" "$SAGE_LOCAL/lib/pkgconfig/blas.pc" "$SAGE_LOCAL/lib/pkgconfig/lapack.pc"
-
 sdh_make_install PREFIX="$SAGE_LOCAL" NO_STATIC=1 $OPENBLAS_CONFIGURE
 cd ..
 ./write_pc_file.py
diff --git a/build/pkgs/openblas/spkg-preinst.in b/build/pkgs/openblas/spkg-preinst.in
new file mode 100644
index 00000000000..ced306d2d4e
--- /dev/null
+++ b/build/pkgs/openblas/spkg-preinst.in
@@ -0,0 +1,2 @@
+# See https://github.com/sagemath/sage/issues/30335
+rm -f "$SAGE_LOCAL/lib/pkgconfig/cblas.pc" "$SAGE_LOCAL/lib/pkgconfig/blas.pc" "$SAGE_LOCAL/lib/pkgconfig/lapack.pc"
diff --git a/build/pkgs/planarity/checksums.ini b/build/pkgs/planarity/checksums.ini
index 6227637695a..309daa93278 100644
--- a/build/pkgs/planarity/checksums.ini
+++ b/build/pkgs/planarity/checksums.ini
@@ -2,4 +2,4 @@ tarball=planarity-VERSION.tar.gz
 sha1=8407bccf33c07bf0dae22d79b5e6ac7d89c62ea3
 md5=200116e6a67544c8e94f9de7c3ba1b1a
 cksum=4207261512
-upstream_url=http://users.ox.ac.uk/~coml0531/sage/planarity-3.0.1.0.tar.gz
+upstream_url=http://users.ox.ac.uk/~coml0531/sage/planarity-VERSION.tar.gz
diff --git a/build/pkgs/plantri/checksums.ini b/build/pkgs/plantri/checksums.ini
index 05dc13d01e7..f999a981a69 100644
--- a/build/pkgs/plantri/checksums.ini
+++ b/build/pkgs/plantri/checksums.ini
@@ -1,5 +1,5 @@
-tarball=plantri53.tar.gz
+tarball=plantri${VERSION_MAJOR}${VERSION_MINOR}.tar.gz
 sha1=a04aec2fa90c43f1c9bef59d041a54d8fa5bf562
 md5=ea765b3508dd56384f94ad1f032e2dd4
 cksum=3200215885
-upstream_url=https://users.cecs.anu.edu.au/~bdm/plantri/plantri53.tar.gz
+upstream_url=https://users.cecs.anu.edu.au/~bdm/plantri/plantri${VERSION_MAJOR}${VERSION_MINOR}.tar.gz
diff --git a/build/pkgs/polytopes_db_4d/checksums.ini b/build/pkgs/polytopes_db_4d/checksums.ini
index edfee6b7841..d87e456c4a8 100644
--- a/build/pkgs/polytopes_db_4d/checksums.ini
+++ b/build/pkgs/polytopes_db_4d/checksums.ini
@@ -2,4 +2,4 @@ tarball=polytopes_db_4d-VERSION.spkg
 sha1=c9779821e365df2d7f9bc684f9e2ec0e95fb8650
 md5=fe775a26fd7b2afc187e9bfabfb1b86a
 cksum=3415837678
-upstream_url=http://ftp.sparcs.org/sage/spkg/huge/polytopes_db_4d-1.0.spkg
+upstream_url=http://ftp.sparcs.org/sage/spkg/huge/polytopes_db_4d-VERSION.spkg
diff --git a/build/pkgs/pycygwin/SPKG.rst b/build/pkgs/pycygwin/SPKG.rst
deleted file mode 100644
index fdf67a08dc1..00000000000
--- a/build/pkgs/pycygwin/SPKG.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-pycygwin: Python bindings for Cygwin's C API
-============================================
-
-Description
------------
-
-Python bindings for Cygwin's C API. Provides some utilities to help with
-the Cygwin port. Naturally, this package should only be installed on
-Cygwin--for other platforms its installation is a no-op.
-
-Website
--------
-
-https://github.com/embray/PyCygwin
diff --git a/build/pkgs/pycygwin/checksums.ini b/build/pkgs/pycygwin/checksums.ini
deleted file mode 100644
index 7ce896982a4..00000000000
--- a/build/pkgs/pycygwin/checksums.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-tarball=PyCygwin-VERSION.tar.gz
-sha1=4021dbf77c353051761d277f5490b38a701ba51b
-md5=12c30c847b144282178bc546d4e2cbcd
-cksum=2254892419
diff --git a/build/pkgs/pycygwin/dependencies b/build/pkgs/pycygwin/dependencies
deleted file mode 100644
index 1db13c07e43..00000000000
--- a/build/pkgs/pycygwin/dependencies
+++ /dev/null
@@ -1,4 +0,0 @@
- cython | $(PYTHON_TOOLCHAIN) $(PYTHON)
-
-----------
-All lines of this file are ignored except the first.
diff --git a/build/pkgs/pycygwin/distros/repology.txt b/build/pkgs/pycygwin/distros/repology.txt
deleted file mode 100644
index eeeab661dc6..00000000000
--- a/build/pkgs/pycygwin/distros/repology.txt
+++ /dev/null
@@ -1 +0,0 @@
-python:pycygwin
diff --git a/build/pkgs/pycygwin/install-requires.txt b/build/pkgs/pycygwin/install-requires.txt
deleted file mode 100644
index 3bec679b60b..00000000000
--- a/build/pkgs/pycygwin/install-requires.txt
+++ /dev/null
@@ -1 +0,0 @@
-pycygwin >=0.1
diff --git a/build/pkgs/pycygwin/package-version.txt b/build/pkgs/pycygwin/package-version.txt
deleted file mode 100644
index 49d59571fbf..00000000000
--- a/build/pkgs/pycygwin/package-version.txt
+++ /dev/null
@@ -1 +0,0 @@
-0.1
diff --git a/build/pkgs/pycygwin/spkg-install.in b/build/pkgs/pycygwin/spkg-install.in
deleted file mode 100644
index eeb8dcdf62c..00000000000
--- a/build/pkgs/pycygwin/spkg-install.in
+++ /dev/null
@@ -1,3 +0,0 @@
-if [ "$UNAME" = "CYGWIN" ]; then
-    cd src && sdh_pip_install .
-fi
diff --git a/build/pkgs/pycygwin/type b/build/pkgs/pycygwin/type
deleted file mode 100644
index a6a7b9cd726..00000000000
--- a/build/pkgs/pycygwin/type
+++ /dev/null
@@ -1 +0,0 @@
-standard
diff --git a/build/pkgs/python3/spkg-build.in b/build/pkgs/python3/spkg-build.in
index 0c08814a242..4cd421bf4b8 100644
--- a/build/pkgs/python3/spkg-build.in
+++ b/build/pkgs/python3/spkg-build.in
@@ -62,17 +62,6 @@ fi
 # common in Cython-generated code.
 export EXTRA_CFLAGS="`testcflags.sh -Wno-unused $OLD_CFLAGS`"
 
-# Remove old symbolic link: it is not needed and its presence can
-# interfere with the Python build.
-rm -f "$SAGE_LOCAL/lib/python"
-
-# Remove old libraries. We really need to do this before building Python
-# since Python tries to import some modules (e.g. ctypes) at build-time.
-# We need to make sure that the old installed libraries in local/lib are
-# not used for that. See https://github.com/sagemath/sage/issues/24605
-rm -f "$SAGE_LOCAL"/lib/lib"$PKG_BASE"*
-
-
     # Note: --without-ensurepip ensures that setuptools+pip are *not* installed
     # automatically when installing python3. They will be installed instead by
     # the separate setuptools and pip packages; see
diff --git a/build/pkgs/qhull/spkg-install.in b/build/pkgs/qhull/spkg-install.in
index 73810f54625..c3df534ad86 100644
--- a/build/pkgs/qhull/spkg-install.in
+++ b/build/pkgs/qhull/spkg-install.in
@@ -5,9 +5,4 @@ sdh_cmake -DCMAKE_VERBOSE_MAKEFILE=ON \
 
 sdh_make
 
-# clean old install
-rm -rf "${SAGE_LOCAL}"/include/libqhull
-rm -rf "${SAGE_LOCAL}"/include/qhull
-rm -rf "${SAGE_LOCAL}"/lib/libqhull*
-
 sdh_make_install
diff --git a/build/pkgs/readline/spkg-install.in b/build/pkgs/readline/spkg-install.in
index 69b4e686d75..a9322e4bed8 100644
--- a/build/pkgs/readline/spkg-install.in
+++ b/build/pkgs/readline/spkg-install.in
@@ -4,16 +4,6 @@ sdh_configure --with-curses --enable-shared --disable-static
 echo "Now building static and shared readline libraries..."
 sdh_make
 
-echo "Build succeedeed.  Deleting old readline headers and libs"
-echo "before installing the new ones..."
-# (Note; Actually also readline does this by itself, but doing it
-# here, too, doesn't hurt either.)
-rm -rf "$SAGE_LOCAL"/include/readline/ "$SAGE_LOCAL"/lib/libreadline.*
-if [[ $? -ne 0 ]]; then
-    echo >&2 "Error removing old version of readline."
-    exit 1
-fi
-
 echo "Now installing the new readline headers and libraries..."
 sdh_make_install
 
diff --git a/build/pkgs/sage_conf/install-requires.txt b/build/pkgs/sage_conf/install-requires.txt
index b136f4a2aff..f58f8ff4bbb 100644
--- a/build/pkgs/sage_conf/install-requires.txt
+++ b/build/pkgs/sage_conf/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sage-conf ~= 10.3b2
+sage-conf ~= 10.3b3
diff --git a/build/pkgs/sage_docbuild/install-requires.txt b/build/pkgs/sage_docbuild/install-requires.txt
index a9f3dad3be9..74baf1b835d 100644
--- a/build/pkgs/sage_docbuild/install-requires.txt
+++ b/build/pkgs/sage_docbuild/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sage-docbuild ~= 10.3b2
+sage-docbuild ~= 10.3b3
diff --git a/build/pkgs/sage_setup/install-requires.txt b/build/pkgs/sage_setup/install-requires.txt
index 398467294a7..352120f20fb 100644
--- a/build/pkgs/sage_setup/install-requires.txt
+++ b/build/pkgs/sage_setup/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sage-setup ~= 10.3b2
+sage-setup ~= 10.3b3
diff --git a/build/pkgs/sage_sws2rst/install-requires.txt b/build/pkgs/sage_sws2rst/install-requires.txt
index 86ccd89829a..f0a5a420fcb 100644
--- a/build/pkgs/sage_sws2rst/install-requires.txt
+++ b/build/pkgs/sage_sws2rst/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sage-sws2rst ~= 10.3b2
+sage-sws2rst ~= 10.3b3
diff --git a/build/pkgs/sagelib/dependencies b/build/pkgs/sagelib/dependencies
index fa6e98df7db..b1ebfd0825e 100644
--- a/build/pkgs/sagelib/dependencies
+++ b/build/pkgs/sagelib/dependencies
@@ -1,4 +1,4 @@
-FORCE $(SCRIPTS) boost_cropped $(BLAS) brial cliquer cypari cysignals cython ecl eclib ecm flint libgd gap giac givaro glpk gmpy2 gsl iml importlib_metadata importlib_resources jupyter_core lcalc lrcalc_python libbraiding libhomfly libpng linbox m4ri m4rie memory_allocator mpc mpfi mpfr $(MP_LIBRARY) ntl numpy pari pip pkgconfig planarity ppl pplpy primesieve primecount primecountpy pycygwin $(PYTHON) requests rw sage_conf singular symmetrica typing_extensions $(PCFILES) | $(PYTHON_TOOLCHAIN) sage_setup $(PYTHON) pythran
+FORCE $(SCRIPTS) boost_cropped $(BLAS) brial cliquer cypari cysignals cython ecl eclib ecm flint libgd gap giac givaro glpk gmpy2 gsl iml importlib_metadata importlib_resources jupyter_core lcalc lrcalc_python libbraiding libhomfly libpng linbox m4ri m4rie memory_allocator mpc mpfi mpfr $(MP_LIBRARY) ntl numpy pari pip pkgconfig planarity ppl pplpy primesieve primecount primecountpy $(PYTHON) requests rw sage_conf singular symmetrica typing_extensions $(PCFILES) | $(PYTHON_TOOLCHAIN) sage_setup $(PYTHON) pythran
 
 ----------
 All lines of this file are ignored except the first.
diff --git a/build/pkgs/sagelib/install-requires.txt b/build/pkgs/sagelib/install-requires.txt
index 4760acdf3a5..7bf049c6a0c 100644
--- a/build/pkgs/sagelib/install-requires.txt
+++ b/build/pkgs/sagelib/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sagemath-standard ~= 10.3b2
+sagemath-standard ~= 10.3b3
diff --git a/build/pkgs/sagelib/spkg-install.in b/build/pkgs/sagelib/spkg-install.in
index 63cfd64fa59..39ffc25f270 100644
--- a/build/pkgs/sagelib/spkg-install.in
+++ b/build/pkgs/sagelib/spkg-install.in
@@ -51,6 +51,8 @@ if [ "$SAGE_EDITABLE" = yes ]; then
     # and renamed the distribution to "sagemath-standard").  There is no clean way to uninstall
     # them, so we just use rm.
     (cd "$SITEPACKAGESDIR" && rm -rf sage sage-[1-9]*.egg-info sage-[1-9]*.dist-info)
+    # Until https://github.com/sagemath/sage/issues/34209 switches us to PEP 660 editable wheels
+    export SETUPTOOLS_ENABLE_FEATURES=legacy-editable
     time sdh_pip_editable_install .
 
     if [ "$SAGE_WHEELS" = yes ]; then
diff --git a/build/pkgs/sagemath_bliss/install-requires.txt b/build/pkgs/sagemath_bliss/install-requires.txt
index 78450942be8..0c4fe90f245 100644
--- a/build/pkgs/sagemath_bliss/install-requires.txt
+++ b/build/pkgs/sagemath_bliss/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sagemath-bliss ~= 10.3b2
+sagemath-bliss ~= 10.3b3
diff --git a/build/pkgs/sagemath_categories/install-requires.txt b/build/pkgs/sagemath_categories/install-requires.txt
index dd5175f57fe..c4de485cea4 100644
--- a/build/pkgs/sagemath_categories/install-requires.txt
+++ b/build/pkgs/sagemath_categories/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sagemath-categories ~= 10.3b2
+sagemath-categories ~= 10.3b3
diff --git a/build/pkgs/sagemath_coxeter3/install-requires.txt b/build/pkgs/sagemath_coxeter3/install-requires.txt
index 05f8575b6a0..5f8c399c22f 100644
--- a/build/pkgs/sagemath_coxeter3/install-requires.txt
+++ b/build/pkgs/sagemath_coxeter3/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sagemath-coxeter3 ~= 10.3b2
+sagemath-coxeter3 ~= 10.3b3
diff --git a/build/pkgs/sagemath_environment/install-requires.txt b/build/pkgs/sagemath_environment/install-requires.txt
index 1eb9fe7e808..336f2357697 100644
--- a/build/pkgs/sagemath_environment/install-requires.txt
+++ b/build/pkgs/sagemath_environment/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sagemath-environment ~= 10.3b2
+sagemath-environment ~= 10.3b3
diff --git a/build/pkgs/sagemath_mcqd/install-requires.txt b/build/pkgs/sagemath_mcqd/install-requires.txt
index cc46fb68674..1c73893daab 100644
--- a/build/pkgs/sagemath_mcqd/install-requires.txt
+++ b/build/pkgs/sagemath_mcqd/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sagemath-mcqd ~= 10.3b2
+sagemath-mcqd ~= 10.3b3
diff --git a/build/pkgs/sagemath_meataxe/install-requires.txt b/build/pkgs/sagemath_meataxe/install-requires.txt
index 76fc2369cb1..0e070b2b415 100644
--- a/build/pkgs/sagemath_meataxe/install-requires.txt
+++ b/build/pkgs/sagemath_meataxe/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sagemath-meataxe ~= 10.3b2
+sagemath-meataxe ~= 10.3b3
diff --git a/build/pkgs/sagemath_objects/install-requires.txt b/build/pkgs/sagemath_objects/install-requires.txt
index b1d01c37823..f341b061e8b 100644
--- a/build/pkgs/sagemath_objects/install-requires.txt
+++ b/build/pkgs/sagemath_objects/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sagemath-objects ~= 10.3b2
+sagemath-objects ~= 10.3b3
diff --git a/build/pkgs/sagemath_repl/install-requires.txt b/build/pkgs/sagemath_repl/install-requires.txt
index ce22d36e261..4a0e58e79ce 100644
--- a/build/pkgs/sagemath_repl/install-requires.txt
+++ b/build/pkgs/sagemath_repl/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sagemath-repl ~= 10.3b2
+sagemath-repl ~= 10.3b3
diff --git a/build/pkgs/sagemath_sirocco/install-requires.txt b/build/pkgs/sagemath_sirocco/install-requires.txt
index 40949dd944f..4737ab2e562 100644
--- a/build/pkgs/sagemath_sirocco/install-requires.txt
+++ b/build/pkgs/sagemath_sirocco/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sagemath-sirocco ~= 10.3b2
+sagemath-sirocco ~= 10.3b3
diff --git a/build/pkgs/sagemath_tdlib/install-requires.txt b/build/pkgs/sagemath_tdlib/install-requires.txt
index cab1a072642..bbf9579b01b 100644
--- a/build/pkgs/sagemath_tdlib/install-requires.txt
+++ b/build/pkgs/sagemath_tdlib/install-requires.txt
@@ -1,2 +1,2 @@
 # This file is updated on every release by the sage-update-version script
-sagemath-tdlib ~= 10.3b2
+sagemath-tdlib ~= 10.3b3
diff --git a/build/pkgs/sagetex/spkg-install.in b/build/pkgs/sagetex/spkg-install.in
index 55f0e9c76e5..058b1344dc2 100644
--- a/build/pkgs/sagetex/spkg-install.in
+++ b/build/pkgs/sagetex/spkg-install.in
@@ -1,6 +1,3 @@
 cd src
 
 sdh_pip_install .
-
-echo "Removing old SageTex version(s)"
-rm -rf $SAGE_LOCAL/share/texmf/tex/generic/sagetex
diff --git a/build/pkgs/singular/spkg-install.in b/build/pkgs/singular/spkg-install.in
index 1c24a5d7ef8..c64820f4af1 100644
--- a/build/pkgs/singular/spkg-install.in
+++ b/build/pkgs/singular/spkg-install.in
@@ -11,44 +11,6 @@ if [ "x$SAGE_DEBUG" = "xyes" ]; then
     SINGULAR_CONFIGURE="$SINGULAR_CONFIGURE --enable-debug --disable-optimizationflags"
 fi
 
-remove_old_version()
-{
-    # the following is a little verbose but it ensures we leave no trace of 3.x
-    # _or_ 4.x
-    rm -f "$SAGE_LOCAL"/bin/*Singular*
-    rm -f "$SAGE_LOCAL"/bin/*singular*
-    rm -rf "$SAGE_LOCAL/include/singular" # 3.x and 4.x
-    rm -rf "$SAGE_LOCAL/include/factory" # 3.x and 4.x
-    rm -f "$SAGE_LOCAL/include/factor.h" # 3.x only
-    rm -f "$SAGE_LOCAL/include/factoryconf.h" # 3.x only
-    rm -rf "$SAGE_LOCAL/include/omalloc" #4.x only
-    rm -f "$SAGE_LOCAL/include/omalloc.h" # 3.x only
-    rm -f "$SAGE_LOCAL/include/omlimits.h" # 3.x only
-    rm -rf "$SAGE_LOCAL/include/resources" #4.x only
-    rm -rf "$SAGE_LOCAL/include/gfanlib" #4.x only
-
-    # Clean up all Singular-related libraries
-    libs=(
-        singular  # 3.x with lower case
-        singcf    # 3.x only additional archives
-        singfac   # 3.x only additional archives
-        Singular  # 4.x with upper case
-        polys
-        factory
-        omalloc
-        resources           # 3.x
-        singular_resources  # 4.x and up
-        gfan
-    )
-    for name in ${libs[*]}; do
-       rm -f "$SAGE_LOCAL"/lib/lib${name}*
-    done
-
-    rm -f "$SAGE_LOCAL"/lib/p_Procs_Field* # 3.x only
-    rm -rf "$SAGE_LOCAL/share/singular"
-    rm -f "$SAGE_LOCAL"/share/info/singular*
-}
-
 config()
 {
     # configure notes (dates from Singular 3.x, maybe outdated for 4.x):
@@ -92,7 +54,7 @@ build_singular()
 
 
 # Actually run all the functions defined above
-for i in remove_old_version config build_singular; do
+for i in config build_singular; do
     echo "############### Singular stage $i ###############"
     cd "$SRC" && $i
 done
diff --git a/build/pkgs/sqlite/checksums.ini b/build/pkgs/sqlite/checksums.ini
index 5cb33394ab3..777aa3953a3 100644
--- a/build/pkgs/sqlite/checksums.ini
+++ b/build/pkgs/sqlite/checksums.ini
@@ -1,5 +1,5 @@
-tarball=sqlite-autoconf-3360000.tar.gz
+tarball=sqlite-autoconf-${VERSION_MAJOR}${VERSION_MINOR}0${VERSION_MICRO}00.tar.gz
 sha1=a4bcf9e951bfb9745214241ba08476299fc2dc1e
 md5=f5752052fc5b8e1b539af86a3671eac7
 cksum=763219165
-upstream_url=https://www.sqlite.org/2021/sqlite-autoconf-3360000.tar.gz
+upstream_url=https://www.sqlite.org/2021/sqlite-autoconf-${VERSION_MAJOR}${VERSION_MINOR}0${VERSION_MICRO}00.tar.gz
diff --git a/build/pkgs/sqlite/spkg-install.in b/build/pkgs/sqlite/spkg-install.in
index d13df2ba1b8..0ddfea7a619 100644
--- a/build/pkgs/sqlite/spkg-install.in
+++ b/build/pkgs/sqlite/spkg-install.in
@@ -1,5 +1,3 @@
-rm -f "$SAGE_LOCAL/bin/sqlite3"
-
 cd src
 
 export CPPFLAGS="$CPPFLAGS -I$SAGE_LOCAL/include"
diff --git a/build/pkgs/symengine/checksums.ini b/build/pkgs/symengine/checksums.ini
index 8eb65f05ef9..9ba59940a9f 100644
--- a/build/pkgs/symengine/checksums.ini
+++ b/build/pkgs/symengine/checksums.ini
@@ -1,5 +1,6 @@
 tarball=symengine-VERSION.tar.gz
-sha1=a2c8957f2099c9199751b165f107bf93d6823818
-md5=fe3c3fee1bd8dfdb9576fc2d28cb1076
-cksum=3544211225
+sha1=2dfee07108509963f3dbe3d9cad9de76d85e551f
+md5=4074f3c76570bdc2ae9914edafa29eb6
+cksum=3782541135
 upstream_url=https://github.com/symengine/symengine/releases/download/vVERSION/symengine-VERSION.tar.gz
+
diff --git a/build/pkgs/symengine/package-version.txt b/build/pkgs/symengine/package-version.txt
index af88ba82486..a8839f70de0 100644
--- a/build/pkgs/symengine/package-version.txt
+++ b/build/pkgs/symengine/package-version.txt
@@ -1 +1 @@
-0.11.1
+0.11.2
\ No newline at end of file
diff --git a/build/pkgs/symengine/spkg-configure.m4 b/build/pkgs/symengine/spkg-configure.m4
index c037d2e5f72..a6a78791dbc 100644
--- a/build/pkgs/symengine/spkg-configure.m4
+++ b/build/pkgs/symengine/spkg-configure.m4
@@ -1,7 +1,7 @@
 SAGE_SPKG_CONFIGURE([symengine], [
     m4_pushdef(SAGE_SYMENGINE_VERSION_MAJOR, [0])
     m4_pushdef(SAGE_SYMENGINE_VERSION_MINOR, [11])
-    SAGE_SPKG_DEPCHECK([gmp arb ecm flint mpc mpfr], [
+    SAGE_SPKG_DEPCHECK([gmp ecm flint mpc mpfr], [
         AC_CHECK_HEADER([symengine/symengine_config.h], [], [sage_spkg_install_symengine=yes])
         AC_MSG_CHECKING([whether we can link a program using symengine])
         SYMENGINE_SAVED_LIBS=$LIBS
diff --git a/build/pkgs/symmetrica/checksums.ini b/build/pkgs/symmetrica/checksums.ini
index 5f70ed5363c..6d06ebda462 100644
--- a/build/pkgs/symmetrica/checksums.ini
+++ b/build/pkgs/symmetrica/checksums.ini
@@ -2,4 +2,4 @@ tarball=symmetrica-VERSION.tar.xz
 sha1=0044cc087ff04267c246e730c6570d89f6e593af
 md5=cd4716c26b5c625a012c22656113ef6f
 cksum=1186250347
-upstream_url=http://users.ox.ac.uk/~coml0531/sage/symmetrica-3.0.1.tar.xz
+upstream_url=http://users.ox.ac.uk/~coml0531/sage/symmetrica-VERSION.tar.xz
diff --git a/build/pkgs/sympow/checksums.ini b/build/pkgs/sympow/checksums.ini
index f6ad79b8f93..87bf0086178 100644
--- a/build/pkgs/sympow/checksums.ini
+++ b/build/pkgs/sympow/checksums.ini
@@ -2,4 +2,4 @@ tarball=sympow-vVERSION.tar.gz
 sha1=37a909c26009415197b5088a2f1b53dd3558f494
 md5=51f2c717c84ec9c2840af740751cf797
 cksum=1444149964
-upstream_url=https://github.com/mkoeppe/sympow/releases/download/v2.023.6/sympow-v2.023.6.tar.gz
+upstream_url=https://github.com/mkoeppe/sympow/releases/download/vVERSION/sympow-vVERSION.tar.gz
diff --git a/build/sage_bootstrap/cmdline.py b/build/sage_bootstrap/cmdline.py
index 1b84cbf6461..e182321d7da 100644
--- a/build/sage_bootstrap/cmdline.py
+++ b/build/sage_bootstrap/cmdline.py
@@ -292,6 +292,9 @@ def make_parser():
     parser_download.add_argument(
         '--on-error', choices=['stop', 'warn'], default='stop',
         help='What to do if the tarball cannot be downloaded')
+    parser.add_argument(
+        '--no-check-certificate', action='store_true',
+        help='Do not check SSL certificates for https connections')
 
     parser_upload = subparsers.add_parser(
         'upload', epilog=epilog_upload,
@@ -381,6 +384,12 @@ def run():
     elif args.subcommand == 'update-latest':
         app.update_latest_cls(args.package_name, commit=args.commit)
     elif args.subcommand == 'download':
+        if args.no_check_certificate:
+            try:
+                import ssl
+                ssl._create_default_https_context = ssl._create_unverified_context
+            except ImportError:
+                pass
         app.download_cls(args.package_name,
                          allow_upstream=args.allow_upstream,
                          on_error=args.on_error)
diff --git a/build/sage_bootstrap/package.py b/build/sage_bootstrap/package.py
index f6a5425005a..43bfbffa47c 100644
--- a/build/sage_bootstrap/package.py
+++ b/build/sage_bootstrap/package.py
@@ -121,6 +121,42 @@ def tarball(self):
             self.__tarball = Tarball(self.tarball_filename, package=self)
         return self.__tarball
 
+    def _substitute_variables_once(self, pattern):
+        """
+        Substitute (at most) one occurrence of variables in ``pattern`` by the values.
+
+        These variables are ``VERSION``, ``VERSION_MAJOR``, ``VERSION_MINOR``,
+        ``VERSION_MICRO``, either appearing like this or in the form ``${VERSION_MAJOR}``
+        etc.
+
+        Return a tuple:
+        - the string with the substitution done or the original string
+        - whether a substitution was done
+        """
+        for var in ('VERSION_MAJOR', 'VERSION_MINOR', 'VERSION_MICRO', 'VERSION'):
+            # As VERSION is a substring of the other three, it needs to be tested last.
+            dollar_brace_var = '${' + var + '}'
+            if dollar_brace_var in pattern:
+                value = getattr(self, var.lower())
+                return pattern.replace(dollar_brace_var, value, 1), True
+            elif var in pattern:
+                value = getattr(self, var.lower())
+                return pattern.replace(var, value, 1), True
+        return pattern, False
+
+    def _substitute_variables(self, pattern):
+        """
+        Substitute all occurrences of ``VERSION`` in ``pattern`` by the actual version.
+
+        Likewise for ``VERSION_MAJOR``, ``VERSION_MINOR``, ``VERSION_MICRO``,
+        either appearing like this or in the form ``${VERSION}``, ``${VERSION_MAJOR}``,
+        etc.
+        """
+        not_done = True
+        while not_done:
+            pattern, not_done = self._substitute_variables_once(pattern)
+        return pattern
+
     @property
     def tarball_pattern(self):
         """
@@ -150,7 +186,7 @@ def tarball_filename(self):
         """
         pattern = self.tarball_pattern
         if pattern:
-            return pattern.replace('VERSION', self.version)
+            return self._substitute_variables(pattern)
         else:
             return None
 
@@ -177,7 +213,7 @@ def tarball_upstream_url(self):
         """
         pattern = self.tarball_upstream_url_pattern
         if pattern:
-            return pattern.replace('VERSION', self.version)
+            return self._substitute_variables(pattern)
         else:
             return None
 
@@ -212,6 +248,39 @@ def version(self):
         """
         return self.__version
 
+    @property
+    def version_major(self):
+        """
+        Return the major version
+
+        OUTPUT:
+
+        String. The package's major version.
+        """
+        return self.version.split('.')[0]
+
+    @property
+    def version_minor(self):
+        """
+        Return the minor version
+
+        OUTPUT:
+
+        String. The package's minor version.
+        """
+        return self.version.split('.')[1]
+
+    @property
+    def version_micro(self):
+        """
+        Return the micro version
+
+        OUTPUT:
+
+        String. The package's micro version.
+        """
+        return self.version.split('.')[2]
+
     @property
     def patchlevel(self):
         """
diff --git a/build/sage_bootstrap/updater.py b/build/sage_bootstrap/updater.py
index 7c1431967a1..67f1c961124 100644
--- a/build/sage_bootstrap/updater.py
+++ b/build/sage_bootstrap/updater.py
@@ -69,8 +69,11 @@ def _update_version(self, new_version):
     def download_upstream(self, download_url=None):
         tarball = self.package.tarball
         if download_url is None:
+            pattern = self.package.tarball_upstream_url_pattern
+            if pattern and 'VERSION' not in pattern:
+                print('Warning: upstream_url pattern does not use the VERSION variable')
             download_url = self.package.tarball_upstream_url
         if download_url is None:
             raise ValueError("package has no default upstream_url pattern, download_url needed")
-        print('Downloading tarball to {0}'.format(tarball.upstream_fqn))
+        print('Downloading tarball from {0} to {1}'.format(download_url, tarball.upstream_fqn))
         Download(download_url, tarball.upstream_fqn).run()
diff --git a/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sage-conf/VERSION.txt
+++ b/pkgs/sage-conf/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sage-conf/_sage_conf/_conf.py.in b/pkgs/sage-conf/_sage_conf/_conf.py.in
index 895d6572d1e..ccc1c9695fb 100644
--- a/pkgs/sage-conf/_sage_conf/_conf.py.in
+++ b/pkgs/sage-conf/_sage_conf/_conf.py.in
@@ -9,6 +9,11 @@ VERSION = "@PACKAGE_VERSION@"
 SAGE_LOCAL = "@prefix@"
 SAGE_ROOT = "@SAGE_ROOT@"
 
+# The semicolon-separated list of GAP root paths. This is the list of
+# locations that are searched for GAP packages. This is passed directly
+# to GAP via the -l flag.
+GAP_ROOT_PATHS = "@GAP_ROOT_PATHS@".replace('${prefix}', SAGE_LOCAL)
+
 # The path to the standalone maxima executable.
 MAXIMA = "@SAGE_MAXIMA@".replace('${prefix}', SAGE_LOCAL)
 
diff --git a/pkgs/sage-conf_conda/VERSION.txt b/pkgs/sage-conf_conda/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sage-conf_conda/VERSION.txt
+++ b/pkgs/sage-conf_conda/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sage-conf_pypi/VERSION.txt
+++ b/pkgs/sage-conf_pypi/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sage-docbuild/VERSION.txt
+++ b/pkgs/sage-docbuild/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sage-setup/VERSION.txt
+++ b/pkgs/sage-setup/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sage-sws2rst/VERSION.txt
+++ b/pkgs/sage-sws2rst/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sagemath-bliss/VERSION.txt b/pkgs/sagemath-bliss/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sagemath-bliss/VERSION.txt
+++ b/pkgs/sagemath-bliss/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sagemath-categories/VERSION.txt
+++ b/pkgs/sagemath-categories/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sagemath-coxeter3/VERSION.txt b/pkgs/sagemath-coxeter3/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sagemath-coxeter3/VERSION.txt
+++ b/pkgs/sagemath-coxeter3/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sagemath-environment/VERSION.txt
+++ b/pkgs/sagemath-environment/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sagemath-mcqd/VERSION.txt b/pkgs/sagemath-mcqd/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sagemath-mcqd/VERSION.txt
+++ b/pkgs/sagemath-mcqd/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sagemath-meataxe/VERSION.txt b/pkgs/sagemath-meataxe/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sagemath-meataxe/VERSION.txt
+++ b/pkgs/sagemath-meataxe/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sagemath-objects/VERSION.txt
+++ b/pkgs/sagemath-objects/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sagemath-repl/VERSION.txt
+++ b/pkgs/sagemath-repl/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sagemath-sirocco/VERSION.txt b/pkgs/sagemath-sirocco/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sagemath-sirocco/VERSION.txt
+++ b/pkgs/sagemath-sirocco/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/pkgs/sagemath-tdlib/VERSION.txt b/pkgs/sagemath-tdlib/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/pkgs/sagemath-tdlib/VERSION.txt
+++ b/pkgs/sagemath-tdlib/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/src/VERSION.txt b/src/VERSION.txt
index 4d40853d20c..0fca3e3265b 100644
--- a/src/VERSION.txt
+++ b/src/VERSION.txt
@@ -1 +1 @@
-10.3.beta2
+10.3.beta3
diff --git a/src/bin/sage b/src/bin/sage
index ba353116e7c..a198ed8f273 100755
--- a/src/bin/sage
+++ b/src/bin/sage
@@ -628,7 +628,9 @@ fi
 
 if [ "$1" = '-gap' -o "$1" = '--gap' ]; then
     shift
-    exec gap "$@"
+    # Use "-A" to avoid warnings about missing packages. The gap
+    # interface and libgap within sage both already do this.
+    exec gap -A "$@"
 fi
 
 if [ "$1" = '-gap3' -o "$1" = '--gap3' ]; then
@@ -1001,6 +1003,7 @@ if [ "$1" = '-tox' -o "$1" = '--tox' ]; then
             exec tox -c "$SAGE_SRC" "$@"
         else
             echo "Run 'sage -i tox' to install"
+            exit 1
         fi
     else
         echo >&2 "error: Sage source directory or tox.ini not available"
@@ -1022,6 +1025,7 @@ if [ "$1" = '-pytest' -o "$1" = '--pytest' ]; then
             exec pytest --rootdir="$SAGE_SRC" --doctest-modules "$@" "$SAGE_SRC"
         else
             echo "Run 'sage -i pytest' to install"
+            exit 1
         fi
     else
         echo >&2 "error: Sage source directory or tox.ini not available"
diff --git a/src/bin/sage-env b/src/bin/sage-env
index 29b96c3c895..8fcfda48fb6 100644
--- a/src/bin/sage-env
+++ b/src/bin/sage-env
@@ -228,7 +228,7 @@ fi
 # depending on SAGE_ROOT and SAGE_LOCAL which are already defined.
 if [ -n "$SAGE_LOCAL" ]; then
     export SAGE_SHARE="$SAGE_LOCAL/share"
-    export SAGE_SPKG_INST="$SAGE_LOCAL/var/lib/sage/installed"
+    export SAGE_SPKG_INST="$SAGE_LOCAL/var/lib/sage/installed"  # deprecated
 fi
 if [ -n "$SAGE_SHARE" ]; then
     export SAGE_DOC="$SAGE_SHARE/doc/sage"
diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh
index 7d38e167623..55fd24dfdf5 100644
--- a/src/bin/sage-version.sh
+++ b/src/bin/sage-version.sh
@@ -4,6 +4,6 @@
 # which stops "setup.py develop" from rewriting it as a Python file.
 :
 # This file is auto-generated by the sage-update-version script, do not edit!
-SAGE_VERSION='10.3.beta2'
-SAGE_RELEASE_DATE='2023-12-13'
-SAGE_VERSION_BANNER='SageMath version 10.3.beta2, Release Date: 2023-12-13'
+SAGE_VERSION='10.3.beta3'
+SAGE_RELEASE_DATE='2023-12-18'
+SAGE_VERSION_BANNER='SageMath version 10.3.beta3, Release Date: 2023-12-18'
diff --git a/src/doc/en/constructions/groups.rst b/src/doc/en/constructions/groups.rst
index 042d37088b1..da90efecfaa 100644
--- a/src/doc/en/constructions/groups.rst
+++ b/src/doc/en/constructions/groups.rst
@@ -180,8 +180,8 @@ Here's another way, working more directly with GAP::
     sage: print(gap.eval("normal := NormalSubgroups( G )"))
     [ Alt( [ 1 .. 5 ] ), Group(()) ]
     sage: G = gap.new("DihedralGroup( 10 )")
-    sage: G.NormalSubgroups()
-    [ Group( [ f1, f2 ] ), Group( [ f2 ] ), Group(  of ... ) ]
+    sage: G.NormalSubgroups().SortedList()
+    [ Group(  of ... ), Group( [ f2 ] ), Group( [ f1, f2 ] ) ]
     sage: print(gap.eval("G := SymmetricGroup( 4 )"))
     Sym( [ 1 .. 4 ] )
     sage: print(gap.eval("normal := NormalSubgroups( G );"))
diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst
index 4ed1762efe6..81b0044b44c 100644
--- a/src/doc/en/developer/packaging.rst
+++ b/src/doc/en/developer/packaging.rst
@@ -1048,9 +1048,11 @@ Sage mirrors when a new release is prepared.  On GitHub PRs
 upgrading a package, the PR description should no longer contain
 the upstream URL to avoid duplication of information.
 
-Note that, like the ``tarball`` field, the ``tpstream_url`` is a
+Note that, like the ``tarball`` field, the ``upstream_url`` is a
 template; the substring ``VERSION`` is substituted with the actual
-version.
+version. It can also be written as ``${VERSION}``, and it is possible
+to refer to the dot-separated components of a version by ``VERSION_MAJOR``,
+``VERSION_MINOR``, and ``VERSION_MICRO``.
 
 For Python packages available from PyPI, you should use an
 ``upstream_url`` from ``pypi.io``, which follows the format
diff --git a/src/doc/en/prep/Quickstarts/Abstract-Algebra.rst b/src/doc/en/prep/Quickstarts/Abstract-Algebra.rst
index c6f11452ce8..101cbcdb4e7 100644
--- a/src/doc/en/prep/Quickstarts/Abstract-Algebra.rst
+++ b/src/doc/en/prep/Quickstarts/Abstract-Algebra.rst
@@ -83,15 +83,13 @@ rather than just a list of numbers.  This can be very powerful.
 
 ::
 
-    sage: for K in D.normal_subgroups():
+    sage: len(D.normal_subgroups())
+    7
+    sage: for K in sorted(D.normal_subgroups()):
     ....:     print(K)
-    Subgroup generated by [(1,2,3,4,5,6,7,8), (1,8)(2,7)(3,6)(4,5)] of (Dihedral group of order 16 as a permutation group)
-    Subgroup generated by [(1,2,3,4,5,6,7,8), (1,3,5,7)(2,4,6,8), (1,5)(2,6)(3,7)(4,8)] of (Dihedral group of order 16 as a permutation group)
-    Subgroup generated by [(1,3,5,7)(2,4,6,8), (1,5)(2,6)(3,7)(4,8), (1,8)(2,7)(3,6)(4,5)] of (Dihedral group of order 16 as a permutation group)
-    Subgroup generated by [(2,8)(3,7)(4,6), (1,3,5,7)(2,4,6,8), (1,5)(2,6)(3,7)(4,8)] of (Dihedral group of order 16 as a permutation group)
-    Subgroup generated by [(1,3,5,7)(2,4,6,8), (1,5)(2,6)(3,7)(4,8)] of (Dihedral group of order 16 as a permutation group)
-    Subgroup generated by [(1,5)(2,6)(3,7)(4,8)] of (Dihedral group of order 16 as a permutation group)
     Subgroup generated by [()] of (Dihedral group of order 16 as a permutation group)
+    ...
+    Subgroup generated by [(1,2,3,4,5,6,7,8), (1,8)(2,7)(3,6)(4,5)] of (Dihedral group of order 16 as a permutation group)
 
 We can access specific subgroups if we know the generators as a
 permutation group.
diff --git a/src/doc/en/reference/databases/index.rst b/src/doc/en/reference/databases/index.rst
index 56289fa5236..7cff08ee68a 100644
--- a/src/doc/en/reference/databases/index.rst
+++ b/src/doc/en/reference/databases/index.rst
@@ -1,68 +1,40 @@
 Databases
 =========
 
-There are numerous specific mathematical databases either included
-in Sage or available as optional packages. Also, Sage includes two
-powerful general database packages.
+Sage includes mathematical databases as standard or optional packages,
+and provides convenient interfaces for easy access to the databases.
 
-Sage includes the ZOPE object oriented database ZODB, which
-"is a Python object persistence system. It provides transparent object-oriented persistency."
+Standard databases
+------------------
 
-Sage also includes the powerful relational database SQLite, along
-with a Python interface to SQLite. SQlite is a small C library that
-implements a self-contained, embeddable, zero-configuration SQL
-database engine.
+These databases are available in the standard install of Sage or if Sage can access
+Internet.
 
+.. toctree::
+   :maxdepth: 1
 
--  Transactions are atomic, consistent, isolated, and durable
-   (ACID) even after system crashes and power failures.
-
--  Zero-configuration - no setup or administration needed.
-
--  Implements most of SQL92. (Features not supported)
-
--  A complete database is stored in a single disk file.
-
--  Database files can be freely shared between machines with
-   different byte orders.
-
--  Supports databases up to 2 tebibytes (2^41 bytes) in size.
-
--  Strings and BLOBs up to 2 gibibytes (2^31 bytes) in size.
-
--  Small code footprint: less than 250KiB fully configured or less
-   than 150KiB with optional features omitted.
-
--  Faster than popular client/server database engines for most
-   common operations.
-
--  Simple, easy to use API.
-
--  TCL bindings included. Bindings for many other languages
-   available separately.
-
--  Well-commented source code with over 95% test coverage.
+   sage/databases/conway
+   sage/databases/oeis
+   sage/databases/sloane
+   sage/databases/findstat
 
--  Self-contained: no external dependencies.
+Optional databases
+------------------
 
--  Sources are in the public domain. Use for any purpose.
+The following databases require you to install optional packages before full access.
 
 .. toctree::
    :maxdepth: 1
 
+   sage/databases/cunningham_tables
+   sage/databases/knotinfo_db
+   sage/databases/cubic_hecke_db
+   sage/databases/symbolic_data
    sage/databases/cremona
    sage/databases/stein_watkins
    sage/databases/jones
-   sage/databases/oeis
-   sage/databases/sloane
-   sage/databases/findstat
-   sage/databases/conway
-   sage/databases/odlyzko
-   sage/databases/symbolic_data
-   sage/databases/cunningham_tables
    sage/databases/db_class_polynomials
    sage/databases/db_modular_polynomials
-   sage/databases/knotinfo_db
-   sage/databases/cubic_hecke_db
+   sage/databases/odlyzko
 
 .. include:: ../footer.txt
diff --git a/src/requirements.txt.m4 b/src/requirements.txt.m4
index 346ea3c6301..34c42860cf1 100644
--- a/src/requirements.txt.m4
+++ b/src/requirements.txt.m4
@@ -32,7 +32,6 @@ dnl ... already needed by sage.env
 pkgconfig==esyscmd(`printf $(sed "s/[.]p.*//;" ../pkgconfig/package-version.txt)')
 pplpy==esyscmd(`printf $(sed "s/[.]p.*//;" ../pplpy/package-version.txt)')
 primecountpy==esyscmd(`printf $(sed "s/[.]p.*//;" ../primecountpy/package-version.txt)')
-pycygwin==esyscmd(`printf $(sed "s/[.]p.*//;" ../pycygwin/package-version.txt)'); sys_platform == 'cygwin'
 requests==esyscmd(`printf $(sed "s/[.]p.*//;" ../requests/package-version.txt)')
 typing_extensions==esyscmd(`printf $(sed "s/[.]p.*//;" ../typing_extensions/package-version.txt)')
 
diff --git a/src/sage/coding/abstract_code.py b/src/sage/coding/abstract_code.py
index 85cdce652b9..ed84dc12517 100644
--- a/src/sage/coding/abstract_code.py
+++ b/src/sage/coding/abstract_code.py
@@ -512,7 +512,7 @@ def list(self):
             (1, 0, 1, 0, 1, 0, 1)
             True
         """
-        return [x for x in self]
+        return list(self)
 
     def length(self):
         r"""
diff --git a/src/sage/coding/ag_code.py b/src/sage/coding/ag_code.py
index f705f75d476..0f189bb69a0 100644
--- a/src/sage/coding/ag_code.py
+++ b/src/sage/coding/ag_code.py
@@ -332,7 +332,7 @@ def designed_distance(self):
         """
         Return the designed distance of the AG code.
 
-        If the code is of dimension zero, then a ``ValueError`` is raised.
+        If the code is of dimension zero, then a :class:`ValueError` is raised.
 
         EXAMPLES::
 
@@ -576,7 +576,7 @@ def designed_distance(self):
         """
         Return the designed distance of the differential AG code.
 
-        If the code is of dimension zero, then a ``ValueError`` is raised.
+        If the code is of dimension zero, then a :class:`ValueError` is raised.
 
         EXAMPLES::
 
diff --git a/src/sage/coding/code_bounds.py b/src/sage/coding/code_bounds.py
index c1e0eb77022..b192792f11f 100644
--- a/src/sage/coding/code_bounds.py
+++ b/src/sage/coding/code_bounds.py
@@ -192,10 +192,10 @@ def _check_n_q_d(n, q, d, field_based=True):
     Check that the length `n`, alphabet size `q` and minimum distance `d` type
     check and make sense for a code over a field.
 
-    More precisely, checks that the parameters are positive integers, that `q`
-    is a prime power for codes over a field, or, more generally, that
-    `q` is of size at least 2, and that `n >= d`. Raises a ``ValueError``
-    otherwise.
+    More precisely, this checks that the parameters are positive
+    integers, that `q` is a prime power for codes over a field, or,
+    more generally, that `q` is of size at least 2, and that `n >= d`.
+    This raises a :class:`ValueError` otherwise.
 
     TESTS::
 
diff --git a/src/sage/coding/code_constructions.py b/src/sage/coding/code_constructions.py
index d96ad3c7627..6870c28ec87 100644
--- a/src/sage/coding/code_constructions.py
+++ b/src/sage/coding/code_constructions.py
@@ -155,8 +155,8 @@ def _is_a_splitting(S1, S2, n, return_automorphism=False):
     This is a special case of Theorem 6.4.3 in [HP2003]_.
     """
     R = IntegerModRing(n)
-    S1 = set(R(x) for x in S1)
-    S2 = set(R(x) for x in S2)
+    S1 = {R(x) for x in S1}
+    S2 = {R(x) for x in S2}
 
     # we first check whether (S1,S2) is a partition of R - {0}
     if (len(S1) + len(S2) != n-1 or len(S1) != len(S2) or
diff --git a/src/sage/coding/databases.py b/src/sage/coding/databases.py
index 3826734c09b..989d67e89d8 100644
--- a/src/sage/coding/databases.py
+++ b/src/sage/coding/databases.py
@@ -174,7 +174,7 @@ def best_linear_code_in_codetables_dot_de(n, k, F, verbose=False):
     i = s.find("
")
     j = s.find("
") if i == -1 or j == -1: - raise IOError("Error parsing data (missing pre tags).") + raise OSError("Error parsing data (missing pre tags).") return s[i+5:j].strip() diff --git a/src/sage/coding/delsarte_bounds.py b/src/sage/coding/delsarte_bounds.py index 47c84b2c59b..e25c0a4faf4 100644 --- a/src/sage/coding/delsarte_bounds.py +++ b/src/sage/coding/delsarte_bounds.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Delsarte (or linear programming) bounds @@ -24,9 +23,8 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ # **************************************************************************** -from __future__ import print_function, division def krawtchouk(n, q, l, x, check=True): @@ -142,7 +140,6 @@ def eberlein(n, w, k, u, check=True): default. Otherwise, pass it as it is. Use ``check=False`` at your own risk. - EXAMPLES:: sage: codes.bounds.eberlein(24,10,2,6) @@ -169,13 +166,12 @@ def eberlein(n, w, k, u, check=True): Traceback (most recent call last): ... TypeError: either m or x-m must be an integer - """ from sage.arith.misc import binomial from sage.arith.srange import srange - if 2*w > n: - return eberlein(n, n-w, k, u) + if 2 * w > n: + return eberlein(n, n - w, k, u) if check: from sage.rings.integer_ring import ZZ @@ -185,10 +181,10 @@ def eberlein(n, w, k, u, check=True): n = n0 return sum([(-1)**j*binomial(u, j)*binomial(w-u, k-j)*binomial(n-w-u, k-j) - for j in srange(0, k+1)]) + for j in srange(k + 1)]) -def _delsarte_LP_building(n, d, d_star, q, isinteger, solver, maxc=0): +def _delsarte_LP_building(n, d, d_star, q, isinteger, solver, maxc=0): r""" LP builder - common for the two functions; not exported. @@ -211,18 +207,18 @@ def _delsarte_LP_building(n, d, d_star, q, isinteger, solver, maxc=0): x_0 is a continuous variable (min=0, max=+oo) ... x_7 is a continuous variable (min=0, max=+oo) - """ from sage.numerical.mip import MixedIntegerLinearProgram p = MixedIntegerLinearProgram(maximization=True, solver=solver) A = p.new_variable(integer=isinteger, nonnegative=True) - p.set_objective(sum([A[r] for r in range(n+1)])) + p.set_objective(sum([A[r] for r in range(n + 1)])) p.add_constraint(A[0] == 1) for i in range(1, d): p.add_constraint(A[i] == 0) - for j in range(1, n+1): - rhs = sum([krawtchouk(n, q, j, r, check=False)*A[r] for r in range(n+1)]) + for j in range(1, n + 1): + rhs = sum([krawtchouk(n, q, j, r, check=False) * A[r] + for r in range(n + 1)]) p.add_constraint(0 <= rhs) if j >= d_star: p.add_constraint(0 <= rhs) @@ -230,7 +226,7 @@ def _delsarte_LP_building(n, d, d_star, q, isinteger, solver, maxc=0): p.add_constraint(0 == rhs) if maxc > 0: - p.add_constraint(sum([A[r] for r in range(n+1)]), max=maxc) + p.add_constraint(sum([A[r] for r in range(n + 1)]), max=maxc) return A, p @@ -272,10 +268,9 @@ def _delsarte_cwc_LP_building(n, d, w, solver, isinteger): Variables: x_0 is a continuous variable (min=0, max=+oo) x_1 is a continuous variable (min=0, max=+oo) - """ - from sage.numerical.mip import MixedIntegerLinearProgram from sage.arith.misc import binomial + from sage.numerical.mip import MixedIntegerLinearProgram p = MixedIntegerLinearProgram(maximization=True, solver=solver) A = p.new_variable(integer=isinteger, nonnegative=True) @@ -287,12 +282,14 @@ def _q(k, i): return mu_i*eberlein(n, w, i, k)/v_i for k in range(1, w+1): - p.add_constraint(sum([A[2*i]*_q(k, i) for i in range(d//2, w+1)]), min=-1) + p.add_constraint(sum([A[2*i]*_q(k, i) for i in range(d//2, w+1)]), + min=-1) return A, p -def delsarte_bound_constant_weight_code(n, d, w, return_data=False, solver="PPL", isinteger=False): +def delsarte_bound_constant_weight_code(n, d, w, return_data=False, + solver="PPL", isinteger=False): r""" Find the Delsarte bound on a constant weight code. @@ -337,15 +334,16 @@ def delsarte_bound_constant_weight_code(n, d, w, return_data=False, solver="PPL" sage: codes.bounds.delsarte_bound_constant_weight_code(17, 4, 3, isinteger=True) 43 - """ from sage.numerical.mip import MIPSolverException if d < 4: - raise ValueError("Violated constraint d>=4 for Binary Constant Weight Codes") + raise ValueError("Violated constraint d>=4 for " + "Binary Constant Weight Codes") if d >= 2*w or 2*w > n: - raise ValueError("Violated constraint d<2w<=n for Binary Constant Weight Codes") + raise ValueError("Violated constraint d<2w<=n for " + "Binary Constant Weight Codes") # minimum distance is even => if there is an odd lower bound on d we can # increase it by 1 @@ -356,23 +354,19 @@ def delsarte_bound_constant_weight_code(n, d, w, return_data=False, solver="PPL" try: bd = p.solve() except MIPSolverException as exc: - print("Solver exception: {}".format(exc)) - if return_data: - return A, p, False - return False + print(f"Solver exception: {exc}") + return (A, p, False) if return_data else False - if return_data: - return A, p, bd - else: - return int(bd) + return (A, p, bd) if return_data else int(bd) -def delsarte_bound_hamming_space(n, d, q, return_data=False, solver="PPL", isinteger=False): +def delsarte_bound_hamming_space(n, d, q, return_data=False, + solver="PPL", isinteger=False): r""" Find the Delsarte bound on codes in ``H_q^n`` of minimal distance ``d`` - Find the Delsarte bound [De1973]_ on the size of codes in the Hamming space ``H_q^n`` - of minimal distance ``d``. + Find the Delsarte bound [De1973]_ on the size of codes in + the Hamming space ``H_q^n`` of minimal distance ``d``. INPUT: @@ -444,94 +438,92 @@ def delsarte_bound_hamming_space(n, d, q, return_data=False, solver="PPL", isint try: bd = p.solve() except MIPSolverException as exc: - print("Solver exception: {}".format(exc)) - if return_data: - return A, p, False - return False + print(f"Solver exception: {exc}") + return (A, p, False) if return_data else False - if return_data: - return A, p, bd - else: - return bd + return (A, p, bd) if return_data else bd -def delsarte_bound_additive_hamming_space(n, d, q, d_star=1, q_base=0, return_data=False, solver="PPL", isinteger=False): +def delsarte_bound_additive_hamming_space(n, d, q, d_star=1, q_base=0, return_data=False, + solver="PPL", isinteger=False): r""" - Find a modified Delsarte bound on additive codes in Hamming space `H_q^n` of minimal distance `d` + Find a modified Delsarte bound on additive codes in Hamming space `H_q^n` of minimal distance `d`. - Find the Delsarte LP bound on ``F_{q_base}``-dimension of additive codes in - Hamming space `H_q^n` of minimal distance ``d`` with minimal distance of the dual - code at least ``d_star``. If ``q_base`` is set to - non-zero, then ``q`` is a power of ``q_base``, and the code is, formally, linear over - ``F_{q_base}``. Otherwise it is assumed that ``q_base==q``. + Find the Delsarte LP bound on ``F_{q_base}``-dimension of additive + codes in Hamming space `H_q^n` of minimal distance ``d`` with + minimal distance of the dual code at least ``d_star``. If + ``q_base`` is set to non-zero, then ``q`` is a power of + ``q_base``, and the code is, formally, linear over + ``F_{q_base}``. Otherwise it is assumed that ``q_base==q``. + INPUT: - INPUT: - - - ``n`` -- the code length + - ``n`` -- the code length - - ``d`` -- the (lower bound on) minimal distance of the code + - ``d`` -- the (lower bound on) minimal distance of the code - - ``q`` -- the size of the alphabet + - ``q`` -- the size of the alphabet - - ``d_star`` -- the (lower bound on) minimal distance of the dual code; - only makes sense for additive codes. + - ``d_star`` -- the (lower bound on) minimal distance of the dual code; + only makes sense for additive codes. - - ``q_base`` -- if ``0``, the code is assumed to be linear. Otherwise, - ``q=q_base^m`` and the code is linear over ``F_{q_base}``. + - ``q_base`` -- if ``0``, the code is assumed to be linear. Otherwise, + ``q=q_base^m`` and the code is linear over ``F_{q_base}``. - - ``return_data`` -- if ``True``, return a triple ``(W,LP,bound)``, where ``W`` is - a weights vector, and ``LP`` the Delsarte bound LP; both of them are Sage LP - data. ``W`` need not be a weight distribution of a code, or, - if ``isinteger==False``, even have integer entries. + - ``return_data`` -- if ``True``, return a triple ``(W,LP,bound)``, + where ``W`` is a weights vector, and ``LP`` the Delsarte bound + LP; both of them are Sage LP data. ``W`` need not be a weight + distribution of a code, or, if ``isinteger==False``, even have + integer entries. - - ``solver`` -- the LP/ILP solver to be used. Defaults to ``'PPL'``. It is arbitrary - precision, thus there will be no rounding errors. With other solvers - (see :class:`MixedIntegerLinearProgram` for the list), you are on your own! + - ``solver`` -- the LP/ILP solver to be used. Defaults to ``'PPL'``. It is + arbitrary precision, thus there will be no rounding errors. With + other solvers (see :class:`MixedIntegerLinearProgram` for the + list), you are on your own! - - ``isinteger`` -- if ``True``, uses an integer programming solver (ILP), rather - that an LP solver. Can be very slow if set to ``True``. + - ``isinteger`` -- if ``True``, uses an integer programming solver (ILP), + rather that an LP solver. Can be very slow if set to ``True``. - EXAMPLES: + EXAMPLES: - The bound on dimension of linear `\GF{2}`-codes of length 11 and minimal distance 6:: + The bound on dimension of linear `\GF{2}`-codes of length 11 and minimal distance 6:: - sage: codes.bounds.delsarte_bound_additive_hamming_space(11, 6, 2) - 3 - sage: a,p,val = codes.bounds.delsarte_bound_additive_hamming_space(\ - 11, 6, 2, return_data=True) - sage: [j for i,j in p.get_values(a).items()] - [1, 0, 0, 0, 0, 0, 5, 2, 0, 0, 0, 0] + sage: codes.bounds.delsarte_bound_additive_hamming_space(11, 6, 2) + 3 + sage: a,p,val = codes.bounds.delsarte_bound_additive_hamming_space(\ + 11, 6, 2, return_data=True) + sage: [j for i,j in p.get_values(a).items()] + [1, 0, 0, 0, 0, 0, 5, 2, 0, 0, 0, 0] - The bound on the dimension of linear `\GF{4}`-codes of length 11 and minimal distance 3:: + The bound on the dimension of linear `\GF{4}`-codes of length 11 and minimal distance 3:: - sage: codes.bounds.delsarte_bound_additive_hamming_space(11,3,4) - 8 + sage: codes.bounds.delsarte_bound_additive_hamming_space(11,3,4) + 8 - The bound on the `\GF{2}`-dimension of additive `\GF{4}`-codes of length 11 and minimal - distance 3:: + The bound on the `\GF{2}`-dimension of additive `\GF{4}`-codes of length 11 and minimal + distance 3:: - sage: codes.bounds.delsarte_bound_additive_hamming_space(11,3,4,q_base=2) - 16 + sage: codes.bounds.delsarte_bound_additive_hamming_space(11,3,4,q_base=2) + 16 - Such a ``d_star`` is not possible:: + Such a ``d_star`` is not possible:: - sage: codes.bounds.delsarte_bound_additive_hamming_space(11,3,4,d_star=9) - Solver exception: PPL : There is no feasible solution - False + sage: codes.bounds.delsarte_bound_additive_hamming_space(11,3,4,d_star=9) + Solver exception: PPL : There is no feasible solution + False - TESTS:: + TESTS:: - sage: a,p,x = codes.bounds.delsarte_bound_additive_hamming_space(\ - 19,15,7,return_data=True,isinteger=True) - sage: [j for i,j in p.get_values(a).items()] - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 307, 0, 0, 1, 34] - sage: codes.bounds.delsarte_bound_additive_hamming_space(19,15,7,solver='glpk') - 3 - sage: codes.bounds.delsarte_bound_additive_hamming_space(\ - 19,15,7, isinteger=True, solver='glpk') - 3 - """ + sage: a,p,x = codes.bounds.delsarte_bound_additive_hamming_space(\ + 19,15,7,return_data=True,isinteger=True) + sage: [j for i,j in p.get_values(a).items()] + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 307, 0, 0, 1, 34] + sage: codes.bounds.delsarte_bound_additive_hamming_space(19,15,7,solver='glpk') + 3 + sage: codes.bounds.delsarte_bound_additive_hamming_space(\ + 19,15,7, isinteger=True, solver='glpk') + 3 + """ from sage.numerical.mip import MIPSolverException if q_base == 0: q_base = q @@ -550,18 +542,18 @@ def delsarte_bound_additive_hamming_space(n, d, q, d_star=1, q_base=0, return_da m = kk*n # this is to emulate repeat/until block bd = q**n+1 - while q_base**m < bd: # need to solve the LP repeatedly, as this is a new constraint! + while q_base**m < bd: + # need to solve the LP repeatedly, as this is a new constraint! # we might become infeasible. More precisely, after rounding down # to the closest value of q_base^m, the LP, with the constraint that # the objective function is at most q_base^m, - A, p = _delsarte_LP_building(n, d, d_star, q, isinteger, solver, q_base**m) + A, p = _delsarte_LP_building(n, d, d_star, q, isinteger, + solver, q_base**m) try: bd = p.solve() except MIPSolverException as exc: print("Solver exception:", exc) - if return_data: - return A, p, False - return False + return (A, p, False) if return_data else False # rounding the bound down to the nearest power of q_base, for q=q_base^m # bd_r = roundres(log(bd, base=q_base)) m = -1 @@ -570,10 +562,7 @@ def delsarte_bound_additive_hamming_space(n, d, q, d_star=1, q_base=0, return_da if q_base**(m+1) == bd: m += 1 - if return_data: - return A, p, m - else: - return m + return (A, p, m) if return_data else m def _delsarte_Q_LP_building(q, d, solver, isinteger): @@ -644,12 +633,13 @@ def _delsarte_Q_LP_building(q, d, solver, isinteger): p.add_constraint(A[i] == 0) for k in range(1, n): - p.add_constraint(sum([q[k][i]*A[i] for i in range(n)]), min=0) + p.add_constraint(sum([q[k][i] * A[i] for i in range(n)]), min=0) return A, p -def delsarte_bound_Q_matrix(q, d, return_data=False, solver="PPL", isinteger=False): +def delsarte_bound_Q_matrix(q, d, return_data=False, + solver="PPL", isinteger=False): r""" Delsarte bound on a code with Q matrix ``q`` and lower bound on min. dist. ``d``. @@ -701,25 +691,19 @@ def delsarte_bound_Q_matrix(q, d, return_data=False, solver="PPL", isinteger=Fal sage: a,p,val = codes.bounds.delsarte_bound_Q_matrix(q_matrix, 6, return_data=True) sage: [j for i,j in p.get_values(a).items()] [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] - """ - - from sage.structure.element import is_Matrix from sage.numerical.mip import MIPSolverException + from sage.structure.element import is_Matrix if not is_Matrix(q): - raise ValueError("Input to delsarte_bound_Q_matrix should be a sage Matrix()") + raise ValueError("Input to delsarte_bound_Q_matrix " + "should be a sage Matrix()") A, p = _delsarte_Q_LP_building(q, d, solver, isinteger) try: bd = p.solve() except MIPSolverException as exc: - print("Solver exception: {}".format(exc)) - if return_data: - return A, p, False - return False + print(f"Solver exception: {exc}") + return (A, p, False) if return_data else False - if return_data: - return A, p, bd - else: - return bd + return (A, p, bd) if return_data else bd diff --git a/src/sage/coding/guruswami_sudan/interpolation.py b/src/sage/coding/guruswami_sudan/interpolation.py index eea537a115a..62ed9f3a8f1 100644 --- a/src/sage/coding/guruswami_sudan/interpolation.py +++ b/src/sage/coding/guruswami_sudan/interpolation.py @@ -130,7 +130,7 @@ def eqs_affine(x0, y0): eqs = [] for i in range(s): for j in range(s - i): - eq = dict() + eq = {} for monomial in monomials: ihat = monomial[0] jhat = monomial[1] diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index 0d84996a483..f8f051a00d9 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -1224,7 +1224,7 @@ def galois_closure(self, F0): n = len(G.columns()) k = len(G.rows()) G0 = [[x**q0 for x in g.list()] for g in G.rows()] - G1 = [[x for x in g.list()] for g in G.rows()] + G1 = [list(g.list()) for g in G.rows()] G2 = G0+G1 MS = MatrixSpace(F,2*k,n) G3 = MS(G2) @@ -1342,7 +1342,7 @@ def minimum_distance(self, algorithm=None): .. NOTE:: - When using GAP, this raises a ``NotImplementedError`` if + When using GAP, this raises a :class:`NotImplementedError` if the base field of the code has size greater than 256 due to limitations in GAP. @@ -1657,7 +1657,7 @@ def permutation_automorphism_group(self, algorithm="partition"): size = Gp.Size() print("\n Using the %s codewords of weight %s \n Supergroup size: \n %s\n " % (wts[wt], wt, size)) Cwt = filter(lambda c: c.WeightCodeword() == wt, eltsC) # bottleneck 2 (repeated - matCwt = list(map(lambda c: c.VectorCodeword(), Cwt)) # for each i until stop = 1) + matCwt = [c.VectorCodeword() for c in Cwt] # for each i until stop = 1) if len(matCwt) > 0: A = libgap(matCwt).MatrixAutomorphisms() Gp = A.Intersection2(Gp) # bottleneck 3 diff --git a/src/sage/coding/source_coding/huffman.py b/src/sage/coding/source_coding/huffman.py index 6c08a4d885a..1d41f84d77f 100644 --- a/src/sage/coding/source_coding/huffman.py +++ b/src/sage/coding/source_coding/huffman.py @@ -367,9 +367,9 @@ def pop(): # Build the binary tree of a Huffman code, where the root of the tree # is associated with the empty string. self._build_code_from_tree(self._tree, d, prefix="") - self._index = dict((i, s) for i, (s, w) in enumerate(symbols)) - self._character_to_code = dict( - (s, d[i]) for i, (s, w) in enumerate(symbols)) + self._index = {i: s for i, (s, w) in enumerate(symbols)} + self._character_to_code = { + s: d[i] for i, (s, w) in enumerate(symbols)} def encode(self, string): r""" diff --git a/src/sage/combinat/integer_vector.py b/src/sage/combinat/integer_vector.py index 456eea36f44..4f1176c62a8 100644 --- a/src/sage/combinat/integer_vector.py +++ b/src/sage/combinat/integer_vector.py @@ -48,7 +48,7 @@ from sage.rings.integer import Integer -def is_gale_ryser(r,s): +def is_gale_ryser(r, s): r""" Tests whether the given sequences satisfy the condition of the Gale-Ryser theorem. @@ -314,20 +314,20 @@ def gale_ryser_theorem(p1, p2, algorithm="gale", """ from sage.matrix.constructor import matrix - if not is_gale_ryser(p1,p2): + if not is_gale_ryser(p1, p2): return False - if algorithm == "ryser": # ryser's algorithm + if algorithm == "ryser": # ryser's algorithm from sage.combinat.permutation import Permutation # Sorts the sequences if they are not, and remembers the permutation # applied - tmp = sorted(enumerate(p1), reverse=True, key=lambda x:x[1]) + tmp = sorted(enumerate(p1), reverse=True, key=lambda x: x[1]) r = [x[1] for x in tmp] r_permutation = [x-1 for x in Permutation([x[0]+1 for x in tmp]).inverse()] m = len(r) - tmp = sorted(enumerate(p2), reverse=True, key=lambda x:x[1]) + tmp = sorted(enumerate(p2), reverse=True, key=lambda x: x[1]) s = [x[1] for x in tmp] s_permutation = [x-1 for x in Permutation([x[0]+1 for x in tmp]).inverse()] @@ -340,12 +340,12 @@ def gale_ryser_theorem(p1, p2, algorithm="gale", k = i + 1 while k < m and r[i] == r[k]: k += 1 - if t >= k - i: # == number rows of the same length + if t >= k - i: # == number rows of the same length for j in range(i, k): r[j] -= 1 c[j] = 1 t -= k - i - else: # Remove the t last rows of that length + else: # Remove the t last rows of that length for j in range(k-t, k): r[j] -= 1 c[j] = 1 @@ -366,17 +366,17 @@ def gale_ryser_theorem(p1, p2, algorithm="gale", k1, k2 = len(p1), len(p2) p = MixedIntegerLinearProgram(solver=solver) b = p.new_variable(binary=True) - for (i,c) in enumerate(p1): - p.add_constraint(p.sum([b[i,j] for j in range(k2)]) == c) - for (i,c) in enumerate(p2): - p.add_constraint(p.sum([b[j,i] for j in range(k1)]) == c) + for (i, c) in enumerate(p1): + p.add_constraint(p.sum([b[i, j] for j in range(k2)]) == c) + for (i, c) in enumerate(p2): + p.add_constraint(p.sum([b[j, i] for j in range(k1)]) == c) p.set_objective(None) p.solve() b = p.get_values(b, convert=ZZ, tolerance=integrality_tolerance) M = [[0]*k2 for i in range(k1)] for i in range(k1): for j in range(k2): - M[i][j] = b[i,j] + M[i][j] = b[i, j] return matrix(M) else: @@ -780,6 +780,43 @@ def __contains__(self, x): return False return True + def _unrank_helper(self, x, rtn): + """ + Return the element at rank ``x`` by iterating through all integer vectors beginning with ``rtn``. + + INPUT: + + - ``x`` - a nonnegative integer + - ``rtn`` - a list of nonnegative integers + + + EXAMPLES:: + + sage: IV = IntegerVectors(k=5) + sage: IV._unrank_helper(10, [2,0,0,0,0]) + [1, 0, 0, 0, 1] + + sage: IV = IntegerVectors(n=7) + sage: IV._unrank_helper(100, [7,0,0,0]) + [2, 0, 0, 5] + + sage: IV = IntegerVectors(n=12, k=7) + sage: IV._unrank_helper(1000, [12,0,0,0,0,0,0]) + [5, 3, 1, 1, 1, 1, 0] + """ + ptr = 0 + while True: + current_rank = self.rank(rtn) + if current_rank < x: + rtn[ptr+1] = rtn[ptr] + rtn[ptr] = 0 + ptr += 1 + elif current_rank > x: + rtn[ptr] -= 1 + rtn[ptr-1] += 1 + else: + return self._element_constructor_(rtn) + class IntegerVectors_all(UniqueRepresentation, IntegerVectors): """ @@ -839,7 +876,10 @@ def __init__(self, n): sage: TestSuite(IV).run() """ self.n = n - IntegerVectors.__init__(self, category=InfiniteEnumeratedSets()) + if self.n == 0: + IntegerVectors.__init__(self, category=EnumeratedSets()) + else: + IntegerVectors.__init__(self, category=InfiniteEnumeratedSets()) def _repr_(self): """ @@ -898,6 +938,68 @@ def __contains__(self, x): return False return sum(x) == self.n + def rank(self, x): + """ + Return the rank of a given element. + + INPUT: + + - ``x`` -- a list with ``sum(x) == n`` + + EXAMPLES:: + + sage: IntegerVectors(n=5).rank([5,0]) + 1 + sage: IntegerVectors(n=5).rank([3,2]) + 3 + """ + if sum(x) != self.n: + raise ValueError("argument is not a member of IntegerVectors({},{})".format(self.n, None)) + + n, k, s = self.n, len(x), 0 + r = binomial(k + n - 1, n + 1) + for i in range(k - 1): + s += x[k - 1 - i] + r += binomial(s + i, i + 1) + return r + + def unrank(self, x): + """ + Return the element at given rank x. + + INPUT: + + - ``x`` -- an integer. + + EXAMPLES:: + + sage: IntegerVectors(n=5).unrank(2) + [4, 1] + sage: IntegerVectors(n=10).unrank(10) + [1, 9] + """ + rtn = [self.n] + while self.rank(rtn) <= x: + rtn.append(0) + rtn.pop() + + return IntegerVectors._unrank_helper(self, x, rtn) + + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: IntegerVectors(n=0).cardinality() + 1 + sage: IntegerVectors(n=10).cardinality() + +Infinity + """ + if self.n == 0: + return Integer(1) + return PlusInfinity() + class IntegerVectors_k(UniqueRepresentation, IntegerVectors): """ @@ -912,7 +1014,10 @@ def __init__(self, k): sage: TestSuite(IV).run() """ self.k = k - IntegerVectors.__init__(self, category=InfiniteEnumeratedSets()) + if self.k == 0: + IntegerVectors.__init__(self, category=EnumeratedSets()) + else: + IntegerVectors.__init__(self, category=InfiniteEnumeratedSets()) def _repr_(self): """ @@ -968,6 +1073,75 @@ def __contains__(self, x): return False return len(x) == self.k + def rank(self, x): + """ + Return the rank of a given element. + + INPUT: + + - ``x`` -- a list with ``len(x) == k`` + + EXAMPLES:: + + sage: IntegerVectors(k=5).rank([0,0,0,0,0]) + 0 + sage: IntegerVectors(k=5).rank([1,1,0,0,0]) + 7 + """ + if len(x) != self.k: + raise ValueError("argument is not a member of IntegerVectors({},{})".format(None, self.k)) + + n, k, s = sum(x), self.k, 0 + r = binomial(n + k - 1, k) + for i in range(k - 1): + s += x[k - 1 - i] + r += binomial(s + i, i + 1) + return r + + def unrank(self, x): + """ + Return the element at given rank x. + + INPUT: + + - ``x`` -- an integer such that x < self.cardinality()`` + + EXAMPLES:: + + sage: IntegerVectors(k=5).unrank(10) + [1, 0, 0, 0, 1] + sage: IntegerVectors(k=5).unrank(15) + [0, 0, 2, 0, 0] + sage: IntegerVectors(k=0).unrank(0) + [] + """ + if self.k == 0 and x != 0: + raise IndexError(f"Index {x} is out of range for the IntegerVector.") + rtn = [0]*self.k + if self.k == 0 and x == 0: + return rtn + + while self.rank(rtn) <= x: + rtn[0] += 1 + rtn[0] -= 1 + + return IntegerVectors._unrank_helper(self, x, rtn) + + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: IntegerVectors(k=0).cardinality() + 1 + sage: IntegerVectors(k=10).cardinality() + +Infinity + """ + if self.k == 0: + return Integer(1) + return PlusInfinity() + class IntegerVectors_nk(UniqueRepresentation, IntegerVectors): """ @@ -1010,11 +1184,11 @@ def _list_rec(self, n, k): res = [] if k == 1: - return [ (n, ) ] + return [(n, )] for nbar in range(n + 1): n_diff = n - nbar - for rest in self._list_rec( nbar , k - 1): + for rest in self._list_rec(nbar, k - 1): res.append((n_diff,) + rest) return res @@ -1153,17 +1327,49 @@ def rank(self, x): if x not in self: raise ValueError("argument is not a member of IntegerVectors({},{})".format(self.n, self.k)) - n = self.n - k = self.k - - r = 0 + k, s, r = self.k, 0, 0 for i in range(k - 1): - k -= 1 - n -= x[i] - r += binomial(k + n - 1, k) - + s += x[k - 1 - i] + r += binomial(s + i, i + 1) return r + def unrank(self, x): + """ + Return the element at given rank x. + + INPUT: + + - ``x`` -- an integer such that ``x < self.cardinality()`` + + EXAMPLES:: + + sage: IntegerVectors(4,5).unrank(30) + [1, 0, 1, 0, 2] + sage: IntegerVectors(2,3).unrank(5) + [0, 0, 2] + """ + if x >= self.cardinality(): + raise IndexError(f"Index {x} is out of range for the IntegerVector.") + rtn = [0]*self.k + rtn[0] = self.n + return IntegerVectors._unrank_helper(self, x, rtn) + + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: IntegerVectors(3,5).cardinality() + 35 + sage: IntegerVectors(99, 3).cardinality() + 5050 + sage: IntegerVectors(10^9 - 1, 3).cardinality() + 500000000500000000 + """ + n, k = self.n, self.k + return Integer(binomial(n + k - 1, n)) + class IntegerVectors_nnondescents(UniqueRepresentation, IntegerVectors): r""" @@ -1320,11 +1526,11 @@ def __init__(self, n=None, k=None, **constraints): category = FiniteEnumeratedSets() else: category = EnumeratedSets() - elif k is not None and 'max_part' in constraints: # n is None + elif k is not None and 'max_part' in constraints: # n is None category = FiniteEnumeratedSets() else: category = EnumeratedSets() - IntegerVectors.__init__(self, category=category) # placeholder category + IntegerVectors.__init__(self, category=category) # placeholder category def _repr_(self): """ diff --git a/src/sage/combinat/integer_vectors_mod_permgroup.py b/src/sage/combinat/integer_vectors_mod_permgroup.py index 996d73a5efa..7877fedee44 100644 --- a/src/sage/combinat/integer_vectors_mod_permgroup.py +++ b/src/sage/combinat/integer_vectors_mod_permgroup.py @@ -1,6 +1,11 @@ # sage.doctest: needs sage.combinat sage.groups r""" Integer vectors modulo the action of a permutation group + +AUTHORS: + +* Nicolas Borie (2010-2012) - original module +* Jukka Kohonen (2023) - fast cardinality method, :issue:`36787`, :issue:`36681` """ # **************************************************************************** # Copyright (C) 2010-12 Nicolas Borie @@ -24,6 +29,12 @@ from sage.combinat.integer_vector import IntegerVectors +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.rational_field import QQ +from sage.rings.integer import Integer +from sage.misc.misc_c import prod +from sage.arith.misc import binomial + class IntegerVectorsModPermutationGroup(UniqueRepresentation): r""" @@ -42,39 +53,39 @@ class IntegerVectorsModPermutationGroup(UniqueRepresentation): v = \max_{\text{lex order}} \{g \cdot v | g \in G \} The action of `G` is on position. This means for example that the - simple transposition `s_1 = (1, 2)` swaps the first and the second entries - of any integer vector `v = [a_1, a_2, a_3, \dots , a_n]` + simple transposition `s_1 = (1, 2)` swaps the first and the second + entries of any integer vector `v = [a_1, a_2, a_3, \dots , a_n]` .. MATH:: s_1 \cdot v = [a_2, a_1, a_3, \dots , a_n] - This functions returns a parent which contains a single integer - vector by orbit under the action of the permutation group `G`. The - approach chosen here is to keep the maximal integer vector for the - lexicographic order in each orbit. Such maximal vector will be - called canonical integer vector under the action of the - permutation group `G`. + This function returns a parent which contains, from each orbit + orbit under the action of the permutation group `G`, a single + canonical vector. The canonical vector is the one that is maximal + within the orbit according to lexicographic order. INPUT: - ``G`` - a permutation group - ``sum`` - (default: None) - a nonnegative integer - ``max_part`` - (default: None) - a nonnegative integer setting the - maximum of entries of elements + maximum value for every element - ``sgs`` - (default: None) - a strong generating system of the group `G`. If you do not provide it, it will be calculated at the creation of the parent OUTPUT: - - If ``sum`` and ``max_part`` are None, it returns the infinite enumerated - set of all integer vectors (list of integers) maximal in their orbit for - the lexicographic order. + - If ``sum`` and ``max_part`` are None, it returns the infinite + enumerated set of all integer vectors (lists of integers) maximal + in their orbit for the lexicographic order. Exceptionally, if + the domain of ``G`` is empty, the result is a finite enumerated + set that contains one element, namely the empty vector. - - If ``sum`` is an integer, it returns a finite enumerated set containing - all integer vectors maximal in their orbit for the lexicographic order - and whose entries sum to ``sum``. + - If ``sum`` is an integer, it returns a finite enumerated set + containing all integer vectors maximal in their orbit for the + lexicographic order and whose entries sum to ``sum``. EXAMPLES: @@ -105,7 +116,7 @@ class IntegerVectorsModPermutationGroup(UniqueRepresentation): The method :meth:`~sage.combinat.integer_vectors_mod_permgroup.IntegerVectorsModPermutationGroup_All.is_canonical` - tests if any integer vector is maximal in its orbit. This method + tests if an integer vector is maximal in its orbit. This method is also used in the containment test:: sage: I = IntegerVectorsModPermutationGroup(PermutationGroup([[(1,2,3,4)]])) @@ -124,7 +135,7 @@ class IntegerVectorsModPermutationGroup(UniqueRepresentation): sage: I.is_canonical('bla') Traceback (most recent call last): ... - AssertionError: bla should be a list or a integer vector + AssertionError: bla should be a list or an integer vector If you give a value to the extra argument ``sum``, the set returned will be a finite set containing only canonical vectors whose entries @@ -153,10 +164,32 @@ class IntegerVectorsModPermutationGroup(UniqueRepresentation): sage: I.orbit([2,2,2]) {[2, 2, 2]} + Even without constraints, for an empty domain the result is + a singleton set:: + + sage: G = PermutationGroup([], domain=[]) + sage: sgs = tuple(tuple(s) for s in G.strong_generating_system()) + sage: list(IntegerVectorsModPermutationGroup(G, sgs=sgs)) + [[]] + + + .. WARNING:: + + Because of :issue:`36527`, permutation groups that have + different domains but similar generators can be erroneously + treated as the same group. This will silently produce + erroneous results. To avoid this issue, compute a strong + generating system for the group as:: + + sgs = tuple(tuple(s) for s in G.strong_generating_system()) + + and provide it as the optional ``sgs`` argument to the + constructor. + TESTS: Let us check that canonical integer vectors of the symmetric group - are just sorted list of integers:: + are just nonincreasing lists of integers:: sage: I = IntegerVectorsModPermutationGroup(SymmetricGroup(5)) # long time sage: p = iter(I) # long time @@ -164,9 +197,9 @@ class IntegerVectorsModPermutationGroup(UniqueRepresentation): ....: v = list(next(p)) ....: assert sorted(v, reverse=True) == v - We now check that there is as much of canonical vectors under the - symmetric group `S_n` whose entries sum to `d` than partitions of - `d` of at most `n` parts:: + We now check that there are as many canonical vectors under the + symmetric group `S_n` whose entries sum to `d` as there are + partitions of `d` of at most `n` parts:: sage: I = IntegerVectorsModPermutationGroup(SymmetricGroup(5)) # long time sage: for i in range(10): # long time @@ -185,15 +218,16 @@ class IntegerVectorsModPermutationGroup(UniqueRepresentation): 18 23 - We present a last corner case: trivial groups. For the trivial - group ``G`` acting on a list of length `n`, all integer vectors of - length `n` are canonical:: + Another corner case is trivial groups. For the trivial group ``G`` + acting on a list of length `n`, all integer vectors of length `n` + are canonical:: sage: # long time sage: G = PermutationGroup([[(6,)]]) sage: G.cardinality() 1 - sage: I = IntegerVectorsModPermutationGroup(G) + sage: sgs = tuple(tuple(s) for s in G.strong_generating_system()) + sage: I = IntegerVectorsModPermutationGroup(G, sgs=sgs) sage: for i in range(10): ....: d1 = I.subset(i).cardinality() ....: d2 = IntegerVectors(i,6).cardinality() @@ -209,6 +243,7 @@ class IntegerVectorsModPermutationGroup(UniqueRepresentation): 792 1287 2002 + """ @staticmethod def __classcall__(cls, G, sum=None, max_part=None, sgs=None): @@ -225,13 +260,22 @@ def __classcall__(cls, G, sum=None, max_part=None, sgs=None): sage: I = IntegerVectorsModPermutationGroup(PermutationGroup([[(1,2,3)]]), 8, max_part=5) """ if sum is None and max_part is None: - return IntegerVectorsModPermutationGroup_All(G, sgs=sgs) + # No constraints. + if G.domain(): + # Nonempty domain, infinite set. + return IntegerVectorsModPermutationGroup_All(G, sgs=sgs) + else: + # Empty domain, singleton set. + return IntegerVectorsModPermutationGroup_with_constraints( + G, 0, max_part=-1, sgs=sgs) else: + # Some constraints, either sum or max_part or both. if sum is not None: assert sum == NN(sum) if max_part is not None: assert max_part == NN(max_part) - return IntegerVectorsModPermutationGroup_with_constraints(G, sum, max_part, sgs=sgs) + return IntegerVectorsModPermutationGroup_with_constraints( + G, sum, max_part, sgs=sgs) class IntegerVectorsModPermutationGroup_All(UniqueRepresentation, RecursivelyEnumeratedSet_forest): @@ -435,7 +479,7 @@ def is_canonical(self, v, check=True): False """ if check: - assert isinstance(v, (ClonableIntArray, list)), '%s should be a list or a integer vector' % v + assert isinstance(v, (ClonableIntArray, list)), '%s should be a list or an integer vector' % v assert (self.n == len(v)), '%s should be of length %s' % (v, self.n) for p in v: assert (p == NN(p)), 'Elements of %s should be integers' % v @@ -634,17 +678,27 @@ def _repr_(self): sage: S = IntegerVectorsModPermutationGroup(PermutationGroup([[(1,2,3,4)]]), 6); S Integer vectors of length 4 and of sum 6 enumerated up to the action of Permutation Group with generators [(1,2,3,4)] sage: S = IntegerVectorsModPermutationGroup(PermutationGroup([[(1,2,3,4)]]), 6, max_part=4); S - Vectors of length 4 and of sum 6 whose entries is in {0, ..., 4} enumerated up to the action of Permutation Group with generators [(1,2,3,4)] + Vectors of length 4 and of sum 6 whose entries are in {0, ..., 4} enumerated up to the action of Permutation Group with generators [(1,2,3,4)] sage: S = IntegerVectorsModPermutationGroup(PermutationGroup([[(1,2,3,4)]]), max_part=4); S - Integer vectors of length 4 whose entries is in {0, ..., 4} enumerated up to the action of Permutation Group with generators [(1,2,3,4)] + Integer vectors of length 4 whose entries are in {0, ..., 4} enumerated up to the action of Permutation Group with generators [(1,2,3,4)] """ if self._sum is not None: if self._max_part >= 0: - return "Vectors of length %s and of sum %s whose entries is in {0, ..., %s} enumerated up to the action of %s" % (self.n, self._sum, self._max_part, self.permutation_group()) + return ("Vectors of length %s and of sum %s" + " whose entries are in {0, ..., %s}" + " enumerated up to the action of %s" + % (self.n, self._sum, self._max_part, + self.permutation_group())) else: - return "Integer vectors of length %s and of sum %s enumerated up to the action of %s" % (self.n, self._sum, self.permutation_group()) + return ("Integer vectors of length %s" + " and of sum %s" + " enumerated up to the action of %s" + % (self.n, self._sum, self.permutation_group())) else: - return "Integer vectors of length %s whose entries is in {0, ..., %s} enumerated up to the action of %s" % (self.n, self._max_part, self.permutation_group()) + return ("Integer vectors of length %s" + " whose entries are in {0, ..., %s}" + " enumerated up to the action of %s" + % (self.n, self._max_part, self.permutation_group())) def roots(self): r""" @@ -751,6 +805,7 @@ def __iter__(self): [2, 0, 2, 0] [2, 0, 1, 1] [1, 1, 1, 1] + sage: I = IntegerVectorsModPermutationGroup(PermutationGroup([[(1,2,3,4)]]), sum=7, max_part=3) sage: for i in I: i [3, 3, 1, 0] @@ -763,18 +818,197 @@ def __iter__(self): [3, 1, 1, 2] [3, 0, 2, 2] [2, 2, 2, 1] + + Check that :issue:`36681` is fixed:: + + sage: G = PermutationGroup([], domain=[]) + sage: I = IntegerVectorsModPermutationGroup(G, sum=0) + sage: list(iter(I)) + [[]] + + Check that :issue:`36681` is fixed:: + + sage: G = PermutationGroup([], domain=[]) + sage: I = IntegerVectorsModPermutationGroup(G, sum=3) + sage: list(iter(I)) + [] + """ + # Special cases when domain is empty. + if self.n == 0: + if self._sum is not None and self._sum > 0: + # No empty vector can have positive sum. + return iter(()) + else: + # Sum is allowed to be zero. It does not matter what + # the maxpart is, the empty vector is a solution. + return iter([self([])]) + + # General case, nonempty domain. if self._max_part < 0: return self.elements_of_depth_iterator(self._sum) else: - SF = RecursivelyEnumeratedSet_forest((self([0]*(self.n), check=False),), - lambda x : [self(y, check=False) for y in canonical_children(self._sgs, x, self._max_part)], - algorithm='breadth') + SF = RecursivelyEnumeratedSet_forest( + (self([0]*(self.n), check=False),), + lambda x: [self(y, check=False) + for y in canonical_children( + self._sgs, x, self._max_part)], + algorithm='breadth') if self._sum is None: return iter(SF) else: return SF.elements_of_depth_iterator(self._sum) + def cardinality(self): + r""" + Return the number of integer vectors in the set. + + The algorithm utilises :wikipedia:`Cycle Index Theorem `, allowing + for a faster than a plain enumeration computation. + + EXAMPLES: + + With a trivial group all vectors are canonical:: + + sage: G = PermutationGroup([], domain=[1,2,3]) + sage: IntegerVectorsModPermutationGroup(G, 5).cardinality() + 21 + sage: IntegerVectors(5, 3).cardinality() + 21 + + With two interchangeable elements, the smaller one + ranges from zero to ``sum//2``:: + + sage: G = PermutationGroup([(1,2)]) + sage: IntegerVectorsModPermutationGroup(G, 1000).cardinality() + 501 + + Binary vectors up to full symmetry are first some ones and + then some zeros:: + + sage: G = SymmetricGroup(10) + sage: I = IntegerVectorsModPermutationGroup(G, max_part=1) + sage: I.cardinality() + 11 + + Binary vectors of constant weight, up to PGL(2,17), which + is 3-transitive, but not 4-transitive:: + + sage: G=PGL(2,17) + sage: I = IntegerVectorsModPermutationGroup(G, sum=3, max_part=1) + sage: I.cardinality() + 1 + sage: I = IntegerVectorsModPermutationGroup(G, sum=4, max_part=1) + sage: I.cardinality() + 3 + + TESTS: + + Check that :issue:`36681` is fixed:: + + sage: G = PermutationGroup([], domain=[]) + sage: sgs = tuple(tuple(t) for t in G.strong_generating_system()) + sage: V = IntegerVectorsModPermutationGroup(G, sum=1, sgs=sgs) + sage: V.cardinality() + 0 + + The case when both ``sum`` and ``max_part`` are specified:: + + sage: G = PermutationGroup([(1,2,3)]) + sage: I = IntegerVectorsModPermutationGroup(G, sum=10, max_part=5) + sage: I.cardinality() + 7 + + All permutation groups of degree 4:: + + sage: for G in SymmetricGroup(4).subgroups(): + ....: sgs = tuple(tuple(t) for t in G.strong_generating_system()) + ....: I1 = IntegerVectorsModPermutationGroup(G, sum=10, sgs=sgs) + ....: assert I1.cardinality() == len(list(I1)) + ....: I2 = IntegerVectorsModPermutationGroup(G, max_part=3, sgs=sgs) + ....: assert I2.cardinality() == len(list(I2)) + ....: I3 = IntegerVectorsModPermutationGroup(G, sum=10, max_part=3, sgs=sgs) + ....: assert I3.cardinality() == len(list(I3)) + + Symmetric group with sums 0 and 1:: + + sage: S10 = SymmetricGroup(10) + sage: IntegerVectorsModPermutationGroup(S10, 0).cardinality() + 1 + sage: IntegerVectorsModPermutationGroup(S10, 1).cardinality() + 1 + + Trivial group with sums 1 and 100:: + + sage: T10 = PermutationGroup([], domain=range(1, 11)) + sage: IntegerVectorsModPermutationGroup(T10, 1).cardinality() + 10 + sage: IntegerVectorsModPermutationGroup(T10, 100).cardinality() + 4263421511271 + + """ + G = self._permgroup + k = G.degree() # Vector length + d = self._sum # Required sum + m = self._max_part # Max of one entry, -1 for no limit + if m == -1: + m = d # Any entry cannot exceed total + + # Some easy special cases. + if k == 0: + # Empty vectors. There is only one, and it has zero sum. + # Here _max_part does not matter because any _max_part + # condition is vacuously true (with no parts). + if d == 0 or d is None: + return Integer(1) + else: + return Integer(0) + if d == 0 or m == 0: + # All-zero vectors. There is only one of them. + return Integer(1) + if d == 1: + # Vectors with one 1 and all other elements zero. + # The 1 can be placed in any orbit, and by symmetry + # it will be on the first element of the orbit. + return Integer(len(G.orbits())) + if d is not None and m >= d and G.is_trivial(): + # Simple calculation with stars and bars. + return Integer(binomial(d + k - 1, k - 1)) + + # General case. + # + # Cardinality is computed using the Cycle Index Theorem. We + # have two cases. With a fixed sum d we work with power + # series and extract the x^d coefficient. Without a fixed sum + # we can do with integer arithmetic. + Z = G.cycle_index() + + if d is None: + # Case 1. Without a fixed sum, the sum can be up to k*m. + result = sum(coeff * (m+1)**len(cycle_type) + for cycle_type, coeff in Z) + # Computed as Rational, but should have an integer value + # by now. + return Integer(result) + + # Case 2. Fixed sum d. Work with power series with enough + # precision that x^d is valid. + R = PowerSeriesRing(QQ, 'x', default_prec=d+1) + x = R.gen() + + # The figure-counting series, for max_part==m, is (1-t**(m+1)) + # / (1-t) = 1+t+...+t**m. For the function-counting series, + # we substitute x**cycle_length for t. + # + funcount = sum( + coeff * prod((1 - x**((m+1)*cycle_len)) / (1 - x**cycle_len) + for cycle_len in cycle_type) + for cycle_type, coeff in Z) + + # Extract the d'th degree coefficient. Computed as Rational, + # but should have an integer value by now. + return Integer(funcount[d]) + def is_canonical(self, v, check=True): r""" Return ``True`` if the integer list ``v`` is maximal in its @@ -798,7 +1032,7 @@ def is_canonical(self, v, check=True): True """ if check: - assert isinstance(v, (ClonableIntArray, list)), '%s should be a list or a integer vector' % v + assert isinstance(v, (ClonableIntArray, list)), '%s should be a list or an integer vector' % v assert (self.n == len(v)), '%s should be of length %s' % (v, self.n) for p in v: assert (p == NN(p)), 'Elements of %s should be integers' % v @@ -980,7 +1214,7 @@ class Element(ClonableIntArray): sage: v = I.element_class(I, [3,2,0,0]) Traceback (most recent call last): ... - AssertionError: [3, 2, 0, 0] should be a integer vector of sum 4 + AssertionError: [3, 2, 0, 0] should be an integer vector of sum 4 """ def check(self): @@ -1000,7 +1234,7 @@ def check(self): AssertionError """ if self.parent()._sum is not None: - assert sum(self) == self.parent()._sum, '%s should be a integer vector of sum %s' % (self, self.parent()._sum) + assert sum(self) == self.parent()._sum, '%s should be an integer vector of sum %s' % (self, self.parent()._sum) if self.parent()._max_part >= 0: assert max(self) <= self.parent()._max_part, 'Entries of %s must be inferior to %s' % (self, self.parent()._max_part) assert self.parent().is_canonical(self) diff --git a/src/sage/combinat/root_system/type_relabel.py b/src/sage/combinat/root_system/type_relabel.py index 350e290b32a..712937d08ce 100644 --- a/src/sage/combinat/root_system/type_relabel.py +++ b/src/sage/combinat/root_system/type_relabel.py @@ -10,7 +10,7 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute -from sage.sets.family import FiniteFamily +from sage.sets.family import Family, FiniteFamily from sage.combinat.root_system import cartan_type from sage.combinat.root_system import ambient_space from sage.combinat.root_system.root_lattice_realizations import RootLatticeRealizations @@ -173,10 +173,10 @@ def __init__(self, type, relabelling): sage: rI5 = CartanType(['I',5]).relabel({1:0,2:1}) sage: rI5.root_system().ambient_space() """ - assert isinstance(relabelling, FiniteFamily) cartan_type.CartanType_decorator.__init__(self, type) - self._relabelling = relabelling._dictionary - self._relabelling_inverse = relabelling.inverse_family()._dictionary + relabelling = Family(relabelling) + self._relabelling = dict(relabelling.items()) + self._relabelling_inverse = dict(relabelling.inverse_family().items()) self._index_set = tuple(sorted(relabelling[i] for i in type.index_set())) # TODO: design an appropriate infrastructure to handle this # automatically? Maybe using categories and axioms? diff --git a/src/sage/crypto/lattice.py b/src/sage/crypto/lattice.py index d24b87fe5d4..ce6c63f66f3 100644 --- a/src/sage/crypto/lattice.py +++ b/src/sage/crypto/lattice.py @@ -211,7 +211,7 @@ def gen_lattice(type='modular', n=4, m=8, q=11, seed=None, [-4 -3 2 -5 0 0 0 0 0 1] ] - sage: sage.crypto.gen_lattice(m=10, q=11, seed=42, lattice=True) + sage: sage.crypto.gen_lattice(m=10, q=11, seed=42, lattice=True) # needs fpylll Free module of degree 10 and rank 10 over Integer Ring User basis matrix: [ 0 0 1 1 0 -1 -1 -1 1 0] diff --git a/src/sage/databases/cremona.py b/src/sage/databases/cremona.py index 3e1615e5fd4..7b0a3263a32 100644 --- a/src/sage/databases/cremona.py +++ b/src/sage/databases/cremona.py @@ -9,10 +9,11 @@ is included by default with Sage. It contains Weierstrass equations, rank, and torsion for curves up to conductor 10000. -The large database includes all curves in John Cremona's tables. It -also includes data related to the BSD conjecture and modular degrees -for all of these curves, and generators for the Mordell-Weil -groups. To install it, run the following in the shell:: +The large database includes all curves in John Cremona's tables. It also +includes data related to the BSD conjecture and modular degrees for all of +these curves, and generators for the Mordell-Weil groups. To install it via the +optional :ref:`database_cremona_ellcurve ` +package, run the following command in the shell :: sage -i database_cremona_ellcurve diff --git a/src/sage/databases/cubic_hecke_db.py b/src/sage/databases/cubic_hecke_db.py index b78e582d91e..d25ef5ca630 100644 --- a/src/sage/databases/cubic_hecke_db.py +++ b/src/sage/databases/cubic_hecke_db.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Cubic Hecke Database +Cubic Hecke database This module contains the class :class:`CubicHeckeDataBase` which serves as an interface to `Ivan Marin's data files @@ -31,10 +31,25 @@ of linear forms on the cubic Hecke algebra on at most four strands satisfying the Markov trace condition for its cubic Hecke subalgebras. +To use the database, you need to install the optional +:ref:`database_cubic_hecke ` package by the Sage +command :: + + sage -i database_cubic_hecke + +EXAMPLES:: + + sage: # optional - database_cubic_hecke + sage: from sage.databases.cubic_hecke_db import CubicHeckeDataBase + sage: cha_db = CubicHeckeDataBase() + sage: cha_db + + AUTHORS: -- Sebastian Oehms (May 2020): initial version -- Sebastian Oehms (March 2022): PyPi version and Markov trace functionality +- Sebastian Oehms (2020-05): initial version +- Sebastian Oehms (2022-03): PyPi version and Markov trace functionality + """ ############################################################################## diff --git a/src/sage/databases/cunningham_tables.py b/src/sage/databases/cunningham_tables.py index 9c23bf31816..94862b98561 100644 --- a/src/sage/databases/cunningham_tables.py +++ b/src/sage/databases/cunningham_tables.py @@ -1,5 +1,19 @@ r""" -Cunningham table +Cunningham tables + +This module provides :func:`cunningham_prime_factors`, which lists the prime +numbers occurring in the factorization of numbers of type `b^n+1` or `b^n-1` +with `b \in \{2,3,5,6,7,10,11,12\}`. For an introduction to Cunningham prime +factors, see :wikipedia:`Cunningham_Project`. The data becomes available if you +install the optional :ref:`cunningham_tables ` package by +the command :: + + sage -i cunningham_tables + +AUTHORS: + +- Yann Laigle-Chapuy (2009-10-18): initial version + """ import os @@ -16,12 +30,25 @@ def cunningham_prime_factors(): They occur in the factorization of numbers of type `b^n+1` or `b^n-1` with `b \in \{2,3,5,6,7,10,11,12\}`. - Data from http://cage.ugent.be/~jdemeyer/cunningham/ + EXAMPLES:: + + sage: # optional - cunningham_tables + sage: from sage.databases.cunningham_tables import cunningham_prime_factors + sage: cunningham_prime_factors() + [2, + 3, + 5, + 7, + 11, + 13, + 17, + ... """ file = os.path.join(SAGE_SHARE,'cunningham_tables','cunningham_prime_factors.sobj') if os.path.exists(file): return [Integer(_) for _ in load(file)] else: from warnings import warn - warn("You might consider installing the optional package for factoring Cunningham numbers with the following command: ``sage -i cunningham_tables``") + warn("You might consider installing the optional package for factoring Cunningham numbers" + " with the following command: ``sage -i cunningham_tables``") return [] diff --git a/src/sage/databases/db_class_polynomials.py b/src/sage/databases/db_class_polynomials.py index 642865a5d63..923d3a419ee 100644 --- a/src/sage/databases/db_class_polynomials.py +++ b/src/sage/databases/db_class_polynomials.py @@ -1,5 +1,23 @@ """ -Database of Hilbert Polynomials +Database of Hilbert polynomials + +This module gives access to the database of Hilbert class polynomials. To use +the database, you need to install the optional :ref:`database_kohel +` package by the Sage command :: + + sage -i database_kohel + +EXAMPLES:: + + sage: # optional - database_kohel + sage: db = HilbertClassPolynomialDatabase() + sage: db[32] + x^2 - 52250000*x + 12167000000 + +AUTHORS: + +- David Kohel (2006-08-04): initial version + """ # **************************************************************************** # Copyright (C) 2006 David Kohel @@ -45,8 +63,9 @@ def __getitem__(self, disc): r""" TESTS:: + sage: # optional - database_kohel sage: db = HilbertClassPolynomialDatabase() - sage: db[32] # optional - database_kohel + sage: db[32] x^2 - 52250000*x + 12167000000 sage: db[123913912] Traceback (most recent call last): @@ -66,16 +85,17 @@ class HilbertClassPolynomialDatabase(ClassPolynomialDatabase): EXAMPLES:: + sage: # optional - database_kohel sage: db = HilbertClassPolynomialDatabase() - sage: db[-4] # optional - database_kohel + sage: db[-4] x - 1728 - sage: db[-7] # optional - database_kohel + sage: db[-7] x + 3375 - sage: f = db[-23]; f # optional - database_kohel + sage: f = db[-23]; f x^3 + 3491750*x^2 - 5151296875*x + 12771880859375 - sage: f.discriminant().factor() # optional - database_kohel + sage: f.discriminant().factor() -1 * 5^18 * 7^12 * 11^4 * 17^2 * 19^2 * 23 - sage: db[-23] # optional - database_kohel + sage: db[-23] x^3 + 3491750*x^2 - 5151296875*x + 12771880859375 """ model = "Cls" diff --git a/src/sage/databases/db_modular_polynomials.py b/src/sage/databases/db_modular_polynomials.py index 4e0422539fc..025682c05ae 100644 --- a/src/sage/databases/db_modular_polynomials.py +++ b/src/sage/databases/db_modular_polynomials.py @@ -1,5 +1,26 @@ """ -Database of Modular Polynomials +Database of modular polynomials + +This module gives access to the database of modular polynomials. To use the +database, you need to install the optional :ref:`database_kohel +` package by the Sage command :: + + sage -i database_kohel + +EXAMPLES:: + + sage: # optional - database_kohel + sage: DBMP = ClassicalModularPolynomialDatabase() + sage: f = DBMP[29] + sage: f.degree() + 58 + sage: f.coefficient([28,28]) + 400152899204646997840260839128 + +AUTHORS: + +- David Kohel (2006-08-04): initial version + """ # **************************************************************************** # Copyright (C) 2006 William Stein @@ -48,8 +69,9 @@ def _dbz_to_integer_list(name): r""" TESTS:: + sage: # optional - database_kohel sage: from sage.databases.db_modular_polynomials import _dbz_to_integer_list - sage: _dbz_to_integer_list('PolMod/Atk/pol.002.dbz') # optional - database_kohel + sage: _dbz_to_integer_list('PolMod/Atk/pol.002.dbz') [[3, 0, 1], [2, 1, -1], [2, 0, 744], @@ -58,9 +80,9 @@ def _dbz_to_integer_list(name): [0, 2, 1], [0, 1, 7256], [0, 0, 15252992]] - sage: _dbz_to_integer_list('PolMod/Cls/pol.001.dbz') # optional - database_kohel + sage: _dbz_to_integer_list('PolMod/Cls/pol.001.dbz') [[1, 0, 1]] - sage: _dbz_to_integer_list('PolMod/Eta/pol.002.dbz') # optional - database_kohel + sage: _dbz_to_integer_list('PolMod/Eta/pol.002.dbz') [[3, 0, 1], [2, 0, 48], [1, 1, -1], [1, 0, 768], [0, 0, 4096]] """ from sage.rings.integer import Integer @@ -130,14 +152,14 @@ def __getitem__(self, level): EXAMPLES:: + sage: # optional - database_kohel sage: DBMP = ClassicalModularPolynomialDatabase() - sage: f = DBMP[29] # optional - database_kohel - sage: f.degree() # optional - database_kohel + sage: f = DBMP[29] + sage: f.degree() 58 - sage: f.coefficient([28,28]) # optional - database_kohel + sage: f.coefficient([28,28]) 400152899204646997840260839128 - - sage: DBMP[50] # optional - database_kohel + sage: DBMP[50] Traceback (most recent call last): ... ValueError: file not found in the Kohel database diff --git a/src/sage/databases/findstat.py b/src/sage/databases/findstat.py index 5b8455a49a2..e1e33e2839c 100644 --- a/src/sage/databases/findstat.py +++ b/src/sage/databases/findstat.py @@ -1,38 +1,28 @@ # -*- coding: utf-8 -*- r""" -FindStat - the Combinatorial Statistic Finder. +FindStat - the search engine for combinatorial statistics and maps -The FindStat database can be found at:: +The interface to the FindStat database is :: sage: findstat() The Combinatorial Statistic Finder (https://www.findstat.org/) -Fix the following three notions: +We use the following three notions - A *combinatorial collection* is a set `S` with interesting combinatorial properties, - a *combinatorial map* is a combinatorially interesting map `f: S \to S'` between combinatorial collections, and - a *combinatorial statistic* is a combinatorially interesting map `s: S \to \ZZ`. -You can use the sage interface to FindStat to: +You can use the FindStat interface to - identify a combinatorial statistic or map given the values on a few small objects, - obtain more terms, formulae, references, etc. for a given statistic or map, - edit statistics and maps and submit new statistics. -AUTHORS: - -- Martin Rubey (2015): initial version. -- Martin Rubey (2020): rewrite, adapt to new FindStat API - -The main entry points ---------------------- -.. csv-table:: - :class: contentstable - :widths: 20, 40 - :delim: | +The main entry points to the database are - :func:`findstat` | search for matching statistics. - :func:`findmap` | search for matching maps. +- :func:`findstat` to search for matching statistics, +- :func:`findmap` to search for matching maps. A guided tour ------------- @@ -195,8 +185,10 @@ def mapping(sigma): :meth:`FindStatStatistic.submit` your changes for review by the FindStat team. -Classes and methods -------------------- +AUTHORS: + +- Martin Rubey (2015): initial version +- Martin Rubey (2020): rewrite, adapt to new FindStat API """ # **************************************************************************** @@ -910,7 +902,7 @@ def findstat(query=None, values=None, distribution=None, domain=None, must be ``None``. - a list of pairs of the form ``(object, value)``, or a - dictionary from sage objects to integer values. The keyword + dictionary from Sage objects to integer values. The keyword arguments ``depth`` and ``max_values`` are passed to the finder, ``values`` and ``distribution`` must be ``None``. @@ -1163,7 +1155,7 @@ def findmap(*args, **kwargs): forms: - a list of pairs of the form ``(object, value)``, or a - dictionary from sage objects to sage objects. + dictionary from Sage objects to Sage objects. - a list of pairs of the form ``(list of objects, list of values)``, or a single pair of the form ``(list of objects, @@ -1763,7 +1755,7 @@ def set_references_raw(self, value): def sage_code(self): r""" - Return the sage code associated with the statistic or map. + Return the Sage code associated with the statistic or map. OUTPUT: @@ -1842,7 +1834,7 @@ def first_terms(self): OUTPUT: - A dictionary from sage objects representing an element of the + A dictionary from Sage objects representing an element of the appropriate collection to integers. This method is overridden in :class:`FindStatStatisticQuery`. @@ -2261,7 +2253,7 @@ def set_first_terms(self, values): INPUT: - a list of pairs of the form ``(object, value)`` where - ``object`` is a sage object representing an element of the + ``object`` is a Sage object representing an element of the appropriate collection and ``value`` is an integer. This information is used when submitting the statistic with @@ -2298,7 +2290,7 @@ def code(self): OUTPUT: - A string. Contributors are encouraged to submit sage code in the form:: + A string. Contributors are encouraged to submit Sage code in the form:: def statistic(x): ... @@ -4061,9 +4053,9 @@ class FindStatCollection(Element, - an integer designating the FindStat id of the collection, or - - a sage object belonging to a collection, or + - a Sage object belonging to a collection, or - - an iterable producing a sage object belonging to a collection. + - an iterable producing a Sage object belonging to a collection. EXAMPLES:: @@ -4277,7 +4269,7 @@ def in_range(self, element): INPUT: - - ``element`` -- a sage object that belongs to the collection. + - ``element`` -- a Sage object that belongs to the collection. OUTPUT: @@ -4452,7 +4444,7 @@ def from_string(self): OUTPUT: - The function that produces the sage object given its FindStat + The function that produces the Sage object given its FindStat representation as a string. EXAMPLES:: diff --git a/src/sage/databases/jones.py b/src/sage/databases/jones.py index 26d4dbaa373..aaab1397f0a 100644 --- a/src/sage/databases/jones.py +++ b/src/sage/databases/jones.py @@ -1,9 +1,10 @@ r""" John Jones's tables of number fields -In order to use the Jones database, the optional database package -must be installed using the Sage command !sage -i -database_jones_numfield +In order to use the Jones database, the optional :ref:`database_jones_numfield +` package must be installed using the Sage command :: + + sage -i database_jones_numfield This is a table of number fields with bounded ramification and degree `\leq 6`. You can query the database for all number diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py index df0b8310456..72e39796965 100644 --- a/src/sage/databases/knotinfo_db.py +++ b/src/sage/databases/knotinfo_db.py @@ -1,15 +1,29 @@ # -*- coding: utf-8 -*- r""" -KnotInfo Database +KnotInfo database -This module contains the class :class:`KnotInfoDataBase` and auxiliary classes -for it which serves as an interface to the lists of named knots and links provided +This module contains the class :class:`KnotInfoDataBase` and auxiliary classes +for it, which serves as an interface to the lists of named knots and links provided at the web-pages `KnotInfo `__ and `LinkInfo `__. +To use the database, you need to install the optional :ref:`database_knotinfo +` package by the Sage command :: + + sage -i database_knotinfo + +EXAMPLES:: + + sage: # optional - database_knotinfo + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db + + AUTHORS: -- Sebastian Oehms August 2020: initial version +- Sebastian Oehms (2020-08): initial version + """ ############################################################################## # Copyright (C) 2020 Sebastian Oehms diff --git a/src/sage/databases/odlyzko.py b/src/sage/databases/odlyzko.py index 547baad0625..4ee78b7de0a 100644 --- a/src/sage/databases/odlyzko.py +++ b/src/sage/databases/odlyzko.py @@ -1,12 +1,19 @@ """ -Tables of zeros of the Riemann-Zeta function +Database of the zeros of the Riemann zeta function + +The main access function to the database of the zeros of the Riemann zeta +function is :func:`zeta_zeros`. In order to use ``zeta_zeros()``, you need to +install the optional :ref:`database_odlyzko_zeta ` +package:: + + sage -i database_odlyzko_zeta AUTHORS: - William Stein: initial version +- Jeroen Demeyer (2015-01-20): converted ``database_odlyzko_zeta`` to new-style + package -- Jeroen Demeyer (2015-01-20): convert ``database_odlyzko_zeta`` to - new-style package """ #***************************************************************************** @@ -30,27 +37,20 @@ def zeta_zeros(): List of the imaginary parts of the first 2,001,052 zeros of the Riemann zeta function, accurate to within 4e-9. - In order to use ``zeta_zeros()``, you will need to - install the optional Odlyzko database package:: - - sage -i database_odlyzko_zeta - - You can see a list of all available optional packages with - ``sage --optional``. - REFERENCES: - http://www.dtc.umn.edu/~odlyzko/zeta_tables/index.html EXAMPLES: - The following example prints the imaginary part of the 13th + The following example shows the imaginary part of the 13th nontrivial zero of the Riemann zeta function:: - sage: zz = zeta_zeros() # optional - database_odlyzko_zeta - sage: zz[12] # optional - database_odlyzko_zeta + sage: # optional - database_odlyzko_zeta + sage: zz = zeta_zeros() + sage: zz[12] 59.347044003 - sage: len(zz) # optional - database_odlyzko_zeta + sage: len(zz) 2001052 """ from sage.misc.verbose import verbose diff --git a/src/sage/databases/oeis.py b/src/sage/databases/oeis.py index b6eeac3499c..50ad342a2b2 100644 --- a/src/sage/databases/oeis.py +++ b/src/sage/databases/oeis.py @@ -8,17 +8,6 @@ - identify a sequence from its first terms. - obtain more terms, formulae, references, etc. for a given sequence. -AUTHORS: - -- Thierry Monteil (2012-02-10 -- 2013-06-21): initial version. - -- Vincent Delecroix (2014): modifies continued fractions because of :trac:`14567` - -- Moritz Firsching (2016): modifies handling of dead sequence, see :trac:`17330` - -- Thierry Monteil (2019): refactorization (unique representation :trac:`28480`, - laziness :trac:`28627`) - EXAMPLES:: sage: oeis @@ -134,13 +123,14 @@ - Some infinite OEIS sequences are implemented in Sage, via the :mod:`sloane_functions ` module. -.. TODO:: +AUTHORS: - - in case of flood, suggest the user to install the off-line database instead. - - interface with the off-line database (or reimplement it). +- Thierry Monteil (2012-02-10 -- 2013-06-21): initial version. +- Vincent Delecroix (2014): modifies continued fractions because of :trac:`14567` +- Moritz Firsching (2016): modifies handling of dead sequence, see :trac:`17330` +- Thierry Monteil (2019): refactorization (unique representation :trac:`28480`, + laziness :trac:`28627`) -Classes and methods -------------------- """ # **************************************************************************** diff --git a/src/sage/databases/sloane.py b/src/sage/databases/sloane.py index d5802620066..61825257470 100644 --- a/src/sage/databases/sloane.py +++ b/src/sage/databases/sloane.py @@ -1,8 +1,8 @@ """ -Local copy of Sloane On-Line Encyclopedia of Integer Sequences +Local copy of the On-Line Encyclopedia of Integer Sequences -The SloaneEncyclopedia object provides access to a local copy of the database -containing only the sequences and their names. To use this you must download +The ``SloaneEncyclopedia`` object provides access to a local copy of the database +containing only the sequences and their names. To use this, you must download and install the database using ``SloaneEncyclopedia.install()``, or ``SloaneEncyclopedia.install_from_gz()`` if you have already downloaded the database manually. @@ -11,14 +11,14 @@ :: - sage: SloaneEncyclopedia[60843] # optional - sloane_database + sage: SloaneEncyclopedia[60843] # optional - sloane_database [1, 6, 21, 107] To get the name of a sequence, type :: - sage: SloaneEncyclopedia.sequence_name(1) # optional - sloane_database + sage: SloaneEncyclopedia.sequence_name(1) # optional - sloane_database 'Number of groups of order n.' To search locally for a particular subsequence, type @@ -33,7 +33,7 @@ :: - sage: SloaneEncyclopedia.find([1,2,3,4,5], 100) # optional - sloane_database + sage: SloaneEncyclopedia.find([1,2,3,4,5], 100) # optional - sloane_database [(15, [1, 2, 3, 4, 5, 7, 7, 8, 9, 11, 11, ... Results in either case are of the form [ (number, list) ]. @@ -65,11 +65,9 @@ sequence_name() to return the description of a sequence; and changed the data type for elements of each sequence from int to Integer. -- Thierry Monteil (2012-02-10): deprecate dead code and update related doc and +- Thierry Monteil (2012-02-10): deprecated dead code and update related doc and tests. -Classes and methods -------------------- """ # **************************************************************************** @@ -265,7 +263,8 @@ def load(self): try: file_seq = bz2.BZ2File(self.__file__, 'r') except IOError: - raise IOError("The Sloane Encyclopedia database must be installed. Use e.g. 'SloaneEncyclopedia.install()' to download and install it.") + raise IOError("The Sloane Encyclopedia database must be installed." + " Use e.g. 'SloaneEncyclopedia.install()' to download and install it.") self.__data__ = {} @@ -295,7 +294,8 @@ def load(self): self.__loaded_names__ = True except KeyError: # Some sequence in the names file is not in the database - raise KeyError("Sloane OEIS sequence and name files do not match. Try reinstalling, e.g. SloaneEncyclopedia.install(overwrite=True).") + raise KeyError("Sloane OEIS sequence and name files do not match." + " Try reinstalling, e.g. SloaneEncyclopedia.install(overwrite=True).") except IOError: # The names database is not installed self.__loaded_names__ = False @@ -323,7 +323,8 @@ def sequence_name(self, N): """ self.load() if not self.__loaded_names__: - raise IOError("The Sloane OEIS names file is not installed. Try reinstalling, e.g. SloaneEncyclopedia.install(overwrite=True).") + raise IOError("The Sloane OEIS names file is not installed." + " Try reinstalling, e.g. SloaneEncyclopedia.install(overwrite=True).") if N not in self.__data__: # sequence N does not exist return '' diff --git a/src/sage/databases/stein_watkins.py b/src/sage/databases/stein_watkins.py index 6abf39426dd..fa5f338391a 100644 --- a/src/sage/databases/stein_watkins.py +++ b/src/sage/databases/stein_watkins.py @@ -1,18 +1,16 @@ r""" The Stein-Watkins table of elliptic curves -Sage gives access to the Stein-Watkins table of elliptic curves, via an -optional package that you must install. This is a huge database of elliptic -curves. You can install the database (a 2.6GB package) with the command - -:: +Sage gives access to the Stein-Watkins table of elliptic curves, via the +optional :ref:`database_stein_watkins ` package +that you must install. This is a huge database of elliptic curves. You can +install the database (a 2.6GB package) with the command :: sage -i database_stein_watkins You can also automatically download a small version, which takes much less -time, using the command - -:: +time, via the optional :ref:`database_stein_watkins_mini ` +package using the command :: sage -i database_stein_watkins_mini diff --git a/src/sage/databases/symbolic_data.py b/src/sage/databases/symbolic_data.py index 7703a2216e5..083b3ced668 100644 --- a/src/sage/databases/symbolic_data.py +++ b/src/sage/databases/symbolic_data.py @@ -1,16 +1,15 @@ """ Ideals from the Symbolic Data project -This file implements a thin wrapper for the optional symbolic data set -of ideals as published on http://www.symbolicdata.org . From the -project website: +This module implements a thin wrapper for the optional symbolic dataset of +ideals as published on http://www.symbolicdata.org. From the project website: - For different purposes algorithms and implementations are tested + For different purposes, algorithms and implementations are tested on certified and reliable data. The development of tools and data for such tests is usually 'orthogonal' to the main implementation efforts, it requires different skills and technologies and is not loved by programmers. On the other hand, - in many cases tools and data could easily be reused - with slight + in many cases, tools and data could easily be reused - with slight modifications - across similar projects. The SymbolicData Project is set out to coordinate such efforts within the Computer Algebra Community. Commonly collected certified and reliable data can @@ -21,32 +20,36 @@ there are not yet well agreed aims of such a benchmarking. Nevertheless various (often high quality) special benchmarks are scattered through the literature. During the last - years efforts toward collection of test data for symbolic + years, efforts toward collection of test data for symbolic computations were intensified. They focused mainly on the creation of general benchmarks for different areas of symbolic computation and the collection of such activities on different Web site. For - further qualification of these efforts it would be of great + further qualification of these efforts, it would be of great benefit to create a commonly available digital archive of these special benchmark data scattered through the literature. This would provide the community with an electronic repository of certified data that could be addressed and extended during further development. +In order to use this dataset, you need to install the optional +:ref:`database_symbolic_data ` package by the Sage +command :: + + sage -i database_symbolic_data + EXAMPLES:: - sage: sd = SymbolicData(); sd # optional - database_symbolic_data + sage: # optional - database_symbolic_data + sage: sd = SymbolicData(); sd SymbolicData with 372 ideals - - sage: sd.ZeroDim__example_1 # optional - database_symbolic_data + sage: sd.ZeroDim__example_1 Ideal (x1^2 + x2^2 - 10, x1^2 + x1*x2 + 2*x2^2 - 16) of Multivariate Polynomial Ring in x1, x2 over Rational Field - - sage: sd.Katsura_3 # optional - database_symbolic_data + sage: sd.Katsura_3 Ideal (u0 + 2*u1 + 2*u2 + 2*u3 - 1, u1^2 + 2*u0*u2 + 2*u1*u3 - u2, 2*u0*u1 + 2*u1*u2 + 2*u2*u3 - u1, u0^2 + 2*u1^2 + 2*u2^2 + 2*u3^2 - u0) of Multivariate Polynomial Ring in u0, u1, u2, u3 over Rational Field - - sage: sd.get_ideal('Katsura_3',GF(127),'degrevlex') # optional - database_symbolic_data + sage: sd.get_ideal('Katsura_3', GF(127), 'degrevlex') Ideal (u0 + 2*u1 + 2*u2 + 2*u3 - 1, u1^2 + 2*u0*u2 + 2*u1*u3 - u2, 2*u0*u1 + 2*u1*u2 + 2*u2*u3 - u1, @@ -54,8 +57,20 @@ AUTHORS: -- Martin Albrecht +- Martin Albrecht (2007-02-19): initial version + """ + +# **************************************************************************** +# Copyright (C) 2007 Martin Albrecht +# +# 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/ +# **************************************************************************** + import os from xml.dom.minidom import parse from sage.rings.rational_field import QQ @@ -90,13 +105,11 @@ def get_ideal(self, name, base_ring=QQ, term_order="degrevlex"): INPUT: - - ``name`` - name as on the symbolic data website - - ``base_ring`` - base ring for the polynomial ring (default: ``QQ``) - - ``term_order`` - term order for the polynomial ring (default: ``degrevlex``) - - OUTPUT: + - ``name`` -- name as on the symbolic data website + - ``base_ring`` -- base ring for the polynomial ring (default: ``QQ``) + - ``term_order`` -- term order for the polynomial ring (default: ``degrevlex``) - ideal as given by ``name`` in ``PolynomialRing(base_ring,vars,term_order)`` + OUTPUT: ideal as given by ``name`` in ``PolynomialRing(base_ring,vars,term_order)`` EXAMPLES:: diff --git a/src/sage/env.py b/src/sage/env.py index a221f33bb82..39d09528788 100644 --- a/src/sage/env.py +++ b/src/sage/env.py @@ -177,7 +177,8 @@ def var(key: str, *fallbacks: Optional[str], force: bool = False) -> Optional[st SAGE_LOCAL = var("SAGE_LOCAL", SAGE_VENV) SAGE_SHARE = var("SAGE_SHARE", join(SAGE_LOCAL, "share")) SAGE_DOC = var("SAGE_DOC", join(SAGE_SHARE, "doc", "sage")) -SAGE_SPKG_INST = var("SAGE_SPKG_INST", join(SAGE_LOCAL, "var", "lib", "sage", "installed")) +SAGE_LOCAL_SPKG_INST = var("SAGE_LOCAL_SPKG_INST", join(SAGE_LOCAL, "var", "lib", "sage", "installed")) +SAGE_SPKG_INST = var("SAGE_SPKG_INST", join(SAGE_LOCAL, "var", "lib", "sage", "installed")) # deprecated # source tree of the Sage distribution SAGE_ROOT = var("SAGE_ROOT") # no fallback for SAGE_ROOT @@ -198,8 +199,7 @@ def var(key: str, *fallbacks: Optional[str], force: bool = False) -> Optional[st GRAPHS_DATA_DIR = var("GRAPHS_DATA_DIR", join(SAGE_SHARE, "graphs")) ELLCURVE_DATA_DIR = var("ELLCURVE_DATA_DIR", join(SAGE_SHARE, "ellcurves")) POLYTOPE_DATA_DIR = var("POLYTOPE_DATA_DIR", join(SAGE_SHARE, "reflexive_polytopes")) -GAP_LIB_DIR = var("GAP_LIB_DIR", join(SAGE_LOCAL, "lib", "gap")) -GAP_SHARE_DIR = var("GAP_SHARE_DIR", join(SAGE_SHARE, "gap")) + COMBINATORIAL_DESIGN_DATA_DIR = var("COMBINATORIAL_DESIGN_DATA_DIR", join(SAGE_SHARE, "combinatorial_designs")) CREMONA_MINI_DATA_DIR = var("CREMONA_MINI_DATA_DIR", join(SAGE_SHARE, "cremona")) CREMONA_LARGE_DATA_DIR = var("CREMONA_LARGE_DATA_DIR", join(SAGE_SHARE, "cremona")) @@ -243,11 +243,13 @@ def var(key: str, *fallbacks: Optional[str], force: bool = False) -> Optional[st # GAP memory and args SAGE_GAP_MEMORY = var('SAGE_GAP_MEMORY', None) -_gap_cmd = "gap -r" -if SAGE_GAP_MEMORY is not None: - _gap_cmd += " -s " + SAGE_GAP_MEMORY + " -o " + SAGE_GAP_MEMORY -SAGE_GAP_COMMAND = var('SAGE_GAP_COMMAND', _gap_cmd) +SAGE_GAP_COMMAND = var('SAGE_GAP_COMMAND', None) +# The semicolon-separated search path for GAP packages. It is passed +# directly to GAP via the -l flag. +GAP_ROOT_PATHS = var("GAP_ROOT_PATHS", + ";".join([join(SAGE_LOCAL, "lib", "gap"), + join(SAGE_LOCAL, "share", "gap")])) # post process if DOT_SAGE is not None and ' ' in DOT_SAGE: diff --git a/src/sage/ext_data/gap/sage.g b/src/sage/ext_data/gap/sage.g index 36e131146d6..ddca6e3fb44 100644 --- a/src/sage/ext_data/gap/sage.g +++ b/src/sage/ext_data/gap/sage.g @@ -134,3 +134,17 @@ end; # # LogTo("/tmp/gapsage.log"); # + + +# Load the GAP packages that GAP itself tries to autoload in the +# default configuration (see "PackagesToLoad" in lib/package.gi). The +# combination of passing -A to gap and these LoadPackage statements +# allows us to load the usual set of packages, but only if they are +# installed. So most people will get exactly the default behavior, +# but minimal installations won't throw warnings and fail tests. +_autoloads := [ "autpgrp", "alnuth", "crisp", "ctbllib", "factint", "fga", + "irredsol", "laguna", "polenta", "polycyclic", "resclasses", + "sophus", "tomlib" ]; +for p in _autoloads do + LoadPackage(p); +od; diff --git a/src/sage/features/__init__.py b/src/sage/features/__init__.py index be55d44d0c2..d5669c3c9ff 100644 --- a/src/sage/features/__init__.py +++ b/src/sage/features/__init__.py @@ -236,7 +236,7 @@ def require(self): Traceback (most recent call last): ... FeatureNotPresentError: gap_package_ve1EeThu is not available. - `TestPackageAvailability("ve1EeThu")` evaluated to `fail` in GAP. + `LoadPackage("ve1EeThu")` evaluated to `fail` in GAP. """ presence = self.is_present() if not presence: @@ -393,22 +393,25 @@ def unhide(self): EXAMPLES: - Polycyclic is a standard GAP package since 4.10 (see :trac:`26856`). The - following test just fails if it is hidden. Thus, in the second - invocation no optional tag is needed:: + PolyCyclic is an optional GAP package. The following test + fails if it is hidden, regardless of whether it is installed + or not:: sage: from sage.features.gap import GapPackage sage: Polycyclic = GapPackage("polycyclic", spkg="gap_packages") sage: Polycyclic.hide() - sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) # needs sage.libs.gap + sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) # needs sage.libs.gap # optional - gap_packages_polycyclic Traceback (most recent call last): ... FeatureNotPresentError: gap_package_polycyclic is not available. Feature `gap_package_polycyclic` is hidden. Use method `unhide` to make it available again. + After unhiding the feature, the test should pass again if PolyCyclic + is installed and loaded:: + sage: Polycyclic.unhide() - sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) # needs sage.libs.gap + sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) # needs sage.libs.gap # optional - gap_packages_polycyclic Pcp-group with orders [ 0, 3, 4 ] """ self._hidden = False @@ -451,7 +454,7 @@ def __str__(self): Traceback (most recent call last): ... FeatureNotPresentError: gap_package_gapZuHoh8Uu is not available. - `TestPackageAvailability("gapZuHoh8Uu")` evaluated to `fail` in GAP. + `LoadPackage("gapZuHoh8Uu")` evaluated to `fail` in GAP. """ lines = ["{feature} is not available.".format(feature=self.feature.name)] if self.reason: @@ -481,7 +484,7 @@ class FeatureTestResult(): ``resolution``:: sage: presence.reason # needs sage.libs.gap - '`TestPackageAvailability("NOT_A_PACKAGE")` evaluated to `fail` in GAP.' + '`LoadPackage("NOT_A_PACKAGE")` evaluated to `fail` in GAP.' sage: bool(presence.resolution) False diff --git a/src/sage/features/gap.py b/src/sage/features/gap.py index a989dcfc1fb..df5545e9c07 100644 --- a/src/sage/features/gap.py +++ b/src/sage/features/gap.py @@ -19,6 +19,9 @@ class GapPackage(Feature): r""" A :class:`~sage.features.Feature` describing the presence of a GAP package. + A GAP package is "present" if it *can be* loaded, not if it *has + been* loaded. + .. SEEALSO:: :class:`Feature sage.libs.gap <~sage.features.sagemath.sage__libs__gap>` @@ -42,9 +45,10 @@ def __init__(self, package, **kwds): def _is_present(self): r""" - Return whether the package is available in GAP. + Return whether or not the GAP package is present. - This does not check whether this package is functional. + If the package is installed but not yet loaded, it is loaded + first. This does *not* check that the package is functional. EXAMPLES:: @@ -57,8 +61,11 @@ def _is_present(self): except ImportError: return FeatureTestResult(self, False, reason="sage.libs.gap is not available") - command = 'TestPackageAvailability("{package}")'.format(package=self.package) + + # This returns "true" even if the package is already loaded. + command = 'LoadPackage("{package}")'.format(package=self.package) presence = libgap.eval(command) + if presence: return FeatureTestResult(self, True, reason="`{command}` evaluated to `{presence}` in GAP.".format(command=command, presence=presence)) diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 4097d3512b9..dd48c55ee6b 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -311,6 +311,29 @@ def __init__(self): spkg='sagemath_groups', type='standard') +class sage__libs__braiding(PythonModule): + r""" + A :class:`~sage.features.Feature` describing the presence of :mod:`sage.libs.braiding`. + + EXAMPLES:: + + sage: from sage.features.sagemath import sage__libs__braiding + sage: sage__libs__braiding().is_present() # needs sage.libs.braiding + FeatureTestResult('sage.libs.braiding', True) + """ + + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.sagemath import sage__libs__braiding + sage: isinstance(sage__libs__braiding(), sage__libs__braiding) + True + """ + PythonModule.__init__(self, 'sage.libs.braiding', + spkg='sagemath_libbraiding', type='standard') + + class sage__libs__ecl(PythonModule): r""" A :class:`~sage.features.Feature` describing the presence of :mod:`sage.libs.ecl`. @@ -330,7 +353,8 @@ def __init__(self): sage: isinstance(sage__libs__ecl(), sage__libs__ecl) True """ - PythonModule.__init__(self, 'sage.libs.ecl') + PythonModule.__init__(self, 'sage.libs.ecl', + spkg='sagemath_symbolics', type='standard') class sage__libs__flint(JoinFeature): @@ -1076,6 +1100,7 @@ def all_features(): sage__geometry__polyhedron(), sage__graphs(), sage__groups(), + sage__libs__braiding(), sage__libs__ecl(), sage__libs__flint(), sage__libs__gap(), diff --git a/src/sage/graphs/base/c_graph.pyx b/src/sage/graphs/base/c_graph.pyx index 7ca6c5dd124..70ce392a43c 100644 --- a/src/sage/graphs/base/c_graph.pyx +++ b/src/sage/graphs/base/c_graph.pyx @@ -624,8 +624,8 @@ cdef class CGraph: OUTPUT: - - Raise a ``NotImplementedError``. This method is not implemented in - this base class. A child class should provide a suitable + - Raise a :class:`NotImplementedError`. This method is not implemented + in this base class. A child class should provide a suitable implementation. .. SEEALSO:: @@ -1267,7 +1267,7 @@ cdef class CGraph: OUTPUT: - - Raise ``NotImplementedError``. This method is not implemented at + - Raise :class:`NotImplementedError`. This method is not implemented at the :class:`CGraph` level. A child class should provide a suitable implementation. diff --git a/src/sage/graphs/base/graph_backends.pyx b/src/sage/graphs/base/graph_backends.pyx index 863f61be013..77961c33cf6 100644 --- a/src/sage/graphs/base/graph_backends.pyx +++ b/src/sage/graphs/base/graph_backends.pyx @@ -6,7 +6,7 @@ This module implements :class:`GenericGraphBackend` (the base class for backends). Any graph backend must redefine the following methods (for which -:class:`GenericGraphBackend` raises a ``NotImplementedError``) +:class:`GenericGraphBackend` raises a :class:`NotImplementedError`) .. csv-table:: :class: contentstable diff --git a/src/sage/graphs/bipartite_graph.py b/src/sage/graphs/bipartite_graph.py index 0a03affa422..6bb65e6ad33 100644 --- a/src/sage/graphs/bipartite_graph.py +++ b/src/sage/graphs/bipartite_graph.py @@ -1,5 +1,4 @@ # autopep8: off -# -*- coding: utf-8 -*- r""" Bipartite graphs diff --git a/src/sage/graphs/bliss.pyx b/src/sage/graphs/bliss.pyx index eac56e21f0d..c623b5fe402 100644 --- a/src/sage/graphs/bliss.pyx +++ b/src/sage/graphs/bliss.pyx @@ -396,9 +396,10 @@ cpdef canonical_form(G, partition=None, return_graph=False, use_edge_labels=True canonical graph of ``G`` or its set of edges - ``use_edge_labels`` -- boolean (default: ``True``); whether to consider - edge labels. The edge labels are assumed to be hashable and sortable. If - this is not the case (ie a ``TypeError`` is raised), the algorithm will - consider the string representations of the labels instead of the labels. + edge labels. The edge labels are assumed to be hashable and + sortable. If this is not the case (ie a :class:`TypeError` is + raised), the algorithm will consider the string representations + of the labels instead of the labels. - ``certificate`` -- boolean (default: ``False``); when set to ``True``, returns the labeling of G into a canonical graph diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index 8ba57d90b3c..a5d43d48085 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Directed graphs @@ -3187,9 +3186,9 @@ def topological_sort(self, implementation="default"): """ Return a topological sort of the digraph if it is acyclic. - If the digraph contains a directed cycle, a ``TypeError`` is raised. As - topological sorts are not necessarily unique, different implementations - may yield different results. + If the digraph contains a directed cycle, a :class:`TypeError` + is raised. As topological sorts are not necessarily unique, + different implementations may yield different results. A topological sort is an ordering of the vertices of the digraph such that each vertex comes before all of its successors. That is, if `u` @@ -3269,7 +3268,8 @@ def topological_sort_generator(self): Return an iterator over all topological sorts of the digraph if it is acyclic. - If the digraph contains a directed cycle, a ``TypeError`` is raised. + If the digraph contains a directed cycle, a :class:`TypeError` + is raised. A topological sort is an ordering of the vertices of the digraph such that each vertex comes before all of its successors. That is, if u comes diff --git a/src/sage/graphs/digraph_generators.py b/src/sage/graphs/digraph_generators.py index 2b29e0e73ef..d182f49afb1 100644 --- a/src/sage/graphs/digraph_generators.py +++ b/src/sage/graphs/digraph_generators.py @@ -74,7 +74,7 @@ from sage.graphs.graph import Graph -class DiGraphGenerators(): +class DiGraphGenerators: r""" A class consisting of constructors for several common digraphs, including orderly generation of isomorphism class representatives. diff --git a/src/sage/graphs/domination.py b/src/sage/graphs/domination.py index 7f631fcfe28..6323cdd29e4 100644 --- a/src/sage/graphs/domination.py +++ b/src/sage/graphs/domination.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Domination diff --git a/src/sage/graphs/generators/basic.py b/src/sage/graphs/generators/basic.py index 5dbacb4de26..0dc2c00baef 100644 --- a/src/sage/graphs/generators/basic.py +++ b/src/sage/graphs/generators/basic.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Basic graphs @@ -400,7 +399,7 @@ def CompleteGraph(n): G.set_pos({0: (0, 0)}) else: G._circle_embedding(list(range(n)), angle=pi/2) - G.add_edges(((i, j) for i in range(n) for j in range(i + 1, n))) + G.add_edges((i, j) for i in range(n) for j in range(i + 1, n)) return G def CorrelationGraph(seqs, alpha, include_anticorrelation): diff --git a/src/sage/graphs/generators/chessboard.py b/src/sage/graphs/generators/chessboard.py index dfe5b15b273..a76d6f98de7 100644 --- a/src/sage/graphs/generators/chessboard.py +++ b/src/sage/graphs/generators/chessboard.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Chessboard graphs diff --git a/src/sage/graphs/generators/degree_sequence.py b/src/sage/graphs/generators/degree_sequence.py index f5e68d89c79..05c021403b0 100644 --- a/src/sage/graphs/generators/degree_sequence.py +++ b/src/sage/graphs/generators/degree_sequence.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Graphs with a given degree sequence @@ -98,8 +97,8 @@ def DegreeSequenceBipartite(s1, s2): True Some sequences being incompatible if, for example, their sums are different, - the functions raises a ``ValueError`` when no graph corresponding to the - degree sequences exists:: + the functions raises a :class:`ValueError` when no graph corresponding + to the degree sequences exists:: sage: g = graphs.DegreeSequenceBipartite([2,2,2,2,1],[5,5]) # needs sage.combinat sage.modules Traceback (most recent call last): diff --git a/src/sage/graphs/generators/families.py b/src/sage/graphs/generators/families.py index 85ea52a4571..c962378cd25 100644 --- a/src/sage/graphs/generators/families.py +++ b/src/sage/graphs/generators/families.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Various families of graphs @@ -594,7 +593,7 @@ def BarbellGraph(n1, n2): OUTPUT: - A barbell graph of order ``2*n1 + n2``. A ``ValueError`` is + A barbell graph of order ``2*n1 + n2``. A :class:`ValueError` is returned if ``n1 < 2`` or ``n2 < 0``. PLOTTING: @@ -929,7 +928,7 @@ def BubbleSortGraph(n): OUTPUT: The bubble sort graph `B(n)` on `n` symbols. If `n < 1`, a - ``ValueError`` is returned. + :class:`ValueError` is returned. EXAMPLES:: @@ -2714,7 +2713,7 @@ def SwitchedSquaredSkewHadamardMatrixGraph(n): G = SquaredSkewHadamardMatrixGraph(n).complement() G.add_vertex((4 * n - 1)**2) G.seidel_switching(list(range((4 * n - 1) * (2 * n - 1)))) - G.name("switch skewhad^2+*_" + str((n))) + G.name("switch skewhad^2+*_" + str(n)) return G @@ -3920,7 +3919,13 @@ def MathonPseudocyclicStronglyRegularGraph(t, G=None, L=None): sage: G.is_strongly_regular(parameters=True) # needs sage.modules sage.rings.finite_rings (45, 22, 10, 11) - Supplying ``G`` and ``L`` (constructed from the automorphism group of ``G``). :: + Supplying ``G`` and ``L`` (constructed from the automorphism group + of ``G``). The entries of L can't be tested directly because + there's some unpredictability in the way that GAP chooses a + representative in ``NormalSubgroups()``, the function that + underlies our own + :meth:`~sage.groups.perm_gps.permgroup.PermutationGroup_generic.normal_subgroups` + method:: sage: # needs sage.groups sage.libs.gap sage.rings.finite_rings sage: G = graphs.PaleyGraph(9) @@ -3931,18 +3936,7 @@ def MathonPseudocyclicStronglyRegularGraph(t, G=None, L=None): ....: for z in subg] sage: ff = list(map(lambda y: (y[0]-1,y[1]-1), ....: Permutation(map(lambda x: 1+r.index(x^-1), r)).cycle_tuples()[1:])) - sage: L = sum(i*(r[a]-r[b]) for i,(a,b) in zip(range(1,len(ff)+1), ff)); L - [ 0 1 -1 -3 -2 -4 3 4 2] - [-1 0 1 -4 -3 -2 2 3 4] - [ 1 -1 0 -2 -4 -3 4 2 3] - [ 3 4 2 0 1 -1 -3 -2 -4] - [ 2 3 4 -1 0 1 -4 -3 -2] - [ 4 2 3 1 -1 0 -2 -4 -3] - [-3 -2 -4 3 4 2 0 1 -1] - [-4 -3 -2 2 3 4 -1 0 1] - [-2 -4 -3 4 2 3 1 -1 0] - - sage: # needs sage.groups sage.libs.gap sage.modules sage.rings.finite_rings + sage: L = sum(i*(r[a]-r[b]) for i,(a,b) in zip(range(1,len(ff)+1), ff)) sage: G.relabel(range(9)) sage: G3x3 = graphs.MathonPseudocyclicStronglyRegularGraph(2, G=G, L=L) sage: G3x3.is_strongly_regular(parameters=True) diff --git a/src/sage/graphs/generators/intersection.py b/src/sage/graphs/generators/intersection.py index 0d19d30f9ea..0434806fb73 100644 --- a/src/sage/graphs/generators/intersection.py +++ b/src/sage/graphs/generators/intersection.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Intersection graphs diff --git a/src/sage/graphs/generators/platonic_solids.py b/src/sage/graphs/generators/platonic_solids.py index d3bc60a9974..5a572611539 100644 --- a/src/sage/graphs/generators/platonic_solids.py +++ b/src/sage/graphs/generators/platonic_solids.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" 1-skeletons of Platonic solids diff --git a/src/sage/graphs/generators/random.py b/src/sage/graphs/generators/random.py index 22c1f583f6a..e0868b36e21 100644 --- a/src/sage/graphs/generators/random.py +++ b/src/sage/graphs/generators/random.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Random graphs diff --git a/src/sage/graphs/generators/smallgraphs.py b/src/sage/graphs/generators/smallgraphs.py index 3b47f29f2ff..bde0cafc0a8 100644 --- a/src/sage/graphs/generators/smallgraphs.py +++ b/src/sage/graphs/generators/smallgraphs.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Various small graphs diff --git a/src/sage/graphs/generators/world_map.py b/src/sage/graphs/generators/world_map.py index f131aa446e2..76ec2b4f9ae 100644 --- a/src/sage/graphs/generators/world_map.py +++ b/src/sage/graphs/generators/world_map.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Graphs from the World Map diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 0f0f995bfbd..ca365540ffb 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Generic graphs (common to directed/undirected) @@ -2933,8 +2932,8 @@ def _check_embedding_validity(self, embedding=None, boolean=True): ``_embedding`` - ``boolean`` -- boolean (default: ``True``); -- whether to return a - boolean answer or raise a ``ValueError`` exception if the embedding is - invalid + boolean answer or raise a :class:`ValueError` exception + if the embedding is invalid EXAMPLES:: @@ -3443,8 +3442,8 @@ def allow_multiple_edges(self, new, check=True, keep_label='any'): .. WARNING:: ``'min'`` and ``'max'`` only works if the labels can be compared. A - ``TypeError`` might be raised when working with non-comparable - objects in Python 3. + :class:`TypeError` might be raised when working with non-comparable + objects. EXAMPLES: @@ -4092,7 +4091,7 @@ def density(self): if n < 2: return Rational(0) if self._directed: - return Rational(self.size()) / Rational((n ** 2 - n)) + return Rational(self.size()) / Rational(n ** 2 - n) return Rational(self.size()) / Rational((n ** 2 - n) / 2) def is_bipartite(self, certificate=False): @@ -18894,8 +18893,8 @@ def to_simple(self, to_undirected=True, keep_label='any', immutable=None): .. WARNING:: ``'min'`` and ``'max'`` only works if the labels can be compared. A - ``TypeError`` might be raised when working with non-comparable - objects in Python 3. + :class:`TypeError` might be raised when working with non-comparable + objects. - ``immutable`` -- boolean (default: ``Non``); whether to create a mutable/immutable copy. ``immutable=None`` (default) means that the @@ -25341,7 +25340,7 @@ def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_lab for el, part in edge_partition: # The multiplicity of a label is the number of edges from u to v # it represents - m = sum((y[1] for y in el)) + m = sum(y[1] for y in el) if m in tmp: tmp[m].append(part) else: diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 8988765d767..adc3c39f43e 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Undirected graphs @@ -3285,8 +3284,8 @@ def bounded_outdegree_orientation(self, bound, solver=None, verbose=False, OUTPUT: - A DiGraph representing the orientation if it exists. A ``ValueError`` - exception is raised otherwise. + A DiGraph representing the orientation if it exists. + A :class:`ValueError` exception is raised otherwise. ALGORITHM: diff --git a/src/sage/graphs/graph_decompositions/modular_decomposition.py b/src/sage/graphs/graph_decompositions/modular_decomposition.py index 7a0c71c2fb1..c994475bd6b 100644 --- a/src/sage/graphs/graph_decompositions/modular_decomposition.py +++ b/src/sage/graphs/graph_decompositions/modular_decomposition.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Modular Decomposition diff --git a/src/sage/graphs/graph_decompositions/tree_decomposition.pyx b/src/sage/graphs/graph_decompositions/tree_decomposition.pyx index 991f06efcce..803f5a09771 100644 --- a/src/sage/graphs/graph_decompositions/tree_decomposition.pyx +++ b/src/sage/graphs/graph_decompositions/tree_decomposition.pyx @@ -1083,37 +1083,31 @@ def label_nice_tree_decomposition(nice_TD, root, directed=False): EXAMPLES:: sage: from sage.graphs.graph_decompositions.tree_decomposition import make_nice_tree_decomposition, label_nice_tree_decomposition - sage: bip_one_four = graphs.CompleteBipartiteGraph(1, 4) - sage: bip_one_four_TD = bip_one_four.treewidth(certificate=True) - sage: nice_TD = make_nice_tree_decomposition(bip_one_four, bip_one_four_TD) + sage: claw = graphs.CompleteBipartiteGraph(1, 3) + sage: claw_TD = claw.treewidth(certificate=True) + sage: nice_TD = make_nice_tree_decomposition(claw, claw_TD) sage: root = sorted(nice_TD.vertices())[0] sage: label_TD = label_nice_tree_decomposition(nice_TD, root, directed=True) - sage: print(label_TD.name()) - Labelled Nice tree decomposition of Tree decomposition - sage: for node in sorted(label_TD): + sage: label_TD.name() + 'Labelled Nice tree decomposition of Tree decomposition' + sage: for node in sorted(label_TD): # random ....: print(node, label_TD.get_vertex(node)) (0, {}) forget (1, {0}) forget (2, {0, 1}) intro (3, {0}) forget - (4, {0, 4}) join - (5, {0, 4}) intro - (6, {0, 4}) intro - (7, {0}) forget - (8, {0}) forget - (9, {0, 3}) intro - (10, {0, 2}) intro - (11, {3}) intro - (12, {2}) intro - (13, {}) leaf - (14, {}) leaf + (4, {0, 3}) intro + (5, {0}) forget + (6, {0, 2}) intro + (7, {2}) intro + (8, {}) leaf """ from sage.graphs.digraph import DiGraph from sage.graphs.graph import Graph directed_TD = DiGraph(nice_TD.breadth_first_search(start=root, edges=True), format='list_of_edges', - name='Labelled {}'.format(nice_TD)) + name='Labelled {}'.format(nice_TD.name())) # The loop starts from the root node # We assume the tree decomposition is valid and nice, diff --git a/src/sage/graphs/graph_generators.py b/src/sage/graphs/graph_generators.py index 0677028e451..3ba5dc6e91f 100644 --- a/src/sage/graphs/graph_generators.py +++ b/src/sage/graphs/graph_generators.py @@ -475,7 +475,7 @@ def wrap_name(x): from . import graph -class GraphGenerators(): +class GraphGenerators: r""" A class consisting of constructors for several common graphs, as well as orderly generation of isomorphism class representatives. See the diff --git a/src/sage/graphs/graph_input.py b/src/sage/graphs/graph_input.py index 193afc9c4eb..4e08af6a4fc 100644 --- a/src/sage/graphs/graph_input.py +++ b/src/sage/graphs/graph_input.py @@ -559,7 +559,7 @@ def from_dict_of_lists(G, D, loops=False, multiedges=False, weighted=False): for u in D: if len(set(D[u])) != len(D[u]): if multiedges is False: - v = next((v for v in D[u] if D[u].count(v) > 1)) + v = next(v for v in D[u] if D[u].count(v) > 1) raise ValueError("non-multigraph got several edges (%s, %s)" % (u, v)) multiedges = True break diff --git a/src/sage/graphs/graph_latex.py b/src/sage/graphs/graph_latex.py index 1cd878bec5a..bff200a1a86 100644 --- a/src/sage/graphs/graph_latex.py +++ b/src/sage/graphs/graph_latex.py @@ -625,7 +625,7 @@ def set_option(self, option_name, option_value=None): - ``option_name`` -- a string for a latex option contained in the list ``sage.graphs.graph_latex.GraphLatex.__graphlatex_options``. - A ``ValueError`` is raised if the option is not allowed. + A :class:`ValueError` is raised if the option is not allowed. - ``option_value`` -- a value for the option. If omitted, or set to ``None``, the option will use the default value. diff --git a/src/sage/graphs/hypergraph_generators.py b/src/sage/graphs/hypergraph_generators.py index 4291e024001..f5218e225f1 100644 --- a/src/sage/graphs/hypergraph_generators.py +++ b/src/sage/graphs/hypergraph_generators.py @@ -32,7 +32,7 @@ """ -class HypergraphGenerators(): +class HypergraphGenerators: r""" A class consisting of constructors for common hypergraphs. """ diff --git a/src/sage/graphs/lovasz_theta.py b/src/sage/graphs/lovasz_theta.py index 49bc7e32092..5335a597ebf 100644 --- a/src/sage/graphs/lovasz_theta.py +++ b/src/sage/graphs/lovasz_theta.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Lovász theta-function of graphs diff --git a/src/sage/graphs/pq_trees.py b/src/sage/graphs/pq_trees.py index 2ddd39691e8..68063bc8e73 100644 --- a/src/sage/graphs/pq_trees.py +++ b/src/sage/graphs/pq_trees.py @@ -558,7 +558,7 @@ def set_contiguous(self, v): In any case, the sets containing ``v`` are contiguous when this function ends. If there is no possibility of doing so, the function - raises a ``ValueError`` exception. + raises a :class:`ValueError` exception. EXAMPLES: @@ -837,7 +837,7 @@ def set_contiguous(self, v): In any case, the sets containing ``v`` are contiguous when this function ends. If there is no possibility of doing so, the function - raises a ``ValueError`` exception. + raises a :class:`ValueError` exception. EXAMPLES: diff --git a/src/sage/graphs/schnyder.py b/src/sage/graphs/schnyder.py index 40f6d923656..b52dcff829a 100644 --- a/src/sage/graphs/schnyder.py +++ b/src/sage/graphs/schnyder.py @@ -555,7 +555,7 @@ def _compute_coordinates(g, x): g.set_pos(coordinates) # Setting _pos attribute to store coordinates -class TreeNode(): +class TreeNode: """ A class to represent each node in the trees used by ``_realizer`` and ``_compute_coordinates`` when finding a planar geometric embedding in diff --git a/src/sage/graphs/tutte_polynomial.py b/src/sage/graphs/tutte_polynomial.py index e82150eeb9c..4868577c892 100644 --- a/src/sage/graphs/tutte_polynomial.py +++ b/src/sage/graphs/tutte_polynomial.py @@ -218,7 +218,7 @@ def edge_multiplicities(G): ######## -class Ear(): +class Ear: r""" An ear is a sequence of vertices @@ -372,7 +372,7 @@ def removed_from(self, G): ################## -class EdgeSelection(): +class EdgeSelection: pass diff --git a/src/sage/groups/abelian_gps/abelian_group.py b/src/sage/groups/abelian_gps/abelian_group.py index f2e79c92353..66558a9ffb0 100644 --- a/src/sage/groups/abelian_gps/abelian_group.py +++ b/src/sage/groups/abelian_gps/abelian_group.py @@ -581,8 +581,8 @@ def is_subgroup(left, right): sage: G.is_subgroup(G) True - sage: H = G.subgroup([G.1]) # needs sage.libs.gap - sage: H.is_subgroup(G) # needs sage.libs.gap + sage: H = G.subgroup([G.1]) # needs sage.libs.gap # optional - gap_package_polycyclic + sage: H.is_subgroup(G) # needs sage.libs.gap # optional - gap_package_polycyclic True sage: G. = AbelianGroup(2) @@ -1206,7 +1206,7 @@ def subgroup(self, gensH, names="f"): EXAMPLES:: - sage: # needs sage.libs.gap + sage: # needs sage.libs.gap # optional - gap_package_polycyclic sage: G. = AbelianGroup(3, [2,3,4]); G Multiplicative Abelian group isomorphic to C2 x C3 x C4 sage: H = G.subgroup([a*b,a]); H @@ -1361,7 +1361,7 @@ def number_of_subgroups(self, order=None): 0 sage: AbelianGroup([1,3,1]).number_of_subgroups(order=2) 0 - sage: AbelianGroup([1,3,0,1]).number_of_subgroups(order=3) # needs sage.libs.gap + sage: AbelianGroup([1,3,0,1]).number_of_subgroups(order=3) # needs sage.libs.gap # optional - gap_package_polycyclic 1 sage: AbelianGroup([1,3,1]).number_of_subgroups(order=-2) Traceback (most recent call last): @@ -1451,12 +1451,12 @@ def subgroups(self, check=False): EXAMPLES:: - sage: AbelianGroup([2,3]).subgroups() # needs sage.libs.gap + sage: AbelianGroup([2,3]).subgroups() # needs sage.libs.gap # optional - gap_package_polycyclic [Multiplicative Abelian subgroup isomorphic to C2 x C3 generated by {f0*f1^2}, Multiplicative Abelian subgroup isomorphic to C2 generated by {f0}, Multiplicative Abelian subgroup isomorphic to C3 generated by {f1}, Trivial Abelian subgroup] - sage: len(AbelianGroup([2,4,8]).subgroups()) # needs sage.libs.gap + sage: len(AbelianGroup([2,4,8]).subgroups()) # needs sage.libs.gap # optional - gap_package_polycyclic 81 TESTS:: @@ -1467,10 +1467,10 @@ def subgroups(self, check=False): Check that :trac:`14196` is fixed:: sage: B = AbelianGroup([1,2]) - sage: B.subgroups() # needs sage.libs.gap + sage: B.subgroups() # needs sage.libs.gap # optional - gap_package_polycyclic [Multiplicative Abelian subgroup isomorphic to C2 generated by {f1}, Trivial Abelian subgroup] - sage: B.subgroups(check=True) # needs sage.libs.gap + sage: B.subgroups(check=True) # needs sage.libs.gap # optional - gap_package_polycyclic [Multiplicative Abelian subgroup isomorphic to C2 generated by {f1}, Trivial Abelian subgroup] """ @@ -1534,10 +1534,10 @@ def subgroup_reduced(self, elts, verbose=False): EXAMPLES:: sage: G = AbelianGroup([4,4]) - sage: G.subgroup( [ G([1,0]), G([1,2]) ]) # needs sage.libs.gap + sage: G.subgroup( [ G([1,0]), G([1,2]) ]) # needs sage.libs.gap # optional - gap_package_polycyclic Multiplicative Abelian subgroup isomorphic to C2 x C4 generated by {f0, f0*f1^2} - sage: AbelianGroup([4,4]).subgroup_reduced( [ [1,0], [1,2] ]) # needs sage.libs.gap + sage: AbelianGroup([4,4]).subgroup_reduced( [ [1,0], [1,2] ]) # needs sage.libs.gap # optional - gap_package_polycyclic Multiplicative Abelian subgroup isomorphic to C2 x C4 generated by {f0^2*f1^2, f0^3} """ @@ -1568,7 +1568,7 @@ def torsion_subgroup(self, n=None): EXAMPLES:: - sage: # needs sage.libs.gap + sage: # needs sage.libs.gap # optional - gap_package_polycyclic sage: G = AbelianGroup([2, 3]) sage: G.torsion_subgroup() Multiplicative Abelian subgroup isomorphic to C2 x C3 generated @@ -1587,7 +1587,7 @@ def torsion_subgroup(self, n=None): :: sage: G = AbelianGroup([2, 2*3, 2*3*5, 0, 2*3*5*7, 2*3*5*7*11]) - sage: G.torsion_subgroup(5) # needs sage.libs.gap + sage: G.torsion_subgroup(5) # needs sage.libs.gap # optional - gap_package_polycyclic Multiplicative Abelian subgroup isomorphic to C5 x C5 x C5 generated by {f2^6, f4^42, f5^462} """ if n is None: @@ -1620,7 +1620,7 @@ def __init__(self, ambient, gens, names="f", category=None): """ EXAMPLES:: - sage: # needs sage.libs.gap + sage: # needs sage.libs.gap # optional - gap_package_polycyclic sage: F = AbelianGroup(5, [30,64,729], names=list("abcde")) sage: a,b,c,d,e = F.gens() sage: F.subgroup([a^3,b]) @@ -1675,23 +1675,23 @@ def __init__(self, ambient, gens, names="f", category=None): Infinite groups can also be handled:: + sage: # needs sage.libs.gap # optional - gap_package_polycyclic sage: G = AbelianGroup([3,4,0], names="abc") sage: a,b,c = G.gens() - sage: F = G.subgroup([a, b^2, c]); F # needs sage.libs.gap + sage: F = G.subgroup([a, b^2, c]); F Multiplicative Abelian subgroup isomorphic to C2 x C3 x Z generated by {a, b^2, c} - - sage: F.gens_orders() # needs sage.libs.gap + sage: F.gens_orders() (2, 3, 0) - sage: F.gens() # needs sage.libs.gap + sage: F.gens() (a, b^2, c) - sage: F.order() # needs sage.libs.gap + sage: F.order() +Infinity Testing issue :trac:`18863`:: sage: G = AbelianGroup(5,[2]) - sage: G.subgroup([prod(g^k for g,k in zip(G.gens(),[1,-2,3,-4,5]))]) # needs sage.libs.gap + sage: G.subgroup([prod(g^k for g,k in zip(G.gens(),[1,-2,3,-4,5]))]) # needs sage.libs.gap # optional - gap_package_polycyclic Multiplicative Abelian subgroup isomorphic to Z generated by {f0*f1^-2*f2^3*f3^-4*f4} """ @@ -1725,35 +1725,38 @@ def __contains__(self, x): EXAMPLES:: + sage: # needs sage.libs.gap # optional - gap_package_polycyclic sage: G. = AbelianGroup(2) - sage: A = G.subgroup([a]) # needs sage.libs.gap + sage: A = G.subgroup([a]) sage: a in G True - sage: a in A # needs sage.libs.gap + sage: a in A True TESTS: Check that :trac:`32910` is fixed:: + sage: # needs sage.libs.gap # optional - gap_package_polycyclic sage: G. = AbelianGroup(2, [4, 576]) sage: Hgens = [a^2, a*b^2] - sage: H = G.subgroup(Hgens) # needs sage.libs.gap - sage: [g in H for g in (a^3, b^2, b^3, a^3*b^2, "junk")] # needs sage.libs.gap + sage: H = G.subgroup(Hgens) + sage: [g in H for g in (a^3, b^2, b^3, a^3*b^2, "junk")] [False, False, False, True, False] Check that :trac:`31507` is fixed:: + sage: # needs sage.libs.gap # optional - gap_package_polycyclic sage: G = AbelianGroup(2, gens_orders=[16, 16]) sage: f0, f1 = G.gens() - sage: H = G.subgroup([f0*f1^3]) # needs sage.libs.gap - sage: [g in H for g in (f0, f0*f1^2, f0*f1^3, f0*f1^4)] # needs sage.libs.gap + sage: H = G.subgroup([f0*f1^3]) + sage: [g in H for g in (f0, f0*f1^2, f0*f1^3, f0*f1^4)] [False, False, True, False] sage: G. = AbelianGroup(2) sage: Hgens = [a*b, a*b^-1] - sage: H = G.subgroup(Hgens) # needs sage.libs.gap - sage: b^2 in H # needs sage.libs.gap + sage: H = G.subgroup(Hgens) # optional - gap_package_polycyclic + sage: b^2 in H # optional - gap_package_polycyclic True """ if not isinstance(x, AbelianGroupElement): @@ -1781,9 +1784,10 @@ def ambient_group(self): EXAMPLES:: + sage: # needs sage.libs.gap # optional - gap_package_polycyclic sage: G. = AbelianGroup([2,3,4]) - sage: H = G.subgroup([a, b^2]) # needs sage.libs.gap - sage: H.ambient_group() is G # needs sage.libs.gap + sage: H = G.subgroup([a, b^2]) + sage: H.ambient_group() is G True """ return self._ambient_group @@ -1805,19 +1809,17 @@ def equals(left, right): EXAMPLES:: + sage: # needs sage.libs.gap # optional - gap_package_polycyclic sage: G = AbelianGroup(3, [2,3,4], names="abc"); G Multiplicative Abelian group isomorphic to C2 x C3 x C4 sage: a,b,c = G.gens() - sage: F = G.subgroup([a,b^2]); F # needs sage.libs.gap + sage: F = G.subgroup([a,b^2]); F Multiplicative Abelian subgroup isomorphic to C2 x C3 generated by {a, b^2} - sage: F = AbelianGroup(2) sage: A = G.subgroup([a]) sage: B = G.subgroup([b]) @@ -1849,8 +1851,8 @@ def _repr_(self): sage: G. = AbelianGroup(2) sage: G._repr_() 'Multiplicative Abelian group isomorphic to Z x Z' - sage: A = G.subgroup([a]) # needs sage.libs.gap - sage: A._repr_() # needs sage.libs.gap + sage: A = G.subgroup([a]) # needs sage.libs.gap # optional - gap_package_polycyclic + sage: A._repr_() # needs sage.libs.gap # optional - gap_package_polycyclic 'Multiplicative Abelian subgroup isomorphic to Z generated by {a}' """ eldv = self._abinvs @@ -1872,11 +1874,12 @@ def gens(self): EXAMPLES:: + sage: # needs sage.libs.gap # optional - gap_package_polycyclic sage: G. = AbelianGroup(2) - sage: A = G.subgroup([a]) # needs sage.libs.gap + sage: A = G.subgroup([a]) sage: G.gens() (a, b) - sage: A.gens() # needs sage.libs.gap + sage: A.gens() (a,) """ return self._gens @@ -1887,9 +1890,10 @@ def gen(self, n): EXAMPLES:: + sage: # needs sage.libs.gap # optional - gap_package_polycyclic sage: G. = AbelianGroup(2) - sage: A = G.subgroup([a]) # needs sage.libs.gap - sage: A.gen(0) # needs sage.libs.gap + sage: A = G.subgroup([a]) + sage: A.gen(0) a """ return self._gens[n] diff --git a/src/sage/groups/abelian_gps/abelian_group_morphism.py b/src/sage/groups/abelian_gps/abelian_group_morphism.py index e56b1c68afd..e4b7b82b42b 100644 --- a/src/sage/groups/abelian_gps/abelian_group_morphism.py +++ b/src/sage/groups/abelian_gps/abelian_group_morphism.py @@ -59,14 +59,14 @@ class AbelianGroupMorphism(Morphism): sage: x,y = H.gens() sage: from sage.groups.abelian_gps.abelian_group_morphism import AbelianGroupMorphism - sage: phi = AbelianGroupMorphism(H,G,[x,y],[a,b]) + sage: phi = AbelianGroupMorphism(H,G,[x,y],[a,b]) # optional - gap_package_polycyclic TESTS:: sage: G. = AbelianGroup(2,[2,3]) sage: H. = AbelianGroup(3,[2,3,4]) - sage: phi = AbelianGroupMorphism(G,H,[x,y],[a,b]) - sage: Hom(G,H) == phi.parent() + sage: phi = AbelianGroupMorphism(G,H,[x,y],[a,b]) # optional - gap_package_polycyclic + sage: Hom(G,H) == phi.parent() # optional - gap_package_polycyclic True AUTHORS: @@ -126,11 +126,11 @@ def _libgap_(self): sage: H = AbelianGroup(2,[2,3],names="xy"); H Multiplicative Abelian group isomorphic to C2 x C3 sage: x,y = H.gens() - sage: phi = AbelianGroupMorphism(H,G,[x,y],[a,b]) - sage: libgap(phi) + sage: phi = AbelianGroupMorphism(H,G,[x,y],[a,b]) # optional - gap_package_polycyclic + sage: libgap(phi) # optional - gap_package_polycyclic [ f1, f2 ] -> [ f1, f2 ] - sage: phi = AbelianGroupMorphism(H,G,[x,y],[a*c**2,b]) - sage: libgap(phi) + sage: phi = AbelianGroupMorphism(H,G,[x,y],[a*c**2,b]) # optional - gap_package_polycyclic + sage: libgap(phi) # optional - gap_package_polycyclic [ f1, f2 ] -> [ f1*f4, f2 ] """ G = libgap(self.domain()) @@ -158,16 +158,16 @@ def kernel(self): sage: G = AbelianGroup(2,[2,3],names="xy"); G Multiplicative Abelian group isomorphic to C2 x C3 sage: x,y = G.gens() - sage: phi = AbelianGroupMorphism(G,H,[x,y],[a,b]) - sage: phi.kernel() + sage: phi = AbelianGroupMorphism(G,H,[x,y],[a,b]) # optional - gap_package_polycyclic + sage: phi.kernel() # optional - gap_package_polycyclic Group([ ]) sage: H = AbelianGroup(3,[2,2,2],names="abc") sage: a,b,c = H.gens() sage: G = AbelianGroup(2,[2,2],names="x") sage: x,y = G.gens() - sage: phi = AbelianGroupMorphism(G,H,[x,y],[a,a]) - sage: phi.kernel() + sage: phi = AbelianGroupMorphism(G,H,[x,y],[a,a]) # optional - gap_package_polycyclic + sage: phi.kernel() # optional - gap_package_polycyclic Group([ f1*f2 ]) """ return libgap(self).Kernel() @@ -186,11 +186,11 @@ def image(self, S): sage: G = AbelianGroup(2,[2,3],names="xy") sage: x,y = G.gens() - sage: subG = G.subgroup([x]) + sage: subG = G.subgroup([x]) # optional - gap_package_polycyclic sage: H = AbelianGroup(3,[2,3,4],names="abc") sage: a,b,c = H.gens() - sage: phi = AbelianGroupMorphism(G,H,[x,y],[a,b]) - sage: phi.image(subG) + sage: phi = AbelianGroupMorphism(G,H,[x,y],[a,b]) # optional - gap_package_polycyclic + sage: phi.image(subG) # optional - gap_package_polycyclic Multiplicative Abelian subgroup isomorphic to C2 generated by {a} """ return self.codomain().subgroup([self(g) for g in S.gens()]) @@ -206,10 +206,10 @@ def _call_(self, g): sage: a,b,c = H.gens() sage: G = AbelianGroup(2, [2,3], names="xy") sage: x,y = G.gens() - sage: phi = AbelianGroupMorphism(G,H,[x,y],[a,b]) - sage: phi(y*x) + sage: phi = AbelianGroupMorphism(G,H,[x,y],[a,b]) # optional - gap_package_polycyclic + sage: phi(y*x) # optional - gap_package_polycyclic a*b - sage: phi(y^2) + sage: phi(y^2) # optional - gap_package_polycyclic b^2 """ # g.word_problem is faster in general than word_problem(g) diff --git a/src/sage/groups/abelian_gps/dual_abelian_group.py b/src/sage/groups/abelian_gps/dual_abelian_group.py index 85318aa6f83..c6012254630 100644 --- a/src/sage/groups/abelian_gps/dual_abelian_group.py +++ b/src/sage/groups/abelian_gps/dual_abelian_group.py @@ -1,3 +1,4 @@ +# sage.doctest: needs sage.rings.number_field r""" Dual groups of Finite Multiplicative Abelian Groups @@ -25,7 +26,6 @@ sage: F = AbelianGroup(5, [2,5,7,8,9], names='abcde') sage: (a, b, c, d, e) = F.gens() - sage: # needs sage.rings.number_field sage: Fd = F.dual_group(names='ABCDE') sage: Fd.base_ring() Cyclotomic Field of order 2520 and degree 576 @@ -82,7 +82,6 @@ def is_DualAbelianGroup(x): EXAMPLES:: - sage: # needs sage.rings.number_field sage: from sage.groups.abelian_gps.dual_abelian_group import is_DualAbelianGroup sage: F = AbelianGroup(5,[3,5,7,8,9], names=list("abcde")) sage: Fd = F.dual_group() @@ -105,7 +104,7 @@ class DualAbelianGroup_class(UniqueRepresentation, AbelianGroupBase): EXAMPLES:: sage: F = AbelianGroup(5,[3,5,7,8,9], names="abcde") - sage: F.dual_group() # needs sage.rings.number_field + sage: F.dual_group() Dual of Abelian Group isomorphic to Z/3Z x Z/5Z x Z/7Z x Z/8Z x Z/9Z over Cyclotomic Field of order 2520 and degree 576 @@ -123,7 +122,7 @@ def __init__(self, G, names, base_ring): EXAMPLES:: sage: F = AbelianGroup(5,[3,5,7,8,9], names="abcde") - sage: F.dual_group() # needs sage.rings.number_field + sage: F.dual_group() Dual of Abelian Group isomorphic to Z/3Z x Z/5Z x Z/7Z x Z/8Z x Z/9Z over Cyclotomic Field of order 2520 and degree 576 """ @@ -180,9 +179,9 @@ def _repr_(self): EXAMPLES:: sage: F = AbelianGroup(5, [2,5,7,8,9], names='abcde') - sage: Fd = F.dual_group(names='ABCDE', # needs sage.rings.number_field + sage: Fd = F.dual_group(names='ABCDE', ....: base_ring=CyclotomicField(2*5*7*8*9)) - sage: Fd # indirect doctest # needs sage.rings.number_field + sage: Fd # indirect doctest Dual of Abelian Group isomorphic to Z/2Z x Z/5Z x Z/7Z x Z/8Z x Z/9Z over Cyclotomic Field of order 5040 and degree 1152 sage: Fd = F.dual_group(names='ABCDE', base_ring=CC) # needs sage.rings.real_mpfr @@ -209,8 +208,8 @@ def _latex_(self): EXAMPLES:: sage: F = AbelianGroup(3, [2]*3) - sage: Fd = F.dual_group() # needs sage.rings.number_field - sage: Fd._latex_() # needs sage.rings.number_field + sage: Fd = F.dual_group() + sage: Fd._latex_() '$\\mathrm{DualAbelianGroup}( AbelianGroup ( 3, (2, 2, 2) ) )$' """ return r"$\mathrm{DualAbelianGroup}( AbelianGroup ( %s, %s ) )$" % (self.ngens(), self.gens_orders()) @@ -251,7 +250,6 @@ def gen(self, i=0): EXAMPLES:: - sage: # needs sage.rings.number_field sage: F = AbelianGroup(3, [1,2,3], names='a') sage: Fd = F.dual_group(names="A") sage: Fd.0 @@ -279,8 +277,8 @@ def gens(self): EXAMPLES:: - sage: F = AbelianGroup([7,11]).dual_group() # needs sage.rings.number_field - sage: F.gens() # needs sage.rings.number_field + sage: F = AbelianGroup([7,11]).dual_group() + sage: F.gens() (X0, X1) """ n = self.group().ngens() @@ -293,8 +291,8 @@ def ngens(self): EXAMPLES:: sage: F = AbelianGroup([7]*100) - sage: Fd = F.dual_group() # needs sage.rings.number_field - sage: Fd.ngens() # needs sage.rings.number_field + sage: Fd = F.dual_group() + sage: Fd.ngens() 100 """ return self.group().ngens() @@ -310,8 +308,8 @@ def gens_orders(self): EXAMPLES:: sage: F = AbelianGroup([5]*1000) - sage: Fd = F.dual_group() # needs sage.rings.number_field - sage: invs = Fd.gens_orders(); len(invs) # needs sage.rings.number_field + sage: Fd = F.dual_group() + sage: invs = Fd.gens_orders(); len(invs) 1000 """ return self.group().gens_orders() @@ -325,8 +323,8 @@ def invariants(self): EXAMPLES:: sage: F = AbelianGroup([5]*1000) - sage: Fd = F.dual_group() # needs sage.rings.number_field - sage: invs = Fd.gens_orders(); len(invs) # needs sage.rings.number_field + sage: Fd = F.dual_group() + sage: invs = Fd.gens_orders(); len(invs) 1000 """ # TODO: deprecate @@ -340,9 +338,9 @@ def __contains__(self, X): sage: F = AbelianGroup(5,[2, 3, 5, 7, 8], names="abcde") sage: a,b,c,d,e = F.gens() - sage: Fd = F.dual_group(names="ABCDE") # needs sage.rings.number_field - sage: A,B,C,D,E = Fd.gens() # needs sage.rings.number_field - sage: A*B^2*D^7 in Fd # needs sage.rings.number_field + sage: Fd = F.dual_group(names="ABCDE") + sage: A,B,C,D,E = Fd.gens() + sage: A*B^2*D^7 in Fd True """ return X.parent() == self and is_DualAbelianGroupElement(X) @@ -354,8 +352,8 @@ def order(self): EXAMPLES:: sage: G = AbelianGroup([2,3,9]) - sage: Gd = G.dual_group() # needs sage.rings.number_field - sage: Gd.order() # needs sage.rings.number_field + sage: Gd = G.dual_group() + sage: Gd.order() 54 """ G = self.group() @@ -368,10 +366,10 @@ def is_commutative(self): EXAMPLES:: sage: G = AbelianGroup([2,3,9]) - sage: Gd = G.dual_group() # needs sage.rings.number_field - sage: Gd.is_commutative() # needs sage.rings.number_field + sage: Gd = G.dual_group() + sage: Gd.is_commutative() True - sage: Gd.is_abelian() # needs sage.rings.number_field + sage: Gd.is_abelian() True """ return True @@ -384,8 +382,8 @@ def list(self): EXAMPLES:: sage: G = AbelianGroup([2,3], names="ab") - sage: Gd = G.dual_group(names="AB") # needs sage.rings.number_field - sage: Gd.list() # needs sage.rings.number_field + sage: Gd = G.dual_group(names="AB") + sage: Gd.list() (1, B, B^2, A, A*B, A*B^2) """ if not self.is_finite(): @@ -400,8 +398,8 @@ def __iter__(self): EXAMPLES:: sage: G = AbelianGroup([2,3], names="ab") - sage: Gd = G.dual_group(names="AB") # needs sage.rings.number_field - sage: [X for X in Gd] # needs sage.rings.number_field + sage: Gd = G.dual_group(names="AB") + sage: [X for X in Gd] [1, B, B^2, A, A*B, A*B^2] sage: # needs sage.rings.real_mpfr diff --git a/src/sage/groups/abelian_gps/dual_abelian_group_element.py b/src/sage/groups/abelian_gps/dual_abelian_group_element.py index 407323d4f34..6fdb8a68c4e 100644 --- a/src/sage/groups/abelian_gps/dual_abelian_group_element.py +++ b/src/sage/groups/abelian_gps/dual_abelian_group_element.py @@ -1,3 +1,4 @@ +# sage.doctest: needs sage.rings.number_field """ Elements (characters) of the dual group of a finite Abelian group @@ -8,13 +9,12 @@ sage: F Multiplicative Abelian group isomorphic to C2 x C3 x C5 x C7 x C8 - sage: Fd = F.dual_group(names="ABCDE"); Fd # needs sage.rings.number_field + sage: Fd = F.dual_group(names="ABCDE"); Fd Dual of Abelian Group isomorphic to Z/2Z x Z/3Z x Z/5Z x Z/7Z x Z/8Z over Cyclotomic Field of order 840 and degree 192 The elements of the dual group can be evaluated on elements of the original group:: - sage: # needs sage.rings.number_field sage: a,b,c,d,e = F.gens() sage: A,B,C,D,E = Fd.gens() sage: A*B^2*D^7 @@ -71,10 +71,10 @@ def is_DualAbelianGroupElement(x) -> bool: EXAMPLES:: sage: from sage.groups.abelian_gps.dual_abelian_group import is_DualAbelianGroupElement - sage: F = AbelianGroup(5, [5,5,7,8,9], names=list("abcde")).dual_group() # needs sage.rings.number_field - sage: is_DualAbelianGroupElement(F) # needs sage.rings.number_field + sage: F = AbelianGroup(5, [5,5,7,8,9], names=list("abcde")).dual_group() + sage: is_DualAbelianGroupElement(F) False - sage: is_DualAbelianGroupElement(F.an_element()) # needs sage.rings.number_field + sage: is_DualAbelianGroupElement(F.an_element()) True """ return isinstance(x, DualAbelianGroupElement) @@ -96,7 +96,6 @@ def __call__(self, g): EXAMPLES:: - sage: # needs sage.rings.number_field sage: F = AbelianGroup(5, [2,3,5,7,8], names="abcde") sage: a,b,c,d,e = F.gens() sage: Fd = F.dual_group(names="ABCDE") @@ -147,7 +146,6 @@ def word_problem(self, words): EXAMPLES:: - sage: # needs sage.rings.number_field sage: G = AbelianGroup(5,[3, 5, 5, 7, 8], names="abcde") sage: Gd = G.dual_group(names="abcde") sage: a,b,c,d,e = Gd.gens() @@ -156,7 +154,7 @@ def word_problem(self, words): sage: w = a^7*b^3*c^5*d^4*e^4 sage: x = a^3*b^2*c^2*d^3*e^5 sage: y = a^2*b^4*c^2*d^4*e^5 - sage: e.word_problem([u,v,w,x,y]) + sage: e.word_problem([u,v,w,x,y]) # needs sage.libs.gap [[b^2*c^2*d^3*e^5, 245]] """ from sage.libs.gap.libgap import libgap diff --git a/src/sage/groups/affine_gps/affine_group.py b/src/sage/groups/affine_gps/affine_group.py index bfcdd282df1..7b5e4695fb1 100644 --- a/src/sage/groups/affine_gps/affine_group.py +++ b/src/sage/groups/affine_gps/affine_group.py @@ -204,7 +204,10 @@ def __init__(self, degree, ring): sage: G = AffineGroup(2, GF(5)); G Affine Group of degree 2 over Finite Field of size 5 + + sage: # needs sage.libs.gap (for gens) sage: TestSuite(G).run() + sage: G.category() Category of finite groups @@ -289,8 +292,10 @@ def cardinality(self): EXAMPLES:: + sage: # needs sage.libs.gap sage: AffineGroup(6, GF(5)).cardinality() 172882428468750000000000000000 + sage: AffineGroup(6, ZZ).cardinality() +Infinity """ @@ -464,6 +469,7 @@ def random_element(self): EXAMPLES:: + sage: # needs sage.libs.gap sage: G = AffineGroup(4, GF(3)) sage: G.random_element() # random [2 0 1 2] [1] @@ -498,6 +504,7 @@ def some_elements(self): EXAMPLES:: + sage: # needs sage.libs.gap sage: G = AffineGroup(4,5) sage: G.some_elements() [ [2 0 0 0] [1] diff --git a/src/sage/groups/affine_gps/euclidean_group.py b/src/sage/groups/affine_gps/euclidean_group.py index 47de04c6544..59fb411b925 100644 --- a/src/sage/groups/affine_gps/euclidean_group.py +++ b/src/sage/groups/affine_gps/euclidean_group.py @@ -146,6 +146,8 @@ class EuclideanGroup(AffineGroup): True sage: G = EuclideanGroup(2, GF(5)); G Euclidean Group of degree 2 over Finite Field of size 5 + + sage: # needs sage.libs.gap (for gens) sage: TestSuite(G).run() REFERENCES: diff --git a/src/sage/groups/affine_gps/group_element.py b/src/sage/groups/affine_gps/group_element.py index 52fbe9365d6..7df4dc8a69a 100644 --- a/src/sage/groups/affine_gps/group_element.py +++ b/src/sage/groups/affine_gps/group_element.py @@ -78,11 +78,14 @@ class AffineGroupElement(MultiplicativeGroupElement): EXAMPLES:: sage: G = AffineGroup(2, GF(3)) + + sage: # needs sage.libs.gap sage: g = G.random_element() sage: type(g) sage: G(g.matrix()) == g True + sage: G(2) [2 0] [0] x |-> [0 2] x + [0] @@ -107,6 +110,7 @@ def __init__(self, parent, A, b=0, convert=True, check=True): TESTS:: + sage: # needs sage.libs.gap sage: G = AffineGroup(4, GF(5)) sage: g = G.random_element() sage: TestSuite(g).run() @@ -200,6 +204,7 @@ def matrix(self): Composition of affine group elements equals multiplication of the matrices:: + sage: # needs sage.libs.gap sage: g1 = G.random_element() sage: g2 = G.random_element() sage: g1.matrix() * g2.matrix() == (g1*g2).matrix() diff --git a/src/sage/groups/braid.py b/src/sage/groups/braid.py index 18d95ef86f2..bd787d3c153 100644 --- a/src/sage/groups/braid.py +++ b/src/sage/groups/braid.py @@ -72,7 +72,7 @@ from sage.combinat.permutation import Permutation from sage.combinat.permutation import Permutations from sage.combinat.subset import Subsets -from sage.features import PythonModule +from sage.features.sagemath import sage__libs__braiding from sage.groups.artin import FiniteTypeArtinGroup, FiniteTypeArtinGroupElement from sage.groups.finitely_presented import FinitelyPresentedGroup from sage.groups.finitely_presented import GroupMorphismWithGensImages @@ -80,7 +80,6 @@ from sage.functions.generalized import sign from sage.groups.perm_gps.permgroup_named import SymmetricGroup from sage.groups.perm_gps.permgroup_named import SymmetricGroupElement -from sage.knots.knot import Knot from sage.libs.gap.libgap import libgap from sage.matrix.constructor import identity_matrix, matrix from sage.misc.lazy_attribute import lazy_attribute @@ -98,7 +97,8 @@ ['leftnormalform', 'rightnormalform', 'centralizer', 'supersummitset', 'greatestcommondivisor', 'leastcommonmultiple', 'conjugatingbraid', 'ultrasummitset', 'thurston_type', 'rigidity', 'sliding_circuits'], - feature=PythonModule('sage.libs.braiding', spkg='libbraiding', type='standard')) + feature=sage__libs__braiding()) +lazy_import('sage.knots.knot', 'Knot') class Braid(FiniteTypeArtinGroupElement): diff --git a/src/sage/groups/galois_group.py b/src/sage/groups/galois_group.py index 5a48380eb21..a7439d18ab3 100644 --- a/src/sage/groups/galois_group.py +++ b/src/sage/groups/galois_group.py @@ -10,15 +10,17 @@ - David Roe (2019): initial version """ -from sage.groups.perm_gps.permgroup import PermutationGroup, PermutationGroup_generic, PermutationGroup_subgroup from sage.groups.abelian_gps.abelian_group import AbelianGroup_class, AbelianGroup_subgroup -from sage.sets.finite_enumerated_set import FiniteEnumeratedSet -from sage.misc.lazy_attribute import lazy_attribute from sage.misc.abstract_method import abstract_method from sage.misc.cachefunc import cached_method -from sage.structure.category_object import normalize_names +from sage.misc.lazy_attribute import lazy_attribute +from sage.misc.lazy_import import lazy_import from sage.rings.integer_ring import ZZ +lazy_import('sage.groups.galois_group_perm', ['GaloisGroup_perm', 'GaloisSubgroup_perm']) +lazy_import('sage.groups.perm_gps.permgroup', 'PermutationGroup') + + def _alg_key(self, algorithm=None, recompute=False): r""" Return a key for use in cached_method calls. @@ -40,6 +42,7 @@ def _alg_key(self, algorithm=None, recompute=False): algorithm = self._get_algorithm(algorithm) return algorithm + class _GMixin: r""" This class provides some methods for Galois groups to be used for both permutation groups @@ -151,6 +154,7 @@ def _gc_map(self): """ return self._gcdata[1] + class _GaloisMixin(_GMixin): """ This class provides methods for Galois groups, allowing concrete instances @@ -275,6 +279,7 @@ def is_galois(self): """ return self.order() == self._field_degree + class _SubGaloisMixin(_GMixin): """ This class provides methods for subgroups of Galois groups, allowing concrete instances @@ -339,164 +344,6 @@ def _gcdata(self): """ return self._ambient_group._gcdata -class GaloisGroup_perm(_GaloisMixin, PermutationGroup_generic): - r""" - The group of automorphisms of a Galois closure of a given field. - - INPUT: - - - ``field`` -- a field, separable over its base - - - ``names`` -- a string or tuple of length 1, giving a variable name for the splitting field - - - ``gc_numbering`` -- boolean, whether to express permutations in terms of the - roots of the defining polynomial of the splitting field (versus the defining polynomial - of the original extension). The default value may vary based on the type of field. - """ - @abstract_method - def transitive_number(self, algorithm=None, recompute=False): - """ - The transitive number (as in the GAP and Magma databases of transitive groups) - for the action on the roots of the defining polynomial of the top field. - - EXAMPLES:: - - sage: R. = ZZ[] - sage: K. = NumberField(x^3 + 2*x + 2) # needs sage.rings.number_field - sage: G = K.galois_group() # needs sage.rings.number_field - sage: G.transitive_number() # needs sage.rings.number_field - 2 - """ - - @lazy_attribute - def _gens(self): - """ - The generators of this Galois group as permutations of the roots. It's important that this - be computed lazily, since it's often possible to compute other attributes (such as the order - or transitive number) more cheaply. - - EXAMPLES:: - - sage: R. = ZZ[] - sage: K. = NumberField(x^5 - 2) # needs sage.rings.number_field - sage: G = K.galois_group(gc_numbering=False) # needs sage.rings.number_field - sage: G._gens # needs sage.rings.number_field - [(1,2,3,5), (1,4,3,2,5)] - """ - return NotImplemented - - def __init__(self, field, algorithm=None, names=None, gc_numbering=False): - r""" - EXAMPLES:: - - sage: R. = ZZ[] - sage: K. = NumberField(x^3 + 2*x + 2) # needs sage.rings.number_field - sage: G = K.galois_group() # needs sage.rings.number_field - sage: TestSuite(G).run() # needs sage.rings.number_field - """ - self._field = field - self._default_algorithm = algorithm - self._base = field.base_field() - self._gc_numbering = gc_numbering - if names is None: - # add a c for Galois closure - names = field.variable_name() + 'c' - self._gc_names = normalize_names(1, names) - # We do only the parts of the initialization of PermutationGroup_generic - # that don't depend on _gens - from sage.categories.permutation_groups import PermutationGroups - category = PermutationGroups().FinitelyGenerated().Finite() - # Note that we DON'T call the __init__ method for PermutationGroup_generic - # Instead, the relevant attributes are computed lazily - super(PermutationGroup_generic, self).__init__(category=category) - - @lazy_attribute - def _deg(self): - r""" - The number of moved points in the permutation representation. - - This will be the degree of the original number field if `_gc_numbering`` - is ``False``, or the degree of the Galois closure otherwise. - - EXAMPLES:: - - sage: # needs sage.rings.number_field - sage: R. = ZZ[] - sage: K. = NumberField(x^5 - 2) - sage: G = K.galois_group(gc_numbering=False); G - Galois group 5T3 (5:4) with order 20 of x^5 - 2 - sage: G._deg - 5 - sage: G = K.galois_group(gc_numbering=True); G._deg - 20 - """ - if self._gc_numbering: - return self.order() - else: - try: - return self._field.degree() - except NotImplementedError: # relative number fields don't support degree - return self._field.relative_degree() - - @lazy_attribute - def _domain(self): - r""" - The integers labeling the roots on which this Galois group acts. - - EXAMPLES:: - - sage: # needs sage.rings.number_field - sage: R. = ZZ[] - sage: K. = NumberField(x^5 - 2) - sage: G = K.galois_group(gc_numbering=False); G - Galois group 5T3 (5:4) with order 20 of x^5 - 2 - sage: G._domain - {1, 2, 3, 4, 5} - sage: G = K.galois_group(gc_numbering=True); G._domain - {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} - """ - return FiniteEnumeratedSet(range(1, self._deg+1)) - - @lazy_attribute - def _domain_to_gap(self): - r""" - Dictionary implementing the identity (used by PermutationGroup_generic). - - EXAMPLES:: - - sage: R. = ZZ[] - sage: K. = NumberField(x^5 - 2) # needs sage.rings.number_field - sage: G = K.galois_group(gc_numbering=False) # needs sage.rings.number_field - sage: G._domain_to_gap[5] # needs sage.rings.number_field - 5 - """ - return {key: i+1 for i, key in enumerate(self._domain)} - - @lazy_attribute - def _domain_from_gap(self): - r""" - Dictionary implementing the identity (used by PermutationGroup_generic). - - EXAMPLES:: - - sage: R. = ZZ[] - sage: K. = NumberField(x^5 - 2) # needs sage.rings.number_field - sage: G = K.galois_group(gc_numbering=True) # needs sage.rings.number_field - sage: G._domain_from_gap[20] # needs sage.rings.number_field - 20 - """ - return {i+1: key for i, key in enumerate(self._domain)} - - def ngens(self): - r""" - Number of generators of this Galois group - - EXAMPLES:: - - sage: QuadraticField(-23, 'a').galois_group().ngens() # needs sage.rings.number_field - 1 - """ - return len(self._gens) class GaloisGroup_ab(_GaloisMixin, AbelianGroup_class): r""" @@ -551,7 +398,7 @@ def permutation_group(self): EXAMPLES:: - sage: GF(3^10).galois_group().permutation_group() # needs sage.rings.finite_rings + sage: GF(3^10).galois_group().permutation_group() # needs sage.libs.gap sage.rings.finite_rings Permutation Group with generators [(1,2,3,4,5,6,7,8,9,10)] """ return PermutationGroup(gap_group=self._gap_().RegularActionHomomorphism().Image()) @@ -568,11 +415,12 @@ def transitive_number(self, algorithm=None, recompute=False): sage: from sage.groups.galois_group import GaloisGroup_ab sage: Gtest = GaloisGroup_ab(field=None, generator_orders=(2,2,4)) - sage: Gtest.transitive_number() + sage: Gtest.transitive_number() # needs sage.libs.gap 2 """ return ZZ(self.permutation_group()._gap_().TransitiveIdentification()) + class GaloisGroup_cyc(GaloisGroup_ab): r""" Cyclic Galois groups @@ -614,17 +462,6 @@ def signature(self): """ return ZZ(1) if (self._field.degree() % 2) else ZZ(-1) -class GaloisSubgroup_perm(PermutationGroup_subgroup, _SubGaloisMixin): - """ - Subgroups of Galois groups (implemented as permutation groups), specified - by giving a list of generators. - - Unlike ambient Galois groups, where we use a lazy ``_gens`` attribute in order - to enable creation without determining a list of generators, - we require that generators for a subgroup be specified during initialization, - as specified in the ``__init__`` method of permutation subgroups. - """ - pass class GaloisSubgroup_ab(AbelianGroup_subgroup, _SubGaloisMixin): """ @@ -633,5 +470,4 @@ class GaloisSubgroup_ab(AbelianGroup_subgroup, _SubGaloisMixin): pass -GaloisGroup_perm.Subgroup = GaloisSubgroup_perm GaloisGroup_ab.Subgroup = GaloisSubgroup_ab diff --git a/src/sage/groups/galois_group_perm.py b/src/sage/groups/galois_group_perm.py new file mode 100644 index 00000000000..162e5174143 --- /dev/null +++ b/src/sage/groups/galois_group_perm.py @@ -0,0 +1,186 @@ +r""" +Galois groups of field extensions as permutation groups +""" + +from sage.groups.galois_group import _GaloisMixin, _SubGaloisMixin +from sage.groups.perm_gps.permgroup import PermutationGroup, PermutationGroup_generic, PermutationGroup_subgroup +from sage.misc.abstract_method import abstract_method +from sage.misc.lazy_attribute import lazy_attribute +from sage.sets.finite_enumerated_set import FiniteEnumeratedSet +from sage.structure.category_object import normalize_names + + +class GaloisGroup_perm(_GaloisMixin, PermutationGroup_generic): + r""" + The group of automorphisms of a Galois closure of a given field. + + INPUT: + + - ``field`` -- a field, separable over its base + + - ``names`` -- a string or tuple of length 1, giving a variable name for the splitting field + + - ``gc_numbering`` -- boolean, whether to express permutations in terms of the + roots of the defining polynomial of the splitting field (versus the defining polynomial + of the original extension). The default value may vary based on the type of field. + """ + @abstract_method + def transitive_number(self, algorithm=None, recompute=False): + """ + The transitive number (as in the GAP and Magma databases of transitive groups) + for the action on the roots of the defining polynomial of the top field. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^3 + 2*x + 2) # needs sage.rings.number_field + sage: G = K.galois_group() # needs sage.rings.number_field + sage: G.transitive_number() # needs sage.rings.number_field + 2 + """ + + @lazy_attribute + def _gens(self): + """ + The generators of this Galois group as permutations of the roots. It's important that this + be computed lazily, since it's often possible to compute other attributes (such as the order + or transitive number) more cheaply. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^5 - 2) # needs sage.rings.number_field + sage: G = K.galois_group(gc_numbering=False) # needs sage.rings.number_field + sage: G._gens # needs sage.rings.number_field + [(1,2,3,5), (1,4,3,2,5)] + """ + return NotImplemented + + def __init__(self, field, algorithm=None, names=None, gc_numbering=False): + r""" + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^3 + 2*x + 2) # needs sage.rings.number_field + sage: G = K.galois_group() # needs sage.rings.number_field + sage: TestSuite(G).run() # needs sage.rings.number_field + """ + self._field = field + self._default_algorithm = algorithm + self._base = field.base_field() + self._gc_numbering = gc_numbering + if names is None: + # add a c for Galois closure + names = field.variable_name() + 'c' + self._gc_names = normalize_names(1, names) + # We do only the parts of the initialization of PermutationGroup_generic + # that don't depend on _gens + from sage.categories.permutation_groups import PermutationGroups + category = PermutationGroups().FinitelyGenerated().Finite() + # Note that we DON'T call the __init__ method for PermutationGroup_generic + # Instead, the relevant attributes are computed lazily + super(PermutationGroup_generic, self).__init__(category=category) + + @lazy_attribute + def _deg(self): + r""" + The number of moved points in the permutation representation. + + This will be the degree of the original number field if `_gc_numbering`` + is ``False``, or the degree of the Galois closure otherwise. + + EXAMPLES:: + + sage: # needs sage.rings.number_field + sage: R. = ZZ[] + sage: K. = NumberField(x^5 - 2) + sage: G = K.galois_group(gc_numbering=False); G + Galois group 5T3 (5:4) with order 20 of x^5 - 2 + sage: G._deg + 5 + sage: G = K.galois_group(gc_numbering=True); G._deg + 20 + """ + if self._gc_numbering: + return self.order() + else: + try: + return self._field.degree() + except NotImplementedError: # relative number fields don't support degree + return self._field.relative_degree() + + @lazy_attribute + def _domain(self): + r""" + The integers labeling the roots on which this Galois group acts. + + EXAMPLES:: + + sage: # needs sage.rings.number_field + sage: R. = ZZ[] + sage: K. = NumberField(x^5 - 2) + sage: G = K.galois_group(gc_numbering=False); G + Galois group 5T3 (5:4) with order 20 of x^5 - 2 + sage: G._domain + {1, 2, 3, 4, 5} + sage: G = K.galois_group(gc_numbering=True); G._domain + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} + """ + return FiniteEnumeratedSet(range(1, self._deg+1)) + + @lazy_attribute + def _domain_to_gap(self): + r""" + Dictionary implementing the identity (used by PermutationGroup_generic). + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^5 - 2) # needs sage.rings.number_field + sage: G = K.galois_group(gc_numbering=False) # needs sage.rings.number_field + sage: G._domain_to_gap[5] # needs sage.rings.number_field + 5 + """ + return {key: i+1 for i, key in enumerate(self._domain)} + + @lazy_attribute + def _domain_from_gap(self): + r""" + Dictionary implementing the identity (used by PermutationGroup_generic). + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^5 - 2) # needs sage.rings.number_field + sage: G = K.galois_group(gc_numbering=True) # needs sage.rings.number_field + sage: G._domain_from_gap[20] # needs sage.rings.number_field + 20 + """ + return {i+1: key for i, key in enumerate(self._domain)} + + def ngens(self): + r""" + Number of generators of this Galois group + + EXAMPLES:: + + sage: QuadraticField(-23, 'a').galois_group().ngens() # needs sage.rings.number_field + 1 + """ + return len(self._gens) + + +class GaloisSubgroup_perm(PermutationGroup_subgroup, _SubGaloisMixin): + """ + Subgroups of Galois groups (implemented as permutation groups), specified + by giving a list of generators. + + Unlike ambient Galois groups, where we use a lazy ``_gens`` attribute in order + to enable creation without determining a list of generators, + we require that generators for a subgroup be specified during initialization, + as specified in the ``__init__`` method of permutation subgroups. + """ + pass + + +GaloisGroup_perm.Subgroup = GaloisSubgroup_perm diff --git a/src/sage/groups/generic.py b/src/sage/groups/generic.py index 69bffca1b9f..5e7f23af8d4 100644 --- a/src/sage/groups/generic.py +++ b/src/sage/groups/generic.py @@ -550,10 +550,10 @@ def discrete_log_rho(a, base, ord=None, operation='*', identity=None, inverse=No It also works with matrices:: - sage: A = matrix(GF(50021), [[10577, 23999, 28893], # needs sage.rings.finite_rings + sage: A = matrix(GF(50021), [[10577, 23999, 28893], # needs sage.modules sage.rings.finite_rings ....: [14601, 41019, 30188], ....: [3081, 736, 27092]]) - sage: discrete_log_rho(A^1234567, A) # needs sage.rings.finite_rings + sage: discrete_log_rho(A^1234567, A) # needs sage.modules sage.rings.finite_rings 1234567 Beware, the order must be prime:: @@ -1234,7 +1234,7 @@ def order_from_multiple(P, m, plist=None, factorization=None, check=True, sage: order_from_multiple(w, 230, operation='*') 23 - sage: # needs sage.rings.finite_rings + sage: # needs sage.modules sage.rings.finite_rings sage: F = GF(2^1279,'a') sage: n = F.cardinality() - 1 # Mersenne prime sage: order_from_multiple(F.random_element(), n, diff --git a/src/sage/groups/group.pyx b/src/sage/groups/group.pyx index cd2521e3478..ef639a0c367 100644 --- a/src/sage/groups/group.pyx +++ b/src/sage/groups/group.pyx @@ -35,9 +35,9 @@ def is_Group(x): EXAMPLES:: - sage: F. = FreeGroup() # needs sage.combinat + sage: F. = FreeGroup() # needs sage.groups sage: from sage.groups.group import is_Group - sage: is_Group(F) # needs sage.combinat + sage: is_Group(F) # needs sage.groups True sage: is_Group("a string") False @@ -140,7 +140,7 @@ cdef class Group(Parent): EXAMPLES:: - sage: SL(2, 7).is_commutative() # needs sage.modules sage.rings.finite_rings + sage: SL(2, 7).is_commutative() # needs sage.libs.gap sage.modules sage.rings.finite_rings False """ return self.is_abelian() @@ -186,6 +186,39 @@ cdef class Group(Parent): """ return self.order() != infinity + def is_trivial(self): + r""" + Return ``True`` if this group is the trivial group. + + A group is trivial, if it consists only of the identity + element. + + .. WARNING:: + + It is in principle undecidable whether a group is + trivial, for example, if the group is given by a finite + presentation. Thus, this method may not terminate. + + EXAMPLES:: + + sage: groups.presentation.Cyclic(1).is_trivial() + True + + sage: G. = FreeGroup('a, b') + sage: H = G / (a^2, b^3, a*b*~a*~b) + sage: H.is_trivial() + False + + A non-trivial presentation of the trivial group:: + + sage: F. = FreeGroup() + sage: J = F / ((~a)*b*a*(~b)^2, (~b)*a*b*(~a)^2) + sage: J.is_trivial() + True + """ + return self.order() == 1 + + def is_multiplicative(self): r""" Returns True if the group operation is given by \* (rather than @@ -212,8 +245,8 @@ cdef class Group(Parent): EXAMPLES:: - sage: G = AbelianGroup([2,3,4,5]) # needs sage.groups - sage: G.an_element() # needs sage.groups + sage: G = AbelianGroup([2,3,4,5]) # needs sage.modules + sage: G.an_element() # needs sage.modules f0*f1*f2*f3 """ return self.prod(self.gens()) diff --git a/src/sage/groups/matrix_gps/coxeter_group.py b/src/sage/groups/matrix_gps/coxeter_group.py index 6b865805298..d1c647f1286 100644 --- a/src/sage/groups/matrix_gps/coxeter_group.py +++ b/src/sage/groups/matrix_gps/coxeter_group.py @@ -79,7 +79,7 @@ class CoxeterMatrixGroup(UniqueRepresentation, FinitelyGeneratedMatrixGroup_gene We can create Coxeter groups from Coxeter matrices:: - sage: # needs sage.rings.number_field + sage: # needs sage.libs.gap sage.rings.number_field sage: W = CoxeterGroup([[1, 6, 3], [6, 1, 10], [3, 10, 1]]); W Coxeter group over Universal Cyclotomic Field with Coxeter matrix: [ 1 6 3] @@ -150,7 +150,7 @@ class CoxeterMatrixGroup(UniqueRepresentation, FinitelyGeneratedMatrixGroup_gene graphs, we can input a Coxeter graph. Following the standard convention, edges with no label (i.e. labelled by ``None``) are treated as 3:: - sage: # needs sage.rings.number_field + sage: # needs sage.libs.gap sage.rings.number_field sage: G = Graph([(0,3,None), (1,3,15), (2,3,7), (0,1,3)]) sage: W = CoxeterGroup(G); W Coxeter group over Universal Cyclotomic Field with Coxeter matrix: @@ -165,7 +165,7 @@ class CoxeterMatrixGroup(UniqueRepresentation, FinitelyGeneratedMatrixGroup_gene Because there currently is no class for `\ZZ \cup \{ \infty \}`, labels of `\infty` are given by `-1` in the Coxeter matrix:: - sage: # needs sage.rings.number_field + sage: # needs sage.libs.gap sage.rings.number_field sage: G = Graph([(0,1,None), (1,2,4), (0,2,oo)]) sage: W = CoxeterGroup(G) sage: W.coxeter_matrix() @@ -183,7 +183,7 @@ class CoxeterMatrixGroup(UniqueRepresentation, FinitelyGeneratedMatrixGroup_gene [2 3 1 3 3] [2 2 3 1 2] [2 2 3 2 1] - sage: W = CoxeterGroup(['H',3], implementation="reflection"); W # needs sage.rings.number_field + sage: W = CoxeterGroup(['H',3], implementation="reflection"); W # needs sage.libs.gap sage.rings.number_field Finite Coxeter group over Number Field in a with defining polynomial x^2 - 5 with a = 2.236067977499790? with Coxeter matrix: @@ -240,15 +240,15 @@ def __init__(self, coxeter_matrix, base_ring, index_set): EXAMPLES:: sage: W = CoxeterGroup([[1,3,2],[3,1,3],[2,3,1]]) - sage: TestSuite(W).run() # long time + sage: TestSuite(W).run() # long time - sage: # needs sage.rings.number_field + sage: # long time, needs sage.rings.number_field sage.symbolic sage: W = CoxeterGroup([[1,3,2],[3,1,4],[2,4,1]], base_ring=QQbar) - sage: TestSuite(W).run() # long time + sage: TestSuite(W).run() sage: W = CoxeterGroup([[1,3,2],[3,1,6],[2,6,1]]) - sage: TestSuite(W).run(max_runs=30) # long time + sage: TestSuite(W).run(max_runs=30) sage: W = CoxeterGroup([[1,3,2],[3,1,-1],[2,-1,1]]) - sage: TestSuite(W).run(max_runs=30) # long time + sage: TestSuite(W).run(max_runs=30) We check that :trac:`16630` is fixed:: @@ -340,7 +340,7 @@ def _repr_(self): EXAMPLES:: - sage: CoxeterGroup([[1,3,2],[3,1,4],[2,4,1]]) # needs sage.rings.number_field + sage: CoxeterGroup([[1,3,2],[3,1,4],[2,4,1]]) # needs sage.libs.gap sage.rings.number_field Finite Coxeter group over Number Field in a with defining polynomial x^2 - 2 with a = 1.414213562373095? with Coxeter matrix: [1 3 2] [3 1 4] @@ -383,8 +383,8 @@ def coxeter_matrix(self): sage: W.coxeter_matrix() [1 3] [3 1] - sage: W = CoxeterGroup(['H',3]) # needs sage.rings.number_field - sage: W.coxeter_matrix() + sage: W = CoxeterGroup(['H',3]) # needs sage.libs.gap sage.rings.number_field + sage: W.coxeter_matrix() # needs sage.libs.gap sage.rings.number_field [1 3 2] [3 1 5] [2 5 1] @@ -423,7 +423,7 @@ def is_finite(self): EXAMPLES:: - sage: # needs sage.rings.number_field + sage: # needs sage.libs.gap sage.rings.number_field sage: [l for l in range(2, 9) if ....: CoxeterGroup([[1,3,2],[3,1,l],[2,l,1]]).is_finite()] [2, 3, 4, 5] @@ -482,8 +482,8 @@ def order(self): sage: W = CoxeterGroup([[1,3],[3,1]]) sage: W.order() 6 - sage: W = CoxeterGroup([[1,-1],[-1,1]]) - sage: W.order() + sage: W = CoxeterGroup([[1,-1],[-1,1]]) # needs sage.libs.gap + sage: W.order() # needs sage.libs.gap +Infinity """ if self.is_finite(): @@ -593,7 +593,7 @@ def positive_roots(self): sage: W.positive_roots() ((1, 0, 0), (1, 1, 0), (0, 1, 0), (1, 1, 1), (0, 1, 1), (0, 0, 1)) - sage: # needs sage.rings.number_field + sage: # needs sage.libs.gap sage.rings.number_field sage: W = CoxeterGroup(['I',5], implementation='reflection') sage: W.positive_roots() ((1, 0), @@ -651,7 +651,7 @@ def roots(self): (0, -1, -1), (0, 0, -1)) - sage: # needs sage.rings.number_field + sage: # needs sage.libs.gap sage.rings.number_field sage: W = CoxeterGroup(['I',5], implementation='reflection') sage: len(W.roots()) 10 diff --git a/src/sage/groups/matrix_gps/finitely_generated.py b/src/sage/groups/matrix_gps/finitely_generated.py index c8098ad578a..ef6ba42a105 100644 --- a/src/sage/groups/matrix_gps/finitely_generated.py +++ b/src/sage/groups/matrix_gps/finitely_generated.py @@ -9,7 +9,7 @@ sage: F = GF(3) sage: gens = [matrix(F, 2, [1,0, -1,1]), matrix(F, 2, [1,1,0,1])] sage: G = MatrixGroup(gens) - sage: G.conjugacy_classes_representatives() + sage: G.conjugacy_classes_representatives() # needs sage.libs.gap ( [1 0] [0 2] [0 1] [2 0] [0 2] [0 1] [0 2] [0 1], [1 1], [2 1], [0 2], [1 2], [2 2], [1 0] @@ -161,6 +161,8 @@ def QuaternionMatrixGroupGF3(): is the product of `I` and `J`. :: sage: from sage.groups.matrix_gps.finitely_generated import QuaternionMatrixGroupGF3 + + sage: # needs sage.libs.gap sage: Q = QuaternionMatrixGroupGF3() sage: Q.order() 8 @@ -176,22 +178,23 @@ def QuaternionMatrixGroupGF3(): TESTS:: - sage: groups.matrix.QuaternionGF3() # needs sage.modules sage.rings.finite_rings + sage: groups.matrix.QuaternionGF3() Matrix group over Finite Field of size 3 with 2 generators ( [1 1] [2 1] [1 2], [1 1] ) + sage: # needs sage.groups sage: Q = QuaternionMatrixGroupGF3() sage: QP = Q.as_permutation_group() sage: QP.is_isomorphic(QuaternionGroup()) True - sage: H = DihedralGroup(4) # needs sage.groups - sage: H.order() # needs sage.groups + sage: H = DihedralGroup(4) + sage: H.order() 8 - sage: QP.is_abelian(), H.is_abelian() # needs sage.groups + sage: QP.is_abelian(), H.is_abelian() (False, False) - sage: QP.is_isomorphic(H) # needs sage.groups + sage: QP.is_isomorphic(H) False """ from sage.rings.finite_rings.finite_field_constructor import FiniteField @@ -340,6 +343,7 @@ class FinitelyGeneratedMatrixGroup_generic(MatrixGroup_generic): sage: MatrixGroup(m1, m2) == MatrixGroup(m2, m1) False + sage: # needs sage.libs.gap sage: G = GL(2, GF(3)) sage: H = G.as_matrix_group() sage: H == G, G == H @@ -415,6 +419,7 @@ def gen(self, i): EXAMPLES:: + sage: # needs sage.libs.gap sage: H = GL(2, GF(3)) sage: h1, h2 = H([[1,0], [2,1]]), H([[1,1], [0,1]]) sage: G = H.subgroup([h1, h2]) @@ -436,6 +441,7 @@ def ngens(self): EXAMPLES:: + sage: # needs sage.libs.gap sage: H = GL(2, GF(3)) sage: h1, h2 = H([[1,0], [2,1]]), H([[1,1], [0,1]]) sage: G = H.subgroup([h1, h2]) diff --git a/src/sage/groups/matrix_gps/finitely_generated_gap.py b/src/sage/groups/matrix_gps/finitely_generated_gap.py index af676af3673..add6875a802 100644 --- a/src/sage/groups/matrix_gps/finitely_generated_gap.py +++ b/src/sage/groups/matrix_gps/finitely_generated_gap.py @@ -120,16 +120,14 @@ def as_permutation_group(self, algorithm=None, seed=None): 21499084800 sage: P = G.as_permutation_group() sage: Psmaller = G.as_permutation_group(algorithm="smaller", seed=6) - sage: P == Psmaller - False sage: P.cardinality() 21499084800 sage: P.degree() 144 sage: Psmaller.cardinality() 21499084800 - sage: Psmaller.degree() # random - 80 + sage: Psmaller.degree() <= P.degree() + True .. NOTE:: diff --git a/src/sage/groups/matrix_gps/linear.py b/src/sage/groups/matrix_gps/linear.py index 65a8c13645c..822bd576afd 100644 --- a/src/sage/groups/matrix_gps/linear.py +++ b/src/sage/groups/matrix_gps/linear.py @@ -16,6 +16,8 @@ Special Linear Group of degree 2 over Integer Ring sage: G = SL(2, GF(3)); G Special Linear Group of degree 2 over Finite Field of size 3 + + sage: # needs sage.libs.gap sage: G.is_finite() True sage: G.conjugacy_classes_representatives() @@ -94,32 +96,38 @@ def GL(n, R, var='a'): EXAMPLES:: sage: G = GL(6, GF(5)) - sage: G.order() - 11064475422000000000000000 sage: G.base_ring() Finite Field of size 5 sage: G.category() Category of finite groups + + sage: # needs sage.libs.gap + sage: G.order() + 11064475422000000000000000 sage: TestSuite(G).run() sage: G = GL(6, QQ) sage: G.category() Category of infinite groups + + sage: # needs sage.libs.gap sage: TestSuite(G).run() Here is the Cayley graph of (relatively small) finite General Linear Group:: + sage: # needs sage.graphs sage.libs.gap sage: g = GL(2,3) - sage: d = g.cayley_graph(); d # needs sage.graphs + sage: d = g.cayley_graph(); d Digraph on 48 vertices - sage: d.plot(color_by_label=True, vertex_size=0.03, # long time # needs sage.graphs sage.plot + sage: d.plot(color_by_label=True, vertex_size=0.03, # long time # needs sage.plot ....: vertex_labels=False) Graphics object consisting of 144 graphics primitives - sage: d.plot3d(color_by_label=True) # long time # needs sage.graphs sage.plot + sage: d.plot3d(color_by_label=True) # long time # needs sage.plot Graphics3d Object :: + sage: # needs sage.libs.gap sage: F = GF(3); MS = MatrixSpace(F, 2, 2) sage: gens = [MS([[2,0], [0,1]]), MS([[2,1], [2,0]])] sage: G = MatrixGroup(gens) @@ -213,10 +221,13 @@ def SL(n, R, var='a'): Special Linear Group of degree 15 over Finite Field of size 7 sage: G.category() Category of finite groups + + sage: # needs sage.libs.gap sage: G.order() 1956712595698146962015219062429586341124018007182049478916067369638713066737882363393519966343657677430907011270206265834819092046250232049187967718149558134226774650845658791865745408000000 sage: len(G.gens()) 2 + sage: G = SL(2, ZZ); G Special Linear Group of degree 2 over Integer Ring sage: G.category() @@ -231,6 +242,8 @@ def SL(n, R, var='a'): sage: G = SL(3, ZZ); G Special Linear Group of degree 3 over Integer Ring + + sage: # needs sage.libs.gap sage: G.gens() ( [0 1 0] [ 0 1 0] [1 1 0] diff --git a/src/sage/groups/matrix_gps/matrix_group.py b/src/sage/groups/matrix_gps/matrix_group.py index c0c22456320..192c4e4d4cf 100644 --- a/src/sage/groups/matrix_gps/matrix_group.py +++ b/src/sage/groups/matrix_gps/matrix_group.py @@ -5,10 +5,10 @@ Loading, saving, ... works:: + sage: # needs sage.libs.gap sage: G = GL(2,5); G General Linear Group of degree 2 over Finite Field of size 5 sage: TestSuite(G).run() - sage: g = G.1; g [4 1] [4 0] @@ -159,7 +159,7 @@ def as_matrix_group(self): EXAMPLES:: sage: G = SU(4, GF(5)) # needs sage.rings.finite_rings - sage: G.as_matrix_group() # needs sage.rings.finite_rings + sage: G.as_matrix_group() # needs sage.libs.gap sage.rings.finite_rings Matrix group over Finite Field in a of size 5^2 with 2 generators ( [ a 0 0 0] [ 1 0 4*a + 3 0] [ 0 2*a + 3 0 0] [ 1 0 0 0] @@ -167,7 +167,8 @@ def as_matrix_group(self): [ 0 0 0 3*a], [ 0 3*a + 1 0 0] ) - sage: G = GO(3,GF(5)) + sage: # needs sage.libs.gap + sage: G = GO(3, GF(5)) sage: G.as_matrix_group() Matrix group over Finite Field of size 5 with 2 generators ( [2 0 0] [0 1 0] @@ -316,8 +317,8 @@ def _repr_option(self, key): EXAMPLES:: - sage: SO3 = groups.matrix.SO(3, QQ) # needs sage.groups sage.modules - sage: SO3._repr_option('element_ascii_art') # needs sage.groups sage.modules + sage: SO3 = groups.matrix.SO(3, QQ) + sage: SO3._repr_option('element_ascii_art') True """ if key == 'element_ascii_art': @@ -479,6 +480,7 @@ def __richcmp__(self, other, op): EXAMPLES:: + sage: # needs sage.libs.gap sage: G = GL(2,3) sage: H = MatrixGroup(G.gens()) sage: H == G @@ -486,6 +488,7 @@ def __richcmp__(self, other, op): sage: G == H True + sage: # needs sage.libs.gap sage: MS = MatrixSpace(QQ, 2, 2) sage: G = MatrixGroup([MS(1), MS([1,2,3,4])]) sage: G == G @@ -542,3 +545,34 @@ def __richcmp__(self, other, op): if lx != rx: return richcmp_not_equal(lx, rx, op) return rich_to_bool(op, 0) + + def is_trivial(self): + r""" + Return ``True`` if this group is the trivial group. + + A group is trivial, if it consists only of the identity + element, that is, if all its generators are the identity. + + EXAMPLES:: + + sage: MatrixGroup([identity_matrix(3)]).is_trivial() + True + sage: SL(2, ZZ).is_trivial() + False + sage: CoxeterGroup(['B',3], implementation="matrix").is_trivial() + False + + TESTS:: + + sage: CoxeterGroup(['A',0], implementation="matrix").is_trivial() + True + sage: MatrixGroup([matrix(SR, [[1,x], [0,1]])]).is_trivial() + False + sage: G = MatrixGroup([identity_matrix(3), identity_matrix(3)]) + sage: G.ngens() + 2 + sage: G.is_trivial() + True + + """ + return all(g.is_one() for g in self.gens()) diff --git a/src/sage/groups/matrix_gps/named_group.py b/src/sage/groups/matrix_gps/named_group.py index 4568c43d326..98841d9f0af 100644 --- a/src/sage/groups/matrix_gps/named_group.py +++ b/src/sage/groups/matrix_gps/named_group.py @@ -10,6 +10,8 @@ Special Linear Group of degree 2 over Integer Ring sage: G = SL(2, GF(3)); G Special Linear Group of degree 2 over Finite Field of size 3 + + sage: # needs sage.libs.gap sage: G.is_finite() True sage: G.conjugacy_classes_representatives() @@ -288,12 +290,13 @@ def __richcmp__(self, other, op): EXAMPLES:: + sage: # needs sage.libs.gap sage: G = GL(2,3) sage: G == MatrixGroup(G.gens()) True - sage: # needs sage.rings.finite_rings - sage: G = groups.matrix.GL(4,2) # needs sage.modules + sage: # needs sage.libs.gap sage.rings.finite_rings + sage: G = groups.matrix.GL(4,2) sage: H = MatrixGroup(G.gens()) sage: G == H True diff --git a/src/sage/groups/matrix_gps/orthogonal.py b/src/sage/groups/matrix_gps/orthogonal.py index 9e90d3ac0dc..8d622f7b14f 100644 --- a/src/sage/groups/matrix_gps/orthogonal.py +++ b/src/sage/groups/matrix_gps/orthogonal.py @@ -39,6 +39,8 @@ sage: G = SO(4, GF(7), 1); G Special Orthogonal Group of degree 4 and form parameter 1 over Finite Field of size 7 + + sage: # needs sage.libs.gap sage: G.random_element() # random [4 3 5 2] [6 6 4 0] @@ -154,7 +156,7 @@ def _OG(n, R, special, e=0, var='a', invariant_form=None): Check that :trac:`26028` is fixed:: - sage: GO(3,25).order() # indirect doctest # needs sage.rings.finite_rings + sage: GO(3,25).order() # indirect doctest # needs sage.libs.gap sage.rings.finite_rings 31200 Check that :trac:`28054` is fixed:: @@ -270,6 +272,8 @@ def GO(n, R, e=0, var='a', invariant_form=None): sage: GO(3, GF(7)) General Orthogonal Group of degree 3 over Finite Field of size 7 + + sage: # needs sage.libs.gap sage: GO(3, GF(7)).order() 672 sage: GO(3, GF(7)).gens() @@ -323,6 +327,7 @@ def GO(n, R, e=0, var='a', invariant_form=None): TESTS:: + sage: # needs sage.libs.gap sage: TestSuite(GO3).run() sage: groups.matrix.GO(2, 3, e=-1) General Orthogonal Group of degree 2 and form parameter -1 over Finite Field of size 3 @@ -378,6 +383,7 @@ def SO(n, R, e=None, var='a', invariant_form=None): sage: G = SO(3,GF(5)); G Special Orthogonal Group of degree 3 over Finite Field of size 5 + sage: # needs sage.libs.gap sage: G = SO(3,GF(5)) sage: G.gens() ( @@ -385,7 +391,6 @@ def SO(n, R, e=None, var='a', invariant_form=None): [0 3 0] [0 2 0] [4 0 0] [0 0 1], [0 3 1], [2 0 4] ) - sage: G = SO(3,GF(5)) sage: G.as_matrix_group() Matrix group over Finite Field of size 5 with 3 generators ( [2 0 0] [3 2 3] [1 4 4] @@ -483,6 +488,7 @@ def invariant_bilinear_form(self): EXAMPLES:: + sage: # needs sage.libs.gap sage: GO(2,3,+1).invariant_bilinear_form() [0 1] [1 0] @@ -503,7 +509,7 @@ def invariant_bilinear_form(self): TESTS:: - sage: GO3m.invariant_form() + sage: GO3m.invariant_form() # needs sage.libs.gap [1 0 0] [0 2 0] [0 0 3] diff --git a/src/sage/groups/matrix_gps/symplectic.py b/src/sage/groups/matrix_gps/symplectic.py index e0f3324b645..ec61d8a0634 100644 --- a/src/sage/groups/matrix_gps/symplectic.py +++ b/src/sage/groups/matrix_gps/symplectic.py @@ -5,6 +5,8 @@ sage: G = Sp(4, GF(7)); G Symplectic Group of degree 4 over Finite Field of size 7 + + sage: # needs sage.libs.gap sage: g = prod(G.gens()); g [3 0 3 0] [1 0 0 0] @@ -138,6 +140,7 @@ def Sp(n, R, var='a', invariant_form=None): sage: groups.matrix.Sp(2, 3) # needs sage.modules sage.rings.finite_rings Symplectic Group of degree 2 over Finite Field of size 3 + sage: # needs sage.libs.gap sage: G = Sp(4,5) sage: TestSuite(G).run() """ diff --git a/src/sage/groups/matrix_gps/unitary.py b/src/sage/groups/matrix_gps/unitary.py index ed6221cb61d..379d8276571 100644 --- a/src/sage/groups/matrix_gps/unitary.py +++ b/src/sage/groups/matrix_gps/unitary.py @@ -8,11 +8,11 @@ sage: # needs sage.rings.finite_rings sage: G = SU(3,5) - sage: G.order() + sage: G.order() # needs sage.libs.gap 378000 sage: G Special Unitary Group of degree 3 over Finite Field in a of size 5^2 - sage: G.gens() + sage: G.gens() # needs sage.libs.gap ( [ a 0 0] [4*a 4 1] [ 0 2*a + 2 0] [ 4 4 0] @@ -100,7 +100,7 @@ def _UG(n, R, special, var='a', invariant_form=None): TESTS:: - sage: GU(3,25).order() # indirect doctest # needs sage.rings.finite_rings + sage: GU(3,25).order() # indirect doctest # needs sage.libs.gap sage.rings.finite_rings 3961191000000 """ prefix = 'General' @@ -195,7 +195,7 @@ def GU(n, R, var='a', invariant_form=None): sage: G = GU(3, 7); G # needs sage.rings.finite_rings General Unitary Group of degree 3 over Finite Field in a of size 7^2 - sage: G.gens() # needs sage.rings.finite_rings + sage: G.gens() # needs sage.libs.gap sage.rings.finite_rings ( [ a 0 0] [6*a 6 1] [ 0 1 0] [ 6 6 0] @@ -207,7 +207,7 @@ def GU(n, R, var='a', invariant_form=None): sage: G = GU(3, 5, var='beta') # needs sage.rings.finite_rings sage: G.base_ring() # needs sage.rings.finite_rings Finite Field in beta of size 5^2 - sage: G.gens() # needs sage.rings.finite_rings + sage: G.gens() # needs sage.libs.gap sage.rings.finite_rings ( [ beta 0 0] [4*beta 4 1] [ 0 1 0] [ 4 4 0] diff --git a/src/sage/groups/pari_group.py b/src/sage/groups/pari_group.py index bbcb1c0a6c9..4d1ca0bff22 100644 --- a/src/sage/groups/pari_group.py +++ b/src/sage/groups/pari_group.py @@ -6,8 +6,10 @@ """ from sage.libs.pari import pari +from sage.misc.lazy_import import lazy_import from sage.rings.integer import Integer -from sage.groups.perm_gps.permgroup_named import TransitiveGroup + +lazy_import('sage.groups.perm_gps.permgroup_named', 'TransitiveGroup') class PariGroup(): diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index 8e2e6d82c10..2d73c8e7cf2 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -344,6 +344,7 @@ def PermutationGroup(gens=None, *args, **kwds): Note that we provide generators for the acting group. The permutation group we construct is its homomorphic image:: + sage: # needs sage.combinat sage: a = lambda g, x: vector(g*x, immutable=True) sage: X = [vector(x, immutable=True) for x in GF(3)^2] sage: G = SL(2,3); G.gens() @@ -1365,6 +1366,38 @@ def ngens(self): """ return len(self.gens()) + def is_trivial(self): + r""" + Return ``True`` if this group is the trivial group. + + A permutation group is trivial, if it consists only of the + identity element, that is, if it has no generators or only + trivial generators. + + EXAMPLES:: + + sage: G = PermutationGroup([], domain=["a", "b", "c"]) + sage: G.is_trivial() + True + sage: SymmetricGroup(0).is_trivial() + True + sage: SymmetricGroup(1).is_trivial() + True + sage: SymmetricGroup(2).is_trivial() + False + sage: DihedralGroup(1).is_trivial() + False + + TESTS:: + + sage: G = PermutationGroup([[], []], canonicalize=False) + sage: G.ngens() + 2 + sage: G.is_trivial() + True + """ + return all(g.is_one() for g in self._gens) + @cached_method def one(self): """ @@ -1547,6 +1580,7 @@ def representative_action(self,x,y): TESTS:: + sage: # needs sage.graphs sage: g = graphs.PetersenGraph() sage: g.relabel(list("abcdefghik")) sage: g.vertices(sort=True) @@ -2337,13 +2371,14 @@ def socle(self): EXAMPLES:: sage: G = SymmetricGroup(4) - sage: G.socle() + sage: s = G.socle(); s Subgroup generated by [(1,2)(3,4), (1,4)(2,3)] of (Symmetric group of order 4! as a permutation group) - sage: G.socle().socle() - Subgroup generated by [(1,2)(3,4), (1,4)(2,3)] of - (Subgroup generated by [(1,2)(3,4), (1,4)(2,3)] - of (Symmetric group of order 4! as a permutation group)) + + The socle of the socle is, essentially, the socle:: + + sage: s.socle() == s.subgroup(s.gens()) + True """ return self.subgroup(gap_group=self._gap_().Socle()) @@ -3706,6 +3741,7 @@ def has_regular_subgroup(self, return_group=False): But the automorphism group of Petersen's graph does not:: + sage: # needs sage.graphs sage: G = graphs.PetersenGraph().automorphism_group() sage: G.has_regular_subgroup() False @@ -3767,6 +3803,7 @@ def blocks_all(self, representatives=True): Picking an interesting group:: + sage: # needs sage.graphs sage: g = graphs.DodecahedralGraph() sage: g.is_vertex_transitive() True @@ -3776,12 +3813,12 @@ def blocks_all(self, representatives=True): Computing its blocks representatives:: - sage: ag.blocks_all() + sage: ag.blocks_all() # needs sage.graphs [[0, 15]] Now the full block:: - sage: sorted(ag.blocks_all(representatives = False)[0]) + sage: sorted(ag.blocks_all(representatives=False)[0]) # needs sage.graphs [[0, 15], [1, 16], [2, 12], [3, 13], [4, 9], [5, 10], [6, 11], [7, 18], [8, 17], [14, 19]] @@ -3977,6 +4014,7 @@ def minimal_generating_set(self): EXAMPLES:: + sage: # needs sage.graphs sage: g = graphs.CompleteGraph(4) sage: g.relabel(['a','b','c','d']) sage: mgs = g.automorphism_group().minimal_generating_set(); len(mgs) diff --git a/src/sage/interfaces/gap.py b/src/sage/interfaces/gap.py index 0a8087a20d5..26afa44edda 100644 --- a/src/sage/interfaces/gap.py +++ b/src/sage/interfaces/gap.py @@ -177,7 +177,7 @@ gap(...), x.[tab], and docs, e.g., gap.function? and x.function? """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2005 William Stein # # Distributed under the terms of the GNU General Public License (GPL) @@ -189,13 +189,13 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from .expect import Expect, ExpectElement, FunctionElement, ExpectFunction from .gap_workspace import gap_workspace_file, prepare_workspace_dir from sage.cpython.string import bytes_to_str -from sage.env import SAGE_EXTCODE, SAGE_GAP_COMMAND, SAGE_GAP_MEMORY +from sage.env import SAGE_EXTCODE, SAGE_GAP_COMMAND, SAGE_GAP_MEMORY, GAP_ROOT_PATHS from sage.misc.misc import is_in_string from sage.misc.cachefunc import cached_method from sage.misc.instancedoc import instancedoc @@ -217,7 +217,17 @@ first_try = True -gap_cmd = SAGE_GAP_COMMAND +if SAGE_GAP_COMMAND is None: + # Passing -A allows us to use a minimal GAP installation without + # producing errors at start-up. The files sage.g and sage.gaprc are + # used to load any additional packages that may be available. + gap_cmd = f'gap -A -l "{GAP_ROOT_PATHS}"' + if SAGE_GAP_MEMORY is not None: + gap_cmd += " -s " + SAGE_GAP_MEMORY + " -o " + SAGE_GAP_MEMORY +else: + gap_cmd = SAGE_GAP_COMMAND + + if platform.processor() == 'ia64' and os.path.exists('/usr/bin/prctl'): # suppress unaligned access to 0x..., ip=0x... warnings gap_cmd = 'prctl --unaligned=silent ' + gap_cmd @@ -234,7 +244,7 @@ def gap_command(use_workspace_cache=True, local=True): return gap_cmd, True -############ Classes with methods for both the GAP3 and GAP4 interface +# ########### Classes with methods for both the GAP3 and GAP4 interface class Gap_generic(ExtraTabCompletion, Expect): r""" @@ -277,10 +287,10 @@ def _synchronize(self, timeout=0.5, cmd='%s;'): E = self._expect from sage.misc.prandom import randrange rnd = randrange(2147483647) - cmd = str(rnd)+';' + cmd = str(rnd) + ';' try: E.sendline(cmd) - E.expect(r'@[nf][@J\s>]*'+str(rnd), timeout=timeout) + E.expect(r'@[nf][@J\s>]*' + str(rnd), timeout=timeout) E.send(' ') E.expect('@i', timeout=timeout) except pexpect.TIMEOUT: @@ -443,7 +453,7 @@ def load_package(self, pkg, verbose=False): print("Loading GAP package {}".format(pkg)) x = self.eval('LoadPackage("{}")'.format(pkg)) if x == 'fail': - raise RuntimeError("Error loading Gap package "+str(pkg)+". " + + raise RuntimeError("Error loading Gap package " + str(pkg) + ". " + "You may want to install gap_packages SPKG.") def eval(self, x, newlines=False, strip=True, split_lines=True, **kwds): @@ -497,10 +507,10 @@ def eval(self, x, newlines=False, strip=True, split_lines=True, **kwds): ' -\n\\\\-' """ # '" - #We remove all of the comments: On each line, we try - #to find a pound sign. If we find it, we check to see if - #it is occurring in a string. If it is not in a string, we - #strip off the comment. + # We remove all of the comments: On each line, we try + # to find a pound sign. If we find it, we check to see if + # it is occurring in a string. If it is not in a string, we + # strip off the comment. if not split_lines: input_line = str(x) else: @@ -510,17 +520,17 @@ def eval(self, x, newlines=False, strip=True, split_lines=True, **kwds): while pound_position != -1: if not is_in_string(line, pound_position): line = line[:pound_position] - pound_position = line.find('#',pound_position+1) - input_line += " "+line + pound_position = line.find('#', pound_position + 1) + input_line += " " + line if not input_line.endswith(';'): input_line += ';' result = Expect.eval(self, input_line, **kwds) if not newlines: - result = result.replace("\\\n","") + result = result.replace("\\\n", "") return result.rstrip() def _execute_line(self, line, wait_for_prompt=True, expect_eof=False): - if self._expect is None: # interface is down + if self._expect is None: # interface is down self._start() E = self._expect try: @@ -530,9 +540,9 @@ def _execute_line(self, line, wait_for_prompt=True, expect_eof=False): except OSError: raise RuntimeError("Error evaluating %s in %s" % (line, self)) if not wait_for_prompt: - return (b'',b'') + return (b'', b'') if len(line) == 0: - return (b'',b'') + return (b'', b'') try: terminal_echo = [] # to be discarded normal_outputs = [] # GAP stdout @@ -546,43 +556,43 @@ def _execute_line(self, line, wait_for_prompt=True, expect_eof=False): warnings.warn( "possibly wrong version of GAP package " "interface. Crossing fingers and continuing.") - elif x == 1: #@@ + elif x == 1: # @@ current_outputs.append(b'@') - elif x == 2: #special char + elif x == 2: # special char c = ord(E.after[1:2]) - ord(b'A') + 1 s = bytes([c]) current_outputs.append(s) - elif x == 3: # garbage collection info, ignore + elif x == 3: # garbage collection info, ignore pass - elif x == 4: # @e -- break loop + elif x == 4: # @e -- break loop E.sendline("quit;") - elif x == 5: # @c completion, doesn't seem to happen when -p is in use + elif x == 5: # @c completion, doesn't seem to happen when -p is in use warnings.warn("I didn't think GAP could do this") - elif x == 6: # @f GAP error message + elif x == 6: # @f GAP error message current_outputs = error_outputs - elif x == 7: # @h help text, but this stopped happening with new help + elif x == 7: # @h help text, but this stopped happening with new help warnings.warn("I didn't think GAP could do this") - elif x == 8: # @i awaiting normal input + elif x == 8: # @i awaiting normal input break - elif x == 9: # @m finished running a child + elif x == 9: # @m finished running a child pass # there is no need to do anything - elif x == 10: #@n normal output line + elif x == 10: # @n normal output line current_outputs = normal_outputs - elif x == 11: #@r echoing input + elif x == 11: # @r echoing input current_outputs = terminal_echo - elif x == 12: #@sN shouldn't happen + elif x == 12: # @sN shouldn't happen warnings.warn("this should never happen") - elif x == 13: #@w GAP is trying to send a Window command + elif x == 13: # @w GAP is trying to send a Window command warnings.warn("this should never happen") - elif x == 14: #@x seems to be safely ignorable + elif x == 14: # @x seems to be safely ignorable pass - elif x == 15:#@z GAP starting a subprocess + elif x == 15: # @z GAP starting a subprocess pass # there is no need to do anything except pexpect.EOF: if not expect_eof: - raise RuntimeError("Unexpected EOF from %s executing %s" % (self,line)) + raise RuntimeError("Unexpected EOF from %s executing %s" % (self, line)) except IOError: - raise RuntimeError("IO Error from %s executing %s" % (self,line)) + raise RuntimeError("IO Error from %s executing %s" % (self, line)) return (b"".join(normal_outputs), b"".join(error_outputs)) def _keyboard_interrupt(self): @@ -687,8 +697,8 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if error += "\nRunning gap_reset_workspace()..." self.quit() gap_reset_workspace() - error = error.replace('\r','') - raise RuntimeError("%s produced error output\n%s\n executing %s" % (self, error,line)) + error = error.replace('\r', '') + raise RuntimeError("%s produced error output\n%s\n executing %s" % (self, error, line)) if not normal: return '' @@ -762,7 +772,7 @@ def _contains(self, v1, v2): sage: 2 in gap('Integers') True """ - return self.eval('%s in %s' % (v1,v2)) == "true" + return self.eval('%s in %s' % (v1, v2)) == "true" def _true_symbol(self): """ @@ -858,20 +868,21 @@ def function_call(self, function, args=None, kwds=None): args, kwds = self._convert_args_kwds(args, kwds) self._check_valid_function_name(function) - #Here we have to do some magic because not all GAP - #functions return a value. If you try to store their - #results to a variable, then GAP will complain. Thus, before - #we evaluate the function, we make it so that the marker string - #is in the 'last' variable in GAP. If the function returns a - #value, then that value will be in 'last', otherwise it will - #be the marker. + # Here we have to do some magic because not all GAP + # functions return a value. If you try to store their + # results to a variable, then GAP will complain. Thus, before + # we evaluate the function, we make it so that the marker string + # is in the 'last' variable in GAP. If the function returns a + # value, then that value will be in 'last', otherwise it will + # be the marker. marker = '__SAGE_LAST__:="__SAGE_LAST__";;' cmd = "%s(%s);;" % (function, ",".join([s.name() for s in args] + - ['%s=%s' % (key,value.name()) for key, value in kwds.items()])) + [f'{key}={value.name()}' + for key, value in kwds.items()])) if len(marker) + len(cmd) <= self._eval_using_file_cutoff: # We combine the two commands so we only run eval() once and the # only output would be from the second command - res = self.eval(marker+cmd) + res = self.eval(marker + cmd) else: self.eval(marker) res = self.eval(cmd) @@ -1035,7 +1046,8 @@ def _matrix_(self, R): from sage.matrix.matrix_space import MatrixSpace M = MatrixSpace(R, n, m) - entries = [[R(self[r,c]) for c in range(1,m+1)] for r in range(1,n+1)] + entries = [[R(self[r, c]) for c in range(1, m + 1)] + for r in range(1, n + 1)] return M(entries) @@ -1089,7 +1101,7 @@ def __init__(self, max_workspace_size=None, self.__seq = 0 self._seed = seed - def set_seed(self,seed=None): + def set_seed(self, seed=None): """ Set the seed for gap interpreter. @@ -1194,8 +1206,8 @@ def _start(self): self.save_workspace() # Now, as self._expect exists, we can compile some useful pattern: self._compiled_full_pattern = self._expect.compile_pattern_list([ - r'@p\d+\.','@@','@[A-Z]',r'@[123456!"#$%&][^+]*\+', - '@e','@c','@f','@h','@i','@m','@n','@r',r'@s\d',r'@w.*\+','@x','@z']) + r'@p\d+\.', '@@', '@[A-Z]', r'@[123456!"#$%&][^+]*\+', + '@e', '@c', '@f', '@h', '@i', '@m', '@n', '@r', r'@s\d', r'@w.*\+', '@x', '@z']) # read everything up to the first "ready" prompt self._expect.expect("@i") @@ -1236,10 +1248,9 @@ def cputime(self, t=None): """ if t is not None: return self.cputime() - t - else: - self.eval('_r_ := Runtimes();') - r = sum(eval(self.eval('[_r_.user_time, _r_.system_time, _r_.user_time_children, _r_.system_time_children]'))) - return r/1000.0 + self.eval('_r_ := Runtimes();') + r = sum(eval(self.eval('[_r_.user_time, _r_.system_time, _r_.user_time_children, _r_.system_time_children]'))) + return r / 1000.0 def save_workspace(self): r""" @@ -1339,7 +1350,7 @@ def set(self, var, value): sage: gap.get('x') '2' """ - cmd = ('%s:=%s;;' % (var, value)).replace('\n','') + cmd = ('%s:=%s;;' % (var, value)).replace('\n', '') self._eval_line(cmd, allow_use_file=True) def get(self, var, use_file=False): @@ -1356,10 +1367,10 @@ def get(self, var, use_file=False): tmp = self._local_tmpfile() if os.path.exists(tmp): os.unlink(tmp) - self.eval('PrintTo("%s", %s);' % (tmp,var), strip=False) + self.eval('PrintTo("%s", %s);' % (tmp, var), strip=False) with open(tmp) as f: r = f.read() - r = r.strip().replace("\\\n","") + r = r.strip().replace("\\\n", "") os.unlink(tmp) return r else: @@ -1470,7 +1481,7 @@ def _tab_completion(self): True """ names = eval(self.eval('NamesSystemGVars()')) + \ - eval(self.eval('NamesUserGVars()')) + eval(self.eval('NamesUserGVars()')) return [n for n in names if n[0] in string.ascii_letters] @@ -1498,20 +1509,21 @@ def gap_reset_workspace(max_workspace_size=None, verbose=False): We temporarily need to change the worksheet filename, and to set ``first_try=True`` to ensure that the new workspace is created:: + sage: # long time sage: ORIGINAL_WORKSPACE = sage.interfaces.gap.WORKSPACE sage: saved_first_try = sage.interfaces.gap.first_try sage: sage.interfaces.gap.first_try = True sage: sage.interfaces.gap.WORKSPACE = tmp_filename() sage: from multiprocessing import Process sage: import time - sage: gap = Gap() # long time (reset GAP session) + sage: gap = Gap() # reset GAP session sage: P = [Process(target=gap, args=("14242",)) for i in range(4)] - sage: for p in P: # long time, indirect doctest + sage: for p in P: # indirect doctest ....: p.start() ....: time.sleep(float(0.2)) - sage: for p in P: # long time + sage: for p in P: ....: p.join() - sage: os.unlink(sage.interfaces.gap.WORKSPACE) # long time + sage: os.unlink(sage.interfaces.gap.WORKSPACE) sage: sage.interfaces.gap.WORKSPACE = ORIGINAL_WORKSPACE sage: sage.interfaces.gap.first_try = saved_first_try """ @@ -1572,8 +1584,8 @@ def _latex_(self): P = self._check_valid() try: s = P.eval('LaTeXObj(%s)' % self.name()) - s = s.replace('\\\\','\\').replace('"','') - s = s.replace('%\\n',' ') + s = s.replace('\\\\', '\\').replace('"', '') + s = s.replace('%\\n', ' ') return s except RuntimeError: return str(self) @@ -1581,7 +1593,7 @@ def _latex_(self): @cached_method def _tab_completion(self): """ - Return additional tab completion entries + Return additional tab completion entries. OUTPUT: @@ -1595,10 +1607,11 @@ def _tab_completion(self): """ P = self.parent() v = P.eval(r'\$SAGE.OperationsAdmittingFirstArgument(%s)' % self.name()) - v = v.replace('Tester(','').replace('Setter(','').replace(')','').replace('\n', '') + v = v.replace('Tester(', '').replace('Setter(', '').replace(')', '').replace('\n', '') v = v.split(',') - v = [ oper.split('"')[1] for oper in v ] - v = [ oper for oper in v if all(ch in string.ascii_letters for ch in oper) ] + v = (oper.split('"')[1] for oper in v) + v = [oper for oper in v + if all(ch in string.ascii_letters for ch in oper)] return sorted(set(v)) @@ -1708,13 +1721,13 @@ def gfq_gap_to_sage(x, F): return F(0) i1 = s.index("(") i2 = s.index(")") - q = eval(s[i1+1:i2].replace('^','**')) + q = eval(s[i1 + 1:i2].replace('^', '**')) if not F.cardinality().is_power_of(q): raise ValueError('%r has no subfield of size %r' % (F, q)) if s.find(')^') == -1: e = 1 else: - e = int(s[i2+2:]) + e = int(s[i2 + 2:]) if F.degree() == 1: g = F(gap.eval('Int(Z(%s))' % q)) elif F.is_conway(): @@ -1724,6 +1737,7 @@ def gfq_gap_to_sage(x, F): raise ValueError('%r is not prime or defined by a Conway polynomial' % F) return g**e + def intmod_gap_to_sage(x): r""" INPUT: @@ -1827,5 +1841,5 @@ def gap_console(): if not get_display_manager().is_in_terminal(): raise RuntimeError('Can use the console only in the terminal. Try %%gap magics instead.') cmd, _ = gap_command(use_workspace_cache=False) - cmd += ' ' + os.path.join(SAGE_EXTCODE,'gap','console.g') + cmd += ' ' + os.path.join(SAGE_EXTCODE, 'gap', 'console.g') os.system(cmd) diff --git a/src/sage/interfaces/gap_workspace.py b/src/sage/interfaces/gap_workspace.py index c104664143a..a2c97260077 100644 --- a/src/sage/interfaces/gap_workspace.py +++ b/src/sage/interfaces/gap_workspace.py @@ -17,7 +17,7 @@ import time import hashlib import subprocess -from sage.env import DOT_SAGE, HOSTNAME, GAP_LIB_DIR, GAP_SHARE_DIR +from sage.env import DOT_SAGE, HOSTNAME, GAP_ROOT_PATHS def gap_workspace_file(system="gap", name="workspace", dir=None): @@ -60,8 +60,12 @@ def gap_workspace_file(system="gap", name="workspace", dir=None): if dir is None: dir = os.path.join(DOT_SAGE, 'gap') - data = f'{GAP_LIB_DIR}:{GAP_SHARE_DIR}' - for path in GAP_LIB_DIR, GAP_SHARE_DIR: + data = f'{GAP_ROOT_PATHS}' + for path in GAP_ROOT_PATHS.split(";"): + if not path: + # If GAP_ROOT_PATHS begins or ends with a semicolon, + # we'll get one empty path. + continue sysinfo = os.path.join(path, "sysinfo.gap") if os.path.exists(sysinfo): data += subprocess.getoutput(f'. "{sysinfo}" && echo ":$GAP_VERSION:$GAParch"') diff --git a/src/sage/interfaces/jmoldata.py b/src/sage/interfaces/jmoldata.py index 59a9a1e26a5..e7354e05c70 100644 --- a/src/sage/interfaces/jmoldata.py +++ b/src/sage/interfaces/jmoldata.py @@ -85,10 +85,6 @@ def jmolpath(self): """ jmolpath = os.path.join(JMOL_DIR, "JmolData.jar") - if sys.platform == 'cygwin': - import cygwin - jmolpath = cygwin.cygpath(jmolpath, 'w') - return jmolpath def is_jmol_available(self): @@ -129,8 +125,6 @@ def export_image(self, - datafile -- full path to the data file Jmol can read or text of a script telling Jmol what to read or load. - If it is a script and the platform is cygwin, the filenames in - the script should be in native windows format. - datafile_cmd -- (default ``'script'``) ``'load'`` or ``'script'`` should be ``"load"`` for a data file. @@ -180,10 +174,6 @@ def export_image(self, sage: archive = NamedTemporaryFile(suffix=".zip") sage: D.export_jmol(archive.name) # needs sage.plot sage: archive_native = archive.name - sage: import sys - sage: if sys.platform == 'cygwin': - ....: import cygwin - ....: archive_native = cygwin.cygpath(archive_native, 'w') sage: script = f'set defaultdirectory "f{archive_native}"\n' sage: script += 'script SCRIPT\n' sage: with NamedTemporaryFile(suffix=".png") as testfile: # optional - java, needs sage.plot @@ -198,12 +188,6 @@ def export_image(self, jmolpath = self.jmolpath() target_native = targetfile - if sys.platform == 'cygwin': - import cygwin - target_native = cygwin.cygpath(target_native, 'w') - if datafile_cmd != 'script': - datafile = cygwin.cygpath(datafile, 'w') - launchscript = "" if (datafile_cmd != 'script'): launchscript = "load " diff --git a/src/sage/interfaces/kenzo.py b/src/sage/interfaces/kenzo.py index e403b04dab7..793b8f257f7 100644 --- a/src/sage/interfaces/kenzo.py +++ b/src/sage/interfaces/kenzo.py @@ -131,11 +131,12 @@ def Sphere(n): EXAMPLES:: - sage: from sage.interfaces.kenzo import Sphere # optional - kenzo - sage: s2 = Sphere(2) # optional - kenzo - sage: s2 # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import Sphere + sage: s2 = Sphere(2) + sage: s2 [K1 Simplicial-Set] - sage: [s2.homology(i) for i in range(8)] # optional - kenzo + sage: [s2.homology(i) for i in range(8)] [Z, 0, Z, 0, 0, 0, 0, 0] """ kenzosphere = __sphere__(n) @@ -162,11 +163,12 @@ def MooreSpace(m, n): EXAMPLES:: - sage: from sage.interfaces.kenzo import MooreSpace # optional - kenzo - sage: m24 = MooreSpace(2,4) # optional - kenzo - sage: m24 # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import MooreSpace + sage: m24 = MooreSpace(2,4) + sage: m24 [K10 Simplicial-Set] - sage: [m24.homology(i) for i in range(8)] # optional - kenzo + sage: [m24.homology(i) for i in range(8)] [Z, 0, 0, 0, C2, 0, 0, 0] """ kenzomoore = __moore__(m, n) @@ -387,10 +389,11 @@ def table(self, p, i1, i2, j1, j2): EXAMPLES:: - sage: from sage.interfaces.kenzo import Sphere # optional - kenzo - sage: S2 = Sphere(2) # optional - kenzo - sage: EMS = S2.em_spectral_sequence() # optional - kenzo - sage: EMS.table(0, -2, 2, -2, 2) # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import Sphere + sage: S2 = Sphere(2) + sage: EMS = S2.em_spectral_sequence() + sage: EMS.table(0, -2, 2, -2, 2) 0 Z 0 0 0 0 0 0 0 0 0 0 Z 0 0 @@ -431,11 +434,12 @@ def homology(self, n): EXAMPLES:: - sage: from sage.interfaces.kenzo import Sphere # optional - kenzo - sage: s2 = Sphere(2) # optional - kenzo - sage: s2 # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import Sphere + sage: s2 = Sphere(2) + sage: s2 [K1 Simplicial-Set] - sage: s2.homology(2) # optional - kenzo + sage: s2.homology(2) Z """ echcm1 = __echcm__(self._kenzo) @@ -490,15 +494,16 @@ def basis(self, dim): EXAMPLES:: - sage: from sage.interfaces.kenzo import KChainComplex # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import KChainComplex sage: m1 = matrix(ZZ, 3, 2, [-1, 1, 3, -4, 5, 6]) sage: m4 = matrix(ZZ, 2, 2, [1, 2, 3, 6]) sage: m5 = matrix(ZZ, 2, 3, [2, 2, 2, -1, -1, -1]) - sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) # optional - kenzo - sage: kenzo_chcm = KChainComplex(sage_chcm) # optional - kenzo - sage: kenzo_chcm # optional - kenzo + sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) + sage: kenzo_chcm = KChainComplex(sage_chcm) + sage: kenzo_chcm [K... Chain-Complex] - sage: for i in range(6): # optional - kenzo + sage: for i in range(6): ....: print("Basis in dimension %i: %s" % (i, kenzo_chcm.basis(i))) Basis in dimension 0: ['G0G0', 'G0G1', 'G0G2'] Basis in dimension 1: ['G1G0', 'G1G1'] @@ -506,7 +511,6 @@ def basis(self, dim): Basis in dimension 3: ['G3G0', 'G3G1'] Basis in dimension 4: ['G4G0', 'G4G1'] Basis in dimension 5: ['G5G0', 'G5G1', 'G5G2'] - """ return __basis_aux1__(self._kenzo, dim).python() @@ -603,26 +607,27 @@ def differential(self, dim=None, comb=None): EXAMPLES:: - sage: from sage.interfaces.kenzo import KChainComplex # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import KChainComplex sage: m1 = matrix(ZZ, 3, 2, [-1, 1, 3, -4, 5, 6]) sage: m4 = matrix(ZZ, 2, 2, [1, 2, 3, 6]) sage: m5 = matrix(ZZ, 2, 3, [2, 2, 2, -1, -1, -1]) sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) - sage: kenzo_chcm = KChainComplex(sage_chcm) # optional - kenzo - sage: kenzo_chcm # optional - kenzo + sage: kenzo_chcm = KChainComplex(sage_chcm) + sage: kenzo_chcm [K... Chain-Complex] - sage: kenzo_chcm.basis(4) # optional - kenzo + sage: kenzo_chcm.basis(4) ['G4G0', 'G4G1'] - sage: kenzo_chcm.differential(4, [1, 'G4G0']) # optional - kenzo + sage: kenzo_chcm.differential(4, [1, 'G4G0']) ----------------------------------------------------------------------{CMBN 3} <1 * G3G0> <3 * G3G1> ------------------------------------------------------------------------------ - sage: kenzo_chcm.basis(5) # optional - kenzo + sage: kenzo_chcm.basis(5) ['G5G0', 'G5G1', 'G5G2'] - sage: kenzo_chcm.differential(5, [1, 'G5G0', 2, 'G5G2']) # optional - kenzo + sage: kenzo_chcm.differential(5, [1, 'G5G0', 2, 'G5G2']) ----------------------------------------------------------------------{CMBN 4} <6 * G4G0> @@ -748,10 +753,11 @@ def homotopy_group(self, n): EXAMPLES:: - sage: from sage.interfaces.kenzo import Sphere # optional - kenzo - sage: s2 = Sphere(2) # optional - kenzo - sage: p = s2.cartesian_product(s2) # optional - kenzo - sage: p.homotopy_group(3) # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import Sphere + sage: s2 = Sphere(2) + sage: p = s2.cartesian_product(s2) + sage: p.homotopy_group(3) Multiplicative Abelian group isomorphic to Z x Z @@ -780,10 +786,11 @@ def em_spectral_sequence(self): EXAMPLES:: - sage: from sage.interfaces.kenzo import Sphere # optional - kenzo - sage: S2 = Sphere(2) # optional - kenzo - sage: EMS = S2.em_spectral_sequence() # optional - kenzo - sage: EMS.table(0, -2, 2, -2, 2) # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import Sphere + sage: S2 = Sphere(2) + sage: EMS = S2.em_spectral_sequence() + sage: EMS.table(0, -2, 2, -2, 2) 0 Z 0 0 0 0 0 0 0 0 0 0 Z 0 0 @@ -1090,15 +1097,16 @@ def KChainComplex(chain_complex): EXAMPLES:: - sage: from sage.interfaces.kenzo import KChainComplex # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import KChainComplex sage: m1 = matrix(ZZ, 3, 2, [-1, 1, 3, -4, 5, 6]) sage: m4 = matrix(ZZ, 2, 2, [1, 2, 3, 6]) sage: m5 = matrix(ZZ, 2, 3, [2, 2, 2, -1, -1, -1]) - sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) # optional - kenzo - sage: kenzo_chcm = KChainComplex(sage_chcm) # optional - kenzo - sage: kenzo_chcm # optional - kenzo + sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) + sage: kenzo_chcm = KChainComplex(sage_chcm) + sage: kenzo_chcm [K... Chain-Complex] - sage: kenzo_chcm.homology(5) # optional - kenzo + sage: kenzo_chcm.homology(5) Z x Z """ d = chain_complex.differential() @@ -1242,8 +1250,9 @@ def KFiniteSimplicialSet(sset): EXAMPLES:: + sage: # optional - kenzo sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet - sage: from sage.interfaces.kenzo import KFiniteSimplicialSet # optional - kenzo + sage: from sage.interfaces.kenzo import KFiniteSimplicialSet sage: s0 = AbstractSimplex(0, name='s0') sage: s1 = AbstractSimplex(0, name='s1') sage: s2 = AbstractSimplex(0, name='s2') @@ -1253,15 +1262,15 @@ def KFiniteSimplicialSet(sset): sage: s012 = AbstractSimplex(2, name='s012') sage: Triangle = SimplicialSet({s01: (s1, s0),\ ....: s02: (s2, s0), s12: (s2, s1)}, base_point = s0) - sage: KTriangle = KFiniteSimplicialSet(Triangle) # optional - kenzo - sage: KTriangle.homology(1) # optional - kenzo + sage: KTriangle = KFiniteSimplicialSet(Triangle) + sage: KTriangle.homology(1) Z - sage: KTriangle.basis(1) # optional - kenzo + sage: KTriangle.basis(1) ['CELL_1_0', 'CELL_1_1', 'CELL_1_2'] sage: S1 = simplicial_sets.Sphere(1) sage: S3 = simplicial_sets.Sphere(3) - sage: KS1vS3 = KFiniteSimplicialSet(S1.wedge(S3)) # optional - kenzo - sage: KS1vS3.homology(3) # optional - kenzo + sage: KS1vS3 = KFiniteSimplicialSet(S1.wedge(S3)) + sage: KS1vS3.homology(3) Z """ from sage.topology.simplicial_set_constructions import ProductOfSimplicialSets @@ -1389,18 +1398,19 @@ def source_complex(self): EXAMPLES:: - sage: from sage.interfaces.kenzo import KChainComplex # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import KChainComplex sage: m1 = matrix(ZZ, 3, 2, [-1, 1, 3, -4, 5, 6]) sage: m4 = matrix(ZZ, 2, 2, [1, 2, 3, 6]) sage: m5 = matrix(ZZ, 2, 3, [2, 2, 2, -1, -1, -1]) - sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) # optional - kenzo - sage: kenzo_chcm = KChainComplex(sage_chcm) # optional - kenzo - sage: kenzo_chcm # optional - kenzo + sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) + sage: kenzo_chcm = KChainComplex(sage_chcm) + sage: kenzo_chcm [K... Chain-Complex] - sage: differential_morphism = kenzo_chcm.differential() # optional - kenzo - sage: differential_morphism # optional - kenzo + sage: differential_morphism = kenzo_chcm.differential() + sage: differential_morphism [K... Morphism (degree -1): K... -> K...] - sage: differential_morphism.source_complex() # optional - kenzo + sage: differential_morphism.source_complex() [K... Chain-Complex] """ return KenzoChainComplex(__sorc_aux__(self._kenzo)) @@ -1415,18 +1425,19 @@ def target_complex(self): EXAMPLES:: - sage: from sage.interfaces.kenzo import KChainComplex # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import KChainComplex sage: m1 = matrix(ZZ, 3, 2, [-1, 1, 3, -4, 5, 6]) sage: m4 = matrix(ZZ, 2, 2, [1, 2, 3, 6]) sage: m5 = matrix(ZZ, 2, 3, [2, 2, 2, -1, -1, -1]) - sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) # optional - kenzo - sage: kenzo_chcm = KChainComplex(sage_chcm) # optional - kenzo - sage: kenzo_chcm # optional - kenzo + sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) + sage: kenzo_chcm = KChainComplex(sage_chcm) + sage: kenzo_chcm [K... Chain-Complex] - sage: differential_morphism = kenzo_chcm.differential() # optional - kenzo - sage: differential_morphism # optional - kenzo + sage: differential_morphism = kenzo_chcm.differential() + sage: differential_morphism [K... Morphism (degree -1): K... -> K...] - sage: differential_morphism.target_complex() # optional - kenzo + sage: differential_morphism.target_complex() [K... Chain-Complex] """ return KenzoChainComplex(__trgt_aux__(self._kenzo)) @@ -1441,22 +1452,23 @@ def degree(self): EXAMPLES:: - sage: from sage.interfaces.kenzo import KChainComplex # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import KChainComplex sage: m1 = matrix(ZZ, 3, 2, [-1, 1, 3, -4, 5, 6]) sage: m4 = matrix(ZZ, 2, 2, [1, 2, 3, 6]) sage: m5 = matrix(ZZ, 2, 3, [2, 2, 2, -1, -1, -1]) - sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) # optional - kenzo - sage: kenzo_chcm = KChainComplex(sage_chcm) # optional - kenzo - sage: kenzo_chcm # optional - kenzo + sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree=-1) + sage: kenzo_chcm = KChainComplex(sage_chcm) + sage: kenzo_chcm [K... Chain-Complex] - sage: differential_morphism = kenzo_chcm.differential() # optional - kenzo - sage: differential_morphism # optional - kenzo + sage: differential_morphism = kenzo_chcm.differential() + sage: differential_morphism [K... Morphism (degree -1): K... -> K...] - sage: differential_morphism.degree() # optional - kenzo + sage: differential_morphism.degree() -1 - sage: differential_morphism.composite(differential_morphism).degree() # optional - kenzo + sage: differential_morphism.composite(differential_morphism).degree() -2 - sage: kenzo_chcm.null_morphism().degree() # optional - kenzo + sage: kenzo_chcm.null_morphism().degree() 0 """ return __degr_aux__(self._kenzo).python() @@ -1485,51 +1497,52 @@ def evaluation(self, dim, comb): EXAMPLES:: - sage: from sage.interfaces.kenzo import KChainComplex # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import KChainComplex sage: m1 = matrix(ZZ, 3, 2, [-1, 1, 3, -4, 5, 6]) sage: m4 = matrix(ZZ, 2, 2, [1, 2, 3, 6]) sage: m5 = matrix(ZZ, 2, 3, [2, 2, 2, -1, -1, -1]) sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) - sage: kenzo_chcm = KChainComplex(sage_chcm) # optional - kenzo - sage: kenzo_chcm # optional - kenzo + sage: kenzo_chcm = KChainComplex(sage_chcm) + sage: kenzo_chcm [K... Chain-Complex] - sage: differential_morphism = kenzo_chcm.differential() # optional - kenzo - sage: differential_morphism # optional - kenzo + sage: differential_morphism = kenzo_chcm.differential() + sage: differential_morphism [K... Morphism (degree -1): K... -> K...] - sage: dif_squared = differential_morphism.composite(differential_morphism) # optional - kenzo - sage: dif_squared # optional - kenzo + sage: dif_squared = differential_morphism.composite(differential_morphism) + sage: dif_squared [K... Morphism (degree -2): K... -> K...] - sage: kenzo_chcm.basis(5) # optional - kenzo + sage: kenzo_chcm.basis(5) ['G5G0', 'G5G1', 'G5G2'] - sage: kenzo_chcm.differential(5, [1, 'G5G0', 2, 'G5G2']) # optional - kenzo + sage: kenzo_chcm.differential(5, [1, 'G5G0', 2, 'G5G2']) ----------------------------------------------------------------------{CMBN 4} <6 * G4G0> <-3 * G4G1> ------------------------------------------------------------------------------ - sage: differential_morphism.evaluation(5, [1, 'G5G0', 2, 'G5G2']) # optional - kenzo + sage: differential_morphism.evaluation(5, [1, 'G5G0', 2, 'G5G2']) ----------------------------------------------------------------------{CMBN 4} <6 * G4G0> <-3 * G4G1> ------------------------------------------------------------------------------ - sage: dif_squared.evaluation(5, [1, 'G5G0', 2, 'G5G2']) # optional - kenzo + sage: dif_squared.evaluation(5, [1, 'G5G0', 2, 'G5G2']) ----------------------------------------------------------------------{CMBN 3} ------------------------------------------------------------------------------ - sage: idnt = kenzo_chcm.identity_morphism() # optional - kenzo - sage: idx2 = idnt.sum(idnt) # optional - kenzo - sage: idnt.evaluation(5, [1, 'G5G0', 2, 'G5G2']) # optional - kenzo + sage: idnt = kenzo_chcm.identity_morphism() + sage: idx2 = idnt.sum(idnt) + sage: idnt.evaluation(5, [1, 'G5G0', 2, 'G5G2']) ----------------------------------------------------------------------{CMBN 5} <1 * G5G0> <2 * G5G2> ------------------------------------------------------------------------------ - sage: idx2.evaluation(5, [1, 'G5G0', 2, 'G5G2']) # optional - kenzo + sage: idx2.evaluation(5, [1, 'G5G0', 2, 'G5G2']) ----------------------------------------------------------------------{CMBN 5} <2 * G5G0> @@ -1556,30 +1569,31 @@ def opposite(self): EXAMPLES:: - sage: from sage.interfaces.kenzo import KChainComplex # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import KChainComplex sage: m1 = matrix(ZZ, 3, 2, [-1, 1, 3, -4, 5, 6]) sage: m4 = matrix(ZZ, 2, 2, [1, 2, 3, 6]) sage: m5 = matrix(ZZ, 2, 3, [2, 2, 2, -1, -1, -1]) - sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) # optional - kenzo - sage: kenzo_chcm = KChainComplex(sage_chcm) # optional - kenzo - sage: kenzo_chcm # optional - kenzo + sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) + sage: kenzo_chcm = KChainComplex(sage_chcm) + sage: kenzo_chcm [K... Chain-Complex] - sage: idnt = kenzo_chcm.identity_morphism() # optional - kenzo - sage: idnt # optional - kenzo + sage: idnt = kenzo_chcm.identity_morphism() + sage: idnt [K... Morphism (degree 0): K... -> K...] - sage: opps_id = idnt.opposite() # optional - kenzo - sage: opps_id # optional - kenzo + sage: opps_id = idnt.opposite() + sage: opps_id [K... Morphism (degree 0): K... -> K...] - sage: kenzo_chcm.basis(4) # optional - kenzo + sage: kenzo_chcm.basis(4) ['G4G0', 'G4G1'] - sage: idnt.evaluation(4, [2, 'G4G0', -5, 'G4G1']) # optional - kenzo + sage: idnt.evaluation(4, [2, 'G4G0', -5, 'G4G1']) ----------------------------------------------------------------------{CMBN 4} <2 * G4G0> <-5 * G4G1> ------------------------------------------------------------------------------ - sage: opps_id.evaluation(4, [2, 'G4G0', -5, 'G4G1']) # optional - kenzo + sage: opps_id.evaluation(4, [2, 'G4G0', -5, 'G4G1']) ----------------------------------------------------------------------{CMBN 4} <-2 * G4G0> @@ -1653,36 +1667,37 @@ def sum(self, object=None): EXAMPLES:: - sage: from sage.interfaces.kenzo import KChainComplex # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import KChainComplex sage: m1 = matrix(ZZ, 3, 2, [-1, 1, 3, -4, 5, 6]) sage: m4 = matrix(ZZ, 2, 2, [1, 2, 3, 6]) sage: m5 = matrix(ZZ, 2, 3, [2, 2, 2, -1, -1, -1]) - sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) # optional - kenzo - sage: kenzo_chcm = KChainComplex(sage_chcm) # optional - kenzo - sage: kenzo_chcm # optional - kenzo + sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) + sage: kenzo_chcm = KChainComplex(sage_chcm) + sage: kenzo_chcm [K... Chain-Complex] - sage: idnt = kenzo_chcm.identity_morphism() # optional - kenzo - sage: idnt # optional - kenzo + sage: idnt = kenzo_chcm.identity_morphism() + sage: idnt [K... Morphism (degree 0): K... -> K...] - sage: opps_id = idnt.opposite() # optional - kenzo - sage: opps_id # optional - kenzo + sage: opps_id = idnt.opposite() + sage: opps_id [K... Morphism (degree 0): K... -> K...] - sage: null = kenzo_chcm.null_morphism() # optional - kenzo - sage: null # optional - kenzo + sage: null = kenzo_chcm.null_morphism() + sage: null [K... Morphism (degree 0): K... -> K...] - sage: idx2 = idnt.sum(idnt) # optional - kenzo - sage: idx5 = idx2.sum( # optional - kenzo + sage: idx2 = idnt.sum(idnt) + sage: idx5 = idx2.sum( ....: (opps_id, idnt, idnt, null, idx2.sum(idnt), opps_id)) - sage: kenzo_chcm.basis(4) # optional - kenzo + sage: kenzo_chcm.basis(4) ['G4G0', 'G4G1'] - sage: idx2.evaluation(4, [2, 'G4G0', -5, 'G4G1']) # optional - kenzo + sage: idx2.evaluation(4, [2, 'G4G0', -5, 'G4G1']) ----------------------------------------------------------------------{CMBN 4} <4 * G4G0> <-10 * G4G1> ------------------------------------------------------------------------------ - sage: idx5.evaluation(4, [2, 'G4G0', -5, 'G4G1']) # optional - kenzo + sage: idx5.evaluation(4, [2, 'G4G0', -5, 'G4G1']) ----------------------------------------------------------------------{CMBN 4} <10 * G4G0> @@ -1719,36 +1734,37 @@ def substract(self, object=None): EXAMPLES:: - sage: from sage.interfaces.kenzo import KChainComplex # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import KChainComplex sage: m1 = matrix(ZZ, 3, 2, [-1, 1, 3, -4, 5, 6]) sage: m4 = matrix(ZZ, 2, 2, [1, 2, 3, 6]) sage: m5 = matrix(ZZ, 2, 3, [2, 2, 2, -1, -1, -1]) - sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) # optional - kenzo - sage: kenzo_chcm = KChainComplex(sage_chcm) # optional - kenzo - sage: kenzo_chcm # optional - kenzo + sage: sage_chcm = ChainComplex({1: m1, 4: m4, 5: m5}, degree = -1) + sage: kenzo_chcm = KChainComplex(sage_chcm) + sage: kenzo_chcm [K... Chain-Complex] - sage: idnt = kenzo_chcm.identity_morphism() # optional - kenzo - sage: idnt # optional - kenzo + sage: idnt = kenzo_chcm.identity_morphism() + sage: idnt [K... Morphism (degree 0): K... -> K...] - sage: opps_id = idnt.opposite() # optional - kenzo - sage: opps_id # optional - kenzo + sage: opps_id = idnt.opposite() + sage: opps_id [K... Morphism (degree 0): K... -> K...] - sage: null = kenzo_chcm.null_morphism() # optional - kenzo - sage: null # optional - kenzo + sage: null = kenzo_chcm.null_morphism() + sage: null [K... Morphism (degree 0): K... -> K...] - sage: idx2 = idnt.substract(opps_id) # optional - kenzo - sage: opps_idx2 = idx2.substract( # optional - kenzo + sage: idx2 = idnt.substract(opps_id) + sage: opps_idx2 = idx2.substract( ....: (opps_id, idnt, idnt, null, idx2.substract(opps_id))) - sage: kenzo_chcm.basis(4) # optional - kenzo + sage: kenzo_chcm.basis(4) ['G4G0', 'G4G1'] - sage: idx2.evaluation(4, [2, 'G4G0', -5, 'G4G1']) # optional - kenzo + sage: idx2.evaluation(4, [2, 'G4G0', -5, 'G4G1']) ----------------------------------------------------------------------{CMBN 4} <4 * G4G0> <-10 * G4G1> ------------------------------------------------------------------------------ - sage: opps_idx2.evaluation(4, [2, 'G4G0', -5, 'G4G1']) # optional - kenzo + sage: opps_idx2.evaluation(4, [2, 'G4G0', -5, 'G4G1']) ----------------------------------------------------------------------{CMBN 4} <-4 * G4G0> @@ -1954,16 +1970,16 @@ def KChainComplexMorphism(morphism): EXAMPLES:: - sage: from sage.interfaces.kenzo import KChainComplexMorphism # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import KChainComplexMorphism sage: C = ChainComplex({0: identity_matrix(ZZ, 1)}) sage: D = ChainComplex({0: zero_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)}) - sage: g = KChainComplexMorphism(f) # optional - kenzo - sage: g # optional - kenzo + sage: g = KChainComplexMorphism(f); g [K... Morphism (degree 0): K... -> K...] - sage: g.source_complex() # optional - kenzo + sage: g.source_complex() [K... Chain-Complex] - sage: g.target_complex() # optional - kenzo + sage: g.target_complex() [K... Chain-Complex] """ source = KChainComplex(morphism.domain()) @@ -2017,21 +2033,22 @@ def BicomplexSpectralSequence(l): EXAMPLES:: - sage: from sage.interfaces.kenzo import BicomplexSpectralSequence # optional - kenzo + sage: # optional - kenzo + sage: from sage.interfaces.kenzo import BicomplexSpectralSequence sage: C1 = ChainComplex({1: matrix(ZZ, 0, 2, [])}, degree_of_differential=-1) sage: C2 = ChainComplex({1: matrix(ZZ, 1, 2, [1, 0])},degree_of_differential=-1) sage: C3 = ChainComplex({0: matrix(ZZ, 0,2 , [])},degree_of_differential=-1) sage: M1 = Hom(C2,C1)({1: matrix(ZZ, 2, 2, [2, 0, 0, 2])}) sage: M2 = Hom(C3,C2)({0: matrix(ZZ, 1, 2, [2, 0])}) sage: l = [M1, M2] - sage: E = BicomplexSpectralSequence(l) # optional - kenzo - sage: E.group(2,0,1) # optional - kenzo + sage: E = BicomplexSpectralSequence(l) + sage: E.group(2,0,1) Additive abelian group isomorphic to Z/2 + Z - sage: E.table(3,0,2,0,2) # optional - kenzo + sage: E.table(3,0,2,0,2) 0 0 0 Z/2 + Z/4 0 0 0 0 Z - sage: E.matrix(2,2,0) # optional - kenzo + sage: E.matrix(2,2,0) [ 0 0] [-4 0] """ diff --git a/src/sage/interfaces/qepcad.py b/src/sage/interfaces/qepcad.py index 7efc51038ea..01ae082cca6 100644 --- a/src/sage/interfaces/qepcad.py +++ b/src/sage/interfaces/qepcad.py @@ -833,7 +833,7 @@ def __init__(self, formula, x^2 + x*y + 3*x*z + 2*y*z + 2*z^2 - x - z < 0, \ -2*x + 1 < 0, -x*y - x*z - 2*y*z - 2*z^2 + z < 0, \ x + 3*y + 3*z - 1 < 0] - sage: qepcad(conds, memcells=2000000) # optional - qepcad + sage: qepcad(conds, memcells=3000000) # optional - qepcad 2 x - 1 > 0 /\ z > 0 /\ z - y < 0 /\ 3 z + 3 y + x - 1 < 0 """ self._cell_cache = {} diff --git a/src/sage/interfaces/scilab.py b/src/sage/interfaces/scilab.py index 3229fd3d82c..a5cb2c1abb9 100644 --- a/src/sage/interfaces/scilab.py +++ b/src/sage/interfaces/scilab.py @@ -249,11 +249,12 @@ def set_seed(self, seed=None): EXAMPLES:: - sage: from sage.interfaces.scilab import Scilab # optional - scilab - sage: s = Scilab() # optional - scilab - sage: s.set_seed(1) # optional - scilab + sage: # optional - scilab + sage: from sage.interfaces.scilab import Scilab + sage: s = Scilab() + sage: s.set_seed(1) 1 - sage: [s.rand() for i in range(5)] # optional - scilab + sage: [s.rand() for i in range(5)] [ 0.6040239, @@ -535,7 +536,7 @@ def scilab_console(): EXAMPLES:: - sage: from sage.interfaces.scilab import scilab_console # optional - scilab + sage: from sage.interfaces.scilab import scilab_console # optional - scilab sage: scilab_console() # optional - scilab; not tested ___________________________________________ scilab-5.0.3 @@ -569,7 +570,7 @@ def scilab_version(): EXAMPLES:: - sage: from sage.interfaces.scilab import scilab_version # optional - scilab + sage: from sage.interfaces.scilab import scilab_version # optional - scilab sage: scilab_version() # optional - scilab 'scilab-...' """ diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index a2f48a996ed..0ef601e124c 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -97,26 +97,26 @@ If you have `SnapPy `__ installed inside -Sage you can obtain an instance of :class:`~spherogram.links.links_base.Link`, +Sage, you can obtain an instance of :class:`~spherogram.links.links_base.Link`, too:: + sage: # optional - snappy sage: L6 = KnotInfo.L6a1_0 - sage: l6s = L6.link(snappy=True); l6s # optional - snappy + sage: l6s = L6.link(snappy=True); l6s Plink failed to import tkinter. - - sage: type(l6s) # optional - snappy + sage: type(l6s) sage: l6 = L6.link() - sage: l6 == l6s.sage_link() # optional - snappy + sage: l6 == l6s.sage_link() True - sage: L6.link(L6.items.name, snappy=True) # optional - snappy + sage: L6.link(L6.items.name, snappy=True) - sage: l6sn = _ # optional - snappy - sage: l6s == l6sn # optional - snappy + sage: l6sn = _ + sage: l6s == l6sn False - sage: l6m = l6.mirror_image() # optional - snappy - sage: l6sn.sage_link().is_isotopic(l6m) # optional - snappy + sage: l6m = l6.mirror_image() + sage: l6sn.sage_link().is_isotopic(l6m) True But observe that the name conversion to SnapPy does not distinguish orientation @@ -238,14 +238,16 @@ from enum import Enum from sage.misc.cachefunc import cached_method +from sage.misc.lazy_import import lazy_import from sage.misc.sage_eval import sage_eval from sage.structure.sage_object import SageObject from sage.structure.unique_representation import UniqueRepresentation from sage.rings.integer_ring import ZZ -from sage.groups.braid import BraidGroup from sage.knots.knot import Knots from sage.databases.knotinfo_db import KnotInfoColumns, db +lazy_import('sage.groups.braid', 'BraidGroup') + def eval_knotinfo(string, locals={}, to_tuple=True): r""" @@ -694,8 +696,7 @@ def braid_length(self): @cached_method def braid(self): r""" - Return the braid notation of self as an instance of :class:`~sage.groups.braid.Braid`. - + Return the braid notation of ``self`` as an instance of :class:`~sage.groups.braid.Braid`. EXAMPLES:: diff --git a/src/sage/libs/gap/libgap.pyx b/src/sage/libs/gap/libgap.pyx index 3803f32b191..63400adab4c 100644 --- a/src/sage/libs/gap/libgap.pyx +++ b/src/sage/libs/gap/libgap.pyx @@ -46,7 +46,7 @@ equivalent:: sage: type(_) - sage: libgap.eval('5/3 + 7*E(3)').sage() + sage: libgap.eval('5/3 + 7*E(3)').sage() # needs sage.rings.number_field 7*zeta3 + 5/3 sage: gens_of_group = libgap.AlternatingGroup(4).GeneratorsOfGroup() @@ -265,7 +265,7 @@ class Gap(Parent): sage: libgap.has_coerce_map_from(ZZ) True - sage: libgap.has_coerce_map_from(CyclotomicField(5)['x','y']) + sage: libgap.has_coerce_map_from(CyclotomicField(5)['x','y']) # needs sage.rings.number_field True """ return True @@ -362,11 +362,12 @@ class Gap(Parent): We gracefully handle the case that the conversion fails (:trac:`18039`):: - sage: F. = GF(9, modulus="first_lexicographic") - sage: libgap(Matrix(F, [[a]])) + sage: F. = GF(9, modulus="first_lexicographic") # needs sage.rings.finite_rings + sage: libgap(Matrix(F, [[a]])) # needs sage.rings.finite_rings Traceback (most recent call last): ... - NotImplementedError: conversion of (Givaro) finite field element to GAP not implemented except for fields defined by Conway polynomials. + NotImplementedError: conversion of (Givaro) finite field element to GAP + not implemented except for fields defined by Conway polynomials. """ ring = M.base_ring() try: diff --git a/src/sage/libs/gap/sage.gaprc b/src/sage/libs/gap/sage.gaprc index 39c878f2329..258db942a98 100644 --- a/src/sage/libs/gap/sage.gaprc +++ b/src/sage/libs/gap/sage.gaprc @@ -1,3 +1,31 @@ # This file is run by Sage when initializing libgap via GAP_Initialize, and may # contain bug fixes/workarounds and/or any Sage-specific patches necessary for # Sage's libgap interface. + + +# Load the GAP packages that GAP itself tries to autoload in the +# default configuration (see "PackagesToLoad" in lib/package.gi). The +# combination of passing -A to gap and these LoadPackage statements +# allows us to load the usual set of packages, but only if they are +# installed. So most people will get exactly the default behavior, +# but minimal installations won't throw warnings and fail tests. +# +# We also temporarily lower the InfoLevel of the InfoWarning class so +# that e.g., +# +# #I polycyclic package is not available. Check that the name is correct +# #I and it is present in one of the GAP root directories (see '??RootPaths') +# +# is not output to the console. +# +_orig_warn_level := InfoLevel(InfoWarning); +SetInfoLevel(InfoWarning, 0); + +_autoloads := [ "autpgrp", "alnuth", "crisp", "ctbllib", "factint", "fga", + "irredsol", "laguna", "polenta", "polycyclic", "resclasses", + "sophus", "tomlib" ]; +for p in _autoloads do + LoadPackage(p); +od; + +SetInfoLevel(InfoWarning, _orig_warn_level); diff --git a/src/sage/libs/gap/saved_workspace.py b/src/sage/libs/gap/saved_workspace.py index 7636707f557..fdaf18f4644 100644 --- a/src/sage/libs/gap/saved_workspace.py +++ b/src/sage/libs/gap/saved_workspace.py @@ -8,7 +8,7 @@ import os import glob -from sage.env import GAP_LIB_DIR +from sage.env import GAP_ROOT_PATHS from sage.interfaces.gap_workspace import gap_workspace_file @@ -31,7 +31,13 @@ def timestamp(): """ libgap_dir = os.path.dirname(__file__) libgap_files = glob.glob(os.path.join(libgap_dir, '*')) - gap_packages = glob.glob(os.path.join(GAP_LIB_DIR, 'pkg', '*')) + gap_packages = [] + for d in GAP_ROOT_PATHS.split(";"): + if d: + # If GAP_ROOT_PATHS begins or ends with a semicolon, + # we'll get one empty d. + gap_packages += glob.glob(os.path.join(d, 'pkg', '*')) + files = libgap_files + gap_packages if len(files) == 0: print('Unable to find LibGAP files.') diff --git a/src/sage/libs/gap/util.pyx b/src/sage/libs/gap/util.pyx index d37fe84f029..8685dc08fa5 100644 --- a/src/sage/libs/gap/util.pyx +++ b/src/sage/libs/gap/util.pyx @@ -217,28 +217,29 @@ cdef initialize() noexcept: # initialize GAP. cdef char* argv[16] argv[0] = "sage" - argv[1] = "-l" - s = str_to_bytes(sage.env.GAP_LIB_DIR + ";" + sage.env.GAP_SHARE_DIR, FS_ENCODING, "surrogateescape") - argv[2] = s - - argv[3] = "-m" - argv[4] = "64m" - - argv[5] = "-q" # no prompt! - argv[6] = "-E" # don't use readline as this will interfere with Python - argv[7] = "--nointeract" # Implies -T - argv[8] = "-x" # set the "screen" width so that GAP is less likely to - argv[9] = "4096" # insert newlines when printing objects + argv[1] = "-A" + argv[2] = "-l" + s = str_to_bytes(sage.env.GAP_ROOT_PATHS, FS_ENCODING, "surrogateescape") + argv[3] = s + + argv[4] = "-m" + argv[5] = "64m" + + argv[6] = "-q" # no prompt! + argv[7] = "-E" # don't use readline as this will interfere with Python + argv[8] = "--nointeract" # Implies -T + argv[9] = "-x" # set the "screen" width so that GAP is less likely to + argv[10] = "4096" # insert newlines when printing objects # 4096 unfortunately is the hard-coded max, but should # be long enough for most cases - cdef int argc = 10 # argv[argc] must be NULL + cdef int argc = 11 # argv[argc] must be NULL gap_mem = sage.env.SAGE_GAP_MEMORY if gap_mem is not None: argc += 2 - argv[10] = "-s" + argv[11] = "-s" s1 = str_to_bytes(gap_mem, FS_ENCODING, "surrogateescape") - argv[11] = s1 - argv[4] = s1 + argv[12] = s1 + argv[5] = s1 from sage.libs.gap.saved_workspace import workspace workspace, workspace_is_up_to_date = workspace() diff --git a/src/sage/misc/package.py b/src/sage/misc/package.py index dcae7d0c0fc..cbc36ea65b9 100644 --- a/src/sage/misc/package.py +++ b/src/sage/misc/package.py @@ -390,7 +390,7 @@ def _spkg_inst_dirs(): """ Generator for the installation manifest directories as resolved paths. - It yields first ``SAGE_SPKG_INST``, then ``SAGE_VENV_SPKG_INST``, + It yields first ``SAGE_LOCAL_SPKG_INST``, then ``SAGE_VENV_SPKG_INST``, if defined; but it both resolve to the same directory, it only yields one element. @@ -402,7 +402,7 @@ def _spkg_inst_dirs(): """ last_inst_dir = None - for inst_dir in (sage.env.SAGE_SPKG_INST, sage.env.SAGE_VENV_SPKG_INST): + for inst_dir in (sage.env.SAGE_LOCAL_SPKG_INST, sage.env.SAGE_VENV_SPKG_INST): if inst_dir: inst_dir = Path(inst_dir).resolve() if inst_dir.is_dir() and inst_dir != last_inst_dir: diff --git a/src/sage/modular/arithgroup/congroup_gamma0.py b/src/sage/modular/arithgroup/congroup_gamma0.py index 5c57f12a511..aecf1582f98 100644 --- a/src/sage/modular/arithgroup/congroup_gamma0.py +++ b/src/sage/modular/arithgroup/congroup_gamma0.py @@ -390,13 +390,13 @@ def gamma_h_subgroups(self): EXAMPLES:: sage: G = Gamma0(11) - sage: G.gamma_h_subgroups() + sage: G.gamma_h_subgroups() # optional - gap_package_polycyclic [Congruence Subgroup Gamma0(11), Congruence Subgroup Gamma_H(11) with H generated by [3], Congruence Subgroup Gamma_H(11) with H generated by [10], Congruence Subgroup Gamma1(11)] sage: G = Gamma0(12) - sage: G.gamma_h_subgroups() + sage: G.gamma_h_subgroups() # optional - gap_package_polycyclic [Congruence Subgroup Gamma0(12), Congruence Subgroup Gamma_H(12) with H generated by [7], Congruence Subgroup Gamma_H(12) with H generated by [11], diff --git a/src/sage/modular/arithgroup/congroup_gammaH.py b/src/sage/modular/arithgroup/congroup_gammaH.py index 60df7b2add9..9310ecae804 100644 --- a/src/sage/modular/arithgroup/congroup_gammaH.py +++ b/src/sage/modular/arithgroup/congroup_gammaH.py @@ -352,7 +352,7 @@ def __richcmp__(self, other, op): sage: Gamma0(2) == Gamma1(2) True - sage: [x._list_of_elements_in_H() for x in sorted(Gamma0(24).gamma_h_subgroups())] + sage: [x._list_of_elements_in_H() for x in sorted(Gamma0(24).gamma_h_subgroups())] # optional - gap_package_polycyclic [[1], [1, 5], [1, 7], @@ -1055,7 +1055,7 @@ def index(self): EXAMPLES:: - sage: [G.index() for G in Gamma0(40).gamma_h_subgroups()] + sage: [G.index() for G in Gamma0(40).gamma_h_subgroups()] # optional - gap_package_polycyclic [72, 144, 144, 144, 144, 288, 288, 288, 288, 144, 288, 288, 576, 576, 144, 288, 288, 576, 576, 144, 288, 288, 576, 576, 288, 576, 1152] """ from .all import Gamma1 @@ -1068,7 +1068,7 @@ def nu2(self): EXAMPLES:: - sage: [H.nu2() for n in [1..10] for H in Gamma0(n).gamma_h_subgroups()] + sage: [H.nu2() for n in [1..10] for H in Gamma0(n).gamma_h_subgroups()] # optional - gap_package_polycyclic [1, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0] sage: GammaH(33,[2]).nu2() 0 @@ -1095,7 +1095,7 @@ def nu3(self): EXAMPLES:: - sage: [H.nu3() for n in [1..10] for H in Gamma0(n).gamma_h_subgroups()] + sage: [H.nu3() for n in [1..10] for H in Gamma0(n).gamma_h_subgroups()] # optional - gap_package_polycyclic [1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] sage: GammaH(33,[2]).nu3() 0 @@ -1279,7 +1279,7 @@ def image_mod_n(self): TESTS:: - sage: for n in [2..20]: + sage: for n in [2..20]: # optional - gap_package_polycyclic ....: for g in Gamma0(n).gamma_h_subgroups(): ....: G = g.image_mod_n() ....: assert G.order() == Gamma(n).index() / g.index() diff --git a/src/sage/modular/arithgroup/farey.cpp b/src/sage/modular/arithgroup/farey.cpp index 5c912b46ca9..209391676de 100644 --- a/src/sage/modular/arithgroup/farey.cpp +++ b/src/sage/modular/arithgroup/farey.cpp @@ -29,7 +29,7 @@ #include #include "farey.hpp" -#include "farey_symbol.h" +#include "sage/modular/arithgroup/farey_symbol.h" using namespace std; diff --git a/src/sage/numerical/mip.pxd b/src/sage/numerical/mip.pxd index 612324a5424..e73cc408d09 100644 --- a/src/sage/numerical/mip.pxd +++ b/src/sage/numerical/mip.pxd @@ -3,8 +3,11 @@ cdef extern from *: cdef int REAL = -1 cdef int INTEGER = 0 +from sage.sets.family cimport FiniteFamily from sage.structure.sage_object cimport SageObject from sage.numerical.backends.generic_backend cimport GenericBackend + + cdef class MIPVariable @@ -26,10 +29,8 @@ cdef class MixedIntegerLinearProgram(SageObject): cpdef sum(self, L) noexcept -cdef class MIPVariable(SageObject): +cdef class MIPVariable(FiniteFamily): cdef MixedIntegerLinearProgram _p - cdef dict _dict - cdef bint _dynamic_indices cdef int _vtype cdef str _name cdef object _lower_bound diff --git a/src/sage/numerical/mip.pyx b/src/sage/numerical/mip.pyx index 1e6e6caa7bd..803724bf29b 100644 --- a/src/sage/numerical/mip.pyx +++ b/src/sage/numerical/mip.pyx @@ -3237,7 +3237,7 @@ class MIPSolverException(RuntimeError): pass -cdef class MIPVariable(SageObject): +cdef class MIPVariable(FiniteFamily): r""" ``MIPVariable`` is a variable used by the class ``MixedIntegerLinearProgram``. @@ -3286,17 +3286,16 @@ cdef class MIPVariable(SageObject): MIPVariable with 0 real components, >= 0 """ - self._dict = {} + super().__init__({}) self._p = mip self._vtype = vtype self._lower_bound = lower_bound self._upper_bound = upper_bound self._name = name - self._dynamic_indices = True if indices is not None: for i in indices: self[i] # creates component - self._dynamic_indices = False + self._keys = indices def __copy__(self): r""" @@ -3398,9 +3397,9 @@ cdef class MIPVariable(SageObject): """ cdef int j - if i in self._dict: - return self._dict[i] - if not self._dynamic_indices: + if i in self._dictionary: + return self._dictionary[i] + if self._keys is not None: raise IndexError("{} does not index a component of {}".format(i, self)) zero = self._p._backend.zero() name = self._name + "[" + str(i) + "]" if self._name else None @@ -3415,7 +3414,7 @@ cdef class MIPVariable(SageObject): name=name) v = self._p.linear_functions_parent()({j : 1}) self._p._variables[v] = j - self._dict[i] = v + self._dictionary[i] = v return v def copy_for_mip(self, mip): @@ -3461,8 +3460,8 @@ cdef class MIPVariable(SageObject): """ cdef MIPVariable cp = type(self)(mip, self._vtype, self._name, self._lower_bound, self._upper_bound) - cp._dict = copy(self._dict) - cp._dynamic_indices = self._dynamic_indices + cp._dictionary = copy(self._dictionary) + cp._keys = self._keys return cp def set_min(self, min): @@ -3501,7 +3500,7 @@ cdef class MIPVariable(SageObject): """ self._lower_bound = min - for v in self._dict.values(): + for v in self._dictionary.values(): self._p.set_min(v,min) def set_max(self, max): @@ -3537,7 +3536,7 @@ cdef class MIPVariable(SageObject): True """ self._upper_bound = max - for v in self._dict.values(): + for v in self._dictionary.values(): self._p.set_max(v,max) def _repr_(self): @@ -3570,9 +3569,9 @@ cdef class MIPVariable(SageObject): """ s = 'MIPVariable{0} with {1} {2} component{3}'.format( " " + self._name if self._name else "", - len(self._dict), + len(self._dictionary), {0:"binary", -1:"real", 1:"integer"}[self._vtype], - "s" if len(self._dict) != 1 else "") + "s" if len(self._dictionary) != 1 else "") if (self._vtype != 0) and (self._lower_bound is not None): s += ', >= {0}'.format(self._lower_bound) if (self._vtype != 0) and (self._upper_bound is not None): @@ -3591,11 +3590,11 @@ cdef class MIPVariable(SageObject): sage: sorted(v.keys()) [0, 1] """ - return self._dict.keys() + return self._dictionary.keys() def items(self): r""" - Return the pairs (keys,value) contained in the dictionary. + Return the pairs (keys, value) contained in the dictionary. EXAMPLES:: @@ -3605,7 +3604,7 @@ cdef class MIPVariable(SageObject): sage: sorted(v.items()) [(0, x_0), (1, x_1)] """ - return self._dict.items() + return self._dictionary.items() def values(self): r""" @@ -3619,7 +3618,7 @@ cdef class MIPVariable(SageObject): sage: sorted(v.values(), key=str) [x_0, x_1] """ - return self._dict.values() + return self._dictionary.values() def mip(self): r""" diff --git a/src/sage/plot/plot3d/base.pyx b/src/sage/plot/plot3d/base.pyx index 7588cde2e27..8f69ed49725 100644 --- a/src/sage/plot/plot3d/base.pyx +++ b/src/sage/plot/plot3d/base.pyx @@ -285,13 +285,8 @@ cdef class Graphics3d(SageObject): tachyon.png.save_as(preview_png) else: # Java needs absolute paths - # On cygwin, they should be native ones scene_native = scene_zip - if sys.platform == 'cygwin': - import cygwin - scene_native = cygwin.cygpath(scene_native, 'w') - script = '''set defaultdirectory "{0}"\nscript SCRIPT\n'''.format(scene_native) jdata.export_image(targetfile=preview_png, datafile=script, image_type="PNG", diff --git a/src/sage/quadratic_forms/quadratic_form__automorphisms.py b/src/sage/quadratic_forms/quadratic_form__automorphisms.py index 89b2c079478..175f447bbea 100644 --- a/src/sage/quadratic_forms/quadratic_form__automorphisms.py +++ b/src/sage/quadratic_forms/quadratic_form__automorphisms.py @@ -349,7 +349,7 @@ def automorphisms(self): 48 sage: 2^3 * factorial(3) 48 - sage: len(Q.automorphisms()) + sage: len(Q.automorphisms()) # needs sage.libs.gap 48 :: @@ -357,14 +357,14 @@ def automorphisms(self): sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Q.number_of_automorphisms() 16 - sage: aut = Q.automorphisms() - sage: len(aut) + sage: aut = Q.automorphisms() # needs sage.libs.gap + sage: len(aut) # needs sage.libs.gap 16 - sage: all(Q(M) == Q for M in aut) + sage: all(Q(M) == Q for M in aut) # needs sage.libs.gap True sage: Q = QuadraticForm(ZZ, 3, [2, 1, 2, 2, 1, 3]) - sage: sorted(Q.automorphisms()) + sage: sorted(Q.automorphisms()) # needs sage.libs.gap [ [-1 0 0] [1 0 0] [ 0 -1 0] [0 1 0] diff --git a/src/sage/repl/rich_output/backend_ipython.py b/src/sage/repl/rich_output/backend_ipython.py index 10ccdc0c2c8..ba17b9244b4 100644 --- a/src/sage/repl/rich_output/backend_ipython.py +++ b/src/sage/repl/rich_output/backend_ipython.py @@ -419,13 +419,6 @@ def threejs_offline_scripts(self): script = os.path.join(THREEJS_DIR, '{}/three.min.js'.format(_required_threejs_version())) - if sys.platform == 'cygwin': - import cygwin - - def normpath(p): - return 'file:///' + cygwin.cygpath(p, 'w').replace('\\', '/') - script = normpath(script) - return '\n'.format(script) diff --git a/src/sage/rings/finite_rings/integer_mod_ring.py b/src/sage/rings/finite_rings/integer_mod_ring.py index 0bb4ce21f4b..eb35543a20e 100644 --- a/src/sage/rings/finite_rings/integer_mod_ring.py +++ b/src/sage/rings/finite_rings/integer_mod_ring.py @@ -617,13 +617,13 @@ def multiplicative_subgroups(self): EXAMPLES:: sage: # needs sage.groups - sage: Integers(5).multiplicative_subgroups() + sage: Integers(5).multiplicative_subgroups() # optional - gap_package_polycyclic ((2,), (4,), ()) - sage: Integers(15).multiplicative_subgroups() + sage: Integers(15).multiplicative_subgroups() # optional - gap_package_polycyclic ((11, 7), (11, 4), (2,), (11,), (14,), (7,), (4,), ()) - sage: Integers(2).multiplicative_subgroups() + sage: Integers(2).multiplicative_subgroups() # optional - gap_package_polycyclic ((),) - sage: len(Integers(341).multiplicative_subgroups()) + sage: len(Integers(341).multiplicative_subgroups()) # optional - gap_package_polycyclic 80 TESTS:: @@ -632,7 +632,7 @@ def multiplicative_subgroups(self): ((),) sage: IntegerModRing(2).multiplicative_subgroups() # needs sage.groups ((),) - sage: IntegerModRing(3).multiplicative_subgroups() # needs sage.groups + sage: IntegerModRing(3).multiplicative_subgroups() # needs sage.groups # optional - gap_package_polycyclic ((2,), ()) """ return tuple(tuple(g.value() for g in H.gens()) diff --git a/src/sage/rings/number_field/galois_group.py b/src/sage/rings/number_field/galois_group.py index 2e6b876e541..9ea602dc1aa 100644 --- a/src/sage/rings/number_field/galois_group.py +++ b/src/sage/rings/number_field/galois_group.py @@ -10,9 +10,9 @@ """ from sage.structure.sage_object import SageObject -from sage.groups.galois_group import _alg_key, GaloisGroup_perm, GaloisSubgroup_perm +from sage.groups.galois_group import _alg_key +from sage.groups.galois_group_perm import GaloisGroup_perm, GaloisSubgroup_perm from sage.groups.perm_gps.permgroup import standardize_generator - from sage.groups.perm_gps.permgroup_element import PermutationGroupElement from sage.misc.superseded import deprecation from sage.misc.cachefunc import cached_method diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index e34606e085b..77048cd3b79 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -371,18 +371,20 @@ def NumberField(polynomial, name=None, check=True, names=None, embedding=None, One can embed into any other field:: - sage: K. = NumberField(x^3-2, embedding=CC.gen()-0.6) + sage: K. = NumberField(x^3 - 2, embedding=CC.gen() - 0.6) sage: CC(a) -0.629960524947436 + 1.09112363597172*I - sage: L = Qp(5) # needs sage.rings.padics + + sage: # needs sage.rings.padics + sage: L = Qp(5) sage: f = polygen(L)^3 - 2 - sage: K. = NumberField(x^3-2, embedding=f.roots()[0][0]) + sage: K. = NumberField(x^3 - 2, embedding=f.roots()[0][0]) sage: a + L(1) 4 + 2*5^2 + 2*5^3 + 3*5^4 + 5^5 + 4*5^6 + 2*5^8 + 3*5^9 + 4*5^12 + 4*5^14 + 4*5^15 + 3*5^16 + 5^17 + 5^18 + 2*5^19 + O(5^20) - sage: L. = NumberField(x^6-x^2+1/10, embedding=1) - sage: K. = NumberField(x^3-x+1/10, embedding=b^2) - sage: a+b + sage: L. = NumberField(x^6 - x^2 + 1/10, embedding=1) + sage: K. = NumberField(x^3 - x + 1/10, embedding=b^2) + sage: a + b b^2 + b sage: CC(a) == CC(b)^2 True @@ -409,7 +411,7 @@ def NumberField(polynomial, name=None, check=True, names=None, embedding=None, Note that the codomain of the embedding must be ``QQbar`` or ``AA`` for this to work (see :trac:`20184`):: - sage: N. = NumberField(x^3 + 2,embedding=1) + sage: N. = NumberField(x^3 + 2, embedding=1) sage: 1 < g False sage: g > 1 @@ -493,7 +495,7 @@ def NumberField(polynomial, name=None, check=True, names=None, embedding=None, The following has been fixed in :trac:`8800`:: sage: P. = QQ[] - sage: K. = NumberField(x^3 - 5,embedding=0) + sage: K. = NumberField(x^3 - 5, embedding=0) sage: L. = K.extension(x^2 + a) sage: F, R = L.construction() sage: F(R) == L # indirect doctest @@ -1561,7 +1563,7 @@ def construction(self): :: sage: P. = QQ[] - sage: K. = NumberField(x^3-5,embedding=0) + sage: K. = NumberField(x^3-5, embedding=0) sage: L. = K.extension(x^2+a) sage: a*b a*b @@ -3391,23 +3393,23 @@ def dirichlet_group(self): sage: K. = NumberField(x^3 + x^2 - 36*x - 4) sage: K.conductor() 109 - sage: K.dirichlet_group() + sage: K.dirichlet_group() # optional - gap_package_polycyclic [Dirichlet character modulo 109 of conductor 1 mapping 6 |--> 1, Dirichlet character modulo 109 of conductor 109 mapping 6 |--> zeta3, Dirichlet character modulo 109 of conductor 109 mapping 6 |--> -zeta3 - 1] sage: K = CyclotomicField(44) sage: L = K.subfields(5)[0][0] - sage: X = L.dirichlet_group() - sage: X + sage: X = L.dirichlet_group() # optional - gap_package_polycyclic + sage: X # optional - gap_package_polycyclic [Dirichlet character modulo 11 of conductor 1 mapping 2 |--> 1, Dirichlet character modulo 11 of conductor 11 mapping 2 |--> zeta5, Dirichlet character modulo 11 of conductor 11 mapping 2 |--> zeta5^2, Dirichlet character modulo 11 of conductor 11 mapping 2 |--> zeta5^3, Dirichlet character modulo 11 of conductor 11 mapping 2 |--> -zeta5^3 - zeta5^2 - zeta5 - 1] - sage: X[4]^2 + sage: X[4]^2 # optional - gap_package_polycyclic Dirichlet character modulo 11 of conductor 11 mapping 2 |--> zeta5^3 - sage: X[4]^2 in X + sage: X[4]^2 in X # optional - gap_package_polycyclic True """ # todo : turn this into an abelian group rather than a list. @@ -8224,7 +8226,7 @@ def _coerce_from_other_number_field(self, x): The following was fixed in :trac:`8800`:: sage: P. = QQ[] - sage: K. = NumberField(x^3 - 5,embedding=0) + sage: K. = NumberField(x^3 - 5, embedding=0) sage: L. = K.extension(x^2 + a) sage: F,R = L.construction() sage: F(R) == L #indirect doctest @@ -12711,12 +12713,12 @@ def _splitting_classes_gens_(K, m, d): sage: L = K.subfields(20)[0][0] sage: L.conductor() 101 - sage: _splitting_classes_gens_(L,101,20) # needs sage.libs.gap + sage: _splitting_classes_gens_(L,101,20) # needs sage.libs.gap # optional - gap_package_polycyclic [95] sage: K = CyclotomicField(44) sage: L = K.subfields(4)[0][0] - sage: _splitting_classes_gens_(L,44,4) # needs sage.libs.gap + sage: _splitting_classes_gens_(L,44,4) # needs sage.libs.gap # optional - gap_package_polycyclic [37] sage: K = CyclotomicField(44) @@ -12728,7 +12730,7 @@ def _splitting_classes_gens_(K, m, d): with zeta44_0 = 3.837971894457990? sage: L.conductor() 11 - sage: _splitting_classes_gens_(L,11,5) # needs sage.libs.gap + sage: _splitting_classes_gens_(L,11,5) # needs sage.libs.gap # optional - gap_package_polycyclic [10] """ diff --git a/src/sage/rings/number_field/number_field_ideal.py b/src/sage/rings/number_field/number_field_ideal.py index 19c9e95ed39..28105221ac7 100644 --- a/src/sage/rings/number_field/number_field_ideal.py +++ b/src/sage/rings/number_field/number_field_ideal.py @@ -1002,6 +1002,7 @@ def is_maximal(self): EXAMPLES:: + sage: x = polygen(ZZ) sage: K. = NumberField(x^3 + 3); K Number Field in a with defining polynomial x^3 + 3 sage: K.ideal(5).is_maximal() @@ -1017,6 +1018,7 @@ def is_prime(self): EXAMPLES:: + sage: x = polygen(ZZ) sage: K. = NumberField(x^2 - 17); K Number Field in a with defining polynomial x^2 - 17 sage: K.ideal(5).is_prime() # inert prime @@ -1031,7 +1033,7 @@ def is_prime(self): Check that we do not factor the norm of the ideal, this used to take half an hour, see :trac:`33360`:: - sage: K. = NumberField([x^2-2,x^2-3,x^2-5]) + sage: K. = NumberField([x^2 - 2, x^2 - 3, x^2 - 5]) sage: t = (((-2611940*c + 1925290/7653)*b - 1537130/7653*c ....: + 10130950)*a + (1343014/7653*c - 8349770)*b ....: + 6477058*c - 2801449990/4002519) @@ -1112,6 +1114,7 @@ def _cache_bnfisprincipal(self, proof=None, gens=False): Check that no warnings are triggered from PARI/GP (see :trac:`30801`):: + sage: x = polygen(ZZ) sage: K. = NumberField(x^2 - x + 112941801) sage: I = K.ideal((112941823, a + 49942513)) sage: I.is_principal() @@ -1494,7 +1497,7 @@ def decomposition_group(self): EXAMPLES:: - sage: QuadraticField(-23, 'w').primes_above(7)[0].decomposition_group() + sage: QuadraticField(-23, 'w').primes_above(7)[0].decomposition_group() # needs sage.groups Subgroup generated by [(1,2)] of (Galois group 2T1 (S2) with order 2 of x^2 + 23) """ return self.number_field().galois_group().decomposition_group(self) @@ -1510,9 +1513,9 @@ def ramification_group(self, v): EXAMPLES:: - sage: QuadraticField(-23, 'w').primes_above(23)[0].ramification_group(0) + sage: QuadraticField(-23, 'w').primes_above(23)[0].ramification_group(0) # needs sage.groups Subgroup generated by [(1,2)] of (Galois group 2T1 (S2) with order 2 of x^2 + 23) - sage: QuadraticField(-23, 'w').primes_above(23)[0].ramification_group(1) + sage: QuadraticField(-23, 'w').primes_above(23)[0].ramification_group(1) # needs sage.groups Subgroup generated by [()] of (Galois group 2T1 (S2) with order 2 of x^2 + 23) """ @@ -1528,7 +1531,7 @@ def inertia_group(self): EXAMPLES:: - sage: QuadraticField(-23, 'w').primes_above(23)[0].inertia_group() + sage: QuadraticField(-23, 'w').primes_above(23)[0].inertia_group() # needs sage.groups Subgroup generated by [(1,2)] of (Galois group 2T1 (S2) with order 2 of x^2 + 23) """ return self.ramification_group(0) @@ -1594,7 +1597,7 @@ def artin_symbol(self): EXAMPLES:: - sage: QuadraticField(-23, 'w').primes_above(7)[0].artin_symbol() + sage: QuadraticField(-23, 'w').primes_above(7)[0].artin_symbol() # needs sage.groups (1,2) """ return self.number_field().galois_group().artin_symbol(self) diff --git a/src/sage/rings/number_field/totallyreal_rel.py b/src/sage/rings/number_field/totallyreal_rel.py index af44569f167..47ee18456f7 100644 --- a/src/sage/rings/number_field/totallyreal_rel.py +++ b/src/sage/rings/number_field/totallyreal_rel.py @@ -51,7 +51,7 @@ sage: [ f[0] for f in ls ] [725, 1125, 1600, 2000, 2225, 2525, 3600, 4225, 4400, 4525, 5125, 5225, 5725, 6125, 7225, 7600, 7625, 8000, 8525, 8725, 9225] - sage: [NumberField(ZZx(x[1]), 't').is_galois() for x in ls] + sage: [NumberField(ZZx(x[1]), 't').is_galois() for x in ls] # needs sage.groups [False, True, True, True, False, False, True, True, False, False, False, False, False, True, True, False, False, True, False, False, False] Eight out of 21 such fields are Galois (with Galois group `C_4` diff --git a/src/sage/sets/family.pxd b/src/sage/sets/family.pxd new file mode 100644 index 00000000000..f5d8f755ecc --- /dev/null +++ b/src/sage/sets/family.pxd @@ -0,0 +1,11 @@ +from sage.structure.parent cimport Parent + + +cdef class AbstractFamily(Parent): + cdef public __custom_name + cdef dict __dict__ # enables Python attributes as needed for EnumeratedSets() + + +cdef class FiniteFamily(AbstractFamily): + cdef public dict _dictionary + cdef public object _keys diff --git a/src/sage/sets/family.py b/src/sage/sets/family.pyx similarity index 96% rename from src/sage/sets/family.py rename to src/sage/sets/family.pyx index f3c3d0a7556..d1c102261a8 100644 --- a/src/sage/sets/family.py +++ b/src/sage/sets/family.pyx @@ -22,10 +22,16 @@ Category of finite enumerated sets """ -# **************************************************************************** -# Copyright (C) 2008 Nicolas Thiery , -# Mike Hansen , -# Florent Hivert +# ***************************************************************************** +# Copyright (C) 2008-2017 Nicolas Thiery +# 2008-2009 Mike Hansen +# 2008-2010 Florent Hivert +# 2013-2021 Travis Scrimshaw +# 2014 Nathann Cohen +# 2017 Erik M. Bray +# 2018 Frédéric Chapoton +# 2019 Markus Wageringel +# 2022-2023 Matthias Koeppe # # 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 @@ -38,19 +44,18 @@ from pprint import pformat, saferepr from collections.abc import Iterable -from sage.misc.abstract_method import abstract_method -from sage.misc.cachefunc import cached_method -from sage.structure.parent import Parent from sage.categories.enumerated_sets import EnumeratedSets from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets -from sage.sets.finite_enumerated_set import FiniteEnumeratedSet -from sage.misc.lazy_import import lazy_import -from sage.rings.integer import Integer +from sage.misc.cachefunc import cached_method from sage.misc.call import AttrCallObject -from sage.sets.non_negative_integers import NonNegativeIntegers +from sage.misc.lazy_import import LazyImport from sage.rings.infinity import Infinity -lazy_import('sage.combinat.combinat', 'CombinatorialClass') +from sage.rings.integer import Integer +from sage.sets.finite_enumerated_set import FiniteEnumeratedSet +from sage.sets.non_negative_integers import NonNegativeIntegers + +CombinatorialClass = LazyImport('sage.combinat.combinat', 'CombinatorialClass') def Family(indices, function=None, hidden_keys=[], hidden_function=None, lazy=False, name=None): @@ -396,8 +401,7 @@ def Family(indices, function=None, hidden_keys=[], hidden_function=None, lazy=Fa return TrivialFamily(indices) if isinstance(indices, (FiniteFamily, LazyFamily, TrivialFamily)): return indices - if (indices in EnumeratedSets() - or isinstance(indices, CombinatorialClass)): + if indices in EnumeratedSets(): return EnumeratedFamily(indices) if isinstance(indices, Iterable): return TrivialFamily(indices) @@ -418,7 +422,7 @@ def Family(indices, function=None, hidden_keys=[], hidden_function=None, lazy=Fa keys=indices) -class AbstractFamily(Parent): +cdef class AbstractFamily(Parent): """ The abstract class for family @@ -436,7 +440,6 @@ def hidden_keys(self): """ return [] - @abstract_method def keys(self): """ Return the keys of the family. @@ -447,8 +450,8 @@ def keys(self): sage: sorted(f.keys()) [3, 4, 7] """ + raise NotImplementedError - @abstract_method(optional=True) def values(self): """ Return the elements (values) of this family. @@ -459,6 +462,7 @@ def values(self): sage: sorted(f.values()) ['aa', 'bb', 'cc'] """ + raise NotImplementedError def items(self): """ @@ -535,7 +539,8 @@ def inverse_family(self): return Family({self[k]: k for k in self.keys()}) -class FiniteFamily(AbstractFamily): + +cdef class FiniteFamily(AbstractFamily): r""" A :class:`FiniteFamily` is an associative container which models a finite family `(f_i)_{i \in I}`. Its elements `f_i` are therefore its @@ -712,8 +717,8 @@ def __eq__(self, other): False """ return (isinstance(other, self.__class__) and - self._keys == other._keys and - self._dictionary == other._dictionary) + self._keys == ( other)._keys and + self._dictionary == ( other)._dictionary) def _repr_(self): """ @@ -869,15 +874,17 @@ def __getitem__(self, i): ... KeyError """ - if i in self._dictionary: - return self._dictionary[i] - - if i not in self.hidden_dictionary: - if i not in self._hidden_keys: - raise KeyError - self.hidden_dictionary[i] = self.hidden_function(i) - - return self.hidden_dictionary[i] + try: + return FiniteFamily.__getitem__(self, i) + except KeyError: + try: + return self.hidden_dictionary[i] + except KeyError: + if i not in self._hidden_keys: + raise KeyError + v = self.hidden_function(i) + self.hidden_dictionary[i] = v + return v def hidden_keys(self): """ @@ -902,11 +909,11 @@ def __getstate__(self): """ from sage.misc.fpickle import pickle_function f = pickle_function(self.hidden_function) - return {'dictionary': self._dictionary, - 'hidden_keys': self._hidden_keys, - 'hidden_dictionary': self.hidden_dictionary, - 'hidden_function': f, - 'keys': self._keys} + state = super().__getstate__() + state.update({'hidden_keys': self._hidden_keys, + 'hidden_dictionary': self.hidden_dictionary, + 'hidden_function': f}) + return state def __setstate__(self, d): """ @@ -922,7 +929,7 @@ def __setstate__(self, d): 6 """ hidden_function = d['hidden_function'] - if isinstance(hidden_function, str): + if isinstance(hidden_function, (str, bytes)): # Let's assume that hidden_function is an unpickled function. from sage.misc.fpickle import unpickle_function hidden_function = unpickle_function(hidden_function) @@ -1074,7 +1081,7 @@ def _repr_(self): """ if self.function_name is not None: name = self.function_name + "(i)" - elif isinstance(self.function, type(lambda x: 1)): + elif isinstance(self.function, types.LambdaType): name = self.function.__name__ name = name + "(i)" else: diff --git a/src/sage/tests/books/judson-abstract-algebra/cyclic-sage.py b/src/sage/tests/books/judson-abstract-algebra/cyclic-sage.py index e54352bafe9..5f8cd284b52 100644 --- a/src/sage/tests/books/judson-abstract-algebra/cyclic-sage.py +++ b/src/sage/tests/books/judson-abstract-algebra/cyclic-sage.py @@ -252,19 +252,19 @@ ~~~~~~~~~~~~~~~~~~~~~~ :: - sage: H = G.subgroup([a^2]) + sage: H = G.subgroup([a^2]) # optional - gap_package_polycyclic sage: H.order() 7 ~~~~~~~~~~~~~~~~~~~~~~ :: - sage: K = G.subgroup([a^12]) + sage: K = G.subgroup([a^12]) # optional - gap_package_polycyclic sage: K.order() 7 ~~~~~~~~~~~~~~~~~~~~~~ :: - sage: allsg = G.subgroups(); allsg + sage: allsg = G.subgroups(); allsg # optional - gap_package_polycyclic [Multiplicative Abelian subgroup isomorphic to C2 x C7 generated by {a}, Multiplicative Abelian subgroup isomorphic to C7 generated by {a^2}, Multiplicative Abelian subgroup isomorphic to C2 generated by {a^7}, @@ -272,8 +272,8 @@ ~~~~~~~~~~~~~~~~~~~~~~ :: - sage: sub = allsg[2] - sage: sub.order() + sage: sub = allsg[2] # optional - gap_package_polycyclic + sage: sub.order() # optional - gap_package_polycyclic 2 ~~~~~~~~~~~~~~~~~~~~~~ :: diff --git a/src/sage/tests/gap_packages.py b/src/sage/tests/gap_packages.py index b13bba24a83..cf02fa5cf6e 100644 --- a/src/sage/tests/gap_packages.py +++ b/src/sage/tests/gap_packages.py @@ -10,11 +10,11 @@ Status Package GAP Output +--------+---------+------------+ - sage: test_packages(['atlasrep', 'tomlib']) + sage: test_packages(['primgrp', 'smallgrp']) Status Package GAP Output +--------+----------+------------+ - atlasrep true - tomlib true + primgrp true + smallgrp true """ import os @@ -116,6 +116,16 @@ def all_installed_packages(ignore_dot_gap=False, gap=None): else: paths = [str(p) for p in gap('GAPInfo.RootPaths')] + # When GAP_ROOT_PATHS begins or ends with a semicolon (to append + # or prepend to the default list), the list of "gap" root paths + # will sometimes contain duplicates while the list for libgap will + # not. I don't know why this is: the appending/prepending does + # work as intended, even for libgap, so the issue is not that + # appending/prepending don't work at all for libgap. For lack of a + # better idea, we deduplicate here to avoid listing the same + # packages twice for the non-lib "gap" interface. + paths = set(paths) + packages = [] for path in paths: if ignore_dot_gap and path.endswith('/.gap/'): diff --git a/src/sage/topology/simplicial_complex_morphism.py b/src/sage/topology/simplicial_complex_morphism.py index 3d4fc063198..0030c4edee8 100644 --- a/src/sage/topology/simplicial_complex_morphism.py +++ b/src/sage/topology/simplicial_complex_morphism.py @@ -244,20 +244,43 @@ def __call__(self, x, orientation=False): (0, 1) sage: g(Simplex([0,1]), orientation=True) # needs sage.modules ((0, 1), -1) + + TESTS: + + Test that the problem in :issue:`36849` has been fixed:: + + sage: S = SimplicialComplex([[1,2]],is_mutable=False).barycentric_subdivision() + sage: T = SimplicialComplex([[1,2],[2,3],[1,3]],is_mutable=False).barycentric_subdivision() + sage: f = {x[0]:x[0] for x in S.cells()[0]} + sage: H = Hom(S,T) + sage: z = H(f) + sage: z.associated_chain_complex_morphism() + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring """ dim = self.domain().dimension() if not isinstance(x, Simplex) or x.dimension() > dim or x not in self.domain().faces()[x.dimension()]: raise ValueError("x must be a simplex of the source of f") tup = x.tuple() - fx = [] - for j in tup: - fx.append(self._vertex_dictionary[j]) + fx = [self._vertex_dictionary[j] for j in tup] if orientation: from sage.algebras.steenrod.steenrod_algebra_misc import convert_perm from sage.combinat.permutation import Permutation if len(set(fx)) == len(tup): - oriented = Permutation(convert_perm(fx)).signature() + # We need to compare the image simplex, as given in + # the order specified by self, with its orientation in + # the codomain. + image = Simplex(set(fx)) + Y_faces = self.codomain()._n_cells_sorted(image.dimension()) + idx = Y_faces.index(image) + actual_image = Y_faces[idx] + # The signature of the permutation specified by self: + sign_image = Permutation(convert_perm(fx)).signature() + # The signature of the permutation of the simplex in the domain: + sign_simplex = Permutation(convert_perm(actual_image)).signature() + oriented = sign_image * sign_simplex else: oriented = 1 return (Simplex(set(fx)), oriented) diff --git a/src/sage/version.py b/src/sage/version.py index 92ef577e70a..30dfeeace6a 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,5 +1,5 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '10.3.beta2' -date = '2023-12-13' -banner = 'SageMath version 10.3.beta2, Release Date: 2023-12-13' +version = '10.3.beta3' +date = '2023-12-18' +banner = 'SageMath version 10.3.beta3, Release Date: 2023-12-18' diff --git a/tox.ini b/tox.ini index 6c928aa2623..4415cab94c2 100644 --- a/tox.ini +++ b/tox.ini @@ -206,6 +206,7 @@ setenv = # # https://hub.docker.com/_/ubuntu?tab=description # as of 2023-05, latest=jammy=22.04, rolling=lunar=23.04, devel=mantic=23.10 + # ubuntu-focal does not have libgap-dev # ubuntu: SYSTEM=debian ubuntu: BASE_IMAGE=ubuntu @@ -221,6 +222,7 @@ setenv = ubuntu-bionic: BASE_TAG=bionic ubuntu-bionic: IGNORE_MISSING_SYSTEM_PACKAGES=yes ubuntu-focal: BASE_TAG=focal + ubuntu-focal: IGNORE_MISSING_SYSTEM_PACKAGES=yes ubuntu-jammy: BASE_TAG=jammy ubuntu-lunar: BASE_TAG=lunar ubuntu-mantic: BASE_TAG=mantic