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 __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], <bound method WeightSpace_with_category._plot_projection of 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], <bound method AmbientSpace_with_category._plot_projection of 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], <bound method AmbientSpace_with_category._plot_projection of Ambient space of the Root system of type ['B', 2]>] sage: options = L.plot_parse_options(affine=False) sage: options._projections [<bound method AmbientSpace_with_category._plot_projection of Ambient space of the Root system of type ['B', 2, 1]>] sage: options.dimension 3 sage: options = L.plot_parse_options(affine=False, projection='barycentric') sage: options._projections [<bound method AmbientSpace_with_category._plot_projection_barycentric of Ambient space of the Root system of type ['B', 2, 1]>] 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))
class PlotOptions: r""" A class for plotting options for root lattice realizations. .. SEEALSO:: - :meth:`RootLatticeRealizations.ParentMethods.plot() <sage.combinat.root_system.root_lattice_realizations.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], <bound method WeightSpace_with_category._plot_projection of 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], <bound method AmbientSpace_with_category._plot_projection of 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], <bound method AmbientSpace_with_category._plot_projection of Ambient space of the Root system of type ['B', 2]>] sage: options = L.plot_parse_options(affine=False) sage: options._projections [<bound method AmbientSpace_with_category._plot_projection of Ambient space of the Root system of type ['B', 2, 1]>] sage: options.dimension 3 sage: options = L.plot_parse_options(affine=False, projection='barycentric') sage: options._projections [<bound method AmbientSpace_with_category._plot_projection_barycentric of Ambient space of the Root system of type ['B', 2, 1]>] 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() <Transform translation='0 1 2'> <Shape><Text string='coucou' solid='true'/><Appearance><Material diffuseColor='0.0 0.0 0.0' shininess='1' specularColor='0.0 0.0 0.0'/></Appearance></Shape> <BLANKLINE> </Transform> """ 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)) <class 'sage.plot.plot3d.base.Graphics3dGroup'> sage: options = L.plot_parse_options() sage: type(options.finalize(0)) <class 'sage.plot.graphics.Graphics'> sage: list(options.finalize(0)) [] """ from sage.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: print H.description() Text '$H_{\alpha^\vee_{1}}$' at the point (0.0,3.15) Line defined by 2 points: [(0.0, 3.0), (0.0, -3.0)] :: 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 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 __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], <bound method WeightSpace_with_category._plot_projection of 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], <bound method AmbientSpace_with_category._plot_projection of 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], <bound method AmbientSpace_with_category._plot_projection of Ambient space of the Root system of type ['B', 2]>] sage: options = L.plot_parse_options(affine=False) sage: options._projections [<bound method AmbientSpace_with_category._plot_projection of Ambient space of the Root system of type ['B', 2, 1]>] sage: options.dimension 3 sage: options = L.plot_parse_options(affine=False, projection='barycentric') sage: options._projections [<bound method AmbientSpace_with_category._plot_projection_barycentric of Ambient space of the Root system of type ['B', 2, 1]>] 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))
class PlotOptions: r""" A class for plotting options for root lattice realizations. .. SEEALSO:: - :meth:`RootLatticeRealizations.ParentMethods.plot() <sage.combinat.root_system.root_lattice_realizations.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], <bound method WeightSpace_with_category._plot_projection of 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], <bound method AmbientSpace_with_category._plot_projection of 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], <bound method AmbientSpace_with_category._plot_projection of Ambient space of the Root system of type ['B', 2]>] sage: options = L.plot_parse_options(affine=False) sage: options._projections [<bound method AmbientSpace_with_category._plot_projection of Ambient space of the Root system of type ['B', 2, 1]>] sage: options.dimension 3 sage: options = L.plot_parse_options(affine=False, projection='barycentric') sage: options._projections [<bound method AmbientSpace_with_category._plot_projection_barycentric of Ambient space of the Root system of type ['B', 2, 1]>] 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() <Transform translation='0 1 2'> <Shape><Text string='coucou' solid='true'/><Appearance><Material diffuseColor='0.0 0.0 0.0' shininess='1' specularColor='0.0 0.0 0.0'/></Appearance></Shape> <BLANKLINE> </Transform> """ 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)) <class 'sage.plot.plot3d.base.Graphics3dGroup'> sage: options = L.plot_parse_options() sage: type(options.finalize(0)) <class 'sage.plot.graphics.Graphics'> sage: list(options.finalize(0)) [] """ from sage.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: print H.description() Text '$H_{\alpha^\vee_{1}}$' at the point (0.0,3.15) Line defined by 2 points: [(0.0, 3.0), (0.0, -3.0)] :: 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)