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 - selfas a matrix.- The resulting matrix is the - geometric representationof- self.- 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 - selfunder 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 of- self.- 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 - selfor 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 - selfas 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 = 15000and 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 - selfof length at most- max_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 of- self.- 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