Skip to content

Commit 174c410

Browse files
author
Release Manager
committed
gh-35915: Is fully commutative for Coxeter group elements <!-- Please provide a concise, informative and self-explanatory title. --> <!-- Don't put issue numbers in the title. Put it in the Description below. --> <!-- For example, instead of "Fixes #12345", use "Add a new method to multiply two integers" --> ### 📚 Description This is adding a method "is_fully_commutative" to all Coxeter group elements. On the way, introduce iterators over the sets of reduced words of a given element. <!-- Describe your changes here in detail. --> <!-- Why is this change required? What problem does it solve? --> <!-- If this PR resolves an open issue, please link to it here. For example "Fixes #12345". --> <!-- If your change requires a documentation PR, please link it appropriately. --> ### 📝 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. - [ ] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation accordingly. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on - #12345: short description why this is a dependency - #34567: ... --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> URL: #35915 Reported by: Frédéric Chapoton Reviewer(s): Travis Scrimshaw
2 parents b84e184 + dca133a commit 174c410

File tree

4 files changed

+202
-71
lines changed

4 files changed

+202
-71
lines changed

src/sage/categories/coxeter_groups.py

Lines changed: 118 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,53 @@ def braid_group_as_finitely_presented_group(self):
251251
rels = self.braid_relations()
252252
return F / [prod(S[I.index(i)] for i in l) * prod(S[I.index(i)]**-1 for i in reversed(r)) for l, r in rels]
253253

254+
def braid_orbit_iter(self, word):
255+
r"""
256+
Iterate over the braid orbit of a word ``word`` of indices.
257+
258+
The input word does not need to be a reduced expression of
259+
an element.
260+
261+
INPUT:
262+
263+
- ``word`` -- a list (or iterable) of indices in
264+
``self.index_set()``
265+
266+
OUTPUT:
267+
268+
all lists that can be obtained from
269+
``word`` by replacements of braid relations
270+
271+
EXAMPLES::
272+
273+
sage: W = CoxeterGroups().example()
274+
sage: sorted(W.braid_orbit_iter([0, 1, 2, 1])) # optional - sage.combinat sage.groups
275+
[[0, 1, 2, 1], [0, 2, 1, 2], [2, 0, 1, 2]]
276+
"""
277+
word = list(word)
278+
from sage.combinat.root_system.braid_orbit import BraidOrbit
279+
280+
braid_rels = self.braid_relations()
281+
I = self.index_set()
282+
283+
from sage.rings.integer_ring import ZZ
284+
be_careful = any(i not in ZZ for i in I)
285+
286+
if be_careful:
287+
Iinv = {i: j for j, i in enumerate(I)}
288+
word = [Iinv[i] for i in word]
289+
braid_rels = [[[Iinv[i] for i in l],
290+
[Iinv[i] for i in r]] for l, r in braid_rels]
291+
292+
orb = BraidOrbit(word, braid_rels)
293+
294+
if be_careful:
295+
for word in orb:
296+
yield [I[i] for i in word]
297+
else:
298+
for I in orb:
299+
yield list(I)
300+
254301
def braid_orbit(self, word):
255302
r"""
256303
Return the braid orbit of a word ``word`` of indices.
@@ -314,26 +361,7 @@ def braid_orbit(self, word):
314361
315362
:meth:`.reduced_words`
316363
"""
317-
word = list(word)
318-
from sage.combinat.root_system.braid_orbit import BraidOrbit
319-
320-
braid_rels = self.braid_relations()
321-
I = self.index_set()
322-
323-
from sage.rings.integer_ring import ZZ
324-
be_careful = any(i not in ZZ for i in I)
325-
326-
if be_careful:
327-
Iinv = {i: j for j, i in enumerate(I)}
328-
word = [Iinv[i] for i in word]
329-
braid_rels = [[[Iinv[i] for i in l],
330-
[Iinv[i] for i in r]] for l, r in braid_rels]
331-
332-
orb = BraidOrbit(word, braid_rels)
333-
334-
if be_careful:
335-
return [[I[i] for i in word] for word in orb]
336-
return [list(I) for I in orb]
364+
return list(self.braid_orbit_iter(word))
337365

338366
def __iter__(self):
339367
r"""
@@ -1493,7 +1521,7 @@ def descents(self, side='right', index_set=None, positive=False):
14931521
return [i for i in index_set if self.has_descent(i, side=side,
14941522
positive=positive)]
14951523

1496-
def is_grassmannian(self, side="right"):
1524+
def is_grassmannian(self, side="right") -> bool:
14971525
"""
14981526
Return whether ``self`` is Grassmannian.
14991527
@@ -1529,6 +1557,49 @@ def is_grassmannian(self, side="right"):
15291557
"""
15301558
return len(self.descents(side=side)) <= 1
15311559

1560+
def is_fully_commutative(self) -> bool:
1561+
r"""
1562+
Check if ``self`` is a fully-commutative element.
1563+
1564+
We use the characterization that an element `w` in a Coxeter
1565+
system `(W,S)` is fully-commutative if and only if for every pair
1566+
of generators `s,t \in S` for which `m(s,t)>2`, no reduced
1567+
word of `w` contains the 'braid' word `sts...` of length
1568+
`m(s,t)` as a contiguous subword. See [Ste1996]_.
1569+
1570+
EXAMPLES::
1571+
1572+
sage: W = CoxeterGroup(['A', 3])
1573+
sage: len([1 for w in W if w.is_fully_commutative()])
1574+
14
1575+
sage: W = CoxeterGroup(['B', 3])
1576+
sage: len([1 for w in W if w.is_fully_commutative()])
1577+
24
1578+
1579+
TESTS::
1580+
1581+
sage: W = CoxeterGroup(matrix(2,2,[1,7,7,1]),index_set='ab')
1582+
sage: len([1 for w in W if w.is_fully_commutative()])
1583+
13
1584+
"""
1585+
word = self.reduced_word()
1586+
from sage.combinat.root_system.braid_orbit import is_fully_commutative as is_fully_comm
1587+
1588+
group = self.parent()
1589+
braid_rels = group.braid_relations()
1590+
I = group.index_set()
1591+
1592+
from sage.rings.integer_ring import ZZ
1593+
be_careful = any(i not in ZZ for i in I)
1594+
1595+
if be_careful:
1596+
Iinv = {i: j for j, i in enumerate(I)}
1597+
word = [Iinv[i] for i in word]
1598+
braid_rels = [[[Iinv[i] for i in l],
1599+
[Iinv[i] for i in r]] for l, r in braid_rels]
1600+
1601+
return is_fully_comm(word, braid_rels)
1602+
15321603
def reduced_word_reverse_iterator(self):
15331604
"""
15341605
Return a reverse iterator on a reduced word for ``self``.
@@ -1589,6 +1660,31 @@ def reduced_word(self):
15891660
result = list(self.reduced_word_reverse_iterator())
15901661
return list(reversed(result))
15911662

1663+
def reduced_words_iter(self):
1664+
r"""
1665+
Iterate over all reduced words for ``self``.
1666+
1667+
See :meth:`reduced_word` for the definition of a reduced
1668+
word.
1669+
1670+
The algorithm uses the Matsumoto property that any two
1671+
reduced expressions are related by braid relations, see
1672+
Theorem 3.3.1(ii) in [BB2005]_.
1673+
1674+
.. SEEALSO::
1675+
1676+
:meth:`braid_orbit_iter`
1677+
1678+
EXAMPLES::
1679+
1680+
sage: W = CoxeterGroups().example()
1681+
sage: s = W.simple_reflections()
1682+
sage: w = s[0] * s[2]
1683+
sage: sorted(w.reduced_words_iter()) # optional - sage.combinat
1684+
[[0, 2], [2, 0]]
1685+
"""
1686+
return self.parent().braid_orbit_iter(self.reduced_word())
1687+
15921688
def reduced_words(self):
15931689
r"""
15941690
Return all reduced words for ``self``.
@@ -1648,7 +1744,7 @@ def reduced_words(self):
16481744
:meth:`.reduced_word`, :meth:`.reduced_word_reverse_iterator`,
16491745
:meth:`length`, :meth:`reduced_word_graph`
16501746
"""
1651-
return self.parent().braid_orbit(self.reduced_word())
1747+
return list(self.reduced_words_iter())
16521748

16531749
def support(self):
16541750
r"""

src/sage/combinat/affine_permutation.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ def __call__(self, i):
217217
"""
218218
return self.value(i)
219219

220-
def is_i_grassmannian(self, i=0, side="right"):
220+
def is_i_grassmannian(self, i=0, side="right") -> bool:
221221
r"""
222222
Test whether ``self`` is `i`-grassmannian, i.e., either is the
223223
identity or has ``i`` as the sole descent.
@@ -277,7 +277,7 @@ def lower_covers(self,side="right"):
277277
S = self.descents(side)
278278
return [self.apply_simple_reflection(i, side) for i in S]
279279

280-
def is_one(self):
280+
def is_one(self) -> bool:
281281
r"""
282282
Tests whether the affine permutation is the identity.
283283
@@ -876,18 +876,21 @@ def to_lehmer_code(self, typ='decreasing', side='right'):
876876
code[i] += (b-i) // (self.k+1) + 1
877877
return Composition(code)
878878

879-
def is_fully_commutative(self):
879+
def is_fully_commutative(self) -> bool:
880880
r"""
881-
Determine whether ``self`` is fully commutative, i.e., has no
882-
reduced words with a braid.
881+
Determine whether ``self`` is fully commutative.
882+
883+
This means that it has no reduced word with a braid.
884+
885+
This uses a specific algorithm.
883886
884887
EXAMPLES::
885888
886889
sage: A = AffinePermutationGroup(['A',7,1])
887-
sage: p=A([3, -1, 0, 6, 5, 4, 10, 9])
890+
sage: p = A([3, -1, 0, 6, 5, 4, 10, 9])
888891
sage: p.is_fully_commutative()
889892
False
890-
sage: q=A([-3, -2, 0, 7, 9, 2, 11, 12])
893+
sage: q = A([-3, -2, 0, 7, 9, 2, 11, 12])
891894
sage: q.is_fully_commutative()
892895
True
893896
"""
@@ -900,14 +903,12 @@ def is_fully_commutative(self):
900903
if c[i] > 0:
901904
if firstnonzero is None:
902905
firstnonzero = i
903-
if m != -1 and c[i] - (i-m) >= c[m]:
906+
if m != -1 and c[i] - (i - m) >= c[m]:
904907
return False
905908
m = i
906-
#now check m (the last non-zero) against firstnonzero.
907-
d = self.n-(m-firstnonzero)
908-
if c[firstnonzero]-d >= c[m]:
909-
return False
910-
return True
909+
# now check m (the last non-zero) against first non-zero.
910+
d = self.n - (m - firstnonzero)
911+
return not c[firstnonzero] - d >= c[m]
911912

912913
def to_bounded_partition(self, typ='decreasing', side='right'):
913914
r"""

src/sage/combinat/fully_commutative_elements.py

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -153,44 +153,23 @@ def is_fully_commutative(self):
153153
sage: x = FC.element_class(FC, [1, 2, 1], check=False); x.is_fully_commutative()
154154
False
155155
"""
156-
matrix = self.parent().coxeter_group().coxeter_matrix()
157-
w = tuple(self)
158-
159-
# The following function detects 'braid' words.
160-
def contains_long_braid(w):
161-
for i in range(len(w) - 2):
162-
a = w[i]
163-
b = w[i + 1]
164-
m = matrix[a, b]
165-
if m > 2 and i + m <= len(w):
166-
ab_braid = (a, b) * (m // 2) + ((a,) if m % 2 else ())
167-
if w[i:i + m] == ab_braid:
168-
return True
169-
return False
156+
word = list(self)
157+
from sage.combinat.root_system.braid_orbit import is_fully_commutative as is_fully_comm
170158

171-
# The following function applies a commutation relation on a word.
172-
def commute_once(word, i):
173-
return word[:i] + (word[i + 1], word[i]) + word[i + 2:]
159+
group = self.parent().coxeter_group()
160+
braid_rels = group.braid_relations()
161+
I = group.index_set()
174162

175-
# A word is the reduced word of an FC element iff no sequence of
176-
# commutation relations on it yields a word with a 'braid' word:
177-
if contains_long_braid(w):
178-
return False
179-
else:
180-
l, checked, queue = len(w), {w}, deque([w])
181-
while queue:
182-
word = queue.pop()
183-
for i in range(l - 1):
184-
a, b = word[i], word[i + 1]
185-
if matrix[a, b] == 2:
186-
new_word = commute_once(word, i)
187-
if new_word not in checked:
188-
if contains_long_braid(new_word):
189-
return False
190-
else:
191-
checked.add(new_word)
192-
queue.appendleft(new_word)
193-
return True
163+
from sage.rings.integer_ring import ZZ
164+
be_careful = any(i not in ZZ for i in I)
165+
166+
if be_careful:
167+
Iinv = {i: j for j, i in enumerate(I)}
168+
word = [Iinv[i] for i in word]
169+
braid_rels = [[[Iinv[i] for i in l],
170+
[Iinv[i] for i in r]] for l, r in braid_rels]
171+
172+
return is_fully_comm(word, braid_rels)
194173

195174
# Representing FC elements: Heaps
196175
def heap(self, **kargs):

src/sage/combinat/root_system/braid_orbit.pyx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,61 @@ cpdef set BraidOrbit(list word, list rels):
7474
return words
7575

7676

77+
cpdef bint is_fully_commutative(list word, list rels):
78+
r"""
79+
Check if the braid orbit of ``word`` is using a braid relation.
80+
81+
INPUT:
82+
83+
- ``word`` -- list of integers
84+
85+
- ``rels`` -- list of pairs ``(A, B)``, where ``A`` and ``B`` are
86+
lists of integers the same length
87+
88+
EXAMPLES::
89+
90+
sage: from sage.combinat.root_system.braid_orbit import is_fully_commutative
91+
sage: rels = [[[2, 1, 2], [1, 2, 1]], [[3, 1], [1, 3]], [[3, 2, 3], [2, 3, 2]]]
92+
sage: word = [1,2,1,3,2,1]
93+
sage: is_fully_commutative(word, rels)
94+
False
95+
sage: word = [1,2,3]
96+
sage: is_fully_commutative(word, rels)
97+
True
98+
"""
99+
cdef int i, l, rel_l, loop_ind, list_len
100+
cdef tuple left, right, test_word, new_word
101+
cdef list rel
102+
103+
l = len(word)
104+
cdef set words = set( [tuple(word)] )
105+
cdef list test_words = [ tuple(word) ]
106+
107+
rels = rels + [[b, a] for a, b in rels]
108+
rels = [[tuple(a), tuple(b), len(a)] for a, b in rels]
109+
110+
loop_ind = 0
111+
list_len = 1
112+
while loop_ind < list_len:
113+
sig_check()
114+
test_word = <tuple> test_words[loop_ind]
115+
loop_ind += 1
116+
for rel in rels:
117+
left = <tuple> rel[0]
118+
right = <tuple> rel[1]
119+
rel_l = <int> rel[2]
120+
for i in range(l-rel_l+1):
121+
if pattern_match(test_word, i, left, rel_l):
122+
if rel_l > 2:
123+
return False
124+
new_word = test_word[:i] + right + test_word[i+rel_l:]
125+
if new_word not in words:
126+
words.add(new_word)
127+
test_words.append(new_word)
128+
list_len += 1
129+
return True
130+
131+
77132
cdef inline bint pattern_match(tuple L, int i, tuple X, int l):
78133
r"""
79134
Return ``True`` if ``L[i:i+l] == X``.

0 commit comments

Comments
 (0)