Source code for flatsurf.geometry.surface

r"""
Generic mutable and immutable surfaces

This module provides base classes and implementations of surfaces. Most
surfaces in sage-flatsurf inherit from some of the classes in this module.

The most important class in this module is
:class:`MutableOrientedSimilaritySurface` which allows you to create a surface
by gluing polygons with similarities.

EXAMPLES:

We build a translation surface by gluing two hexagons, labeled 0 and 1::

    sage: from flatsurf import MutableOrientedSimilaritySurface, polygons
    sage: S = MutableOrientedSimilaritySurface(QuadraticField(3))

    sage: S.add_polygon(polygons.regular_ngon(6))
    0
    sage: S.add_polygon(polygons.regular_ngon(6))
    1

    sage: S.glue((0, 0), (1, 3))
    sage: S.glue((0, 1), (1, 4))
    sage: S.glue((0, 2), (1, 5))
    sage: S.glue((0, 3), (1, 0))
    sage: S.glue((0, 4), (1, 1))
    sage: S.glue((0, 5), (1, 2))

    sage: S
    Translation Surface built from 2 regular hexagons

We signal that the construction is complete. This refines the category of the
surface and makes more functionality available::

    sage: S.set_immutable()
    sage: S
    Translation Surface in H_2(1^2) built from 2 regular hexagons

"""

# ********************************************************************
#  This file is part of sage-flatsurf.
#
#        Copyright (C) 2016-2020 Vincent Delecroix
#                           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/>.
# ********************************************************************
import collections.abc

from sage.structure.parent import Parent
from sage.misc.cachefunc import cached_method

from flatsurf.geometry.surface_objects import SurfacePoint


[docs] class Surface_base(Parent): r""" A base class for all surfaces in sage-flatsurf. This class patches bits of the category framework in SageMath that assume that all parent structures are immutable. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.square_torus() sage: from flatsurf.geometry.surface import Surface_base sage: isinstance(S, Surface_base) True """ def _refine_category_(self, category): r""" Refine the category of this surface to a subcategory ``category``. We need to override this method from ``Parent`` since we need to disable a hashing check that is otherwise enabled when doctesting. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.category() Category of finite type oriented similarity surfaces sage: S._refine_category_(S.refined_category()) sage: S.category() Category of connected without boundary finite type translation surfaces """ from sage.structure.debug_options import debug old_refine_category_hash_check = debug.refine_category_hash_check debug.refine_category_hash_check = False try: super()._refine_category_(category) finally: debug.refine_category_hash_check = old_refine_category_hash_check # The (cached) an_element is going to fail its test suite because it has the wrong category now. # Make sure that we create a new copy of an_element when requested. self._cache_an_element = None
[docs] def an_element(self): r""" Return a point of this surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.square_torus() sage: S.an_element() Point (1/2, 1/2) of polygon 0 """ # Do not use the builtin caching of an_element if this surface is mutable. if self.is_mutable(): return self._an_element_() return super().an_element()
[docs] class MutablePolygonalSurface(Surface_base): r""" A base class for mutable surfaces that are built by gluing polygons. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: from flatsurf.geometry.surface import MutablePolygonalSurface sage: isinstance(S, MutablePolygonalSurface) True """
[docs] def __init__(self, base, category=None): from sage.all import ZZ self._next_label = ZZ(0) self._roots = () self._polygons = {} self._mutable = True super().__init__(base, category=category)
def _test_refined_category(self, **options): r""" Test that this surface has been refined to its best possible subcategory (that can be computed cheaply.) We override this method here to disable this check for mutable surfaces. Mutable surfaces have not been refined yet since changes in the surface might require a widening of the category which is not possible. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S._test_refined_category() sage: S.set_immutable() sage: S._test_refined_category() """ if self._mutable: return super()._test_refined_category(**options)
[docs] def add_polygon(self, polygon, *, label=None): r""" Add an unglued polygon to this surface and return its label. INPUT: - ``polygon`` -- a simple Euclidean polygon - ``label`` -- a hashable identifier or ``None`` (default: ``None``); if ``None`` an integer identifier is automatically selected EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.add_polygon(polygons.square(), label=0) Traceback (most recent call last): ... ValueError: polygon label already present in this surface sage: S.add_polygon(polygons.square(), label='X') 'X' """ if not self._mutable: raise Exception("cannot modify an immutable surface") if label is None: while self._next_label in self._polygons: self._next_label += 1 label = self._next_label if label in self._polygons: raise ValueError("polygon label already present in this surface") self._polygons[label] = polygon.change_ring(self.base_ring()) return label
[docs] def add_polygons(self, polygons): r""" Add several polygons with automatically assigned labels at once. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: from flatsurf import polygons sage: S.add_polygons([polygons.square(), polygons.square()]) doctest:warning ... UserWarning: add_polygons() has been deprecated and will be removed in a future version of sage-flatsurf; use labels = [add_polygon(p) for p in polygons] instead [0, 1] """ import warnings warnings.warn( "add_polygons() has been deprecated and will be removed in a future version of sage-flatsurf; use labels = [add_polygon(p) for p in polygons] instead" ) return [self.add_polygon(p) for p in polygons]
[docs] def set_default_graphical_surface(self, graphical_surface): r""" EXAMPLES: This has been disabled because it tends to break caching:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.set_default_graphical_surface(S.graphical_surface()) Traceback (most recent call last): ... NotImplementedError: set_default_graphical_surface() has been removed from this version of sage-flatsurf. If you want to change the default plotting of a surface, create a subclass and override graphical_surface() instead """ raise NotImplementedError( "set_default_graphical_surface() has been removed from this version of sage-flatsurf. If you want to change the default plotting of a surface, create a subclass and override graphical_surface() instead" )
[docs] def remove_polygon(self, label): r""" Remove the polygon with label ``label`` from this surface (and all data associated to it.) EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.remove_polygon(0) sage: S.add_polygon(polygons.square()) 0 """ if not self._mutable: raise Exception("cannot modify an immutable surface") self._polygons.pop(label) self._roots = tuple(root for root in self._roots if root != label)
[docs] def roots(self): r""" Return a label for each connected component on this surface. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.roots`. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.roots() () sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.roots() (0,) sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 1 sage: S.roots() (0, 1) sage: S.glue((0, 0), (1, 0)) sage: S.roots() (0,) .. SEEALSO:: :meth:`components` """ return LabeledView( self, RootedComponents_MutablePolygonalSurface(self).keys(), finite=True )
[docs] def components(self): r""" Return the connected components as the sequence of their respective polygon labels. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.components() () sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.components() ((0,),) sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 1 sage: S.components() ((0,), (1,)) sage: S.glue((0, 0), (1, 0)) sage: S.components() ((0, 1),) """ return LabeledView( self, RootedComponents_MutablePolygonalSurface(self).values(), finite=True )
[docs] def polygon(self, label): r""" Return the polygon with label ``label`` in this surface. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.polygon`. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.polygon(0) Traceback (most recent call last): ... KeyError: 0 sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.polygon(0) Polygon(vertices=[(0, 0), (1, 0), (1, 1), (0, 1)]) """ return self._polygons[label]
[docs] def set_immutable(self): r""" Make this surface immutable. Any mutation attempts from now on will be an error. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.glue((0, 0), (0, 2)) sage: S.glue((0, 1), (0, 3)) Note that declaring a surface immutable refines its category and thereby unlocks more methods that are available to such a surface:: sage: S.category() Category of finite type oriented similarity surfaces sage: old_methods = set(method for method in dir(S) if not method.startswith('_')) sage: S.set_immutable() sage: S.category() Category of connected without boundary finite type translation surfaces sage: new_methods = set(method for method in dir(S) if not method.startswith('_')) sage: new_methods - old_methods {'affine_automorphism_group', 'area', 'canonicalize', 'canonicalize_mapping', 'erase_marked_points', 'holonomy_field', 'is_veering_triangulated', 'j_invariant', 'l_infinity_delaunay_triangulation', 'minimal_translation_cover', 'normalized_coordinates', 'pyflatsurf', 'rel_deformation', 'stratum', 'veech_group', 'veering_triangulation'} An immutable surface cannot be mutated anymore:: sage: S.remove_polygon(0) Traceback (most recent call last): ... Exception: cannot modify an immutable surface However, the category of an immutable might be further refined as (expensive to determine) features of the surface are deduced. """ if self._mutable: self.set_roots(self.roots()) self._mutable = False self._refine_category_(self.refined_category())
[docs] def is_mutable(self): r""" Return whether this surface can be modified. This implements :meth:`flatsurf.geometry.categories.topological_surfaces.TopologicalSurfaces.ParentMethods.is_mutable`. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.is_mutable() True sage: S.set_immutable() sage: S.is_mutable() False """ return self._mutable
[docs] def __eq__(self, other): r""" Return whether this surface is indistinguishable from ``other``. See :meth:`~.categories.similarity_surfaces.SimilaritySurfaces.FiniteType.ParentMethods._test_eq_surface` for details on this notion of equality. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: T = MutableOrientedSimilaritySurface(QQ) sage: S == T True sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S == T False TESTS:: sage: S = MutableOrientedSimilaritySurface(QQ) sage: T = MutableOrientedSimilaritySurface(QQ) sage: S != T False sage: S.add_polygon(polygons.square()) 0 sage: S != T True """ if not isinstance(other, MutablePolygonalSurface): return False if self.base() != other.base(): return False if self._polygons != other._polygons: return False # Note that the order of the root labels matters since it changes the order of iteration in labels() if self._roots != other._roots: return False if self._mutable != other._mutable: return False return True
[docs] def __hash__(self): r""" Return a hash value for this surface that is compatible with :meth:`__eq__`. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: T = MutableOrientedSimilaritySurface(QQ) sage: hash(S) == hash(T) Traceback (most recent call last): ... TypeError: cannot hash a mutable surface sage: S.set_immutable() sage: T.set_immutable() sage: hash(S) == hash(T) True """ if self._mutable: raise TypeError("cannot hash a mutable surface") return hash((tuple(self.labels()), tuple(self.polygons()), self._roots))
def _repr_(self): r""" Return a printable representation of this surface. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S Empty Surface sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S Translation Surface with boundary built from a square """ if not self.is_finite_type(): return "Surface built from infinitely many polygons" if len(self.labels()) == 0: return "Empty Surface" return f"{self._describe_surface()} built from {self._describe_polygons()}" def _describe_surface(self): r""" Return a string describing this kind of surface. This is a helper method for :meth:`_repr_`. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S._describe_surface() 'Translation Surface' """ return "Surface" def _describe_polygons(self): r""" Return a string describing the nature of the polygons that make up this surface. This is a helper method for :meth:`_repr_`. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S._describe_polygons() '' """ polygons = [ (-len(p.erase_marked_vertices().vertices()), p.describe_polygon()) for p in self.polygons() ] polygons.sort() polygons = [description for (edges, description) in polygons] if not polygons: return "" collated = [] while polygons: count = 1 polygon = polygons.pop() while polygons and polygons[-1] == polygon: count += 1 polygons.pop() if count == 1: collated.append(f"{polygon[0]} {polygon[1]}") else: collated.append(f"{count} {polygon[2]}") description = collated.pop() if collated: description = ", ".join(collated) + " and " + description return description
[docs] def set_root(self, root): r""" Set ``root`` as the label at which exploration of a connected component starts. This method can be called for connected and disconnected surfaces. In either case, it establishes ``root`` as the new label from which enumeration of the connected component containing it starts. If another label for this component had been set earlier, it is replaced. .. NOTE:: After roots have been declared explicitly, gluing operations come at an additional cost since the root labels have to be updated sometimes. It is therefore good practice to declare the root labels after all the gluings have been established when creating a surface. INPUT: - ``root`` -- a polygon label in this surface EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.set_root(0) Traceback (most recent call last): ... ValueError: root must be a label in the surface sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.add_polygon(polygons.square()) 1 sage: S.set_root(0) sage: S.set_root(1) sage: S.roots() (0, 1) Note that the roots get updated automatically when merging components:: sage: S.glue((0, 0), (1, 0)) sage: S.roots() (0,) The purpose of changing the root label is to modify the order of exploration, e.g., in :meth:`labels`:: sage: S.labels() (0, 1) sage: S.set_root(1) sage: S.labels() (1, 0) .. SEEALSO:: :meth:`set_roots` to replace all the root labels """ if not self._mutable: raise Exception("cannot modify an immutable surface") root = [label for label in self.labels() if label == root] if not root: raise ValueError("root must be a label in the surface") assert len(root) == 1 root = root[0] component = [component for component in self.components() if root in component] assert len(component) == 1 component = component[0] self._roots = tuple(r for r in self._roots if r not in component) + (root,)
[docs] def set_roots(self, roots): r""" Declare that the labels in ``roots`` are the labels from which their corresponding connected components should be enumerated. There must be at most one label for each connected component in ``roots``. Components that have no label set explicitly will have their label chosen automatically. INPUT: - ``roots`` -- a sequence of polygon labels in this surface EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.add_polygon(polygons.square()) 1 sage: S.add_polygon(polygons.square()) 2 sage: S.glue((0, 0), (1, 0)) sage: S.set_roots([1]) sage: S.roots() (1, 2) Setting the roots of connected components affects their enumeration in :meth:`labels`:: sage: S.labels() (1, 0, 2) sage: S.set_roots([0, 2]) sage: S.labels() (0, 1, 2) There must be at most one root per component:: sage: S.set_roots([0, 1, 2]) Traceback (most recent call last): ... ValueError: there must be at most one root label for each connected component """ if not self._mutable: raise Exception("cannot modify an immutable surface") roots = [[label for label in self.labels() if label == root] for root in roots] if any(len(root) == 0 for root in roots): raise ValueError("roots must be existing labels in the surface") assert all(len(root) == 1 for root in roots) roots = tuple(root[0] for root in roots) for component in self.components(): if len([root for root in roots if root in component]) > 1: raise ValueError( "there must be at most one root label for each connected component" ) self._roots = tuple(roots)
[docs] def change_base_label(self, label): r""" This is a deprecated alias for :meth:`set_root`. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.change_base_label(0) doctest:warning ... UserWarning: change_base_label() has been deprecated and will be removed in a future version of sage-flatsurf; use set_root() instead """ import warnings warnings.warn( "change_base_label() has been deprecated and will be removed in a future version of sage-flatsurf; use set_root() instead" ) self.set_root(label)
[docs] @cached_method def labels(self): r""" Return the polygon labels in this surface. This replaces the generic :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.labels` method with a more efficient implementation. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.labels() () sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.labels() (0,) .. SEEALSO:: :meth:`polygon` to translate polygon labels to the corresponding polygons :meth:`polygons` for the corresponding sequence of polygons """ return LabelsFromView(self, self._polygons.keys(), finite=True)
[docs] @cached_method def polygons(self): r""" Return the polygons that make up this surface. The order the polygons are returned is guaranteed to be compatible with the order of the labels in :meth:`~.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.labels`. This replaces the generic :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.polygons` with a more efficient implementation. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.polygons() () sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.add_polygon(polygons.square()) 1 sage: S.add_polygon(polygons.square()) 2 sage: S.polygons() (Polygon(vertices=[(0, 0), (1, 0), (1, 1), (0, 1)]), Polygon(vertices=[(0, 0), (1, 0), (1, 1), (0, 1)]), Polygon(vertices=[(0, 0), (1, 0), (1, 1), (0, 1)])) .. SEEALSO:: :meth:`polygon` to get a single polygon """ return Polygons_MutableOrientedSimilaritySurface(self)
[docs] class OrientedSimilaritySurface(Surface_base): r""" Base class for surfaces built from Euclidean polygons that are glued with orientation preserving similarities. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: from flatsurf.geometry.surface import OrientedSimilaritySurface sage: isinstance(S, OrientedSimilaritySurface) True """ Element = SurfacePoint
[docs] def __init__(self, base, category=None): from sage.categories.all import Rings if base not in Rings(): raise TypeError("base ring must be a ring") from flatsurf.geometry.categories import SimilaritySurfaces if category is None: category = SimilaritySurfaces().Oriented() category &= SimilaritySurfaces().Oriented() super().__init__(base, category=category)
def _describe_surface(self): if not self.is_finite_type(): return "Surface built from infinitely many polygons" if not self.is_connected(): # Many checks do not work yet if a surface is not connected, so we stop here. return "Disconnected Surface" if self.is_translation_surface(positive=True): description = "Translation Surface" elif self.is_translation_surface(positive=False): description = "Half-Translation Surface" elif self.is_dilation_surface(positive=True): description = "Positive Dilation Surface" elif self.is_dilation_surface(positive=False): description = "Dilation Surface" elif self.is_cone_surface(): description = "Cone Surface" if self.is_rational_surface(): description = f"Rational {description}" else: description = "Surface" if hasattr(self, "stratum"): try: description += f" in {self.stratum()}" except NotImplementedError: # Computation of the stratum might fail due to #227. pass elif self.genus is not NotImplemented: description = f"Genus {self.genus()} {description}" if self.is_with_boundary(): description += " with boundary" return description
[docs] class MutableOrientedSimilaritySurface_base(OrientedSimilaritySurface): r""" Base class for surface built from Euclidean polyogns glued by orientation preserving similarities. This provides the features of :class:`MutableOrientedSimilaritySurface` without making a choice about how data is stored internally; it is a generic base class for other implementations of mutable surfaces. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: from flatsurf.geometry.surface import MutableOrientedSimilaritySurface_base sage: isinstance(S, MutableOrientedSimilaritySurface_base) True """
[docs] def triangle_flip(self, label, edge, in_place=False, test=None, direction=None): r""" Overrides :meth:`.categories.similarity_surfaces.SimilaritySurfaces.Oriented.ParentMethods.triangle_flip` to provide in-place flipping of triangles. See that method for details. """ if test is not None: if not test: import warnings warnings.warn( "the test keyword has been deprecated in triangle_flip and will be removed in a future version of sage-flatsurf; it should not be passed in anymore" ) else: import warnings warnings.warn( "the test keyword has been deprecated in triangle_flip and will be removed in a future version of sage-flatsurf; is is_convex(strict=True) instead." ) if len(self.polygon(label).vertices()) != 3: return False if ( len(self.polygon(self.opposite_edge(label, edge)[0]).vertices()) != 3 ): return False return self.is_convex(label, edge, strict=True) if direction is not None: import warnings warnings.warn( "the direction keyword has been removed from triangle_flip(); the diagonal of the quadrilateral is now always turned counterclockwise" ) if not in_place: return super().triangle_flip(label=label, edge=edge, in_place=in_place) opposite_label, opposite_edge = self.opposite_edge(label, edge) label = [label, opposite_label] diagonal = [edge, opposite_edge] P = [self.polygon(lbl) for lbl in label] if any(len(p.vertices()) != 3 for p in P): raise ValueError("attached polygons must be triangles") if not self.is_convex(label[0], edge, strict=True): raise ValueError( "cannot flip this edge because the surrounding quadrilateral is not strictly convex" ) from flatsurf import Polygon T = self.edge_transformation(label[1], diagonal[1]) P[1] = Polygon(vertices=[T(v) for v in P[1].vertices()]) gluings = [ [self.opposite_edge(lbl, (d + i) % 3) for i in range(3)] for (lbl, d) in zip(label, diagonal) ] assert P[0].vertex(diagonal[0]) == P[1].vertex(diagonal[1] + 1) assert P[0].vertex(diagonal[0] + 1) == P[1].vertex(diagonal[1]) Q = [ [ P[1 - i].vertex(diagonal[1 - i] + 2), P[i].vertex(diagonal[i] + 2), P[i].vertex(diagonal[i]), ] for i in range(2) ] Q = [ Polygon(vertices=Q[i][3 - diagonal[i] :] + Q[i][: 3 - diagonal[i]]) for i in range(2) ] # Shift the new polygons to the origin so things don't seem to wiggle # around randomly. Q = [q.translate(-q.vertex(0)) for q in Q] for i in range(2): self.replace_polygon(label[i], Q[i]) self.glue((label[0], diagonal[0]), (label[1], diagonal[1])) def to_new(lbl, edge): for i in range(2): if lbl == label[i]: assert edge != diagonal[i] if edge == (diagonal[i] + 1) % 3: return label[1 - i], (diagonal[1 - i] + 2) % 3 assert edge == (diagonal[i] + 2) % 3 return label[i], (diagonal[i] + 1) % 3 return lbl, edge for i in range(2): self.glue((label[i], (diagonal[i] + 1) % 3), to_new(*gluings[i][2])) self.glue((label[i], (diagonal[i] + 2) % 3), to_new(*gluings[1 - i][1])) return self
[docs] def standardize_polygons(self, in_place=False): r""" Replace each polygon with a new polygon which differs by translation and reindexing. The new polygon will have the property that vertex zero is the origin, and all vertices lie either in the upper half plane, or on the x-axis with non-negative x-coordinate. This is done to the current surface if in_place=True. A mutable copy is created and returned if in_place=False (as default). This overrides :meth:`flatsurf.geometry.categories.similarity_surfaces.SimilaritySurfaces.FiniteType.Oriented.ParentMethods.standardize_polygons` to provide in-place standardizing of surfaces. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface, Polygon sage: p = Polygon(vertices = ([(1,1),(2,1),(2,2),(1,2)])) sage: s = MutableOrientedSimilaritySurface(QQ) sage: s.add_polygon(p) 0 sage: s.glue((0, 0), (0, 2)) sage: s.glue((0, 1), (0, 3)) sage: s.set_root(0) sage: s.set_immutable() sage: s.standardize_polygons().polygon(0) Polygon(vertices=[(0, 0), (1, 0), (1, 1), (0, 1)]) """ if not in_place: S = MutableOrientedSimilaritySurface.from_surface(self) S.standardize_polygons(in_place=True) return S cv = {} # dictionary for non-zero canonical vertices for label, polygon in zip(self.labels(), self.polygons()): best = 0 best_pt = polygon.vertex(best) for v in range(1, len(polygon.vertices())): pt = polygon.vertex(v) if (pt[1] < best_pt[1]) or (pt[1] == best_pt[1] and pt[0] < best_pt[0]): best = v best_pt = pt # We replace the polygon if the best vertex is not the zero vertex, or # if the coordinates of the best vertex differs from the origin. if not (best == 0 and best_pt.is_zero()): cv[label] = best for label, v in cv.items(): self.set_vertex_zero(label, v, in_place=True) return self
[docs] class MutableOrientedSimilaritySurface( MutableOrientedSimilaritySurface_base, MutablePolygonalSurface ): r""" A surface built from Euclidean polyogns glued by orientation preserving similarities. This is the main tool to create new surfaces of finite type in sage-flatsurf. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.glue((0, 0), (0, 2)) sage: S.glue((0, 1), (0, 3)) sage: S.set_immutable() sage: S Translation Surface in H_1(0) built from a square TESTS:: sage: TestSuite(S).run() """
[docs] def __init__(self, base, category=None): self._gluings = {} from flatsurf.geometry.categories import SimilaritySurfaces if category is None: category = SimilaritySurfaces().Oriented().FiniteType() category &= SimilaritySurfaces().Oriented().FiniteType() super().__init__(base, category=category)
[docs] @classmethod def from_surface(cls, surface, labels=None, category=None): r""" Return a mutable copy of ``surface``. INPUT: - ``labels`` -- a set of labels or ``None`` (default: ``None``); if ``None``, the entire surface is copied, otherwise only these labels are copied and glued like in the original surface. EXAMPLES:: sage: from flatsurf import translation_surfaces, MutableOrientedSimilaritySurface, polygons sage: T = translation_surfaces.square_torus() We build a surface that is made from two tori: sage: S = MutableOrientedSimilaritySurface.from_surface(T) sage: S.add_polygon(polygons.square()) 1 sage: S.glue((1, 0), (1, 2)) sage: S.glue((1, 1), (1, 3)) sage: S.set_immutable() sage: S Disconnected Surface built from 2 squares We can build partial copies of an infinite surface:: sage: S = translation_surfaces.infinite_staircase() sage: T = MutableOrientedSimilaritySurface.from_surface(S) Traceback (most recent call last): ... TypeError: cannot create a full copy of an infinite surface sage: T = MutableOrientedSimilaritySurface.from_surface(S, labels=S.labels()[:10]) sage: T.components() ((0, 1, -1, 2, -2, 3, -3, 4, -4, 5),) sage: T = MutableOrientedSimilaritySurface.from_surface(S, labels=S.labels()[:30:3]) sage: T.components() ((0,), (-12,), (-9,), (-6,), (-3,), (2,), (5,), (8,), (11,), (14,)) """ if labels is None: if not surface.is_finite_type(): raise TypeError("cannot create a full copy of an infinite surface") labels = surface.labels() self = MutableOrientedSimilaritySurface(surface.base_ring(), category=category) for label in labels: self.add_polygon(surface.polygon(label), label=label) for label in labels: for edge in range(len(surface.polygon(label).vertices())): cross = surface.opposite_edge(label, edge) if cross and cross[0] in labels: self.glue((label, edge), cross) if isinstance(surface, MutablePolygonalSurface): # Only copy explicitly set roots over if surface._roots: self.set_roots(root for root in surface._roots if root in labels) else: self.set_roots(root for root in surface.roots() if root in labels) return self
[docs] def add_polygon(self, polygon, *, label=None): # Overrides add_polygon from MutablePolygonalSurface label = super().add_polygon(polygon, label=label) assert label not in self._gluings self._gluings[label] = [None] * len(polygon.vertices()) if self._roots: self._roots = self._roots + (label,) return label
[docs] def remove_polygon(self, label): # Overrides remove_polygon from MutablePolygonalSurface self._unglue_polygon(label) self._gluings.pop(label) super().remove_polygon(label)
[docs] def glue(self, x, y): r""" Glue ``x`` and ``y`` with an (orientation preserving) similarity. INPUT: - ``x`` -- a pair consisting of a polygon label and an edge index for that polygon - ``y`` -- a pair consisting of a polygon label and an edge index for that polygon EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface, polygons sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.add_polygon(polygons.square()) 0 Glue two opposite sides of the square to each other:: sage: S.glue((0, 1), (0, 3)) Glue the other sides of the square to themselves:: sage: S.glue((0, 0), (0, 0)) sage: S.glue((0, 2), (0, 2)) Note that existing gluings are removed when gluing already glued sides:: sage: S.glue((0, 0), (0, 2)) sage: S.set_immutable() sage: S Translation Surface in H_1(0) built from a square """ if not self._mutable: raise Exception( "cannot modify immutable surface; create a copy with MutableOrientedSimilaritySurface.from_surface()" ) if x[0] not in self._polygons: raise ValueError if y[0] not in self._polygons: raise ValueError self.unglue(*x) self.unglue(*y) if self._roots: component = set(self.component(x[0])) if y[0] not in component: # Gluing will join two connected components. cross_component = set(self.component(y[0])) for root in reversed(self._roots): if root in component or root in cross_component: self._roots = tuple([r for r in self._roots if r != root]) break else: assert False, "did not find any root to eliminate" self._gluings[x[0]][x[1]] = y self._gluings[y[0]][y[1]] = x
[docs] def unglue(self, label, edge): r""" Unglue the side ``edge`` of the polygon ``label`` if it is glued. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface, translation_surfaces sage: T = translation_surfaces.square_torus() sage: S = MutableOrientedSimilaritySurface.from_surface(T) sage: S.unglue(0, 0) sage: S.gluings() (((0, 1), (0, 3)), ((0, 3), (0, 1))) sage: S.set_immutable() sage: S Translation Surface with boundary built from a square """ if not self._mutable: raise Exception( "cannot modify immutable surface; create a copy with MutableOrientedSimilaritySurface.from_surface()" ) cross = self._gluings[label][edge] if cross is not None: self._gluings[cross[0]][cross[1]] = None self._gluings[label][edge] = None if cross is not None and self._roots: component = set(self.component(label)) if cross[0] not in component: # Ungluing created a new connected component. cross_component = set(self.component(cross[0])) assert label not in cross_component for root in self._roots: if root in component: self._roots = self._roots + ( LabeledView( surface=self, view=cross_component, finite=True ).min(), ) break if root in cross_component: self._roots = self._roots + ( LabeledView( surface=self, view=component, finite=True ).min(), ) break else: assert False, "did not find any root to split"
def _unglue_polygon(self, label): r""" Remove all gluigns from polygon ``label``. This is a helper method to completely unglue a polygon before removing or replacing it. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface, translation_surfaces sage: T = translation_surfaces.square_torus() sage: S = MutableOrientedSimilaritySurface.from_surface(T) sage: S._unglue_polygon(0) sage: S.gluings() () """ for edge, cross in enumerate(self._gluings[label]): if cross is None: continue cross_label, cross_edge = cross self._gluings[cross_label][cross_edge] = None self._gluings[label] = [None] * len(self.polygon(label).vertices())
[docs] def set_edge_pairing(self, label0, edge0, label1, edge1): r""" TESTS:: sage: from flatsurf import polygons, MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.add_polygon(polygons.square()) 0 sage: S.set_edge_pairing(0, 0, 0, 2) doctest:warning ... UserWarning: set_edge_pairing(label0, edge0, label1, edge1) has been deprecated and will be removed in a future version of sage-flatsurf; use glue((label0, edge0), (label1, edge1)) instead sage: S.set_edge_pairing(0, 1, 0, 3) sage: S.gluings() (((0, 0), (0, 2)), ((0, 1), (0, 3)), ((0, 2), (0, 0)), ((0, 3), (0, 1))) """ import warnings warnings.warn( "set_edge_pairing(label0, edge0, label1, edge1) has been deprecated and will be removed in a future version of sage-flatsurf; use glue((label0, edge0), (label1, edge1)) instead" ) return self.glue((label0, edge0), (label1, edge1))
[docs] def change_edge_gluing(self, label0, edge0, label1, edge1): r""" TESTS:: sage: from flatsurf import polygons, MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.add_polygon(polygons.square()) 0 sage: S.change_edge_gluing(0, 0, 0, 2) doctest:warning ... UserWarning: change_edge_gluing(label0, edge0, label1, edge1) has been deprecated and will be removed in a future version of sage-flatsurf; use glue((label0, edge0), (label1, edge1)) instead sage: S.change_edge_gluing(0, 1, 0, 3) sage: S.gluings() (((0, 0), (0, 2)), ((0, 1), (0, 3)), ((0, 2), (0, 0)), ((0, 3), (0, 1))) """ import warnings warnings.warn( "change_edge_gluing(label0, edge0, label1, edge1) has been deprecated and will be removed in a future version of sage-flatsurf; use glue((label0, edge0), (label1, edge1)) instead" ) return self.glue((label0, edge0), (label1, edge1))
[docs] def change_polygon_gluings(self, label, gluings): r""" TESTS:: sage: from flatsurf import polygons, MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.add_polygon(polygons.square()) 0 sage: S.change_polygon_gluings(0, [(0, 2), (0, 3), (0, 0), (0, 1)]) doctest:warning ... UserWarning: change_polygon_gluings() has been deprecated and will be removed in a future version of sage-flatsurf; use glue() in a loop instead sage: S.gluings() (((0, 0), (0, 2)), ((0, 1), (0, 3)), ((0, 2), (0, 0)), ((0, 3), (0, 1))) """ import warnings warnings.warn( "change_polygon_gluings() has been deprecated and will be removed in a future version of sage-flatsurf; use glue() in a loop instead" ) for edge0, cross in enumerate(gluings): if cross is None: self.unglue(label, edge0) else: self.glue((label, edge0), cross)
[docs] def change_polygon(self, label, polygon, gluing_list=None): r""" TESTS:: sage: from flatsurf import MutableOrientedSimilaritySurface, translation_surfaces sage: T = translation_surfaces.square_torus() sage: S = MutableOrientedSimilaritySurface.from_surface(T) sage: S.change_polygon(0, 2 * S.polygon(0)) doctest:warning ... UserWarning: change_polygon() has been deprecated and will be removed in a future version of sage-flatsurf; use replace_polygon() or remove_polygon() and add_polygon() instead sage: S.polygon(0) Polygon(vertices=[(0, 0), (2, 0), (2, 2), (0, 2)]) """ import warnings warnings.warn( "change_polygon() has been deprecated and will be removed in a future version of sage-flatsurf; use replace_polygon() or remove_polygon() and add_polygon() instead" ) if not self._mutable: raise Exception( "cannot modify immutable surface; create a copy with MutableOrientedSimilaritySurface.from_surface()" ) # Note that this obscure feature. If the number of edges is unchanged, we keep the gluings, otherwise we trash them all. if len(polygon.vertices()) != len(self.polygon(label).vertices()): self._unglue_polygon(label) self._gluings[label] = [None] * len(polygon.vertices()) self._polygons[label] = polygon if gluing_list is not None: for i, cross in enumerate(gluing_list): self.glue((label, i), cross)
[docs] def refine_polygon(self, label, surface, gluings): old = self.polygon(label) old_gluings = [self.opposite_edge(label, e) for e in range(len(old.vertices()))] self.remove_polygon(label) for surface_label in surface.labels(): self.add_polygon(surface.polygon(surface_label), label=surface_label) for a, b in surface.gluings(): self.glue(a, b) for edge, opposite in gluings.items(): if old_gluings[edge][0] == label: self.glue(gluings[old_gluings[edge][1]], opposite) else: self.glue(old_gluings[edge], opposite)
[docs] def replace_polygon(self, label, polygon): r""" Replace the polygon ``label`` with ``polygon`` while keeping its gluings intact. INPUT: - ``label`` -- an element of :meth:`~.MutablePolygonalSurface.labels` - ``polygon`` -- a Euclidean polygon EXAMPLES:: sage: from flatsurf import Polygon, MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.add_polygon(Polygon(vertices=[(0, 0), (1, 0), (1, 1), (0, 1)])) 0 sage: S.glue((0, 0), (0, 2)) sage: S.glue((0, 1), (0, 3)) sage: S.replace_polygon(0, Polygon(vertices=[(0, 0), (2, 0), (2, 2), (0, 2)])) The replacement of a polygon must have the same number of sides:: sage: S.replace_polygon(0, Polygon(vertices=[(0, 0), (2, 0), (2, 2)])) Traceback (most recent call last): ... ValueError: polygon must be a quadrilateral To replace the polygon without keeping its glueings, remove the polygon first and then add a new one:: sage: S.remove_polygon(0) sage: S.add_polygon(Polygon(vertices=[(0, 0), (2, 0), (2, 2)]), label=0) 0 """ old = self.polygon(label) if len(old.vertices()) != len(polygon.vertices()): from flatsurf.geometry.categories.polygons import Polygons article, singular, plural = Polygons._describe_polygon(len(old.vertices())) raise ValueError(f"polygon must be {article} {singular}") self._polygons[label] = polygon
[docs] def opposite_edge(self, label, edge=None): r""" Return the edge that ``edge`` of ``label`` is glued to or ``None`` if this edge is unglued. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.opposite_edge`. INPUT: - ``label`` -- one of the labels included in :meth:`~.MutablePolygonalSurface.labels` - ``edge`` -- a non-negative integer to specify an edge (the edges of a polygon are numbered starting from zero.) EXAMPLES:: sage: from flatsurf import Polygon, MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: S.add_polygon(Polygon(vertices=[(0, 0), (1, 0), (1, 1), (0, 1)])) 0 sage: S.glue((0, 0), (0, 1)) sage: S.glue((0, 2), (0, 2)) sage: S.opposite_edge(0, 0) (0, 1) sage: S.opposite_edge(0, 1) (0, 0) sage: S.opposite_edge(0, 2) (0, 2) sage: S.opposite_edge(0, 3) sage: S.opposite_edge((0, 0)) doctest:warning ... UserWarning: calling opposite_edge() with a single argument has been deprecated and will be removed in a future version of sage-flatsurf; use opposite_edge(label, edge) instead (0, 1) """ if edge is None: import warnings warnings.warn( "calling opposite_edge() with a single argument has been deprecated and will be removed in a future version of sage-flatsurf; use opposite_edge(label, edge) instead" ) label, edge = label return self._gluings[label][edge]
[docs] def set_vertex_zero(self, label, v, in_place=False): r""" Overrides :meth:`flatsurf.geometry.categories.similarity_surfaces.SimilaritySurfaces.Oriented.ParentMethods.set_vertex_zero` to make it possible to set the zero vertex in-place. """ if not in_place: return super().set_vertex_zero(label, v, in_place=in_place) us = self if not us.is_mutable(): raise ValueError( "set_vertex_zero can only be done in_place for a mutable surface." ) p = us.polygon(label) n = len(p.vertices()) if not (0 <= v < n): raise ValueError glue = [] from flatsurf import Polygon pp = Polygon( edges=[p.edge((i + v) % n) for i in range(n)], base_ring=us.base_ring() ) for i in range(n): e = (v + i) % n ll, ee = us.opposite_edge(label, e) if ll == label: ee = (ee + n - v) % n glue.append((ll, ee)) us.remove_polygon(label) us.add_polygon(pp, label=label) for e, cross in enumerate(glue): us.glue((label, e), cross) return self
[docs] def relabel(self, relabeling=None, in_place=False): r""" Overrides :meth:`flatsurf.geometry.categories.similarity_surfaces.SimilaritySurfaces.Oriented.ParentMethods.relabel` to allow relabeling in-place. """ if not in_place: return super().relabel(relabeling=relabeling, in_place=in_place) if relabeling is None: relabeling = {label: l for (l, label) in enumerate(self.labels())} if callable(relabeling): relabeling = {label: relabeling(label) for label in self.labels()} polygons = {label: self.polygon(label) for label in self.labels()} old_gluings = { label: [ self.opposite_edge(label, e) for e in range(len(self.polygon(label).vertices())) ] for label in self.labels() } roots = list(self.roots()) labels = list(self.labels()) for label in labels: self.remove_polygon(label) for label in labels: self.add_polygon(polygons[label], label=relabeling.get(label, label)) for label, gluings in old_gluings.items(): for e, gluing in enumerate(gluings): if gluing is None: continue opposite_label, opposite_edge = gluing self.glue( (relabeling.get(label, label), e), (relabeling.get(opposite_label, opposite_label), opposite_edge), ) self.set_roots([relabeling.get(root, root) for root in roots]) return self
[docs] def join_polygons(self, p1, e1, test=False, in_place=False): r""" Overrides :meth:`flatsurf.geometry.categories.similarity_surfaces.SimilaritySurfaces.Oriented.ParentMethods.join_polygons` to allow joining in-place. """ if test: in_place = False if not in_place: return super().join_polygon(p1, e1, test=test, in_place=in_place) poly1 = self.polygon(p1) p2, e2 = self.opposite_edge(p1, e1) poly2 = self.polygon(p2) if p1 == p2: raise ValueError("Can't glue polygon to itself.") t = self.edge_transformation(p2, e2) dt = t.derivative() es = [] edge_map = {} # Store the pairs for the old edges. for i in range(e1): edge_map[len(es)] = (p1, i) es.append(poly1.edge(i)) ne = len(poly2.vertices()) for i in range(1, ne): ee = (e2 + i) % ne edge_map[len(es)] = (p2, ee) es.append(dt * poly2.edge(ee)) for i in range(e1 + 1, len(poly1.vertices())): edge_map[len(es)] = (p1, i) es.append(poly1.edge(i)) from flatsurf import Polygon new_polygon = Polygon(edges=es, base_ring=self.base_ring()) # Do the gluing. ss = self s = ss inv_edge_map = {} for key, value in edge_map.items(): inv_edge_map[value] = (p1, key) glue_list = [] for i in range(len(es)): p3, e3 = edge_map[i] p4, e4 = self.opposite_edge(p3, e3) if p4 == p1 or p4 == p2: glue_list.append(inv_edge_map[(p4, e4)]) else: glue_list.append((p4, e4)) if p2 in s.roots(): s.set_roots(p1 if label == p2 else label for label in s.roots()) s.remove_polygon(p2) s.remove_polygon(p1) s.add_polygon(new_polygon, label=p1) for e, cross in enumerate(glue_list): s.glue((p1, e), cross) return s
[docs] def subdivide_polygon(self, p, v1, v2, test=False, new_label=None): r""" Overrides :meth:`flatsurf.geometry.categories.similarity_surfaces.SimilaritySurfaces.Oriented.ParentMethods.subdivide_polygon` to allow subdividing in-place. """ if test: return super().subdivide_polygon( p=p, v1=v1, v2=v2, test=test, new_label=new_label ) poly = self.polygon(p) ne = len(poly.vertices()) if v1 < 0 or v2 < 0 or v1 >= ne or v2 >= ne: raise ValueError("Provided vertices out of bounds.") if abs(v1 - v2) <= 1 or abs(v1 - v2) >= ne - 1: raise ValueError("Provided diagonal is not actually a diagonal.") if v2 < v1: v2 = v2 + ne newedges1 = [poly.vertex(v2) - poly.vertex(v1)] for i in range(v2, v1 + ne): newedges1.append(poly.edge(i)) from flatsurf import Polygon newpoly1 = Polygon(edges=newedges1, base_ring=self.base_ring()) newedges2 = [poly.vertex(v1) - poly.vertex(v2)] for i in range(v1, v2): newedges2.append(poly.edge(i)) newpoly2 = Polygon(edges=newedges2, base_ring=self.base_ring()) # Store the old gluings old_gluings = {(p, i): self.opposite_edge(p, i) for i in range(ne)} # Update the polygon with label p, add a new polygon. self.remove_polygon(p) self.add_polygon(newpoly1, label=p) if new_label is None: new_label = self.add_polygon(newpoly2) else: new_label = self.add_polygon(newpoly2, label=new_label) # This gluing is the diagonal we used. self.glue((p, 0), (new_label, 0)) # Setup conversion from old to new labels. old_to_new_labels = {} for i in range(v1, v2): old_to_new_labels[(p, i % ne)] = (new_label, i - v1 + 1) for i in range(v2, ne + v1): old_to_new_labels[(p, i % ne)] = (p, i - v2 + 1) for e in range(1, len(newpoly1.vertices())): pair = old_gluings[(p, (v2 + e - 1) % ne)] if pair in old_to_new_labels: pair = old_to_new_labels[pair] self.glue((p, e), (pair[0], pair[1])) for e in range(1, len(newpoly2.vertices())): pair = old_gluings[(p, (v1 + e - 1) % ne)] if pair in old_to_new_labels: pair = old_to_new_labels[pair] self.glue((new_label, e), (pair[0], pair[1]))
[docs] def reposition_polygons(self, in_place=False, relabel=None): r""" Overrides :meth:`flatsurf.geometry.categories.similarity_surfaces.SimilaritySurfaces.FiniteType.Oriented.ParentMethods.reposition_polygons` to allow normalizing in-place. """ if not in_place: return super().reposition_polygons(in_place=in_place, relabel=relabel) if relabel is not None: if relabel: raise NotImplementedError( "the relabel keyword has been removed from reposition_polygon; 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 reposition_polygons()" ) s = self labels = list(s.labels()) from flatsurf.geometry.similarity import SimilarityGroup S = SimilarityGroup(self.base_ring()) identity = S.one() it = iter(labels) label = next(it) changes = {label: identity} for label in it: polygon = self.polygon(label) adjacencies = { edge: self.opposite_edge(label, edge)[0] for edge in range(len(polygon.vertices())) } edge = min( adjacencies, # pylint: disable-next=cell-var-from-loop key=lambda edge: labels.index(adjacencies[edge]), ) label2, edge2 = s.opposite_edge(label, edge) changes[label] = changes[label2] * s.edge_transformation(label, edge) it = iter(labels) # Skip the base label: label = next(it) for label in it: p = s.polygon(label) p = changes[label].derivative() * p s.replace_polygon(label, p) return s
[docs] def triangulate(self, in_place=False, label=None, relabel=None): r""" Overrides :meth:`flatsurf.geometry.categories.similarity_surfaces.SimilaritySurfaces.Oriented.ParentMethods.triangulate` to allow triangulating in-place. TESTS: Verify that the monotile can be triangulated:: sage: from flatsurf import Polygon, MutableOrientedSimilaritySurface sage: K = QuadraticField(3) sage: a = K.gen() sage: # build the vectors sage: l = [(1, 0), (1, 2), (a, 11), (a, 1), (1, 4), (1, 6), (a, 3), ....: (a, 5), (1, 8), (1, 6), (a, 9), (a, 7), (1, 10), (1, 0)] sage: vecs = [] sage: for m, e in l: ....: v = vector(K, [m * cos(2*pi*e/12), m * sin(2*pi*e/12)]) ....: vecs.append(v) sage: p = Polygon(edges=vecs) sage: from collections import defaultdict sage: d = defaultdict(list) sage: for i, e in enumerate(p.edges()): ....: e.set_immutable() ....: d[e].append(i) sage: Sbase = MutableOrientedSimilaritySurface(K) sage: _ = Sbase.add_polygon(p) sage: for v in list(d): ....: if v in d: ....: indices = d[v] ....: v_op = -v ....: v_op.set_immutable() ....: opposite_indices = d[v_op] ....: assert len(indices) == len(opposite_indices), (len(indices), len(opposite_indices)) ....: if len(indices) == 1: ....: del d[v] ....: del d[v_op] ....: Sbase.glue((0, indices[0]), (0, opposite_indices[0])) sage: (i0, j0), (i1, j1) = d.values() sage: S1 = MutableOrientedSimilaritySurface.from_surface(Sbase) sage: S1.glue((0, i0), (0, i1)) sage: S1.glue((0, j0), (0, j1)) sage: S1.set_immutable() sage: S1.triangulate().codomain() Triangulation of Translation Surface in H_3(4, 0) built from a non-convex tridecagon with a marked vertex """ if relabel is not None: import warnings warnings.warn( "the relabel keyword argument of triangulate() is ignored, it has been deprecated and will be removed in a future version of sage-flatsurf" ) if not in_place: return super().triangulate(in_place=False, label=label) import warnings warnings.warn( "in-place triangulation has been deprecated and the in_place keyword argument will be removed from triangulate() in a future version of sage-flatsurf" ) labels = [label] if label is not None else list(self.labels()) for label in labels: self.refine_polygon( label, *MutableOrientedSimilaritySurface._triangulate(self, label) ) from flatsurf.geometry.morphism import NamedUnknownMorphism return NamedUnknownMorphism._create_morphism(None, self, "Triangulation")
@staticmethod def _triangulate(surface, label): r""" Helper method for :meth:`triangulate`. Returns a triangulation of the polygon with ``label`` of ``surface`` together with a bidict that can be fed to :meth:`refine_polygon`. EXAMPLES:: sage: from flatsurf import translation_surfaces, MutableOrientedSimilaritySurface sage: S = translation_surfaces.square_torus() sage: MutableOrientedSimilaritySurface._triangulate(S, 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)})) """ triangulation, edge_to_edge = surface.polygon(label).triangulate() if len(triangulation.labels()) == 1: relabeling = {triangulation.root(): label} else: relabeling = {lbl: (label, lbl) for lbl in triangulation.labels()} triangulation = triangulation.relabel(relabeling) from bidict import bidict edge_to_edge = bidict( {edge: (relabeling[l], e) for (edge, (l, e)) in edge_to_edge.items()} ) return triangulation, edge_to_edge
[docs] def delaunay_single_flip(self): r""" Perform a single in place flip of a triangulated mutable surface in-place. """ lc = self._label_comparator() for (l1, e1), (l2, e2) in self.gluings(): if ( lc.lt(l1, l2) or (l1 == l2 and e1 <= e2) ) and self._delaunay_edge_needs_flip(l1, e1): self.triangle_flip(l1, e1, in_place=True) return True return False
[docs] def cmp(self, s2, limit=None): r""" Compare two surfaces. This is an ordering returning -1, 0, or 1. The surfaces will be considered equal if and only if there is a translation automorphism respecting the polygons and the root labels. If the two surfaces are infinite, we just examine the first limit polygons. """ if limit is not None: import warnings warnings.warn( "limit has been deprecated as a keyword argument for _cmp() 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.is_finite_type(): if s2.is_finite_type(): if limit is not None: raise ValueError("limit only enabled for finite surfaces") sign = len(self.polygons()) - len(s2.polygons()) if sign > 0: return 1 if sign < 0: return -1 lw1 = self.labels() labels1 = list(lw1) lw2 = s2.labels() labels2 = list(lw2) for l1, l2 in zip(lw1, lw2): ret = self.polygon(l1).cmp(self.polygon(l2)) if ret != 0: return ret for e in range(len(self.polygon(l1).vertices())): ll1, e1 = self.opposite_edge(l1, e) ll2, e2 = s2.opposite_edge(l2, e) num1 = labels1.index(ll1) num2 = labels2.index(ll2) ret = (num1 > num2) - (num1 < num2) if ret: return ret ret = (e1 > e2) - (e1 < e2) if ret: return ret return 0 else: # s1 is finite but s2 is infinite. return -1 else: if s2.is_finite_type(): # s1 is infinite but s2 is finite. return 1 else: if limit is None: raise NotImplementedError # both surfaces are infinite. from itertools import islice lw1 = self.labels() labels1 = list(islice(lw1, limit)) lw2 = s2.labels() labels2 = list(islice(lw2, limit)) count = 0 for l1, l2 in zip(lw1, lw2): ret = self.polygon(l1).cmp(s2.polygon(l2)) if ret != 0: return ret for e in range(len(self.polygon(l1).vertices())): ll1, ee1 = self.opposite_edge(l1, e) ll2, ee2 = s2.opposite_edge(l2, e) num1 = labels1.index(ll1) num2 = labels2.index(ll2) ret = (num1 > num2) - (num1 < num2) if ret: return ret ret = (ee1 > ee2) - (ee1 < ee2) if ret: return ret if count >= limit: break count += 1 return 0
[docs] def __eq__(self, other): r""" Return whether this surface is indistinguishable from ``other``. See :meth:`~.categories.similarity_surfaces.SimilaritySurfaces.FiniteType.ParentMethods._test_eq_surface` for details on this notion of equality. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: T = MutableOrientedSimilaritySurface(AA) sage: S == T False sage: S == S True """ if not isinstance(other, MutableOrientedSimilaritySurface): return False if not super().__eq__(other): return False if self._gluings != other._gluings: return False return True
[docs] def __hash__(self): r""" Return a hash value for this surface that is compatible with :meth:`__eq__`. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: T = MutableOrientedSimilaritySurface(QQ) sage: hash(S) == hash(T) Traceback (most recent call last): ... TypeError: cannot hash a mutable surface sage: S.set_immutable() sage: T.set_immutable() sage: hash(S) == hash(T) True """ if self._mutable: raise TypeError("cannot hash a mutable surface") return hash((super().__hash__(), tuple(self.gluings())))
[docs] class BaseRingChangedSurface(OrientedSimilaritySurface): r""" Changes the ring over which a surface is defined. EXAMPLES: This class is used in the implementation of :meth:`flatsurf.geometry.categories.similarity_surfaces.SimilaritySurfaces.Oriented.ParentMethods.change_ring`:: sage: from flatsurf import translation_surfaces sage: T = translation_surfaces.square_torus() sage: S = T.change_ring(AA) sage: from flatsurf.geometry.surface import BaseRingChangedSurface sage: isinstance(S, BaseRingChangedSurface) True sage: TestSuite(S).run() """
[docs] def __init__(self, surface, ring, category=None): if surface.is_mutable(): raise NotImplementedError("surface must be immutable") self._reference = surface super().__init__(ring, category=category or surface.category())
[docs] def is_mutable(self): r""" Return whether this surface can be modified, i.e., return ``False``. This implements :meth:`flatsurf.geometry.categories.topological_surfaces.TopologicalSurfaces.ParentMethods.is_mutable`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: T = translation_surfaces.square_torus() sage: S = T.change_ring(AA) sage: S.is_mutable() False """ return False
[docs] def labels(self): r""" Return the labels of the polygons of this surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: T = translation_surfaces.square_torus() sage: S = T.change_ring(AA) sage: S.labels() (0,) """ return self._reference.labels()
[docs] def roots(self): r""" Return a label for each connected component on this surface. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.roots`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: T = translation_surfaces.square_torus() sage: S = T.change_ring(AA) sage: S.roots() (0,) """ return self._reference.roots()
[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: T = translation_surfaces.square_torus() sage: S = T.change_ring(AA) sage: p = S.polygon(0) sage: p.base_ring() Algebraic Real Field """ return self._reference.polygon(label).change_ring(self.base_ring())
[docs] def opposite_edge(self, label, edge): r""" Return the edge that ``edge`` of ``label`` is glued to or ``None`` if this edge is unglued. This implements :meth:`flatsurf.geometry.categories.polygonal_surfaces.PolygonalSurfaces.ParentMethods.opposite_edge`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: T = translation_surfaces.square_torus() sage: S = T.change_ring(AA) sage: S.opposite_edge(0, 0) (0, 2) """ return self._reference.opposite_edge(label, edge)
[docs] def __eq__(self, other): r""" Return whether this surface is indistinguishable from ``other``. See :meth:`~.categories.similarity_surfaces.SimilaritySurfaces.FiniteType.ParentMethods._test_eq_surface` for details on this notion of equality. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: T = translation_surfaces.square_torus() sage: T.change_ring(AA) == T.change_ring(AA) True """ if not isinstance(other, BaseRingChangedSurface): return False return self._reference == other._reference and self.base() == other.base()
[docs] 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: T = translation_surfaces.square_torus() sage: hash(T.change_ring(AA)) == hash(T.change_ring(AA)) True """ return hash((self._reference, self.base()))
[docs] class RootedComponents_MutablePolygonalSurface(collections.abc.Mapping): r""" Connected components of a :class:`MutablePolygonalSurface`. The components are represented as a mapping that maps the root labels to the labels of the corresponding component. This is a helper method for :meth:`MutablePolygonalSurface.components` and :meth:`MutablePolygonalSurface.roots`. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.add_polygon(polygons.square()) 1 sage: from flatsurf.geometry.surface import RootedComponents_MutablePolygonalSurface sage: components = RootedComponents_MutablePolygonalSurface(S) """
[docs] def __init__(self, surface): self._surface = surface
[docs] def __getitem__(self, root): r""" Return the labels of the connected component rooted at the label ``root``. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.add_polygon(polygons.square()) 1 sage: S.glue((0, 0), (1, 0)) sage: from flatsurf.geometry.surface import RootedComponents_MutablePolygonalSurface sage: components = RootedComponents_MutablePolygonalSurface(S) sage: components[0] (0, 1) """ return self._surface.component(root)
[docs] def __iter__(self): r""" Iterate over the keys of this mapping, i.e., the root labels of the connected components. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.add_polygon(polygons.square()) 1 sage: S.glue((0, 0), (1, 0)) sage: from flatsurf.geometry.surface import RootedComponents_MutablePolygonalSurface sage: components = RootedComponents_MutablePolygonalSurface(S) sage: list(components) [0] """ # Shortcut enumeration if this is known to be a connected surface. connected = "Connected" in self._surface.category().axioms() for root in self._surface._roots: yield root if connected: return labels = set(self._surface._polygons) for root in self._surface._roots: for label in self._surface.component(root): labels.remove(label) while labels: root = LabeledView(surface=self._surface, view=labels, finite=True).min() yield root if connected: return for label in self._surface.component(root): labels.remove(label)
[docs] def __len__(self): r""" Return the number of connected components of this surface. EXAMPLES:: sage: from flatsurf import MutableOrientedSimilaritySurface sage: S = MutableOrientedSimilaritySurface(QQ) sage: from flatsurf import polygons sage: S.add_polygon(polygons.square()) 0 sage: S.add_polygon(polygons.square()) 1 sage: S.glue((0, 0), (1, 0)) sage: from flatsurf.geometry.surface import RootedComponents_MutablePolygonalSurface sage: components = RootedComponents_MutablePolygonalSurface(S) sage: len(components) 1 """ components = 0 for root in self: components += 1 return components
[docs] class LabeledCollection(collections.abc.Collection): r""" Abstract base class for collection of labels as returned by ``labels()`` methods of surfaces. This also serves as a base clas for things such as ``polygons()`` that are tied to labels. INPUT: - ``surface`` -- a polygonal surface, the labels are taken from that surface; subclasses might change this to only represent a subset of the labels of this surface - ``finite`` -- a boolean or ``None`` (default: ``None``); whether this is a finite set; if ``None``, it is not known whether the set is finite (some operations might not be supported in that case or not terminate if the set is actually infinite.) EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.square_torus() sage: labels = S.labels() sage: from flatsurf.geometry.surface import LabeledCollection sage: isinstance(labels, LabeledCollection) True """
[docs] def __init__(self, surface, finite=None): if finite is None and surface.is_finite_type(): finite = True self._surface = surface self._finite = finite
[docs] def __repr__(self): r""" Return a printable representation of this set. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.square_torus() sage: S.labels() (0,) sage: S = translation_surfaces.infinite_staircase() sage: S.labels() (0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6, 7, -7, 8, …) """ from itertools import islice items = list(islice(self, 17)) if self._finite is True or len(items) < 17: return repr(tuple(self)) return f"({', '.join(str(x) for x in islice(self, 16))}, …)"
[docs] def __len__(self): r""" Return the size of this set. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.square_torus() sage: len(S.labels()) 1 Python does not allow ``__len__`` to return anything but an integer, so we cannot return infinity:: sage: S = translation_surfaces.infinite_staircase() sage: len(S.labels()) Traceback (most recent call last): ... NotImplementedError: len() of an infinite set """ if self._finite is False: raise TypeError("infinite set has no integer length") length = 0 for x in self: # pylint: disable=not-an-iterable length += 1 return length
[docs] def __contains__(self, x): r""" Return whether ``x`` is contained in this set. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.square_torus() sage: labels = S.labels() sage: 0 in labels True sage: 1 in labels False """ for item in self: # pylint: disable=not-an-iterable if x == item: return True return False
[docs] class LabeledSet(LabeledCollection, collections.abc.Set): r""" Abstract base class for sets of labels or related objects, such as the set of gluings of a surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.square_torus() sage: gluings = S.gluings() sage: from flatsurf.geometry.surface import LabeledSet sage: isinstance(gluings, LabeledSet) True """
[docs] class LabeledView(LabeledCollection): r""" A set of labels (or something resembling labels such as ``polygons()``) backed by another collection ``view``. INPUT: - ``surface`` -- a polygonal surface, the labels in ``view`` are labels of that surface - ``view`` -- a collection that all queries are going to be redirected to. Note that ``labels()`` guarantees that iteration over labels happens in a breadth-first-search so iteration over ``view`` must follow that same order. However, subclasses can remove this requirement by overriding :meth:`__iter__`. - ``finite`` -- a boolean or ``None`` (default: ``None``); whether this is a finite set; if ``None``, it is not known whether the set is finite (some operations might not be supported in that case or not terminate if the set is actually infinite.) EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.t_fractal() sage: labels = S.labels() sage: from flatsurf.geometry.surface import LabeledView sage: isinstance(labels, LabeledView) True """
[docs] def __init__(self, surface, view, finite=None): super().__init__(surface, finite=finite) self._view = view
[docs] def __iter__(self): return iter(self._view)
[docs] def __contains__(self, x): return x in self._view
[docs] def __len__(self): return len(self._view)
[docs] def min(self): r""" Return a minimal item in this set. If the items can be compared, this is just the actual ``min`` of the items. Otherwise, we take the one with minimal ``repr``. .. NOTE:: If the items cannot be compared, and there are clashes in the ``repr``, this method will fail. Also, if comparison of items is not consistent, then this can produce somewhat random output. Finally, note with this approach the min of a set is not the always the min of the mins of a each partition of that set. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.t_fractal() sage: S.labels().min() Traceback (most recent call last): ... NotImplementedError: cannot determine minimum of an infinite set :: sage: from flatsurf import translation_surfaces sage: C = translation_surfaces.cathedral(1,2) sage: C.labels().min() 0 :: sage: labels = list(C.labels())[:3] sage: from flatsurf.geometry.surface import LabeledView sage: LabeledView(C, labels).min() 0 """ if self._finite is False: raise NotImplementedError("cannot determine minimum of an infinite set") try: return min(self) except TypeError: reprs = {repr(item): item for item in self} if len(reprs) != len(self): raise TypeError( "cannot determine minimum of tset without ordering and with non-unique repr()" ) return reprs[min(reprs)]
[docs] class ComponentLabels(LabeledCollection): r""" The labels of a connected component. INPUT: - ``surface`` -- a polygonal surface - ``root`` -- a label of the connected component from which enumeration of the component starts. - ``finite`` -- a boolean or ``None`` (default: ``None``); whether this is a finite component; if ``None``, it is not known whether the component is finite (some operations might not be supported in that case or not terminate if the component is actually infinite.) EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.t_fractal() sage: component = S.component(0) sage: from flatsurf.geometry.surface import ComponentLabels sage: isinstance(component, ComponentLabels) True """
[docs] def __init__(self, surface, root, finite=None): super().__init__(surface, finite=finite) self._root = root
[docs] def __iter__(self): r""" Return an iterator of this component that enumerates labels starting from the root label in a breadth-first-search. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: C = translation_surfaces.cathedral(1, 2) sage: component = C.component(0) sage: list(component) [0, 1, 3, 2] """ from collections import deque seen = set() pending = deque([self._root]) while pending: label = pending.popleft() if label in seen: continue seen.add(label) yield label for e in range(len(self._surface.polygon(label).vertices())): cross = self._surface.opposite_edge(label, e) if cross is not None: pending.append(cross[0])
[docs] class Labels(LabeledCollection, collections.abc.Sequence): r""" The labels of a surface. .. NOTE:: This is a generic implementation that represents the set of labels of a surface in a breadth-first iteration starting from the root labels of the connected components. This implementation makes no assumption on the surface and can be very slow to answer, e.g., containment or compute the number of labels in the surface (because it needs to iterate over the entire surface.) When possible, a faster implementation should be used such as :class:`LabelsFromView`. EXAMPLES:: sage: from flatsurf import polygons, similarity_surfaces sage: T = polygons.triangle(1, 2, 5) sage: S = similarity_surfaces.billiard(T) sage: S = S.minimal_cover("translation") sage: labels = S.labels() sage: labels ((0, 1, 0), (1, 1, 0), (1, 0, -1), (1, 1/2*c0, 1/2*c0), (0, 1/2*c0, -1/2*c0), (0, 0, 1), (0, -1/2*c0, -1/2*c0), (0, 0, -1), (0, -1/2*c0, 1/2*c0), (0, 1/2*c0, 1/2*c0), (1, 1/2*c0, -1/2*c0), (1, -1/2*c0, -1/2*c0), (1, 0, 1), (1, -1/2*c0, 1/2*c0), (1, -1, 0), (0, -1, 0)) TESTS:: sage: from flatsurf.geometry.surface import Labels sage: type(labels) == Labels True """
[docs] def __iter__(self): for component in self._surface.components(): yield from component
[docs] def __getitem__(self, key): r""" Return the labels at position ``key``. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: C = translation_surfaces.cathedral(1, 2) sage: labels = C.labels() sage: labels[0] 0 sage: labels[-1] 2 sage: labels[::-1] [2, 3, 1, 0] """ if not isinstance(key, slice): key = int(key) sgn = 1 if key >= 0 else -1 item = self[key : key + sgn : sgn] if not item: raise IndexError(key) return item[0] from more_itertools import islice_extended return list(islice_extended(self, key.start, key.stop, key.step))
[docs] class LabelsFromView(Labels, LabeledView): r""" The labels of a surface backed by another set that can quickly compute the length of the labels and decide containment in the set. .. NOTE:: Iteration of the view collection does not have to be in breadth-first search order in the surface since this class is picking up the generic :meth:`Labels.__iter__`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: C = translation_surfaces.cathedral(1, 2) sage: labels = C.labels() sage: from flatsurf.geometry.surface import LabelsFromView sage: type(labels) == LabelsFromView True """
[docs] class Polygons(LabeledCollection): r""" The collection of polygons of a surface. The polygons are returned in the same order as labels of the surface are returned by :class:`.Labels`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: C = translation_surfaces.cathedral(1, 2) sage: polygons = C.polygons() sage: from flatsurf.geometry.surface import Polygons sage: isinstance(polygons, Polygons) True """
[docs] def __iter__(self): r""" Iterate over the polygons in the same order as ``labels()`` does. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: C = translation_surfaces.cathedral(1, 2) sage: labels = C.labels() sage: polygons = C.polygons() sage: for entry in zip(labels, polygons): ....: print(entry) (0, Polygon(vertices=[(0, 0), (1, 0), (1, 1), (0, 1)])) (1, Polygon(vertices=[(1, 0), (1, -2), (3/2, -5/2), (2, -2), (2, 0), (2, 1), (2, 3), (3/2, 7/2), (1, 3), (1, 1)])) (3, Polygon(vertices=[(3, 0), (7/2, -1/2), (11/2, -1/2), (6, 0), (6, 1), (11/2, 3/2), (7/2, 3/2), (3, 1)])) (2, Polygon(vertices=[(2, 0), (3, 0), (3, 1), (2, 1)])) """ for label in self._surface.labels(): yield self._surface.polygon(label)
[docs] def __len__(self): r""" Return the number of polygons in this surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: C = translation_surfaces.cathedral(1, 2) sage: polygons = C.polygons() sage: len(polygons) 4 """ return len(self._surface.labels())
[docs] class Polygons_MutableOrientedSimilaritySurface(Polygons): r""" The collection of polygons of a :class:`MutableOrientedSimilaritySurface`. This is a faster version of :class:`Polygons`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: C = translation_surfaces.cathedral(1, 2) sage: polygons = C.polygons() sage: from flatsurf.geometry.surface import Polygons_MutableOrientedSimilaritySurface sage: isinstance(polygons, Polygons_MutableOrientedSimilaritySurface) True """
[docs] def __init__(self, surface): # This hack makes __len__ 20% faster (it saves one attribute lookup.) self._polygons = surface._polygons super().__init__(surface)
[docs] def __len__(self): return len(self._polygons)
[docs] class Edges(LabeledSet): r""" The set of edges of a surface. The set of edges contains of pairs (label, index) with the labels of the polygons and the actual edges indexed from 0 in the second component. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: C = translation_surfaces.cathedral(1, 2) sage: edges = C.edges() sage: edges ((0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (3, 0), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (3, 6), (3, 7), (2, 0), (2, 1), (2, 2), (2, 3)) TESTS:: sage: from flatsurf.geometry.surface import Edges sage: isinstance(edges, Edges) True """
[docs] def __iter__(self): for label, polygon in zip(self._surface.labels(), self._surface.polygons()): for edge in range(len(polygon.vertices())): yield (label, edge)
[docs] def __contains__(self, x): label, edge = x if label not in self._surface.labels(): return False polygon = self._surface.polygon(label) return 0 <= len(polygon.vertices()) < edge
[docs] class Gluings(LabeledSet): r""" The set of gluings of the surface. Each gluing consists of two pairs (label, index) that describe the edges being glued. Note that each gluing (that is not a self-gluing) is reported twice. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.square_torus() sage: gluings = S.gluings() sage: gluings (((0, 0), (0, 2)), ((0, 1), (0, 3)), ((0, 2), (0, 0)), ((0, 3), (0, 1))) TESTS:: sage: from flatsurf.geometry.surface import Gluings sage: isinstance(gluings, Gluings) True """
[docs] def __iter__(self): for label, edge in self._surface.edges(): cross = self._surface.opposite_edge(label, edge) if cross is None: continue yield (label, edge), cross
[docs] def __contains__(self, x): x, y = x if x not in self._surface.edges(): return False cross = self._surface.opposite_edge(*x) if cross is None: return False return y == cross
# Import deprecated symbols so imports using flatsurf.geometry.surface do not break. from flatsurf.geometry.surface_legacy import ( # noqa, we import at the bottom of the file to break a circular import # pylint: disable=wrong-import-position Surface, Surface_list, Surface_dict, surface_list_from_polygons_and_gluings, )