Maximal Subgroups and Branching Rules

Branching rules

If \(G\) is a Lie group and \(H\) is a subgroup, one often needs to know how representations of \(G\) restrict to \(H\). Irreducibles usually do not restrict to irreducibles. In some cases the restriction is regular and predictable, in other cases it is chaotic. In some cases it follows a rule that can be described combinatorially, but the combinatorial description is subtle. The description of how irreducibles decompose into irreducibles is called a branching rule.

References for this topic:

Sage can compute how a character of \(G\) restricts to \(H\). It does so not by memorizing a combinatorial rule, but by computing the character and restricting the character to a maximal torus of \(H\). What Sage has memorized (in a series of built-in encoded rules) are the various embeddings of maximal tori of maximal subgroups of \(G\). The maximal subgroups of Lie groups were determined in [Dynkin1952]. This approach to computing branching rules has a limitation: the character must fit into memory and be computable by Sage’s internal code in real time.

It is sufficient to consider the case where \(H\) is a maximal subgroup of \(G\), since if this is known then one may branch down successively through a series of subgroups, each maximal in its predecessors. A problem is therefore to understand the maximal subgroups in a Lie group, and to give branching rules for each, and a goal of this tutorial is to explain the embeddings of maximal subgroups.

Sage has a class BranchingRule for branching rules. The function branching_rule returns elements of this class. For example, the natural embedding of \(Sp(4)\) into \(SL(4)\) corresponds to the branching rule that we may create as follows:

sage: b = branching_rule("A3","C2",rule="symmetric"); b
symmetric branching rule A3 => C2
>>> from sage.all import *
>>> b = branching_rule("A3","C2",rule="symmetric"); b
symmetric branching rule A3 => C2
b = branching_rule("A3","C2",rule="symmetric"); b

The name “symmetric” of this branching rule will be explained further later, but it means that \(Sp(4)\) is the fixed subgroup of an involution of \(Sl(4)\). Here A3 and C2 are the Cartan types of the groups \(G=SL(4)\) and \(H=Sp(4)\).

Now we may see how representations of \(SL(4)\) decompose into irreducibles when they are restricted to \(Sp(4)\):

sage: A3 = WeylCharacterRing("A3",style="coroots")
sage: chi = A3(1,0,1); chi.degree()
15
sage: C2 = WeylCharacterRing("C2",style="coroots")
sage: chi.branch(C2,rule=b)
C2(0,1) + C2(2,0)
>>> from sage.all import *
>>> A3 = WeylCharacterRing("A3",style="coroots")
>>> chi = A3(Integer(1),Integer(0),Integer(1)); chi.degree()
15
>>> C2 = WeylCharacterRing("C2",style="coroots")
>>> chi.branch(C2,rule=b)
C2(0,1) + C2(2,0)
A3 = WeylCharacterRing("A3",style="coroots")
chi = A3(1,0,1); chi.degree()
C2 = WeylCharacterRing("C2",style="coroots")
chi.branch(C2,rule=b)

Alternatively, we may pass chi to b as an argument of its branch method, which gives the same result:

sage: b.branch(chi)
C2(0,1) + C2(2,0)
>>> from sage.all import *
>>> b.branch(chi)
C2(0,1) + C2(2,0)
b.branch(chi)

It is believed that the built-in branching rules of Sage are sufficient to handle all maximal subgroups and this is certainly the case when the rank if less than or equal to 8.

However, if you want to branch to a subgroup that is not maximal you may not find a built-in branching rule. We may compose branching rules to build up embeddings. For example, here are two different embeddings of \(Sp(4)\) with Cartan type C2 in \(Sp(8)\), with Cartan type C4. One embedding factors through \(Sp(4)\times Sp(4)\), while the other factors through \(SL(4)\). To check that the embeddings are not conjugate, we branch a (randomly chosen) representation. Observe that we do not have to build the intermediate Weyl character rings.

sage: C4 = WeylCharacterRing("C4",style="coroots")
sage: b1 = branching_rule("C4","A3","levi")*branching_rule("A3","C2","symmetric"); b1
composite branching rule C4 => (levi) A3 => (symmetric) C2
sage: b2 = branching_rule("C4","C2xC2","orthogonal_sum")*branching_rule("C2xC2","C2","proj1"); b2
composite branching rule C4 => (orthogonal_sum) C2xC2 => (proj1) C2
sage: C2 = WeylCharacterRing("C2",style="coroots")
sage: C4 = WeylCharacterRing("C4",style="coroots")
sage: [C4(2,0,0,1).branch(C2, rule=br) for br in [b1,b2]]
[4*C2(0,0) + 7*C2(0,1) + 15*C2(2,0) + 7*C2(0,2) + 11*C2(2,1) + C2(0,3) + 6*C2(4,0) + 3*C2(2,2),
 10*C2(0,0) + 40*C2(1,0) + 50*C2(0,1) + 16*C2(2,0) + 20*C2(1,1) + 4*C2(3,0) + 5*C2(2,1)]
>>> from sage.all import *
>>> C4 = WeylCharacterRing("C4",style="coroots")
>>> b1 = branching_rule("C4","A3","levi")*branching_rule("A3","C2","symmetric"); b1
composite branching rule C4 => (levi) A3 => (symmetric) C2
>>> b2 = branching_rule("C4","C2xC2","orthogonal_sum")*branching_rule("C2xC2","C2","proj1"); b2
composite branching rule C4 => (orthogonal_sum) C2xC2 => (proj1) C2
>>> C2 = WeylCharacterRing("C2",style="coroots")
>>> C4 = WeylCharacterRing("C4",style="coroots")
>>> [C4(Integer(2),Integer(0),Integer(0),Integer(1)).branch(C2, rule=br) for br in [b1,b2]]
[4*C2(0,0) + 7*C2(0,1) + 15*C2(2,0) + 7*C2(0,2) + 11*C2(2,1) + C2(0,3) + 6*C2(4,0) + 3*C2(2,2),
 10*C2(0,0) + 40*C2(1,0) + 50*C2(0,1) + 16*C2(2,0) + 20*C2(1,1) + 4*C2(3,0) + 5*C2(2,1)]
C4 = WeylCharacterRing("C4",style="coroots")
b1 = branching_rule("C4","A3","levi")*branching_rule("A3","C2","symmetric"); b1
b2 = branching_rule("C4","C2xC2","orthogonal_sum")*branching_rule("C2xC2","C2","proj1"); b2
C2 = WeylCharacterRing("C2",style="coroots")
C4 = WeylCharacterRing("C4",style="coroots")
[C4(2,0,0,1).branch(C2, rule=br) for br in [b1,b2]]

What’s in a branching rule?

The essence of the branching rule is a function from the weight lattice of \(G\) to the weight lattice of the subgroup \(H\), usually implemented as a function on the ambient vector spaces. Indeed, we may conjugate the embedding so that a Cartan subalgebra \(U\) of \(H\) is contained in a Cartan subalgebra \(T\) of \(G\). Since the ambient vector space of the weight lattice of \(G\) is \(\hbox{Lie}(T)^*\), we get map \(\hbox{Lie}(T)^*\to\hbox{Lie}(U)^*\), and this must be implemented as a function. For speed, the function usually just returns a list, which can be coerced into \(\hbox{Lie}(U)^*\).

sage: b = branching_rule("A3","C2","symmetric")
sage: for r in RootSystem("A3").ambient_space().simple_roots():
....:     print("{} {}".format(r, b(r)))
(1, -1, 0, 0) [1, -1]
(0, 1, -1, 0) [0, 2]
(0, 0, 1, -1) [1, -1]
>>> from sage.all import *
>>> b = branching_rule("A3","C2","symmetric")
>>> for r in RootSystem("A3").ambient_space().simple_roots():
...     print("{} {}".format(r, b(r)))
(1, -1, 0, 0) [1, -1]
(0, 1, -1, 0) [0, 2]
(0, 0, 1, -1) [1, -1]
b = branching_rule("A3","C2","symmetric")
for r in RootSystem("A3").ambient_space().simple_roots():
    print("{} {}".format(r, b(r)))

We could conjugate this map by an element of the Weyl group of \(G\), and the resulting map would give the same decomposition of representations of \(G\) into irreducibles of \(H\). However it is a good idea to choose the map so that it takes dominant weights to dominant weights, and, insofar as possible, simple roots of \(G\) into simple roots of \(H\). This includes sometimes the affine root \(\alpha_0\) of \(G\), which we recall is the negative of the highest root.

The branching rule has a describe() method that shows how the roots (including the affine root) restrict. This is a useful way of understanding the embedding. You might want to try it with various branching rules of different kinds, "extended", "symmetric", "levi" etc.

sage: b.describe()

0
O-------+
|       |
|       |
O---O---O
1   2   3
A3~

root restrictions A3 => C2:

O=<=O
1   2
C2

1 => 1
2 => 2
3 => 1

For more detailed information use verbose=True
>>> from sage.all import *
>>> b.describe()
<BLANKLINE>
0
O-------+
|       |
|       |
O---O---O
1   2   3
A3~
<BLANKLINE>
root restrictions A3 => C2:
<BLANKLINE>
O=<=O
1   2
C2
<BLANKLINE>
1 => 1
2 => 2
3 => 1
<BLANKLINE>
For more detailed information use verbose=True
b.describe()

The extended Dynkin diagram of \(G\) and the ordinary Dynkin diagram of \(H\) are shown for reference, and 3 => 1 means that the third simple root \(\alpha_3\) of \(G\) restricts to the first simple root of \(H\). In this example, the affine root does not restrict to a simple roots, so it is omitted from the list of restrictions. If you add the parameter verbose=true you will be shown the restriction of all simple roots and the affine root, and also the restrictions of the fundamental weights (in coroot notation).

Maximal subgroups

Sage has a database of maximal subgroups for every simple Cartan type of rank \(\le 8\). You may access this with the maximal_subgroups method of the Weyl character ring:

sage: E7 = WeylCharacterRing("E7",style="coroots")
sage: E7.maximal_subgroups()
A7:branching_rule("E7","A7","extended")
E6:branching_rule("E7","E6","levi")
A2:branching_rule("E7","A2","miscellaneous")
A1:branching_rule("E7","A1","iii")
A1:branching_rule("E7","A1","iv")
A1xF4:branching_rule("E7","A1xF4","miscellaneous")
G2xC3:branching_rule("E7","G2xC3","miscellaneous")
A1xG2:branching_rule("E7","A1xG2","miscellaneous")
A1xA1:branching_rule("E7","A1xA1","miscellaneous")
A1xD6:branching_rule("E7","A1xD6","extended")
A5xA2:branching_rule("E7","A5xA2","extended")
>>> from sage.all import *
>>> E7 = WeylCharacterRing("E7",style="coroots")
>>> E7.maximal_subgroups()
A7:branching_rule("E7","A7","extended")
E6:branching_rule("E7","E6","levi")
A2:branching_rule("E7","A2","miscellaneous")
A1:branching_rule("E7","A1","iii")
A1:branching_rule("E7","A1","iv")
A1xF4:branching_rule("E7","A1xF4","miscellaneous")
G2xC3:branching_rule("E7","G2xC3","miscellaneous")
A1xG2:branching_rule("E7","A1xG2","miscellaneous")
A1xA1:branching_rule("E7","A1xA1","miscellaneous")
A1xD6:branching_rule("E7","A1xD6","extended")
A5xA2:branching_rule("E7","A5xA2","extended")
E7 = WeylCharacterRing("E7",style="coroots")
E7.maximal_subgroups()

It should be understood that there are other ways of embedding \(A_2=\hbox{SL}(3)\) into the Lie group \(E_7\), but only one way as a maximal subgroup. On the other hand, there are but only one way to embed it as a maximal subgroup. The embedding will be explained below. You may obtain the branching rule as follows, and use it to determine the decomposition of irreducible representations of \(E_7\) as follows:

sage: b = E7.maximal_subgroup("A2"); b
miscellaneous branching rule E7 => A2
sage: E7, A2 = [WeylCharacterRing(x,style="coroots") for x in ["E7","A2"]]
sage: E7(1,0,0,0,0,0,0).branch(A2,rule=b)
A2(1,1) + A2(4,4)
>>> from sage.all import *
>>> b = E7.maximal_subgroup("A2"); b
miscellaneous branching rule E7 => A2
>>> E7, A2 = [WeylCharacterRing(x,style="coroots") for x in ["E7","A2"]]
>>> E7(Integer(1),Integer(0),Integer(0),Integer(0),Integer(0),Integer(0),Integer(0)).branch(A2,rule=b)
A2(1,1) + A2(4,4)
b = E7.maximal_subgroup("A2"); b
E7, A2 = [WeylCharacterRing(x,style="coroots") for x in ["E7","A2"]]
E7(1,0,0,0,0,0,0).branch(A2,rule=b)

This gives the same branching rule as just pasting line beginning to the right of the colon onto the command line:

sage:branching_rule("E7","A2","miscellaneous")
miscellaneous branching rule E7 => A2
>>> from sage.all import *
sage:branching_rule("E7","A2","miscellaneous")
miscellaneous branching rule E7 => A2

There are two distinct embeddings of \(A_1=\hbox{SL}(2)\) into \(E_7\) as maximal subgroups, so the maximal_subgroup method will return a list of rules:

sage: WeylCharacterRing("E7").maximal_subgroup("A1")
[iii branching rule E7 => A1, iv branching rule E7 => A1]
>>> from sage.all import *
>>> WeylCharacterRing("E7").maximal_subgroup("A1")
[iii branching rule E7 => A1, iv branching rule E7 => A1]
WeylCharacterRing("E7").maximal_subgroup("A1")

The list of maximal subgroups returned by the maximal_subgroups method for irreducible Cartan types of rank up to 8 is believed to be complete up to outer automorphisms. You may want a list that is complete up to inner automorphisms. For example, \(E_6\) has a nontrivial Dynkin diagram automorphism so it has an outer automorphism that is not inner:

sage: E6, A2xG2 = [WeylCharacterRing(x,style="coroots") for x in ["E6","A2xG2"]]
sage: b = E6.maximal_subgroup("A2xG2"); b
miscellaneous branching rule E6 => A2xG2
sage: E6(1,0,0,0,0,0).branch(A2xG2,rule=b)
A2xG2(0,1,1,0) + A2xG2(2,0,0,0)
sage: E6(0,0,0,0,0,1).branch(A2xG2,rule=b)
A2xG2(1,0,1,0) + A2xG2(0,2,0,0)
>>> from sage.all import *
>>> E6, A2xG2 = [WeylCharacterRing(x,style="coroots") for x in ["E6","A2xG2"]]
>>> b = E6.maximal_subgroup("A2xG2"); b
miscellaneous branching rule E6 => A2xG2
>>> E6(Integer(1),Integer(0),Integer(0),Integer(0),Integer(0),Integer(0)).branch(A2xG2,rule=b)
A2xG2(0,1,1,0) + A2xG2(2,0,0,0)
>>> E6(Integer(0),Integer(0),Integer(0),Integer(0),Integer(0),Integer(1)).branch(A2xG2,rule=b)
A2xG2(1,0,1,0) + A2xG2(0,2,0,0)
E6, A2xG2 = [WeylCharacterRing(x,style="coroots") for x in ["E6","A2xG2"]]
b = E6.maximal_subgroup("A2xG2"); b
E6(1,0,0,0,0,0).branch(A2xG2,rule=b)
E6(0,0,0,0,0,1).branch(A2xG2,rule=b)

Since as we see the two 27 dimensional irreducibles (which are interchanged by the outer automorphism) have different branching, the \(A_2\times G_2\) subgroup is changed to a different one by the outer automorphism. To obtain the second branching rule, we compose the given one with this automorphism:

sage: b1 = branching_rule("E6","E6","automorphic")*b; b1
composite branching rule E6 => (automorphic) E6 => (miscellaneous) A2xG2
>>> from sage.all import *
>>> b1 = branching_rule("E6","E6","automorphic")*b; b1
composite branching rule E6 => (automorphic) E6 => (miscellaneous) A2xG2
b1 = branching_rule("E6","E6","automorphic")*b; b1

Levi subgroups

A Levi subgroup may or may not be maximal. They are easily classified. If one starts with a Dynkin diagram for \(G\) and removes a single node, one obtains a smaller Dynkin diagram, which is the Dynkin diagram of a smaller subgroup \(H\).

For example, here is the A3 Dynkin diagram:

sage: A3 = WeylCharacterRing("A3")
sage: A3.dynkin_diagram()
O---O---O
1   2   3
A3
>>> from sage.all import *
>>> A3 = WeylCharacterRing("A3")
>>> A3.dynkin_diagram()
O---O---O
1   2   3
A3
A3 = WeylCharacterRing("A3")
A3.dynkin_diagram()

We see that we may remove the node 3 and obtain \(A_2\), or the node 2 and obtain \(A_1 \times A_1\). These correspond to the Levi subgroups \(GL(3)\) and \(GL(2) \times GL(2)\) of \(GL(4)\).

Let us construct the irreducible representations of \(GL(4)\) and branch them down to these down to \(GL(3)\) and \(GL(2) \times GL(2)\):

sage: reps = [A3(v) for v in A3.fundamental_weights()]; reps
[A3(1,0,0,0), A3(1,1,0,0), A3(1,1,1,0)]
sage: A2 = WeylCharacterRing("A2")
sage: A1xA1 = WeylCharacterRing("A1xA1")
sage: [pi.branch(A2, rule="levi") for pi in reps]
[A2(0,0,0) + A2(1,0,0), A2(1,0,0) + A2(1,1,0), A2(1,1,0) + A2(1,1,1)]
sage: [pi.branch(A1xA1, rule="levi") for pi in reps]
[A1xA1(1,0,0,0) + A1xA1(0,0,1,0),
 A1xA1(1,1,0,0) + A1xA1(1,0,1,0) + A1xA1(0,0,1,1),
 A1xA1(1,1,1,0) + A1xA1(1,0,1,1)]
>>> from sage.all import *
>>> reps = [A3(v) for v in A3.fundamental_weights()]; reps
[A3(1,0,0,0), A3(1,1,0,0), A3(1,1,1,0)]
>>> A2 = WeylCharacterRing("A2")
>>> A1xA1 = WeylCharacterRing("A1xA1")
>>> [pi.branch(A2, rule="levi") for pi in reps]
[A2(0,0,0) + A2(1,0,0), A2(1,0,0) + A2(1,1,0), A2(1,1,0) + A2(1,1,1)]
>>> [pi.branch(A1xA1, rule="levi") for pi in reps]
[A1xA1(1,0,0,0) + A1xA1(0,0,1,0),
 A1xA1(1,1,0,0) + A1xA1(1,0,1,0) + A1xA1(0,0,1,1),
 A1xA1(1,1,1,0) + A1xA1(1,0,1,1)]
reps = [A3(v) for v in A3.fundamental_weights()]; reps
A2 = WeylCharacterRing("A2")
A1xA1 = WeylCharacterRing("A1xA1")
[pi.branch(A2, rule="levi") for pi in reps]
[pi.branch(A1xA1, rule="levi") for pi in reps]

Let us redo this calculation in coroot notation. As we have explained, coroot notation does not distinguish between representations of \(GL(4)\) that have the same restriction to \(SL(4)\), so in effect we are now working with the groups \(SL(4)\) and its Levi subgroups \(SL(3)\) and \(SL(2) \times SL(2)\), which is the derived group of its Levi subgroup:

sage: A3 = WeylCharacterRing("A3", style="coroots")
sage: reps = [A3(v) for v in A3.fundamental_weights()]; reps
[A3(1,0,0), A3(0,1,0), A3(0,0,1)]
sage: A2 = WeylCharacterRing("A2", style="coroots")
sage: A1xA1 = WeylCharacterRing("A1xA1", style="coroots")
sage: [pi.branch(A2, rule="levi") for pi in reps]
[A2(0,0) + A2(1,0), A2(0,1) + A2(1,0), A2(0,0) + A2(0,1)]
sage: [pi.branch(A1xA1, rule="levi") for pi in reps]
[A1xA1(1,0) + A1xA1(0,1), 2*A1xA1(0,0) + A1xA1(1,1), A1xA1(1,0) + A1xA1(0,1)]
>>> from sage.all import *
>>> A3 = WeylCharacterRing("A3", style="coroots")
>>> reps = [A3(v) for v in A3.fundamental_weights()]; reps
[A3(1,0,0), A3(0,1,0), A3(0,0,1)]
>>> A2 = WeylCharacterRing("A2", style="coroots")
>>> A1xA1 = WeylCharacterRing("A1xA1", style="coroots")
>>> [pi.branch(A2, rule="levi") for pi in reps]
[A2(0,0) + A2(1,0), A2(0,1) + A2(1,0), A2(0,0) + A2(0,1)]
>>> [pi.branch(A1xA1, rule="levi") for pi in reps]
[A1xA1(1,0) + A1xA1(0,1), 2*A1xA1(0,0) + A1xA1(1,1), A1xA1(1,0) + A1xA1(0,1)]
A3 = WeylCharacterRing("A3", style="coroots")
reps = [A3(v) for v in A3.fundamental_weights()]; reps
A2 = WeylCharacterRing("A2", style="coroots")
A1xA1 = WeylCharacterRing("A1xA1", style="coroots")
[pi.branch(A2, rule="levi") for pi in reps]
[pi.branch(A1xA1, rule="levi") for pi in reps]

Now we may observe a distinction difference in branching from

\[GL(4) \to GL(2) \times GL(2)\]

versus

\[SL(4) \to SL(2) \times SL(2).\]

Consider the representation A3(0,1,0), which is the six dimensional exterior square. In the coroot notation, the restriction contained two copies of the trivial representation, 2*A1xA1(0,0). The other way, we had instead three distinct representations in the restriction, namely A1xA1(1,1,0,0) and A1xA1(0,0,1,1), that is, \(\det \otimes 1\) and \(1 \otimes \det\).

The Levi subgroup A1xA1 is actually not maximal. Indeed, we may factor the embedding:

\[SL(2) \times SL(2) \to Sp(4) \to SL(4).\]

Therefore there are branching rules A3 -> C2 and C2 -> A2, and we could accomplish the branching in two steps, thus:

sage: A3 = WeylCharacterRing("A3", style="coroots")
sage: C2 = WeylCharacterRing("C2", style="coroots")
sage: B2 = WeylCharacterRing("B2", style="coroots")
sage: D2 = WeylCharacterRing("D2", style="coroots")
sage: A1xA1 = WeylCharacterRing("A1xA1", style="coroots")
sage: reps = [A3(fw) for fw in A3.fundamental_weights()]
sage: [pi.branch(C2, rule="symmetric").branch(B2, rule="isomorphic"). \
         branch(D2, rule="extended").branch(A1xA1, rule="isomorphic") for pi in reps]
[A1xA1(1,0) + A1xA1(0,1), 2*A1xA1(0,0) + A1xA1(1,1), A1xA1(1,0) + A1xA1(0,1)]
>>> from sage.all import *
>>> A3 = WeylCharacterRing("A3", style="coroots")
>>> C2 = WeylCharacterRing("C2", style="coroots")
>>> B2 = WeylCharacterRing("B2", style="coroots")
>>> D2 = WeylCharacterRing("D2", style="coroots")
>>> A1xA1 = WeylCharacterRing("A1xA1", style="coroots")
>>> reps = [A3(fw) for fw in A3.fundamental_weights()]
>>> [pi.branch(C2, rule="symmetric").branch(B2, rule="isomorphic"). \
         branch(D2, rule="extended").branch(A1xA1, rule="isomorphic") for pi in reps]
[A1xA1(1,0) + A1xA1(0,1), 2*A1xA1(0,0) + A1xA1(1,1), A1xA1(1,0) + A1xA1(0,1)]
A3 = WeylCharacterRing("A3", style="coroots")
C2 = WeylCharacterRing("C2", style="coroots")
B2 = WeylCharacterRing("B2", style="coroots")
D2 = WeylCharacterRing("D2", style="coroots")
A1xA1 = WeylCharacterRing("A1xA1", style="coroots")
reps = [A3(fw) for fw in A3.fundamental_weights()]
[pi.branch(C2, rule="symmetric").branch(B2, rule="isomorphic"). \

As you can see, we’ve redone the branching rather circuitously this way, making use of the branching rules A3 -> C2 and B2 -> D2, and two accidental isomorphisms C2 = B2 and D2 = A1xA1. It is much easier to go in one step using rule="levi", but reassuring that we get the same answer!

Subgroups classified by the extended Dynkin diagram

It is also true that if we remove one node from the extended Dynkin diagram that we obtain the Dynkin diagram of a subgroup. For example:

sage: G2 = WeylCharacterRing("G2", style="coroots")
sage: G2.extended_dynkin_diagram()
  3
O=<=O---O
1   2   0
G2~
>>> from sage.all import *
>>> G2 = WeylCharacterRing("G2", style="coroots")
>>> G2.extended_dynkin_diagram()
  3
O=<=O---O
1   2   0
G2~
G2 = WeylCharacterRing("G2", style="coroots")
G2.extended_dynkin_diagram()

Observe that by removing the 1 node that we obtain an \(A_2\) Dynkin diagram. Therefore the exceptional group \(G_2\) contains a copy of \(SL(3)\). We branch the two representations of \(G_2\) corresponding to the fundamental weights to this copy of \(A_2\):

sage: G2 = WeylCharacterRing("G2", style="coroots")
sage: A2 = WeylCharacterRing("A2", style="coroots")
sage: [G2(f).degree() for f in G2.fundamental_weights()]
[7, 14]
sage: [G2(f).branch(A2, rule="extended") for f in G2.fundamental_weights()]
[A2(0,0) + A2(0,1) + A2(1,0), A2(0,1) + A2(1,0) + A2(1,1)]
>>> from sage.all import *
>>> G2 = WeylCharacterRing("G2", style="coroots")
>>> A2 = WeylCharacterRing("A2", style="coroots")
>>> [G2(f).degree() for f in G2.fundamental_weights()]
[7, 14]
>>> [G2(f).branch(A2, rule="extended") for f in G2.fundamental_weights()]
[A2(0,0) + A2(0,1) + A2(1,0), A2(0,1) + A2(1,0) + A2(1,1)]
G2 = WeylCharacterRing("G2", style="coroots")
A2 = WeylCharacterRing("A2", style="coroots")
[G2(f).degree() for f in G2.fundamental_weights()]
[G2(f).branch(A2, rule="extended") for f in G2.fundamental_weights()]

The two representations of \(G_2\), of degrees 7 and 14 respectively, are the action on the octonions of trace zero and the adjoint representation.

For embeddings of this type, the rank of the subgroup \(H\) is the same as the rank of \(G\). This is in contrast with embeddings of Levi type, where \(H\) has rank one less than \(G\).

Levi subgroups of \(G_2\)

The exceptional group \(G_2\) has two Levi subgroups of type \(A_1\). Neither is maximal, as we can see from the extended Dynkin diagram: the subgroups \(A_1\times A_1\) and \(A_2\) are maximal and each contains a Levi subgroup. (Actually \(A_1\times A_1\) contains a conjugate of both.) Only the Levi subgroup containing the short root is implemented as an instance of rule="levi". To obtain the other, use the rule:

sage: branching_rule("G2","A2","extended")*branching_rule("A2","A1","levi")
composite branching rule G2 => (extended) A2 => (levi) A1
>>> from sage.all import *
>>> branching_rule("G2","A2","extended")*branching_rule("A2","A1","levi")
composite branching rule G2 => (extended) A2 => (levi) A1
branching_rule("G2","A2","extended")*branching_rule("A2","A1","levi")

which branches to the \(A_1\) Levi subgroup containing a long root.

Orthogonal and symplectic subgroups of orthogonal and symplectic groups

If \(G = \hbox{SO}(n)\) then \(G\) has a subgroup \(\hbox{SO}(n-1)\). Depending on whether \(n\) is even or odd, we thus have branching rules ['D',r] to ['B',r-1] or ['B',r] to ['D',r]. These are handled as follows:

sage: branching_rule("B4","D4",rule="extended")
extended branching rule B4 => D4
sage: branching_rule("D4","B3",rule="symmetric")
symmetric branching rule D4 => B3
>>> from sage.all import *
>>> branching_rule("B4","D4",rule="extended")
extended branching rule B4 => D4
>>> branching_rule("D4","B3",rule="symmetric")
symmetric branching rule D4 => B3
branching_rule("B4","D4",rule="extended")
branching_rule("D4","B3",rule="symmetric")

If \(G = \hbox{SO}(r+s)\) then \(G\) has a subgroup \(\hbox{SO}(r) \times \hbox{SO}(s)\). This lifts to an embedding of the universal covering groups

\[\hbox{spin}(r) \times \hbox{spin}(s) \to \hbox{spin}(r+s).\]

Sometimes this embedding is of extended type, and sometimes it is not. It is of extended type unless \(r\) and \(s\) are both odd. If it is of extended type then you may use rule="extended". In any case you may use rule="orthogonal_sum". The name refer to the origin of the embedding \(SO(r) \times SO(s) \to SO(r+s)\) from the decomposition of the underlying quadratic space as a direct sum of two orthogonal subspaces.

There are four cases depending on the parity of \(r\) and \(s\). For example, if \(r = 2k\) and \(s = 2l\) we have an embedding:

['D',k] x ['D',l] --> ['D',k+l]

This is of extended type. Thus consider the embedding D4xD3 -> D7. Here is the extended Dynkin diagram:

  0 O           O 7
    |           |
    |           |
O---O---O---O---O---O
1   2   3   4   5   6

Removing the 4 vertex results in a disconnected Dynkin diagram:

  0 O           O 7
    |           |
    |           |
O---O---O       O---O
1   2   3       5   6

This is D4xD3. Therefore use the “extended” branching rule:

sage: D7 = WeylCharacterRing("D7", style="coroots")
sage: D4xD3 = WeylCharacterRing("D4xD3", style="coroots")
sage: spin = D7(D7.fundamental_weights()[7]); spin
D7(0,0,0,0,0,0,1)
sage: spin.branch(D4xD3, rule="extended")
D4xD3(0,0,1,0,0,1,0) + D4xD3(0,0,0,1,0,0,1)
>>> from sage.all import *
>>> D7 = WeylCharacterRing("D7", style="coroots")
>>> D4xD3 = WeylCharacterRing("D4xD3", style="coroots")
>>> spin = D7(D7.fundamental_weights()[Integer(7)]); spin
D7(0,0,0,0,0,0,1)
>>> spin.branch(D4xD3, rule="extended")
D4xD3(0,0,1,0,0,1,0) + D4xD3(0,0,0,1,0,0,1)
D7 = WeylCharacterRing("D7", style="coroots")
D4xD3 = WeylCharacterRing("D4xD3", style="coroots")
spin = D7(D7.fundamental_weights()[7]); spin
spin.branch(D4xD3, rule="extended")

But we could equally well use the “orthogonal_sum” rule:

sage: spin.branch(D4xD3, rule="orthogonal_sum")
D4xD3(0,0,1,0,0,1,0) + D4xD3(0,0,0,1,0,0,1)
>>> from sage.all import *
>>> spin.branch(D4xD3, rule="orthogonal_sum")
D4xD3(0,0,1,0,0,1,0) + D4xD3(0,0,0,1,0,0,1)
spin.branch(D4xD3, rule="orthogonal_sum")

Similarly we have embeddings:

['D',k] x ['B',l] --> ['B',k+l]

These are also of extended type. For example consider the embedding of D3xB2 -> B5. Here is the B5 extended Dynkin diagram:

    O 0
    |
    |
O---O---O---O=>=O
1   2   3   4   5

Removing the 3 node gives:

    O 0
    |
O---O       O=>=O
1   2       4   5

and this is the Dynkin diagram or D3xB2. For such branchings we again use either rule="extended" or rule="orthogonal_sum".

Finally, there is an embedding

['B',k] x ['B',l] --> ['D',k+l+1]

This is not of extended type, so you may not use rule="extended". You must use rule="orthogonal_sum":

sage: D5 = WeylCharacterRing("D5",style="coroots")
sage: B2xB2 = WeylCharacterRing("B2xB2",style="coroots")
sage: [D5(v).branch(B2xB2,rule="orthogonal_sum") for v in D5.fundamental_weights()]
[B2xB2(1,0,0,0) + B2xB2(0,0,1,0),
 B2xB2(0,2,0,0) + B2xB2(1,0,1,0) + B2xB2(0,0,0,2),
 B2xB2(0,2,0,0) + B2xB2(0,2,1,0) + B2xB2(1,0,0,2) + B2xB2(0,0,0,2),
 B2xB2(0,1,0,1), B2xB2(0,1,0,1)]
>>> from sage.all import *
>>> D5 = WeylCharacterRing("D5",style="coroots")
>>> B2xB2 = WeylCharacterRing("B2xB2",style="coroots")
>>> [D5(v).branch(B2xB2,rule="orthogonal_sum") for v in D5.fundamental_weights()]
[B2xB2(1,0,0,0) + B2xB2(0,0,1,0),
 B2xB2(0,2,0,0) + B2xB2(1,0,1,0) + B2xB2(0,0,0,2),
 B2xB2(0,2,0,0) + B2xB2(0,2,1,0) + B2xB2(1,0,0,2) + B2xB2(0,0,0,2),
 B2xB2(0,1,0,1), B2xB2(0,1,0,1)]
D5 = WeylCharacterRing("D5",style="coroots")
B2xB2 = WeylCharacterRing("B2xB2",style="coroots")
[D5(v).branch(B2xB2,rule="orthogonal_sum") for v in D5.fundamental_weights()]

Non-maximal Levi subgroups and Projection from Reducible Types

Not all Levi subgroups are maximal. Recall that the Dynkin-diagram of a Levi subgroup \(H\) of \(G\) is obtained by removing a node from the Dynkin diagram of \(G\). Removing the same node from the extended Dynkin diagram of \(G\) results in the Dynkin diagram of a subgroup of \(G\) that is strictly larger than \(H\). However this subgroup may or may not be proper, so the Levi subgroup may or may not be maximal.

If the Levi subgroup is not maximal, the branching rule may or may not be implemented in Sage. However if it is not implemented, it may be constructed as a composition of two branching rules.

For example, prior to Sage-6.1 branching_rule("E6","A5","levi") returned a not-implemented error and the advice to branch to A5xA1. And we can see from the extended Dynkin diagram of \(E_6\) that indeed \(A_5\) is not a maximal subgroup, since removing node 2 from the extended Dynkin diagram (see below) gives A5xA1. To construct the branching rule to \(A_5\) we may proceed as follows:

sage: b = branching_rule("E6","A5xA1","extended")*branching_rule("A5xA1","A5","proj1"); b
composite branching rule E6 => (extended) A5xA1 => (proj1) A5
sage: E6 = WeylCharacterRing("E6",style="coroots")
sage: A5 = WeylCharacterRing("A5",style="coroots")
sage: E6(0,1,0,0,0,0).branch(A5,rule=b)
3*A5(0,0,0,0,0) + 2*A5(0,0,1,0,0) + A5(1,0,0,0,1)
sage: b.describe()

        O 0
        |
        |
        O 2
        |
        |
O---O---O---O---O
1   3   4   5   6
E6~
root restrictions E6 => A5:

O---O---O---O---O
1   2   3   4   5
A5

0 => (zero)
1 => 1
3 => 2
4 => 3
5 => 4
6 => 5

For more detailed information use verbose=True
>>> from sage.all import *
>>> b = branching_rule("E6","A5xA1","extended")*branching_rule("A5xA1","A5","proj1"); b
composite branching rule E6 => (extended) A5xA1 => (proj1) A5
>>> E6 = WeylCharacterRing("E6",style="coroots")
>>> A5 = WeylCharacterRing("A5",style="coroots")
>>> E6(Integer(0),Integer(1),Integer(0),Integer(0),Integer(0),Integer(0)).branch(A5,rule=b)
3*A5(0,0,0,0,0) + 2*A5(0,0,1,0,0) + A5(1,0,0,0,1)
>>> b.describe()
<BLANKLINE>
        O 0
        |
        |
        O 2
        |
        |
O---O---O---O---O
1   3   4   5   6
E6~
root restrictions E6 => A5:
<BLANKLINE>
O---O---O---O---O
1   2   3   4   5
A5
<BLANKLINE>
0 => (zero)
1 => 1
3 => 2
4 => 3
5 => 4
6 => 5
<BLANKLINE>
For more detailed information use verbose=True
b = branching_rule("E6","A5xA1","extended")*branching_rule("A5xA1","A5","proj1"); b
E6 = WeylCharacterRing("E6",style="coroots")
A5 = WeylCharacterRing("A5",style="coroots")
E6(0,1,0,0,0,0).branch(A5,rule=b)
b.describe()

Note that it is not necessary to construct the Weyl character ring for the intermediate group A5xA1.

This last example illustrates another common problem: how to extract one component from a reducible root system. We used the rule "proj1" to extract the first component. We could similarly use "proj2" to get the second, or more generally any combination of components:

sage: branching_rule("A2xB2xG2","A2xG2","proj13")
proj13 branching rule A2xB2xG2 => A2xG2
>>> from sage.all import *
>>> branching_rule("A2xB2xG2","A2xG2","proj13")
proj13 branching rule A2xB2xG2 => A2xG2
branching_rule("A2xB2xG2","A2xG2","proj13")

Symmetric subgroups

If \(G\) admits an outer automorphism (usually of order two) then we may try to find the branching rule to the fixed subgroup \(H\). It can be arranged that this automorphism maps the maximal torus \(T\) to itself and that a maximal torus \(U\) of \(H\) is contained in \(T\).

Suppose that the Dynkin diagram of \(G\) admits an automorphism. Then \(G\) itself admits an outer automorphism. The Dynkin diagram of the group \(H\) of invariants may be obtained by “folding” the Dynkin diagram of \(G\) along the automorphism. The exception is the branching rule \(GL(2r) \to SO(2r)\).

Here are the branching rules that can be obtained using rule="symmetric".

\(G\)

\(H\)

Cartan Types

\(GL(2r)\)

\(Sp(2r)\)

['A',2r-1] => ['C',r]

\(GL(2r+1)\)

\(SO(2r+1)\)

['A',2r] => ['B',r]

\(GL(2r)\)

\(SO(2r)\)

['A',2r-1] => ['D',r]

\(SO(2r)\)

\(SO(2r-1)\)

['D',r] => ['B',r-1]

\(E_6\)

\(F_4\)

['E',6] => ['F',4]

Tensor products

If \(G_1\) and \(G_2\) are Lie groups, and we have representations \(\pi_1: G_1 \to GL(n)\) and \(\pi_2: G_2 \to GL(m)\) then the tensor product is a representation of \(G_1 \times G_2\). It has its image in \(GL(nm)\) but sometimes this is conjugate to a subgroup of \(SO(nm)\) or \(Sp(nm)\). In particular we have the following cases.

Group

Subgroup

Cartan Types

\(GL(rs)\)

\(GL(r)\times GL(s)\)

['A', rs-1] => ['A',r-1] x ['A',s-1]

\(SO(4rs+2r+2s+1)\)

\(SO(2r+1)\times SO(2s+1)\)

['B',2rs+r+s] => ['B',r] x ['B',s]

\(SO(4rs+2s)\)

\(SO(2r+1)\times SO(2s)\)

['D',2rs+s] => ['B',r] x ['D',s]

\(SO(4rs)\)

\(SO(2r)\times SO(2s)\)

['D',2rs] => ['D',r] x ['D',s]

\(SO(4rs)\)

\(Sp(2r)\times Sp(2s)\)

['D',2rs] => ['C',r] x ['C',s]

\(Sp(4rs+2s)\)

\(SO(2r+1)\times Sp(2s)\)

['C',2rs+s] => ['B',r] x ['C',s]

\(Sp(4rs)\)

\(Sp(2r)\times SO(2s)\)

['C',2rs] => ['C',r] x ['D',s]

These branching rules are obtained using rule="tensor".

Symmetric powers

The \(k\)-th symmetric and exterior power homomorphisms map \(GL(n) \to GL \left(\binom{n+k-1}{k} \right)\) and \(GL \left(\binom{n}{k} \right)\). The corresponding branching rules are not implemented but a special case is. The \(k\)-th symmetric power homomorphism \(SL(2) \to GL(k+1)\) has its image inside of \(SO(2r+1)\) if \(k = 2r\) and inside of \(Sp(2r)\) if \(k = 2r-1\). Hence there are branching rules:

['B',r] => A1
['C',r] => A1

and these may be obtained using rule="symmetric_power".

Plethysms

The above branching rules are sufficient for most cases, but a few fall between the cracks. Mostly these involve maximal subgroups of fairly small rank.

The rule rule="plethysm" is a powerful rule that includes any branching rule from types \(A\), \(B\), \(C\) or \(D\) as a special case. Thus it could be used in place of the above rules and would give the same results. However, it is most useful when branching from \(G\) to a maximal subgroup \(H\) such that \(rank(H) < rank(G)-1\).

We consider a homomorphism \(H \to G\) where \(G\) is one of \(SL(r+1)\), \(SO(2r+1)\), \(Sp(2r)\) or \(SO(2r)\). The function branching_rule_from_plethysm produces the corresponding branching rule. The main ingredient is the character \(\chi\) of the representation of \(H\) that is the homomorphism to \(GL(r+1)\), \(GL(2r+1)\) or \(GL(2r)\).

Let us consider the symmetric fifth power representation of \(SL(2)\). This is implemented above by rule="symmetric_power", but suppose we want to use rule="plethysm". First we construct the homomorphism by invoking its character, to be called chi:

sage: A1 = WeylCharacterRing("A1", style="coroots")
sage: chi = A1([5])
sage: chi.degree()
6
sage: chi.frobenius_schur_indicator()
-1
>>> from sage.all import *
>>> A1 = WeylCharacterRing("A1", style="coroots")
>>> chi = A1([Integer(5)])
>>> chi.degree()
6
>>> chi.frobenius_schur_indicator()
-1
A1 = WeylCharacterRing("A1", style="coroots")
chi = A1([5])
chi.degree()
chi.frobenius_schur_indicator()

This confirms that the character has degree 6 and is symplectic, so it corresponds to a homomorphism \(SL(2) \to Sp(6)\), and there is a corresponding branching rule C3 -> A1:

sage: A1 = WeylCharacterRing("A1", style="coroots")
sage: C3 = WeylCharacterRing("C3", style="coroots")
sage: chi = A1([5])
sage: sym5rule = branching_rule_from_plethysm(chi, "C3")
sage: [C3(hwv).branch(A1, rule=sym5rule) for hwv in C3.fundamental_weights()]
[A1(5), A1(4) + A1(8), A1(3) + A1(9)]
>>> from sage.all import *
>>> A1 = WeylCharacterRing("A1", style="coroots")
>>> C3 = WeylCharacterRing("C3", style="coroots")
>>> chi = A1([Integer(5)])
>>> sym5rule = branching_rule_from_plethysm(chi, "C3")
>>> [C3(hwv).branch(A1, rule=sym5rule) for hwv in C3.fundamental_weights()]
[A1(5), A1(4) + A1(8), A1(3) + A1(9)]
A1 = WeylCharacterRing("A1", style="coroots")
C3 = WeylCharacterRing("C3", style="coroots")
chi = A1([5])
sym5rule = branching_rule_from_plethysm(chi, "C3")
[C3(hwv).branch(A1, rule=sym5rule) for hwv in C3.fundamental_weights()]

This is identical to the results we would obtain using rule="symmetric_power":

sage: A1 = WeylCharacterRing("A1", style="coroots")
sage: C3 = WeylCharacterRing("C3", style="coroots")
sage: [C3(v).branch(A1, rule="symmetric_power") for v in C3.fundamental_weights()]
[A1(5), A1(4) + A1(8), A1(3) + A1(9)]
>>> from sage.all import *
>>> A1 = WeylCharacterRing("A1", style="coroots")
>>> C3 = WeylCharacterRing("C3", style="coroots")
>>> [C3(v).branch(A1, rule="symmetric_power") for v in C3.fundamental_weights()]
[A1(5), A1(4) + A1(8), A1(3) + A1(9)]
A1 = WeylCharacterRing("A1", style="coroots")
C3 = WeylCharacterRing("C3", style="coroots")
[C3(v).branch(A1, rule="symmetric_power") for v in C3.fundamental_weights()]

But the next example of plethysm gives a branching rule not available by other methods:

sage: G2 = WeylCharacterRing("G2", style="coroots")
sage: D7 = WeylCharacterRing("D7", style="coroots")
sage: ad = G2.adjoint_representation(); ad.degree()
14
sage: ad.frobenius_schur_indicator()
1
sage: for r in D7.fundamental_weights():  # long time (1.29s)
....:    print(D7(r).branch(G2, rule=branching_rule_from_plethysm(ad, "D7")))
G2(0,1)
G2(0,1) + G2(3,0)
G2(0,0) + G2(2,0) + G2(3,0) + G2(0,2) + G2(4,0)
G2(0,1) + G2(2,0) + G2(1,1) + G2(0,2) + G2(2,1) + G2(4,0) + G2(3,1)
G2(1,0) + G2(0,1) + G2(1,1) + 2*G2(3,0) + 2*G2(2,1) + G2(1,2) + G2(3,1) + G2(5,0) + G2(0,3)
G2(1,1)
G2(1,1)
>>> from sage.all import *
>>> G2 = WeylCharacterRing("G2", style="coroots")
>>> D7 = WeylCharacterRing("D7", style="coroots")
>>> ad = G2.adjoint_representation(); ad.degree()
14
>>> ad.frobenius_schur_indicator()
1
>>> for r in D7.fundamental_weights():  # long time (1.29s)
...    print(D7(r).branch(G2, rule=branching_rule_from_plethysm(ad, "D7")))
G2(0,1)
G2(0,1) + G2(3,0)
G2(0,0) + G2(2,0) + G2(3,0) + G2(0,2) + G2(4,0)
G2(0,1) + G2(2,0) + G2(1,1) + G2(0,2) + G2(2,1) + G2(4,0) + G2(3,1)
G2(1,0) + G2(0,1) + G2(1,1) + 2*G2(3,0) + 2*G2(2,1) + G2(1,2) + G2(3,1) + G2(5,0) + G2(0,3)
G2(1,1)
G2(1,1)
G2 = WeylCharacterRing("G2", style="coroots")
D7 = WeylCharacterRing("D7", style="coroots")
ad = G2.adjoint_representation(); ad.degree()
ad.frobenius_schur_indicator()
for r in D7.fundamental_weights():  # long time (1.29s)
   print(D7(r).branch(G2, rule=branching_rule_from_plethysm(ad, "D7")))

In this example, \(ad\) is the 14-dimensional adjoint representation of the exceptional group \(G_2\). Since the Frobenius-Schur indicator is 1, the representation is orthogonal, and factors through \(SO(14)\), that is, \(D7\).

We do not actually have to create the character (or for that matter its ambient WeylCharacterRing) in order to create the branching rule:

sage: branching_rule("D4","A2.adjoint_representation()","plethysm")
plethysm (along A2(1,1)) branching rule D4 => A2
>>> from sage.all import *
>>> branching_rule("D4","A2.adjoint_representation()","plethysm")
plethysm (along A2(1,1)) branching rule D4 => A2
branching_rule("D4","A2.adjoint_representation()","plethysm")

The adjoint representation of any semisimple Lie group is orthogonal, so we do not need to compute the Frobenius-Schur indicator.

Miscellaneous other subgroups

Use rule="miscellaneous" for the following rules. Every maximal subgroup \(H\) of an exceptional group \(G\) are either among these, or the five \(A_1\) subgroups described in the next section, or (if \(G\) and \(H\) have the same rank) is available using rule="extended".

\[\begin{split}\begin{aligned} B_3 & \to G_2, \\ E_6 & \to A_2, \\ E_6 & \to G_2, \\ F_4 & \to G_2 \times A_1, \\ E_6 & \to G_2 \times A_2, \\ E_7 & \to G_2 \times C_3, \\ E_7 & \to F_4 \times A_1, \\ E_7 & \to A_1 \times A_1, \\ E_7 & \to G_2 \times A_1, \\ E_7 & \to A_2 \\ E_8 & \to G_2 \times F_4. \\ E_8 & \to A_2 \times A_1. \\ E_8 & \to B_2 \end{aligned}\end{split}\]

The first rule corresponds to the embedding of \(G_2\) in \(\hbox{SO}(7)\) in its action on the trace zero octonions. The two branching rules from \(E_6\) to \(G_2\) or \(A_2\) are described in [Testerman1989]. We caution the reader that Theorem G.2 of that paper, proved there in positive characteristic is false over the complex numbers. On the other hand, the assumption of characteristic \(p\) is not important for Theorems G.1 and A.1, which describe the torus embeddings, hence contain enough information to compute the branching rule. There are other ways of embedding \(G_2\) or \(A_2\) into \(E_6\). These may embeddings be characterized by the condition that the two 27-dimensional representations of \(E_6\) restrict irreducibly to \(G_2\) or \(A_2\). Their images are maximal subgroups.

The remaining rules come about as follows. Let \(G\) be \(F_4\), \(E_6\), \(E_7\) or \(E_8\), and let \(H\) be \(G_2\), or else (if \(G=E_7\)) \(F_4\). We embed \(H\) into \(G\) in the most obvious way; that is, in the chain of subgroups

\[G_2\subset F_4\subset E_6 \subset E_7 \subset E_8\]

Then the centralizer of \(H\) is \(A_1\), \(A_2\), \(C_3\), \(F_4\) (if \(H=G_2\)) or \(A_1\) (if \(G=E_7\) and \(H=F_4\)). This gives us five of the cases. Regarding the branching rule \(E_6 \to G_2 \times A_2\), Rubenthaler [Rubenthaler2008] describes the embedding and applies it in an interesting way.

The embedding of \(A_1\times A_1\) into \(E_7\) is as follows. Deleting the 5 node of the \(E_7\) Dynkin diagram gives the Dynkin diagram of \(A_4\times A_2\), so this is a Levi subgroup. We embed \(\hbox{SL}(2)\) into this Levi subgroup via the representation \([4]\otimes[2]\). This embeds the first copy of \(A_1\). The other \(A_1\) is the connected centralizer. See [Seitz1991], particularly the proof of (3.12).

The embedding if \(G_2\times A_1\) into \(E_7\) is as follows. Deleting the 2 node of the \(E_7\) Dynkin diagram gives the \(A_6\) Dynkin diagram, which is the Levi subgroup \(\hbox{SL}(7)\). We embed \(G_2\) into \(\hbox{SL}(7)\) via the irreducible seven-dimensional representation of \(G_2\). The \(A_1\) is the centralizer.

The embedding if \(A_2\times A_1\) into \(E_8\) is as follows. Deleting the 2 node of the \(E_8\) Dynkin diagram gives the \(A_7\) Dynkin diagram, which is the Levi subgroup \(\hbox{SL}(8)\). We embed \(A_2\) into \(\hbox{SL}(8)\) via the irreducible eight-dimensional adjoint representation of \(\hbox{SL}(2)\). The \(A_1\) is the centralizer.

The embedding \(A_2\) into \(E_7\) is proved in [Seitz1991] (5.8). In particular, he computes the embedding of the \(\hbox{SL}(3)\) torus in the \(E_7\) torus, which is what is needed to implement the branching rule. The embedding of \(B_2\) into \(E_8\) is also constructed in [Seitz1991] (6.7). The embedding of the \(B_2\) Cartan subalgebra, needed to implement the branching rule, is easily deduced from (10) on page 111.

Maximal A1 subgroups of Exceptional Groups

There are seven embeddings of \(SL(2)\) into an exceptional group as a maximal subgroup: one each for \(G_2\) and \(F_4\), two nonconjugate embeddings for \(E_7\) and three for \(E_8\) These are constructed in [Testerman1992]. Create the corresponding branching rules as follows. The names of the rules are roman numerals referring to the seven cases of Testerman’s Theorem 1:

sage: branching_rule("G2","A1","i")
i branching rule G2 => A1
sage: branching_rule("F4","A1","ii")
ii branching rule F4 => A1
sage: branching_rule("E7","A1","iii")
iii branching rule E7 => A1
sage: branching_rule("E7","A1","iv")
iv branching rule E7 => A1
sage: branching_rule("E8","A1","v")
v branching rule E8 => A1
sage: branching_rule("E8","A1","vi")
vi branching rule E8 => A1
sage: branching_rule("E8","A1","vii")
vii branching rule E8 => A1
>>> from sage.all import *
>>> branching_rule("G2","A1","i")
i branching rule G2 => A1
>>> branching_rule("F4","A1","ii")
ii branching rule F4 => A1
>>> branching_rule("E7","A1","iii")
iii branching rule E7 => A1
>>> branching_rule("E7","A1","iv")
iv branching rule E7 => A1
>>> branching_rule("E8","A1","v")
v branching rule E8 => A1
>>> branching_rule("E8","A1","vi")
vi branching rule E8 => A1
>>> branching_rule("E8","A1","vii")
vii branching rule E8 => A1
branching_rule("G2","A1","i")
branching_rule("F4","A1","ii")
branching_rule("E7","A1","iii")
branching_rule("E7","A1","iv")
branching_rule("E8","A1","v")
branching_rule("E8","A1","vi")
branching_rule("E8","A1","vii")

The embeddings are characterized by the root restrictions in their branching rules: usually a simple root of the ambient group \(G\) restricts to the unique simple root of \(A_1\), except for root \(\alpha_4\) for rules iv, vi and vii, and the root \(\alpha_6\) for root vii; this is essentially the way Testerman characterizes the embeddings, and this information may be obtained from Sage by employing the describe() method of the branching rule. Thus:

sage: branching_rule("E8","A1","vii").describe()

        O 2
        |
        |
O---O---O---O---O---O---O---O
1   3   4   5   6   7   8   0
E8~
root restrictions E8 => A1:

O
1
A1

1 => 1
2 => 1
3 => 1
4 => (zero)
5 => 1
6 => (zero)
7 => 1
8 => 1

For more detailed information use verbose=True
>>> from sage.all import *
>>> branching_rule("E8","A1","vii").describe()
<BLANKLINE>
        O 2
        |
        |
O---O---O---O---O---O---O---O
1   3   4   5   6   7   8   0
E8~
root restrictions E8 => A1:
<BLANKLINE>
O
1
A1
<BLANKLINE>
1 => 1
2 => 1
3 => 1
4 => (zero)
5 => 1
6 => (zero)
7 => 1
8 => 1
<BLANKLINE>
For more detailed information use verbose=True
branching_rule("E8","A1","vii").describe()

Writing your own branching rules

Sage has many built-in branching rules. Indeed, at least up to rank eight (including all the exceptional groups) branching rules to all maximal subgroups are implemented as built in rules, except for a few obtainable using branching_rule_from_plethysm. This means that all the rules in [McKayPatera1981] are available in Sage.

Still in this section we are including instructions for coding a rule by hand. As we have already explained, the branching rule is a function from the weight lattice of G to the weight lattice of H, and if you supply this you can write your own branching rules.

As an example, let us consider how to implement the branching rule A3 -> C2. Here H = C2 = Sp(4) embedded as a subgroup in A3 = GL(4). The Cartan subalgebra \(\hbox{Lie}(U)\) consists of diagonal matrices with eigenvalues u1, u2, -u2, -u1. Then C2.space() is the two dimensional vector spaces consisting of the linear functionals u1 and u2 on U. On the other hand \(Lie(T) = \mathbf{R}^4\). A convenient way to see the restriction is to think of it as the adjoint of the map [u1,u2] -> [u1,u2,-u2,-u1], that is, [x0,x1,x2,x3] -> [x0-x3,x1-x2]. Hence we may encode the rule:

def brule(x):
    return [x[0]-x[3], x[1]-x[2]]

or simply:

brule = lambda x: [x[0]-x[3], x[1]-x[2]]

Let us check that this agrees with the built-in rule:

sage: A3 = WeylCharacterRing(['A', 3])
sage: C2 = WeylCharacterRing(['C', 2])
sage: brule = lambda x: [x[0]-x[3], x[1]-x[2]]
sage: A3(1,1,0,0).branch(C2, rule=brule)
C2(0,0) + C2(1,1)
sage: A3(1,1,0,0).branch(C2, rule="symmetric")
C2(0,0) + C2(1,1)
>>> from sage.all import *
>>> A3 = WeylCharacterRing(['A', Integer(3)])
>>> C2 = WeylCharacterRing(['C', Integer(2)])
>>> brule = lambda x: [x[Integer(0)]-x[Integer(3)], x[Integer(1)]-x[Integer(2)]]
>>> A3(Integer(1),Integer(1),Integer(0),Integer(0)).branch(C2, rule=brule)
C2(0,0) + C2(1,1)
>>> A3(Integer(1),Integer(1),Integer(0),Integer(0)).branch(C2, rule="symmetric")
C2(0,0) + C2(1,1)
A3 = WeylCharacterRing(['A', 3])
C2 = WeylCharacterRing(['C', 2])
brule = lambda x: [x[0]-x[3], x[1]-x[2]]
A3(1,1,0,0).branch(C2, rule=brule)
A3(1,1,0,0).branch(C2, rule="symmetric")

Although this works, it is better to make the rule into an element of the BranchingRule class, as follows.

sage: brule = BranchingRule("A3","C2",lambda x : [x[0]-x[3], x[1]-x[2]],"custom")
sage: A3(1,1,0,0).branch(C2, rule=brule)
C2(0,0) + C2(1,1)
>>> from sage.all import *
>>> brule = BranchingRule("A3","C2",lambda x : [x[Integer(0)]-x[Integer(3)], x[Integer(1)]-x[Integer(2)]],"custom")
>>> A3(Integer(1),Integer(1),Integer(0),Integer(0)).branch(C2, rule=brule)
C2(0,0) + C2(1,1)
brule = BranchingRule("A3","C2",lambda x : [x[0]-x[3], x[1]-x[2]],"custom")
A3(1,1,0,0).branch(C2, rule=brule)

Automorphisms and triality

The case where \(G=H\) can be treated as a special case of a branching rule. In most cases if \(G\) has a nontrivial outer automorphism, it has order two, corresponding to the symmetry of the Dynkin diagram. Such an involution exists in the cases \(A_r\), \(D_r\), \(E_6\).

So the automorphism acts on the representations of \(G\), and its effect may be computed using the branching rule code:

sage: A4 = WeylCharacterRing("A4",style="coroots")
sage: A4(1,0,1,0).degree()
45
sage: A4(0,1,0,1).degree()
45
sage: A4(1,0,1,0).branch(A4,rule="automorphic")
A4(0,1,0,1)
>>> from sage.all import *
>>> A4 = WeylCharacterRing("A4",style="coroots")
>>> A4(Integer(1),Integer(0),Integer(1),Integer(0)).degree()
45
>>> A4(Integer(0),Integer(1),Integer(0),Integer(1)).degree()
45
>>> A4(Integer(1),Integer(0),Integer(1),Integer(0)).branch(A4,rule="automorphic")
A4(0,1,0,1)
A4 = WeylCharacterRing("A4",style="coroots")
A4(1,0,1,0).degree()
A4(0,1,0,1).degree()
A4(1,0,1,0).branch(A4,rule="automorphic")

In the special case where G=D4, the Dynkin diagram has extra symmetries, and these correspond to outer automorphisms of the group. These are implemented as the "triality" branching rule:

sage: branching_rule("D4","D4","triality").describe()

    O 4
    |
    |
O---O---O
1   |2  3
    |
    O 0
D4~
root restrictions D4 => D4:

    O 4
    |
    |
O---O---O
1   2   3
D4

1 => 3
2 => 2
3 => 4
4 => 1

For more detailed information use verbose=True
>>> from sage.all import *
>>> branching_rule("D4","D4","triality").describe()
<BLANKLINE>
    O 4
    |
    |
O---O---O
1   |2  3
    |
    O 0
D4~
root restrictions D4 => D4:
<BLANKLINE>
    O 4
    |
    |
O---O---O
1   2   3
D4
<BLANKLINE>
1 => 3
2 => 2
3 => 4
4 => 1
<BLANKLINE>
For more detailed information use verbose=True
branching_rule("D4","D4","triality").describe()

Triality his is not an automorphisms of \(SO(8)\), but of its double cover \(spin(8)\). Note that \(spin(8)\) has three representations of degree 8, namely the standard representation of \(SO(8)\) and the two eight-dimensional spin representations. These are permuted by triality:

sage: D4 = WeylCharacterRing("D4",style="coroots")
sage: D4(0,0,0,1).branch(D4,rule="triality")
D4(1,0,0,0)
sage: D4(0,0,0,1).branch(D4,rule="triality").branch(D4,rule="triality")
D4(0,0,1,0)
sage: D4(0,0,0,1).branch(D4,rule="triality").branch(D4,rule="triality").branch(D4,rule="triality")
D4(0,0,0,1)
>>> from sage.all import *
>>> D4 = WeylCharacterRing("D4",style="coroots")
>>> D4(Integer(0),Integer(0),Integer(0),Integer(1)).branch(D4,rule="triality")
D4(1,0,0,0)
>>> D4(Integer(0),Integer(0),Integer(0),Integer(1)).branch(D4,rule="triality").branch(D4,rule="triality")
D4(0,0,1,0)
>>> D4(Integer(0),Integer(0),Integer(0),Integer(1)).branch(D4,rule="triality").branch(D4,rule="triality").branch(D4,rule="triality")
D4(0,0,0,1)
D4 = WeylCharacterRing("D4",style="coroots")
D4(0,0,0,1).branch(D4,rule="triality")
D4(0,0,0,1).branch(D4,rule="triality").branch(D4,rule="triality")
D4(0,0,0,1).branch(D4,rule="triality").branch(D4,rule="triality").branch(D4,rule="triality")

By contrast, rule="automorphic" simply interchanges the two spin representations, as it always does in type \(D\):

sage: D4(0,0,0,1).branch(D4,rule="automorphic")
D4(0,0,1,0)
sage: D4(0,0,1,0).branch(D4,rule="automorphic")
D4(0,0,0,1)
>>> from sage.all import *
>>> D4(Integer(0),Integer(0),Integer(0),Integer(1)).branch(D4,rule="automorphic")
D4(0,0,1,0)
>>> D4(Integer(0),Integer(0),Integer(1),Integer(0)).branch(D4,rule="automorphic")
D4(0,0,0,1)
D4(0,0,0,1).branch(D4,rule="automorphic")
D4(0,0,1,0).branch(D4,rule="automorphic")