Skip to content

Commit

Permalink
sagemathgh-38810: Introduce negated optional tag
Browse files Browse the repository at this point in the history
    
<!-- ^ Please provide a concise and informative title. -->
<!-- ^ Don't put issue numbers in the title, do this in the PR
description below. -->
<!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method
to calculate 1 + 2". -->
<!-- v Describe your changes below in detail. -->
<!-- v Why is this change required? What problem does it solve? -->
<!-- v If this PR resolves an open issue, please link to it here. For
example, "Fixes sagemath#12345". -->

motivated by
sagemath#38764 (comment)

fixes
sagemath#38764 (comment)

example:
```sage
      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.
```
along the way, we

- define `sloane_database` feature (I wonder why it was missing)
- fix `SloaneEncyclopedia` with the code by @sheerluck: https://github.c
om/sheerluck)sagemath#34655 (comment)
1418189098
- fix the problem of `solve()` for rubiks cube of silently choosing the
gap algorithm even when an algorithm is explicitly specified.

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [x] I have updated the documentation and checked the documentation
preview.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on. For example,
-->
<!-- - sagemath#12345: short description why this is a dependency -->
<!-- - sagemath#34567: ... -->
    
URL: sagemath#38810
Reported by: Kwankyu Lee
Reviewer(s):
  • Loading branch information
Release Manager committed Nov 27, 2024
2 parents b9e396a + 03ffc18 commit edc24d5
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 36 deletions.
34 changes: 27 additions & 7 deletions src/doc/en/developer/coding_basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1256,17 +1255,38 @@ 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`). The main applications are:
- **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
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'

- **features:** When a line requires a feature 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::
For lines that require an internet connection::

sage: oeis(60843) # optional - internet
A060843: Busy Beaver problem: a(n) = maximal number of steps that an
Expand Down
22 changes: 18 additions & 4 deletions src/sage/databases/sloane.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand 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()
Expand All @@ -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:
Expand Down
18 changes: 9 additions & 9 deletions src/sage/doctest/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<cmd_explanation>.*?)\))?"
tag_with_explanation_regex = r"((?:!?\w|[.])*)\s*(?:\((?P<cmd_explanation>.*?)\))?"
optional_regex = re.compile(
rf"[^ a-z]\s*(?P<cmd>{special_optional_regex})(?:\s|[:-])*(?P<tags>(?:(?:{tag_with_explanation_regex})\s*)*)",
re.IGNORECASE,
Expand Down Expand Up @@ -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")
Expand Down
42 changes: 42 additions & 0 deletions src/sage/features/dot2tex.py
Original file line number Diff line number Diff line change
@@ -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 <spkg_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()]
59 changes: 59 additions & 0 deletions src/sage/features/sloane_database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# sage_setup: distribution = sagemath-environment
r"""
Feature for testing the presence of Sloane Online Encyclopedia of Integer Sequences
"""

# ****************************************************************************
# Copyright (C) 2024 Kwankyu Lee <[email protected]>
#
# 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 available.
EXAMPLES::
sage: from sage.features.sloane_database import SloaneOEIS
sage: bool(SloaneOEIS().is_present()) # optional - !sloane_database
False
"""
try:
from sage.databases.sloane import SloaneEncyclopedia
except ImportError:
return False
return SloaneEncyclopedia.is_installed()


def all_features():
return [SloaneOEIS()]
2 changes: 1 addition & 1 deletion src/sage/graphs/graph_latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -1566,7 +1566,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}
Expand Down
30 changes: 15 additions & 15 deletions src/sage/groups/perm_gps/cubegroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -1422,25 +1422,22 @@ 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.
INPUT:
- ``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::
Expand All @@ -1452,19 +1449,22 @@ 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
"""
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:
Expand Down

0 comments on commit edc24d5

Please sign in to comment.