Cactus Groups¶
AUTHORS:
Travis Scrimshaw (1-2023): initial version
- class sage.groups.cactus_group.CactusGroup(n)[source]¶
Bases:
UniqueRepresentation
,Group
The cactus group.
The \(n\)-fruit cactus group \(J_n\) is the group generated by \(s_{pq}\) for \(1 \leq p < q \leq n\) with relations:
\(s_{pq}^2 = 1\)
\(s_{pq} s_{kl} = s_{kl} s_{pq}\) if the intervals \([p, q]\) and \([k, l]\) are disjoint, and
\(s_{pq} s_{kl} = s_{p+q-l,p+q-k} s_{pq}\) if \([k, l] \subseteq [p, q]\).
INPUT:
n
– integer
EXAMPLES:
We construct the cactus group \(J_3\) and do some basic computations:
sage: J3 = groups.misc.Cactus(3) sage: s12,s13,s23 = J3.group_generators() sage: s12 * s13 s[1,2]*s[1,3] sage: x = s12 * s23; x s[1,2]*s[2,3] sage: x^4 s[1,2]*s[2,3]*s[1,2]*s[2,3]*s[1,2]*s[2,3]*s[1,2]*s[2,3] sage: s12 * s13 == s13 * s23 True
>>> from sage.all import * >>> J3 = groups.misc.Cactus(Integer(3)) >>> s12,s13,s23 = J3.group_generators() >>> s12 * s13 s[1,2]*s[1,3] >>> x = s12 * s23; x s[1,2]*s[2,3] >>> x**Integer(4) s[1,2]*s[2,3]*s[1,2]*s[2,3]*s[1,2]*s[2,3]*s[1,2]*s[2,3] >>> s12 * s13 == s13 * s23 True
We verify the key equality in Lemma 2.3 in [White2015], which shows that \(J_5\) is generated by \(s_{1q}\):
sage: J5 = groups.misc.Cactus(5) sage: gens = J5.group_generators() sage: all(gens[(p,q)] == gens[(1,q)] * gens[(1,q-p+1)] * gens[(1,q)] ....: for p in range(1, 6) for q in range(p+1, 6)) True
>>> from sage.all import * >>> J5 = groups.misc.Cactus(Integer(5)) >>> gens = J5.group_generators() >>> all(gens[(p,q)] == gens[(Integer(1),q)] * gens[(Integer(1),q-p+Integer(1))] * gens[(Integer(1),q)] ... for p in range(Integer(1), Integer(6)) for q in range(p+Integer(1), Integer(6))) True
- class Element(parent, data)[source]¶
Bases:
MultiplicativeGroupElement
An element of a cactus group.
- to_matrix()[source]¶
Return
self
as a matrix.The resulting matrix is the
geometric representation
ofself
.EXAMPLES:
sage: J3 = groups.misc.Cactus(3) sage: s12,s13,s23 = J3.gens() sage: s12.to_matrix() [ -1 0 2*t] [ 0 1 0] [ 0 0 1] sage: (s12*s13).to_matrix() [2*t 0 -1] [ 0 -1 0] [ 1 0 0] sage: (s13*s23).to_matrix() [2*t 0 -1] [ 0 -1 0] [ 1 0 0] sage: (s13*s12).to_matrix() [ 0 0 1] [ 0 -1 0] [ -1 0 2*t] sage: all(x.to_matrix() * y.to_matrix() == (x*y).to_matrix() ....: for x in J3.gens() for y in J3.gens()) True
>>> from sage.all import * >>> J3 = groups.misc.Cactus(Integer(3)) >>> s12,s13,s23 = J3.gens() >>> s12.to_matrix() [ -1 0 2*t] [ 0 1 0] [ 0 0 1] >>> (s12*s13).to_matrix() [2*t 0 -1] [ 0 -1 0] [ 1 0 0] >>> (s13*s23).to_matrix() [2*t 0 -1] [ 0 -1 0] [ 1 0 0] >>> (s13*s12).to_matrix() [ 0 0 1] [ 0 -1 0] [ -1 0 2*t] >>> all(x.to_matrix() * y.to_matrix() == (x*y).to_matrix() ... for x in J3.gens() for y in J3.gens()) True
- to_permutation()[source]¶
Return the image of
self
under the canonical projection to the permutation group.EXAMPLES:
sage: J3 = groups.misc.Cactus(3) sage: s12,s13,s23 = J3.gens() sage: s12.to_permutation() [2, 1, 3] sage: s23.to_permutation() [1, 3, 2] sage: s13.to_permutation() [3, 2, 1] sage: elt = s12*s23*s13 sage: elt.to_permutation() [1, 3, 2] sage: J7 = groups.misc.Cactus(7) sage: J7.group_generators()[3,6].to_permutation() [1, 2, 6, 5, 4, 3, 7]
>>> from sage.all import * >>> J3 = groups.misc.Cactus(Integer(3)) >>> s12,s13,s23 = J3.gens() >>> s12.to_permutation() [2, 1, 3] >>> s23.to_permutation() [1, 3, 2] >>> s13.to_permutation() [3, 2, 1] >>> elt = s12*s23*s13 >>> elt.to_permutation() [1, 3, 2] >>> J7 = groups.misc.Cactus(Integer(7)) >>> J7.group_generators()[Integer(3),Integer(6)].to_permutation() [1, 2, 6, 5, 4, 3, 7]
We check that this respects the multiplication order of permutations:
sage: P3 = Permutations(3) sage: elt = s12*s23 sage: elt.to_permutation() == P3(s12) * P3(s23) True sage: Permutations.options.mult='r2l' sage: elt.to_permutation() == P3(s12) * P3(s23) True sage: Permutations.options.mult='l2r'
>>> from sage.all import * >>> P3 = Permutations(Integer(3)) >>> elt = s12*s23 >>> elt.to_permutation() == P3(s12) * P3(s23) True >>> Permutations.options.mult='r2l' >>> elt.to_permutation() == P3(s12) * P3(s23) True >>> Permutations.options.mult='l2r'
- bilinear_form(t=None)[source]¶
Return the
t
-bilinear form ofself
.We define a bilinear form \(B\) on the group algebra by
\[\begin{split}B(s_{ij}, s_{pq}) = \begin{cases} 1 & \text{if } i = p, j = q, \\ -t & \text{if } [i, j] \not\subseteq [p, q] \text{ and } [p, q] \not\subseteq [i, j], \\ 0 & \text{otherwise}. \end{cases}\end{split}\]In other words, it is \(1\) if \(s_{ij} = s_{pq}\), \(-t\) if \(s_{ij}\) and \(s_{pq}\) generate a free group, and \(0\) otherwise (they commute or almost commute).
INPUT:
t
– (default: \(t\) in \(\ZZ[t]\)) the variable \(t\)
EXAMPLES:
sage: J = groups.misc.Cactus(4) sage: B = J.bilinear_form() sage: B [ 1 0 0 -t -t 0] [ 0 1 0 0 -t -t] [ 0 0 1 0 0 0] [-t 0 0 1 0 -t] [-t -t 0 0 1 0] [ 0 -t 0 -t 0 1]
>>> from sage.all import * >>> J = groups.misc.Cactus(Integer(4)) >>> B = J.bilinear_form() >>> B [ 1 0 0 -t -t 0] [ 0 1 0 0 -t -t] [ 0 0 1 0 0 0] [-t 0 0 1 0 -t] [-t -t 0 0 1 0] [ 0 -t 0 -t 0 1]
We reorder the generators so the bilinear form is more “Coxeter-like”. In particular, when we remove the generator \(s_{1,4}\), we recover the bilinear form in Example 6.2.5 of [DJS2003]:
sage: J.gens() (s[1,2], s[1,3], s[1,4], s[2,3], s[2,4], s[3,4]) sage: S = SymmetricGroup(6) sage: g = S([1,4,6,2,5,3]) sage: B.permute_rows_and_columns(g, g) sage: B [ 1 -t 0 0 -t 0] [-t 1 -t 0 0 0] [ 0 -t 1 -t 0 0] [ 0 0 -t 1 -t 0] [-t 0 0 -t 1 0] [ 0 0 0 0 0 1]
>>> from sage.all import * >>> J.gens() (s[1,2], s[1,3], s[1,4], s[2,3], s[2,4], s[3,4]) >>> S = SymmetricGroup(Integer(6)) >>> g = S([Integer(1),Integer(4),Integer(6),Integer(2),Integer(5),Integer(3)]) >>> B.permute_rows_and_columns(g, g) >>> B [ 1 -t 0 0 -t 0] [-t 1 -t 0 0 0] [ 0 -t 1 -t 0 0] [ 0 0 -t 1 -t 0] [-t 0 0 -t 1 0] [ 0 0 0 0 0 1]
- gen(i, j=None)[source]¶
Return the \(i\)-th generator of
self
or the generator \(s_{ij}\).EXAMPLES:
sage: J3 = groups.misc.Cactus(3) sage: J3.gen(1) s[1,3] sage: J3.gen(1,2) s[1,2] sage: J3.gen(0,2) Traceback (most recent call last): ... ValueError: s[0,2] is not a valid generator sage: J3.gen(1,4) Traceback (most recent call last): ... ValueError: s[1,4] is not a valid generator sage: J3.gen(2,1) Traceback (most recent call last): ... ValueError: s[2,1] is not a valid generator
>>> from sage.all import * >>> J3 = groups.misc.Cactus(Integer(3)) >>> J3.gen(Integer(1)) s[1,3] >>> J3.gen(Integer(1),Integer(2)) s[1,2] >>> J3.gen(Integer(0),Integer(2)) Traceback (most recent call last): ... ValueError: s[0,2] is not a valid generator >>> J3.gen(Integer(1),Integer(4)) Traceback (most recent call last): ... ValueError: s[1,4] is not a valid generator >>> J3.gen(Integer(2),Integer(1)) Traceback (most recent call last): ... ValueError: s[2,1] is not a valid generator
- gens()[source]¶
Return the generators of
self
as a tuple.EXAMPLES:
sage: J3 = groups.misc.Cactus(3) sage: J3.gens() (s[1,2], s[1,3], s[2,3])
>>> from sage.all import * >>> J3 = groups.misc.Cactus(Integer(3)) >>> J3.gens() (s[1,2], s[1,3], s[2,3])
- geometric_representation_generators(t=None)[source]¶
Return the matrices corresponding to the generators of
self
.We construct a representation over \(R = \ZZ[t]\) of \(J_n\) as follows. Let \(E\) be the vector space over \(R\) spanned by \(\{\epsilon_v\}_v\), where \(v\) is a generator of \(J_n\). Fix some generator \(v\), and let \(E_v\) denote the span of \(\epsilon_u - \epsilon_{u'}\), where \(u'\) is the reflected interval of \(u\) in \(v\), over all \(u\) such that \(u \subset v\). Let \(F_v\) denote the orthogonal complement of \(R \epsilon_v \oplus E_v\) with respect to the
bilinear form
\(B\). We define the action of \(v\) on \(E\) by\[\rho(v) = -I |_{R\epsilon_v \oplus E_v} \oplus I |_{F_v}.\]By Theorem 6.2.3 of [DJS2003], this defines a representation of \(J_n\). It is expected that this is a faithful representation (see Remark 6.2.4 of [DJS2003]). As this arises from a blow-up and an analog of the geometric representation of the corresponding Coxeter group (the symmetric group), we call this the geometric representation.
INPUT:
t
– (default: \(t\) in \(\ZZ[t]\)) the variable \(t\)
EXAMPLES:
sage: J3 = groups.misc.Cactus(3) sage: list(J3.geometric_representation_generators()) [ [ -1 0 2*t] [ 0 0 1] [ 1 0 0] [ 0 1 0] [ 0 -1 0] [ 0 1 0] [ 0 0 1], [ 1 0 0], [2*t 0 -1] ]
>>> from sage.all import * >>> J3 = groups.misc.Cactus(Integer(3)) >>> list(J3.geometric_representation_generators()) [ [ -1 0 2*t] [ 0 0 1] [ 1 0 0] [ 0 1 0] [ 0 -1 0] [ 0 1 0] [ 0 0 1], [ 1 0 0], [2*t 0 -1] ]
We ran the following code with
max_tests = 15000
and did not find a counterexample to the faithfulness of this representation:sage: visited = set([J3.one()]) sage: cur = set([(J3.one(), J3.one().to_matrix())]) sage: mats = set([J3.one().to_matrix()]) sage: RG = list(J3.geometric_representation_generators()) sage: count = 0 sage: max_tests = 1000 sage: while cur: ....: count += 1 ....: if count >= max_tests: ....: break ....: elt, mat = cur.pop() ....: for g,r in zip(J3, RG): ....: val = elt * g ....: if val in visited: ....: continue ....: visited.add(val) ....: matp = mat * r ....: matp.set_immutable() ....: assert matp not in mats, f"not injective {val} \n{matp}" ....: mats.add(matp) ....: cur.add((val, matp))
>>> from sage.all import * >>> visited = set([J3.one()]) >>> cur = set([(J3.one(), J3.one().to_matrix())]) >>> mats = set([J3.one().to_matrix()]) >>> RG = list(J3.geometric_representation_generators()) >>> count = Integer(0) >>> max_tests = Integer(1000) >>> while cur: ... count += Integer(1) ... if count >= max_tests: ... break ... elt, mat = cur.pop() ... for g,r in zip(J3, RG): ... val = elt * g ... if val in visited: ... continue ... visited.add(val) ... matp = mat * r ... matp.set_immutable() ... assert matp not in mats, f"not injective {val} \n{matp}" ... mats.add(matp) ... cur.add((val, matp))
- group_generators()[source]¶
Return the group generators of
self
.EXAMPLES:
sage: J3 = groups.misc.Cactus(3) sage: J3.group_generators() Finite family {(1, 2): s[1,2], (1, 3): s[1,3], (2, 3): s[2,3]}
>>> from sage.all import * >>> J3 = groups.misc.Cactus(Integer(3)) >>> J3.group_generators() Finite family {(1, 2): s[1,2], (1, 3): s[1,3], (2, 3): s[2,3]}
- n()[source]¶
Return the value \(n\).
EXAMPLES:
sage: J3 = groups.misc.Cactus(3) sage: J3.n() 3
>>> from sage.all import * >>> J3 = groups.misc.Cactus(Integer(3)) >>> J3.n() 3
- one()[source]¶
Return the identity element in
self
.EXAMPLES:
sage: J3 = groups.misc.Cactus(3) sage: J3.one() 1
>>> from sage.all import * >>> J3 = groups.misc.Cactus(Integer(3)) >>> J3.one() 1
- random_element(max_length=10)[source]¶
Return a random element of
self
of length at mostmax_length
.EXAMPLES:
sage: J3 = groups.misc.Cactus(3) sage: J3.random_element() # random s[1,2]*s[2,3]*s[1,2]*s[1,3]
>>> from sage.all import * >>> J3 = groups.misc.Cactus(Integer(3)) >>> J3.random_element() # random s[1,2]*s[2,3]*s[1,2]*s[1,3]
- right_angled_coxeter_group()[source]¶
Return the right-angled Coxeter group that
self
(set-theoretically) embeds into.This is defined following [Most2019], where it was called the diagram group. It has generators (of order \(2\)) indexed by subsets of \(\{1, \ldots, n\}\) that commute if and only if one subset is contained in the other or they are disjoint. For the pure cactus group, this is also a group homomorphism, otherwise it is a group 1-cocycle [BCL2022].
EXAMPLES:
sage: J3 = groups.misc.Cactus(3) sage: J3.right_angled_coxeter_group() Coxeter group over Rational Field with Coxeter matrix: [ 1 -1 -1 2] [-1 1 -1 2] [-1 -1 1 2] [ 2 2 2 1]
>>> from sage.all import * >>> J3 = groups.misc.Cactus(Integer(3)) >>> J3.right_angled_coxeter_group() Coxeter group over Rational Field with Coxeter matrix: [ 1 -1 -1 2] [-1 1 -1 2] [-1 -1 1 2] [ 2 2 2 1]
- class sage.groups.cactus_group.PureCactusGroup(n)[source]¶
Bases:
KernelSubgroup
The pure cactus group.
The pure cactus group \(PJ_n\) is the kernel of the natural surjection of the cactus group \(J_n\) onto the symmetric group \(S_n\). In particular, we have the following (non-split) exact sequence:
\[1 \longrightarrow PJ_n \longrightarrow J_n \longrightarrow S_n \longrightarrow 1.\]- gen(i)[source]¶
Return the
i
-th generator ofself
.EXAMPLES:
sage: PJ3 = groups.misc.PureCactus(3) sage: PJ3.gen(0) s[2,3]*s[1,2]*s[2,3]*s[1,3] sage: PJ3.gen(1) s[1,2]*s[2,3]*s[1,2]*s[1,3] sage: PJ3.gen(5) Traceback (most recent call last): ... IndexError: tuple index out of range
>>> from sage.all import * >>> PJ3 = groups.misc.PureCactus(Integer(3)) >>> PJ3.gen(Integer(0)) s[2,3]*s[1,2]*s[2,3]*s[1,3] >>> PJ3.gen(Integer(1)) s[1,2]*s[2,3]*s[1,2]*s[1,3] >>> PJ3.gen(Integer(5)) Traceback (most recent call last): ... IndexError: tuple index out of range
- gens()[source]¶
Return the generators of
self
.ALGORITHM:
We use Wikipedia article Schreier’s_lemma and compute the traversal using the lex minimum elements (defined by the order of the generators of the ambient cactus group).
EXAMPLES:
We verify Corollary A.2 of [BCL2022]:
sage: PJ3 = groups.misc.PureCactus(3) sage: PJ3.gens() (s[2,3]*s[1,2]*s[2,3]*s[1,3], s[1,2]*s[2,3]*s[1,2]*s[1,3]) sage: a, b = PJ3.gens() sage: a * b # they are inverses of each other 1 sage: J3 = groups.misc.Cactus(3) sage: gen = (J3.gen(1,2)*J3.gen(1,3))^3 sage: gen s[1,2]*s[2,3]*s[1,2]*s[1,3] sage: gen == b True
>>> from sage.all import * >>> PJ3 = groups.misc.PureCactus(Integer(3)) >>> PJ3.gens() (s[2,3]*s[1,2]*s[2,3]*s[1,3], s[1,2]*s[2,3]*s[1,2]*s[1,3]) >>> a, b = PJ3.gens() >>> a * b # they are inverses of each other 1 >>> J3 = groups.misc.Cactus(Integer(3)) >>> gen = (J3.gen(Integer(1),Integer(2))*J3.gen(Integer(1),Integer(3)))**Integer(3) >>> gen s[1,2]*s[2,3]*s[1,2]*s[1,3] >>> gen == b True