# Ticket #4327: trac_4327-root_system_plot_refactor-nt.patch

File trac_4327-root_system_plot_refactor-nt.patch, 127.4 KB (added by nthiery, 6 years ago)
• ## doc/en/reference/combinat/root_systems.rst

# HG changeset patch
# User Nicolas M. Thiery <nthiery@users.sf.net>
# Date 1368475206 14400
# Node ID a209da9b9684cf783f9d99a04f0bf03b07056898
# Parent  75e26170e32bcbb5f78e1784a1df985ecb71e1db
#4327: Refactor and extend root systems plots

diff --git a/doc/en/reference/combinat/root_systems.rst b/doc/en/reference/combinat/root_systems.rst
 a Root Systems ../sage/combinat/root_system/coxeter_matrix ../sage/combinat/root_system/root_system ../sage/combinat/root_system/plot ../sage/combinat/root_system/root_lattice_realizations ../sage/combinat/root_system/weight_lattice_realizations ../sage/combinat/root_system/root_space
• ## doc/en/thematic_tutorials/index.rst

diff --git a/doc/en/thematic_tutorials/index.rst b/doc/en/thematic_tutorials/index.rst
 a Algebraic Combinatorics * :class:Tutorial: Symmetric Functions  * :ref:lie * :ref:sage.combinat.root_system.plot * :ref:abelian_sandpile_model .. Words
• ## doc/en/thematic_tutorials/lie.rst

diff --git a/doc/en/thematic_tutorials/lie.rst b/doc/en/thematic_tutorials/lie.rst
 a DMS-1001079, DMS--0652641, DMS--0652652, lie/iwahori_hecke_algebra lie/kazhdan_lusztig_polynomials lie/bibliography
• ## doc/en/thematic_tutorials/lie/lie_basics.rst

diff --git a/doc/en/thematic_tutorials/lie/lie_basics.rst b/doc/en/thematic_tutorials/lie/lie_basics.rst
 a does support relabeled Cartan types. See Standard realizations of the ambient spaces ------------------------------------------- These realizations follow the Appendix in Bourbaki, *Lie Groups and Lie Algebras, Chapters 4-6*. These realizations follow the Appendix in Bourbaki, *Lie Groups and Lie Algebras, Chapters 4-6*. See the :ref:Root system plot tutorial  for how to visualize them. Type A ^^^^^^
• ## sage/combinat/root_system/cartan_type.py

diff --git a/sage/combinat/root_system/cartan_type.py b/sage/combinat/root_system/cartan_type.py
 a class CartanTypeFactory(SageObject): if "x" in t: import type_reducible return type_reducible.CartanType([CartanType(u) for u in t.split("x")]) elif t[-1] == "*": return CartanType(t[:-1]).dual() elif t[-1] == "~": return CartanType(t[:-1]).affine() else: return CartanType([t[0], eval(t[1:])]) class CartanTypeFactory(SageObject): sage: CartanType.color(3) 'green' The default color is black. Well, some sort of black, because plots don't handle well plain black:: The default color is black:: sage: CartanType.color(0) (0.1, 0.1, 0.1) 'black' Negative indices get the same color as their positive counterparts:: class CartanTypeFactory(SageObject): sage: CartanType.color(-3) 'green' """ return cls._colors.get(i, (0.1, 0.1, 0.1)) return cls._colors.get(i, 'black') CartanType = CartanTypeFactory()
• ## new file sage/combinat/root_system/plot.py

diff --git a/sage/combinat/root_system/plot.py b/sage/combinat/root_system/plot.py
new file mode 100644
 - r""" 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, ...). Vizualizing 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. .. SEEALSO:: - :ref:sage.combinat.root_system.root_system -- An overview of root systems in Sage - :meth:RootLatticeRealizations.ParentMethods.plot()  -- the main plotting function, with pointers to all the subroutines 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() 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 :meth: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) 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") 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 correspondance 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) .. NOTE:: Setting a larger figure size as above can help reduce the overlap between the text labels when the figure gets crowded. 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) 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:Wikipedia's 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, projection=lambda v: M*vector(v), labels=False) # long time 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. .. TOPIC:: Exercise Draw all finite root systems in 2D, using the canonical projection onto their Coxeter plane. See Stembridge's page _. 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) The same picture in 3D, for type B_3:: sage: RootSystem(["B",3]).ambient_space().plot(alcoves=True, alcove_labels=True) .. TOPIC:: Exercise Can you spot the fundamental chamber? The fundamental weights? The simple roots? The longest element of the Weyl group? 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 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 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 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) .. NOTE:: Such pictures may tend to be a bit flat, and it may be helpful to play with the aspect_ratio and more generaly with the various options of the :meth:~sage.plot.plot3d.base.Graphics3d.show method:: sage: p = L.plot(affine=False, level=1) sage: p.show(aspect_ratio=[1,1,2], frame=False) .. TOPIC:: Exercise Draw the alcove picture at level 1, and compare the position of the fundamental weights and the vertices of the fundamental alcove. 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 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: positive_roots = TransitiveIdealGraded(attrcall("pred"), L.simple_roots()) sage: it = iter(positive_roots) sage: first_positive_roots = [it.next() for i in range(10)] sage: L.plot(roots=first_positive_roots, affine=False, alcoves=False) .. TOPIC:: Exercises #. Use the same trick to draw the reflection hyperplanes in the weight lattice for the coroots of small height. Add the indexing of the alcoves by elements of the Weyl group. See below for a solution. #. Draw the positive roots in the weight lattice and in the extended weight lattice. #. Draw the reflection hyperplanes in the root lattice #. Recreate John Stembridge's "Sandwich" arrangement pictures _ by choosing appropriate coroots for the reflection hyperplanes. Here is a polished solution for the first exercise:: sage: L = RootSystem(["A",1,1]).weight_space() sage: positive_coroots = TransitiveIdealGraded(attrcall("pred"), L.simple_coroots()) sage: it = iter(positive_coroots) sage: first_positive_coroots = [it.next() 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 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 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) 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) 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) .. TOPIC:: Exercises #. Draw the fundamental alcove in the ambient space, just by itself (no reflection hyperplane, root, ...).  The automorphism group of the Dynkin diagram for A_3^{(1)} (a cycle of length 4) is the dihedral group. Visualize the corresponding symmetries of the fundamental alcove. #. Draw the fundamental alcoves for the other rank 4 affine types, and recover the automorphism groups of their Dynkin diagram from the pictures. 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 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() .. NOTE:: By default the axes are disabled in root system plots since they tend to polute 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 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 :meth:~sage.combinat.root_system.root_lattice_realizations.RootLatticeRealizations.ParentMethods.plot_parse_options, and pass it down to each piece. We use this to plot our two walks:: 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) # long time 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 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) Here we show a weight at level 0 and the reduced word implementing the translation by this weight:: 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)        # long time 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) sage: p 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) .. TOPIC:: Exercise #. Draw the tiling of 3D space by the fundamental polygons for types A,B,C,D. Hints: use the wireframe option of :meth:RootLatticeRealizations.ParentMethods.plot_alcoves and the color option of :meth:plot to only draw the alcove facets indexed by 0. .. TOPIC:: Solution :: sage: L = RootSystem(["A",3,1]).ambient_space() sage: alcoves = CartesianProduct([0,1],[0,1],[0,1]) sage: color = lambda i: "black" if i==0 else None sage: L.plot_alcoves(alcoves=alcoves, color=color, bounding_box=10,wireframe=True).show(frame=False) # long time 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]], coroots="simple") # long time (10 s) sage: p 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 .. 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 upcoming fast interface to the PPL library (see e.g. :trac: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 .. 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 .. TOPIC:: Exercises #. Locate the identity element of W in the previous picture #. Rotate the picture appropriately to highlight the various symmetries of the permutohedron. #. Make a function out of the previous example, and explore the Cayley graphs of all rank 2 and 3 Weyl groups. #. Draw the root poset for type B_2 and B_3 #. Draw the root poset for type E_8 to recover the picture from :wikipedia:File:E8_3D.png Similarly, we display a crystal graph by positioning each element according to its weight:: sage: C = CrystalOfTableaux(["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) .. 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 accross all types of root systems for writing such extensions. Here is an analogue picture in 3D:: sage: C = CrystalOfTableaux(["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 .. TOPIC:: Exercise Explore the previous picture and notice how the edges of the crystal graph are parallel to the simple roots. Enjoy and please post your best pictures on the Sage-Combinat wiki _. """ #***************************************************************************** #       Copyright (C) 2013 Nicolas M. Thiery # #  Distributed under the terms of the GNU General Public License (GPL) #                  http://www.gnu.org/licenses/ #***************************************************************************** from sage.misc.cachefunc import cached_method, cached_function from sage.misc.latex import latex from sage.misc.lazy_import import lazy_import from sage.modules.free_module_element import vector from sage.rings.all import ZZ, QQ from sage.combinat.root_system.cartan_type import CartanType lazy_import("sage.combinat.root_system.root_lattice_realizations", "RootLatticeRealizations") class PlotOptions: r""" A class for plotting options for root lattice realizations. .. SEEALSO:: - :meth:RootLatticeRealizations.ParentMethods.plot()  for a description of the plotting options - :ref:sage.combinat.root_system.plot for a tutorial on root system plotting """ def __init__(self, space, projection=True, bounding_box=3, color=CartanType.color, labels=True, level=None, affine=None, ): r""" TESTS:: sage: L = RootSystem(['B',2,1]).weight_space() sage: options = L.plot_parse_options() sage: options.dimension 2 sage: options._projections [Weight space over the Rational Field of the Root system of type ['B', 2], ] sage: L = RootSystem(['B',2,1]).ambient_space() sage: options = L.plot_parse_options() sage: options.dimension 2 sage: options._projections [Ambient space of the Root system of type ['B', 2], ] sage: options = L.plot_parse_options(affine=True) sage: options.dimension 2 sage: options._projections [Ambient space of the Root system of type ['B', 2], ] sage: options = L.plot_parse_options(affine=False) sage: options._projections [] sage: options.dimension 3 sage: options = L.plot_parse_options(affine=False, projection='barycentric') sage: options._projections [] sage: options.dimension 3 """ self.space = space self._color=color self.labels=labels # self.level = l != None: whether to intersect the alcove picture at level l # self.affine: whether to project at level l and then onto the classical space if affine is None: affine = space.cartan_type().is_affine() if affine: if level is None: level = 1 if not space.cartan_type().is_affine(): raise ValueError("affine option only valid for affine types") projections=[space.classical()] projection_space = space.classical() else: projections=[] projection_space = space self.affine = affine self.level = level if projection is True: projections.append(projection_space._plot_projection) elif projection == "barycentric": projections.append(projection_space._plot_projection_barycentric) elif projection is not False: # assert projection is a callable projections.append(projection) self._projections = projections self.origin_projected = self.projection(space.zero()) self.dimension = len(self.origin_projected) # Bounding box from sage.rings.real_mpfr import RR from sage.geometry.polyhedron.all import Polyhedron from sage.combinat.cartesian_product import CartesianProduct if bounding_box in RR: bounding_box = [[-bounding_box,bounding_box]] * self.dimension else: if not len(bounding_box) == self.dimension: raise TypeError("bounding_box argument doesn't match with the plot dimension") elif not all(len(b)==2 for b in bounding_box): raise TypeError("Invalid bounding box %s"%bounding_box) self.bounding_box = Polyhedron(vertices=CartesianProduct(*bounding_box)) @cached_method def in_bounding_box(self, x): r""" 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 """ return self.bounding_box.contains(self.projection(x)) def text(self, label, position): r""" 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 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: options = RootSystem(["A",2]).root_lattice().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() """ if self.labels: if self.dimension <= 2: if not isinstance(label, basestring): label = "$"+str(latex(label))+"$" from sage.plot.text import text return text(label, position, fontsize=15) elif self.dimension == 3: # LaTeX labels not yet supported in 3D if isinstance(label, basestring): label = label.replace("{","").replace("}","").replace("$","").replace("_","") else: label = str(label) from sage.plot.plot3d.shapes2 import text3d return text3d(label, position) else: raise NotImplementedError("Plots in dimension > 3") else: return self.empty() def color(self, i): r""" Return the color to be used for i. INPUT: - i -- an element of the index, or an element of a root lattice realization If i is a monomial like \alpha_j then j is used as 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 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 """ if i in ZZ: return self._color(i) else: assert i.parent() in RootLatticeRealizations if len(i) == 1 and i.leading_coefficient().is_one(): return self._color(i.leading_support()) else: return self._color("other") def projection(self, v): r""" 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) """ for projection in self._projections: v = projection(v) v = vector(v) v.set_immutable() return v def intersection_at_level_1(self, x): r""" 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] 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] """ if self.level is not None: return x * self.level / x.level() else: return x def empty(self, *args): r""" Return an empty plot. EXAMPLES:: sage: L = RootSystem(["A",2]).root_lattice() sage: options = L.plot_parse_options(labels=True) This currently returns int(0):: sage: 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 """ return 0 # if self.dimension == 2: # from sage.plot.graphics import Graphics # G = Graphics() # elif self.dimension == 3: # from sage.plot.plot3d.base import Graphics3dGroup # G = Graphics3dGroup() # else: # assert False, "Dimension too high (or too low!)" # self.finalize(G) # return G def finalize(self, G): r""" 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] If the input is 0, this returns an empty graphics object:: sage: type(options.finalize(0)) sage: options = L.plot_parse_options() sage: type(options.finalize(0)) sage: list(options.finalize(0)) [] """ from sage.plot.graphics import Graphics if self.dimension == 2: if G == 0: G = Graphics() G.set_aspect_ratio(1) # TODO: make this customizable G.axes(False) elif self.dimension == 3: if G == 0: from sage.plot.plot3d.base import Graphics3dGroup G = Graphics3dGroup() G.aspect_ratio(1) # TODO: Configuration axes return G def family_of_vectors(self, vectors): r""" 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 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)] Handling of colors and labels:: sage: color=lambda i: "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' 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 sage: list(p) [Text '$0$' at the point (0.0,0.0), Arrow from (0.0,0.0) to (0.5,0.866024518389), Text '$1$' at the point (0.525,0.909325744308), Arrow from (0.0,0.0) to (-0.5,0.866024518389), Text '$2$' at the point (-0.525,0.909325744308)] """ from sage.plot.arrow import arrow tail = self.origin_projected G = self.empty() for i in vectors.keys(): if self.color(i) is None: continue head = self.projection(vectors[i]) if head != tail: G += arrow(tail, head, rgbcolor=self.color(i)) G += self.text(i, 1.05*head) return self.finalize(G) def cone(self, rays=[], lines=[], color="black", alpha=1, wireframe=False, label=None, draw_degenerate=True, as_polyhedron=False): r""" 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 for this cone. The label itself will be constructed by calling :func:~sage.misc.latex.latex or :func: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) sage: p 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 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 This method is tested indirectly but extensively by the various plot methods of root lattice realizations. """ if color is None: return self.empty() from sage.geometry.polyhedron.all import Polyhedron # TODO: we currently convert lines into rays, which simplify a # bit the calculation of the intersection. But it would be # nice to benefit from the new lines option of Polyhedrons rays = list(rays)+[ray for ray in lines]+[-ray for ray in lines] # Compute the intersection at level 1, if needed if self.level: old_rays = rays vertices = [self.intersection_at_level_1(ray) for ray in old_rays if ray.level() > 0] rays = [ray for ray in old_rays if ray.level() == 0] rays += [vertex - self.intersection_at_level_1(ray) for ray in old_rays if ray.level() < 0 for vertex in vertices] else: vertices = [] # Apply the projection (which is supposed to be affine) vertices = [ self.projection(vertex) for vertex in vertices ] rays = [ self.projection(ray)-self.projection(self.space.zero()) for ray in rays ] rays = [ ray for ray in rays if ray ] # Polyhedron does not accept yet zero rays # Build the polyhedron p = Polyhedron(vertices=vertices, rays = rays) if as_polyhedron: return p # Compute the intersection with the bounding box q = p & self.bounding_box if q.dim() >= 0 and p.dim() >= 0 and (draw_degenerate or p.dim()==q.dim()): if wireframe: options = dict(point=False, line=dict(width=10), polygon=False) center = q.center() q = q.translation(-center).dilation(ZZ(95)/ZZ(100)).translation(center) else: options = dict(wireframe=False) result = q.plot(color = color, alpha=alpha, **options) if label is not None: # Put the label on the vertex having largest z, then y, then x coordinate. vertices = sorted([vector(v) for v in q.vertices()], key=lambda x: list(reversed(x))) result += self.text(label, 1.05*vector(vertices[-1])) return result else: return self.empty() def reflection_hyperplane(self, coroot, as_polyhedron=False): r""" 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 TESTS:: sage: from sage.combinat.root_system.plot import plot_expose sage: plot_expose(H) Line defined by 2 points: [(0.0, 3.0), (0.0, -3.0)] Text '$H_{\alpha^\vee_{1}}$' at the point (0.0,3.15) :: sage: L = RootSystem(["A",3,1]).ambient_space() sage: alphacheck = L.simple_coroots() sage: options = L.plot_parse_options() sage: H = options.reflection_hyperplane(alphacheck[1], as_polyhedron=True); H A 2-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex and 2 lines sage: H.lines() (A line in the direction (0, 0, 1), A line in the direction (0, 1, 0)) sage: H.vertices() (A vertex at (0, 0, 0),) :: sage: all(options.reflection_hyperplane(c, as_polyhedron=True).dim() == 2 ... for c in alphacheck) True .. 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. """ from sage.matrix.constructor import matrix L = self.space label = coroot # scalar currently only handles scalar product with # elements of self.coroot_lattice(). Furthermore, the # latter is misnamed: for ambient spaces, this does # not necessarily coincide with the coroot lattice of # the rootsystem. So we need to do a coercion. coroot = self.space.coroot_lattice()(coroot) # Compute the kernel of the linear form associated to the coroot vectors = matrix([b.scalar(coroot) for b in L.basis()]).right_kernel().basis() basis = [L.from_vector(v) for v in vectors] if self.dimension == 3: # LaTeX labels not yet supported in 3D text_label = "H_%s$"%(str(label)) else: text_label = "$H_{%s}$"%(latex(label)) return self.cone(lines = basis, color = self.color(label), label=text_label, as_polyhedron=as_polyhedron) def plot_expose(graphic_object): r""" Print some data on a 2D graphic objects for testing purposes. EXAMPLES:: sage: from sage.combinat.root_system.plot import plot_expose sage: plot_expose(polytopes.n_cube(2).plot()) Point set defined by 4 point(s): [(-1.0, -1.0), (-1.0, 1.0), (1.0, -1.0), (1.0, 1.0)] Line defined by 2 points: [(-1.0, -1.0), (-1.0, 1.0)] Line defined by 2 points: [(-1.0, -1.0), (1.0, -1.0)] Line defined by 2 points: [(-1.0, 1.0), (1.0, 1.0)] Line defined by 2 points: [(1.0, -1.0), (1.0, 1.0)] Polygon defined by 4 points: [(1.0, 1.0), (-1.0, 1.0), (-1.0, -1.0), (1.0, -1.0)] """ for g in graphic_object: if hasattr(g, "xdata"): print "%s:\t%s"%(g, zip(g.xdata, g.ydata)) else: print g @cached_function def barycentric_projection_matrix(n, angle=0): r""" Returns 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 Two vectors in dimension 1:: sage: barycentric_projection_matrix(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] Four vectors in dimension 3:: sage: m = barycentric_projection_matrix(3); m [ 1/3*sqrt(2)*sqrt(3) -1/3*sqrt(2)*sqrt(3)             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) 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] Here is a plot of them:: sage: sum(arrow((0,0,0),x) for x in m.columns()) 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)] TESTS:: sage: for n in range(1, 7): ...       m = barycentric_projection_matrix(n) ...       assert sum(m.columns()).is_zero() ...       assert matrix(QQ, n+1,n+1, lambda i,j: 1 if i==j else -1/n) == m.transpose()*m """ from sage.matrix.constructor import matrix from sage.functions.other import sqrt n = ZZ(n) if n == 0: return matrix(QQ, 0, 1) a = 1/n b = sqrt(1-a**2) result = b * barycentric_projection_matrix(n-1) result = result.augment(vector([0]*(n-1))) result = result.stack(matrix([[a]*n+[-1]])) assert sum(result.columns()).is_zero() if angle and n == 2: from sage.functions.trig import sin from sage.functions.trig import cos rotation = matrix([[sin(angle), cos(angle)],[-cos(angle), sin(angle)]]) result = rotation * result result.set_immutable() return result
• ## sage/combinat/root_system/root_lattice_realizations.py

diff --git a/sage/combinat/root_system/root_lattice_realizations.py b/sage/combinat/root_system/root_lattice_realizations.py
 a # -*- coding: utf-8 -*- """ Root lattice realizations """ #***************************************************************************** #       Copyright (C) 2007-2012 Nicolas M. Thiery #       Copyright (C) 2007-2013 Nicolas M. Thiery #                          2012 Nicolas Borie  # #       (with contributions of many others) # #  Distributed under the terms of the GNU General Public License (GPL) # #    This code is distributed in the hope that it will be useful, #    but WITHOUT ANY WARRANTY; without even the implied warranty of #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU #    General Public License for more details. # #  The full text of the GPL is available at: # #                  http://www.gnu.org/licenses/ #***************************************************************************** from sage.misc.abstract_method import abstract_method, AbstractMethod from sage.misc.all import attrcall from sage.misc.misc import attrcall from sage.misc.cachefunc import cached_method, cached_in_parent_method from sage.misc.lazy_attribute import lazy_attribute from sage.misc.superseded import deprecated_function_alias from sage.categories.coxeter_groups import CoxeterGroups from sage.categories.category_types import Category_over_base_ring from sage.categories.modules_with_basis import ModulesWithBasis from sage.structure.element import Element from sage.sets.family import Family from sage.rings.all import ZZ, QQ from sage.modules.free_module_element import vector from sage.combinat.backtrack import TransitiveIdeal, TransitiveIdealGraded from sage.misc.superseded import deprecated_function_alias from sage.structure.element import Element from sage.combinat.root_system.plot import PlotOptions, barycentric_projection_matrix class RootLatticeRealizations(Category_over_base_ring): r""" class RootLatticeRealizations(Category_o To describe the embedding, a root lattice realization must implement a method :meth:~RootLatticeRealizations.ParentMethods.simple_root (i) :meth:~RootLatticeRealizations.ParentMethods.simple_root returning for each i in the index set the image of the simple root \alpha_i under the embedding. class RootLatticeRealizations(Category_o Using those, this category provides tools for reflections, roots, the Weyl group and its action, ... .. seealso:: .. SEEALSO:: - :class:~sage.combinat.root_system.root_system.RootSystem - :class:~sage.combinat.root_system.weight_lattice_realizations.WeightLatticeRealizations class RootLatticeRealizations(Category_o class ParentMethods: def __init_extra__(self): """ Registers the embedding of the root lattice into self r""" Register the embedding of the root lattice into self. Also registers the embedding of the root space over the same base field K into self if K is not \ZZ. class RootLatticeRealizations(Category_o under the action of the dihedral group generated by the operators \tau_+ and \tau_-. .. seealso:: :meth:almost_positive_roots, :meth:tau_plus_minus .. SEEALSO:: - :meth:almost_positive_roots - :meth:tau_plus_minus EXAMPLES:: class RootLatticeRealizations(Category_o REFERENCES: .. [CFZ2] Chapoton, Fomin, Zelevinsky - Polytopal realizations of generalized associahedra .. [CFZ2] Chapoton, Fomin, Zelevinsky - Polytopal realizations of generalized associahedra """ # TODO: this should use a generic function for computing # orbits under the action of a group: class RootLatticeRealizations(Category_o """ Return the projection of \alpha_0 in the classical space. This is used e.g. to construct the projections onto the classical space. EXAMPLES: This is the opposite of the highest root in the untwisted case:: sage: L = RootSystem(["B",3,1]).root_space() sage: L._classical_alpha_0() -alpha[1] - 2*alpha[2] - 2*alpha[3] sage: L._to_classical_on_basis(0) -alpha[1] - 2*alpha[2] - 2*alpha[3] sage: L.classical().highest_root() class RootLatticeRealizations(Category_o for i in self.index_set() if i != special_node) \ / a[special_node] ###################################################################### # Root system plots def plot(self, roots="simple", coroots=False, reflection_hyperplanes="simple", fundamental_weights=None, fundamental_chamber=None, alcoves=None, alcove_labels=False, alcove_walk=None, **options): r""" Return a picture of this root lattice realization. INPUT: - roots -- which roots to display, if any. Can be one of the following: * "simple" -- The simple roots (the default) * "classical" -- Not yet implemented * "all" -- Only works in the finite case * A list or tuple of roots * False - coroots -- which coroots to display, if any. Can be one of the following: * "simple" -- The simple coroots (the default) * "classical" -- Not yet implemented * "all" -- Only works in the finite case * A list or tuple of coroots * False - fundamental_weights -- a boolean or None (default: None) whether to display the fundamental weights. If None, the fundamental weights are drawn if available. - reflection_hyperplanes -- which reflection hyperplanes to display, if any. Can be one of the following: * "simple" -- The simple roots * "classical" -- Not yet implemented * "all" -- Only works in the finite case * A list or tuple of roots * False (the default) - fundamental_chamber -- whether and how to draw the fundamental chamber. Can be one of the following: * A boolean -- Set to True to draw the fundamental chamber * "classical" -- Draw the classical fundamental chamber * None -- (the default) The fundamental chamber is drawn except in the root lattice where this is not yet implemented. For affine types the classical fundamental chamber is drawn instead. - alcoves -- one of the following (default: True): * A boolean -- Whether to display the alcoves * A list of alcoves -- The alcoves to be drawn. Each alcove is specified by the coordinates of its center in the root lattice (affine type only). Otherwise the alcoves that intersect the bounding box are drawn. - alcove_labels -- one of the following (default: False): * A boolean -- Whether to display the elements of the Weyl group indexing the alcoves. This currently requires to also set the alcoves option. * A number l -- The label is drawn at level l (affine type only), which only makes sense if affine is False. - bounding_box -- a rational number or a list of pairs thereof (default: 3) Specifies a bounding box, in the coordinate system for this plot, in which to plot alcoves and other infinite objects. If the bounding box is a number a, then the bounding box is of the form [-a,a] in all directions. Beware that there can be some border effects and the returned graphic is not necessarily strictly contained in the bounding box. - alcove_walk -- an alcove walk or None (default: None) The alcove walk is described by a list (or iterable) of vertices of the Dynkin diagram which specifies which wall is crossed at each step, starting from the fundamental alcove. - projection -- one of the following (default: True): * True -- The default projection for the root lattice realization is used. * False -- No projection is used. * barycentric -- A barycentric projection is used. * A function -- If a function is specified, it should implement a linear (or affine) map taking as input an element of this root lattice realization and returning its desired coordinates in the plot, as a vector with rational coordinates. - color -- a function mapping vertices of the Dynkin diagram to colors (default: "black" for 0, "blue" for 1, "red" for 2, "green" for 3) This is used to set the color for the simple roots, fundamental weights, reflection hyperplanes, alcove facets, etc. If the color is None, the object is not drawn. - labels -- a boolean (default: True) whether to display labels on the simple roots, fundamental weights, etc. EXAMPLES:: sage: L = RootSystem(["A",2,1]).ambient_space().plot() .. SEEALSO:: - :meth:plot_parse_options - :meth:plot_roots, :meth:plot_coroots - :meth:plot_fundamental_weights - :meth:plot_fundamental_chamber - :meth:plot_reflection_hyperplanes - :meth:plot_alcoves - :meth:plot_alcove_walk """ plot_options = self.plot_parse_options(**options) G = plot_options.empty() if roots: G += self.plot_roots(roots, plot_options=plot_options) # if coroots is None: #    coroot_lattice = self.root_system.coroot_lattice() #    if self.has_coerce_map_from(coroot_lattice): #        coroots="simple" #    else: #        coroots=False if coroots: G += self.plot_coroots(coroots, plot_options=plot_options) if fundamental_weights is None: fundamental_weights = hasattr(self, "fundamental_weights") if fundamental_weights: G += self.plot_fundamental_weights(plot_options=plot_options) if reflection_hyperplanes: G += self.plot_reflection_hyperplanes(reflection_hyperplanes, plot_options=plot_options) if alcoves is None: alcoves = self.cartan_type().is_affine() and hasattr(self, "fundamental_weights") if alcoves: G += self.plot_alcoves(alcoves, alcove_labels=alcove_labels, plot_options=plot_options) if fundamental_chamber is None: if not hasattr(self, "fundamental_weights"): fundamental_chamber = False elif self.cartan_type().is_affine(): fundamental_chamber = "classical" else: fundamental_chamber = True if fundamental_chamber: G += self.plot_fundamental_chamber(fundamental_chamber, plot_options=plot_options) if alcove_walk is not None: G += self.plot_alcove_walk(alcove_walk, plot_options=plot_options) return plot_options.finalize(G) def plot_parse_options(self, **args): r""" Return an option object to be used for root system plotting. EXAMPLES:: sage: L = RootSystem(["A",2,1]).ambient_space() sage: options = L.plot_parse_options() sage: options .. SEEALSO:: - :meth:plot for a description of the plotting options - :ref:sage.combinat.root_system.plot for a tutorial on root system plotting """ if len(args) == 1 and "plot_options" in args: return args["plot_options"] else: return PlotOptions(self, **args) def _plot_projection(self, x): r""" Implement the default projection to be used for plots. EXAMPLES: By default, this is just the identity:: sage: L = RootSystem(["B",3]).root_lattice() sage: l = L.an_element(); l 2*alpha[1] + 2*alpha[2] + 3*alpha[3] sage: L._plot_projection(l) 2*alpha[1] + 2*alpha[2] + 3*alpha[3] In the ambient space of type A_2, this is the barycentric projection. In the ambient space of affine type this goes through the classical ambient space. .. SEEALSO:: - :meth:sage.combinat.root_system.type_A.AmbientSpace._plot_projection - :meth:sage.combinat.root_system.type_affine.AmbientSpace._plot_projection - :meth:plot for a description of the plotting options - :ref:sage.combinat.root_system.plot for a tutorial on root system plotting """ return x @cached_method def _plot_projection_barycentric_matrix(self): """ A rational approximation of the matrix for the barycentric projection OUTPUT: a matrix with rational coefficients whose column sum is zero .. SEE_ALSO:: - :func:sage.combinat.root_system.plot.barycentric_projection_matrix - :meth:_plot_projection_barycentric EXAMPLES:: sage: RootSystem(["A",0]).ambient_space()._plot_projection_barycentric_matrix() [] sage: m = RootSystem(["A",1]).ambient_space()._plot_projection_barycentric_matrix(); m [ 1 -1] sage: sum(m.columns()) (0) sage: m = RootSystem(["A",2]).ambient_space()._plot_projection_barycentric_matrix(); m [      1/2        -1       1/2] [ 989/1142         0 -989/1142] sage: sum(m.columns()) (0, 0) sage: m = RootSystem(["A",3]).ambient_space()._plot_projection_barycentric_matrix(); m [      1277/1564      -1277/1564               0               0] [1009460/2141389        849/1801      -1121/1189               0] [            1/3             1/3             1/3              -1] sage: sum(m.columns()) (0, 0, 0) """ from sage.matrix.constructor import matrix from sage.symbolic.constants import pi m = matrix(QQ, barycentric_projection_matrix(self.dimension()-1, angle=2*pi/3).n(20)) # We want to guarantee that the sum of the columns of the # result is zero. This is close to be the case for the # original matrix and for the current rational # approximation. We tidy up the work by replacing the # first colum by the opposite of the sum of the others. if self.dimension()>1: # not needed in the trivial cases m.set_column(0, -sum(m[:,1:].columns())) m.set_immutable() return m def _plot_projection_barycentric(self, x): r""" Implement the barycentric projection to be used for plots. It is in fact a rational approximation thereof, but the sum of the basis vectors is guaranteed to be mapped to zero. EXAMPLES:: sage: L = RootSystem(["A",2]).ambient_space() sage: e = L.basis() sage: L._plot_projection_barycentric(e[0]) (1/2, 989/1142) sage: L._plot_projection_barycentric(e[1]) (-1, 0) sage: L._plot_projection_barycentric(e[2]) (1/2, -989/1142) .. SEEALSO:: - :meth:_plot_projection, :meth:plot - :ref:sage.combinat.root_system.plot for a tutorial on root system plotting """ return self._plot_projection_barycentric_matrix()*vector(x) def plot_roots(self, collection="simple", **options): r""" Plot the (simple/classical) roots of this root lattice. INPUT: - collection -- which roots to display can be one of the following: * "simple" (the default) * "classical" * "all" - **options -- Plotting options .. SEEALSO:: - :meth:plot for a description of the plotting options - :ref:sage.combinat.root_system.plot for a tutorial on root system plotting EXAMPLES:: sage: RootSystem(["B",3]).ambient_space().plot_roots() sage: RootSystem(["B",3]).ambient_space().plot_roots("all") TESTS:: sage: list(RootSystem(["A",2]).root_lattice().plot_roots()) [Arrow from (0.0,0.0) to (1.0,0.0), Text '$\alpha_{1}$' at the point (1.05,0.0), Arrow from (0.0,0.0) to (0.0,1.0), Text '$\alpha_{2}$' at the point (0.0,1.05)] sage: list(RootSystem(["A",2]).weight_lattice().plot_roots(labels=False)) [Arrow from (0.0,0.0) to (2.0,-1.0), Arrow from (0.0,0.0) to (-1.0,2.0)] sage: list(RootSystem(["A",2]).ambient_lattice().plot_roots()) [Arrow from (0.0,0.0) to (1.5,0.86...), Text '$\alpha_{1}$' at the point (1.575,0.90...), Arrow from (0.0,0.0) to (-1.5,0.86...), Text '$\alpha_{2}$' at the point (-1.575,0.90...)] sage: list(RootSystem(["B",2]).ambient_space().plot_roots()) [Arrow from (0.0,0.0) to (1.0,-1.0), Text '$\alpha_{1}$' at the point (1.05,-1.05), Arrow from (0.0,0.0) to (0.0,1.0), Text '$\alpha_{2}$' at the point (0.0,1.05)] sage: list(RootSystem(["A",2]).root_lattice().plot_roots("all")) [Arrow from (0.0,0.0) to (1.0,0.0), Text '$\alpha_{1}$' at the point (1.05,0.0), Arrow from (0.0,0.0) to (0.0,1.0), Text '$\alpha_{2}$' at the point (0.0,1.05), Arrow from (0.0,0.0) to (1.0,1.0), Text '$\alpha_{1} + \alpha_{2}$' at the point (1.05,1.05), Arrow from (0.0,0.0) to (-1.0,0.0), Text '$\left(-1\right)\alpha_{1}$' at the point (-1.05,0.0), Arrow from (0.0,0.0) to (0.0,-1.0), Text '$\left(-1\right)\alpha_{2}$' at the point (0.0,-1.05), Arrow from (0.0,0.0) to (-1.0,-1.0), Text '$\left(-1\right)\alpha_{1} + \left(-1\right)\alpha_{2}$' at the point (-1.05,-1.05)] """ plot_options = self.plot_parse_options(**options) root_lattice = self.root_system.root_lattice() if collection == "simple": roots = root_lattice.simple_roots() elif collection == "classical": if not self.cartan_type().is_affine(): raise ValueError("plotting classical roots only available in affine type") raise NotImplementedError("classical roots") elif collection == "all": assert self.cartan_type().is_finite(), "plotting all roots only available in finite type" roots = root_lattice.roots() elif isinstance(collection, (list, tuple)): roots = collection else: raise ValueError("Unknown value: %s"%collection) roots = Family(roots, self) return plot_options.family_of_vectors(roots) def plot_coroots(self, collection="simple", **options): r""" Plot the (simple/classical) coroots of this root lattice. INPUT: - collection -- which coroots to display. Can be one of the following: * "simple" (the default) * "classical" * "all" - **options -- Plotting options .. SEEALSO:: - :meth:plot for a description of the plotting options - :ref:sage.combinat.root_system.plot for a tutorial on root system plotting EXAMPLES:: sage: RootSystem(["B",3]).ambient_space().plot_coroots() TESTS:: sage: list(RootSystem(["B",2]).ambient_space().plot_coroots()) [Arrow from (0.0,0.0) to (1.0,-1.0), Text '$\alpha^\vee_{1}$' at the point (1.05,-1.05), Arrow from (0.0,0.0) to (0.0,2.0), Text '$\alpha^\vee_{2}$' at the point (0.0,2.1)] """ # Functionally speaking, this is duplicated from plot_roots ... # Can we avoid that, say by going to the dual space? plot_options = self.plot_parse_options(**options) coroot_lattice = self.root_system.coroot_lattice() if not self.has_coerce_map_from(coroot_lattice): raise ValueError("Can't plot the coroots: there is no embedding of the coroot lattice to this space") if collection == "simple": coroots = coroot_lattice.simple_roots() elif collection == "classical": if not self.cartan_type().is_affine(): raise ValueError("plotting classical coroots only available in affine type") raise NotImplementedError("classical coroots") elif collection == "all": assert self.cartan_type().is_finite(), "plotting all coroots only available in finite type" coroots = coroot_lattice.roots() elif isinstance(collection, (list, tuple)): coroots = collection else: raise ValueError("Unknown value: %s"%collection) coroots = Family(coroots, self) return plot_options.family_of_vectors(coroots) def plot_fundamental_weights(self, **options): r""" Plot the fundamental weights of this root lattice. INPUT: - **options -- Plotting options .. SEEALSO:: - :meth:plot for a description of the plotting options - :ref:sage.combinat.root_system.plot for a tutorial on root system plotting EXAMPLES:: sage: RootSystem(["B",3]).ambient_space().plot_fundamental_weights() TESTS:: sage: list(RootSystem(["A",2]).weight_lattice().plot_fundamental_weights()) [Arrow from (0.0,0.0) to (0.0,1.0), Text '$\Lambda_{2}$' at the point (0.0,1.05), Arrow from (0.0,0.0) to (1.0,0.0), Text '$\Lambda_{1}$' at the point (1.05,0.0)] sage: list(RootSystem(["A",2]).ambient_lattice().plot_fundamental_weights()) [Arrow from (0.0,0.0) to (-0.5,0.86...), Text '$\Lambda_{2}$' at the point (-0.525,0.90...), Arrow from (0.0,0.0) to (0.5,0.86...), Text '$\Lambda_{1}$' at the point (0.525,0.90...)] """ plot_options = self.plot_parse_options(**options) # We build the family of fundamental weights in this space, # indexed by the fundamental weights in the weight lattice. # # To this end, we don't use the embdding of the weight # lattice into self as for the roots or coroots because # the ambient space can define the fundamental weights # slightly differently (the usual GL_n vs SL_n catch). weight_lattice = self.root_system.weight_lattice() fundamental_weights = Family(dict(zip(weight_lattice.fundamental_weights(), self.fundamental_weights()))) return plot_options.family_of_vectors(fundamental_weights) def plot_reflection_hyperplanes(self, collection="simple", **options): r""" Plot the simple reflection hyperplanes. INPUT: - collection -- which reflection hyperplanes to display. Can be one of the following: * "simple" (the default) * "classical" * "all" - **options -- Plotting options .. SEEALSO:: - :meth:plot for a description of the plotting options - :ref:sage.combinat.root_system.plot for a tutorial on root system plotting EXAMPLES:: sage: RootSystem(["A",2,1]).ambient_space().plot_reflection_hyperplanes() sage: RootSystem(["G",2,1]).ambient_space().plot_reflection_hyperplanes() sage: RootSystem(["A",3]).weight_space().plot_reflection_hyperplanes() sage: RootSystem(["B",3]).ambient_space().plot_reflection_hyperplanes() sage: RootSystem(["A",3,1]).weight_space().plot_reflection_hyperplanes() sage: RootSystem(["B",3,1]).ambient_space().plot_reflection_hyperplanes() sage: RootSystem(["A",2,1]).weight_space().plot_reflection_hyperplanes(affine=False, level=1) sage: RootSystem(["A",2]).root_lattice().plot_reflection_hyperplanes() TESTS:: sage: from sage.combinat.root_system.plot import plot_expose sage: L = RootSystem(["A",2]).ambient_space() sage: plot_expose(L.plot_reflection_hyperplanes()) Line defined by 2 points: [(-1.73..., 3.0), (1.73..., -3.0)] Text '$H_{\alpha^\vee_{1}}$' at the point (-1.81...,3.15) Line defined by 2 points: [(1.73..., 3.0), (-1.73..., -3.0)] Text '$H_{\alpha^\vee_{2}}$' at the point (1.81...,3.15) sage: plot_expose(L.plot_reflection_hyperplanes("all")) Line defined by 2 points: [(-1.73..., 3.0), (1.73..., -3.0)] Text '$H_{\alpha^\vee_{1}}$' at the point (-1.81...,3.15) Line defined by 2 points: [(1.73..., 3.0), (-1.73..., -3.0)] Text '$H_{\alpha^\vee_{2}}$' at the point (1.81...,3.15) Line defined by 2 points: [(3.0, 0.0), (-3.0, 0.0)] Text '$H_{\alpha^\vee_{1} + \alpha^\vee_{2}}$' at the point (3.15,0.0) sage: L = RootSystem(["A",2,1]).ambient_space() sage: plot_expose(L.plot_reflection_hyperplanes()) Line defined by 2 points: [(3.0, 0.86...), (-3.0, 0.86...)] Text '$H_{\alpha^\vee_{0}}$' at the point (3.15,0.90...) Line defined by 2 points: [(-1.73..., 3.0), (1.73..., -3.0)] Text '$H_{\alpha^\vee_{1}}$' at the point (-1.81...,3.15) Line defined by 2 points: [(1.73..., 3.0), (-1.73..., -3.0)] Text '$H_{\alpha^\vee_{2}}$' at the point (1.81...,3.15) .. TODO:: Provide an option for transparency? """ plot_options = self.plot_parse_options(**options) coroot_lattice = self.root_system.coroot_lattice() # Recall that the coroots are given by the roots of the coroot lattice if collection == "simple": coroots = coroot_lattice.simple_roots() elif collection == "classical": if not self.cartan_type().is_affine(): raise ValueError("plotting classical reflection hyperplanes only available in affine type") raise NotImplementedError("classical roots") elif collection == "all": assert self.cartan_type().is_finite(), "plotting all reflection hyperplanes only available in finite type" coroots = coroot_lattice.positive_roots() elif isinstance(collection, (list, tuple)): coroots = collection else: raise ValueError("Unknown value: %s"%collection) G = plot_options.empty() for coroot in coroots: G += plot_options.reflection_hyperplane(coroot) return plot_options.finalize(G) def plot_hedron(self, **options): r""" Plot the polyhedron whose vertices are given by the orbit of \rho. In type A, this is the usual permutohedron. .. SEEALSO:: - :meth:plot for a description of the plotting options - :ref:sage.combinat.root_system.plot for a tutorial on root system plotting EXAMPLES:: sage: RootSystem(["A",2]).ambient_space().plot_hedron() sage: RootSystem(["A",3]).ambient_space().plot_hedron() sage: RootSystem(["B",3]).ambient_space().plot_hedron() sage: RootSystem(["C",3]).ambient_space().plot_hedron() sage: RootSystem(["D",3]).ambient_space().plot_hedron() Surprise: polyhedrons of large dimension know how to project themselves nicely:: sage: RootSystem(["F",4]).ambient_space().plot_hedron() # long time TESTS:: sage: from sage.combinat.root_system.plot import plot_expose sage: L = RootSystem(["B",2]).ambient_space() sage: plot_expose(L.plot_hedron()) Point set defined by 8 point(s): [(-1.5, -0.5), (-1.5, 0.5), (-0.5, -1.5), (-0.5, 1.5), (0.5, -1.5), (0.5, 1.5), (1.5, -0.5), (1.5, 0.5)] Line defined by 2 points:        [(-1.5, -0.5), (-1.5, 0.5)] Line defined by 2 points:        [(-1.5, -0.5), (-0.5, -1.5)] Line defined by 2 points:        [(-1.5, 0.5), (-0.5, 1.5)] Line defined by 2 points:        [(-0.5, -1.5), (0.5, -1.5)] Line defined by 2 points:        [(-0.5, 1.5), (0.5, 1.5)] Line defined by 2 points:        [(0.5, -1.5), (1.5, -0.5)] Line defined by 2 points:        [(0.5, 1.5), (1.5, 0.5)] Line defined by 2 points:        [(1.5, -0.5), (1.5, 0.5)] Polygon defined by 8 points:     [(1.5, 0.5), (0.5, 1.5), (-0.5, 1.5), (-1.5, 0.5), (-1.5, -0.5), (-0.5, -1.5), (0.5, -1.5), (1.5, -0.5)] """ from sage.geometry.polyhedron.all import Polyhedron plot_options = self.plot_parse_options(**options) assert self.cartan_type().is_finite() vertices = [plot_options.projection(vertex) for vertex in self.rho().orbit()] return Polyhedron(vertices=vertices).plot() def plot_fundamental_chamber(self, style="normal", **options): r""" Plot the (classical) fundamental chamber. INPUT: - style -- "normal" or "classical" (default: "normal") - **options -- Plotting options .. SEEALSO:: - :meth:plot for a description of the plotting options - :ref:sage.combinat.root_system.plot for a tutorial on root system plotting EXAMPLES: 2D plots:: sage: RootSystem(["B",2]).ambient_space().plot_fundamental_chamber() sage: RootSystem(["B",2,1]).ambient_space().plot_fundamental_chamber() sage: RootSystem(["B",2,1]).ambient_space().plot_fundamental_chamber("classical") 3D plots:: sage: RootSystem(["A",3,1]).weight_space() .plot_fundamental_chamber() sage: RootSystem(["B",3,1]).ambient_space().plot_fundamental_chamber() This feature is currently not available in the root lattice/space:: sage: list(RootSystem(["A",2]).root_lattice().plot_fundamental_chamber()) Traceback (most recent call last): ... TypeError: classical fundamental chamber not yet available in the root lattice TESTS:: sage: from sage.combinat.root_system.plot import plot_expose sage: L = RootSystem(["B",2,1]).ambient_space() sage: plot_expose(L.plot_fundamental_chamber()) Polygon defined by 3 points:     [(0.5, 0.5), (1.0, 0.0), (0.0, 0.0)] sage: plot_expose(L.plot_fundamental_chamber(style="classical")) Polygon defined by 3 points:     [(0.0, 0.0), (3.0, 3.0), (3.0, 0.0)] """ plot_options = self.plot_parse_options(**options) if not hasattr(self, "fundamental_weights"): raise TypeError("classical fundamental chamber not yet available in the root lattice") Lambda = self.fundamental_weights() cartan_type = self.cartan_type() if style=="classical": if not cartan_type.is_affine(): raise TypeError("classical fundamental chamber only available in affine type") I = cartan_type.classical().index_set() lines = [Lambda[cartan_type.special_node()]] else: I = cartan_type.index_set() lines = [] return plot_options.cone(rays = [Lambda[i] for i in I], lines=lines, color="lightgrey", alpha=.3) def plot_alcoves(self, alcoves=True, alcove_labels=False, wireframe=False, **options): r""" Plot the alcoves and optionaly their labels. INPUT: - alcoves -- a list of alcoves or True (default: True) - alcove_labels -- a boolean or a number specifying at which level to put the label (default: False) - **options -- Plotting options .. SEEALSO:: - :meth:plot for a description of the plotting options - :ref:sage.combinat.root_system.plot for a tutorial on root system plotting, and in particular how the alcoves can be specified. EXAMPLES: 2D plots:: sage: RootSystem(["B",2,1]).ambient_space().plot_alcoves()                      # long time (3s) 3D plots:: sage: RootSystem(["A",2,1]).weight_space() .plot_alcoves(affine=False)          # long time (3s) sage: RootSystem(["G",2,1]).ambient_space().plot_alcoves(affine=False, level=1) # long time (3s) Here we plot a single alcove:: sage: L = RootSystem(["A",3,1]).ambient_space() sage: W = L.weyl_group() sage: L.plot(alcoves=[W.one()], reflection_hyperplanes=False, bounding_box=2) TESTS:: sage: from sage.combinat.root_system.plot import plot_expose sage: L = RootSystem(["A",2,1]).weight_space() sage: plot_expose(L.plot_alcoves(alcoves=[[0,0]])) Line defined by 2 points: [(0.0, 1.0), (1.0, 0.0)] Line defined by 2 points: [(0.0, 1.0), (0.0, 0.0)] Line defined by 2 points: [(1.0, 0.0), (0.0, 0.0)] Line defined by 2 points: [(0.0, 1.0), (-1.0, 1.0)] Line defined by 2 points: [(-1.0, 1.0), (0.0, 0.0)] Line defined by 2 points: [(-1.0, 1.0), (-1.0, 0.0)] Line defined by 2 points: [(0.0, 0.0), (-1.0, 0.0)] Line defined by 2 points: [(-1.0, 0.0), (0.0, -1.0)] Line defined by 2 points: [(1.0, 0.0), (1.0, -1.0)] Line defined by 2 points: [(0.0, 0.0), (1.0, -1.0)] Line defined by 2 points: [(1.0, -1.0), (0.0, -1.0)] Line defined by 2 points: [(0.0, 0.0), (0.0, -1.0)] """ plot_options = self.plot_parse_options(**options) if not hasattr(self, "fundamental_weights"): raise TypeError("alcoves not yet available in the root lattice") Lambda = self.fundamental_weights() cartan_type = self.cartan_type() I = cartan_type.index_set() W = self.weyl_group() if alcove_labels is not False: rho = self.rho() if alcove_labels is not True: # The input is the desired level rho = rho * alcove_labels / rho.level() else: rho = plot_options.intersection_at_level_1(rho) # The rays of the fundamental alcove fundamental_alcove_rays = Lambda.map(plot_options.intersection_at_level_1) def alcove_in_bounding_box(w): return any(plot_options.in_bounding_box(w.action(fundamental_alcove_rays[i])) for i in I) def alcove_facet(w, i): # Alcove facets with degenerate intersection with the # bounding box bring no information; we might as well # not draw them. Besides this avoids ugly fat points # in dimension 2. return plot_options.cone(rays=[w.action(fundamental_alcove_rays[j]) for j in I if j != i], color = plot_options.color(i), wireframe=wireframe, draw_degenerate=False) def alcove_label(w): label = "$1$" if w.is_one() else "$s_{"+"".join(str(j) for j in w.reduced_word())+"}$" position = plot_options.projection(w.action(rho)) if position in plot_options.bounding_box: return plot_options.text(label, position) else: return plot_options.empty() G = plot_options.empty() if alcoves is not True: alcoves = list(alcoves) if alcoves is True or (len(alcoves)>0 and W.is_parent_of(alcoves[0])): if alcoves is True: alcoves = W.weak_order_ideal(alcove_in_bounding_box, side="right") # We assume that the fundamental alcove lies within # the bounding box, and explore the alcoves # intersecting the bounding box by going up right # order (i.e. going away from the fundamental alcove) for w in alcoves: for i in w.descents(side="right", positive=True): G += alcove_facet(w, i) if alcove_labels is not False: G += alcove_label(w) else: if not cartan_type.is_affine(): raise TypeError("alcoves=list only available in affine type") translation_factors = cartan_type.translation_factors() simple_roots = self.simple_roots() translation_vectors = Family({i: translation_factors[i]*simple_roots[i] for i in cartan_type.classical().index_set()}) # The elements of the classical weyl group, as elements of W W0 = [W.from_reduced_word(w.reduced_word()) for w in self.weyl_group().classical()] for alcove in alcoves: # The translation mapping the center of the # fundamental polygon to polygon indexed by alcove shift = sum(x*v for x,v in zip(alcove, translation_vectors)) shift = W.from_morphism(shift.translation) for w in W0: for i in w.descents(side="right", positive=True): G += alcove_facet(shift * w, i) if alcove_labels: G += alcove_label(w) return plot_options.finalize(G) # In this alternative commented-out implementation, the # alcove picture is constructed directly in the # projection. It only works for rank 2+1 with, but it is # faster; we keep for reference for now. With #12553 # (Cythoned PPL polytopes), the difference is likely to # disappear. If this is confirmed, the code below should be discarded. # # from sage.plot.line import line # translation_vectors = Family({i: translation_factors[i]*plot_options.projection(simple_roots[i]) #                               for i in cartan_type.classical().index_set()}) # # # For each polygon P to be drawn, alcoves_shift contains the translation # # from fundamental polygon to P in the plot coordinate system # def immutable_vector(x): #     # Takes care of possible numerical instabilities #     x = x.numerical_approx(8) #     x.set_immutable() #     return x # # # Construct the fundamental polygon # # The classical group acting on self # W0 = self.weyl_group().classical().list() # # The coordinates of the vertices of the fundamental alcove # fundamental_alcove_rays = Lambda.map(plot_options.intersection_at_level_1) # # The coordinates of the vertices of the fundamental polygon # fundamental_polygon_rays = { #     (i, w): plot_options.projection(w.action(fundamental_alcove_rays[i])) #     for w in W0 #     for i in I #     } # # # Get the center of the polygons # if alcoves is True: #     def neighbors(x): #         return filter(lambda y: plot_options.bounding_box.contains(plot_options.origin_projected+y), #                       [immutable_vector(x+epsilon*t) for t in translation_vectors for epsilon in [-1,1]]) #     alcoves_shift = list(TransitiveIdeal(neighbors, [immutable_vector(plot_options.origin_projected)])) # else: #     alcoves_shift = [sum(x*v for x,v in zip(alcove, translation_vectors)) #                      for alcove in alcoves] # # G = plot_options.empty() # for shift in alcoves_shift: #     # for each center of polygon and each element of classical #     # parabolic subgroup, we have to draw an alcove. #     polygon_center = plot_options.origin_projected + shift # #     for w in W0: #         for i in I: #             facet_indices = [j for j in I if j != i] #             assert len(facet_indices) == 2 #             facet = [fundamental_polygon_rays[j, w] + shift for j in facet_indices] #             # This takes a bit of time; do we really want that feature? #             #if not all(bounding_box_as_polytope.contains(v) for v in facet): #             #    continue #             G += line(facet, #                       rgbcolor = plot_options.color(i), #                       thickness = 2 if i == special_node else 1) def plot_bounding_box(self, **options): r""" Plot the bounding box. INPUT: - **options -- Plotting options This is mostly for testing purposes. .. SEEALSO:: - :meth:plot for a description of the plotting options - :ref:sage.combinat.root_system.plot for a tutorial on root system plotting EXAMPLES:: sage: L = RootSystem(["A",2,1]).ambient_space() sage: L.plot_bounding_box() TESTS:: sage: list(L.plot_bounding_box()) [Polygon defined by 4 points] """ plot_options = self.plot_parse_options(**options) return plot_options.bounding_box.plot(color="gray", alpha=0.5, wireframe=False) def plot_alcove_walk(self, word, start=None, foldings=None, color ="orange", **options): r""" Plot an alcove walk. INPUT: - word -- a list of elements of the index set - foldings -- a list of booleans or None (default: None) - start -- an element of this space (default: None for \rho) - **options -- plotting options .. SEEALSO:: - :meth:plot for a description of the plotting options - :ref:sage.combinat.root_system.plot for a tutorial on root system plotting EXAMPLES: An alcove walk of 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: p = L.plot_alcoves(bounding_box=5)           # long time (5s) sage: p += L.plot_alcove_walk(w1)                  # long time sage: p                                            # long time The same plot with another alcove walk:: 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_alcove_walk(w2, color="orange")  # long time And another with some foldings:: sage: L.plot_alcoves(bounding_box=3) + \ ...   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="green")            # long time (3s) TESTS:: sage: from sage.combinat.root_system.plot import plot_expose sage: L = RootSystem(["A",2,1]).weight_space() 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="green", ...                          start=L.rho()) sage: plot_expose(p) Arrow from (1.0,1.0) to (2.0,2.0) Arrow from (2.0,2.0) to (1.0,4.0) Line defined by 2 points: [(1.0, 4.0), (1.5, 4.5)] Arrow from (1.5,4.5) to (1.0,4.0) Arrow from (1.0,4.0) to (-1.0,5.0) Arrow from (-1.0,5.0) to (-2.0,7.0) Arrow from (-2.0,7.0) to (-1.0,8.0) Line defined by 2 points: [(-1.0, 8.0), (-1.5, 9.0)] Arrow from (-1.5,9.0) to (-1.0,8.0) Arrow from (-1.0,8.0) to (1.0,7.0) Line defined by 2 points: [(1.0, 7.0), (1.5, 6.0)] Arrow from (1.5,6.0) to (1.0,7.0) Arrow from (1.0,7.0) to (2.0,8.0) """ from sage.plot.line import line from sage.plot.arrow import arrow plot_options = self.plot_parse_options(**options) W = self.weyl_group() s = W.simple_reflections() if start is None: start = plot_options.intersection_at_level_1(self.rho()) if foldings is None: foldings = [False] * len(word) w = W.one() source  = plot_options.projection(start) G = plot_options.empty() for (i, folding) in zip(word, foldings): w = w * s[i] target = plot_options.projection(w.action(start)) if folding: middle = (source+target)/2 G += line ([source, middle], rgbcolor=color) G += arrow(middle, source, rgbcolor=color) # reset w w = w * s[i] else: G += arrow(source, target, rgbcolor=color) source=target return G ########################################################################## class ElementMethods: @abstract_method class RootLatticeRealizations(Category_o if i not in index_set: return False return True
• ## sage/combinat/root_system/root_system.py

diff --git a/sage/combinat/root_system/root_system.py b/sage/combinat/root_system/root_system.py
 a Quickref Documentation ------------- - :mod:sage.combinat.root_system.root_system    -- This current overview - :ref:sage.combinat.root_system.root_system    -- This current overview - :class:CartanType                             -- An introduction to Cartan types - :class:RootSystem                             -- An introduction to root systems - :ref:sage.combinat.root_system.plot           -- A root system visualization tutorial - The Lie Methods and Related Combinatorics thematic tutorial See also -------- - :class:CoxeterGroups, :class:WeylGroups, ...-- The categories of Coxeter and Weyl groups - :mod:sage.combinat.crystals.crystals          -- An introduction to crystals - :ref:sage.combinat.crystals.crystals          -- An introduction to crystals - :mod:.type_A, :mod:.type_B_affine, ...      -- Type specific root system data """ class RootSystem(UniqueRepresentation, S sage: L = RootSystem(["A",2,1]).ambient_space(); L Ambient space of the Root system of type ['A', 2, 1] Define the "identity" by an appropriate vector at level -3:: Define the "identity" by an appropriate vector at level -3:: sage: e = L.basis(); Lambda = L.fundamental_weights() sage: id = e[0] + 2*e[1] + 3*e[2]  - 3*Lambda[0] class RootSystem(UniqueRepresentation, S sage: [L.classical()(s[0].action(x)) for x in S3] [(0, 2, 4), (0, 1, 5), (-1, 1, 6), (-2, 2, 6), (-1, 3, 4), (-2, 3, 5)] We can also plot various components of the ambient spaces:: sage: L = RootSystem(['A',2]).ambient_space() sage: L.plot() For more on plotting, see :ref:sage.combinat.root_system.plot. .. RUBRIC:: Dual root systems The root system is aware of its dual root system:: class RootSystem(UniqueRepresentation, S sage: R.dual Dual of root system of type ['B', 3] R.dual is really the root system of type C_3:: R.dual is really the root system of type C_3:: sage: R.dual.cartan_type() ['C', 3] class RootSystem(UniqueRepresentation, S return None return AmbientSpace(self, base_ring) def coambient_space(self, base_ring=QQ): r""" Return the coambient space for this root system. This is the ambient space of the dual root system. .. SEEALSO:: - :meth:ambient_space EXAMPLES:: sage: L = RootSystem(["B",2]).ambient_space(); L Ambient space of the Root system of type ['B', 2] sage: coL = RootSystem(["B",2]).coambient_space(); coL Coambient space of the Root system of type ['B', 2] The roots and coroots are interchanged:: sage: coL.simple_roots() Finite family {1: (1, -1), 2: (0, 2)} sage: L.simple_coroots() Finite family {1: (1, -1), 2: (0, 2)} sage: coL.simple_coroots() Finite family {1: (1, -1), 2: (0, 1)} sage: L.simple_roots() Finite family {1: (1, -1), 2: (0, 1)} """ return self.dual.ambient_space(base_ring) def WeylDim(ct, coeffs): """
• ## sage/combinat/root_system/type_A.py

diff --git a/sage/combinat/root_system/type_A.py b/sage/combinat/root_system/type_A.py
 a Root system data for type A #                  http://www.gnu.org/licenses/ #***************************************************************************** from sage.rings.all import ZZ from sage.combinat.root_system.root_lattice_realizations import RootLatticeRealizations import ambient_space class AmbientSpace(ambient_space.AmbientSpace): class AmbientSpace(ambient_space.Ambient given, returns (k, ... ,k), the k-th power of the determinant. EXAMPLES: EXAMPLES:: sage: e = RootSystem(['A',3]).ambient_space() sage: e.det(1/2) (1/2, 1/2, 1/2, 1/2) """ return self.sum(self.monomial(j)*k for j in range(self.n)) __doc__ += """ By default, this ambient space uses the barycentric projection for plotting:: sage: L = RootSystem(["A",2]).ambient_space() sage: e = L.basis() sage: L._plot_projection(e[0]) (1/2, 989/1142) sage: L._plot_projection(e[1]) (-1, 0) sage: L._plot_projection(e[2]) (1/2, -989/1142) sage: L = RootSystem(["A",3]).ambient_space() sage: l = L.an_element(); l (2, 2, 3, 0) sage: L._plot_projection(l) (0, -1121/1189, 7/3) .. SEEALSO:: - :meth:sage.combinat.root_system.root_lattice_realizations.RootLatticeRealizations.ParentMethods._plot_projection """ _plot_projection = RootLatticeRealizations.ParentMethods.__dict__['_plot_projection_barycentric'] from cartan_type import CartanType_standard_finite, CartanType_simply_laced, CartanType_simple class CartanType(CartanType_standard_finite, CartanType_simply_laced, CartanType_simple):
• ## sage/combinat/root_system/type_G.py

diff --git a/sage/combinat/root_system/type_G.py b/sage/combinat/root_system/type_G.py
 a Root system data for type G #***************************************************************************** import ambient_space from sage.sets.family import Family from sage.combinat.root_system.root_lattice_realizations import RootLatticeRealizations class AmbientSpace(ambient_space.AmbientSpace): """ EXAMPLES:: class AmbientSpace(ambient_space.Ambient return Family({ 1: self([1,0,-1]), 2: self([2,-1,-1])}) __doc__ += """ By default, this ambient space uses the barycentric projection for plotting:: sage: L = RootSystem(["G",2]).ambient_space() sage: e = L.basis() sage: L._plot_projection(e[0]) (1/2, 989/1142) sage: L._plot_projection(e[1]) (-1, 0) sage: L._plot_projection(e[2]) (1/2, -989/1142) sage: L = RootSystem(["A",3]).ambient_space() sage: l = L.an_element(); l (2, 2, 3, 0) sage: L._plot_projection(l) (0, -1121/1189, 7/3) .. SEEALSO:: - :meth:sage.combinat.root_system.root_lattice_realizations.RootLatticeRealizations.ParentMethods._plot_projection """ _plot_projection = RootLatticeRealizations.ParentMethods.__dict__['_plot_projection_barycentric'] from cartan_type import CartanType_standard_finite, CartanType_simple, CartanType_crystalographic class CartanType(CartanType_standard_finite, CartanType_simple, CartanType_crystalographic): def __init__(self):
• ## sage/combinat/root_system/type_affine.py

diff --git a/sage/combinat/root_system/type_affine.py b/sage/combinat/root_system/type_affine.py
 a class AmbientSpace(CombinatorialFreeModu """ return self.simple_root(i).associated_coroot() def coroot_lattice(self): """ EXAMPLES:: sage: RootSystem(["A",3,1]).ambient_lattice().coroot_lattice() Ambient lattice of the Root system of type ['A', 3, 1] .. TODO:: Factor out this code with the classical ambient space. """ return self def _plot_projection(self, x): """ Implements the default projection to be used for plots For affine ambient spaces, the default implementation is to project onto the classical coordinates according to the default projection for the classical ambient space, while keeping an extra coordinate for the coefficient of \delta^\vee to keep the level information. .. SEEALSO:: :meth:sage.combinat.root_system.root_lattice_realizations.RootLatticeRealizations._plot_projection EXAMPLES:: sage: L = RootSystem(["B",2,1]).ambient_space() sage: e = L.basis() sage: L._plot_projection(e[0]) (1, 0, 0) sage: L._plot_projection(e[1]) (0, 1, 0) sage: L._plot_projection(e["delta"]) (0, 0, 0) sage: L._plot_projection(e["deltacheck"]) (0, 0, 1) sage: L = RootSystem(["A",2,1]).ambient_space() sage: e = L.basis() sage: L._plot_projection(e[0]) (1/2, 989/1142, 0) sage: L._plot_projection(e[1]) (-1, 0, 0) sage: L._plot_projection(e["delta"]) (0, 0, 0) sage: L._plot_projection(e["deltacheck"]) (0, 0, 1) """ from sage.modules.free_module_element import vector classical = self.classical() # Any better way to concatenate two vectors? return vector(list(vector(classical._plot_projection(classical(x)))) + [x["deltacheck"]]) class Element(CombinatorialFreeModule.Element): def inner_product(self, other):
• ## sage/combinat/root_system/type_dual.py

diff --git a/sage/combinat/root_system/type_dual.py b/sage/combinat/root_system/type_dual.py
 a Root system data for dual Cartan types #***************************************************************************** from sage.misc.misc import attrcall from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute from sage.structure.unique_representation import UniqueRepresentation from sage.structure.sage_object import SageObject from sage.combinat.root_system.cartan_type import CartanType_crystalographic, CartanType_finite, CartanType_affine, CartanType_simple from sage.combinat.root_system.root_lattice_realizations import RootLatticeRealizations import sage import ambient_space class AmbientSpace(ambient_space.Ambient sage: TestSuite(L).run() """ @lazy_attribute def _dual_space(self): """ The dual of this ambient space. EXAMPLES:: sage: L = CartanType(["F",4]).dual().root_system().ambient_space(); L Ambient space of the Root system of type ['F', 4]^* sage: L._dual_space Ambient space of the Root system of type ['F', 4] The basic data for this space will be fetched from the dual space:: sage: L._dual_space.simple_root(1) (0, 1, -1, 0) sage: L.simple_root(1) (0, 1, -1, 0) """ K = self.base_ring() return self.cartan_type().dual().root_system().ambient_space(K) #return self.root_system.dual.ambient_space() def dimension(self): """ Return the dimension of this ambient space. class AmbientSpace(ambient_space.Ambient sage: F4.dual().root_system().ambient_space().simple_root(1) (0, 1, -1, 0) """ # Can't yet use _dual_space for the base ring is not yet initialized return self.root_system.dual.ambient_space().dimension() @cached_method class AmbientSpace(ambient_space.Ambient sage: F4.root_system().ambient_space().simple_coroots() Finite family {1: (0, 1, -1, 0), 2: (0, 0, 1, -1), 3: (0, 0, 0, 2), 4: (1, -1, -1, -1)} """ K = self.base_ring() dual_coroot = self.cartan_type().dual().root_system().ambient_space(K).simple_coroot(i) dual_coroot = self._dual_space.simple_coroot(i) return self.sum_of_terms(dual_coroot) @cached_method class AmbientSpace(ambient_space.Ambient """ return self.fundamental_weights_from_simple_roots() @lazy_attribute def _plot_projection(self): """ A hack so that if an ambient space uses barycentric projection, then so does its dual EXAMPLES:: sage: L = CartanType(["G",2]).dual().root_system().ambient_space() sage: L._plot_projection == L._plot_projection_barycentric True sage: L = RootSystem(["G",2]).coambient_space() sage: L._plot_projection == L._plot_projection_barycentric True """ dual_space = self.cartan_type().dual().root_system().ambient_space(self.base_ring()) if dual_space._plot_projection == dual_space._plot_projection_barycentric: return self._plot_projection_barycentric else: RootLatticeRealizations.ParentMethods.__dict__["_plot_projection"] class CartanType_finite(CartanType, sage.combinat.root_system.cartan_type.CartanType_finite): AmbientSpace = AmbientSpace
• ## sage/combinat/root_system/weight_lattice_realizations.py

diff --git a/sage/combinat/root_system/weight_lattice_realizations.py b/sage/combinat/root_system/weight_lattice_realizations.py
 a class WeightLatticeRealizations(Category d = prod([ rho.dot_product(x) for x in self.positive_roots()]) from sage.rings.integer import Integer return Integer(n/d) def plot(self, size=[[0],[0]], projection='usual', simple_roots=True, fundamental_weights=True, alcovewalks=[]): r""" Return a graphics object built from a space of weight(space/lattice). There is a different technic to plot if the Cartan type is affine or not. The graphics returned is a Graphics object. This function is experimental, and is subject to short term evolutions. EXAMPLES:: By default, the plot returned has no axes and the ratio between axes is 1. sage: G = RootSystem(['C',2]).weight_lattice().plot() sage: G.axes(True) sage: G.set_aspect_ratio(2) For a non affine Cartan type, the plot method work for type with 2 generators, it will draw the hyperlane(line for this dimension) accrow the fundamentals weights. sage: G = RootSystem(['A',2]).weight_lattice().plot() sage: G = RootSystem(['B',2]).weight_lattice().plot() sage: G = RootSystem(['G',2]).weight_lattice().plot() The plot returned has a size of one fundamental polygon by default. We can ask plot to give a bigger plot by using the argument size sage: G = RootSystem(['G',2,1]).weight_space().plot(size = [[0..1],[-1..1]]) sage: G = RootSystem(['A',2,1]).weight_space().plot(size = [[-1..1],[-1..1]]) A very important argument is the projection which will draw the plot. There are some usual projections is this method. If you want to draw in the plane a very special Cartan type, Sage will ask you to specify the projection. The projection is a matrix over a ring. In practice, calcul over float is a good way to draw. sage: L = RootSystem(['A',2,1]).weight_space() sage: G = L.plot(projection=matrix(RR, [[0,0.5,-0.5],[0,0.866,0.866]])) sage: G = RootSystem(['C',2,1]).weight_space().plot() By default, the plot method draw the simple roots, this can be disabled by setting the argument simple_roots=False sage: G = RootSystem(['A',2]).weight_space().plot(simple_roots=False) By default, the plot method draw the fundamental weights,this can be disabled by setting the argument fundamental_weights=False sage: G = RootSystem(['A',2]).weight_space().plot(fundamental_weights=False, simple_roots=False) There is in a plot an argument to draw alcoves walks. The good way to do this is to use the crystals theory. the plot method contains only the drawing part... sage: L = RootSystem(['A',2,1]).weight_space() sage: G = L.plot(size=[[-1..1],[-1..1]],alcovewalks=[[0,2,0,1,2,1,2,0,2,1]]) """ from sage.plot.all import Graphics from sage.plot.line import line from cartan_type import CartanType from sage.matrix.constructor import matrix from sage.rings.all import QQ, RR from sage.plot.arrow import arrow from sage.plot.point import point # We begin with an empty plot G G = Graphics() ct = self.cartan_type() n = ct.n # Define a set of colors # TODO : Colors in option ? colors=[(0,1,0),(1,0,0),(0,0,1),(1,1,0),(0,1,1),(1,0,1)] # plot the affine types: if ct.is_affine(): # Check the projection # TODO : try to have usual_projection for main plotable types if projection == 'usual': if ct == CartanType(['A',2,1]): projection = matrix(RR, [[0,0.5,-0.5],[0,0.866,0.866]]) elif ct == CartanType(['C',2,1]): projection = matrix(QQ, [[0,1,1],[0,0,1]]) elif ct == CartanType(['G',2,1]): projection = matrix(RR, [[0,0.5,0],[0,0.866,1.732]]) else: raise 'There is no usual projection for this Cartan type, you have to give one in argument' assert(n + 1 == projection.ncols()) assert(2 == projection.nrows()) # Check the size is correct with the lattice assert(len(size) == n) # Select the center of the translated fundamental polygon to plot translation_factors = ct.translation_factors() simple_roots = self.simple_roots() translation_vectors = [translation_factors[i]*simple_roots[i] for i in ct.classical().index_set()] initial = [[]] for i in range(n): prod_list = [] for elem in size[i]: for partial_list in initial: prod_list.append( [elem]+partial_list ); initial = prod_list; part_lattice = [] for combinaison in prod_list: elem_lattice = self.zero() for i in range(n): elem_lattice = elem_lattice + combinaison[i]*translation_vectors[i] part_lattice.append(elem_lattice) # Get the vertices of the fundamental alcove fundamental_weights = self.fundamental_weights() vertices = map(lambda x: (1/x.level())*x, fundamental_weights.list()) # Recup the group which act on the fundamental polygon classical = self.weyl_group().classical() for center in part_lattice: for w in classical: # for each center of polygon and each element of classical # parabolic subgroup, we have to draw an alcove. #first, iterate over pairs of fundamental weights, drawing lines border of polygons: for i in range(1,n+1): for j in range(i+1,n+1): p1=projection*((w.action(vertices[i])).to_vector() + center.to_vector()) p2=projection*((w.action(vertices[j])).to_vector() + center.to_vector()) G+=line([p1,p2],rgbcolor=(0,0,0),thickness=2) #next, get all lines from point to a fundamental weight, that separe different #chanber in a same polygon (important: associate a color with a fundamental weight) pcenter = projection*(center.to_vector()) for i in range(1,n+1): p3=projection*((w.action(vertices[i])).to_vector() + center.to_vector()) G+=line([p3,pcenter], rgbcolor=colors[n-i+1]) #Draw alcovewalks #FIXME : The good way to draw this is to use the alcoves walks works made in Cristals #The code here just draw like example and import the good things. rho = (1/self.rho().level())*self.rho() W = self.weyl_group() for walk in alcovewalks: target = W.from_reduced_word(walk).action(rho) for i in range(len(walk)): walk.pop() origin = W.from_reduced_word(walk).action(rho) G+=arrow(projection*(origin.to_vector()),projection*(target.to_vector()), rgbcolor=(0.6,0,0.6), width=1, arrowsize=5) target = origin else: # non affine plot # Check the projection # TODO : try to have usual_projection for main plotable types if projection == 'usual': if ct == CartanType(['A',2]): projection = matrix(RR, [[0.5,-0.5],[0.866,0.866]]) elif ct == CartanType(['B',2]): projection = matrix(QQ, [[1,0],[1,1]]) elif ct == CartanType(['C',2]): projection = matrix(QQ, [[1,1],[0,1]]) elif ct == CartanType(['G',2]): projection = matrix(RR, [[0.5,0],[0.866,1.732]]) else: raise 'There is no usual projection for this Cartan type, you have to give one in argument' # Get the fundamental weights fundamental_weights = self.fundamental_weights() WeylGroup = self.weyl_group() #Draw not the alcove but the cones delimited by the hyperplanes #The size of the line depend of the fundamental weights. pcenter = projection*(self.zero().to_vector()) for w in WeylGroup: for i in range(1,n+1): p3=3*projection*((w.action(fundamental_weights[i])).to_vector()) G+=line([p3,pcenter], rgbcolor=colors[n-i+1]) #Draw the simple roots if simple_roots: SimpleRoots = self.simple_roots() if ct.is_affine(): G+=arrow((0,0), projection*(SimpleRoots[0].to_vector()), rgbcolor=(0,0,0)) for j in range(1,n+1): G+=arrow((0,0),projection*(SimpleRoots[j].to_vector()), rgbcolor=colors[j]) #Draw the fundamental weights if fundamental_weights: FundWeight = self.fundamental_weights() for j in range(1,n+1): G+=point(projection*(FundWeight[j].to_vector()), rgbcolor=colors[j], pointsize=60) G.set_aspect_ratio(1) G.axes(False) return G
• ## sage/doctest/sources.py

diff --git a/sage/doctest/sources.py b/sage/doctest/sources.py
 a class FileDocTestSource(DocTestSource): There are 18 tests in sage/combinat/partition.py that are not being run There are 12 tests in sage/combinat/tableau.py that are not being run There are 15 tests in sage/combinat/root_system/cartan_type.py that are not being run There are 8 tests in sage/combinat/root_system/type_A.py that are not being run There are 8 tests in sage/combinat/root_system/type_G.py that are not being run There are 3 unexpected tests being run in sage/doctest/parsing.py There are 1 unexpected tests being run in sage/doctest/reporting.py There are 9 tests in sage/graphs/graph_plot.py that are not being run