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 16 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
144 changes: 101 additions & 43 deletions src/sage/combinat/k_regular_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ def pad_right(T, length, zero=0):
(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
sage: pad_right([(1, 2), (3, 4)], 4, (0, 0))
[(1, 2), (3, 4), (0, 0), (0, 0)]

TESTS::

Expand Down Expand Up @@ -979,7 +981,7 @@ 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_exponent=10, sequence=None):
def guess(self, f, n_verify=100, max_exponent=10, sequence=None):
r"""
Guess a `k`-regular sequence whose first terms coincide with `(f(n))_{n\geq0}`.

Expand All @@ -988,12 +990,14 @@ def guess(self, f, n_max=100, max_exponent=10, sequence=None):
- ``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
- ``n_verify`` -- (default: ``100``) a positive integer. The resulting
`k`-regular sequence coincides with `f` on the first ``n_verify``
terms.

- ``max_exponent`` -- (default: ``10``) a positive integer specifying
the maximum exponent of `k` which is tried when guessing the sequence
the maximum exponent of `k` which is tried when guessing the sequence,
i.e., relations between `f(k^t n+r)` are used for
`0\le t\le \mathtt{max\_exponent}` and `0\le r < k^j`

- ``sequence`` -- (default: ``None``) a `k`-regular sequence used
for bootstrapping the guessing by adding information of the
Expand All @@ -1006,20 +1010,20 @@ def guess(self, f, n_max=100, max_exponent=10, sequence=None):
ALGORITHM:

For the purposes of this description, the left vector valued sequence
associated with a regular sequence is consists of the left vector
associated with a regular sequence consists of the left vector
multiplied by the corresponding matrix product, but without the right
vector of the regular sequence.

The algorithm maintains a left vector valued sequence consisting
of the left vector valued sequence of the argument ``sequence``
(replaced by an empty tuple if ``sequence`` is `None`) plus several
(replaced by an empty tuple if ``sequence`` is ``None``) plus several
components of the shape `m \mapsto f(k^t\cdot m +r)` for suitable
``t`` and ``r``.

Implicitly, the algorithm also maintains a `d \times n_max` matrix ``A``
Implicitly, the algorithm also maintains a `d \times n_\mathrm{verify}` matrix ``A``
(where ``d`` is the dimension of the left vector valued sequence)
whose columns are the current left vector valued sequence evaluated at
the non-negative integers less than `n_max` and ensures that this
the non-negative integers less than `n_\mathrm{verify}` and ensures that this
matrix has full row rank.

EXAMPLES:
dkrenn marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -1053,6 +1057,8 @@ def guess(self, f, n_max=100, max_exponent=10, sequence=None):
[-1 2]},
(0, 1))
dkrenn marked this conversation as resolved.
Show resolved Hide resolved

The ``INFO`` messages mean that the right vector valued sequence is the sequence `(s(n), s(2n+1))^\top`.

We guess again, but this time, we use a constant sequence
for bootstrapping the guessing process::

Expand Down Expand Up @@ -1121,11 +1127,11 @@ def guess(self, f, n_max=100, max_exponent=10, sequence=None):
sage: logging.basicConfig(level=logging.DEBUG)
sage: Seq2.guess(s)
INFO:...:including f_{1*m+0}
DEBUG:...:M_0: f_{2*m+0} = (1) * X_m
DEBUG:...:M_0: f_{2*m+0} = (1) * F_m
INFO:...:including f_{2*m+1}
DEBUG:...:M_1: f_{2*m+1} = (0, 1) * X_m
DEBUG:...:M_0: f_{4*m+1} = (0, 1) * X_m
DEBUG:...:M_1: f_{4*m+3} = (-1, 2) * X_m
DEBUG:...:M_1: f_{2*m+1} = (0, 1) * F_m
DEBUG:...:M_0: f_{4*m+1} = (0, 1) * F_m
DEBUG:...:M_1: f_{4*m+3} = (-1, 2) * F_m
2-regular sequence 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, ...
sage: from importlib import reload
sage: logging.shutdown(); _ = reload(logging)
Expand Down Expand Up @@ -1196,6 +1202,51 @@ def guess(self, f, n_max=100, max_exponent=10, sequence=None):
Traceback (most recent call last):
...
RuntimeError: aborting as exponents would be larger than max_exponent=1

::

sage: R = kRegularSequenceSpace(2, QQ)
sage: one = R.one_hadamard()
sage: S = R.guess(lambda n: sum(n.bits()), sequence=one) + one
sage: T = R.guess(lambda n: n*n, sequence=S, n_verify=4); T
2-regular sequence 0, 1, 4, 9, 16, 25, 36, 163/3, 64, 89, ...
sage: T.linear_representation()
((0, 0, 1),
Finite family {0: [1 0 0]
[0 1 0]
[0 0 4],
1: [ 0 1 0]
[ -1 2 0]
[13/3 -5/3 16/3]},
(1, 2, 0))

::

sage: two = Seq2.one_hadamard() * 2
sage: two.linear_representation()
((1), Finite family {0: [1], 1: [1]}, (2))
sage: two_again = Seq2.guess(lambda n: 2, sequence=two)
sage: two_again.linear_representation()
((1), Finite family {0: [1], 1: [1]}, (2))

::

sage: def s(k):
....: return k
sage: S1 = Seq2.guess(s)
sage: S2 = Seq2.guess(s, sequence=S1)
sage: S1
2-regular sequence 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...
sage: S2
2-regular sequence 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...

::

sage: A = Seq2((Matrix([[1, 1], [1, 1]]), Matrix([[1, 1], [1, 1]])), left=(1, 1), right=(1, 1))
dkrenn marked this conversation as resolved.
Show resolved Hide resolved
sage: Seq2.guess(lambda n: n, sequence=A, n_verify=5)
Traceback (most recent call last):
...
RuntimeError: no invertible submatrix found
"""
import logging
logger = logging.getLogger(__name__)
Expand All @@ -1212,18 +1263,17 @@ def guess(self, f, n_max=100, max_exponent=10, sequence=None):
seq = lambda m: vector([])
else:
mu = [M.rows() for M in sequence.mu]
seq = lambda m: sequence.left * sequence._mu_of_word_(
self._n_to_index_(m))
seq = lambda m: (sequence._mu_of_word_(self._n_to_index_(m))
* sequence.right)
dkrenn marked this conversation as resolved.
Show resolved Hide resolved
logger.info('including %s', sequence)

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

# A `line` will be a triple `(t, r, s)` corresponding to an entry
# `k**t * m + r` belonging to `M_s`
# TODO: what is `M_s`?
# A `line` will be a pair `(t, r)` corresponding to an entry
# `k**t * m + r`

# The elements of `lines` will be correspond to the current components
# The elements of `lines` will correspond to the current components
# of the left vector valued sequence described in the algorithm section
# of the docstring.

Expand All @@ -1234,10 +1284,12 @@ def values(m, lines):
"""
return tuple(seq(m)) + tuple(f(k**t_R * m + r_R) for t_R, r_R in lines)

@cached_function(key=lambda lines: len(lines)) # we assume that existing lines are not changed (we allow appending of new lines)
@cached_function(key=lambda lines: len(lines))
# we assume that existing lines are not changed
# (we allow appending of new lines)
def some_inverse_U_matrix(lines):
r"""
Find an invertible `d \times d` times submatrix of the matrix
Find an invertible `d \times d` submatrix of the matrix
``A`` described in the algorithm section of the docstring.

The output is the inverse of the invertible submatrix and
Expand All @@ -1246,17 +1298,19 @@ 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):
# The following search for an inverse works but is inefficient;
# see :trac:`35748` for details.
for m_indices in cantor_product(xsrange(n_verify), repeat=d, min_slope=1):
# Iterate over all increasing lists of length d consisting
# of non-negative integers less than `n_max`.
# of non-negative integers less than `n_verify`.

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('no inverse submatrix found')
raise RuntimeError('no invertible submatrix found')

def linear_combination_candidate(t_L, r_L, lines):
r"""
Expand All @@ -1276,12 +1330,20 @@ def verify_linear_combination(t_L, r_L, linear_combination, lines):
``r_L``, i.e., `m \mapsto f(k**t_L * m + r_L)`, is the linear
combination ``linear_combination`` of the current vector valued
sequence.

Note that we only evaluate the subsequence of ``f`` where arguments
of ``f`` are at most ``n_verify``. This might lead to detection of
linear dependence which would not be true for higher values, but this
coincides with the documentation of ``n_verify``.
However, this is not a guarantee that the given function will never
be evaluated beyond ``n_verify``, determining an invertible submatrix
in ``some_inverse_U_matrix`` might require us to do so.
"""
return all(f(k**t_L * m + r_L) ==
linear_combination * vector(values(m, lines))
for m in xsrange(0, (n_max - r_L) // k**t_L + 1))
for m in xsrange(0, (n_verify - r_L) // k**t_L + 1))

class NoLinearCombination(ValueError):
class NoLinearCombination(RuntimeError):
pass

def find_linear_combination(t_L, r_L, lines):
Expand All @@ -1301,37 +1363,33 @@ def find_linear_combination(t_L, r_L, lines):
to_branch = []
lines = []

def include(line):
to_branch.append(line)
lines.append(line)
t, r = line
def include(t, r):
to_branch.append((t, r))
lines.append((t, r))
logger.info('including f_{%s*m+%s}', k**t, r)

if left is None:
line_L = (0, 0) # entries (t, r) --> k**t * m + r
include(line_L)
left = vector((len(seq(0)) + len(lines)-1)*(zero,) + (one,))
include(0, 0) # entries (t, r) --> k**t * m + r
assert len(lines) == 1
left = vector(len(seq(0))*(zero,) + (one,))

while to_branch:
line_R = to_branch.pop(0)
t_R, r_R = line_R
t_R, r_R = to_branch.pop(0)
if t_R >= max_exponent:
raise RuntimeError(f'aborting as exponents would be larger '
f'than max_exponent={max_exponent}')

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

try:
solution = find_linear_combination(t_L, r_L, lines)
linear_combination = find_linear_combination(t_L, r_L, lines)
except NoLinearCombination:
include(line_L)
solution = (len(lines)-1)*(zero,) + (one,)
logger.debug('M_%s: f_{%s*m+%s} = %s * X_m',
s_L, k**t_L, r_L, solution)
mu[s_L].append(solution)
include(t_L, r_L) # entries (t, r) --> k**t * m + r
linear_combination = (len(lines)-1)*(zero,) + (one,)
logger.debug('M_%s: f_{%s*m+%s} = %s * F_m',
s_L, k**t_L, r_L, linear_combination)
mu[s_L].append(linear_combination)

d = len(seq(0)) + len(lines)
mu = tuple(Matrix(domain, [pad_right(tuple(row), d, zero=zero) for row in M])
Expand Down