Skip to content

Commit edc24d5

Browse files
author
Release Manager
committed
gh-38810: Introduce negated optional tag <!-- ^ 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 #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 #12345". --> motivated by #38764 (comment) fixes #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)#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, --> <!-- - #12345: short description why this is a dependency --> <!-- - #34567: ... --> URL: #38810 Reported by: Kwankyu Lee Reviewer(s):
2 parents b9e396a + 03ffc18 commit edc24d5

File tree

7 files changed

+171
-36
lines changed

7 files changed

+171
-36
lines changed

src/doc/en/developer/coding_basics.rst

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
General Conventions
77
===================
88

9-
109
There are many ways to contribute to Sage, including sharing scripts
1110
and Jupyter notebooks that implement new functionality using Sage,
1211
improving to the Sage library, or to working on the many underlying
@@ -1256,17 +1255,38 @@ framework. Here is a comprehensive list:
12561255
Neither of this applies to files or directories which are explicitly given
12571256
as command line arguments: those are always tested.
12581257

1259-
- **optional/needs:** A line tagged with ``optional - FEATURE``
1260-
or ``needs FEATURE`` is not tested unless the ``--optional=KEYWORD`` flag
1261-
is passed to ``sage -t`` (see
1262-
:ref:`section-optional-doctest-flag`). The main applications are:
1258+
- **optional** or **needs:** A line tagged with ``optional - FEATURE`` or
1259+
``needs FEATURE`` is tested if the feature is available in Sage. If
1260+
``FEATURE`` starts with an exclamation point ``!``, then the condition is
1261+
negated, that is, the doctest runs only if the feature is not available.
1262+
1263+
If the feature is included in the ``--optional=KEYWORD`` flag passed to
1264+
``sage -t`` (see :ref:`section-optional-doctest-flag`), then the line is
1265+
tested regardless of the feature availability.
1266+
1267+
The main applications are:
12631268

12641269
- **optional packages:** When a line requires an optional package to be
1265-
installed (e.g. the ``sloane_database`` package)::
1270+
installed (e.g. the ``rubiks`` package)::
1271+
1272+
sage: C = RubiksCube("R*L")
1273+
sage: C.solve() # optional - rubiks (a hybrid algorithm is used)
1274+
'L R'
1275+
sage: C.solve() # optional - !rubiks (GAP is used)
1276+
'L*R'
1277+
1278+
- **features:** When a line requires a feature to be present::
12661279

12671280
sage: SloaneEncyclopedia[60843] # optional - sloane_database
1281+
[1, 6, 21, 107, 47176870]
1282+
1283+
sage: SloaneEncyclopedia[60843] # optional - !sloane_database
1284+
Traceback (most recent call last):
1285+
...
1286+
OSError: The Sloane Encyclopedia database must be installed. Use e.g.
1287+
'SloaneEncyclopedia.install()' to download and install it.
12681288

1269-
- **internet:** For lines that require an internet connection::
1289+
For lines that require an internet connection::
12701290

12711291
sage: oeis(60843) # optional - internet
12721292
A060843: Busy Beaver problem: a(n) = maximal number of steps that an

src/sage/databases/sloane.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
::
1313
1414
sage: SloaneEncyclopedia[60843] # optional - sloane_database
15-
[1, 6, 21, 107]
15+
[1, 6, 21, 107, 47176870]
1616
1717
To get the name of a sequence, type
1818
@@ -149,6 +149,17 @@ def __len__(self):
149149
self.load()
150150
return len(self.__data__)
151151

152+
def is_installed(self):
153+
"""
154+
Check if a local copy of the encyclopedia is installed.
155+
156+
EXAMPLES::
157+
158+
sage: SloaneEncyclopedia.is_installed() # optional - sloane_database
159+
True
160+
"""
161+
return os.path.exists(self.__file__) and os.path.exists(self.__file_names__)
162+
152163
def find(self, seq, maxresults=30):
153164
"""
154165
Return a list of all sequences which have seq as a subsequence, up
@@ -274,7 +285,7 @@ def load(self):
274285
for L in file_seq:
275286
if len(L) == 0:
276287
continue
277-
m = entry.search(L)
288+
m = entry.search(L.decode('utf-8'))
278289
if m:
279290
seqnum = int(m.group('num'))
280291
msg = m.group('body').strip()
@@ -287,10 +298,13 @@ def load(self):
287298
for L in file_names:
288299
if not L:
289300
continue
290-
m = entry.search(L)
301+
m = entry.search(L.decode('utf-8'))
291302
if m:
292303
seqnum = int(m.group('num'))
293-
self.__data__[seqnum][3] = m.group('body').strip()
304+
if seqnum in self.__data__:
305+
self.__data__[seqnum][3] = m.group('body').strip()
306+
else:
307+
self.__data__[seqnum] = [seqnum, None, 'unknown', m.group('body').strip()]
294308
file_names.close()
295309
self.__loaded_names__ = True
296310
except KeyError:

src/sage/doctest/parsing.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
special_optional_regex = (
5959
"py2|long time|not implemented|not tested|optional|needs|known bug"
6060
)
61-
tag_with_explanation_regex = r"((?:\w|[.])*)\s*(?:\((?P<cmd_explanation>.*?)\))?"
61+
tag_with_explanation_regex = r"((?:!?\w|[.])*)\s*(?:\((?P<cmd_explanation>.*?)\))?"
6262
optional_regex = re.compile(
6363
rf"[^ a-z]\s*(?P<cmd>{special_optional_regex})(?:\s|[:-])*(?P<tags>(?:(?:{tag_with_explanation_regex})\s*)*)",
6464
re.IGNORECASE,
@@ -1124,14 +1124,14 @@ def check_and_clear_tag_counts():
11241124
continue
11251125

11261126
if self.optional_tags is not True:
1127-
extra = {
1128-
tag
1129-
for tag in optional_tags
1130-
if (
1131-
tag not in self.optional_tags
1132-
and tag not in available_software
1133-
)
1134-
}
1127+
extra = set()
1128+
for tag in optional_tags:
1129+
if tag not in self.optional_tags:
1130+
if tag.startswith('!'):
1131+
if tag[1:] in available_software:
1132+
extra.add(tag)
1133+
elif tag not in available_software:
1134+
extra.add(tag)
11351135
if extra and any(tag in ["bug"] for tag in extra):
11361136
# Bug only occurs on a specific platform?
11371137
bug_platform = optional_tags_with_values.get("bug")

src/sage/features/dot2tex.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# sage_setup: distribution = sagemath-environment
2+
r"""
3+
Check for ``dot2tex``
4+
"""
5+
6+
# *****************************************************************************
7+
# Copyright (C) 2024 Kwankyu Lee
8+
#
9+
# Distributed under the terms of the GNU General Public License (GPL)
10+
# as published by the Free Software Foundation; either version 2 of
11+
# the License, or (at your option) any later version.
12+
# https://www.gnu.org/licenses/
13+
# *****************************************************************************
14+
15+
from . import PythonModule
16+
17+
18+
class dot2tex(PythonModule):
19+
r"""
20+
A :class:`sage.features.Feature` describing the presence of :ref:`dot2tex <spkg_dot2tex>`.
21+
22+
dot2tex is provided by an optional package in the Sage distribution.
23+
24+
EXAMPLES::
25+
26+
sage: from sage.features.dot2tex import dot2tex
27+
sage: dot2tex().is_present() # optional - dot2tex
28+
FeatureTestResult('dot2tex', True)
29+
"""
30+
def __init__(self):
31+
r"""
32+
TESTS::
33+
34+
sage: from sage.features.dot2tex import dot2tex
35+
sage: isinstance(dot2tex(), dot2tex)
36+
True
37+
"""
38+
PythonModule.__init__(self, 'dot2tex', spkg='dot2tex')
39+
40+
41+
def all_features():
42+
return [dot2tex()]
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# sage_setup: distribution = sagemath-environment
2+
r"""
3+
Feature for testing the presence of Sloane Online Encyclopedia of Integer Sequences
4+
"""
5+
6+
# ****************************************************************************
7+
# Copyright (C) 2024 Kwankyu Lee <[email protected]>
8+
#
9+
# This program is free software: you can redistribute it and/or modify
10+
# it under the terms of the GNU General Public License as published by
11+
# the Free Software Foundation, either version 2 of the License, or
12+
# (at your option) any later version.
13+
# https://www.gnu.org/licenses/
14+
# ****************************************************************************
15+
16+
from . import Feature
17+
18+
19+
class SloaneOEIS(Feature):
20+
r"""
21+
A :class:`~sage.features.Feature` which describes the presence of
22+
the Sloane Online Encyclopedia of Integer Sequences.
23+
24+
EXAMPLES::
25+
26+
sage: from sage.features.sloane_database import SloaneOEIS
27+
sage: bool(SloaneOEIS().is_present()) # optional - sloane_database
28+
True
29+
"""
30+
def __init__(self):
31+
r"""
32+
TESTS::
33+
34+
sage: from sage.features.sloane_database import SloaneOEIS
35+
sage: isinstance(SloaneOEIS(), SloaneOEIS)
36+
True
37+
"""
38+
Feature.__init__(self, name='sloane_database',
39+
description='Sloane Online Encyclopedia of Integer Sequences')
40+
41+
def _is_present(self):
42+
r"""
43+
Return whether the database is available.
44+
45+
EXAMPLES::
46+
47+
sage: from sage.features.sloane_database import SloaneOEIS
48+
sage: bool(SloaneOEIS().is_present()) # optional - !sloane_database
49+
False
50+
"""
51+
try:
52+
from sage.databases.sloane import SloaneEncyclopedia
53+
except ImportError:
54+
return False
55+
return SloaneEncyclopedia.is_installed()
56+
57+
58+
def all_features():
59+
return [SloaneOEIS()]

src/sage/graphs/graph_latex.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1566,7 +1566,7 @@ def tkz_picture(self):
15661566
For a complicated vertex, a TeX box is used. ::
15671567
15681568
sage: B = crystals.Tableaux(['B', 2], shape=[1])
1569-
sage: latex(B)
1569+
sage: latex(B) # optional - !dot2tex
15701570
\begin{tikzpicture}
15711571
...
15721572
\newsavebox{\vertex}

src/sage/groups/perm_gps/cubegroup.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,25 +1422,22 @@ def __richcmp__(self, other, op):
14221422
return NotImplemented
14231423
return richcmp(self._state, other._state, op)
14241424

1425-
def solve(self, algorithm='hybrid', timeout=15):
1425+
def solve(self, algorithm='default', timeout=15):
14261426
r"""
14271427
Solve the Rubik's cube.
14281428
14291429
INPUT:
14301430
14311431
- ``algorithm`` -- must be one of the following:
14321432
1433-
- ``hybrid`` -- try ``kociemba`` for timeout seconds, then ``dietz``
1434-
- ``kociemba`` -- use Dik T. Winter's program
1435-
(reasonable speed, few moves)
1436-
- ``dietz`` -- use Eric Dietz's cubex program
1437-
(fast but lots of moves)
1438-
- ``optimal`` -- use Michael Reid's optimal program
1439-
(may take a long time)
1433+
- ``hybrid`` -- (default) try ``kociemba`` for timeout seconds, then ``dietz``
1434+
- ``kociemba`` -- use Dik T. Winter's program (reasonable speed, few moves)
1435+
- ``dietz`` -- use Eric Dietz's cubex program (fast but lots of moves)
1436+
- ``optimal`` -- use Michael Reid's optimal program (may take a long time)
14401437
- ``gap`` -- use GAP word solution (can be slow)
14411438
1442-
Any choice other than ``gap`` requires the optional package
1443-
``rubiks``. Otherwise, the ``gap`` algorithm is used.
1439+
Any choice other than ``gap`` requires the optional package ``rubiks``.
1440+
If the package is not installed, the ``gap`` algorithm is used by default.
14441441
14451442
EXAMPLES::
14461443
@@ -1452,19 +1449,22 @@ def solve(self, algorithm='hybrid', timeout=15):
14521449
solutions::
14531450
14541451
sage: s = C.solve('dietz'); s # optional - rubiks
1455-
"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'"
1452+
"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'
1453+
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'
1454+
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'
1455+
B' L' D' B U' U' L' B' D' F' F' L D F'"
14561456
sage: C2 = RubiksCube(s) # optional - rubiks
14571457
sage: C == C2 # optional - rubiks
14581458
True
14591459
"""
14601460
from sage.features.rubiks import Rubiks
14611461
if Rubiks().is_present():
14621462
import sage.interfaces.rubik # here to avoid circular referencing
1463+
if algorithm == 'default':
1464+
algorithm = "hybrid"
14631465
else:
1464-
algorithm = 'gap'
1465-
1466-
if algorithm == "default":
1467-
algorithm = "hybrid"
1466+
if algorithm == 'default':
1467+
algorithm = 'gap'
14681468

14691469
if algorithm == "hybrid":
14701470
try:

0 commit comments

Comments
 (0)