diff --git a/src/sage/databases/oeis.py b/src/sage/databases/oeis.py index f4c7df11cd6..9438b56adbf 100644 --- a/src/sage/databases/oeis.py +++ b/src/sage/databases/oeis.py @@ -147,7 +147,6 @@ 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 @@ -155,6 +154,9 @@ 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 @@ -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. @@ -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 @@ -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`. @@ -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``. @@ -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. @@ -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', @@ -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. @@ -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:: @@ -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 @@ -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""" @@ -1755,8 +1857,7 @@ def show(self): https://oeis.org/A012345 AUTHOR - Patrick Demichel (patrick.demichel(AT)hp.com) - + ... TESTS::