Source code for flatsurf.geometry.lazy

r"""
Triangulations, Delaunay triangulations, and Delaunay decompositions of
(possibly 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().codomain()
    Triangulation of The infinite staircase

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

    sage: S.delaunay_decompose().codomain()
    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().codomain() 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().codomain() sage: S._is_triangulated(0) True sage: S._is_triangulated(1) True sage: S = S.triangulate(label=0).codomain() 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().codomain() 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().codomain() 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().codomain() 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().codomain() 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).codomain() 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().codomain() 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().codomain() 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().codomain() 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().codomain() sage: S.is_triangulated() True sage: S = translation_surfaces.infinite_staircase().triangulate(label=0).codomain() 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().codomain()) == hash(S.triangulate().codomain()) 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().codomain() == S.triangulate().codomain() 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().codomain() 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().codomain() 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().codomain() 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().codomain() 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 a (possibly infinite type) surface. ALGORITHM: Basically we just flip edges that violate the Delaunay condition until everything is Delaunay. The complication arises because the surface can be infinite. The strategy is therefore, to perform the flips such that more and more triangles do not contain any vertices in their circumscribed circle. These triangles are then actual triangles of the Delaunay triangulation. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_triangulate().codomain() 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_triangulate().codomain() 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) # Labels of known Delaunay polygons in self._surface self._certified_labels = set() # The triangle flips that have been performed so far. # When a flip of (label, edge) happens, we record the pair ((label, edge), (opposite_label, opposite_edge)). self._flips = [] 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_triangulate().codomain() 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_triangulate().codomain() 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_triangulate().codomain() 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_triangulate().codomain() 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") self._certify(label) return self._surface.polygon(label)
def _certify(self, label): r""" Perform flips until ``label`` is final in the underlying partially Delauany triangulated surface. ALGORITHM: We walk all the labels of the surface and certify each one until we reach ``label``. (On infinite type surfaces that are not connected, this typically doesn't terminate.) To certify a label, we perform edge flips until we can show that no vertices are in the interior of the circumscribed circle of its triangle. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_triangulate().codomain() sage: S._certify((0, 0)) """ if label in self._certified_labels: return # Ensure that all previous labels are certified so that the shape of # the Delaunay triangulation does not depend on the access pattern on # it. (Removing this would likely make everything faster but it also # adds some strange randomness.) for lbl in self.labels(): if lbl == label: break self._certify(lbl) # To certify that a polygon is final, we need to make sure that there # are no vertices contained in its circumscribed circle C of radius R. # To that end, we walk the surface from that polygon until we have seen # all the polygons that contain points that are at distance <R from the # center of C. Whenever we see an edge that is not Delaunay, we flip # it. If in this walk we don't come across a vertex that is in the # interior of C, then we are done. Otherwise, we restart the process. # For finite type surfaces this is guaranteed to terminate. For # infinite type surfaces, there is of course no such guarantee. while not self._certify_walk(label): pass self._certified_labels.add(label) def _certify_flip(self, label, edge): assert label not in self._certified_labels assert self._surface.opposite_edge(label, edge)[0] not in self._certified_labels self._flips.append(((label, edge), self._surface.opposite_edge(label, edge))) self._surface.triangle_flip(label, edge, in_place=True) def _certify_walk(self, label): r""" Return whether there are no vertices contained in the circumscribed circle of the polygon with ``label``. Along the way, we flip any edges that are not Delaunay. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().apply_matrix(matrix([[1, 2], [0, 1]]), in_place=False).codomain().delaunay_triangulate().codomain() sage: S._certify_walk((0, 0)) False sage: S._certify((0, 0)) sage: S._certify_walk((0, 0)) True """ vertices_in_circumcircle = False done = set() todo = {(label, self._surface.polygon(label).circumscribed_circle())} modified = set() while todo: item = todo.pop() done.add(item) label, circle = item if label in modified: continue polygon = self._surface.polygon(label) for v in range(3): V = polygon.vertex(v) if circle.point_position(V) == 1: vertices_in_circumcircle = True for e in range(3): if ( circle.line_segment_position( polygon.vertex(e), polygon.vertex(e + 1) ) == 1 ): (opposite_label, opposite_edge) = self._surface.opposite_edge( label, e ) if self._surface._delaunay_edge_needs_flip(label, e): modified.add(label) modified.add(opposite_label) self._certify_flip(label, e) else: item = ( opposite_label, self._surface.edge_transformation(label, e) * circle, ) if item not in done: todo.add(item) return not vertices_in_circumcircle and not modified
[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_triangulate().codomain() sage: S.opposite_edge((0, 0), 0) ((1, 1), 1) """ self._certify(label) while True: cross_label, cross_edge = self._surface.opposite_edge(label, edge) if cross_label in self._certified_labels: return cross_label, cross_edge self._certify(cross_label)
[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_triangulate().codomain() 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_triangulate().codomain() 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_triangulate().codomain() 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_triangulate().codomain()) == hash(S.delaunay_triangulate().codomain()) 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_triangulate().codomain() == S.delaunay_triangulate().codomain() 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_triangulate().codomain() """ 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_decompose().codomain() 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_decompose().codomain() 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 Delaunay 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_decompose().codomain() 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_decompose().codomain() sage: S._label(frozenset({ ....: (0, 0), ....: (0, 1)})) (0, 0) """ for label in self._reference.labels(): if label in cell: return label assert False @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_decompose().codomain() 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 form the Delaunay cell that contains the Delaunay triangle ``label``, together with the exterior edges in that cell (in a counterclockwise order.) EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase().delaunay_decompose().codomain() 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_decompose().codomain() 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_decompose().codomain() 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_decompose().codomain() 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_decompose().codomain() 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_decompose().codomain()) == hash(S.delaunay_decompose().codomain()) 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_triangulate().codomain() 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_decompose().codomain() 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_decompose().codomain().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)