r"""
.. jupyter-execute::
:hide-code:
# Allow jupyter-execute blocks in this module to contain doctests
import jupyter_doctest_tweaks
EXAMPLES:
.. jupyter-execute::
sage: import flatsurf
sage: flatsurf.translation_surfaces.veech_2n_gon(4).plot()
...Graphics object consisting of 18 graphics primitives
"""
# ****************************************************************************
# This file is part of sage-flatsurf.
#
# Copyright (C) 2013-2019 Vincent Delecroix <20100.delecroix@gmail.com>
# 2013-2019 W. Patrick Hooper <wphooper@gmail.com>
# 2022-2023 Julian Rüth <julian.rueth@fsfe.org>
#
# sage-flatsurf is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# sage-flatsurf is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with sage-flatsurf. If not, see <https://www.gnu.org/licenses/>.
# ****************************************************************************
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.modules.free_module_element import vector
[docs]
class GraphicalSurface:
r"""
This class manages the rendering of a SimilaritySurface.
This class essentially consists of a collection of GraphicalPolygons which
control how individual polygons are positioned. In addition, this class
stores options which are passed to the polygons when they are rendered.
Some setup features set in the constructor and can be set again later via
:meth:`process_options`.
The basic tasks of the class are to render the polygons, edges and labels.
To customize a rendering, it is useful to know something about how this
class works. (Apologies!)
There are attributes which control whether or not certain objects are
rendered, namely:
- ``will_plot_polygons`` -- Whether to plot polygons which are right-side up.
- ``will_plot_upside_down_polygons`` -- Whether to plot polygons which are
upside down. Defaults to False.
- ``will_plot_polygon_labels`` -- Whether to plot polygon labels.
- ``will_plot_edges`` -- If this is False then no edges will be plotted.
- ``will_plot_non_adjacent_edges`` = Whether to plot polygon edges which are
not adjacent to the edge it is glued to.
- ``will_plot_adjacent_edges`` -- Whether to plot polygon edges which are
adjacent to the polygon they are glued to.
- ``will_plot_self_glued_edges`` -- Whether to plot polygon edges which are
glued to themselves.
- ``will_plot_edge_labels`` -- Whether to plot polygon edges labels.
- ``will_plot_zero_flags`` -- Whether to plot line segments from the
baricenter to the zero vertex of each polygon. Useful in working out
vertex and edge labels. Defaults to False.
The :meth:`plot` method calls some other built in methods: :meth:`plot_polygon`,
:meth:`plot_polygon_label`, :meth:`plot_edge` and :meth:`plot_edge_label`. These in turn
call methods in :class:`~.polygon.GraphicalPolygon`.
- ``polygon_options`` -- Options passed to :meth:`.polygon.GraphicalPolygon.plot_polygon` when
plotting a polygon right-side up.
- ``upside_down_polygon_options`` -- Options passed to
:meth:`.polygon.GraphicalPolygon.plot_polygon` when plotting a polygon upside-down.
- ``polygon_label_options`` -- Options passed to :meth:`.polygon.GraphicalPolygon.plot_label`
when plotting a polygon label.
- ``non_adjacent_edge_options`` -- Options passed to
:meth:`.polygon.GraphicalPolygon.plot_edge` when plotting a polygon edge which is not
adjacent to the edge it is glued to.
- ``adjacent_edge_options`` -- Options passed to
:meth:`.polygon.GraphicalPolygon.plot_edge` when plotting a polygon edge which is
adjacent to the edge it is glued to.
- ``self_glued_edge_options`` -- Options passed to :meth:`.polygon.GraphicalPolygon.plot_edge`
when plotting a polygon edge which is glued to itself.
- ``edge_label_options`` -- Options passed to :meth:`.polygon.GraphicalPolygon.plot_edge_label`
when plotting a edge label.
- ``zero_flag_options`` -- Options passed to
:meth:`.polygon.GraphicalPolygon.plot_zero_flag` when plotting a zero_flag.
INPUT:
- ``similarity_surface`` -- a similarity surface
- ``polygon_labels`` -- a boolean (default ``True``) whether the label
of polygons are displayed
- ``edge_labels`` -- option to control the display of edge labels. It
can be one of
- ``False`` or ``None`` for no labels
- ``'gluings'`` -- to put on each side of each non-adjacent edge, the
name of the polygon to which it is glued
- ``'number'`` -- to put on each side of each edge the number of the
edge
- ``'gluings and number'`` -- full information
- ``'letter'`` -- add matching letters to glued edges in an arbitrary way
- ``adjacencies`` -- a list of pairs ``(p,e)`` to be used to set
adjacencies of polygons.
- ``default_position_function`` -- a function mapping polygon labels to
similarities describing the position of the corresponding polygon.
If adjacencies is not defined and the surface is finite, make_all_visible()
is called to make all polygons visible.
EXAMPLES:
.. jupyter-execute::
sage: from flatsurf import similarity_surfaces
sage: from flatsurf.graphical.surface import GraphicalSurface
sage: s = similarity_surfaces.example()
sage: gs = GraphicalSurface(s)
sage: gs.polygon_options["color"]="red"
sage: gs.plot()
...Graphics object consisting of 13 graphics primitives
TESTS:
Verify that surfaces with boundary can be plotted::
sage: from flatsurf import Polygon, MutableOrientedSimilaritySurface
sage: S = MutableOrientedSimilaritySurface(QQ)
sage: S.add_polygon(Polygon(vertices=[(0,0), (-1, -1), (1,0)]))
0
sage: S.add_polygon(Polygon(vertices=[(0,0), (0, 1), (-1,-1)]))
1
sage: S.glue((0, 0), (1, 2))
sage: S.set_immutable()
sage: S
Translation Surface with boundary built from 2 triangles
sage: S.plot()
Graphics object consisting of 9 graphics primitives
"""
def __init__(
self,
surface,
adjacencies=None,
polygon_labels=True,
edge_labels="gluings",
default_position_function=None,
):
self._ss = surface
self._default_position_function = default_position_function
self._polygons = {}
if not self._ss.is_connected():
raise NotImplementedError("cannot plot disconnected surfaces yet")
self._visible = set(self._ss.roots())
if adjacencies is None:
if self._ss.is_finite_type():
self.make_all_visible()
self._edge_labels = None
self.will_plot_polygons = True
r"""
Whether to plot polygons which are right-side up.
"""
self.polygon_options = {"color": "lightgray"}
r"""Options passed to :meth:`.polygon.GraphicalPolygon.plot_polygon` when plotting a polygon right-side up."""
self.will_plot_upside_down_polygons = False
r"""
Whether to plot polygons which are upside down
"""
self.upside_down_polygon_options = {"color": "lightgray", "zorder": -1}
r"""Options passed to :meth:`.polygon.GraphicalPolygon.plot_polygon` when plotting a polygon upside-down."""
self.will_plot_polygon_labels = True
r"""
Whether to plot polygon labels.
"""
self.polygon_label_options = {
"color": "black",
"vertical_alignment": "center",
"horizontal_alignment": "center",
}
r"""Options passed to :meth:`.polygon.GraphicalPolygon.plot_label` when plotting a polygon label."""
self.will_plot_edges = True
r"""
If this is False then no edges will be plotted.
"""
self.will_plot_non_adjacent_edges = True
r"""
Whether to plot polygon edges which are not adjacent to the edge it is glued to.
"""
self.non_adjacent_edge_options = {"color": "blue"}
r"""Options passed to :meth:`.polygon.GraphicalPolygon.plot_edge` when plotting a polygon edge
which is not adjacent to the edge it is glued to."""
self.will_plot_adjacent_edges = True
r"""
Whether to plot polygon edges which are adjacent to the polygon they are glued to.
"""
self.adjacent_edge_options = {"color": "blue", "linestyle": ":"}
r"""Options passed to :meth:`.polygon.GraphicalPolygon.plot_edge`
when plotting a polygon edge which is adjacent to the edge it is glued to."""
self.will_plot_self_glued_edges = True
r"""
Whether to plot polygon edges which are glued to themselves.
"""
self.self_glued_edge_options = {"color": "red"}
r"""Options passed to :meth:`.polygon.GraphicalPolygon.plot_edge` when plotting a polygon edge
which is glued to itself."""
self.will_plot_edge_labels = True
r"""
Whether to plot polygon edges which are not adjacent to the polygon they are glued to.
"""
self.edge_label_options = {"color": "blue"}
r"""Options passed to :meth:`.polygon.GraphicalPolygon.plot_edge_label` when plotting a polygon label."""
self.will_plot_zero_flags = False
r"""
Whether to plot line segments from the baricenter to the zero vertex of each polygon.
"""
self.zero_flag_options = {"color": "green", "thickness": 0.5}
r"""Options passed to :meth:`.polygon.GraphicalPolygon.plot_zero_flag` when plotting a zero_flag."""
self.process_options(
adjacencies=adjacencies,
polygon_labels=polygon_labels,
edge_labels=edge_labels,
)
[docs]
def process_options(
self,
adjacencies=None,
polygon_labels=None,
edge_labels=None,
default_position_function=None,
):
r"""
Process the options listed as if the graphical_surface was first
created.
INPUT:
Consult :class:`GraphicalSurface` for the possible arguments.
TESTS::
sage: from flatsurf import translation_surfaces
sage: c = translation_surfaces.chamanara(1/2)
sage: gs = c.graphical_surface()
sage: gs.process_options(edge_labels='hey')
Traceback (most recent call last):
...
ValueError: invalid value for edge_labels (='hey')
"""
if adjacencies is not None:
for p, e in adjacencies:
self.make_adjacent(p, e)
if polygon_labels is not None:
if not isinstance(polygon_labels, bool):
raise ValueError("polygon_labels must be True, False or None.")
self.will_plot_polygon_labels = polygon_labels
if edge_labels is not None:
if edge_labels is True:
edge_labels = "gluings"
if edge_labels is False:
self._edge_labels = None
self.will_plot_edge_labels = False
elif edge_labels in ["gluings", "number", "gluings and number", "letter"]:
self._edge_labels = edge_labels
self.will_plot_edge_labels = True
else:
raise ValueError(
"invalid value for edge_labels (={!r})".format(edge_labels)
)
if default_position_function is not None:
self._default_position_function = default_position_function
[docs]
def copy(self):
r"""
Make a copy of this GraphicalSurface.
EXAMPLES::
sage: from flatsurf import translation_surfaces
sage: s = translation_surfaces.octagon_and_squares()
sage: gs = s.graphical_surface()
sage: gs.will_plot_zero_flags = True
sage: gs.graphical_polygon(1).transformation()
(x, y) |-> (x + 2, y)
sage: gs.make_adjacent(0,2)
sage: gs.graphical_polygon(1).transformation()
(x, y) |-> (x + (a + 4), y + (a + 2))
sage: gs.polygon_options["color"]="yellow"
sage: gs2 = gs.copy()
sage: gs2 == gs
False
sage: gs2.will_plot_zero_flags
True
sage: gs2.graphical_polygon(1).transformation()
(x, y) |-> (x + (a + 4), y + (a + 2))
sage: gs2.polygon_options
{'color': 'yellow'}
"""
gs = GraphicalSurface(
self.get_surface(),
default_position_function=self._default_position_function,
)
# Copy plot options
gs.will_plot_polygons = self.will_plot_polygons
gs.polygon_options = dict(self.polygon_options)
gs.will_plot_upside_down_polygons = self.will_plot_upside_down_polygons
gs.upside_down_polygon_options = dict(self.upside_down_polygon_options)
gs.will_plot_polygon_labels = self.will_plot_polygon_labels
gs.polygon_label_options = dict(self.polygon_label_options)
gs.will_plot_edges = self.will_plot_edges
gs.will_plot_non_adjacent_edges = self.will_plot_non_adjacent_edges
gs.non_adjacent_edge_options = dict(self.non_adjacent_edge_options)
gs.will_plot_adjacent_edges = self.will_plot_adjacent_edges
gs.adjacent_edge_options = dict(self.adjacent_edge_options)
gs.will_plot_self_glued_edges = self.will_plot_self_glued_edges
gs.self_glued_edge_options = dict(self.self_glued_edge_options)
gs.will_plot_edge_labels = self.will_plot_edge_labels
gs.edge_label_options = dict(self.edge_label_options)
gs.will_plot_zero_flags = self.will_plot_zero_flags
gs.zero_flag_options = dict(self.zero_flag_options)
# Copy polygons and visible set.
gs._polygons = {label: gp.copy() for label, gp in self._polygons.items()}
gs._visible = set(self._visible)
gs._edge_labels = self._edge_labels
return gs
def __repr__(self):
return "Graphical representation of {!r}".format(self._ss)
[docs]
def visible(self):
r"""
Return a copy of the set of visible labels.
"""
return set(self._visible)
[docs]
def is_visible(self, label):
r"""
Return whether the polygon with the given label is marked as visible.
"""
return label in self._visible
[docs]
def make_visible(self, label):
r"""
Mark the polygon with the given label as visible.
"""
self._visible.add(label)
[docs]
def hide(self, label):
r"""
Mark the polygon with the given label as invisible.
"""
self._visible.remove(label)
[docs]
def make_all_visible(self, adjacent=None, limit=None):
r"""
Attempt to show all invisible polygons by walking over the surface.
INPUT:
- ``adjacent`` -- (default ``None``) whether the newly added polygon are
set to be adjacent or not. Defaults to true unless a default_position_function was
provided.
- ``limit`` -- (default ``None``) maximal number of additional polygons to make visible
EXAMPLES:
.. jupyter-execute::
sage: from flatsurf import similarity_surfaces
sage: s = similarity_surfaces.example()
sage: g = s.graphical_surface()
sage: g.make_all_visible()
sage: g.plot()
...Graphics object consisting of 13 graphics primitives
.. jupyter-execute::
sage: s = similarity_surfaces.example()
sage: g = s.graphical_surface(adjacencies=[])
sage: g.make_all_visible(adjacent=False)
sage: g.plot()
...Graphics object consisting of 16 graphics primitives
"""
if adjacent is None:
adjacent = self._default_position_function is None
if limit is None:
if not self._ss.is_finite_type():
raise NotImplementedError
if adjacent:
for label, poly in zip(self._ss.labels(), self._ss.polygons()):
for e in range(len(poly.vertices())):
opposite_edge = self._ss.opposite_edge(label, e)
if opposite_edge is None:
continue
l2, _ = opposite_edge
if not self.is_visible(l2):
self.make_adjacent(label, e)
else:
from flatsurf.geometry.similarity import SimilarityGroup
T = SimilarityGroup(self._ss.base_ring())
for label in self._ss.labels():
if not self.is_visible(label):
if self._default_position_function is None:
# No reasonable way to display the polygon, so we do this hack:
g = self.graphical_polygon(label)
poly = self._ss.polygon(label)
t = T(
(
QQ(self.xmax() - g.xmin() + 1),
QQ(-(g.ymin() + g.ymax()) / ZZ(2)),
)
)
g.set_transformation(t)
self.make_visible(label)
else:
if limit <= 0:
raise ValueError
if adjacent:
i = 0
for label, poly in zip(self._ss.labels(), self._ss.polygons()):
for e in range(len(poly.vertices())):
l2, e2 = self._ss.opposite_edge(label, e)
if not self.is_visible(l2):
self.make_adjacent(label, e)
i = i + 1
if i >= limit:
return
else:
from flatsurf.geometry.similarity import SimilarityGroup
T = SimilarityGroup(self._ss.base_ring())
i = 0
for label in self._ss.labels():
if not self.is_visible(label):
if self._default_position_function is None:
# No reasonable way to display the polygon, so we do this hack:
g = self.graphical_polygon(label)
poly = self._ss.polygon(label)
t = T(
(
QQ(self.xmax() - g.xmin() + 1),
QQ(-(g.ymin() + g.ymax()) / ZZ(2)),
)
)
g.set_transformation(t)
self.make_visible(label)
i = i + 1
if i >= limit:
return
[docs]
def get_surface(self):
r"""
Return the underlying similarity surface.
"""
return self._ss
[docs]
def xmin(self):
r"""
Return the minimal x-coordinate of a vertex of a visible graphical polygon.
.. TODO::
this should be xmin
"""
return min([self.graphical_polygon(label).xmin() for label in self.visible()])
[docs]
def ymin(self):
r"""
Return the minimal y-coordinate of a vertex of a visible graphical polygon.
"""
return min([self.graphical_polygon(label).ymin() for label in self.visible()])
[docs]
def xmax(self):
r"""
Return the maximal x-coordinate of a vertex of a visible graphical polygon.
"""
return max([self.graphical_polygon(label).xmax() for label in self.visible()])
[docs]
def ymax(self):
r"""
Return the minimal y-coordinate of a vertex of a visible graphical polygon.
"""
return max([self.graphical_polygon(label).ymax() for label in self.visible()])
[docs]
def bounding_box(self):
r"""
Return the quadruple (x1,y1,x2,y2) where x1 and y1 are the minimal
x- and y-coordinates of a visible graphical polygon and x2 and y2 are the
maximal x-and y- coordinates of a visible graphical polygon.
"""
return self.xmin(), self.ymin(), self.xmax(), self.ymax()
[docs]
def graphical_polygon(self, label):
r"""
Return the graphical_polygon with the given label.
"""
if label in self._polygons:
return self._polygons[label]
else:
t = None
if self._default_position_function is not None:
t = self._default_position_function(label)
from flatsurf.graphical.polygon import GraphicalPolygon
p = GraphicalPolygon(self._ss.polygon(label), transformation=t)
self._polygons[label] = p
return p
[docs]
def make_adjacent(self, p, e, reverse=False, visible=True):
r"""
Move the polygon across the prescribed edge so that is adjacent.
The polygon moved is obtained from opposite_edge(p,e).
If reverse=True then the polygon is moved so that there is a fold
at the edge.
If visible is True (by default), we also make the moved polygon visible.
EXAMPLES::
sage: from flatsurf import similarity_surfaces
sage: s = similarity_surfaces.example()
sage: gs = s.graphical_surface(adjacencies=[])
sage: gs.graphical_polygon(0)
GraphicalPolygon with vertices [(0.0, 0.0), (2.0, -2.0), (2.0, 0.0)]
sage: gs.graphical_polygon(1)
GraphicalPolygon with vertices [(0.0, 0.0), (2.0, 0.0), (1.0, 3.0)]
sage: print("Polygon 0, edge 0 is opposite "+str(gs.opposite_edge(0,0)))
Polygon 0, edge 0 is opposite (1, 1)
sage: gs.make_adjacent(0,0)
sage: gs.graphical_polygon(0)
GraphicalPolygon with vertices [(0.0, 0.0), (2.0, -2.0), (2.0, 0.0)]
sage: gs.graphical_polygon(1)
GraphicalPolygon with vertices [(0.4, -2.8), (2.0, -2.0), (0.0, 0.0)]
"""
pp, ee = self._ss.opposite_edge(p, e)
if reverse:
from flatsurf.geometry.similarity import SimilarityGroup
G = SimilarityGroup(self._ss.base_ring())
q = self._ss.polygon(p)
a = q.vertex(e)
b = q.vertex(e + 1)
# This is the similarity carrying the origin to a and (1,0) to b:
g = G(b[0] - a[0], b[1] - a[1], a[0], a[1])
qq = self._ss.polygon(pp)
aa = qq.vertex(ee + 1)
bb = qq.vertex(ee)
# This is the similarity carrying the origin to aa and (1,0) to bb:
gg = G(bb[0] - aa[0], bb[1] - aa[1], aa[0], aa[1])
reflection = G(
self._ss.base_ring().one(),
self._ss.base_ring().zero(),
self._ss.base_ring().zero(),
self._ss.base_ring().zero(),
-1,
)
# This is the similarity carrying (a,b) to (aa,bb):
g = gg * reflection * (~g)
else:
g = self._ss.edge_transformation(pp, ee)
h = self.graphical_polygon(p).transformation()
self.graphical_polygon(pp).set_transformation(h * g)
if visible:
self.make_visible(pp)
[docs]
def is_adjacent(self, p, e):
r"""
Returns the truth value of the statement
'The polygon opposite edge (p,e) is adjacent to that edge.'
EXAMPLES::
sage: from flatsurf import similarity_surfaces
sage: s = similarity_surfaces.example()
sage: g = s.graphical_surface(adjacencies=[])
sage: g.is_adjacent(0,0)
False
sage: g.is_adjacent(0,1)
False
sage: g.make_all_visible(adjacent=True)
sage: g.is_adjacent(0,0)
True
sage: g.is_adjacent(0,1)
False
TESTS:
Verify that this works correctly for boundary edges::
sage: from flatsurf import Polygon, MutableOrientedSimilaritySurface
sage: S = MutableOrientedSimilaritySurface(QQ)
sage: S.add_polygon(Polygon(vertices=[(0,0), (-1, -1), (1,0)]))
0
sage: G = S.graphical_surface()
sage: G.is_adjacent(0, 0)
False
"""
opposite_edge = self.opposite_edge(p, e)
if opposite_edge is None:
return False
pp, ee = opposite_edge
if not self.is_visible(pp):
return False
g = self.graphical_polygon(p)
gg = self.graphical_polygon(pp)
return g.transformed_vertex(e) == gg.transformed_vertex(
ee + 1
) and g.transformed_vertex(e + 1) == gg.transformed_vertex(ee)
[docs]
def to_surface(
self,
point,
v=None,
label=None,
ring=None,
return_all=False,
singularity_limit=None,
search_all=False,
search_limit=None,
):
r"""Converts from graphical coordinates to similarity surface coordinates.
A point always must be provided. If a vector v is provided then a
SimilaritySurfaceTangentVector will be returned. If v is not provided, then a
SurfacePoint is returned.
INPUT:
- ``point`` -- Coordinates of a point in graphical coordinates to be
converted to surface coordinates.
- ``v`` -- (default ``None``) If provided a tangent vector in graphical
coordinates based at the provided point.
- ``label`` -- (default ``None``) If provided, then we only convert
points and tangent vectors in the corresponding graphical polygon.
- ``ring`` -- (default ``None``) If provided, then objects returned
will be defined over the given ring, otherwise we use the base_ring
of the surface.
- ``return_all`` -- (default ``False``) By default we return the first
point or vector we find. However if the graphical polygons overlap,
then a point or vector might correspond to more than one point or
vector on the surface. If ``return_all`` is set to ``True`` then we
return a set of all points we find instead.
- ``singularity_limit`` -- (default ``None``) This only has an effect
if returning a singular point (i.e., ``v`` is ``None``) and the
surface is infinite. In this case, the singularity should be
returned but it could be infinite. Then singularity_limit controls
how far we look for the singularity to close. This value is passed
to the ``point()`` method on the surface.
- ``search_all`` -- (default ``False``) By default we look just in
polygons with visible label. If set to ``True``, then we instead
look in all labels.
- ``search_limit`` -- (default ``None``) If ``search_all`` is ``True``,
then we look at the first ``search_limit`` polygons instead of all
polygons. This must be set to an positive integer if ``search_all``
is and the surface is infinite.
EXAMPLES::
sage: from flatsurf import similarity_surfaces
sage: s = similarity_surfaces.example()
sage: gs = s.graphical_surface()
sage: gs.to_surface((1,-2))
Point (1, 1/2) of polygon 1
sage: gs.to_surface((1,-2), v=(1,0))
SimilaritySurfaceTangentVector in polygon 1 based at (1, 1/2) with vector (1, -1/2)
sage: from flatsurf import translation_surfaces
sage: s = translation_surfaces.infinite_staircase()
sage: gs = s.graphical_surface()
sage: gs.to_surface((4,4), (1,1), search_all=True, search_limit=20)
SimilaritySurfaceTangentVector in polygon 8 based at (0, 0) with vector (1, 1)
sage: s = translation_surfaces.square_torus()
sage: pc = s.minimal_cover(cover_type="planar")
sage: gs = pc.graphical_surface()
sage: gs.to_surface((3,2), search_all=True, search_limit=20)
Vertex 0 of polygon (0, (x, y) |-> (x + 3, y + 2))
sage: p = gs.to_surface((sqrt(3),sqrt(2)), ring=AA, search_all=True, search_limit=20)
doctest:warning
...
UserWarning: the ring parameter is deprecated and will be removed in a future version of sage-flatsurf; define the surface over a larger ring instead so that this points' coordinates live in the base ring
sage: next(iter(p.coordinates(next(iter(p.labels()))))).parent()
Vector space of dimension 2 over Algebraic Real Field
sage: v = gs.to_surface((3/2,3/2),(sqrt(3),sqrt(2)),ring=AA,search_all=True, search_limit=20)
sage: v.bundle()
Tangent bundle of Minimal Planar Cover of Translation Surface in H_1(0) built from a square defined over Algebraic Real Field
"""
if singularity_limit is not None:
import warnings
warnings.warn(
"singularity_limit has been deprecated as a keyword argument for to_surface() and will be removed from a future version of sage-flatsurf"
)
surface = self.get_surface()
point = vector(point, ring or surface.base_ring())
if label is None:
if search_all:
if search_limit is None:
if surface.is_finite_type():
labels = surface.labels()
else:
raise ValueError(
"If search_all=True and the surface is infinite, then a search_limit must be provided."
)
else:
from itertools import islice
labels = islice(surface.labels(), search_limit)
else:
labels = self.visible()
else:
labels = [label]
points = set()
for label in labels:
gp = self.graphical_polygon(label)
coords = gp.transform_back(point)
pos = surface.polygon(label).get_point_position(coords)
if not pos.is_inside():
continue
if v is None:
points.add(
surface.point(label, coords, ring=ring, limit=singularity_limit)
)
else:
direction = (~(gp.transformation().derivative())) * vector(v)
if pos.is_vertex():
vertex = pos.get_vertex()
polygon = surface.polygon(label)
from flatsurf.geometry.euclidean import ccw
if ccw(polygon.edge(vertex), direction) < 0:
continue
if (
ccw(
polygon.edge((vertex - 1) % len(polygon.vertices())),
direction,
)
< 0
):
continue
points.add(
surface.tangent_vector(
label,
coords,
direction,
ring=ring,
)
)
if not return_all:
return next(iter(points))
if return_all:
return points
else:
raise ValueError("Point or vector is not in a visible graphical polygon.")
[docs]
def opposite_edge(self, p, e):
r"""
Given the label ``p`` of a polygon and an edge ``e`` in that polygon
returns the pair (``pp``, ``ee``) to which this edge is glued.
"""
return self._ss.opposite_edge(p, e)
[docs]
def reset_letters(self, p, e):
r"""
Resets the letter dictionary for storing letters used in
edge labeling if edge_labels="letter" is used.
"""
try:
del self._letters
del self._next_letter
except AttributeError:
pass
def _get_letter_for_edge(self, p, e):
if not hasattr(self, "_letters"):
self._letters = {}
self._next_letter = 1
try:
return self._letters[(p, e)]
except KeyError:
# convert number to string
nl = self._next_letter
self._next_letter = nl + 1
letter = ""
while nl != 0:
val = nl % 52
if val == 0:
val = 52
letter = "Z" + letter
elif val < 27:
letter = chr(97 + val - 1) + letter
else:
letter = chr(65 + val - 27) + letter
nl = (nl - val) / 52
self._letters[(p, e)] = letter
self._letters[self._ss.opposite_edge(p, e)] = letter
return letter
[docs]
def edge_labels(self, lab):
r"""
Return the list of edge labels to be used for the polygon with label ``lab``.
EXAMPLES::
sage: from flatsurf import similarity_surfaces
sage: s = similarity_surfaces.example()
sage: g = s.graphical_surface(adjacencies=[])
sage: g.edge_labels(0)
['1', '1', '1']
sage: g.make_all_visible(adjacent=True)
sage: g.edge_labels(0)
[None, '1', '1']
sage: g.make_adjacent(0,0)
sage: g.edge_labels(0)
[None, '1', '1']
sage: g.edge_labels(1)
['0', None, '0']
sage: s = similarity_surfaces.example()
sage: g = s.graphical_surface(adjacencies=[], edge_labels='number')
sage: g.edge_labels(0)
['0', '1', '2']
sage: g = s.graphical_surface(adjacencies=[], edge_labels='gluings and number')
sage: g.edge_labels(0)
['0 -> (1, 1)', '1 -> (1, 2)', '2 -> (1, 0)']
sage: g.make_all_visible(adjacent=True)
sage: g.edge_labels(0)
['0', '1 -> (1, 2)', '2 -> (1, 0)']
"""
if not self._edge_labels:
return None
s = self._ss
g = self.graphical_polygon(lab)
p = g.base_polygon()
if self._edge_labels == "gluings":
labels = []
for e in range(len(p.vertices())):
if self.is_adjacent(lab, e):
labels.append(None)
elif s.opposite_edge(lab, e) is None:
labels.append(None)
else:
llab, _ = s.opposite_edge(lab, e)
labels.append(str(llab))
elif self._edge_labels == "number":
labels = list(map(str, range(len(p.vertices()))))
elif self._edge_labels == "gluings and number":
labels = []
for e in range(len(p.vertices())):
if self.is_adjacent(lab, e):
labels.append(str(e))
else:
labels.append("{} -> {}".format(e, s.opposite_edge(lab, e)))
elif self._edge_labels == "letter":
labels = []
for e in range(len(p.vertices())):
llab, ee = s.opposite_edge(lab, e)
if not self.is_visible(llab) or self.is_adjacent(lab, e):
labels.append(None)
else:
labels.append(self._get_letter_for_edge(lab, e))
else:
raise RuntimeError("invalid option for edge_labels")
return labels
[docs]
def plot_polygon(self, label, graphical_polygon, upside_down):
r"""
Internal method for plotting polygons returning a Graphics object.
Calls :meth:`.polygon.GraphicalPolygon.plot_polygon` passing
the attribute ``upside_down_polygon_options`` if the polygon is upside down
and ``polygon_options`` otherwise.
Override this method for fine control of how the polygons are drawn.
INPUT:
- ``label`` -- The label of the polygon being plotted.
- ``graphical_polygon`` -- The associated graphical polygon.
- ``upside_down`` -- True if and only if the polygon will be rendered upside down.
"""
if upside_down:
return graphical_polygon.plot_polygon(**self.upside_down_polygon_options)
else:
return graphical_polygon.plot_polygon(**self.polygon_options)
[docs]
def plot_polygon_label(self, label, graphical_polygon, upside_down):
r"""
Internal method for plotting polygon labels returning a Graphics2D.
Calls :meth:`.polygon.GraphicalPolygon.plot_label` passing the
attribute ``polygon_label_options``.
Override this method for fine control of how the polygons are drawn.
INPUT:
- ``label`` -- The label of the polygon being plotted.
- ``graphical_polygon`` -- The associated graphical polygon.
- ``upside_down`` -- True if and only if the polygon will be rendered upside down.
"""
return graphical_polygon.plot_label(label, **self.polygon_label_options)
[docs]
def plot_edge(self, label, edge, graphical_polygon, is_adjacent, is_self_glued):
r"""
Internal method for plotting a polygon's edge returning a Graphics2D.
The method calls :meth:`.polygon.GraphicalPolygon.plot_edge`.
Depending on the geometry of the edge pair, it passes one of the attributes
``adjacent_edge_options``, ``self_glued_edge_options`` or ``non_adjacent_edge_options``.
Override this method for fine control of how the edge is drawn.
INPUT:
- ``label`` -- The label of the polygon.
- ``edge`` -- Integer representing the edge of the polygon.
- ``graphical_polygon`` -- The associated graphical polygon.
- ``is_adjacent`` -- True if and only if the polygon opposite this edge is visible and adjacent to this edge.
In this case, plot_edge is called only once for this edge.
- ``is_self_glued`` -- True if and only if the edge is glued to itself by a 180 degree rotation.
This is never True if is_adjacent is True.
"""
if is_adjacent:
return graphical_polygon.plot_edge(edge, **self.adjacent_edge_options)
elif is_self_glued:
return graphical_polygon.plot_edge(edge, **self.self_glued_edge_options)
else:
return graphical_polygon.plot_edge(edge, **self.non_adjacent_edge_options)
[docs]
def plot_edge_label(self, p, e, edge_label, graphical_polygon):
r"""
Internal method for plotting an edge label.
Calls :meth:`.polygon.GraphicalPolygon.plot_edge_label` passing
the attribute ``edge_label_options``.
Override this method for fine control of how the edge is drawn.
INPUT:
- ``p`` -- The label of the polygon.
- ``e`` -- Integer representing the edge of the polygon.
- ``edge_label`` -- A string containing the label to be printed on the edge.
- ``graphical_polygon`` -- The associated graphical polygon.
"""
return graphical_polygon.plot_edge_label(
e, edge_label, **self.edge_label_options
)
[docs]
def plot_zero_flag(self, label, graphical_polygon):
r"""
Internal method for plotting a polygon's zero_flag and returning a Graphics2D.
Simply calls :meth:`.polygon.GraphicalPolygon.plot_zero_flag` passing
the attribute` `zero_flag_options``.
Override this method for fine control of how the edge is drawn.
INPUT:
- ``label`` -- The label of the polygon.
- ``graphical_polygon`` -- The associated graphical polygon.
"""
return graphical_polygon.plot_zero_flag(**self.zero_flag_options)
[docs]
def plot(self, **kwargs):
r"""
Return a plot of this surface.
INPUT:
- ``kwargs`` -- arguments are normally forwarded to the polygon
plotting. However, prefixed arguments, e.g., ``polygon_label_color``,
are routed correspondingly. Also, a dictionary suffixed with
``_options`` is merged with the existing options of this surface. See
examples below for details.
EXAMPLES:
.. jupyter-execute::
sage: from flatsurf import similarity_surfaces
sage: s = similarity_surfaces.example()
sage: from flatsurf.graphical.surface import GraphicalSurface
sage: gs = GraphicalSurface(s)
sage: gs.plot()
...Graphics object consisting of 13 graphics primitives
Keyword arguments that end in ``_options`` are merged into the
corresponding attribute before plotting; see :class:`GraphicalSurface`
for a list of all supported ``_options``:
.. jupyter-execute::
sage: gs.plot(polygon_label_options={"color": "red"})
...Graphics object consisting of 13 graphics primitives
Keyword arguments that are prefixed with such an aspect of plotting,
are also merged into the corresponding attribute before plotting; see
:class:`GraphicalSurface` for a list of all supported prefixes, i.e.,
``_options``:
.. jupyter-execute::
sage: gs.plot(polygon_label_color="red")
...Graphics object consisting of 13 graphics primitives
All other arguments are passed to the polygon plotting itself:
.. jupyter-execute::
sage: gs.plot(fill=None)
...Graphics object consisting of 13 graphics primitives
TESTS:
Check that label options are handled correctly::
sage: from flatsurf import translation_surfaces
sage: S = translation_surfaces.square_torus()
sage: S.plot(polygon_labels=True, edge_labels=True)
...Graphics object consisting of 10 graphics primitives
sage: S.plot(polygon_labels=False, edge_labels=True)
...Graphics object consisting of 9 graphics primitives
sage: S.plot(polygon_labels=True, edge_labels=False)
...Graphics object consisting of 6 graphics primitives
sage: S.plot(polygon_labels=False, edge_labels=False)
...Graphics object consisting of 5 graphics primitives
"""
if kwargs:
surface = self.copy()
options = [
option
for option in surface.__dict__
if option.endswith("_options") and option != "process_options"
]
# Sort recognized options so we do not pass polygon_label_color to
# polygon_options as label_color.
options.sort(key=lambda option: -len(option))
for key, value in kwargs.items():
if key in options:
setattr(surface, key, {**getattr(surface, key), **value})
continue
for option in options:
prefix = option[: -len("options")]
if key.startswith(prefix) and key != prefix:
getattr(surface, option)[key[len(prefix) :]] = value
break
else:
surface.polygon_options[key] = value
return surface.plot()
from sage.plot.graphics import Graphics
p = Graphics()
# Make sure we don't plot adjacent edges more than once.
plotted_adjacent_edges = set()
for label in self._visible:
polygon = self.graphical_polygon(label)
upside_down = polygon.transformation().sign() == -1
# Plot the polygons
if upside_down and self.will_plot_upside_down_polygons:
p += self.plot_polygon(label, polygon, upside_down)
elif self.will_plot_polygons:
p += self.plot_polygon(label, polygon, upside_down)
if self.will_plot_zero_flags:
p += self.plot_zero_flag(label, polygon)
# Add the polygon label
if self.will_plot_polygon_labels:
p += self.plot_polygon_label(label, polygon, upside_down)
# Plot the edges
if self.will_plot_edges:
for i in range(len(self._ss.polygon(label).vertices())):
if self.is_adjacent(label, i):
if (
self.will_plot_adjacent_edges
and (label, i) not in plotted_adjacent_edges
):
plotted_adjacent_edges.add(self._ss.opposite_edge(label, i))
p += self.plot_edge(label, i, polygon, True, False)
elif (label, i) == self._ss.opposite_edge(label, i):
# Self-glued edge
if self.will_plot_self_glued_edges:
p += self.plot_edge(label, i, polygon, False, True)
else:
if self.will_plot_non_adjacent_edges:
p += self.plot_edge(label, i, polygon, False, False)
# Plot the edge labels.
if self.will_plot_edge_labels:
# get the edge labels
edge_labels = self.edge_labels(label)
if edge_labels is not None:
for i in range(len(self._ss.polygon(label).vertices())):
if edge_labels[i] is not None:
p += self.plot_edge_label(label, i, edge_labels[i], polygon)
return p