From 627b9f15c516ed2aafffb635a4fab7d78d8b12d6 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Sun, 13 Oct 2024 20:06:09 +0900 Subject: [PATCH 1/5] Introduce negated optional tag --- src/doc/en/developer/coding_basics.rst | 25 ++++++++++++++++++--- src/sage/databases/sloane.py | 22 +++++++++++++++---- src/sage/doctest/parsing.py | 18 ++++++++-------- src/sage/graphs/graph_latex.py | 3 ++- src/sage/groups/perm_gps/cubegroup.py | 30 +++++++++++++------------- 5 files changed, 66 insertions(+), 32 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 5e15c4b2455..bd89514cae0 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -1258,13 +1258,32 @@ framework. Here is a comprehensive list: - **optional/needs:** A line tagged with ``optional - FEATURE`` or ``needs FEATURE`` is not tested unless the ``--optional=KEYWORD`` flag - is passed to ``sage -t`` (see - :ref:`section-optional-doctest-flag`). The main applications are: + is passed to ``sage -t`` (see :ref:`section-optional-doctest-flag`). + + If ``FEATURE`` starts with an exclamation point ``!``, then the condition is + negated, that is, the doctest runs only if the feature is not available. + + The main applications are: - **optional packages:** When a line requires an optional package to be - installed (e.g. the ``sloane_database`` package):: + installed (e.g. the ``rubiks`` package):: + + sage: C = RubiksCube("R*L") + sage: C.solve() # optional - rubiks (a hybrid algorithm is used) + 'L R' + sage: C.solve() # optional - !rubiks (GAP is used) + 'L*R' + + - **optional database:** When a line requires a database to be present:: sage: SloaneEncyclopedia[60843] # optional - sloane_database + [1, 6, 21, 107, 47176870] + + sage: SloaneEncyclopedia[60843] # optional - !sloane_database + Traceback (most recent call last): + ... + OSError: The Sloane Encyclopedia database must be installed. Use e.g. + 'SloaneEncyclopedia.install()' to download and install it. - **internet:** For lines that require an internet connection:: diff --git a/src/sage/databases/sloane.py b/src/sage/databases/sloane.py index 78fc268b486..aac252d84a8 100644 --- a/src/sage/databases/sloane.py +++ b/src/sage/databases/sloane.py @@ -12,7 +12,7 @@ :: sage: SloaneEncyclopedia[60843] # optional - sloane_database - [1, 6, 21, 107] + [1, 6, 21, 107, 47176870] To get the name of a sequence, type @@ -149,6 +149,17 @@ def __len__(self): self.load() return len(self.__data__) + def is_installed(self): + """ + Check if a local copy of the encyclopedia is installed. + + EXAMPLES:: + + sage: SloaneEncyclopedia.is_installed() # optional - sloane_database + True + """ + return os.path.exists(self.__file__) and os.path.exists(self.__file_names__) + def find(self, seq, maxresults=30): """ Return a list of all sequences which have seq as a subsequence, up @@ -274,7 +285,7 @@ def load(self): for L in file_seq: if len(L) == 0: continue - m = entry.search(L) + m = entry.search(L.decode('utf-8')) if m: seqnum = int(m.group('num')) msg = m.group('body').strip() @@ -287,10 +298,13 @@ def load(self): for L in file_names: if not L: continue - m = entry.search(L) + m = entry.search(L.decode('utf-8')) if m: seqnum = int(m.group('num')) - self.__data__[seqnum][3] = m.group('body').strip() + if seqnum in self.__data__: + self.__data__[seqnum][3] = m.group('body').strip() + else: + self.__data__[seqnum] = [seqnum, None, 'unknown', m.group('body').strip()] file_names.close() self.__loaded_names__ = True except KeyError: diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index d9b054ae2dd..97d01a6a120 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -58,7 +58,7 @@ special_optional_regex = ( "py2|long time|not implemented|not tested|optional|needs|known bug" ) -tag_with_explanation_regex = r"((?:\w|[.])*)\s*(?:\((?P.*?)\))?" +tag_with_explanation_regex = r"((?:!?\w|[.])*)\s*(?:\((?P.*?)\))?" optional_regex = re.compile( rf"[^ a-z]\s*(?P{special_optional_regex})(?:\s|[:-])*(?P(?:(?:{tag_with_explanation_regex})\s*)*)", re.IGNORECASE, @@ -1124,14 +1124,14 @@ def check_and_clear_tag_counts(): continue if self.optional_tags is not True: - extra = { - tag - for tag in optional_tags - if ( - tag not in self.optional_tags - and tag not in available_software - ) - } + extra = set() + for tag in optional_tags: + if tag not in self.optional_tags: + if tag.startswith('!'): + if tag[1:] in available_software: + extra.add(tag) + elif tag not in available_software: + extra.add(tag) if extra and any(tag in ["bug"] for tag in extra): # Bug only occurs on a specific platform? bug_platform = optional_tags_with_values.get("bug") diff --git a/src/sage/graphs/graph_latex.py b/src/sage/graphs/graph_latex.py index f0fb9329002..12fe5c6ed99 100644 --- a/src/sage/graphs/graph_latex.py +++ b/src/sage/graphs/graph_latex.py @@ -207,6 +207,7 @@ % \end{tikzpicture} + EXAMPLES: This example illustrates switching between the built-in styles when using the @@ -1566,7 +1567,7 @@ def tkz_picture(self): For a complicated vertex, a TeX box is used. :: sage: B = crystals.Tableaux(['B', 2], shape=[1]) - sage: latex(B) + sage: latex(B) # optional - !dot2tex \begin{tikzpicture} ... \newsavebox{\vertex} diff --git a/src/sage/groups/perm_gps/cubegroup.py b/src/sage/groups/perm_gps/cubegroup.py index 8eadbac39f1..fffdbe84753 100644 --- a/src/sage/groups/perm_gps/cubegroup.py +++ b/src/sage/groups/perm_gps/cubegroup.py @@ -1420,7 +1420,7 @@ def __richcmp__(self, other, op): return NotImplemented return richcmp(self._state, other._state, op) - def solve(self, algorithm='hybrid', timeout=15): + def solve(self, algorithm='default', timeout=15): r""" Solve the Rubik's cube. @@ -1428,17 +1428,14 @@ def solve(self, algorithm='hybrid', timeout=15): - ``algorithm`` -- must be one of the following: - - ``hybrid`` -- try ``kociemba`` for timeout seconds, then ``dietz`` - - ``kociemba`` -- use Dik T. Winter's program - (reasonable speed, few moves) - - ``dietz`` -- use Eric Dietz's cubex program - (fast but lots of moves) - - ``optimal`` -- use Michael Reid's optimal program - (may take a long time) + - ``hybrid`` -- (default) try ``kociemba`` for timeout seconds, then ``dietz`` + - ``kociemba`` -- use Dik T. Winter's program (reasonable speed, few moves) + - ``dietz`` -- use Eric Dietz's cubex program (fast but lots of moves) + - ``optimal`` -- use Michael Reid's optimal program (may take a long time) - ``gap`` -- use GAP word solution (can be slow) - Any choice other than ``gap`` requires the optional package - ``rubiks``. Otherwise, the ``gap`` algorithm is used. + Any choice other than ``gap`` requires the optional package ``rubiks``. + If the package is not installed, the ``gap`` algorithm is used by default. EXAMPLES:: @@ -1450,7 +1447,10 @@ def solve(self, algorithm='hybrid', timeout=15): solutions:: sage: s = C.solve('dietz'); s # optional - rubiks - "U' L' L' U L U' L U D L L D' L' D L' D' L D L' U' L D' L' U L' B' U' L' U B L D L D' U' L' U L B L B' L' U L U' L' F' L' F L' F L F' L' D' L' D D L D' B L B' L B' L B F' L F F B' L F' B D' D' L D B' B' L' D' B U' U' L' B' D' F' F' L D F'" + "U' L' L' U L U' L U D L L D' L' D L' D' L D L' U' L D' L' U L' B' + U' L' U B L D L D' U' L' U L B L B' L' U L U' L' F' L' F L' F L F' + L' D' L' D D L D' B L B' L B' L B F' L F F B' L F' B D' D' L D B' + B' L' D' B U' U' L' B' D' F' F' L D F'" sage: C2 = RubiksCube(s) # optional - rubiks sage: C == C2 # optional - rubiks True @@ -1458,11 +1458,11 @@ def solve(self, algorithm='hybrid', timeout=15): from sage.features.rubiks import Rubiks if Rubiks().is_present(): import sage.interfaces.rubik # here to avoid circular referencing + if algorithm == 'default': + algorithm = "hybrid" else: - algorithm = 'gap' - - if algorithm == "default": - algorithm = "hybrid" + if algorithm == 'default': + algorithm = 'gap' if algorithm == "hybrid": try: From d7db870dedecfa4d106338ed9e578a41de6d9876 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Sun, 13 Oct 2024 23:22:11 +0900 Subject: [PATCH 2/5] Edit optional needs tags explanation --- src/doc/en/developer/coding_basics.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index bd89514cae0..9b56de46ad0 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -6,7 +6,6 @@ General Conventions =================== - There are many ways to contribute to Sage, including sharing scripts and Jupyter notebooks that implement new functionality using Sage, improving to the Sage library, or to working on the many underlying @@ -1256,13 +1255,15 @@ framework. Here is a comprehensive list: Neither of this applies to files or directories which are explicitly given as command line arguments: those are always tested. -- **optional/needs:** A line tagged with ``optional - FEATURE`` - or ``needs FEATURE`` is not tested unless the ``--optional=KEYWORD`` flag - is passed to ``sage -t`` (see :ref:`section-optional-doctest-flag`). - - If ``FEATURE`` starts with an exclamation point ``!``, then the condition is +- **optional** or **needs:** A line tagged with ``optional - FEATURE`` or + ``needs FEATURE`` is tested if the feature is available in Sage. If + ``FEATURE`` starts with an exclamation point ``!``, then the condition is negated, that is, the doctest runs only if the feature is not available. + If the feature is included in the ``--optional=KEYWORD`` flag passed to + ``sage -t`` (see :ref:`section-optional-doctest-flag`), then the line is + tested regardless of the feature availability. + The main applications are: - **optional packages:** When a line requires an optional package to be @@ -1274,7 +1275,7 @@ framework. Here is a comprehensive list: sage: C.solve() # optional - !rubiks (GAP is used) 'L*R' - - **optional database:** When a line requires a database to be present:: + - **features:** When a line requires a feature to be present:: sage: SloaneEncyclopedia[60843] # optional - sloane_database [1, 6, 21, 107, 47176870] @@ -1285,7 +1286,7 @@ framework. Here is a comprehensive list: OSError: The Sloane Encyclopedia database must be installed. Use e.g. 'SloaneEncyclopedia.install()' to download and install it. - - **internet:** For lines that require an internet connection:: + For lines that require an internet connection:: sage: oeis(60843) # optional - internet A060843: Busy Beaver problem: a(n) = maximal number of steps that an From 71197264e74f9537d19fa5c1ae0e8fc1bb2f47f9 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Wed, 16 Oct 2024 06:54:48 +0900 Subject: [PATCH 3/5] Add features --- src/sage/features/dot2tex.py | 42 +++++++++++++++++++++ src/sage/features/sloane_database.py | 56 ++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 src/sage/features/dot2tex.py create mode 100644 src/sage/features/sloane_database.py diff --git a/src/sage/features/dot2tex.py b/src/sage/features/dot2tex.py new file mode 100644 index 00000000000..e9f97b6e704 --- /dev/null +++ b/src/sage/features/dot2tex.py @@ -0,0 +1,42 @@ +# sage_setup: distribution = sagemath-environment +r""" +Check for ``dot2tex`` +""" + +# ***************************************************************************** +# Copyright (C) 2024 Kwankyu Lee +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# https://www.gnu.org/licenses/ +# ***************************************************************************** + +from . import PythonModule + + +class dot2tex(PythonModule): + r""" + A :class:`sage.features.Feature` describing the presence of :ref:`dot2tex `. + + dot2tex is provided by an optional package in the Sage distribution. + + EXAMPLES:: + + sage: from sage.features.dot2tex import dot2tex + sage: dot2tex().is_present() # optional - dot2tex + FeatureTestResult('dot2tex', True) + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.dot2tex import dot2tex + sage: isinstance(dot2tex(), dot2tex) + True + """ + PythonModule.__init__(self, 'dot2tex', spkg='dot2tex') + + +def all_features(): + return [dot2tex()] diff --git a/src/sage/features/sloane_database.py b/src/sage/features/sloane_database.py new file mode 100644 index 00000000000..9abfbea8891 --- /dev/null +++ b/src/sage/features/sloane_database.py @@ -0,0 +1,56 @@ +# sage_setup: distribution = sagemath-environment +r""" +Feature for testing the presence of Sloane Online Encyclopedia of Integer Sequences +""" + +# **************************************************************************** +# Copyright (C) 2024 Kwankyu Lee +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from . import Feature + + +class SloaneOEIS(Feature): + r""" + A :class:`~sage.features.Feature` which describes the presence of + the Sloane Online Encyclopedia of Integer Sequences. + + EXAMPLES:: + + sage: from sage.features.sloane_database import SloaneOEIS + sage: bool(SloaneOEIS().is_present()) # optional - sloane_database + True + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.sloane_database import SloaneOEIS + sage: isinstance(SloaneOEIS(), SloaneOEIS) + True + """ + Feature.__init__(self, name='sloane_database', + description='Sloane Online Encyclopedia of Integer Sequences') + + def _is_present(self): + r""" + Return whether the database is installed. + + EXAMPLES:: + + sage: from sage.features.sloane_database import SloaneOEIS + sage: bool(SloaneOEIS().is_present()) # optional - !sloane_database + False + """ + from sage.databases.sloane import SloaneEncyclopedia + return SloaneEncyclopedia.is_installed() + + +def all_features(): + return [SloaneOEIS()] From 0812d9d5ea96a1d956450c9b6abaf3b18547dbff Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Wed, 16 Oct 2024 15:02:25 +0900 Subject: [PATCH 4/5] Fix modularization regression --- src/sage/features/sloane_database.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/features/sloane_database.py b/src/sage/features/sloane_database.py index 9abfbea8891..84aad5ce67a 100644 --- a/src/sage/features/sloane_database.py +++ b/src/sage/features/sloane_database.py @@ -40,7 +40,7 @@ def __init__(self): def _is_present(self): r""" - Return whether the database is installed. + Return whether the database is available. EXAMPLES:: @@ -48,7 +48,10 @@ def _is_present(self): sage: bool(SloaneOEIS().is_present()) # optional - !sloane_database False """ - from sage.databases.sloane import SloaneEncyclopedia + try: + from sage.databases.sloane import SloaneEncyclopedia + except ImportError: + return False return SloaneEncyclopedia.is_installed() From 4ca8c0dae5b1ac957985eddfdf5f928f5df9f839 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Thu, 17 Oct 2024 00:22:51 +0900 Subject: [PATCH 5/5] Delete a spurious empty line --- src/sage/graphs/graph_latex.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/graphs/graph_latex.py b/src/sage/graphs/graph_latex.py index 12fe5c6ed99..f50758496c4 100644 --- a/src/sage/graphs/graph_latex.py +++ b/src/sage/graphs/graph_latex.py @@ -207,7 +207,6 @@ % \end{tikzpicture} - EXAMPLES: This example illustrates switching between the built-in styles when using the