Beispiel #1
0
    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()
Beispiel #2
0
    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))
Beispiel #3
0
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)
Beispiel #4
0
    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()
Beispiel #5
0
    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))
Beispiel #6
0
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)