Tutorial: visualizing root systems#

Root systems encode the positions of collections of hyperplanes in space, and form the fundamental combinatorial data underlying Coxeter and Weyl groups, Lie algebras and groups, etc. The theory can be a bit intimidating at first because of the many technical gadgets (roots, coroots, weights, …). Visualizing them goes a long way toward building a geometric intuition.

This tutorial starts from simple plots and guides you all the way to advanced plots with your own combinatorial data drawn on top of it.

See also

First plots#

In this first plot, we draw the root system for type \(A_2\) in the ambient space. It is generated from two hyperplanes at a 120 degree angle:

sage: L = RootSystem(["A",2]).ambient_space()
sage: L.plot()
Graphics object consisting of 13 graphics primitives
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2)]).ambient_space()
>>> L.plot()
Graphics object consisting of 13 graphics primitives
../../../_images/plot-1.svg

Each of those hyperplane \(H_{\alpha^\vee_i}\) is described by a linear form \(\alpha_i^\vee\) called simple coroot. To each such hyperplane corresponds a reflection along a vector called root. In this picture, the reflections are orthogonal and the two simple roots \(\alpha_1\) and \(\alpha_2\) are vectors which are normal to the reflection hyperplanes. The same color code is used uniformly: blue for 1, red for 2, green for 3, … (see CartanType.color()). The fundamental weights, \(\Lambda_1\) and \(\Lambda_2\) form the dual basis of the coroots.

The two reflections generate a group of order six which is nothing but the usual symmetric group \(S_3\), in its natural action by permutations of the coordinates of the ambient space. Wait, but the ambient space should be of dimension \(3\) then? That’s perfectly right. Here is the full picture in 3D:

sage: L = RootSystem(["A",2]).ambient_space()
sage: L.plot(projection=False)
Graphics3d Object
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2)]).ambient_space()
>>> L.plot(projection=False)
Graphics3d Object
../../../_images/plot-2.svg

However in this space, the line \((1,1,1)\) is fixed by the action of the group. Therefore, the so called barycentric projection orthogonal to \((1,1,1)\) gives a convenient 2D picture which contains all the essential information. The same projection is used by default in type \(G_2\):

sage: L = RootSystem(["G",2]).ambient_space()
sage: L.plot(reflection_hyperplanes="all")
Graphics object consisting of 21 graphics primitives
>>> from sage.all import *
>>> L = RootSystem(["G",Integer(2)]).ambient_space()
>>> L.plot(reflection_hyperplanes="all")
Graphics object consisting of 21 graphics primitives
../../../_images/plot-3.svg

The group is now the dihedral group of order 12, generated by the two reflections \(s_1\) and \(s_2\). The picture displays the hyperplanes for all 12 reflections of the group. Those reflections delimit 12 chambers which are in one to one correspondence with the elements of the group. The fundamental chamber, which is grayed out, is associated with the identity of the group.

Warning

The fundamental chamber is currently plotted as the cone generated by the fundamental weights. As can be seen on the previous 3D picture this is not quite correct if the fundamental weights do not span the space.

Another caveat is that some plotting features may require manipulating elements with rational coordinates which will fail if one is working in, say, the weight lattice. It is therefore recommended to use the root, weight, or ambient spaces for plotting purposes rather than their lattice counterparts.

Coming back to the symmetric group, here is the picture in the weight space, with all roots and all reflection hyperplanes; remark that, unlike in the ambient space, a root is not necessarily orthogonal to its corresponding reflection hyperplane:

sage: L = RootSystem(["A",2]).weight_space()
sage: L.plot(roots="all", reflection_hyperplanes="all").show(figsize=15)
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2)]).weight_space()
>>> L.plot(roots="all", reflection_hyperplanes="all").show(figsize=Integer(15))

Note

Setting a larger figure size as above can help reduce the overlap between the text labels when the figure gets crowded.

../../../_images/plot-4.svg

One can further customize which roots to display, as in the following example showing the positive roots in the weight space for type [‘G’,2], labelled by their coordinates in the root lattice:

sage: Q = RootSystem(["G",2]).root_space()
sage: L = RootSystem(["G",2]).ambient_space()
sage: L.plot(roots=list(Q.positive_roots()), fundamental_weights=False)
Graphics object consisting of 17 graphics primitives
>>> from sage.all import *
>>> Q = RootSystem(["G",Integer(2)]).root_space()
>>> L = RootSystem(["G",Integer(2)]).ambient_space()
>>> L.plot(roots=list(Q.positive_roots()), fundamental_weights=False)
Graphics object consisting of 17 graphics primitives
../../../_images/plot-5.svg

One can also customize the projection by specifying a function. Here, we display all the roots for type \(E_8\) using the projection from its eight dimensional ambient space onto 3D described on Wikipedia%27s E8 3D picture:

sage: M = matrix([[0., -0.556793440452, 0.19694925177, -0.19694925177, 0.0805477263944, -0.385290876171, 0., 0.385290876171],
....:             [0.180913155536, 0., 0.160212955043, 0.160212955043, 0., 0.0990170516545, 0.766360424875, 0.0990170516545],
....:             [0.338261212718, 0, 0, -0.338261212718, 0.672816364803, 0.171502564281, 0, -0.171502564281]])
sage: L = RootSystem(["E",8]).ambient_space()
sage: L.dimension()
8
sage: L.plot(roots="all", reflection_hyperplanes=False,             # long time
....:        projection=lambda v: M*vector(v), labels=False)
Graphics3d Object
>>> from sage.all import *
>>> M = matrix([[RealNumber('0.'), -RealNumber('0.556793440452'), RealNumber('0.19694925177'), -RealNumber('0.19694925177'), RealNumber('0.0805477263944'), -RealNumber('0.385290876171'), RealNumber('0.'), RealNumber('0.385290876171')],
...             [RealNumber('0.180913155536'), RealNumber('0.'), RealNumber('0.160212955043'), RealNumber('0.160212955043'), RealNumber('0.'), RealNumber('0.0990170516545'), RealNumber('0.766360424875'), RealNumber('0.0990170516545')],
...             [RealNumber('0.338261212718'), Integer(0), Integer(0), -RealNumber('0.338261212718'), RealNumber('0.672816364803'), RealNumber('0.171502564281'), Integer(0), -RealNumber('0.171502564281')]])
>>> L = RootSystem(["E",Integer(8)]).ambient_space()
>>> L.dimension()
8
>>> L.plot(roots="all", reflection_hyperplanes=False,             # long time
...        projection=lambda v: M*vector(v), labels=False)
Graphics3d Object
../../../_images/plot-6.svg

The projection function should be linear or affine, and return a vector with rational coordinates. The rationale for the later constraint is to allow for using the PPL exact library for manipulating polytopes. Indeed exact calculations give cleaner pictures (adjacent objects, intersection with the bounding box, …). Besides the interface to PPL is indeed currently faster than that for CDD, and it is likely to become even more so.

Alcoves and chambers#

We now draw the root system for type \(G_2\), with its alcoves (in finite type, those really are the chambers) and the corresponding elements of the Weyl group. We enlarge a bit the bounding box to make sure everything fits in the picture:

sage: RootSystem(["G",2]).ambient_space().plot(alcoves=True,
....:                                          alcove_labels=True, bounding_box=5)
Graphics object consisting of 37 graphics primitives
>>> from sage.all import *
>>> RootSystem(["G",Integer(2)]).ambient_space().plot(alcoves=True,
...                                          alcove_labels=True, bounding_box=Integer(5))
Graphics object consisting of 37 graphics primitives
../../../_images/plot-7.svg

The same picture in 3D, for type \(B_3\):

sage: RootSystem(["B",3]).ambient_space().plot(alcoves=True, alcove_labels=True)
Graphics3d Object
>>> from sage.all import *
>>> RootSystem(["B",Integer(3)]).ambient_space().plot(alcoves=True, alcove_labels=True)
Graphics3d Object
../../../_images/plot-8.svg

Alcove pictures for affine types#

We now draw the usual alcove picture for affine type \(A_2^{(1)}\):

sage: L = RootSystem(["A",2,1]).ambient_space()
sage: L.plot()                              # long time
Graphics object consisting of 160 graphics primitives
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2),Integer(1)]).ambient_space()
>>> L.plot()                              # long time
Graphics object consisting of 160 graphics primitives
../../../_images/plot-9.svg

This picture is convenient because it is low dimensional and contains most of the relevant information. Beside, by choosing the ambient space, the elements of the Weyl group act as orthogonal affine maps. In particular, reflections are usual (affine) orthogonal reflections. However this is in fact only a slice of the real picture: the Weyl group actually acts by linear maps on the full ambient space. Those maps stabilize the so-called level \(l\) hyperplanes, and we are visualizing here what’s happening at level \(1\). Here is the full picture in 3D:

sage: L.plot(bounding_box=[[-3,3],[-3,3],[-1,1]], affine=False)     # long time
Graphics3d Object
>>> from sage.all import *
>>> L.plot(bounding_box=[[-Integer(3),Integer(3)],[-Integer(3),Integer(3)],[-Integer(1),Integer(1)]], affine=False)     # long time
Graphics3d Object
../../../_images/plot-10.svg

In fact, in type \(A\), this really is a picture in 4D, but as usual the barycentric projection kills the boring extra dimension for us.

It’s usually more readable to only draw the intersection of the reflection hyperplanes with the level \(1\) hyperplane:

sage: L.plot(affine=False, level=1)                                 # long time
Graphics3d Object
>>> from sage.all import *
>>> L.plot(affine=False, level=Integer(1))                                 # long time
Graphics3d Object
../../../_images/plot-11.svg

Such 3D pictures are useful to better understand technicalities, like the fact that the fundamental weights do not necessarily all live at level 1:

sage: L = RootSystem(["G",2,1]).ambient_space()
sage: L.plot(affine=False, level=1)
Graphics3d Object
>>> from sage.all import *
>>> L = RootSystem(["G",Integer(2),Integer(1)]).ambient_space()
>>> L.plot(affine=False, level=Integer(1))
Graphics3d Object
../../../_images/plot-12.svg

Note

Such pictures may tend to be a bit flat, and it may be helpful to play with the aspect_ratio and more generally with the various options of the show() method:

sage: p = L.plot(affine=False, level=1)
sage: p.show(aspect_ratio=[1,1,2], frame=False)
>>> from sage.all import *
>>> p = L.plot(affine=False, level=Integer(1))
>>> p.show(aspect_ratio=[Integer(1),Integer(1),Integer(2)], frame=False)

As for finite root systems, the alcoves are indexed by the elements of the Weyl group \(W\). Two alcoves indexed by \(u\) and \(v\) respectively share a wall if \(u\) and \(v\) are neighbors in the right Cayley graph: \(u = vs_i\); the color of that wall is given by \(i\):

sage: L = RootSystem(["C",2,1]).ambient_space()
sage: L.plot(coroots="simple", alcove_labels=True)                  # long time
Graphics object consisting of 216 graphics primitives
>>> from sage.all import *
>>> L = RootSystem(["C",Integer(2),Integer(1)]).ambient_space()
>>> L.plot(coroots="simple", alcove_labels=True)                  # long time
Graphics object consisting of 216 graphics primitives
../../../_images/plot-13.svg

Even 2D pictures of the rank \(1 + 1\) cases can give some food for thought. Here, we draw the root lattice, with the positive roots of small height in the root poset:

sage: L = RootSystem(["A",1,1]).root_lattice()
sage: seed = L.simple_roots()
sage: succ = attrcall("pred")
sage: positive_roots = RecursivelyEnumeratedSet(seed, succ, structure='graded')
sage: it = iter(positive_roots)
sage: first_positive_roots = [next(it) for i in range(10)]
sage: L.plot(roots=first_positive_roots, affine=False, alcoves=False)
Graphics object consisting of 24 graphics primitives
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(1),Integer(1)]).root_lattice()
>>> seed = L.simple_roots()
>>> succ = attrcall("pred")
>>> positive_roots = RecursivelyEnumeratedSet(seed, succ, structure='graded')
>>> it = iter(positive_roots)
>>> first_positive_roots = [next(it) for i in range(Integer(10))]
>>> L.plot(roots=first_positive_roots, affine=False, alcoves=False)
Graphics object consisting of 24 graphics primitives
../../../_images/plot-14.svg

Here is a polished solution for the first exercise:

sage: L = RootSystem(["A",1,1]).weight_space()
sage: seed = L.simple_coroots()
sage: succ = attrcall("pred")
sage: positive_coroots = RecursivelyEnumeratedSet(seed, succ, structure='graded')
sage: it = iter(positive_coroots)
sage: first_positive_coroots = [next(it) for i in range(20)]
sage: p = L.plot(fundamental_chamber=True,
....:            reflection_hyperplanes=first_positive_coroots,
....:            affine=False, alcove_labels=1,
....:            bounding_box=[[-9,9],[-1,2]],
....:            projection=lambda x: matrix([[1,-1],[1,1]])*vector(x))
sage: p.show(figsize=20)                                            # long time
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(1),Integer(1)]).weight_space()
>>> seed = L.simple_coroots()
>>> succ = attrcall("pred")
>>> positive_coroots = RecursivelyEnumeratedSet(seed, succ, structure='graded')
>>> it = iter(positive_coroots)
>>> first_positive_coroots = [next(it) for i in range(Integer(20))]
>>> p = L.plot(fundamental_chamber=True,
...            reflection_hyperplanes=first_positive_coroots,
...            affine=False, alcove_labels=Integer(1),
...            bounding_box=[[-Integer(9),Integer(9)],[-Integer(1),Integer(2)]],
...            projection=lambda x: matrix([[Integer(1),-Integer(1)],[Integer(1),Integer(1)]])*vector(x))
>>> p.show(figsize=Integer(20))                                            # long time

Higher dimension affine pictures#

We now do some plots for rank 4 affine types, at level 1. The space is tiled by the alcoves, each of which is a 3D simplex:

sage: L = RootSystem(["A",3,1]).ambient_space()
sage: L.plot(reflection_hyperplanes=False, bounding_box=85/100)     # long time
Graphics3d Object
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(3),Integer(1)]).ambient_space()
>>> L.plot(reflection_hyperplanes=False, bounding_box=Integer(85)/Integer(100))     # long time
Graphics3d Object
../../../_images/plot-15.svg

It is recommended to use a small bounding box here, for otherwise the number of simplices grows quicker than what Sage can handle smoothly. It can help to specify explicitly which alcoves to visualize. Here is the fundamental alcove, specified by an element of the Weyl group:

sage: W = L.weyl_group()
sage: L.plot(reflection_hyperplanes=False, alcoves=[W.one()], bounding_box=2)
Graphics3d Object
>>> from sage.all import *
>>> W = L.weyl_group()
>>> L.plot(reflection_hyperplanes=False, alcoves=[W.one()], bounding_box=Integer(2))
Graphics3d Object
../../../_images/plot-16.svg

and the fundamental polygon, specified by the coordinates of its center in the root lattice:

sage: W = L.weyl_group()
sage: L.plot(reflection_hyperplanes=False, alcoves=[[0,0]], bounding_box=2)
Graphics3d Object
>>> from sage.all import *
>>> W = L.weyl_group()
>>> L.plot(reflection_hyperplanes=False, alcoves=[[Integer(0),Integer(0)]], bounding_box=Integer(2))
Graphics3d Object
../../../_images/plot-17.svg

Finally, we draw the alcoves in the classical fundamental chambers, using that those are indexed by the elements of the Weyl group having no other left descent than \(0\). In order to see the inner structure, we only draw the wireframe of the facets of the alcoves. Specifying the wireframe option requires a more flexible syntax for plots which will be explained later on in this tutorial:

sage: L = RootSystem(["B",3,1]).ambient_space()
sage: W = L.weyl_group()
sage: alcoves = [~w for d in range(12)
....:            for w in W.affine_grassmannian_elements_of_given_length(d)]
sage: p = L.plot_fundamental_chamber("classical")
sage: p += L.plot_alcoves(alcoves=alcoves, wireframe=True)
sage: p += L.plot_fundamental_weights()
sage: p.show(frame=False)
>>> from sage.all import *
>>> L = RootSystem(["B",Integer(3),Integer(1)]).ambient_space()
>>> W = L.weyl_group()
>>> alcoves = [~w for d in range(Integer(12))
...            for w in W.affine_grassmannian_elements_of_given_length(d)]
>>> p = L.plot_fundamental_chamber("classical")
>>> p += L.plot_alcoves(alcoves=alcoves, wireframe=True)
>>> p += L.plot_fundamental_weights()
>>> p.show(frame=False)
../../../_images/plot-18.svg

Drawing on top of a root system plot#

The root system plots have been designed to be used as wallpaper on top of which to draw more information. In the following example, we draw an alcove walk, specified by a word of indices of simple reflections, on top of the weight lattice in affine type \(A_{2,1}\):

sage: L = RootSystem(["A",2,1]).ambient_space()
sage: w1 = [0,2,1,2,0,2,1,0,2,1,2,1,2,0,2,0,1,2,0]
sage: L.plot(alcove_walk=w1, bounding_box=6)        # long time
Graphics object consisting of 535 graphics primitives
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2),Integer(1)]).ambient_space()
>>> w1 = [Integer(0),Integer(2),Integer(1),Integer(2),Integer(0),Integer(2),Integer(1),Integer(0),Integer(2),Integer(1),Integer(2),Integer(1),Integer(2),Integer(0),Integer(2),Integer(0),Integer(1),Integer(2),Integer(0)]
>>> L.plot(alcove_walk=w1, bounding_box=Integer(6))        # long time
Graphics object consisting of 535 graphics primitives
../../../_images/plot-19.svg

Now, what about drawing several alcove walks, and specifying some colors? A single do-it-all plot method would be cumbersome; so instead, it is actually built on top of many methods (see the list below) that can be called independently and combined at will:

sage: L.plot_roots() + L.plot_reflection_hyperplanes()
Graphics object consisting of 12 graphics primitives
>>> from sage.all import *
>>> L.plot_roots() + L.plot_reflection_hyperplanes()
Graphics object consisting of 12 graphics primitives
../../../_images/plot-20.svg

Note

By default the axes are disabled in root system plots since they tend to pollute the picture. Annoyingly they come back when combining them. Here is a workaround:

sage: p = L.plot_roots() + L.plot_reflection_hyperplanes()
sage: p.axes(False)
sage: p
Graphics object consisting of 12 graphics primitives
>>> from sage.all import *
>>> p = L.plot_roots() + L.plot_reflection_hyperplanes()
>>> p.axes(False)
>>> p
Graphics object consisting of 12 graphics primitives
../../../_images/plot-21.svg

In order to specify common information for all the pieces of a root system plot (choice of projection, bounding box, color code for the index set, …), the easiest is to create an option object using plot_parse_options(), and pass it down to each piece. We use this to plot our two walks:

sage: # long time
sage: plot_options = L.plot_parse_options(bounding_box=[[-2,5],[-2,6]])
sage: w2 = [2,1,2,0,2,0,2,1,2,0,1,2,1,2,1,0,1,2,0,2,0,1,2,0,2]
sage: p = L.plot_alcoves(plot_options=plot_options)
sage: p += L.plot_alcove_walk(w1, color="green",
....:                         plot_options=plot_options)
sage: p += L.plot_alcove_walk(w2, color="orange",
....:                         plot_options=plot_options)
sage: p
Graphics object consisting of ... graphics primitives
>>> from sage.all import *
>>> # long time
>>> plot_options = L.plot_parse_options(bounding_box=[[-Integer(2),Integer(5)],[-Integer(2),Integer(6)]])
>>> w2 = [Integer(2),Integer(1),Integer(2),Integer(0),Integer(2),Integer(0),Integer(2),Integer(1),Integer(2),Integer(0),Integer(1),Integer(2),Integer(1),Integer(2),Integer(1),Integer(0),Integer(1),Integer(2),Integer(0),Integer(2),Integer(0),Integer(1),Integer(2),Integer(0),Integer(2)]
>>> p = L.plot_alcoves(plot_options=plot_options)
>>> p += L.plot_alcove_walk(w1, color="green",
...                         plot_options=plot_options)
>>> p += L.plot_alcove_walk(w2, color="orange",
...                         plot_options=plot_options)
>>> p
Graphics object consisting of ... graphics primitives
../../../_images/plot-22.svg

And another with some foldings:

sage: p += L.plot_alcove_walk([0,1,2,0,2,0,1,2,0,1],
....:                         foldings=[False, False, True, False, False,
....:                                   False, True, False, True, False],
....:                         color="purple")
sage: p.axes(False)
sage: p.show(figsize=20)
>>> from sage.all import *
>>> p += L.plot_alcove_walk([Integer(0),Integer(1),Integer(2),Integer(0),Integer(2),Integer(0),Integer(1),Integer(2),Integer(0),Integer(1)],
...                         foldings=[False, False, True, False, False,
...                                   False, True, False, True, False],
...                         color="purple")
>>> p.axes(False)
>>> p.show(figsize=Integer(20))
../../../_images/plot-23.svg

Here we show a weight at level \(0\) and the reduced word implementing the translation by this weight:

sage: # long time
sage: L = RootSystem(["A",2,1]).ambient_space()
sage: P = RootSystem(["A",2,1]).weight_space(extended=True)
sage: Lambda = P.fundamental_weights()
sage: t = 6*Lambda[1] - 2*Lambda[2] - 4*Lambda[0]
sage: walk = L.reduced_word_of_translation(L(t))
sage: plot_options = L.plot_parse_options(bounding_box=[[-2,5],[-2,5]])
sage: p = L.plot(plot_options=plot_options)
sage: p += L.plot_alcove_walk(walk, color="green",
....:                         plot_options=plot_options)
sage: p += plot_options.family_of_vectors({t: L(t)})
sage: plot_options.finalize(p)
Graphics object consisting of ... graphics primitives
sage: p
Graphics object consisting of ... graphics primitives
>>> from sage.all import *
>>> # long time
>>> L = RootSystem(["A",Integer(2),Integer(1)]).ambient_space()
>>> P = RootSystem(["A",Integer(2),Integer(1)]).weight_space(extended=True)
>>> Lambda = P.fundamental_weights()
>>> t = Integer(6)*Lambda[Integer(1)] - Integer(2)*Lambda[Integer(2)] - Integer(4)*Lambda[Integer(0)]
>>> walk = L.reduced_word_of_translation(L(t))
>>> plot_options = L.plot_parse_options(bounding_box=[[-Integer(2),Integer(5)],[-Integer(2),Integer(5)]])
>>> p = L.plot(plot_options=plot_options)
>>> p += L.plot_alcove_walk(walk, color="green",
...                         plot_options=plot_options)
>>> p += plot_options.family_of_vectors({t: L(t)})
>>> plot_options.finalize(p)
Graphics object consisting of ... graphics primitives
>>> p
Graphics object consisting of ... graphics primitives
../../../_images/plot-24.svg

Note that the coloring of the translated alcove does not match with that of the fundamental alcove: the translation actually lives in the extended Weyl group and is the composition of the simple reflections indexed by the alcove walk together with a rotation implementing an automorphism of the Dynkin diagram.

We conclude with a rank \(3 + 1\) alcove walk:

sage: L = RootSystem(["B",3,1]).ambient_space()
sage: w3 = [0,2,1,3,2,0,2,1,0,2,3,1,2,1,3,2,0,2,0,1,2,0]
sage: (L.plot_fundamental_weights()
....:   + L.plot_reflection_hyperplanes(bounding_box=2) + L.plot_alcove_walk(w3))
Graphics3d Object
>>> from sage.all import *
>>> L = RootSystem(["B",Integer(3),Integer(1)]).ambient_space()
>>> w3 = [Integer(0),Integer(2),Integer(1),Integer(3),Integer(2),Integer(0),Integer(2),Integer(1),Integer(0),Integer(2),Integer(3),Integer(1),Integer(2),Integer(1),Integer(3),Integer(2),Integer(0),Integer(2),Integer(0),Integer(1),Integer(2),Integer(0)]
>>> (L.plot_fundamental_weights()
...   + L.plot_reflection_hyperplanes(bounding_box=Integer(2)) + L.plot_alcove_walk(w3))
Graphics3d Object
../../../_images/plot-25.svg

Hand drawing on top of a root system plot (aka Coxeter graph paper)#

Taken from John Stembridge’s excellent data archive:

“If you’ve ever worked with affine reflection groups, you’ve probably wasted lots of time drawing the reflecting hyperplanes of the rank 2 groups on scraps of paper. You may also have wished you had pads of graph paper with these lines drawn in for you. If so, you’ve come to the right place. Behold! Coxeter graph paper!”.

Now you can create your own customized color Coxeter graph paper:

sage: L = RootSystem(["C",2,1]).ambient_space()
sage: p = L.plot(bounding_box=[[-8,9],[-5,7]],      # long time (10 s)
....:            coroots="simple")
sage: p                                             # long time
Graphics object consisting of ... graphics primitives
>>> from sage.all import *
>>> L = RootSystem(["C",Integer(2),Integer(1)]).ambient_space()
>>> p = L.plot(bounding_box=[[-Integer(8),Integer(9)],[-Integer(5),Integer(7)]],      # long time (10 s)
...            coroots="simple")
>>> p                                             # long time
Graphics object consisting of ... graphics primitives
../../../_images/plot-27.svg

By default Sage’s plot are bitmap pictures which would come out ugly if printed on paper. Instead, we recommend saving the picture in postscript or svg before printing it:

sage: p.save("C21paper.eps")                # not tested
>>> from sage.all import *
>>> p.save("C21paper.eps")                # not tested

Note

Drawing pictures with a large number of alcoves is currently somewhat ridiculously slow. This is due to the use of generic code that works uniformly in all dimension rather than taylor-made code for 2D. Things should improve with the fast interface to the PPL library (see e.g. Issue #12553).

Drawing custom objects on top of a root system plot#

So far so good. Now, what if one wants to draw, on top of a root system plot, some object for which there is no preexisting plot method? Again, the plot_options object come in handy, as it can be used to compute appropriate coordinates. Here we draw the permutohedron, that is the Cayley graph of the symmetric group \(W\), by positioning each element \(w\) at \(w(\rho)\), where \(\rho\) is in the fundamental alcove:

sage: L = RootSystem(["A",2]).ambient_space()
sage: rho = L.rho()
sage: plot_options = L.plot_parse_options()
sage: W = L.weyl_group()
sage: g = W.cayley_graph(side="right")
sage: positions = {w: plot_options.projection(w.action(rho)) for w in W}
sage: p = L.plot_alcoves()
sage: p += g.plot(pos=positions, vertex_size=0,
....:             color_by_label=plot_options.color)
sage: p.axes(False)
sage: p
Graphics object consisting of 30 graphics primitives
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2)]).ambient_space()
>>> rho = L.rho()
>>> plot_options = L.plot_parse_options()
>>> W = L.weyl_group()
>>> g = W.cayley_graph(side="right")
>>> positions = {w: plot_options.projection(w.action(rho)) for w in W}
>>> p = L.plot_alcoves()
>>> p += g.plot(pos=positions, vertex_size=Integer(0),
...             color_by_label=plot_options.color)
>>> p.axes(False)
>>> p
Graphics object consisting of 30 graphics primitives
../../../_images/plot-28.svg

Todo

Could we have nice \(\LaTeX\) labels in this graph?

The same picture for \(A_3\) gives a nice 3D permutohedron:

sage: L = RootSystem(["A",3]).ambient_space()
sage: rho = L.rho()
sage: plot_options = L.plot_parse_options()
sage: W = L.weyl_group()
sage: g = W.cayley_graph(side="right")
sage: positions = {w: plot_options.projection(w.action(rho)) for w in W}
sage: p = L.plot_roots()
sage: p += g.plot3d(pos3d=positions, color_by_label=plot_options.color)
sage: p
Graphics3d Object
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(3)]).ambient_space()
>>> rho = L.rho()
>>> plot_options = L.plot_parse_options()
>>> W = L.weyl_group()
>>> g = W.cayley_graph(side="right")
>>> positions = {w: plot_options.projection(w.action(rho)) for w in W}
>>> p = L.plot_roots()
>>> p += g.plot3d(pos3d=positions, color_by_label=plot_options.color)
>>> p
Graphics3d Object
../../../_images/plot-29.svg

Similarly, we display a crystal graph by positioning each element according to its weight:

sage: C = crystals.Tableaux(["A",2], shape=[4,2])
sage: L = C.weight_lattice_realization()
sage: plot_options = L.plot_parse_options()

sage: g = C.digraph()
sage: positions = {x: plot_options.projection(x.weight()) for x in C}
sage: p = L.plot()
sage: p += g.plot(pos=positions,
....:             color_by_label=plot_options.color, vertex_size=0)
sage: p.axes(False)
sage: p.show(figsize=15)
>>> from sage.all import *
>>> C = crystals.Tableaux(["A",Integer(2)], shape=[Integer(4),Integer(2)])
>>> L = C.weight_lattice_realization()
>>> plot_options = L.plot_parse_options()

>>> g = C.digraph()
>>> positions = {x: plot_options.projection(x.weight()) for x in C}
>>> p = L.plot()
>>> p += g.plot(pos=positions,
...             color_by_label=plot_options.color, vertex_size=Integer(0))
>>> p.axes(False)
>>> p.show(figsize=Integer(15))
../../../_images/plot-30.svg

Note

In the above picture, many pairs of tableaux have the same weight and are thus superposed (look for example near the center). Some more layout logic would be needed to separate those nodes properly, but the foundations are laid firmly and uniformly across all types of root systems for writing such extensions.

Here is an analogue picture in 3D:

sage: C = crystals.Tableaux(["A",3], shape=[3,2,1])
sage: L = C.weight_lattice_realization()
sage: plot_options = L.plot_parse_options()
sage: g = C.digraph()
sage: positions = {x: plot_options.projection(x.weight()) for x in C}
sage: p = L.plot(reflection_hyperplanes=False, fundamental_weights=False)
sage: p += g.plot3d(pos3d=positions, vertex_labels=True,
....:               color_by_label=plot_options.color, edge_labels=True)
sage: p
Graphics3d Object
>>> from sage.all import *
>>> C = crystals.Tableaux(["A",Integer(3)], shape=[Integer(3),Integer(2),Integer(1)])
>>> L = C.weight_lattice_realization()
>>> plot_options = L.plot_parse_options()
>>> g = C.digraph()
>>> positions = {x: plot_options.projection(x.weight()) for x in C}
>>> p = L.plot(reflection_hyperplanes=False, fundamental_weights=False)
>>> p += g.plot3d(pos3d=positions, vertex_labels=True,
...               color_by_label=plot_options.color, edge_labels=True)
>>> p
Graphics3d Object

Enjoy and please post your best pictures on the Sage-Combinat wiki.

class sage.combinat.root_system.plot.PlotOptions(space, projection=True, bounding_box=3, color=<bound method CartanTypeFactory.color of <class 'sage.combinat.root_system.cartan_type.CartanTypeFactory'>>, labels=True, level=None, affine=None, arrowsize=5)[source]#

Bases: object

A class for plotting options for root lattice realizations.

See also

color(i)[source]#

Return the color to be used for objects indexed by \(i\).

INPUT:

  • i – an index

EXAMPLES:

sage: L = RootSystem(["A",2]).root_lattice()
sage: options = L.plot_parse_options(labels=False)
sage: alpha = L.simple_roots()
sage: options.color(1)
'blue'
sage: options.color(2)
'red'
sage: for alpha in L.roots():
....:     print("{} {}".format(alpha, options.color(alpha)))
alpha[1]             blue
alpha[2]             red
alpha[1] + alpha[2]  black
-alpha[1]            black
-alpha[2]            black
-alpha[1] - alpha[2] black
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2)]).root_lattice()
>>> options = L.plot_parse_options(labels=False)
>>> alpha = L.simple_roots()
>>> options.color(Integer(1))
'blue'
>>> options.color(Integer(2))
'red'
>>> for alpha in L.roots():
...     print("{} {}".format(alpha, options.color(alpha)))
alpha[1]             blue
alpha[2]             red
alpha[1] + alpha[2]  black
-alpha[1]            black
-alpha[2]            black
-alpha[1] - alpha[2] black
cone(rays=[], lines=[], color='black', thickness=1, alpha=1, wireframe=False, label=None, draw_degenerate=True, as_polyhedron=False)[source]#

Return the cone generated by the given rays and lines.

INPUT:

  • rays, lines – lists of elements of the root lattice realization (default: [])

  • color – a color (default: "black")

  • alpha – a number in the interval \([0, 1]\) (default: \(1\)) the desired transparency

  • label – an object to be used as the label for this cone. The label itself will be constructed by calling latex() or repr() on the object depending on the graphics backend.

  • draw_degenerate – a boolean (default: True) whether to draw cones with a degenerate intersection with the bounding box

  • as_polyhedron – a boolean (default: False) whether to return the result as a polyhedron, without clipping it to the bounding box, and without making a plot out of it (for testing purposes)

OUTPUT:

A graphic object, a polyhedron, or 0.

EXAMPLES:

sage: L = RootSystem(["A",2]).root_lattice()
sage: options = L.plot_parse_options()
sage: alpha = L.simple_roots()
sage: p = options.cone(rays=[alpha[1]], lines=[alpha[2]],
....:                  color='green', label=2); p
Graphics object consisting of 2 graphics primitives
sage: list(p)
[Polygon defined by 4 points,
 Text '$2$' at the point (3.15...,3.15...)]
sage: options.cone(rays=[alpha[1]], lines=[alpha[2]],
....:              color='green', label=2, as_polyhedron=True)
A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 1 vertex, 1 ray, 1 line
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2)]).root_lattice()
>>> options = L.plot_parse_options()
>>> alpha = L.simple_roots()
>>> p = options.cone(rays=[alpha[Integer(1)]], lines=[alpha[Integer(2)]],
...                  color='green', label=Integer(2)); p
Graphics object consisting of 2 graphics primitives
>>> list(p)
[Polygon defined by 4 points,
 Text '$2$' at the point (3.15...,3.15...)]
>>> options.cone(rays=[alpha[Integer(1)]], lines=[alpha[Integer(2)]],
...              color='green', label=Integer(2), as_polyhedron=True)
A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 1 vertex, 1 ray, 1 line

An empty result, being outside of the bounding box:

sage: options = L.plot_parse_options(labels=True,
....:                                bounding_box=[[-10,-9]]*2)
sage: options.cone(rays=[alpha[1]], lines=[alpha[2]],
....:              color='green', label=2)
0
>>> from sage.all import *
>>> options = L.plot_parse_options(labels=True,
...                                bounding_box=[[-Integer(10),-Integer(9)]]*Integer(2))
>>> options.cone(rays=[alpha[Integer(1)]], lines=[alpha[Integer(2)]],
...              color='green', label=Integer(2))
0

Test that the options are properly passed down:

sage: L = RootSystem(["A",2]).root_lattice()
sage: options = L.plot_parse_options()
sage: p = options.cone(rays=[alpha[1] + alpha[2]],
....:                  color='green', label=2, thickness=4, alpha=.5)
sage: list(p)
[Line defined by 2 points, Text '$2$' at the point (3.15...,3.15...)]
sage: sorted(p[0].options().items())
[('alpha', 0.500000000000000), ('legend_color', None),
 ('legend_label', None), ('rgbcolor', 'green'), ('thickness', 4),
 ('zorder', 1)]
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2)]).root_lattice()
>>> options = L.plot_parse_options()
>>> p = options.cone(rays=[alpha[Integer(1)] + alpha[Integer(2)]],
...                  color='green', label=Integer(2), thickness=Integer(4), alpha=RealNumber('.5'))
>>> list(p)
[Line defined by 2 points, Text '$2$' at the point (3.15...,3.15...)]
>>> sorted(p[Integer(0)].options().items())
[('alpha', 0.500000000000000), ('legend_color', None),
 ('legend_label', None), ('rgbcolor', 'green'), ('thickness', 4),
 ('zorder', 1)]

This method is tested indirectly but extensively by the various plot methods of root lattice realizations.

empty(*args)[source]#

Return an empty plot.

EXAMPLES:

sage: L = RootSystem(["A",2]).root_lattice()
sage: options = L.plot_parse_options(labels=True)
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2)]).root_lattice()
>>> options = L.plot_parse_options(labels=True)

This currently returns int(0):

sage: options.empty()
0
>>> from sage.all import *
>>> options.empty()
0

This is not a plot, so may cause some corner cases. On the other hand, \(0\) behaves as a fast neutral element, which is important given the typical idioms used in the plotting code:

sage: p = point([0,0])
sage: p + options.empty() is p
True
>>> from sage.all import *
>>> p = point([Integer(0),Integer(0)])
>>> p + options.empty() is p
True
family_of_vectors(vectors)[source]#

Return a plot of a family of vectors.

INPUT:

  • vectors – family or vectors in self

The vectors are labelled by their index.

EXAMPLES:

sage: L = RootSystem(["A",2]).root_lattice()
sage: options = L.plot_parse_options()
sage: alpha = L.simple_roots()
sage: p = options.family_of_vectors(alpha); p
Graphics object consisting of 4 graphics primitives
sage: list(p)
[Arrow from (0.0,0.0) to (1.0,0.0),
 Text '$1$' at the point (1.05,0.0),
 Arrow from (0.0,0.0) to (0.0,1.0),
 Text '$2$' at the point (0.0,1.05)]
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2)]).root_lattice()
>>> options = L.plot_parse_options()
>>> alpha = L.simple_roots()
>>> p = options.family_of_vectors(alpha); p
Graphics object consisting of 4 graphics primitives
>>> list(p)
[Arrow from (0.0,0.0) to (1.0,0.0),
 Text '$1$' at the point (1.05,0.0),
 Arrow from (0.0,0.0) to (0.0,1.0),
 Text '$2$' at the point (0.0,1.05)]

Handling of colors and labels:

sage: def color(i):
....:     return "purple" if i==1 else None
sage: options = L.plot_parse_options(labels=False, color=color)
sage: p = options.family_of_vectors(alpha)
sage: list(p)
[Arrow from (0.0,0.0) to (1.0,0.0)]
sage: p[0].options()['rgbcolor']
'purple'
>>> from sage.all import *
>>> def color(i):
...     return "purple" if i==Integer(1) else None
>>> options = L.plot_parse_options(labels=False, color=color)
>>> p = options.family_of_vectors(alpha)
>>> list(p)
[Arrow from (0.0,0.0) to (1.0,0.0)]
>>> p[Integer(0)].options()['rgbcolor']
'purple'

Matplotlib emits a warning for arrows of length 0 and draws nothing anyway. So we do not draw them at all:

sage: L = RootSystem(["A",2,1]).ambient_space()
sage: options = L.plot_parse_options()
sage: Lambda = L.fundamental_weights()
sage: p = options.family_of_vectors(Lambda); p
Graphics object consisting of 5 graphics primitives
sage: list(p)
[Text '$0$' at the point (0.0,0.0),
 Arrow from (0.0,0.0) to (0.5,0.86602451838...),
 Text '$1$' at the point (0.525,0.909325744308...),
 Arrow from (0.0,0.0) to (-0.5,0.86602451838...),
 Text '$2$' at the point (-0.525,0.909325744308...)]
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2),Integer(1)]).ambient_space()
>>> options = L.plot_parse_options()
>>> Lambda = L.fundamental_weights()
>>> p = options.family_of_vectors(Lambda); p
Graphics object consisting of 5 graphics primitives
>>> list(p)
[Text '$0$' at the point (0.0,0.0),
 Arrow from (0.0,0.0) to (0.5,0.86602451838...),
 Text '$1$' at the point (0.525,0.909325744308...),
 Arrow from (0.0,0.0) to (-0.5,0.86602451838...),
 Text '$2$' at the point (-0.525,0.909325744308...)]
finalize(G)[source]#

Finalize a root system plot.

INPUT:

  • G – a root system plot or 0

This sets the aspect ratio to 1 and remove the axes. This should be called by all the user-level plotting methods of root systems. This will become mostly obsolete when customization options won’t be lost anymore upon addition of graphics objects and there will be a proper empty object for 2D and 3D plots.

EXAMPLES:

sage: L = RootSystem(["B",2,1]).ambient_space()
sage: options = L.plot_parse_options()
sage: p = L.plot_roots(plot_options=options)
sage: p += L.plot_coroots(plot_options=options)
sage: p.axes()
True
sage: p = options.finalize(p)
sage: p.axes()
False
sage: p.aspect_ratio()
1.0

sage: options = L.plot_parse_options(affine=False)
sage: p = L.plot_roots(plot_options=options)
sage: p += point([[1,1,0]])
sage: p = options.finalize(p)
sage: p.aspect_ratio()
[1.0, 1.0, 1.0]
>>> from sage.all import *
>>> L = RootSystem(["B",Integer(2),Integer(1)]).ambient_space()
>>> options = L.plot_parse_options()
>>> p = L.plot_roots(plot_options=options)
>>> p += L.plot_coroots(plot_options=options)
>>> p.axes()
True
>>> p = options.finalize(p)
>>> p.axes()
False
>>> p.aspect_ratio()
1.0

>>> options = L.plot_parse_options(affine=False)
>>> p = L.plot_roots(plot_options=options)
>>> p += point([[Integer(1),Integer(1),Integer(0)]])
>>> p = options.finalize(p)
>>> p.aspect_ratio()
[1.0, 1.0, 1.0]

If the input is 0, this returns an empty graphics object:

sage: type(options.finalize(0))
<class 'sage.plot.plot3d.base.Graphics3dGroup'>

sage: options = L.plot_parse_options()
sage: type(options.finalize(0))
<class 'sage.plot.graphics.Graphics'>
sage: list(options.finalize(0))
[]
>>> from sage.all import *
>>> type(options.finalize(Integer(0)))
<class 'sage.plot.plot3d.base.Graphics3dGroup'>

>>> options = L.plot_parse_options()
>>> type(options.finalize(Integer(0)))
<class 'sage.plot.graphics.Graphics'>
>>> list(options.finalize(Integer(0)))
[]
in_bounding_box(x)[source]#

Return whether x is in the bounding box.

INPUT:

  • x – an element of the root lattice realization

This method is currently one of the bottlenecks, and therefore cached.

EXAMPLES:

sage: L = RootSystem(["A",2,1]).ambient_space()
sage: options = L.plot_parse_options()
sage: alpha = L.simple_roots()
sage: options.in_bounding_box(alpha[1])
True
sage: options.in_bounding_box(3*alpha[1])
False
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2),Integer(1)]).ambient_space()
>>> options = L.plot_parse_options()
>>> alpha = L.simple_roots()
>>> options.in_bounding_box(alpha[Integer(1)])
True
>>> options.in_bounding_box(Integer(3)*alpha[Integer(1)])
False
index_of_object(i)[source]#

Try to return the node of the Dynkin diagram indexing the object \(i\).

OUTPUT: a node of the Dynkin diagram or None

EXAMPLES:

sage: L = RootSystem(["A",3]).root_lattice()
sage: alpha = L.simple_roots()
sage: omega = RootSystem(["A",3]).weight_lattice().fundamental_weights()
sage: options = L.plot_parse_options(labels=False)
sage: options.index_of_object(3)
3
sage: options.index_of_object(alpha[1])
1
sage: options.index_of_object(omega[2])
2
sage: options.index_of_object(omega[2]+omega[3])
sage: options.index_of_object(30)
sage: options.index_of_object("bla")
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(3)]).root_lattice()
>>> alpha = L.simple_roots()
>>> omega = RootSystem(["A",Integer(3)]).weight_lattice().fundamental_weights()
>>> options = L.plot_parse_options(labels=False)
>>> options.index_of_object(Integer(3))
3
>>> options.index_of_object(alpha[Integer(1)])
1
>>> options.index_of_object(omega[Integer(2)])
2
>>> options.index_of_object(omega[Integer(2)]+omega[Integer(3)])
>>> options.index_of_object(Integer(30))
>>> options.index_of_object("bla")
intersection_at_level_1(x)[source]#

Return x scaled at the appropriate level, if level is set; otherwise return x.

INPUT:

  • x – an element of the root lattice realization

EXAMPLES:

sage: L = RootSystem(["A",2,1]).weight_space()
sage: options = L.plot_parse_options()
sage: options.intersection_at_level_1(L.rho())
1/3*Lambda[0] + 1/3*Lambda[1] + 1/3*Lambda[2]

sage: options = L.plot_parse_options(affine=False, level=2)
sage: options.intersection_at_level_1(L.rho())
2/3*Lambda[0] + 2/3*Lambda[1] + 2/3*Lambda[2]
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2),Integer(1)]).weight_space()
>>> options = L.plot_parse_options()
>>> options.intersection_at_level_1(L.rho())
1/3*Lambda[0] + 1/3*Lambda[1] + 1/3*Lambda[2]

>>> options = L.plot_parse_options(affine=False, level=Integer(2))
>>> options.intersection_at_level_1(L.rho())
2/3*Lambda[0] + 2/3*Lambda[1] + 2/3*Lambda[2]

When level is not set, x is returned:

sage: options = L.plot_parse_options(affine=False)
sage: options.intersection_at_level_1(L.rho())
Lambda[0] + Lambda[1] + Lambda[2]
>>> from sage.all import *
>>> options = L.plot_parse_options(affine=False)
>>> options.intersection_at_level_1(L.rho())
Lambda[0] + Lambda[1] + Lambda[2]
projection(v)[source]#

Return the projection of v.

INPUT:

  • x – an element of the root lattice realization

OUTPUT:

An immutable vector with integer or rational coefficients.

EXAMPLES:

sage: L = RootSystem(["A",2,1]).ambient_space()
sage: options = L.plot_parse_options()
sage: options.projection(L.rho())
(0, 989/571)

sage: options = L.plot_parse_options(projection=False)
sage: options.projection(L.rho())
(2, 1, 0)
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2),Integer(1)]).ambient_space()
>>> options = L.plot_parse_options()
>>> options.projection(L.rho())
(0, 989/571)

>>> options = L.plot_parse_options(projection=False)
>>> options.projection(L.rho())
(2, 1, 0)
reflection_hyperplane(coroot, as_polyhedron=False)[source]#

Return a plot of the reflection hyperplane indexed by this coroot.

  • coroot – a coroot

EXAMPLES:

sage: L = RootSystem(["B",2]).weight_space()
sage: alphacheck = L.simple_coroots()
sage: options = L.plot_parse_options()
sage: H = options.reflection_hyperplane(alphacheck[1]); H
Graphics object consisting of 2 graphics primitives
>>> from sage.all import *
>>> L = RootSystem(["B",Integer(2)]).weight_space()
>>> alphacheck = L.simple_coroots()
>>> options = L.plot_parse_options()
>>> H = options.reflection_hyperplane(alphacheck[Integer(1)]); H
Graphics object consisting of 2 graphics primitives

Todo

Display the periodic orientation by adding a \(+\) and a \(-\) sign close to the label. Typically by using the associated root to shift a bit from the vertex upon which the hyperplane label is attached.

text(label, position, rgbcolor=(0, 0, 0))[source]#

Return text widget with label label at position position

INPUT:

  • label – a string, or a Sage object upon which latex will be called

  • position – a position

  • rgbcolor – the color as an RGB tuple

EXAMPLES:

sage: L = RootSystem(["A",2]).root_lattice()
sage: options = L.plot_parse_options()
sage: list(options.text("coucou", [0,1]))
[Text 'coucou' at the point (0.0,1.0)]
sage: list(options.text(L.simple_root(1), [0,1]))
[Text '$\alpha_{1}$' at the point (0.0,1.0)]
sage: list(options.text(L.simple_root(2), [1,0], rgbcolor=(1,0.5,0)))
[Text '$\alpha_{2}$' at the point (1.0,0.0)]

sage: options = L.plot_parse_options(labels=False)
sage: options.text("coucou", [0,1])
0

sage: options = RootSystem(["B",3]).root_lattice().plot_parse_options()
sage: print(options.text("coucou", [0,1,2]).x3d_str())
<Transform translation='0 1 2'>
<Shape><Text string='coucou' solid='true'/><Appearance><Material diffuseColor='0.0 0.0 0.0' shininess='1.0' specularColor='0.0 0.0 0.0'/></Appearance></Shape>

</Transform>
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2)]).root_lattice()
>>> options = L.plot_parse_options()
>>> list(options.text("coucou", [Integer(0),Integer(1)]))
[Text 'coucou' at the point (0.0,1.0)]
>>> list(options.text(L.simple_root(Integer(1)), [Integer(0),Integer(1)]))
[Text '$\alpha_{1}$' at the point (0.0,1.0)]
>>> list(options.text(L.simple_root(Integer(2)), [Integer(1),Integer(0)], rgbcolor=(Integer(1),RealNumber('0.5'),Integer(0))))
[Text '$\alpha_{2}$' at the point (1.0,0.0)]

>>> options = L.plot_parse_options(labels=False)
>>> options.text("coucou", [Integer(0),Integer(1)])
0

>>> options = RootSystem(["B",Integer(3)]).root_lattice().plot_parse_options()
>>> print(options.text("coucou", [Integer(0),Integer(1),Integer(2)]).x3d_str())
<Transform translation='0 1 2'>
<Shape><Text string='coucou' solid='true'/><Appearance><Material diffuseColor='0.0 0.0 0.0' shininess='1.0' specularColor='0.0 0.0 0.0'/></Appearance></Shape>
<BLANKLINE>
</Transform>
thickness(i)[source]#

Return the thickness to be used for lines indexed by \(i\).

INPUT:

  • i – an index

EXAMPLES:

sage: L = RootSystem(["A",2,1]).root_lattice()
sage: options = L.plot_parse_options(labels=False)
sage: alpha = L.simple_roots()
sage: options.thickness(0)
2
sage: options.thickness(1)
1
sage: options.thickness(2)
1
sage: for alpha in L.simple_roots():
....:     print("{} {}".format(alpha, options.thickness(alpha)))
alpha[0] 2
alpha[1] 1
alpha[2] 1
>>> from sage.all import *
>>> L = RootSystem(["A",Integer(2),Integer(1)]).root_lattice()
>>> options = L.plot_parse_options(labels=False)
>>> alpha = L.simple_roots()
>>> options.thickness(Integer(0))
2
>>> options.thickness(Integer(1))
1
>>> options.thickness(Integer(2))
1
>>> for alpha in L.simple_roots():
...     print("{} {}".format(alpha, options.thickness(alpha)))
alpha[0] 2
alpha[1] 1
alpha[2] 1
sage.combinat.root_system.plot.barycentric_projection_matrix(angle=0)[source]#

Return a family of \(n+1\) vectors evenly spaced in a real vector space of dimension \(n\).

Those vectors are of norm \(1\), the scalar product between any two vector is \(1/n\), thus the distance between two tips is constant.

The family is built recursively and uniquely determined by the following property: the last vector is \((0,\dots,0,-1)\), and the projection of the first \(n\) vectors in dimension \(n-1\), after appropriate rescaling to norm \(1\), retrieves the family for \(n-1\).

OUTPUT:

A matrix with \(n+1\) columns of height \(n\) with rational or symbolic coefficients.

EXAMPLES:

One vector in dimension \(0\):

sage: from sage.combinat.root_system.root_lattice_realizations import barycentric_projection_matrix
sage: m = barycentric_projection_matrix(0); m
[]
sage: matrix(QQ,0,1).nrows()
0
sage: matrix(QQ,0,1).ncols()
1
>>> from sage.all import *
>>> from sage.combinat.root_system.root_lattice_realizations import barycentric_projection_matrix
>>> m = barycentric_projection_matrix(Integer(0)); m
[]
>>> matrix(QQ,Integer(0),Integer(1)).nrows()
0
>>> matrix(QQ,Integer(0),Integer(1)).ncols()
1

Two vectors in dimension 1:

sage: barycentric_projection_matrix(1)
[ 1 -1]
>>> from sage.all import *
>>> barycentric_projection_matrix(Integer(1))
[ 1 -1]

Three vectors in dimension 2:

sage: barycentric_projection_matrix(2)
[ 1/2*sqrt(3) -1/2*sqrt(3)            0]
[         1/2          1/2           -1]
>>> from sage.all import *
>>> barycentric_projection_matrix(Integer(2))
[ 1/2*sqrt(3) -1/2*sqrt(3)            0]
[         1/2          1/2           -1]

Four vectors in dimension 3:

sage: m = barycentric_projection_matrix(3); m
[ 1/3*sqrt(3)*sqrt(2) -1/3*sqrt(3)*sqrt(2)                    0                    0]
[         1/3*sqrt(2)          1/3*sqrt(2)         -2/3*sqrt(2)                    0]
[                 1/3                  1/3                  1/3                   -1]
>>> from sage.all import *
>>> m = barycentric_projection_matrix(Integer(3)); m
[ 1/3*sqrt(3)*sqrt(2) -1/3*sqrt(3)*sqrt(2)                    0                    0]
[         1/3*sqrt(2)          1/3*sqrt(2)         -2/3*sqrt(2)                    0]
[                 1/3                  1/3                  1/3                   -1]

The columns give four vectors that sum up to zero:

sage: sum(m.columns())
(0, 0, 0)
>>> from sage.all import *
>>> sum(m.columns())
(0, 0, 0)

and have regular mutual angles:

sage: m.transpose()*m
[   1 -1/3 -1/3 -1/3]
[-1/3    1 -1/3 -1/3]
[-1/3 -1/3    1 -1/3]
[-1/3 -1/3 -1/3    1]
>>> from sage.all import *
>>> m.transpose()*m
[   1 -1/3 -1/3 -1/3]
[-1/3    1 -1/3 -1/3]
[-1/3 -1/3    1 -1/3]
[-1/3 -1/3 -1/3    1]

Here is a plot of them:

sage: sum(arrow((0,0,0),x) for x in m.columns())
Graphics3d Object
>>> from sage.all import *
>>> sum(arrow((Integer(0),Integer(0),Integer(0)),x) for x in m.columns())
Graphics3d Object

For 2D drawings of root systems, it is desirable to rotate the result to match with the usual conventions:

sage: barycentric_projection_matrix(2, angle=2*pi/3)
[         1/2           -1          1/2]
[ 1/2*sqrt(3)            0 -1/2*sqrt(3)]
>>> from sage.all import *
>>> barycentric_projection_matrix(Integer(2), angle=Integer(2)*pi/Integer(3))
[         1/2           -1          1/2]
[ 1/2*sqrt(3)            0 -1/2*sqrt(3)]