Tutorial for the Macaulay2 package WeylGroups

The purpose of this tutorial is to explain how to use the basic commands and data structures of the package WeylGroups. You need a working installation of Macaulay 2 (see here). You also need to install the package WeylGroups and the package Graphics that it uses.

Some familiarity with the Macaulay 2 language is probably useful, and as far as mathematics are concerned, if you don't know what a root system or a Weyl group is, you are probably on the wrong webpage. Anyway, general information about root systems and Weyl groups can be found in Bourbaki, Lie groups and Lie Algebras, chapter VI (especially in the tables at the end) and information about the Bruhat order can be found in the book of Björner and Brenti called Combinatorics of the Bruhat order.


Recall...

As usual in M2, help about a particular command or class (say RootSystem) is available by typing

help "RootSystem"

or even

viewHelp "RootSystem"

to open the help file in a browser (assuming M2 has been properly configured and knows your browser).


Root systems and their Dynkin types

Let us first define a simple root system by giving its type.

i2 : R=rootSystemA(3)

The system outputs

o2 = RootSystem{...8...}
o2 : RootSystem

because a root system is implemented as a kind of hash table in which some information is stored when it has been computed. At first, only the Cartan matrix and its inverse are computed. It is possible to see how it looks like internally

i3 : peek R 

o3 = RootSystem{CartanMatrixTr => | 2  -1 0  |             }
                                  | -1 2  -1 |
				  | 0  -1 2  |
		CartanMatrixTrInv => | 3/4 1/2 1/4 |
		                     | 1/2 1   1/2 |
				     | 1/4 1/2 3/4 |
		PositiveRoots => set {}
		ReducedDecompositions => MutableHashTable{}
		Reflections => HashTable{}
		RootNorms => {1, 1, 1}
		RootSystemRank => 3
		WeylGroupList => {}

but it should not really matter to the normal user.

We can take the direct sum of root systems.

i4 : S=rootSystemA(3) ++ rootSystemF4

It is possible to obtain basic information about any root system. First of all, its Dynkin type (as a list of simple types, stored as a class called DynkinType):

i5 : dynkinType S

o5 = DynkinType{{A, 3}, {F, 4}}
o5 : DynkinType

Note that dynkinType is not fooled by low rank isomorphisms.

i6 : dynkinType(rootSystemD(3))

o6 = DynkinType{{A,3}}
o6 : DynkinType

With the same command dynkinType, one can construct a DynkinType by hand, giving the simple types in a list. Note that there is no capital letter in the command dynkinType as opposed to the class DynkinType. In general, for every class in the package, there is a constructing command with the same name but with no capital letter (sorry Mr Dynkin).

i7 : D = dynkinType({{"A",2},{"B",3}})

o7 = DynkinType{{A, 2}, {B, 3}}
o7 : DynkinType

Starting with a DynkinType, you can get a root system of that type.

i8 : rootSystem D

o8 = RootSystem{...8...}
o8 : RootSystem

You can extract the Cartan matrix of the root system and its rank.

i9 : cartanMatrix(S)

o9 = | 2  -1 0  0  0  0  0  |
     | -1 2  -1 0  0  0  0  |
     | 0  -1 2  0  0  0  0  |
     | 0  0  0  2  -1 0  0  |
     | 0  0  0  -1 2  -2 0  |
     | 0  0  0  0  -1 2  -1 |
     | 0  0  0  0  0  -1 2  |
	      7       7
o9 : Matrix ZZ  <---ZZ

i10 : rank S
o10 = 7

Roots, weights and reflections

Since a root system is mainly a set of roots, we should play a bit with them, and also with weights.

The n-th simple root (say n=3) is given by

i11 : r3=simpleRoot(R,3)
o11 = | 0  |
      | -1 |
      | 2  |
o11 : Root

The class Root is a subclass of Weight and both are implemented as vectors with coordinates on the set of fundamental weights. Note that the simple roots or the fundamental weights are ordered as they are in the Cartan matrix of the root system. There is no canonical convention. This package tends to give them in the same order as in the tables of Bourbaki.

The norm of a root is given by

i12 : norm(R,r3)

o12 = 1

You can reflect the root r3 using the 2nd simple reflection to get another root

i13 : reflect(R,2,r3)

o13 = | -1 |
      | 1  |
      | 1  |
o13 : Root

and you can even apply several simple reflections at a time by giving them in a list.

i14 : myroot=reflect(R,{2,1,3},r3)

o14 = | 1  |
      | -1 |
      | -1 |
o14 : Root

Note that they are applied from the right as in the usual notation for composition i.e. the 3rd simple reflection is applied first in this example.

Finally, one can compute the reflection corresponding to a root, as an element of the Weyl group of the root system, and then apply it to any root to get another root.

i15 : myreflection=reflection(R,myroot)

o15 = WeylGroupElement{RootSystem{...8...}, | 3  |}
                                            | -1 |
                                            | -1 |
o15 : WeylGroupElement 

i16 : myreflection*r3

o16 = | 1  |
      | -2 |
      | 1  |
o16 : Root

We will see more about Weyl group elements and their implementation in the next section so don't bother about the way o15 looks.

The coefficients of a root on the simple roots is given by

i17 : rootCoefficients(R,myroot)

o17 = | 0  |
      | -1 |
      | -1 |
        3
o17 : ZZ

One can also construct a weight by hand.

i18 : l=weight(R,{1,2,1})

o18 = | 1 |
      | 2 |
      | 1 |
o18 : Weight

and check if it is a root.

i19 : isRoot(R,l)

o19 = false

Note that to do this checking, all positive roots are computed and stored in the root system (try peek again), so the first check can be longer than subsequent ones.

Weights can be summed, subtracted or multiplied by integers like vectors.

i20 : (l+l, 2*l, l-l)

o20 = (| 2 |, | 2 |, 0)
       | 4 |  | 4 |
       | 2 |  | 4 |
o20 : Sequence 

Given a root r and a weight l, one can get the integer <l,rˇ> i.e. evaluate the dual of r at l.

i21 : eval(R,l,r3)

o21 = 1

i22 : eval(R,r3,r3)

o22 = 2

Working with a Weyl group

The Weyl group of a root system is not implemented as a structure that is computed once and for all. Instead, there is a class called WeylGroupElement representing an element of the Weyl group of a root system. Internally, it contains a reference to the root system and a weight. This weight is w(ρ) where ρ is the sum of the fundamental weights and w is the Weyl group element represented. Since the cone on the fundamental weights is a fundamental domain for the action of the Weyl group on the vector space of the root system and ρ is in the interior of this cone, we have w(ρ)=w'(ρ) if and only if w=w'. So we use w(ρ) to describe w internally.

There are two particular elements that are often useful: the neutral element of the Weyl group and the longest element (with respect to the chosen set of simple reflections).

i23 : wn = neutralWeylGroupElement R

o23 = WeylGroupElement{RootSystem{...8...}, | 1 |}
					    | 1 |
					    | 1 |
o23 : WeylGroupElement

i24 : w0 = longestWeylGroupElement R

o24 = WeylGroupElement{RootSystem{...8...}, | -1 |}
					    | -1 |
					    | -1 |
o24 : WeylGroupElement

As you can see, the weight w(ρ) when w is the neutral element is just ρ itself whereas when w is the longest element w0 the weight is -ρ.

Other Weyl group elements can be defined by using reflection to obtain the reflection corresponding to a root as above, or by giving them as products of simple reflections as follows.

i25 : w1=reduce(R,{1,2,3,2,1,3})

o25 = WeylGroupElement{RootSystem{...8...}, | -1 |}
					    | 2  |
					    | -3 |
o25 : WeylGroupElement

Conversely, given the element w1, you can get its length and a reduced decomposition of it.

i26 : coxeterLength(w1)

o26 = 4

i27 : reducedDecomposition(w1)

o27 = {1,3,2,1}
o27 : List

Note that the reduced decomposition returned is the smallest one for the lexicographic order, so it also identifies uniquely the Weyl group element.

Given a decomposition in simple roots (as a list of numbers), you can check if it is reduced.

i28 : isReduced(R,{3,2,1})

o28 = true

Naturally, you can multiply elements of a Weyl group.

i29 : w1=reduce(R,{1,2,3}); w2=reduce(R,{2,3,2});

i31 : w1*w2

o31 = WeylGroupElement{RootSystem{...8...}, | -1 |}
                                            | 3  |
                                            | -1 |
o31 : WeylGroupElement

i32 : reducedDecomposition(w1*w2)

o32 = {1, 3}
o32 : List

Left, right and double cosets

The package provides a way to deal with left and right cosets of a Weyl group, when these cosets are defined using standard parabolic subgroups, i.e. subgroups generated by a subset of the simple reflections. Such a subgroup is implemented as an object of the class Parabolic.

i33 : R=rootSystemF4; P=parabolic(R,set{1,2})

o34 = set {1, 2}
o34 : Parabolic

Let WP denote the subgroup of the Weyl group W generated by the chosen reflections (here 1 and 2). In fact, it is naturally the Weyl group of a root system (corresponding to a Levy subgroup of the associated parabolic subgroup in the algebraic group giving rise to the original root system). You can obtain this root system.

i35 : rootSystem(R,P)

o35 = RootSystem{...8...}
o35 : RootSystem

Left cosets, i.e. elements of the form w+WP are represented as objects of the class WeylGroupLeftCoset.

i36 : lc= reduce(R,{1,3,4})%P

o36 = WeylGroupLeftCoset{set {1, 2}, WeylGroupElement{RootSystem{...8...}, | 1  |}}
                                                                           | 3  |
                                                                           | -2 |
                                                                           | 1  |
o36 : WeylGroupLeftCoset

In fact, any such coset has a representative in W that is minimal for the Bruhat order, and internally, we store this minimal element. You can see it by asking:

i37 : minimalRepresentative(lc)

o37 = WeylGroupElement{RootSystem{...8...}, | 1  |}
                                            | 3  |
                                            | -2 |
                                            | 1  |
o37 : WeylGroupElement

i38 : reducedDecomposition(minimalRepresentative(lc))

o38 = {3, 4}
o38 : List

The Weyl group acts on the left on left cosets.

i39 : w0=longestWeylGroupElement R;

i40 : w0*lc

o40 = WeylGroupLeftCoset{set {1, 2}, WeylGroupElement{RootSystem{...8...}, | 1  |}}
                                                                          | -5 |
                                                                          | 6  |
                                                                          | -5 |
o40 : WeylGroupLeftCoset

The same kind of objects and functions exist for right cosets and double cosets as in

i41 : P%reduce(R,{1,3,4})  

o41 = WeylGroupRightCoset{set {1, 2}, WeylGroupElement{RootSystem{...8...}, | 1  |}}
                                                                            | 3  |
                                                                            | -2 |
                                                                            | 1  |
o41 : WeylGroupRightCoset

i42 : P % reduce(R,{1,3,4}) % P

o42 = WeylGroupDoubleCoset{set {1, 2}, set {1, 2}, WeylGroupElement{RootSystem{...8...}, | 1  |}}
                                                                                         | 3  |
                                                                                         | -2 |
                                                                                         | 1  |
o42 : WeylGroupDoubleCoset

and you can ask for minimal representatives, but note that there is no action remaining on double cosets.


Bruhat order and Hasse diagrams

There are several functions in the package to deal with the (strong) Bruhat order on a Weyl group or on a quotient of it by a parabolic subgroup. Some pictures of Hasse diagrams can be produced and this is why the package "WeylGroups" needs to load the package "Graphics". To display these pictures (in SVG format), you will need an SVG compatible browser (Firefox, for example) or any other application that can display SVG pictures.

Given an element w of a WeylGroup, you can compute the elements that are just below (resp. above) it in the Bruhat order, i.e. there is no element in between, i.e. the ones that have length one less (resp. one more).

i43 : R=rootSystemF4; w=reduce(R,{1,2,3,4,1,2}); L=underBruhat(w)

o45 = {{WeylGroupElement{RootSystem{...8...}, | -2 |}, | 0  |},
                                             | -2 |   | 0  |  
                                             | 5  |   | -1 |  
                                             | 4  |   | 2  |  
     ----------------------------------------------------------
     {WeylGroupElement{RootSystem{...8...}, | -3 |}, | -1 |},
                                            | -1 |   | 2  |  
                                            | 6  |   | -2 |  
                                            | 1  |   | 0  |  
     ----------------------------------------------------------
     {WeylGroupElement{RootSystem{...8...}, | -5 |}, | 1  |},
                                            | 2  |   | 1  |  
                                            | 2  |   | -2 |  
                                            | 3  |   | 0  |  
     ----------------------------------------------------------
     {WeylGroupElement{RootSystem{...8...}, | 3  |}, | -1 |}}
                                            | -5 |   | 0  |
                                            | 6  |   | 0  |
                                            | 3  |   | 2  |
o45 : List

Notice that M2 returns a list of pairs {w',r} made of a Weyl group element w' and a root r. The root tells you that you have to take the reflection s with respect to r so that ws=w'. You can see how all these elements w' decompose in simple reflections.

i46 : apply(L,x->reducedDecomposition(x#0))

o46 = {{1, 2, 1, 3, 2}, {1, 2, 1, 3, 4}, {1, 2, 3, 2, 4}, {2, 1, 3, 2, 4}}
o46 : List

Given two elements w1 and w2, you might also want to compute the interval [w2,w1] for the Bruhat order, i.e. the set of w' less or equal to w1 and greater or equal to w2.

i47 : w1=reduce(R,{1,2,3,4}); w2=reduce(R,{1,2,3,4,1,2}); ib=intervalBruhat(w1,w2)

o47 = ... a big mess that I don't want to display here...
o47 : HasseDiagram

Note the order in which the arguments w1 and w2 are written. The interval is returned as an object of class HasseDiagram. It contains all information on the Bruhat order for the given interval and for that reason, it does not display very well. Basically, it describes the Hasse Diagram of the interval, layer by layer. Let us extract part of it and see what there is to see.

i48 : ib#0

o48 = {{WeylGroupElement{RootSystem{...8...}, | -3 |}, {{0, | -1 |}, {1, | 1  |}}}}
                                              | -2 |        | 2  |       | 1  |
                                              | 6  |        | -2 |       | -2 |
                                              | 3  |        | 0  |       | 0  |
o48 : List

In M2, the first element of a list has index 0. So here, we have extracted this first element. It represents the top row of the Hasse diagram. Since this Hasse diagram is an interval, the top row just has one vertex w1 together with edges to the second row. This top row is of the form {{w1,someEdges}} where someEdges is a list {{0,r1},{1,r2}} describing edges frow w1 to the second row. The {0,r1} and {1,r2} are these edges. The 0 tells us that the first edge goes to the element of index 0 in the next row (with the M2 convention for lists) and the r1 is the root corresponding to the reflection to get there. So, all in all, this layer has one vertex connected to two vertices in the next row. Let us see this next row.

i49 : ib#1

o49 = {{WeylGroupElement{RootSystem{...8...}, | -3 |}, {{0, | 2  |}}},
                                              | -1 |        | -1 |    
                                              | 6  |        | 0  |    
                                              | 1  |        | 0  |    
      ----------------------------------------------------------------
      {WeylGroupElement{RootSystem{...8...}, | -5 |}, {{0, | -1 |}}}}
                                             | 2  |        | 2  |
                                             | 2  |        | -2 |
                                             | 3  |        | 0  |
o49 : List

This time, we have two vertices, each of them having one edge to the next row. Finally, the last row

i50 : ib#2

o50 = {{WeylGroupElement{RootSystem{...8...}, | -4 |}, {}}}
                                              | 1  |
                                              | 4  |
                                              | 1  |
o50 : List

has one vertex w2 and of course no edge to the next row. We therefore recognize the structure of a non-empty interval with l(w2)=l(w1)-2.

We can also produce pictures of Hasse diagrams in SVG format. First, we transform the Hasse diagram into an object of class HasseGraph, which is nearly the same thing, except that it contains strings instead of Weyl group elements and roots. In other words, it is really ready to be displayed as a graph with labels (the strings).

i51 : mygraph=hasseDiagramToGraph(ib,"labels"=>"reducedDecomposition")

o51 = HasseGraph{{{121324, {{2, 0}, {121, 1}}}}, {{12134, {{1, 0}}}, {12324, {{2, 0}}}}, {{1234, {}}}}

o51 : HasseGraph

The option says that we want the labels on the vertices and edges to be reduced decompositions (of the elements and of the reflections).

Then we can produce the description of a picture as a list of graphic primitives in the specifications of the package Graphics and finally turn this list of graphic primitives into an SVG picture that we store in a file called "myinterval.svg".

i52 : svgPicture(hasseGraphToPicture(mygraph),"myinterval")

o52 = myinterval.svg
o52 : File

Your browser can be directly called from M2 to view the SVG picture:

i53 : viewPicture "myinterval.svg"

If your browser does not support SVG, it will not display anything. Instead, you can manually open the picture in anything that opens SVG images. The SVG picture that you should see is here so if your browser displays it, you should be fine.

It is also possible to compute and display intervals for the Bruhat order (induced) on quotients of the Weyl group by a parabolic subgroup. For the fun of it, let us compute the whole Hasse diagram of the Weyl group of E6 modulo the parabolic subgroup generated by the reflections 1,2,3,4 and 5. This parabolic subgroup is a Weyl group of type D5.

i54 : R=rootSystemE(6);

i55 : P=parabolic(R,set {1,2,3,4,5});

i56 : myinter=intervalBruhat((neutralWeylGroupElement R) % P,
(longestWeylGroupElement R) % P);

i57 : mygraph=hasseDiagramToGraph(myinter,"labels"=>"reduced decomposition");

i58 : mypicture=hasseGraphToPicture(mygraph); 

i59 : svgPicture(mypicture,"hasseDiagE6modD5");

i60 : viewPicture("hasseDiagE6modD5.svg")

See the result here.

The picture can be made nicer by passing svg options to svgPicture.

i61 : myfancypicture=hasseGraphToPicture(mygraph,
"point radius"=>4,
"edge options"=>new HashTable from {"stroke-width"=>"4"},
"point options"=>new HashTable from {"fill-opacity"=>"1","fill"=>"red"});

i62 : svgPicture(myfancypicture,"fancyhasseDiagE6modD5");

i63 : viewPicture("fancyhasseDiagE6modD5.svg")

See the result here.

Note that it is also possible to generate a picture in the pgf/tikz language to include in a tex or latex document, by using the command pgfPicture of the package Graphics.

Maybe a word of caution is needed. Recall that the number of elements in a Weyl group grows quickly with the rank of the group: for example, the Weyl group of E6 already has 27.34.5=51840 elements, so if you wanted to compute the whole Hasse diagram, your computer would probably do it, but it would take several hours and occupy some memory.

Since the hasse diagram contains a lot of information on a Weyl group and can take some time to be computed, it can be convenient to store it in a file. Strictly speaking, this is not possible for the moment, but it is possible to store an object of class HasseGraph in a file, and then from it, it is quite quick to recover the original Hasse diagram.

i64 : storeHasseGraph(mygraph,"mygraph.txt")

o64 = mygraph.txt
o64 : File

i65 : mygraph2=loadHasseGraph("mygraph.txt");

i66 : mygraph===mygraph2

o66 = true