Skip to content

Commit edc4c57

Browse files
author
Release Manager
committedAug 5, 2023
gh-35894: Regular sequences: implement convolution / ring structure ### 📚 Description Add/implement convolution of two regular sequences, therefore making the parent a ring. Details of the algorithm: [convolution-sagemath-pr- 35894.pdf](https://github.com/sagemath/sage/files/12216984/convolution- sagemath-pr-35894.pdf) See also meta issue #21202. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. It should be `[x]` not `[x ]`. --> - [x] The title is concise, informative, and self-explanatory. - [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 accordingly. ### ⌛ Dependencies No formal dependencies, but there are trivial merge conflicts---in both branches, methods are inserted at the same position in the code--- between: - #21343: deal with mu[0]*right != right in k-regular sequences - #35894: Regular sequences: implement convolution / ring structure URL: #35894 Reported by: Daniel Krenn Reviewer(s): cheuberg, Daniel Krenn
2 parents adb62c0 + 26ca939 commit edc4c57

File tree

2 files changed

+248
-16
lines changed

2 files changed

+248
-16
lines changed
 

‎src/sage/combinat/k_regular_sequence.py

+152-3
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,115 @@ def forward_differences(self, **kwds):
10371037
"""
10381038
return self.subsequence(1, {1: 1, 0: -1}, **kwds)
10391039

1040+
@minimize_result
1041+
def _mul_(self, other):
1042+
r"""
1043+
Return the product of this `k`-regular sequence with ``other``,
1044+
where the multiplication is convolution of power series.
1045+
1046+
The operator `*` is mapped to :meth:`convolution`.
1047+
1048+
INPUT:
1049+
1050+
- ``other`` -- a :class:`kRegularSequence`
1051+
1052+
- ``minimize`` -- (default: ``None``) a boolean or ``None``.
1053+
If ``True``, then :meth:`~RecognizableSeries.minimized` is called after the operation,
1054+
if ``False``, then not. If this argument is ``None``, then
1055+
the default specified by the parent's ``minimize_results`` is used.
1056+
1057+
OUTPUT:
1058+
1059+
A :class:`kRegularSequence`
1060+
1061+
ALGORITHM:
1062+
1063+
See pdf attached to
1064+
`github pull request #35894 <https://github.com/sagemath/sage/pull/35894>`_
1065+
which contains a draft describing the details of the used algorithm.
1066+
1067+
EXAMPLES::
1068+
1069+
sage: Seq2 = kRegularSequenceSpace(2, ZZ)
1070+
sage: E = Seq2((Matrix([[0, 1], [0, 1]]), Matrix([[0, 0], [0, 1]])),
1071+
....: vector([1, 0]), vector([1, 1]))
1072+
sage: E
1073+
2-regular sequence 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, ...
1074+
1075+
We can build the convolution (in the sense of power-series) of `E` by
1076+
itself via::
1077+
1078+
sage: E.convolution(E)
1079+
2-regular sequence 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, ...
1080+
1081+
This is the same as using multiplication operator::
1082+
1083+
sage: E * E
1084+
2-regular sequence 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, ...
1085+
1086+
Building :meth:`partial_sums` can also be seen as a convolution::
1087+
1088+
sage: o = Seq2.one_hadamard()
1089+
sage: E * o
1090+
2-regular sequence 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, ...
1091+
sage: E * o == E.partial_sums(include_n=True)
1092+
True
1093+
1094+
TESTS::
1095+
1096+
sage: E * o == o * E
1097+
True
1098+
"""
1099+
from sage.arith.srange import srange
1100+
from sage.matrix.constructor import Matrix
1101+
from sage.matrix.special import zero_matrix
1102+
from sage.modules.free_module_element import vector
1103+
1104+
P = self.parent()
1105+
k = P.k
1106+
1107+
def tensor_product(left, right):
1108+
T = left.tensor_product(right)
1109+
T.subdivide()
1110+
return T
1111+
1112+
matrices_0 = {r: sum(tensor_product(self.mu[s], other.mu[r-s])
1113+
for s in srange(0, r+1))
1114+
for r in P.alphabet()}
1115+
matrices_1 = {r: sum(tensor_product(self.mu[s], other.mu[k+r-s])
1116+
for s in srange(r+1, k))
1117+
for r in P.alphabet()}
1118+
left = vector(tensor_product(Matrix(self.left), Matrix(other.left)))
1119+
right = vector(tensor_product(Matrix(self.right), Matrix(other.right)))
1120+
1121+
def linear_representation_morphism_recurrence_order_1(C, D):
1122+
r"""
1123+
Return the morphism of a linear representation
1124+
for the sequence `z_n` satisfying
1125+
`z_{kn+r} = C_r z_n + D_r z_{n-1}`.
1126+
"""
1127+
Z = zero_matrix(C[0].dimensions()[0])
1128+
1129+
def blocks(r):
1130+
upper = list([C[s], D[s], Z]
1131+
for s in reversed(srange(max(0, r-2), r+1)))
1132+
lower = list([Z, C[s], D[s]]
1133+
for s in reversed(srange(k-3+len(upper), k)))
1134+
return upper + lower
1135+
1136+
return {r: Matrix.block(blocks(r)) for r in P.alphabet()}
1137+
1138+
result = P.element_class(
1139+
P,
1140+
linear_representation_morphism_recurrence_order_1(matrices_0,
1141+
matrices_1),
1142+
vector(list(left) + (2*len(list(left)))*[0]),
1143+
vector(list(right) + (2*len(list(right)))*[0]))
1144+
1145+
return result
1146+
1147+
convolution = _mul_
1148+
10401149
@minimize_result
10411150
def partial_sums(self, include_n=False):
10421151
r"""
@@ -1223,7 +1332,10 @@ class kRegularSequenceSpace(RecognizableSeriesSpace):
12231332
Element = kRegularSequence
12241333

12251334
@classmethod
1226-
def __normalize__(cls, k, coefficient_ring, **kwds):
1335+
def __normalize__(cls, k,
1336+
coefficient_ring,
1337+
category=None,
1338+
**kwds):
12271339
r"""
12281340
Normalizes the input in order to ensure a unique
12291341
representation.
@@ -1234,13 +1346,17 @@ def __normalize__(cls, k, coefficient_ring, **kwds):
12341346
12351347
sage: Seq2 = kRegularSequenceSpace(2, ZZ)
12361348
sage: Seq2.category()
1237-
Category of modules over Integer Ring
1349+
Category of algebras over Integer Ring
12381350
sage: Seq2.alphabet()
12391351
{0, 1}
12401352
"""
12411353
from sage.arith.srange import srange
1354+
from sage.categories.algebras import Algebras
1355+
category = category or Algebras(coefficient_ring)
12421356
nargs = super().__normalize__(coefficient_ring,
1243-
alphabet=srange(k), **kwds)
1357+
alphabet=srange(k),
1358+
category=category,
1359+
**kwds)
12441360
return (k,) + nargs
12451361

12461362
def __init__(self, k, *args, **kwds):
@@ -1331,6 +1447,39 @@ def _n_to_index_(self, n):
13311447
except OverflowError:
13321448
raise ValueError('value {} of index is negative'.format(n)) from None
13331449

1450+
@cached_method
1451+
def one(self):
1452+
r"""
1453+
Return the one element of this :class:`kRegularSequenceSpace`,
1454+
i.e. the unique neutral element for `*` and also
1455+
the embedding of the one of the coefficient ring into
1456+
this :class:`kRegularSequenceSpace`.
1457+
1458+
EXAMPLES::
1459+
1460+
sage: Seq2 = kRegularSequenceSpace(2, ZZ)
1461+
sage: O = Seq2.one(); O
1462+
2-regular sequence 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
1463+
sage: O.linear_representation()
1464+
((1), Finite family {0: [1], 1: [0]}, (1))
1465+
1466+
TESTS::
1467+
1468+
sage: Seq2.one() is Seq2.one()
1469+
True
1470+
"""
1471+
from sage.matrix.constructor import Matrix
1472+
from sage.modules.free_module_element import vector
1473+
1474+
R = self.coefficient_ring()
1475+
one = R.one()
1476+
zero = R.zero()
1477+
return self.element_class(self,
1478+
[Matrix([[one]])]
1479+
+ (self.k-1)*[Matrix([[zero]])],
1480+
vector([one]),
1481+
vector([one]))
1482+
13341483
def some_elements(self):
13351484
r"""
13361485
Return some elements of this `k`-regular sequence.

‎src/sage/combinat/recognizable_series.py

+96-13
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,12 @@ def minimized(self):
11401140
sage: all(c == d and v == w
11411141
....: for (c, v), (d, w) in islice(zip(iter(S), iter(M)), 20))
11421142
True
1143+
1144+
TESTS::
1145+
1146+
sage: Rec((Matrix([[0]]), Matrix([[0]])),
1147+
....: vector([1]), vector([0])).minimized().linear_representation()
1148+
((), Finite family {0: [], 1: []}, ())
11431149
"""
11441150
return self._minimized_right_()._minimized_left_()
11451151

@@ -1314,7 +1320,7 @@ def _add_(self, other):
13141320

13151321
result = P.element_class(
13161322
P,
1317-
dict((a, self.mu[a].block_sum(other.mu[a])) for a in P.alphabet()),
1323+
{a: self.mu[a].block_sum(other.mu[a]) for a in P.alphabet()},
13181324
vector(tuple(self.left) + tuple(other.left)),
13191325
vector(tuple(self.right) + tuple(other.right)))
13201326

@@ -1376,6 +1382,11 @@ def _rmul_(self, other):
13761382
sage: 1 * E is E
13771383
True
13781384
1385+
::
1386+
1387+
sage: 0 * E is Seq2.zero()
1388+
True
1389+
13791390
We test that ``_rmul_`` and ``_lmul_`` are actually called::
13801391
13811392
sage: def print_name(f):
@@ -1394,9 +1405,11 @@ def _rmul_(self, other):
13941405
_lmul_
13951406
2-regular sequence 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, ...
13961407
"""
1408+
P = self.parent()
1409+
if other.is_zero():
1410+
return P._zero_()
13971411
if other.is_one():
13981412
return self
1399-
P = self.parent()
14001413
return P.element_class(P, self.mu, other * self.left, self.right)
14011414

14021415
def _lmul_(self, other):
@@ -1433,6 +1446,11 @@ def _lmul_(self, other):
14331446
sage: E * 1 is E
14341447
True
14351448
1449+
::
1450+
1451+
sage: E * 0 is Seq2.zero()
1452+
True
1453+
14361454
The following is not tested, as `MS^i` for integers `i` does
14371455
not work, thus ``vector([m])`` fails. (See :trac:`21317` for
14381456
details.)
@@ -1449,9 +1467,11 @@ def _lmul_(self, other):
14491467
sage: M # not tested
14501468
sage: M.linear_representation() # not tested
14511469
"""
1470+
P = self.parent()
1471+
if other.is_zero():
1472+
return P._zero_()
14521473
if other.is_one():
14531474
return self
1454-
P = self.parent()
14551475
return P.element_class(P, self.mu, self.left, self.right * other)
14561476

14571477
@minimize_result
@@ -1557,8 +1577,7 @@ def tensor_product(left, right):
15571577
return T
15581578
result = P.element_class(
15591579
P,
1560-
dict((a, tensor_product(self.mu[a], other.mu[a]))
1561-
for a in P.alphabet()),
1580+
{a: tensor_product(self.mu[a], other.mu[a]) for a in P.alphabet()},
15621581
vector(tensor_product(Matrix(self.left), Matrix(other.left))),
15631582
vector(tensor_product(Matrix(self.right), Matrix(other.right))))
15641583

@@ -1952,6 +1971,59 @@ def some_elements(self, **kwds):
19521971
break
19531972
yield self(mu, *LR, **kwds)
19541973

1974+
@cached_method
1975+
def _zero_(self):
1976+
r"""
1977+
Return the zero element of this :class:`RecognizableSeriesSpace`,
1978+
i.e. the unique neutral element for `+`.
1979+
1980+
TESTS::
1981+
1982+
sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1])
1983+
sage: Z = Rec._zero_(); Z
1984+
0
1985+
sage: Z.linear_representation()
1986+
((), Finite family {0: [], 1: []}, ())
1987+
"""
1988+
from sage.matrix.constructor import Matrix
1989+
from sage.modules.free_module_element import vector
1990+
from sage.sets.family import Family
1991+
1992+
return self.element_class(
1993+
self, Family(self.alphabet(), lambda a: Matrix()),
1994+
vector([]), vector([]))
1995+
1996+
@cached_method
1997+
def one(self):
1998+
r"""
1999+
Return the one element of this :class:`RecognizableSeriesSpace`,
2000+
i.e. the embedding of the one of the coefficient ring into
2001+
this :class:`RecognizableSeriesSpace`.
2002+
2003+
EXAMPLES::
2004+
2005+
sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1])
2006+
sage: O = Rec.one(); O
2007+
[] + ...
2008+
sage: O.linear_representation()
2009+
((1), Finite family {0: [0], 1: [0]}, (1))
2010+
2011+
TESTS::
2012+
2013+
sage: Rec.one() is Rec.one()
2014+
True
2015+
"""
2016+
from sage.matrix.constructor import Matrix
2017+
from sage.modules.free_module_element import vector
2018+
2019+
R = self.coefficient_ring()
2020+
one = R.one()
2021+
zero = R.zero()
2022+
return self.element_class(self,
2023+
len(self.alphabet())*[Matrix([[zero]])],
2024+
vector([one]),
2025+
vector([one]))
2026+
19552027
@cached_method
19562028
def one_hadamard(self):
19572029
r"""
@@ -1979,7 +2051,7 @@ def one_hadamard(self):
19792051
from sage.modules.free_module_element import vector
19802052

19812053
one = self.coefficient_ring()(1)
1982-
return self(dict((a, Matrix([[one]])) for a in self.alphabet()),
2054+
return self({a: Matrix([[one]]) for a in self.alphabet()},
19832055
vector([one]), vector([one]))
19842056

19852057
def _element_constructor_(self, data,
@@ -2006,6 +2078,19 @@ def _element_constructor_(self, data,
20062078
sage: Rec(S) is S
20072079
True
20082080
2081+
::
2082+
2083+
sage: A = Rec(42); A
2084+
42*[] + ...
2085+
sage: A.linear_representation()
2086+
((42), Finite family {0: [0], 1: [0]}, (1))
2087+
sage: Z = Rec(0); Z
2088+
0
2089+
sage: Z.linear_representation()
2090+
((), Finite family {0: [], 1: []}, ())
2091+
2092+
::
2093+
20092094
sage: Rec((M0, M1))
20102095
Traceback (most recent call last):
20112096
...
@@ -2024,20 +2109,18 @@ def _element_constructor_(self, data,
20242109
ValueError: left or right vector is None
20252110
"""
20262111
if isinstance(data, int) and data == 0:
2027-
from sage.matrix.constructor import Matrix
2028-
from sage.modules.free_module_element import vector
2029-
from sage.sets.family import Family
2030-
2031-
return self.element_class(
2032-
self, Family(self.alphabet(), lambda a: Matrix()),
2033-
vector([]), vector([]))
2112+
return self._zero_()
20342113

20352114
if type(data) == self.element_class and data.parent() == self:
20362115
element = data
20372116

20382117
elif isinstance(data, RecognizableSeries):
20392118
element = self.element_class(self, data.mu, data.left, data.right)
20402119

2120+
elif data in self.coefficient_ring():
2121+
c = self.coefficient_ring()(data)
2122+
return c * self.one()
2123+
20412124
else:
20422125
mu = data
20432126
if left is None or right is None:

0 commit comments

Comments
 (0)
Please sign in to comment.