Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

guessing k-regular sequences #35682

Merged
merged 79 commits into from
Jul 20, 2023
Merged
Changes from 36 commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
c92f2fe
method for guessing k-regular sequences
dkrenn Aug 8, 2016
8cc4611
function "value"
dkrenn Aug 9, 2016
f91a850
function "split_interlace"
dkrenn Aug 9, 2016
a755c22
one linebreak to avoid a long line
dkrenn Aug 9, 2016
0ed5254
docstring of pad_right
dkrenn Aug 9, 2016
7029bc6
Merge branch 'u/dkrenn/sequences/k-regular' into u/dkrenn/sequences/k…
dkrenn Aug 9, 2016
99799a9
switch to Python's logging
dkrenn Aug 10, 2016
77e1e67
Merge branch 'u/dkrenn/sequences/k-regular' into u/dkrenn/sequences/k…
dkrenn Aug 10, 2016
cc14b63
post-merge Änderungen
dkrenn Aug 10, 2016
11c7ec4
Merge branch 'u/dkrenn/sequences/k-regular' into u/dkrenn/sequences/k…
dkrenn Aug 10, 2016
73ad7bd
Merge branch 'u/dkrenn/sequences/k-regular' into u/dkrenn/sequences/k…
dkrenn Aug 21, 2016
c349170
fix code and doctests to adapt with upstream recognizable series
dkrenn Aug 21, 2016
15e4008
Merge branch 'u/dkrenn/sequences/k-regular' into u/dkrenn/sequences/k…
dkrenn Aug 22, 2016
eaf8df2
Merge branch 'u/dkrenn/sequences/k-regular' into u/dkrenn/sequences/k…
dkrenn Aug 22, 2016
355d24d
simplify code by using features of recognizable series better (and ad…
dkrenn Aug 22, 2016
348fcff
solve transpositioning problem
dkrenn Aug 22, 2016
2e99c5c
another example
dkrenn Aug 22, 2016
65da1e7
Merge branch 'u/dkrenn/sequences/k-regular' into u/dkrenn/sequences/k…
dkrenn Aug 24, 2016
3e955e1
guessing partial sums
dkrenn Aug 25, 2016
a68e20d
Merge branch 't/21203/sequences/k-regular' into t/21204/sequences/k-r…
dkrenn Aug 25, 2016
816fcfd
Merge branch 't/21325/sequences/k-regular-subseq' into t/21204/sequen…
dkrenn Aug 25, 2016
0d5fbed
Merge ../7.2 into t/21204/sequences/k-regular-guess
dkrenn Aug 25, 2016
9818d94
Merge branch 't/21319/sequences/rec-hash' into t/21204/sequences/k-re…
dkrenn Aug 25, 2016
90e4ed5
doctests
dkrenn Aug 25, 2016
899e56e
finish .guess (docstrings etc)
dkrenn Aug 25, 2016
0dae02b
Merge branch 't/21319/sequences/rec-hash' into t/21204/sequences/k-re…
dkrenn Aug 26, 2016
bde8e67
Merge branch 't/21319/sequences/rec-hash' into t/21204/sequences/k-re…
dkrenn Jan 24, 2017
622c3f6
Merge branch 'u/dkrenn/sequences/rec-hash' into u/dkrenn/sequences/k-…
dkrenn Apr 6, 2018
4e7f615
Merge branch 'u/dkrenn/sequences/rec-hash' into u/dkrenn/sequences/k-…
dkrenn Mar 29, 2019
e726278
Merge branch 't/21319/sequences/rec-hash' into t/21204/sequences/k-re…
dkrenn May 11, 2021
dcc7cd1
Trac #21204: cherry-pick to avoid merge conflict
dkrenn May 11, 2021
c0519f0
Trac #21204: fixup code and tests
dkrenn May 11, 2021
5ce5276
Merge branch 't/21319/sequences/rec-hash' into t/21204/sequences/k-re…
dkrenn Jun 25, 2021
00047ce
Trac #21204: fix punctuation
dkrenn Jun 25, 2021
3c634df
Merge branch 't/21319/sequences/rec-hash' into t/21204/sequences/k-re…
dkrenn Jan 5, 2022
858253f
Merge tag '10.0' into u/dkrenn/sequences/k-regular-guess
dkrenn May 25, 2023
2536600
Fix pycodestyle issue E306
cheuberg Oct 2, 2022
bb32fee
Trac #21204: Fix typo
cheuberg Oct 11, 2022
4792a8f
Add ALGORITHM section to docstring
cheuberg Oct 11, 2022
b65aba3
Add some further comments
cheuberg Oct 11, 2022
bc63bd0
Merge remote-tracking branch 'cheuberg/u/cheuberg/sequences/k-regular…
dkrenn May 26, 2023
113acab
rename to linear_combination
dkrenn May 26, 2023
028b29d
rename to linear_combination_candidate
dkrenn May 26, 2023
305519d
fixup renameing
dkrenn May 26, 2023
46daf57
remove unused third component of "line"
dkrenn May 26, 2023
9be3124
remove split_interlace as never used
dkrenn May 26, 2023
9fc2663
rewrite preparation of left to be more readable
dkrenn May 26, 2023
3fc45d5
fix rest/latex: \operatorname
dkrenn May 26, 2023
c532a1e
use .linear_representation
dkrenn May 26, 2023
fd320c1
describe variants in example
dkrenn May 26, 2023
e6a5a84
compare outputs of variants in example
dkrenn May 26, 2023
221d33c
logging-info: show sequences in k-kernel properly
dkrenn May 26, 2023
0297668
apply suggestion oneline-description of .guess
dkrenn May 26, 2023
9e5682b
rewrite documentation of parameter "sequence"
dkrenn May 26, 2023
f878f75
fixup catching NoLinearCombination exception
dkrenn May 26, 2023
d0ddcab
rename, describe and test max_exponent
dkrenn May 26, 2023
100d978
write error message
dkrenn May 26, 2023
98f0587
Merge branch 'u/dkrenn/sequences/k-regular-guess' of github.com:dkren…
dkrenn May 26, 2023
dbf2bdc
Apply suggestions from code review
dkrenn May 26, 2023
3a39323
Apply suggestions from code review
dkrenn Jun 7, 2023
ec28f89
remove unneeded import
dkrenn Jun 7, 2023
29238d9
make NoLinearCombination a RuntimeError
dkrenn Jun 7, 2023
1b118be
simplify code by removing variables
dkrenn Jun 7, 2023
67562fb
consequently rename to linear_combination
dkrenn Jun 7, 2023
c15742d
more consistent naming in DEBUG output
dkrenn Jun 8, 2023
c198d90
fix left/right issue when bootstraping guessing
dkrenn Jun 8, 2023
ae8a287
fixup n_max
dkrenn Jun 8, 2023
6aab949
add another test
dkrenn Jun 8, 2023
250a98c
add another test
dkrenn Jun 8, 2023
9c772e5
better handling of n_max
dkrenn Jun 8, 2023
d4edd45
fix whitespaces
dkrenn Jun 8, 2023
f2dd055
fix docstring (LaTeX)
dkrenn Jun 9, 2023
43f58c6
remove W293 blank line contains whitespace
dkrenn Jun 9, 2023
bb0efc5
add comment and ref to #35748
dkrenn Jun 9, 2023
a98c32d
test RuntimeError no invertible matrix
dkrenn Jun 9, 2023
a2d8b25
break long line in doctest
dkrenn Jul 17, 2023
1c56e7f
implement coefficient_of_n (for __getitem__)
dkrenn Jul 17, 2023
1050e70
fix left/right vector sequence issue in docstrings
dkrenn Jul 17, 2023
822ce11
Update src/sage/combinat/k_regular_sequence.py
dkrenn Jul 17, 2023
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
367 changes: 367 additions & 0 deletions src/sage/combinat/k_regular_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,114 @@
from sage.misc.cachefunc import cached_function, cached_method


def pad_right(T, length, zero=0):
r"""
Pad ``T`` to the right by using ``zero`` to have
at least the given ``length``.

INPUT:

- ``T`` -- A tuple, list or other iterable

- ``length`` -- a nonnegative integer

- ``zero`` -- (default: ``0``) the elements to pad with

OUTPUT:

An object of the same type as ``T``

EXAMPLES::

sage: from sage.combinat.k_regular_sequence import pad_right
sage: pad_right((1, 2, 3), 10)
(1, 2, 3, 0, 0, 0, 0, 0, 0, 0)
sage: pad_right((1, 2, 3), 2)
(1, 2, 3)
dkrenn marked this conversation as resolved.
Show resolved Hide resolved

TESTS::

sage: pad_right([1, 2, 3], 10)
[1, 2, 3, 0, 0, 0, 0, 0, 0, 0]
"""
return T + type(T)(zero for _ in range(length - len(T)))


def value(D, k):
r"""
Return the value of the expansion with digits `D` in base `k`, i.e.

.. MATH::

\sum_{0\leq j < \operator{len}D} D[j] k^j.
dkrenn marked this conversation as resolved.
Show resolved Hide resolved

INPUT:

- ``D`` -- a tuple or other iterable

- ``k`` -- the base

OUTPUT:

An element in the common parent of the base `k` and of the entries
of `D`

EXAMPLES::

sage: from sage.combinat.k_regular_sequence import value
sage: value(42.digits(7), 7)
42
"""
return sum(d * k**j for j, d in enumerate(D))


def split_interlace(n, k, p):
dkrenn marked this conversation as resolved.
Show resolved Hide resolved
r"""
Split each digit in the `k`-ary expansion of `n` into `p` parts and
return the value of the expansion obtained by each of these parts.
dkrenn marked this conversation as resolved.
Show resolved Hide resolved

INPUT:

- ``n`` -- an integer

- ``k`` -- an integer specifying the base

- ``p`` -- a positive integer specifying in how many parts
the input ``n`` is split. This has to be a divisor of ``k``.

OUTPUT:

A tuple of integers

EXAMPLES::

sage: from sage.combinat.k_regular_sequence import split_interlace
sage: [(n, split_interlace(n, 4, 2)) for n in srange(20)]
[(0, (0, 0)), (1, (1, 0)), (2, (0, 1)), (3, (1, 1)),
(4, (2, 0)), (5, (3, 0)), (6, (2, 1)), (7, (3, 1)),
(8, (0, 2)), (9, (1, 2)), (10, (0, 3)), (11, (1, 3)),
(12, (2, 2)), (13, (3, 2)), (14, (2, 3)), (15, (3, 3)),
(16, (4, 0)), (17, (5, 0)), (18, (4, 1)), (19, (5, 1))]
sage: [(n, split_interlace(n, 6, 3)) for n in srange(9)]
[(0, (0, 0, 0)), (1, (1, 0, 0)), (2, (0, 1, 0)),
(3, (1, 1, 0)), (4, (0, 0, 1)), (5, (1, 0, 1)),
(6, (2, 0, 0)), (7, (3, 0, 0)), (8, (2, 1, 0))]

TESTS::

sage: split_interlace(42, 4, 3)
Traceback (most recent call last):
...
ValueError: p=3 is not a divisor of k=4.
"""
if k % p != 0:
raise ValueError('p={} is not a divisor of k={}.'.format(p, k))
ki = k // p
return tuple(value(D, ki)
for D in zip(*(d.digits(ki, padto=p)
for d in n.digits(k, padto=1))))


class kRegularSequence(RecognizableSeries):
def __init__(self, parent, mu, left=None, right=None):
r"""
Expand Down Expand Up @@ -918,6 +1026,265 @@ def _n_to_index_(self, n):
except OverflowError:
raise ValueError('value {} of index is negative'.format(n)) from None

def guess(self, f, n_max=100, max_dimension=10, sequence=None):
r"""
Guess a `k`-regular sequence of `(f(n))_{n\geq0}`.
dkrenn marked this conversation as resolved.
Show resolved Hide resolved

INPUT:

- ``f`` -- a function (callable) which determines the sequence.
It takes nonnegative integers as an input

- ``n_max`` -- (default: ``100``) a positive integer. The resulting
`k`-regular sequence coincides with `f` on the first ``n_max``
terms
dkrenn marked this conversation as resolved.
Show resolved Hide resolved

- ``max_dimension`` -- (default: ``10``) a positive integer specifying
the maxium dimension which is tried when guessing the sequence

- ``sequence`` -- (default: ``None``) a `k`-regular sequence used
for bootstrapping this guessing
dkrenn marked this conversation as resolved.
Show resolved Hide resolved

OUTPUT:

A :class:`kRegularSequence`

EXAMPLES:
dkrenn marked this conversation as resolved.
Show resolved Hide resolved

Binary sum of digits::

sage: @cached_function
....: def s(n):
....: if n == 0:
....: return 0
....: return s(n//2) + ZZ(is_odd(n))
sage: all(s(n) == sum(n.digits(2)) for n in srange(10))
True
sage: [s(n) for n in srange(10)]
[0, 1, 1, 2, 1, 2, 2, 3, 1, 2]

Variant 1::
dkrenn marked this conversation as resolved.
Show resolved Hide resolved

sage: Seq2 = kRegularSequenceSpace(2, ZZ)
sage: import logging
sage: logging.basicConfig(level=logging.INFO)
sage: S1 = Seq2.guess(s)
INFO:...:including f_{1*m+0}
INFO:...:M_0: f_{2*m+0} = (1) * X_m
INFO:...:including f_{2*m+1}
INFO:...:M_1: f_{2*m+1} = (0, 1) * X_m
INFO:...:M_0: f_{4*m+1} = (0, 1) * X_m
INFO:...:M_1: f_{4*m+3} = (-1, 2) * X_m
sage: S1
2-regular sequence 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, ...
sage: S1.mu[0], S1.mu[1], S1.left, S1.right
dkrenn marked this conversation as resolved.
Show resolved Hide resolved
(
[1 0] [ 0 1]
[0 1], [-1 2], (1, 0), (0, 1)
)

sage: from importlib import reload
sage: logging.shutdown(); _ = reload(logging)

Variant 2::

sage: C = Seq2((Matrix([[1]]), Matrix([[1]])), vector([1]), vector([1])); C
2-regular sequence 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
sage: S2 = Seq2.guess(s, sequence=C)
sage: S2
2-regular sequence 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, ...
sage: S2.mu[0], S2.mu[1], S2.left, S2.right
(
[1 0] [1 0]
[0 1], [1 1], (0, 1), (1, 0)
)
dkrenn marked this conversation as resolved.
Show resolved Hide resolved

The sequence of all natural numbers::

sage: S = Seq2.guess(lambda n: n)
sage: S
2-regular sequence 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...
sage: S.mu[0], S.mu[1], S.left, S.right
(
[2 0] [ 0 1]
[2 1], [-2 3], (1, 0), (0, 1)
)

The indicator function of the even integers::

sage: S = Seq2.guess(lambda n: ZZ(is_even(n)))
sage: S
2-regular sequence 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, ...
sage: S.mu[0], S.mu[1], S.left, S.right
(
[0 1] [0 0]
[0 1], [0 1], (1, 0), (1, 1)
)

The indicator function of the odd integers::

sage: S = Seq2.guess(lambda n: ZZ(is_odd(n)))
sage: S
2-regular sequence 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, ...
sage: S.mu[0], S.mu[1], S.left, S.right
(
[0 0] [0 1]
[0 1], [0 1], (1, 0), (0, 1)
)

TESTS::

sage: S = Seq2.guess(lambda n: 2, sequence=C)
sage: S
2-regular sequence 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, ...
sage: S.mu[0], S.mu[1], S.left, S.right
([1], [1], (2), (1))

We guess some partial sums sequences::

sage: S = Seq2((Matrix([1]), Matrix([2])), vector([1]), vector([1]))
sage: S
2-regular sequence 1, 2, 2, 4, 2, 4, 4, 8, 2, 4, ...
sage: from itertools import islice
sage: L = []; ps = 0
sage: for s in islice(S, 110):
....: ps += s
....: L.append(ps)
sage: G = Seq2.guess(lambda n: L[n])
sage: G
2-regular sequence 1, 3, 5, 9, 11, 15, 19, 27, 29, 33, ...
sage: G.mu[0], G.mu[1], G.left, G.right
(
[ 0 1] [3 0]
[-3 4], [3 2], (1, 0), (1, 1)
)
sage: G == S.partial_sums(include_n=True)
True

::

sage: Seq3 = kRegularSequenceSpace(3, QQ)
sage: S = Seq3((Matrix([1]), Matrix([3]), Matrix([2])), vector([1]), vector([1]))
sage: S
3-regular sequence 1, 3, 2, 3, 9, 6, 2, 6, 4, 3, ...
sage: from itertools import islice
sage: L = []; ps = 0
sage: for s in islice(S, 110):
....: ps += s
....: L.append(ps)
sage: G = Seq3.guess(lambda n: L[n])
sage: G
3-regular sequence 1, 4, 6, 9, 18, 24, 26, 32, 36, 39, ...
sage: G.mu[0], G.mu[1], G.mu[2], G.left, G.right
(
[ 0 1] [18/5 2/5] [ 6 0]
[-6 7], [18/5 27/5], [24 2], (1, 0), (1, 1)
)
sage: G == S.partial_sums(include_n=True)
True
"""
import logging
logger = logging.getLogger(__name__)

from sage.arith.srange import srange, xsrange
from sage.matrix.constructor import Matrix
from sage.misc.mrange import cantor_product
from sage.modules.free_module_element import vector

k = self.k
domain = self.coefficient_ring()
if sequence is None:
mu = [[] for _ in srange(k)]
seq = lambda m: tuple()
else:
mu = [M.rows() for M in sequence.mu]
seq = lambda m: sequence.left * sequence._mu_of_word_(
self._n_to_index_(m))

zero = domain(0)
one = domain(1)

def values(m, lines):
return tuple(seq(m)) + tuple(f(k**t_R * m + r_R) for t_R, r_R, s_R in lines)

@cached_function(key=lambda lines: len(lines)) # we assume that existing lines are not changed (we allow appending of new lines)
dkrenn marked this conversation as resolved.
Show resolved Hide resolved
def some_inverse_U_matrix(lines):
d = len(seq(0)) + len(lines)

for m_indices in cantor_product(xsrange(n_max), repeat=d, min_slope=1):
U = Matrix(domain, d, d, [values(m, lines) for m in m_indices]).transpose()
dkrenn marked this conversation as resolved.
Show resolved Hide resolved
try:
return U.inverse(), m_indices
except ZeroDivisionError:
pass
else:
raise RuntimeError

def guess_linear_dependence(t_L, r_L, lines):
dkrenn marked this conversation as resolved.
Show resolved Hide resolved
dkrenn marked this conversation as resolved.
Show resolved Hide resolved
iU, m_indices = some_inverse_U_matrix(lines)
X_L = vector(f(k**t_L * m + r_L) for m in m_indices)
return X_L * iU

def verify_linear_dependence(t_L, r_L, linear_dependence, lines):
dkrenn marked this conversation as resolved.
Show resolved Hide resolved
return all(f(k**t_L * m + r_L) ==
linear_dependence * vector(values(m, lines))
for m in xsrange(0, (n_max - r_L) // k**t_L + 1))

def find_linear_dependence(t_L, r_L, lines):
linear_dependence = guess_linear_dependence(t_L, r_L, lines)
if not verify_linear_dependence(t_L, r_L, linear_dependence, lines):
raise ValueError
return linear_dependence

left = None
if seq(0):
dkrenn marked this conversation as resolved.
Show resolved Hide resolved
try:
solution = find_linear_dependence(0, 0, [])
except ValueError:
pass
else:
left = vector(solution)

to_branch = []
lines = []
def include(line):
to_branch.append(line)
lines.append(line)
t, r, s = line
dkrenn marked this conversation as resolved.
Show resolved Hide resolved
logger.info('including f_{%s*m+%s}', k**t, r)

if left is None:
line_L = (0, 0, 0) # entries (t, r, s) --> k**t * m + r, belong to M_s
include(line_L)
left = vector((len(seq(0)) + len(lines)-1)*(zero,) + (one,))
dkrenn marked this conversation as resolved.
Show resolved Hide resolved

while to_branch:
line_R = to_branch.pop(0)
t_R, r_R, s_R = line_R
if t_R >= max_dimension:
raise RuntimeError
dkrenn marked this conversation as resolved.
Show resolved Hide resolved

t_L = t_R + 1
for s_L in srange(k):
r_L = k**t_R * s_L + r_R
line_L = t_L, r_L, s_L

try:
solution = find_linear_dependence(t_L, r_L, lines)
except ValueError:
include(line_L)
solution = (len(lines)-1)*(zero,) + (one,)
logger.info('M_%s: f_{%s*m+%s} = %s * X_m',
s_L, k**t_L, r_L, solution)
mu[s_L].append(solution)

d = len(seq(0)) + len(lines)
mu = tuple(Matrix(domain, [pad_right(tuple(row), d, zero=zero) for row in M])
for M in mu)
right = vector(values(0, lines))
dkrenn marked this conversation as resolved.
Show resolved Hide resolved
left = vector(pad_right(tuple(left), d, zero=zero))
return self(mu, left, right)

def from_recurrence(self, *args, **kwds):
r"""
Construct the unique `k`-regular sequence which fulfills the given
Expand Down