Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 116 additions & 15 deletions src/sage/databases/oeis.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,16 @@

from sage.version import version
from sage.cpython.string import bytes_to_str
from sage.misc.cachefunc import cached_method
from sage.misc.flatten import flatten
from sage.misc.html import HtmlFragment
from sage.misc.temporary_file import tmp_filename
from sage.misc.unknown import Unknown
from sage.misc.verbose import verbose
from sage.repl.preparse import preparse
from sage.rings.integer import Integer
from sage.rings.integer_ring import ZZ
from sage.rings.infinity import infinity
from sage.structure.global_options import GlobalOptions
from sage.structure.sage_object import SageObject
from sage.structure.unique_representation import UniqueRepresentation

Expand Down Expand Up @@ -246,7 +248,7 @@ class OEIS:
- a list representing a sequence of integers.
- a string, representing a text search.

- ``max_results`` -- integer (default: 30); the maximum number of
- ``max_results`` -- integer (default: 3); the maximum number of
results to return, they are sorted according to their relevance. In
any cases, the OEIS website will never provide more than 100 results.

Expand All @@ -255,6 +257,8 @@ class OEIS:
This is useful if you are looking for a sequence that may appear
after the 100 first found sequences.

``max_results`` can also be set using :meth:`options`.

OUTPUT:

- if ``query`` is an integer or an OEIS ID (e.g. 'A000045'), returns
Expand Down Expand Up @@ -361,8 +365,7 @@ class OEIS:
sage: oeis('A000045') # optional -- internet
A000045: Fibonacci numbers: F(n) = F(n-1) + F(n-2) with F(0) = 0 and F(1) = 1.
"""

def __call__(self, query, max_results=3, first_result=0):
def __call__(self, query, max_results=None, first_result=0):
r"""
See the documentation of :class:`OEIS`.

Expand All @@ -383,6 +386,46 @@ def __call__(self, query, max_results=3, first_result=0):
elif isinstance(query, (list, tuple)):
return self.find_by_subsequence(query, max_results, first_result)

class options(GlobalOptions):
r"""
Set and display the options for the OEIS.

If no parameters are set, then the function returns a copy of
the options dictionary.

The ``options`` can be accessed as using
:class:`oeis.options`.

@OPTIONS@

EXAMPLES::

sage: oeis.options
Current options for OEIS
- fetch_b_file: False
- max_results: 3

sage: oeis.options.max_results = 5
sage: oeis('beaver') # optional -- internet
0: A...: ...eaver...
1: A...: ...eaver...
2: A...: ...eaver...
3: A...: ...eaver...
4: A...: ...eaver...

sage: oeis.options._reset()
sage: oeis.options.max_results
3
"""
NAME = 'OEIS'
module = 'sage.databases.oeis'
max_results = dict(default=3,
description='the maximum number of results to return',
checker=lambda x: x in ZZ and x > 0)
fetch_b_file = dict(default=False,
description='whether to fetch terms from the b-file by default',
checker=lambda x: isinstance(x, bool))

def __repr__(self) -> str:
r"""
Return the representation of ``self``.
Expand Down Expand Up @@ -440,7 +483,7 @@ def find_by_entry(self, entry):
sequence._raw = entry
return sequence

def find_by_description(self, description, max_results=3, first_result=0):
def find_by_description(self, description, max_results=None, first_result=0):
r"""
Search for OEIS sequences corresponding to the description.

Expand Down Expand Up @@ -483,7 +526,19 @@ def find_by_description(self, description, max_results=3, first_result=0):
1: A...: ...eaver...
2: A...: ...eaver...
3: A...: ...eaver...

Alternatively, we can also set the global option `max_results`::

sage: oeis.options.max_results = 5
sage: oeis('beaver') # optional -- internet
0: A...: ...eaver...
1: A...: ...eaver...
2: A...: ...eaver...
3: A...: ...eaver...
4: A...: ...eaver...
"""
if max_results is None:
max_results = self.options['max_results']
options = {'q': description,
'n': str(max_results),
'fmt': 'text',
Expand All @@ -493,7 +548,7 @@ def find_by_description(self, description, max_results=3, first_result=0):
T = [self.find_by_entry(entry=s) for s in sequence_list]
return FancyTuple([s for s in T if not s.is_dead()])

def find_by_subsequence(self, subsequence, max_results=3, first_result=0):
def find_by_subsequence(self, subsequence, max_results=None, first_result=0):
r"""
Search for OEIS sequences containing the given subsequence.

Expand Down Expand Up @@ -689,7 +744,7 @@ def __init__(self, ident):

def online_update(self):
r"""
Fetch the online OEIS to update the informations about this sequence.
Fetch the sequence from the OEIS.

TESTS::

Expand Down Expand Up @@ -1205,15 +1260,17 @@ def is_full(self):
else:
return Unknown

@cached_method
def first_terms(self, number=None):
r"""
Return the first few terms of the sequence.

INPUT:

- ``number`` -- integer or ``None`` (default); the number of
terms returned (if less than the number of available terms). When set
to ``None``, returns all the known terms.
- ``number`` -- integer, ``infinity`` or ``None`` (default);
the number of terms returned. When set to ``None``,
returns all the known terms. When set to an integer larger
than the number of terms in the internal format, or to
``infinity``, the b-file is fetched.

OUTPUT: tuple of integers

Expand All @@ -1237,9 +1294,54 @@ def first_terms(self, number=None):
(1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
sage: s.first_terms(5)
(1, 1, 1, 1, 2)

sage: len(oeis(45).first_terms()) # optional -- internet
41
sage: len(oeis(45).first_terms(oo)) # optional -- internet
2001

sage: len(oeis(1).first_terms()) # optional -- internet
94

sage: oeis.options.fetch_b_file = True
sage: len(oeis(1).first_terms()) # optional -- internet
2048
sage: oeis.options._reset()
"""
fields = ['S', 'T', 'U']
return to_tuple(" ".join(flatten([self._field(a) for a in fields])))[:number]
def fetch_b_file():
url = oeis_url + f"b{self.id(format='int')}.txt"
terms = _fetch(url)
first_terms = tuple()
check = None
for term in terms.split('\n'):
if not term or term[0] == '#':
continue
k, v = [Integer(e) for e in term.strip().split()]
if check is not None and k != check + 1:
raise ValueError(f"malformed b-file {url}: key {check} followed by {k}")
check = k
first_terms += (v,)
self._first_terms = True, first_terms

if ((number is infinity or oeis.options['fetch_b_file'])
and self is not oeis._imaginary_sequence()): # all other sequences have a b-file
# self._first_terms is a pair (all?, first_terms)
if not hasattr(self, "_first_terms") or not self._first_terms[0]:
fetch_b_file()
return self._first_terms[1]

if not hasattr(self, "_first_terms"):
fields = ['S', 'T', 'U']
first_terms = to_tuple(" ".join(flatten([self._field(a) for a in fields])))
self._first_terms = (False, first_terms)

if number is None:
return self._first_terms[1]

if number > len(self._first_terms[1]) and not self._first_terms[0]:
fetch_b_file()

return self._first_terms[1][:number]

def _repr_(self):
r"""
Expand Down Expand Up @@ -1755,8 +1857,7 @@ def show(self):
https://oeis.org/A012345
<BLANKLINE>
AUTHOR
Patrick Demichel (patrick.demichel(AT)hp.com)
<BLANKLINE>
...

TESTS::

Expand Down
Loading