Weyl Groups
The mutation matrices \(M_0, M_1, \ldots, M_{n-1}\) generate a group under matrix multiplication: the Weyl group \(W\). This section describes the general theory and illustrates it for every Dynkin type.
1. General properties
Every Weyl group is determined by its mutation matrices, which satisfy three types of relations.
Involutions. Each generator is its own inverse:
This is because the mutation rule is self-reversing: mutating the same node twice restores the original vector.
Commutation. If nodes \(i\) and \(j\) are not connected by any edge in the Dynkin diagram, then their mutations commute:
This is because mutating at node \(i\) only changes the \(i\)-th component, and the formula for that component involves only the neighbors of \(i\). If \(j\) is not a neighbor, the two mutations don’t interfere.
Braid relations. If nodes \(i\) and \(j\) are connected by \(m\) edges (counting multiplicity, in either direction), then:
where the order \(m_{ij}\) depends on the edge type:
Edge type |
Adjacency |
Order of \(M_i M_j\) |
Appears in |
|---|---|---|---|
No edge |
\(A_{ij} = A_{ji} = 0\) |
2 (commuting) |
Non-adjacent nodes |
Simple edge |
\(A_{ij} = A_{ji} = 1\) |
3 |
ADE types |
Double edge (2,1) |
\(A_{ij} = 2, A_{ji} = 1\) |
4 |
B, C, F4 |
Triple edge (3,1) |
\(A_{ij} = 3, A_{ji} = 1\) |
6 |
G2 |
These three sets of relations (involutions, commutations, braids) are a complete presentation of the Weyl group. That is, \(W\) is the largest group generated by \(n\) involutions satisfying these relations, and nothing more.
The Coxeter element. The product of all generators in order,
is called the Coxeter element. Its order \(h\) is the Coxeter number, an important invariant that satisfies:
where \(|\Phi^+|\) is the number of positive roots and \(n\) is the number of nodes (rank).
2. Summary table
Type |
Rank \(n\) |
\(|\Phi^+|\) |
\(h\) |
\(|W|\) |
\(W\) isomorphic to |
|---|---|---|---|---|---|
\(A_n\) |
\(n\) |
\(\frac{n(n+1)}{2}\) |
\(n + 1\) |
\((n+1)!\) |
\(S_{n+1}\) (symmetric group) |
\(D_n\) |
\(n\) |
\(n(n-1)\) |
\(2(n-1)\) |
\(2^{n-1} \cdot n!\) |
\((\mathbb{Z}/2)^{n-1} \rtimes S_n\) |
\(E_6\) |
6 |
36 |
12 |
51,840 |
\(O^-(6, \mathbb{F}_2)\) |
\(E_7\) |
7 |
63 |
18 |
2,903,040 |
\(Sp(6, \mathbb{F}_2) \times \mathbb{Z}/2\) |
\(E_8\) |
8 |
120 |
30 |
696,729,600 |
\(O^+(8, \mathbb{F}_2)\) |
\(B_n\) |
\(n\) |
\(n^2\) |
\(2n\) |
\(2^n \cdot n!\) |
Signed permutations (hyperoctahedral) |
\(C_n\) |
\(n\) |
\(n^2\) |
\(2n\) |
\(2^n \cdot n!\) |
Same as \(B_n\) |
\(F_4\) |
4 |
24 |
12 |
1,152 |
\((\mathbb{Z}/2)^4 \rtimes S_4\) (with extra relations) |
\(G_2\) |
2 |
6 |
6 |
12 |
Dihedral group \(D_6\) |
Note that \(B_n\) and \(C_n\) have the same Weyl group despite having different root systems. This is because the Weyl group depends only on the angles between simple roots (the pairwise orders), not on their lengths.
3. Worked examples
A2: the symmetric group S3
>>> from mutation_game import MutationGame
>>> import numpy as np
>>> game = MutationGame.from_dynkin("A2")
>>> # Two generators
>>> print("M_0 ="); print(game.mutation_matrix(0))
M_0 =
[[-1 1]
[ 0 1]]
>>> print("M_1 ="); print(game.mutation_matrix(1))
M_1 =
[[ 1 0]
[ 1 -1]]
>>> # Braid relation: (M_0 M_1)^3 = I
>>> prod = game.mutation_matrix(0) @ game.mutation_matrix(1)
>>> print("(M_0 M_1)^3 ="); print(np.linalg.matrix_power(prod, 3))
(M_0 M_1)^3 =
[[1 0]
[0 1]]
The Weyl group \(W(A_2) \cong S_3\) has 6 elements. We can list them all:
>>> # All 6 elements: I, M_0, M_1, M_0 M_1, M_1 M_0, M_0 M_1 M_0
>>> M0, M1 = game.mutation_matrix(0), game.mutation_matrix(1)
>>> I = np.eye(2, dtype=int)
>>> elements = [I, M0, M1, M0 @ M1, M1 @ M0, M0 @ M1 @ M0]
>>> print(f" |W(A2)| = {len(elements)}")
|W(A2)| = 6
These correspond to the 6 permutations of \(\{1, 2, 3\}\): the identity, two transpositions \((12)\) and \((23)\), two 3-cycles \((123)\) and \((132)\), and the transposition \((13)\).
G2: the dihedral group D6
>>> game = MutationGame.from_dynkin("G2")
>>> M0, M1 = game.mutation_matrix(0), game.mutation_matrix(1)
>>> # Triple edge => order 6
>>> prod = M0 @ M1
>>> P = np.eye(2, dtype=int)
>>> for k in range(1, 10):
... P = P @ prod
... if np.allclose(P, np.eye(2)):
... print(f" (M_0 M_1)^{k} = I")
... break
(M_0 M_1)^6 = I
>>> # Coxeter number
>>> print(f" Coxeter number h = 6")
>>> print(f" |W(G2)| = 2h = 12")
Coxeter number h = 6
|W(G2)| = 2h = 12
\(W(G_2)\) is the dihedral group \(D_6\) — the symmetry group of a regular hexagon (6 rotations + 6 reflections). The Coxeter element acts as a 60-degree rotation.
F4: generators, relations, and Coxeter element
>>> game = MutationGame.from_dynkin("F4")
>>> # The four generators
>>> for k in range(4):
... print(f"M_{k} ="); print(game.mutation_matrix(k))
M_0 =
[[-1 1 0 0]
[ 0 1 0 0]
[ 0 0 1 0]
[ 0 0 0 1]]
M_1 =
[[ 1 0 0 0]
[ 1 -1 1 0]
[ 0 0 1 0]
[ 0 0 0 1]]
M_2 =
[[ 1 0 0 0]
[ 0 1 0 0]
[ 0 2 -1 1]
[ 0 0 0 1]]
M_3 =
[[ 1 0 0 0]
[ 0 1 0 0]
[ 0 0 1 0]
[ 0 0 1 -1]]
Note \(M_2\) has a 2 in its replaced row — this comes from the double
edge (two incoming edges from node 1 to node 2).
>>> # Pairwise orders reveal the Dynkin diagram structure
>>> for i in range(4):
... for j in range(i+1, 4):
... prod = game.mutation_matrix(i) @ game.mutation_matrix(j)
... P = np.eye(4, dtype=int)
... for order in range(1, 20):
... P = P @ prod
... if np.allclose(P, np.eye(4)):
... print(f" (M_{i} M_{j})^{order} = I")
... break
(M_0 M_1)^3 = I
(M_0 M_2)^2 = I
(M_0 M_3)^2 = I
(M_1 M_2)^4 = I
(M_1 M_3)^2 = I
(M_2 M_3)^3 = I
Reading off the orders: 3 (simple edge 0-1), 4 (double edge 1-2), 3 (simple edge 2-3), and 2 for all non-adjacent pairs. This recovers the \(F_4\) Dynkin diagram.
>>> # Coxeter element and Coxeter number
>>> C = np.eye(4, dtype=int)
>>> for k in range(4):
... C = C @ game.mutation_matrix(k)
>>> print("Coxeter element ="); print(C)
Coxeter element =
[[ 0 1 0 -1]
[ 1 1 0 -1]
[ 0 2 0 -1]
[ 0 0 1 -1]]
>>> P = np.eye(4, dtype=int)
>>> for h in range(1, 30):
... P = P @ C
... if np.allclose(P, np.eye(4)):
... print(f" Coxeter number h = {h}")
... break
Coxeter number h = 12
>>> # Verify: |Phi+| = n*h/2
>>> roots = game.calculate_roots()
>>> pos = [r for r in roots if all(v >= 0 for v in r)]
>>> print(f" n*h/2 = {4*12//2}, |Phi+| = {len(pos)}")
n*h/2 = 24, |Phi+| = 24
D4: triality
\(D_4\) is special because it has a triality symmetry — the three outer nodes (0, 2, 3) are interchangeable.
>>> game = MutationGame.from_dynkin("D4")
>>> # Pairwise orders
>>> for i in range(4):
... for j in range(i+1, 4):
... prod = game.mutation_matrix(i) @ game.mutation_matrix(j)
... P = np.eye(4, dtype=int)
... for order in range(1, 20):
... P = P @ prod
... if np.allclose(P, np.eye(4)):
... print(f" (M_{i} M_{j})^{order} = I")
... break
(M_0 M_1)^3 = I
(M_0 M_2)^2 = I
(M_0 M_3)^2 = I
(M_1 M_2)^3 = I
(M_1 M_3)^3 = I
(M_2 M_3)^2 = I
Node 1 is connected to all three outer nodes (0, 2, 3) by simple edges, while the outer nodes commute with each other. The Weyl group has order \(2^3 \cdot 4! = 192\).
>>> # Coxeter number
>>> C = np.eye(4, dtype=int)
>>> for k in range(4):
... C = C @ game.mutation_matrix(k)
>>> P = np.eye(4, dtype=int)
>>> for h in range(1, 30):
... P = P @ C
... if np.allclose(P, np.eye(4)):
... print(f" Coxeter number h = {h}")
... break
Coxeter number h = 6
4. Recovering the Dynkin diagram from the Weyl group
A remarkable fact: you can recover the Dynkin diagram entirely from the pairwise orders of the generators. The recipe is:
Compute \((M_i M_j)^k\) for increasing \(k\) until you reach the identity
Record the order \(m_{ij}\) for each pair
Draw an edge between nodes \(i\) and \(j\) if \(m_{ij} > 2\):
\(m_{ij} = 3\): simple edge
\(m_{ij} = 4\): double edge
\(m_{ij} = 6\): triple edge
>>> from mutation_game import MutationGame
>>> import numpy as np
>>> # Mystery graph: what type is it?
>>> game = MutationGame.from_dynkin("E6")
>>> n = game.num_nodes
>>> print("Pairwise orders:")
>>> for i in range(n):
... for j in range(i+1, n):
... prod = game.mutation_matrix(i) @ game.mutation_matrix(j)
... P = np.eye(n, dtype=int)
... for order in range(1, 20):
... P = P @ prod
... if np.allclose(P, np.eye(n)):
... if order > 2:
... print(f" nodes {i}-{j}: order {order} => edge")
... break
Pairwise orders:
nodes 0-1: order 3 => edge
nodes 1-2: order 3 => edge
nodes 2-3: order 3 => edge
nodes 2-5: order 3 => edge
nodes 3-4: order 3 => edge
This recovers the \(E_6\) diagram: a chain 0-1-2-3-4 with a branch
at node 2 connecting to node 5.