Source code for flatsurf.geometry.lazy

r"""
Triangulations, Delaunay triangulations, and Delaunay decompositions of
infinite surfaces.

EXAMPLES:

Typically, you don't need to create these surfaces directly, they are created
by invoking methods on the underlying surfaces::

    sage: from flatsurf import translation_surfaces
    sage: S = translation_surfaces.infinite_staircase()
    sage: S.triangulate()
    Triangulation of The infinite staircase

    sage: S.delaunay_triangulation()
    Delaunay triangulation of The infinite staircase

    sage: S.delaunay_decomposition()
    Delaunay cell decomposition of The infinite staircase

"""
# ********************************************************************
#  This file is part of sage-flatsurf.
#
#       Copyright (C) 2013-2019 Vincent Delecroix
#                     2013-2019 W. Patrick Hooper
#                          2023 Julian Rüth
#
#  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.misc.cachefunc import cached_method

from flatsurf.geometry.surface import (
    MutableOrientedSimilaritySurface_base,
    OrientedSimilaritySurface,
    Labels,
)


[docs]class LazyTriangulatedSurface(OrientedSimilaritySurface): r""" A triangulated surface whose structure is computed on demand. Used to triangulate surfaces when in-place-triangulation is not requested. In particular, this is used to triangulate infinite type surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: S = S.triangulate() TESTS:: sage: from flatsurf.geometry.lazy import LazyTriangulatedSurface sage: isinstance(S, LazyTriangulatedSurface) True sage: TestSuite(S).run() # long time (1s) """ def __init__(self, surface, labels=None, relabel=None, category=None): if relabel is not None: if relabel: raise NotImplementedError( "the relabel keyword has been removed from LazyTriangulatedSurface; use relabel() to use integer labels instead" ) else: import warnings warnings.warn( "the relabel keyword will be removed in a future version of sage-flatsurf; do not pass it explicitly anymore to LazyTriangulatedSurface()" ) if surface.is_mutable(): raise ValueError("Surface must be immutable.") if labels is not None: labels = set(labels) if surface.is_finite_type(): if labels == set(surface.labels()): labels = None self._reference = surface self._triangulated_reference_labels = labels OrientedSimilaritySurface.__init__( self, surface.base_ring(), category=category or self._reference.category(), ) def _is_triangulated(self, reference_label): r""" Return whether the ``reference_label`` of the reference surface is explicitly triangulated in this surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: S = S.triangulate() sage: S._is_triangulated(0) True sage: S._is_triangulated(1) True sage: S = S.triangulate(label=0) sage: S._is_triangulated(0) True sage: S._is_triangulated(1) False """ if self._triangulated_reference_labels is None: return True return reference_label in self._triangulated_reference_labels
[docs] def is_mutable(self): r""" Return whether this surface is mutable, i.e., return ``False``. This implements :meth:`flatsurf.geometry.categories.topological_surfaces.TopologicalSurfaces.ParentMethods.is_mutable`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().triangulate() sage: S.is_mutable() False """ return False
[docs] def is_compact(self): r""" Return whether this surface is compact as a topological space. This implements :meth:`flatsurf.geometry.categories.topological_surfaces.TopologicalSurfaces.ParentMethods.is_compact`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().triangulate() sage: S.is_compact() False """ return self._reference.is_compact()
[docs] def roots(self): r""" Return root labels for the polygons forming the connected components of this surface. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.roots`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().triangulate() sage: S.roots() ((0, 0),) """ return tuple(self._image(root)[0].root() for root in self._reference.roots())
@cached_method def _image(self, reference_label): r""" Return a triangulation of the ``reference_label`` in the underlying (typically non-triangulated) reference surface. If the ``reference_label`` is not being triangulated, the return a surface just consisting of this polygon. INPUT: - ``reference_label`` -- a polygon label in the reference surface that we are triangulating. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().triangulate() sage: S._image(0) (Translation Surface with boundary built from 2 isosceles triangles, bidict({0: ((0, 0), 0), 1: ((0, 0), 1), 2: ((0, 1), 1), 3: ((0, 1), 2)})) sage: S = translation_surfaces.infinite_staircase().triangulate(label=1) sage: S._image(0) (Translation Surface with boundary built from a square, bidict({0: (0, 0), 1: (0, 1), 2: (0, 2), 3: (0, 3)})) """ reference_polygon = self._reference.polygon(reference_label) if not self._is_triangulated(reference_label): from flatsurf import MutableOrientedSimilaritySurface triangulation = MutableOrientedSimilaritySurface( self._reference.base_ring() ) triangulation.add_polygon(reference_polygon) from bidict import bidict return triangulation, bidict( {e: (reference_label, e) for e in range(len(reference_polygon.edges()))} ) from flatsurf.geometry.surface import MutableOrientedSimilaritySurface return MutableOrientedSimilaritySurface._triangulate( self._reference, reference_label ) def _reference_label(self, label): r""" Return the label of the underlying (untriangulated) reference surface which led to the creation of the polygon with ``label`` in this triangulation. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().triangulate() sage: S._reference_label((0, 0)) 0 """ if label in self._reference.labels(): if not self._is_triangulated(label): return label if len(self._reference.polygon(label).vertices()) == 3: return label if not isinstance(label, tuple): raise KeyError(label) if len(label) != 2: raise KeyError(label) if label[0] not in self._reference.labels(): raise KeyError(label) if label not in self._image(label[0])[0].labels(): raise KeyError(label) return label[0]
[docs] def polygon(self, label): r""" Return the polygon with ``label``. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.polygon`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().triangulate() sage: S.polygon((0, 0)) Polygon(vertices=[(0, 0), (1, 0), (1, 1)]) """ reference_label = self._reference_label(label) if not self._is_triangulated(reference_label): return self._reference.polygon(reference_label) triangulation, _ = self._image(reference_label) return triangulation.polygon(label)
[docs] def opposite_edge(self, label, edge): r""" Return the polygon label and edge index when crossing over the ``edge`` of the polygon ``label``. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.opposite_edge`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().triangulate() sage: S.opposite_edge((0, 0), 0) ((1, 1), 1) """ reference_label = self._reference_label(label) triangulation, outer_edges = self._image(reference_label) if (label, edge) in outer_edges.values(): # pylint does not understand the bidict return type, so we disable a failing check here # pylint: disable=unsubscriptable-object reference_edge = outer_edges.inverse[(label, edge)] ( opposite_reference_label, opposite_reference_edge, ) = self._reference.opposite_edge(reference_label, reference_edge) opposite_triangulation, opposite_outer_edges = self._image( opposite_reference_label ) return opposite_outer_edges[opposite_reference_edge] return triangulation.opposite_edge(label, edge)
[docs] def is_triangulated(self, limit=None): r""" Return whether this surface is triangulated. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().triangulate() sage: S.is_triangulated() True sage: S = translation_surfaces.infinite_staircase().triangulate(label=0) sage: S.is_triangulated() Traceback (most recent call last): ... NotImplementedError: cannot decide whether this (potentially infinite type) surface is triangulated """ if limit is not None: import warnings warnings.warn( "limit has been deprecated as a keyword argument for is_triangulated() and will be removed from a future version of sage-flatsurf; " "if you rely on this check, you can try to run this method on MutableOrientedSimilaritySurface.from_surface(surface, labels=surface.labels()[:limit])" ) if self._triangulated_reference_labels is None: return True return super().is_triangulated(limit=limit)
def __hash__(self): r""" Return a hash value for this surface that is compatible with :meth:`__eq__`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: hash(S.triangulate()) == hash(S.triangulate()) True """ return hash(self._reference) def __eq__(self, other): r""" Return whether this surface is indistinguishable from ``other``. See :meth:`SimilaritySurfaces.FiniteType._test_eq_surface` for details on this notion of equality. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: S.triangulate() == S.triangulate() True """ if not isinstance(other, LazyTriangulatedSurface): return False return ( self._reference == other._reference and self._triangulated_reference_labels == other._triangulated_reference_labels )
[docs] def labels(self): r""" Return the labels of this surface. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.labels`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().triangulate() sage: S.labels() ((0, 0), (1, 1), (-1, 1), (0, 1), (1, 0), (2, 0), (-1, 0), (-2, 0), (2, 1), (3, 1), (-2, 1), (-3, 1), (3, 0), (4, 0), (-3, 0), (-4, 0), …) """ return TriangulationLabels(self, finite=self._reference.is_finite_type())
def _repr_(self): r""" Return a printable representation of this surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().triangulate() sage: S Triangulation of The infinite staircase """ if self._triangulated_reference_labels is not None: return f"Partial Triangulation of {self._reference!r}" return f"Triangulation of {self._reference!r}"
[docs]class TriangulationLabels(Labels): r""" The labels of a triangulation of a (possibly infinite) surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().triangulate() sage: labels = S.labels() TESTS:: sage: from flatsurf.geometry.lazy import TriangulationLabels sage: isinstance(labels, TriangulationLabels) True """ def __contains__(self, label): r""" Return whether ``label`` is present as a label in this triangulation. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().triangulate() sage: labels = S.labels() sage: 0 in labels False sage: (0, 0) in labels True sage: (0, 1) in labels True sage: (0, 2) in labels False """ try: self._surface._reference_label(label) except KeyError: return False return True
[docs]class LazyOrientedSimilaritySurface(OrientedSimilaritySurface): r""" A surface that forwards all queries to an underlying reference surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: T = matrix([[2, 0], [0, 1]]) * S sage: from flatsurf.geometry.lazy import LazyOrientedSimilaritySurface sage: isinstance(T, LazyOrientedSimilaritySurface) True """ def __init__(self, base_ring, reference, category=None): super().__init__(base_ring, category=category or reference.category()) self._reference = reference
[docs] def is_compact(self): r""" Return whether this surface is compact as a topological space. This implements :meth:`flatsurf.geometry.categories.topological_surfaces.TopologicalSurfaces.ParentMethods.is_compact`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.octagon_and_squares() sage: r = matrix(ZZ,[[0, 1], [1, 0]]) sage: S = r * S sage: S.is_compact() True """ return self._reference.is_compact()
[docs] def is_translation_surface(self, positive=True): r""" Return whether this surface is a translation surface, i.e., glued edges can be transformed into each other by translations. This implements :meth:`flatsurf.geometry.categories.similarity_surfaces.SimilaritySurfaces.ParentMethods.is_translation_surface`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.octagon_and_squares() sage: r = matrix(ZZ,[[0, 1], [1, 0]]) sage: S = r * S sage: S.is_translation_surface() True """ return self._reference.is_translation_surface(positive=positive)
[docs] def roots(self): r""" Return root labels for the polygons forming the connected components of this surface. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.roots`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: from flatsurf.geometry.lazy import LazyMutableOrientedSimilaritySurface sage: T = LazyMutableOrientedSimilaritySurface(S) sage: T.roots() (0,) """ return self._reference.roots()
[docs] def labels(self): r""" Return the labels of this surface which are just the labels of the underlying reference surface. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.labels`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: from flatsurf.geometry.lazy import LazyMutableOrientedSimilaritySurface sage: T = LazyMutableOrientedSimilaritySurface(S) sage: T.labels() (0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6, 7, -7, 8, …) """ return self._reference.labels()
[docs] def polygon(self, label): r""" Return the polygon with ``label``. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.polygon`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: T = matrix([[2, 0], [0, 1]]) * S sage: T.polygon(0) Polygon(vertices=[(0, 0), (2, 0), (2, 1), (0, 1)]) """ return self._reference.polygon(label)
[docs] def opposite_edge(self, label, edge): r""" Return the polygon label and edge index when crossing over the ``edge`` of the polygon ``label``. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.opposite_edge`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: T = matrix([[2, 0], [0, 1]]) * S sage: T.opposite_edge(0, 0) (1, 2) """ return self._reference.opposite_edge(label, edge)
[docs] def is_mutable(self): r""" Return whether this surface could be changing. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: T = matrix([[2, 0], [0, 1]]) * S sage: T.is_mutable() False """ return self._reference.is_mutable()
[docs]class GL2RImageSurface(LazyOrientedSimilaritySurface): r""" The GL(2,R) image of an oriented similarity surface obtained by applying a matrix to each polygon while keeping the gluings intact. EXAMPLE:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.octagon_and_squares() sage: r = matrix(ZZ,[[0, 1], [1, 0]]) sage: SS = r * S sage: S.canonicalize() == SS.canonicalize() True TESTS:: sage: TestSuite(SS).run() sage: from flatsurf.geometry.lazy import GL2RImageSurface sage: isinstance(SS, GL2RImageSurface) True """ def __init__(self, reference, m, category=None): if reference.is_mutable(): if not reference.is_finite_type(): raise NotImplementedError( "cannot apply matrix to mutable surface of infinite type" ) from flatsurf.geometry.surface import MutableOrientedSimilaritySurface reference = MutableOrientedSimilaritySurface.from_surface(reference) self._reference = reference from sage.structure.element import get_coercion_model cm = get_coercion_model() base_ring = cm.common_parent(m.base_ring(), self._reference.base_ring()) from sage.all import matrix self._matrix = matrix(base_ring, m, immutable=True) super().__init__( base_ring, reference, category=category or self._reference.category() ) @cached_method def _sgn(self): r""" Return the sign of the determinant of the matrix underlying this surface, i.e., whether the matrix reversed orientation or not. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.octagon_and_squares() sage: r = matrix(ZZ,[[0, 1], [1, 0]]) sage: S = r * S sage: S._sgn() -1 """ return self._matrix.det().sign()
[docs] def polygon(self, label): r""" Return the polygon with ``label``. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.polygon`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.octagon_and_squares() sage: r = matrix(ZZ,[[0, 1], [1, 0]]) sage: S = r * S sage: S.polygon(0) Polygon(vertices=[(0, 0), (a, -a), (a + 2, -a), (2*a + 2, 0), (2*a + 2, 2), (a + 2, a + 2), (a, a + 2), (0, 2)]) """ return self._matrix * self._reference.polygon(label)
[docs] def opposite_edge(self, label, edge): r""" Return the polygon label and edge index when crossing over the ``edge`` of the polygon ``label``. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.opposite_edge`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.octagon_and_squares() sage: r = matrix(ZZ,[[0, 1], [1, 0]]) sage: S = r * S sage: S.opposite_edge(0, 0) (2, 0) """ reference_edge = edge if self._sgn() == -1: reference_edge = len(self.polygon(label).edges()) - 1 - edge opposite_label, opposite_edge = self._reference.opposite_edge( label, reference_edge ) if self._sgn() == -1: opposite_edge = ( len(self._reference.polygon(opposite_label).edges()) - 1 - opposite_edge ) return opposite_label, opposite_edge
[docs] def is_mutable(self): r""" Return whether this surface is mutable, i.e., return ``False``. This implements :meth:`flatsurf.geometry.categories.topological_surfaces.TopologicalSurfaces.ParentMethods.is_mutable`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.octagon_and_squares() sage: r = matrix(ZZ,[[0, 1], [1, 0]]) sage: S = r * S sage: S.is_mutable() False """ return False
def _repr_(self): r""" Return a printable representation of this surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.octagon_and_squares() sage: matrix([[0, 1], [1, 0]]) * S Translation Surface in H_3(4) built from 2 squares and a regular octagon sage: matrix([[0, 2], [1, 0]]) * S Translation Surface in H_3(4) built from a rhombus, a rectangle and an octagon :: sage: m = matrix([[2, 1], [1, 1]]) sage: m * translation_surfaces.infinite_staircase() GL2R image of The infinite staircase """ if self.is_finite_type(): from flatsurf.geometry.surface import MutableOrientedSimilaritySurface S = MutableOrientedSimilaritySurface.from_surface(self) S.set_immutable() return repr(S) return f"GL2R image of {self._reference!r}" def __hash__(self): r""" Return a hash value for this surface that is compatible with :meth:`__eq__`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.octagon_and_squares() sage: r = matrix(ZZ,[[0, 1], [1, 0]]) sage: hash(r * S) == hash(r * S) True """ return hash((self._reference, self._matrix)) def __eq__(self, other): r""" Return whether this image is indistinguishable from ``other``. See :meth:`SimilaritySurfaces.FiniteType._test_eq_surface` for details on this notion of equality. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.octagon_and_squares() sage: m = matrix(ZZ,[[0, 1], [1, 0]]) sage: m * S == m * S True """ if not isinstance(other, GL2RImageSurface): return False return ( self._reference == other._reference and self._matrix == other._matrix and self.base_ring() == other.base_ring() )
[docs] def is_triangulated(self, limit=None): r""" Return whether this surface is triangulated. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.octagon_and_squares() sage: m = matrix(ZZ,[[0, 1], [1, 0]]) sage: (m * S).is_triangulated() False """ if limit is not None: import warnings warnings.warn( "limit has been deprecated as a keyword argument for is_triangulated() and will be removed from a future version of sage-flatsurf; " "if you rely on this check, you can try to run this method on MutableOrientedSimilaritySurface.from_surface(surface, labels=surface.labels()[:limit])" ) return self._reference.is_triangulated(limit=limit)
[docs]class LazyMutableOrientedSimilaritySurface( LazyOrientedSimilaritySurface, MutableOrientedSimilaritySurface_base ): r""" A helper surface for :class:`LazyDelaunayTriangulatedSurface`. A mutable wrapper of an (infinite) reference surface. When a polygon is not present in this wrapper yet, it is taken from the reference surface and can then be modified. .. NOTE:: This surface does not implement the entire surface interface correctly. It just supports the operations in the way that they are necessary to make :class:`LazyDelaunayTriangulatedSurface` work. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: from flatsurf.geometry.lazy import LazyMutableOrientedSimilaritySurface sage: T = LazyMutableOrientedSimilaritySurface(S) sage: p = T.polygon(0) sage: p Polygon(vertices=[(0, 0), (1, 0), (1, 1), (0, 1)]) sage: q = p * 2 sage: S.replace_polygon(0, q) Traceback (most recent call last): ... AttributeError: '_InfiniteStaircase_with_category' object has no attribute 'replace_polygon'... sage: T.replace_polygon(0, q) sage: T.polygon(0) Polygon(vertices=[(0, 0), (2, 0), (2, 2), (0, 2)]) """ def __init__(self, surface, category=None): from flatsurf.geometry.categories import SimilaritySurfaces if surface not in SimilaritySurfaces().Oriented().WithoutBoundary(): raise NotImplementedError("cannot handle surfaces with boundary yet") from flatsurf.geometry.surface import MutableOrientedSimilaritySurface self._surface = MutableOrientedSimilaritySurface(surface.base_ring()) super().__init__( surface.base_ring(), surface, category=category or surface.category() )
[docs] def is_mutable(self): r""" Return whether this surface is mutable, i.e., return ``True``. This implements :meth:`flatsurf.geometry.categories.topological_surfaces.TopologicalSurfaces.ParentMethods.is_mutable`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: from flatsurf.geometry.lazy import LazyMutableOrientedSimilaritySurface sage: T = LazyMutableOrientedSimilaritySurface(S) sage: T.is_mutable() True """ return True
[docs] def replace_polygon(self, label, polygon): r""" Swap out the polygon with the label ``label`` with ``polygon``. The polygons must have the same number of sides since gluings are kept. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: from flatsurf.geometry.lazy import LazyMutableOrientedSimilaritySurface sage: T = LazyMutableOrientedSimilaritySurface(S) sage: T.replace_polygon(0, T.polygon(0)) """ self._ensure_polygon(label) return self._surface.replace_polygon(label, polygon)
[docs] def glue(self, x, y): r""" Glue the (label, edge) pair ``x`` with the pair ``y`` in this surface. This unglues any existing gluings of these edges. .. NOTE:: After a sequence of such glue operations, no edges must be unglued. Otherwise, gluings get copied over from the underlying surface with confusing side effects. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: from flatsurf.geometry.lazy import LazyMutableOrientedSimilaritySurface sage: T = LazyMutableOrientedSimilaritySurface(S) sage: T.gluings() (((0, 0), (1, 2)), ((0, 1), (-1, 3)), ((0, 2), (1, 0)), ((0, 3), (-1, 1)), ((1, 0), (0, 2)), ((1, 1), (2, 3)), ((1, 2), (0, 0)), ((1, 3), (2, 1)), ((-1, 0), (-2, 2)), ((-1, 1), (0, 3)), ((-1, 2), (-2, 0)), ((-1, 3), (0, 1)), ((2, 0), (3, 2)), ((2, 1), (1, 3)), ((2, 2), (3, 0)), ((2, 3), (1, 1)), …) sage: T.glue((0, 0), (1, 0)) sage: T.glue((1, 2), (0, 2)) sage: T.gluings() (((0, 0), (1, 0)), ((0, 1), (-1, 3)), ((0, 2), (1, 2)), ((0, 3), (-1, 1)), ((1, 0), (0, 0)), ((1, 1), (2, 3)), ((1, 2), (0, 2)), ((1, 3), (2, 1)), ((-1, 0), (-2, 2)), ((-1, 1), (0, 3)), ((-1, 2), (-2, 0)), ((-1, 3), (0, 1)), ((2, 0), (3, 2)), ((2, 1), (1, 3)), ((2, 2), (3, 0)), ((2, 3), (1, 1)), …) """ return self._surface.glue(x, y)
def _ensure_gluings(self, label): r""" Make sure that the surface used to internally represent this surface has copied over all the gluings for ``label`` from the underlying surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: from flatsurf.geometry.lazy import LazyMutableOrientedSimilaritySurface sage: T = LazyMutableOrientedSimilaritySurface(S) sage: T._ensure_polygon(0) sage: T._ensure_gluings(0) """ self._ensure_polygon(label) for edge in range(len(self._surface.polygon(label).vertices())): cross = self._surface.opposite_edge(label, edge) if cross is None: cross_label, cross_edge = self._reference.opposite_edge(label, edge) self._ensure_polygon(cross_label) assert ( self._surface.opposite_edge(cross_label, cross_edge) is None ), "surface must not have a boundary" # Note that we cannot detect whether something has been # explicitly unglued. So we just reestablish any gluings of # this edge. self._surface.glue((label, edge), (cross_label, cross_edge)) def _ensure_polygon(self, label): r""" Make sure that the surface used to internally represent this surface has copied over the polygon ``label`` from the underlying surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: from flatsurf.geometry.lazy import LazyMutableOrientedSimilaritySurface sage: T = LazyMutableOrientedSimilaritySurface(S) sage: T._ensure_polygon(0) """ if label not in self._surface.labels(): self._surface.add_polygon(self._reference.polygon(label), label=label)
[docs] def polygon(self, label): r""" Return the polygon with ``label``. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.polygon`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: from flatsurf.geometry.lazy import LazyMutableOrientedSimilaritySurface sage: T = LazyMutableOrientedSimilaritySurface(S) sage: T.polygon(0) Polygon(vertices=[(0, 0), (1, 0), (1, 1), (0, 1)]) """ self._ensure_polygon(label) return self._surface.polygon(label)
[docs] def opposite_edge(self, label, edge): r""" Return the polygon label and edge index when crossing over the ``edge`` of the polygon ``label``. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.opposite_edge`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: from flatsurf.geometry.lazy import LazyMutableOrientedSimilaritySurface sage: T = LazyMutableOrientedSimilaritySurface(S) sage: T.opposite_edge(0, 0) (1, 2) """ self._ensure_polygon(label) self._ensure_gluings(label) cross_label, cross_edge = self._surface.opposite_edge(label, edge) self._ensure_polygon(cross_label) self._ensure_gluings(cross_label) return cross_label, cross_edge
[docs]class LazyDelaunayTriangulatedSurface(OrientedSimilaritySurface): r""" Delaunay triangulation of an (infinite type) surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_triangulation() sage: len(S.polygon(S.root()).vertices()) 3 sage: TestSuite(S).run() # long time (.8s) sage: S.is_delaunay_triangulated() True sage: from flatsurf.geometry.lazy import LazyDelaunayTriangulatedSurface sage: isinstance(S, LazyDelaunayTriangulatedSurface) True :: sage: from flatsurf.geometry.chamanara import chamanara_surface sage: S = chamanara_surface(QQ(1/2)) sage: m = matrix([[2,1],[1,1]])**4 sage: S = (m*S).delaunay_triangulation() sage: TestSuite(S).run() # long time (1s) sage: S.is_delaunay_triangulated() True sage: TestSuite(S).run() # long time (.5s) sage: from flatsurf.geometry.lazy import LazyDelaunayTriangulatedSurface sage: isinstance(S, LazyDelaunayTriangulatedSurface) True """ def __init__(self, similarity_surface, direction=None, relabel=None, category=None): if relabel is not None: if relabel: raise NotImplementedError( "the relabel keyword has been removed from LazyDelaunayTriangulatedSurface; use relabel() to use integer labels instead" ) else: import warnings warnings.warn( "the relabel keyword will be removed in a future version of sage-flatsurf; do not pass it explicitly anymore to LazyDelaunayTriangulatedSurface()" ) if direction is not None: direction = None import warnings warnings.warn( "the direction keyword argument has been deprecated for LazyDelaunayTriangulatedSurface and will be removed in a future version of sage-flatsurf; " "its value is ignored in this version of sage-flatsurf; if you see this message when restoring a pickle, the object might not be fully functional" ) if similarity_surface.is_mutable(): raise ValueError("surface must be immutable") if not similarity_surface.is_connected(): raise NotImplementedError("surface must be connected") if not similarity_surface.is_triangulated(): raise ValueError("surface must be triangulated") self._reference = similarity_surface # This surface will converge to the Delaunay Triangulation self._surface = LazyMutableOrientedSimilaritySurface(similarity_surface) # Set of labels corresponding to known delaunay polygons self._certified_labels = set() # Triangle flips (as morphisms) that have been performed so far. self._flips = [] # Triangulate the base polygon root = self._surface.root() # Certify the base polygon (or apply flips...) while not self._certify_or_improve(root): pass OrientedSimilaritySurface.__init__( self, self._surface.base_ring(), category=category or self._surface.category(), )
[docs] def is_mutable(self): r""" Return whether this surface is mutable, i.e., return ``False``. This implements :meth:`flatsurf.geometry.categories.topological_surfaces.TopologicalSurfaces.ParentMethods.is_mutable`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_triangulation() sage: S.is_mutable() False """ return False
[docs] def is_compact(self): r""" Return whether this surface is compact as a topological space. This implements :meth:`flatsurf.geometry.categories.topological_surfaces.TopologicalSurfaces.ParentMethods.is_compact`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_triangulation() sage: S.is_compact() False """ return self._reference.is_compact()
[docs] def roots(self): r""" Return root labels for the polygons forming the connected components of this surface. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.roots`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_triangulation() sage: S.roots() ((0, 0),) """ return self._surface.roots()
[docs] @cached_method def polygon(self, label): r""" Return the polygon with ``label``. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.polygon`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_triangulation() sage: S.polygon((0, 0)) Polygon(vertices=[(0, 0), (1, 0), (1, 1)]) """ if label not in self.labels(): raise ValueError("no polygon with this label") if label not in self._certified_labels: # If the label is not final in this surface, we walk the surface # and thereby certify its polygons until we find that label. # Note that this is somewhat inefficient since we start the walk # from the start every time. However, the certification process is # what consumes time, so unless there are a lot of labels, this # should not impact performance too much. for certified_label in self._walk(): if label == certified_label: assert label in self._certified_labels break return self._surface.polygon(label)
def _walk(self): r""" Return an iterator that walks the labels of the surface in order. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_triangulation() sage: from itertools import islice sage: list(islice(S._walk(), 3)) [(0, 0), (1, 1), (-1, 1)] """ visited = set() from collections import deque next = deque( [(self.root(), 0), (self.root(), 1), (self.root(), 2)], ) while next: label, edge = next.popleft() if label in visited: continue yield label visited.add(label) for edge in range(3): next.append(self.opposite_edge(label, edge))
[docs] @cached_method def opposite_edge(self, label, edge): r""" Return the polygon label and edge index when crossing over the ``edge`` of the polygon ``label``. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.opposite_edge`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_triangulation() sage: S.opposite_edge((0, 0), 0) ((1, 1), 1) """ self.polygon(label) while True: cross_label, cross_edge = self._surface.opposite_edge(label, edge) if self._certify_or_improve(cross_label): break return self._surface.opposite_edge(label, edge)
def _certify_or_improve(self, label): r""" This method attempts to develop the circumscribing disk about the polygon with label ``label`` into the surface. The method returns True if this is successful. In this case the label is added to the set _certified_labels. It returns False if it failed to develop the disk into the surface. (In this case the original polygon was not a Delaunay triangle. The algorithm divides any non-certified polygon in self._s it encounters into triangles. If it encounters a pair of triangles which need a diagonal flip then it does the flip. """ if label in self._certified_labels: # Already certified. return True p = self._surface.polygon(label) assert len(p.vertices()) == 3 c = p.circumscribing_circle() # Develop through each of the 3 edges: for e in range(3): edge_certified = False # This keeps track of a chain of polygons the disk develops through: edge_stack = [] # We repeat this until we can verify that the portion of the circle # that passes through the edge e developes into the surface. while not edge_certified: if len(edge_stack) == 0: # Start at the beginning with label l and edge e. # The 3rd coordinate in the tuple represents what edge to develop # through in the triangle opposite this edge. edge_stack = [(label, e, 1, c)] ll, ee, step, cc = edge_stack[len(edge_stack) - 1] lll, eee = self._surface.opposite_edge(ll, ee) if lll not in self._certified_labels: ppp = self._surface.polygon(lll) assert len(ppp.vertices()) == 3 if self._surface._delaunay_edge_needs_flip(ll, ee): # Perform the flip self._surface.triangle_flip(ll, ee, in_place=True) # If we touch the original polygon, then we return False. if label == ll or label == lll: return False # We might have flipped a polygon from earlier in the chain # In this case we need to trim the stack down so that we recheck # that polygon. for index, tup in enumerate(edge_stack): if tup[0] == ll or tup[0] == lll: edge_stack = edge_stack[:index] break # The following if statement makes sure that we check both subsequent edges of the # polygon opposite the last edge listed in the stack. if len(edge_stack) > 0: ll, ee, step, cc = edge_stack.pop() edge_stack.append((ll, ee, 1, cc)) continue # If we reach here then we know that no flip was needed. ccc = self._surface.edge_transformation(ll, ee) * cc # Check if the disk passes through the next edge in the chain. lp = ccc.line_segment_position( ppp.vertex((eee + step) % 3), ppp.vertex((eee + step + 1) % 3) ) if lp == 1: # disk passes through edge and opposite polygon is not certified. edge_stack.append((lll, (eee + step) % 3, 1, ccc)) continue # We reach this point if the disk doesn't pass through the edge eee+step of polygon lll. # Either lll is already certified or the disk didn't pass # through edge (lll,eee+step) # Trim off unnecessary edges off the stack. # prune_count=1 ll, ee, step, cc = edge_stack.pop() if step == 1: # if we have just done step 1 (one edge), move on to checking # the next edge. edge_stack.append((ll, ee, 2, cc)) # if we have pruned an edge, continue to look at pruning in the same way. while step == 2 and len(edge_stack) > 0: ll, ee, step, cc = edge_stack.pop() # prune_count= prune_count+1 if step == 1: edge_stack.append((ll, ee, 2, cc)) if len(edge_stack) == 0: # We're done with this edge edge_certified = True self._certified_labels.add(label) return True
[docs] def is_triangulated(self, limit=None): r""" Return whether this surface is triangulated, which it naturally is. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_triangulation() sage: S.is_triangulated() True """ if limit is not None: import warnings warnings.warn( "limit has been deprecated as a keyword argument for is_triangulated() and will be removed from a future version of sage-flatsurf; " "if you rely on this check, you can try to run this method on MutableOrientedSimilaritySurface.from_surface(surface, labels=surface.labels()[:limit])" ) return True
[docs] def is_delaunay_triangulated(self, limit=None): r""" Return whether this surface is Delaunay triangulated, which it naturally is. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_triangulation() sage: S.is_delaunay_triangulated() True """ if limit is not None: import warnings warnings.warn( "limit has been deprecated as a keyword argument for is_delaunay_triangulated() and will be removed from a future version of sage-flatsurf; " "if you rely on this check, you can try to run this method on MutableOrientedSimilaritySurface.from_surface(surface, labels=surface.labels()[:limit])" ) return True
[docs] def labels(self): r""" Return the labels of this surface. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.labels`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_triangulation() sage: S.labels() ((0, 0), (1, 1), (-1, 1), (0, 1), (1, 0), (2, 0), (-1, 0), (-2, 0), (2, 1), (3, 1), (-2, 1), (-3, 1), (3, 0), (4, 0), (-3, 0), (-4, 0), …) """ return self._surface.labels()
def __hash__(self): r""" Return a hash value for this surface that is compatible with :meth:`__eq__`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: hash(S.delaunay_triangulation()) == hash(S.delaunay_triangulation()) True """ return hash(self._reference) def __eq__(self, other): r""" Return whether this surface is indistinguishable from ``other``. See :meth:`SimilaritySurfaces.FiniteType._test_eq_surface` for details on this notion of equality. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: S.delaunay_triangulation() == S.delaunay_triangulation() True """ if not isinstance(other, LazyDelaunayTriangulatedSurface): return False return self._reference == other._reference def _repr_(self): r""" Return a printable representation of this surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_triangulation() """ reference = self._reference if isinstance(reference, LazyTriangulatedSurface): reference = reference._reference return f"Delaunay triangulation of {reference!r}"
[docs]class LazyDelaunaySurface(OrientedSimilaritySurface): r""" Delaunay cell decomposition of a (possibly infinite type) surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: m = matrix([[2, 1], [1, 1]]) sage: S = (m * S).delaunay_decomposition() sage: S.polygon(S.root()) Polygon(vertices=[(0, 0), (1, 0), (1, 1), (0, 1)]) sage: S.is_delaunay_decomposed() True sage: TestSuite(S).run() # long time (2s) sage: from flatsurf.geometry.lazy import LazyDelaunaySurface sage: isinstance(S, LazyDelaunaySurface) True :: sage: from flatsurf.geometry.chamanara import chamanara_surface sage: S = chamanara_surface(QQ(1/2)) sage: m = matrix([[3, 4], [-4, 3]]) * matrix([[4, 0],[0, 1/4]]) sage: S = (m * S).delaunay_decomposition() sage: S.is_delaunay_decomposed() True sage: TestSuite(S).run() # long time (1.5s) sage: from flatsurf.geometry.lazy import LazyDelaunaySurface sage: isinstance(S, LazyDelaunaySurface) True """ def __init__(self, similarity_surface, direction=None, relabel=None, category=None): if relabel is not None: if relabel: raise NotImplementedError( "the relabel keyword has been removed from LazyDelaunaySurface; use relabel() to use integer labels instead" ) else: import warnings warnings.warn( "the relabel keyword will be removed in a future version of sage-flatsurf; do not pass it explicitly anymore to LazyDelaunaySurface()" ) if direction is not None: direction = None import warnings warnings.warn( "the direction keyword argument has been deprecated for LazyDelaunayTriangulatedSurface and will be removed in a future version of sage-flatsurf; " "its value is ignored in this version of sage-flatsurf; if you see this message when restoring a pickle, the object might not be fully functional" ) if similarity_surface.is_mutable(): raise ValueError("surface must be immutable") if not similarity_surface.is_delaunay_triangulated(): raise ValueError("surface must be triangulated") self._reference = similarity_surface super().__init__( similarity_surface.base_ring(), category=category or similarity_surface.category(), )
[docs] @cached_method def polygon(self, label): r""" Return the polygon with ``label``. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.polygon`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_decomposition() sage: S.polygon((0, 0)) Polygon(vertices=[(0, 0), (1, 0), (1, 1), (0, 1)]) """ if label not in self._reference.labels(): raise ValueError("no polygon with this label") cell, edges = self._cell(label) if label != self._label(cell): raise ValueError("no polygon with this label") edges = [self._reference.polygon(edge[0]).edge(edge[1]) for edge in edges] from flatsurf import Polygon return Polygon(edges=edges)
@cached_method def _label(self, cell): r""" Return a canonical label for the Delaunay cell that is made up by the Delaunay triangles ``cell``. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_decomposition() sage: S._label(frozenset({ ....: (0, 0), ....: (0, 1)})) (0, 0) """ for label in self._reference.labels(): if label in cell: return label @cached_method def _normalize_label(self, label): r""" Return a canonical label for the Delaunay cell that contains the Delaunay triangle ``label``. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_decomposition() sage: S._normalize_label((0, 0)) (0, 0) sage: S._normalize_label((0, 1)) (0, 0) """ cell, _ = self._cell(label) return self._label(cell) @cached_method def _cell(self, label): r""" Return the labels of the Delaunay triangles that contain the Delaunay triangle ``label`` together with the interior edges in that cell. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_decomposition() This cell (a square) is formed by two triangles that form a cylinder, i.e., the two triangles are glued at two of their edges:: sage: S._cell((0, 0)) (frozenset({(0, 0), (0, 1)}), [((0, 0), 0), ((0, 0), 1), ((0, 1), 1), ((0, 1), 2)]) """ edges = [] cell = set() explore = [(label, 2), (label, 1), (label, 0)] while explore: triangle, edge = explore.pop() cell.add(triangle) delaunay = self._reference._delaunay_edge_needs_join(triangle, edge) if not delaunay: edges.append((triangle, edge)) continue cross_triangle, cross_edge = self._reference.opposite_edge(triangle, edge) for shift in [2, 1]: next_triangle, next_edge = cross_triangle, (cross_edge + shift) % 3 if (next_triangle, next_edge) in edges: raise NotImplementedError if (next_triangle, next_edge) in explore: raise NotImplementedError explore.append((next_triangle, next_edge)) cell = frozenset(cell) normalized_label = self._label(cell) if normalized_label != label: return self._cell(normalized_label) return cell, edges
[docs] @cached_method def opposite_edge(self, label, edge): r""" Return the polygon label and edge index when crossing over the ``edge`` of the polygon ``label``. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.opposite_edge`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_decomposition() sage: S.opposite_edge((0, 0), 0) ((1, 1), 2) """ if label not in self._reference.labels(): raise ValueError cell, edges = self._cell(label) if label != self._label(cell): raise ValueError edge = edges[edge] cross_triangle, cross_edge = self._reference.opposite_edge(*edge) cross_cell, cross_edges = self._cell(cross_triangle) cross_label = self._label(cross_cell) return cross_label, cross_edges.index((cross_triangle, cross_edge))
[docs] def roots(self): r""" Return root labels for the polygons forming the connected components of this surface. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.roots`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_decomposition() sage: S.roots() ((0, 0),) """ return self._reference.roots()
[docs] def is_compact(self): r""" Return whether this surface is compact as a topological space. This implements :meth:`flatsurf.geometry.categories.topological_surfaces.TopologicalSurfaces.ParentMethods.is_compact`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_decomposition() sage: S.is_compact() False """ return self._reference.is_compact()
[docs] def is_mutable(self): r""" Return whether this surface is mutable, i.e., return ``False``. This implements :meth:`flatsurf.geometry.categories.topological_surfaces.TopologicalSurfaces.ParentMethods.is_mutable`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_decomposition() sage: S.is_mutable() False """ return False
def __hash__(self): r""" Return a hash value for this surface that is compatible with :meth:`__eq__`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: hash(S.delaunay_decomposition()) == hash(S.delaunay_decomposition()) True """ return hash(self._reference) def __eq__(self, other): r""" Return whether this surface is indistinguishable from ``other``. See :meth:`SimilaritySurfaces.FiniteType._test_eq_surface` for details on this notion of equality. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: from flatsurf.geometry.lazy import LazyDelaunaySurface sage: S = translation_surfaces.infinite_staircase() sage: m = matrix([[2, 1], [1, 1]]) sage: S = (m * S).delaunay_triangulation() sage: S == S True """ if not isinstance(other, LazyDelaunaySurface): return False return self._reference == other._reference def _repr_(self): r""" Return a printable representation of this surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: S.delaunay_decomposition() Delaunay cell decomposition of The infinite staircase """ reference = self._reference if isinstance(reference, LazyDelaunayTriangulatedSurface): reference = reference._reference if isinstance(reference, LazyTriangulatedSurface): reference = reference._reference return f"Delaunay cell decomposition of {reference!r}"
[docs] def is_delaunay_decomposed(self, limit=None): r""" Return whether this surface is decomposed into Delaunay cells, which it naturally is. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: S.delaunay_decomposition().is_delaunay_decomposed() True """ if limit is not None: import warnings warnings.warn( "limit has been deprecated as a keyword argument for is_delaunay_decomposed() and will be removed from a future version of sage-flatsurf; " "if you rely on this check, you can try to run this method on MutableOrientedSimilaritySurface.from_surface(surface, labels=surface.labels()[:limit])" ) return True
[docs]class LazyRelabeledSurface(LazyOrientedSimilaritySurface): r""" A relabeled surface which forwards all requests to an underlying reference surface after translation of labels. Subclasses may override ``_to_reference_label`` and ``_from_reference_label`` to establish a custom mapping of labels. Otherwise, labels are mapped to the non-negative integers in order. EXAMPLES:: sage: from flatsurf.geometry.chamanara import chamanara_surface sage: S = chamanara_surface(1/2) TESTS:: sage: from flatsurf.geometry.lazy import LazyRelabeledSurface sage: isinstance(S, LazyRelabeledSurface) True """ def __init__(self, reference, category=None): super().__init__( base_ring=reference.base_ring(), reference=reference, category=category or reference.category(), ) def _to_reference_label(self, label): r""" Return the image of ``label`` in the underlying reference surface. EXAMPLES:: sage: from flatsurf.geometry.chamanara import chamanara_surface sage: S = chamanara_surface(1/2) sage: S._to_reference_label(0) (0, 1, 0) """ return self._reference.labels()[label] def _from_reference_label(self, reference_label): r""" Return the preimage of ``reference_label`` in the labels of this surface. EXAMPLES:: sage: from flatsurf.geometry.chamanara import chamanara_surface sage: S = chamanara_surface(1/2) sage: S._from_reference_label((0, 1, 0)) 0 """ label = 0 for lbl in self._reference.labels(): if lbl == reference_label: return label label += 1 raise ValueError def _test_label_map(self, **options): r""" Verify that :meth:`_to_reference_label` and :meth:`_from_reference_label` are inverse to each other. EXAMPLES:: sage: from flatsurf.geometry.chamanara import chamanara_surface sage: S = chamanara_surface(1/2) sage: S._test_label_map() """ tester = self._tester(**options) from itertools import islice for label in islice(self._reference.labels(), 30): tester.assertEqual( label, self._to_reference_label(self._from_reference_label(label)) ) for label in islice( range(30) if not self.is_finite_type() else range(len(self._reference.labels())), 30, ): tester.assertEqual( label, self._from_reference_label(self._to_reference_label(label)) )
[docs] def labels(self): r""" Return the labels of this surface after renaming. sage: from flatsurf.geometry.chamanara import chamanara_surface sage: S = chamanara_surface(1/2) sage: S.labels() (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, …) """ from flatsurf.geometry.surface import LabelsFromView if self._reference.is_finite_type(): return LabelsFromView(self, range(len(self._reference.labels()))) from sage.all import NN return LabelsFromView(self, NN)
[docs] def polygon(self, label): r""" Return the polygon with ``label`` in this surface. EXAMPLES:: sage: from flatsurf.geometry.chamanara import chamanara_surface sage: S = chamanara_surface(1/2) sage: S.polygon(0) Polygon(vertices=[(0, 0), (1, 0), (-1, 2), (-1, 1)]) """ return self._reference.polygon(self._to_reference_label(label))
[docs] def opposite_edge(self, label, edge): r""" Return the polygon label and its edge that is across from the polygon with ``label`` and its ``edge``. Return ``None`` if there is no polygon glued to that edge. EXAMPLES:: sage: from flatsurf.geometry.chamanara import chamanara_surface sage: S = chamanara_surface(1/2) sage: S.opposite_edge(0, 0) (1, 0) sage: S.opposite_edge(1, 0) (0, 0) """ label = self._to_reference_label(label) opposite_edge = self._reference.opposite_edge(label, edge) if opposite_edge is None: return None opposite_label, opposite_edge = opposite_edge opposite_label = self._from_reference_label(opposite_label) return opposite_label, opposite_edge
[docs] def roots(self): r""" Return the labels of the roots of the components of this surface. EXAMPLES:: sage: from flatsurf.geometry.chamanara import chamanara_surface sage: S = chamanara_surface(1/2) sage: S.roots() (0,) """ return tuple( self._from_reference_label(label) for label in self._reference.roots() )
def __hash__(self): r""" Return a hash value for this surface that is compatible with ``__eq__``. EXAMPLES:: sage: from flatsurf.geometry.chamanara import chamanara_surface sage: S = chamanara_surface(1/2) sage: hash(S) == hash(S) True """ return hash(self._reference) def __eq__(self, other): r""" Return whether this surface and ``other`` are indistinguishable. EXAMPLES:: sage: from flatsurf.geometry.chamanara import chamanara_surface sage: S = chamanara_surface(1/2) sage: T = chamanara_surface(1/2) sage: S == T True """ if type(self) != type(other): # Since we encourage subclassing this surface, we are very strict here. return False return self._reference == other._reference def _repr_(self): r""" Return a printable representation of this surface. Since the relabeling is often just done to make the labels a bit easier to work with, we do not mention it when printing this surface. EXAMPLES:: sage: from flatsurf.geometry.chamanara import chamanara_surface sage: S = chamanara_surface(1/2) sage: S Minimal Translation Cover of Chamanara surface with parameter 1/2 """ return repr(self._reference)