Source code for flatsurf.geometry.similarity_surface_generators

# ********************************************************************
#  This file is part of sage-flatsurf.
#
#        Copyright (C) 2016-2020 Vincent Delecroix
#                      2020-2023 Julian Rüth
#                           2023 Sam Freedman
#
#  sage-flatsurf is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 2 of the License, or
#  (at your option) any later version.
#
#  sage-flatsurf is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with sage-flatsurf. If not, see <https://www.gnu.org/licenses/>.
# ********************************************************************
from sage.rings.all import ZZ, QQ, RIF, AA, NumberField, polygen
from sage.modules.all import VectorSpace, vector
from sage.structure.coerce import py_scalar_parent
from sage.structure.element import get_coercion_model, parent
from sage.misc.cachefunc import cached_method
from sage.structure.sequence import Sequence

from flatsurf.geometry.polygon import (
    polygons,
    EuclideanPolygon,
    Polygon,
)

from flatsurf.geometry.surface import (
    OrientedSimilaritySurface,
    MutableOrientedSimilaritySurface,
)
from flatsurf.geometry.origami import Origami


ZZ_1 = ZZ(1)
ZZ_2 = ZZ(2)


[docs] def flipper_nf_to_sage(K, name="a"): r""" Convert a flipper number field into a Sage number field .. NOTE:: Currently, the code is not careful at all with root isolation. EXAMPLES:: sage: import flipper # optional - flipper # random output due to matplotlib warnings with some combinations of setuptools and matplotlib sage: import realalg # optional - flipper sage: from flatsurf.geometry.similarity_surface_generators import flipper_nf_to_sage sage: K = realalg.RealNumberField([-2r] + [0r]*5 + [1r]) # optional - flipper sage: K_sage = flipper_nf_to_sage(K) # optional - flipper sage: K_sage # optional - flipper Number Field in a with defining polynomial x^6 - 2 with a = 1.122462048309373? sage: AA(K_sage.gen()) # optional - flipper 1.122462048309373? """ r = K.lmbda.interval() lower = r.lower * ZZ(10) ** (-r.precision) upper = r.upper * ZZ(10) ** (-r.precision) p = QQ["x"](K.coefficients) s = AA.polynomial_root(p, RIF(lower, upper)) return NumberField(p, name, embedding=s)
[docs] def flipper_nf_element_to_sage(x, K=None): r""" Convert a flipper number field element into Sage EXAMPLES:: sage: from flatsurf.geometry.similarity_surface_generators import flipper_nf_element_to_sage sage: import flipper # optional - flipper sage: T = flipper.load('SB_6') # optional - flipper sage: h = T.mapping_class('s_0S_1S_2s_3s_4s_3S_5') # optional - flipper sage: flipper_nf_element_to_sage(h.dilatation()) # optional - flipper a sage: AA(_) # optional - flipper 6.45052513748511? """ if K is None: K = flipper_nf_to_sage(x.field) coeffs = list(map(QQ, x.coefficients)) coeffs.extend([0] * (K.degree() - len(coeffs))) return K(coeffs)
[docs] class EInfinitySurface(OrientedSimilaritySurface): r""" The surface based on the `E_\infinity` graph. The biparite graph is shown below, with edges numbered:: 0 1 2 -2 3 -3 4 -4 *---o---*---o---*---o---*---o---*... | |-1 o Here, black vertices are colored ``*``, and white ``o``. Black nodes represent vertical cylinders and white nodes represent horizontal cylinders. """ def __init__(self, lambda_squared=None, field=None): if lambda_squared is None: from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing R = PolynomialRing(ZZ, "x") x = R.gen() field = NumberField( x**3 - ZZ(5) * x**2 + ZZ(4) * x - ZZ(1), "r", embedding=AA(ZZ(4)) ) self._lambda_squared = field.gen() else: if field is None: self._lambda_squared = lambda_squared field = lambda_squared.parent() else: self._lambda_squared = field(lambda_squared) from flatsurf.geometry.categories import TranslationSurfaces super().__init__( field, category=TranslationSurfaces().InfiniteType().Connected().WithoutBoundary(), )
[docs] def is_compact(self): r""" Return whether this surface is compact as a topological space, i.e., return ``False``. This implements :meth:`flatsurf.geometry.categories.topological_surfaces.TopologicalSurfaces.ParentMethods.is_compact`. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.e_infinity_surface() sage: S.is_compact() False """ return False
[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.e_infinity_surface() sage: S.is_mutable() False """ return False
[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.e_infinity_surface() sage: S.roots() (0,) """ return (ZZ(0),)
def _repr_(self): r""" Return a printable representation of this surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.e_infinity_surface() sage: S EInfinitySurface(r) """ return f"EInfinitySurface({repr(self._lambda_squared)})"
[docs] @cached_method def get_white(self, n): r"""Get the weight of the white endpoint of edge n.""" if n == 0 or n == 1: return self._lambda_squared if n == -1: return self._lambda_squared - 1 if n == 2: return 1 - 3 * self._lambda_squared + self._lambda_squared**2 if n > 2: x = self.get_white(n - 1) y = self.get_black(n) return self._lambda_squared * y - x return self.get_white(-n)
[docs] @cached_method def get_black(self, n): r"""Get the weight of the black endpoint of edge n.""" if n == 0: return self.base_ring().one() if n == 1 or n == -1 or n == 2: return self._lambda_squared - 1 if n > 2: x = self.get_black(n - 1) y = self.get_white(n - 1) return y - x return self.get_black(1 - n)
[docs] def polygon(self, lab): r""" Return the polygon labeled by ``lab``. """ if lab not in self.labels(): raise ValueError(f"{lab=} not a valid label") return polygons.rectangle(2 * self.get_black(lab), self.get_white(lab))
[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.e_infinity_surface() sage: S.labels() (0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6, 7, -7, 8, …) """ from flatsurf.geometry.surface import LabelsFromView return LabelsFromView(self, ZZ, finite=False)
[docs] def opposite_edge(self, p, e): r""" Return the pair ``(pp,ee)`` to which the edge ``(p,e)`` is glued to. """ if p == 0: if e == 0: return (0, 2) if e == 1: return (1, 3) if e == 2: return (0, 0) if e == 3: return (1, 1) if p == 1: if e == 0: return (-1, 2) if e == 1: return (0, 3) if e == 2: return (2, 0) if e == 3: return (0, 1) if p == -1: if e == 0: return (2, 2) if e == 1: return (-1, 3) if e == 2: return (1, 0) if e == 3: return (-1, 1) if p == 2: if e == 0: return (1, 2) if e == 1: return (-2, 3) if e == 2: return (-1, 0) if e == 3: return (-2, 1) if p > 2: if e % 2: return -p, (e + 2) % 4 else: return 1 - p, (e + 2) % 4 else: if e % 2: return -p, (e + 2) % 4 else: return 1 - p, (e + 2) % 4
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: hash(translation_surfaces.e_infinity_surface()) == hash(translation_surfaces.e_infinity_surface()) True """ return hash((self.base_ring(), self._lambda_squared)) 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.e_infinity_surface() sage: S == S True """ if not isinstance(other, EInfinitySurface): return False return ( self._lambda_squared == other._lambda_squared and self.base_ring() == other.base_ring() )
[docs] class TFractalSurface(OrientedSimilaritySurface): r""" The TFractal surface. The TFractal surface is a translation surface of finite area built from infinitely many polygons. The basic building block is the following polygon:: w/r w w/r +---+------+---+ | 1 | 2 | 3 | h2 +---+------+---+ | 0 | h1 +------+ w where ``w``, ``h1``, ``h2``, ``r`` are some positive numbers. Default values are ``w=h1=h2=1`` and ``r=2``. .. TODO:: In that surface, the linear flow can be computed more efficiently using only one affine interval exchange transformation with 5 intervals. But the underlying geometric construction is not a covering. Warning: we can not play at the same time with tuples and element of a cartesian product (see Sage trac ticket #19555) """ def __init__(self, w=ZZ_1, r=ZZ_2, h1=ZZ_1, h2=ZZ_1): from sage.combinat.words.words import Words field = Sequence([w, r, h1, h2]).universe() if not field.is_field(): field = field.fraction_field() from flatsurf.geometry.categories import TranslationSurfaces super().__init__( field, category=TranslationSurfaces() .InfiniteType() .WithoutBoundary() .Compact() .Connected(), ) self._w = field(w) self._r = field(r) self._h1 = field(h1) self._h2 = field(h2) self._words = Words("LR", finite=True, infinite=False) self._wL = self._words("L") self._wR = self._words("R") self._root = (self._words(""), 0)
[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.t_fractal() sage: S.roots() ((word: , 0),) """ return (self._root,)
[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.t_fractal() sage: S.is_mutable() False """ return False
def _repr_(self): return "The T-fractal surface with parameters w={}, r={}, h1={}, h2={}".format( self._w, self._r, self._h1, self._h2, )
[docs] @cached_method 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.t_fractal() sage: S.labels() ((word: , 0), (word: , 2), (word: , 3), (word: , 1), (word: R, 2), (word: R, 0), (word: L, 2), (word: L, 0), (word: R, 3), (word: R, 1), (word: L, 3), (word: L, 1), (word: RR, 2), (word: RR, 0), (word: RL, 2), (word: RL, 0), …) """ from sage.sets.finite_enumerated_set import FiniteEnumeratedSet from sage.categories.cartesian_product import cartesian_product labels = cartesian_product([self._words, FiniteEnumeratedSet([0, 1, 2, 3])]) from flatsurf.geometry.surface import LabelsFromView return LabelsFromView(self, labels, finite=False)
[docs] def opposite_edge(self, p, e): r""" Labeling of polygons:: wl,0 wr,0 +-----+---------+------+ | | | | | w,1 | w,2 | w,3 | | | | | +-----+---------+------+ | | | w,0 | | | +---------+ w and we always have: bot->0, right->1, top->2, left->3 EXAMPLES:: sage: import flatsurf.geometry.similarity_surface_generators as sfg sage: T = sfg.tfractal_surface() sage: W = T._words sage: w = W('LLRLRL') sage: T.opposite_edge((w,0),0) ((word: LLRLR, 1), 2) sage: T.opposite_edge((w,0),1) ((word: LLRLRL, 0), 3) sage: T.opposite_edge((w,0),2) ((word: LLRLRL, 2), 0) sage: T.opposite_edge((w,0),3) ((word: LLRLRL, 0), 1) """ w, i = p w = self._words(w) i = int(i) e = int(e) if e == 0: f = 2 elif e == 1: f = 3 elif e == 2: f = 0 elif e == 3: f = 1 else: raise ValueError("e (={!r}) must be either 0,1,2 or 3".format(e)) if i == 0: if e == 0: if w.is_empty(): lab = (w, 2) elif w[-1] == "L": lab = (w[:-1], 1) elif w[-1] == "R": lab = (w[:-1], 3) if e == 1: lab = (w, 0) if e == 2: lab = (w, 2) if e == 3: lab = (w, 0) elif i == 1: if e == 0: lab = (w + self._wL, 2) if e == 1: lab = (w, 2) if e == 2: lab = (w + self._wL, 0) if e == 3: lab = (w, 3) elif i == 2: if e == 0: lab = (w, 0) if e == 1: lab = (w, 3) if e == 2: if w.is_empty(): lab = (w, 0) elif w[-1] == "L": lab = (w[:-1], 1) elif w[-1] == "R": lab = (w[:-1], 3) if e == 3: lab = (w, 1) elif i == 3: if e == 0: lab = (w + self._wR, 2) if e == 1: lab = (w, 1) if e == 2: lab = (w + self._wR, 0) if e == 3: lab = (w, 2) else: raise ValueError("i (={!r}) must be either 0,1,2 or 3".format(i)) # the fastest label constructor return lab, f
[docs] def polygon(self, lab): r""" Return the polygon with label ``lab``:: w/r w/r +---+------+---+ | 1 | 2 | 3 | | | | | h2 +---+------+---+ | 0 | h1 +------+ w EXAMPLES:: sage: import flatsurf.geometry.similarity_surface_generators as sfg sage: T = sfg.tfractal_surface() sage: T.polygon(('L',0)) Polygon(vertices=[(0, 0), (1/2, 0), (1/2, 1/2), (0, 1/2)]) sage: T.polygon(('LRL',0)) Polygon(vertices=[(0, 0), (1/8, 0), (1/8, 1/8), (0, 1/8)]) """ w = self._words(lab[0]) return (1 / self._r ** w.length()) * self._base_polygon(lab[1])
@cached_method def _base_polygon(self, i): if i == 0: w = self._w h = self._h1 if i == 1 or i == 3: w = self._w / self._r h = self._h2 if i == 2: w = self._w h = self._h2 return Polygon( base_ring=self.base_ring(), edges=[(w, 0), (0, h), (-w, 0), (0, -h)] ) 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: hash(translation_surfaces.t_fractal()) == hash(translation_surfaces.t_fractal()) True """ return hash((self._w, self._h1, self._r, self._h2)) 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: translation_surfaces.t_fractal() == translation_surfaces.t_fractal() True """ if not isinstance(other, TFractalSurface): return False return ( self._w == other._w and self._h1 == other._h1 and self._r == other._r and self._h2 == other._h2 )
[docs] def tfractal_surface(w=ZZ_1, r=ZZ_2, h1=ZZ_1, h2=ZZ_1): return TFractalSurface(w, r, h1, h2)
[docs] class SimilaritySurfaceGenerators: r""" Examples of similarity surfaces. """
[docs] @staticmethod def example(): r""" Construct a SimilaritySurface from a pair of triangles. EXAMPLES:: sage: from flatsurf import similarity_surfaces sage: ex = similarity_surfaces.example() sage: ex Genus 1 Surface built from 2 isosceles triangles TESTS:: sage: TestSuite(ex).run() sage: from flatsurf.geometry.categories import SimilaritySurfaces sage: ex in SimilaritySurfaces() True """ s = MutableOrientedSimilaritySurface(QQ) s.add_polygon( Polygon(vertices=[(0, 0), (2, -2), (2, 0)], base_ring=QQ), label=0 ) s.add_polygon(Polygon(vertices=[(0, 0), (2, 0), (1, 3)], base_ring=QQ), label=1) s.glue((0, 0), (1, 1)) s.glue((0, 1), (1, 2)) s.glue((0, 2), (1, 0)) s.set_immutable() return s
[docs] @staticmethod def self_glued_polygon(P): r""" Return the HalfTranslationSurface formed by gluing all edges of P to themselves. EXAMPLES:: sage: from flatsurf import Polygon, similarity_surfaces sage: p = Polygon(edges=[(2,0),(-1,3),(-1,-3)]) sage: s = similarity_surfaces.self_glued_polygon(p) sage: s Half-Translation Surface in Q_0(-1^4) built from an isosceles triangle sage: TestSuite(s).run() """ s = MutableOrientedSimilaritySurface(P.base_ring()) s.add_polygon(P) for i in range(len(P.vertices())): s.glue((0, i), (0, i)) s.set_immutable() return s
[docs] @staticmethod def billiard(P, rational=None): r""" Return the ConeSurface associated to the billiard in the polygon ``P``. INPUT: - ``P`` -- a polygon - ``rational`` -- a boolean or ``None`` (default: ``None``) -- whether to assume that all the angles of ``P`` are a rational multiple of π. EXAMPLES:: sage: from flatsurf import Polygon, similarity_surfaces sage: P = Polygon(vertices=[(0,0), (1,0), (0,1)]) sage: Q = similarity_surfaces.billiard(P, rational=True) doctest:warning ... UserWarning: the rational keyword argument of billiard() has been deprecated and will be removed in a future version of sage-flatsurf; rationality checking is now faster so this is not needed anymore sage: Q Genus 0 Rational Cone Surface built from 2 isosceles triangles sage: from flatsurf.geometry.categories import ConeSurfaces sage: Q in ConeSurfaces().Rational() True sage: M = Q.minimal_cover(cover_type="translation") sage: M Minimal Translation Cover of Genus 0 Rational Cone Surface built from 2 isosceles triangles sage: TestSuite(M).run() sage: from flatsurf.geometry.categories import TranslationSurfaces sage: M in TranslationSurfaces() True A non-convex examples (L-shape polygon):: sage: P = Polygon(vertices=[(0,0), (2,0), (2,1), (1,1), (1,2), (0,2)]) sage: Q = similarity_surfaces.billiard(P) sage: TestSuite(Q).run() sage: M = Q.minimal_cover(cover_type="translation") sage: TestSuite(M).run() sage: M.stratum() H_2(2, 0^5) A quadrilateral from Eskin-McMullen-Mukamel-Wright:: sage: from flatsurf import Polygon sage: P = Polygon(angles=(1, 1, 1, 7)) sage: S = similarity_surfaces.billiard(P) sage: TestSuite(S).run() sage: S = S.minimal_cover(cover_type="translation") sage: TestSuite(S).run() sage: S = S.erase_marked_points() # optional: pyflatsurf sage: TestSuite(S).run() sage: S, _ = S.normalized_coordinates() sage: TestSuite(S).run() Unfolding a triangle with non-algebraic lengths:: sage: from flatsurf import EuclideanPolygonsWithAngles sage: E = EuclideanPolygonsWithAngles((3, 3, 5)) sage: from pyexactreal import ExactReals # optional: pyexactreal sage: R = ExactReals(E.base_ring()) # optional: pyexactreal sage: angles = (3, 3, 5) sage: slopes = EuclideanPolygonsWithAngles(*angles).slopes() sage: P = Polygon(angles=angles, edges=[R.random_element() * slopes[0]]) # optional: pyexactreal sage: S = similarity_surfaces.billiard(P); S # optional: pyexactreal Genus 0 Rational Cone Surface built from 2 isosceles triangles sage: TestSuite(S).run() # long time (6s), optional: pyexactreal sage: from flatsurf.geometry.categories import ConeSurfaces sage: S in ConeSurfaces() True """ if not isinstance(P, EuclideanPolygon): raise TypeError("invalid input") if rational is not None: import warnings warnings.warn( "the rational keyword argument of billiard() has been deprecated and will be removed in a future version of sage-flatsurf; rationality checking is now faster so this is not needed anymore" ) from flatsurf.geometry.categories import ConeSurfaces category = ConeSurfaces() if P.is_rational(): category = category.Rational() V = P.base_ring() ** 2 if not P.is_convex(): # triangulate non-convex ones base_ring = P.base_ring() comb_edges = P.triangulation() vertices = P.vertices() comb_triangles = SimilaritySurfaceGenerators._billiard_build_faces( len(vertices), comb_edges ) triangles = [] internal_edges = [] # list (p1, e1, p2, e2) external_edges = [] # list (p1, e1) edge_to_lab = {} for num, (i, j, k) in enumerate(comb_triangles): triangles.append( Polygon( vertices=[vertices[i], vertices[j], vertices[k]], base_ring=base_ring, ) ) edge_to_lab[(i, j)] = (num, 0) edge_to_lab[(j, k)] = (num, 1) edge_to_lab[(k, i)] = (num, 2) for num, (i, j, k) in enumerate(comb_triangles): if (j, i) in edge_to_lab: num2, e2 = edge_to_lab[j, i] internal_edges.append((num, 0, num2, e2)) else: external_edges.append((num, 0)) if (k, j) in edge_to_lab: num2, e2 = edge_to_lab[k, j] internal_edges.append((num, 1, num2, e2)) else: external_edges.append((num, 1)) if (i, k) in edge_to_lab: num2, e2 = edge_to_lab[i, k] internal_edges.append((num, 2, num2, e2)) else: external_edges.append((num, 1)) P = triangles else: internal_edges = [] external_edges = [(0, i) for i in range(len(P.vertices()))] base_ring = P.base_ring() P = [P] surface = MutableOrientedSimilaritySurface(base_ring, category=category) m = len(P) for p in P: surface.add_polygon(p) for p in P: surface.add_polygon( Polygon(edges=[V((-x, y)) for x, y in reversed(p.edges())]) ) for p1, e1, p2, e2 in internal_edges: surface.glue((p1, e1), (p2, e2)) ne1 = len(surface.polygon(p1).vertices()) ne2 = len(surface.polygon(p2).vertices()) surface.glue((m + p1, ne1 - e1 - 1), (m + p2, ne2 - e2 - 1)) for p, e in external_edges: ne = len(surface.polygon(p).vertices()) surface.glue((p, e), (m + p, ne - e - 1)) surface.set_immutable() return surface
@staticmethod def _billiard_build_faces(n, edges): r""" Given a combinatorial list of pairs ``edges`` forming a cell-decomposition of a polygon (with vertices labeled from ``0`` to ``n-1``) return the list of cells. This is a helper method for :meth:`billiard`. EXAMPLES:: sage: from flatsurf.geometry.similarity_surface_generators import SimilaritySurfaceGenerators sage: SimilaritySurfaceGenerators._billiard_build_faces(4, [(0,2)]) [[0, 1, 2], [2, 3, 0]] sage: SimilaritySurfaceGenerators._billiard_build_faces(4, [(1,3)]) [[1, 2, 3], [3, 0, 1]] sage: SimilaritySurfaceGenerators._billiard_build_faces(5, [(0,2), (0,3)]) [[0, 1, 2], [3, 4, 0], [0, 2, 3]] sage: SimilaritySurfaceGenerators._billiard_build_faces(5, [(0,2)]) [[0, 1, 2], [2, 3, 4, 0]] sage: SimilaritySurfaceGenerators._billiard_build_faces(5, [(1,4)]) [[1, 2, 3, 4], [4, 0, 1]] sage: SimilaritySurfaceGenerators._billiard_build_faces(5, [(1,3),(3,0)]) [[1, 2, 3], [3, 4, 0], [0, 1, 3]] """ polygons = [list(range(n))] for u, v in edges: j = None for i, p in enumerate(polygons): if u in p and v in p: if j is not None: raise RuntimeError j = i if j is None: raise RuntimeError p = polygons[j] i0 = p.index(u) i1 = p.index(v) if i0 > i1: i0, i1 = i1, i0 polygons[j] = p[i0 : i1 + 1] polygons.append(p[i1:] + p[: i0 + 1]) return polygons
[docs] @staticmethod def polygon_double(P): r""" Return the ConeSurface associated to the billiard in the polygon ``P``. Differs from billiard(P) only in the graphical display. Here, we display the polygons separately. """ from sage.matrix.constructor import matrix n = len(P.vertices()) r = matrix(2, [-1, 0, 0, 1]) Q = Polygon(edges=[r * v for v in reversed(P.edges())]) surface = MutableOrientedSimilaritySurface(P.base_ring()) surface.add_polygon(P, label=0) surface.add_polygon(Q, label=1) for i in range(n): surface.glue((0, i), (1, n - i - 1)) surface.set_immutable() return surface
[docs] @staticmethod def right_angle_triangle(w, h): r""" TESTS:: sage: from flatsurf import similarity_surfaces sage: R = similarity_surfaces.right_angle_triangle(2, 3) sage: R Genus 0 Cone Surface built from 2 right triangles sage: from flatsurf.geometry.categories import ConeSurfaces sage: R in ConeSurfaces() True sage: TestSuite(R).run() """ F = Sequence([w, h]).universe() if not F.is_field(): F = F.fraction_field() V = VectorSpace(F, 2) s = MutableOrientedSimilaritySurface(F) s.add_polygon( Polygon(base_ring=F, edges=[V((w, 0)), V((-w, h)), V((0, -h))]), label=0 ) s.add_polygon( Polygon(base_ring=F, edges=[V((0, h)), V((-w, -h)), V((w, 0))]), label=1 ) s.glue((0, 0), (1, 2)) s.glue((0, 1), (1, 1)) s.glue((0, 2), (1, 0)) s.set_immutable() return s
similarity_surfaces = SimilaritySurfaceGenerators()
[docs] class DilationSurfaceGenerators:
[docs] @staticmethod def basic_dilation_torus(a): r""" Return a dilation torus built from a `1 \times 1` square and a `a \times 1` rectangle. Each edge of the square is glued to the opposite edge of the rectangle. This results in horizontal edges glued by a dilation with a scaling factor of a, and vertical edges being glued by translation:: b a +----+---------+ | 0 | 1 | c | | | c +----+---------+ a b EXAMPLES:: sage: from flatsurf import dilation_surfaces sage: ds = dilation_surfaces.basic_dilation_torus(AA(sqrt(2))) sage: ds Genus 1 Positive Dilation Surface built from a square and a rectangle sage: from flatsurf.geometry.categories import DilationSurfaces sage: ds in DilationSurfaces().Positive() True sage: TestSuite(ds).run() """ s = MutableOrientedSimilaritySurface(a.parent().fraction_field()) s.add_polygon( Polygon(base_ring=s.base_ring(), edges=[(0, 1), (-1, 0), (0, -1), (1, 0)]), label=0, ) s.add_polygon( Polygon(base_ring=s.base_ring(), edges=[(0, 1), (-a, 0), (0, -1), (a, 0)]), label=1, ) # label 1 s.glue((0, 0), (1, 2)) s.glue((0, 1), (1, 3)) s.glue((0, 2), (1, 0)) s.glue((0, 3), (1, 1)) s.set_roots([0]) s.set_immutable() return s
[docs] @staticmethod def genus_two_square(a, b, c, d): r""" A genus two dilation surface is returned. The unit square is made into an octagon by marking a point on each of its edges. Then opposite sides of this octagon are glued together by translation. (Since we currently require strictly convex polygons, we subdivide the square into a hexagon and two triangles as depicted below.) The parameters ``a``, ``b``, ``c``, and ``d`` should be real numbers strictly between zero and one. These represent the lengths of an edge of the resulting octagon, as below:: c +--+-------+ d |2/ | |/ | + 0 + | /| | /1| b +-------+--+ a The other edges will have length `1-a`, `1-b`, `1-c`, and `1-d`. Dilations used to glue edges will be by factors `c/a`, `d/b`, `(1-c)/(1-a)` and `(1-d)/(1-b)`. EXAMPLES:: sage: from flatsurf import dilation_surfaces sage: ds = dilation_surfaces.genus_two_square(1/2, 1/3, 1/4, 1/5) sage: ds Genus 2 Positive Dilation Surface built from 2 right triangles and a hexagon sage: from flatsurf.geometry.categories import DilationSurfaces sage: ds in DilationSurfaces().Positive() True sage: TestSuite(ds).run() """ field = Sequence([a, b, c, d]).universe().fraction_field() s = MutableOrientedSimilaritySurface(QQ) hexagon = Polygon( edges=[(a, 0), (1 - a, b), (0, 1 - b), (-c, 0), (c - 1, -d), (0, d - 1)], base_ring=field, ) s.add_polygon(hexagon, label=0) s.set_roots([0]) triangle1 = Polygon(base_ring=field, edges=[(1 - a, 0), (0, b), (a - 1, -b)]) s.add_polygon(triangle1, label=1) triangle2 = Polygon(base_ring=field, edges=[(1 - c, d), (c - 1, 0), (0, -d)]) s.add_polygon(triangle2, label=2) s.glue((0, 0), (0, 3)) s.glue((0, 2), (0, 5)) s.glue((0, 1), (1, 2)) s.glue((0, 4), (2, 0)) s.glue((1, 0), (2, 1)) s.glue((1, 1), (2, 2)) s.set_immutable() return s
dilation_surfaces = DilationSurfaceGenerators()
[docs] class HalfTranslationSurfaceGenerators: # TODO: ideally, we should be able to construct a non-convex polygon and make the construction # below as a special case of billiard unfolding.
[docs] @staticmethod def step_billiard(w, h): r""" Return a (finite) step billiard associated to the given widths ``w`` and heights ``h``. EXAMPLES:: sage: from flatsurf import half_translation_surfaces sage: S = half_translation_surfaces.step_billiard([1,1,1,1], [1,1/2,1/3,1/5]) sage: S StepBilliard(w=[1, 1, 1, 1], h=[1, 1/2, 1/3, 1/5]) sage: from flatsurf.geometry.categories import DilationSurfaces sage: S in DilationSurfaces() True sage: TestSuite(S).run() """ n = len(h) if len(w) != n: raise ValueError if n < 2: raise ValueError("w and h must have length at least 2") H = sum(h) W = sum(w) R = Sequence(w + h).universe() base_ring = R.fraction_field() P = [] Prev = [] x = 0 y = H for i in range(n - 1): P.append( Polygon( vertices=[ (x, 0), (x + w[i], 0), (x + w[i], y - h[i]), (x + w[i], y), (x, y), ], base_ring=base_ring, ) ) x += w[i] y -= h[i] assert x == W - w[-1] assert y == h[-1] P.append( Polygon( vertices=[(x, 0), (x + w[-1], 0), (x + w[-1], y), (x, y)], base_ring=base_ring, ) ) Prev = [ Polygon( vertices=[(x, -y) for x, y in reversed(p.vertices())], base_ring=base_ring, ) for p in P ] S = MutableOrientedSimilaritySurface(base_ring) S.rename( "StepBilliard(w=[{}], h=[{}])".format( ", ".join(map(str, w)), ", ".join(map(str, h)) ) ) for p in P: S.add_polygon(p) # get labels 0, ..., n-1 for p in Prev: S.add_polygon(p) # get labels n, n+1, ..., 2n-1 # reflection gluings # (gluings between the polygon and its reflection) S.glue((0, 4), (n, 4)) S.glue((n - 1, 0), (2 * n - 1, 2)) S.glue((n - 1, 1), (2 * n - 1, 1)) S.glue((n - 1, 2), (2 * n - 1, 0)) for i in range(n - 1): # glue((polygon1, edge1), (polygon2, edge2)) S.glue((i, 0), (n + i, 3)) S.glue((i, 2), (n + i, 1)) S.glue((i, 3), (n + i, 0)) # translation gluings S.glue((n - 2, 1), (n - 1, 3)) S.glue((2 * n - 2, 2), (2 * n - 1, 3)) for i in range(n - 2): S.glue((i, 1), (i + 1, 4)) S.glue((n + i, 2), (n + i + 1, 4)) S.set_immutable() return S
half_translation_surfaces = HalfTranslationSurfaceGenerators()
[docs] class TranslationSurfaceGenerators: r""" Common and less common translation surfaces. """
[docs] @staticmethod def square_torus(a=1): r""" Return flat torus obtained by identification of the opposite sides of a square. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: T = translation_surfaces.square_torus() sage: T Translation Surface in H_1(0) built from a square sage: from flatsurf.geometry.categories import TranslationSurfaces sage: T in TranslationSurfaces() True sage: TestSuite(T).run() Rational directions are completely periodic:: sage: v = T.tangent_vector(0, (1/33, 1/257), (13,17)) sage: L = v.straight_line_trajectory() sage: L.flow(13+17) sage: L.is_closed() True TESTS:: sage: TestSuite(T).run() """ return TranslationSurfaceGenerators.torus((a, 0), (0, a))
[docs] @staticmethod def torus(u, v): r""" Return the flat torus obtained as the quotient of the plane by the pair of vectors ``u`` and ``v``. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: T = translation_surfaces.torus((1, AA(2).sqrt()), (AA(3).sqrt(), 3)) sage: T Translation Surface in H_1(0) built from a quadrilateral sage: T.polygon(0) Polygon(vertices=[(0, 0), (1, 1.414213562373095?), (2.732050807568878?, 4.414213562373095?), (1.732050807568878?, 3)]) sage: from flatsurf.geometry.categories import TranslationSurfaces sage: T in TranslationSurfaces() True """ u = vector(u) v = vector(v) field = Sequence([u, v]).universe().base_ring() if isinstance(field, type): field = py_scalar_parent(field) if not field.is_field(): field = field.fraction_field() s = MutableOrientedSimilaritySurface(field) p = Polygon(vertices=[(0, 0), u, u + v, v], base_ring=field) s.add_polygon(p) s.glue((0, 0), (0, 2)) s.glue((0, 1), (0, 3)) s.set_immutable() return s
[docs] @staticmethod def veech_2n_gon(n): r""" The regular 2n-gon with opposite sides identified. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: s = translation_surfaces.veech_2n_gon(5) sage: s Translation Surface in H_2(1^2) built from a regular decagon sage: TestSuite(s).run() """ p = polygons.regular_ngon(2 * n) s = MutableOrientedSimilaritySurface(p.base_ring()) s.add_polygon(p) for i in range(2 * n): s.glue((0, i), (0, (i + n) % (2 * n))) s.set_immutable() return s
[docs] @staticmethod def veech_double_n_gon(n): r""" A pair of regular n-gons with each edge of one identified to an edge of the other to make a translation surface. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: s=translation_surfaces.veech_double_n_gon(5) sage: s Translation Surface in H_2(2) built from 2 regular pentagons sage: TestSuite(s).run() """ from sage.matrix.constructor import Matrix p = polygons.regular_ngon(n) s = MutableOrientedSimilaritySurface(p.base_ring()) m = Matrix([[-1, 0], [0, -1]]) s.add_polygon(p, label=0) s.add_polygon(m * p, label=1) for i in range(n): s.glue((0, i), (1, i)) s.set_immutable() return s
[docs] @staticmethod def regular_octagon(): r""" Return the translation surface built from the regular octagon by identifying opposite sides. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: T = translation_surfaces.regular_octagon() sage: T Translation Surface in H_2(2) built from a regular octagon sage: TestSuite(T).run() sage: from flatsurf.geometry.categories import TranslationSurfaces sage: T in TranslationSurfaces() True """ return translation_surfaces.veech_2n_gon(4)
[docs] @staticmethod def mcmullen_genus2_prototype(w, h, t, e, rel=0, base_ring=None): r""" McMullen prototypes in the stratum H(2). These prototype appear at least in McMullen "Teichmüller curves in genus two: Discriminant and spin" (2004). The notation from that paper are quadruple ``(a, b, c, e)`` which translates in our notation as ``w = b``, ``h = c``, ``t = a`` (and ``e = e``). The associated discriminant is `D = e^2 + 4 wh`. If ``rel`` is a positive parameter (less than w-lambda) the surface belongs to the eigenform locus in H(1,1). EXAMPLES:: sage: from flatsurf import translation_surfaces sage: from surface_dynamics import Stratum sage: prototypes = { ....: 5: [(1,1,0,-1)], ....: 8: [(1,1,0,-2), (2,1,0,0)], ....: 9: [(2,1,0,-1)], ....: 12: [(1,2,0,-2), (2,1,0,-2), (3,1,0,0)], ....: 13: [(1,1,0,-3), (3,1,0,-1), (3,1,0,1)], ....: 16: [(3,1,0,-2), (4,1,0,0)], ....: 17: [(1,2,0,-3), (2,1,0,-3), (2,2,0,-1), (2,2,1,-1), (4,1,0,-1), (4,1,0,1)], ....: 20: [(1,1,0,-4), (2,2,1,-2), (4,1,0,-2), (4,1,0,2)], ....: 21: [(1,3,0,-3), (3,1,0,-3)], ....: 24: [(1,2,0,-4), (2,1,0,-4), (3,2,0,0)], ....: 25: [(2,2,0,-3), (2,2,1,-3), (3,2,0,-1), (4,1,0,-3)]} sage: for D in sorted(prototypes): # long time (.5s) ....: for w,h,t,e in prototypes[D]: ....: T = translation_surfaces.mcmullen_genus2_prototype(w,h,t,e) ....: assert T.stratum() == Stratum([2], 1) ....: assert (D.is_square() and T.base_ring() is QQ) or (T.base_ring().polynomial().discriminant() == D) An example with some relative homology:: sage: U8 = translation_surfaces.mcmullen_genus2_prototype(2,1,0,0,1/4) # discriminant 8 sage: U8 Translation Surface in H_2(1^2) built from a rectangle and a quadrilateral sage: U12 = translation_surfaces.mcmullen_genus2_prototype(3,1,0,0,3/10) # discriminant 12 sage: U12 Translation Surface in H_2(1^2) built from a rectangle and a quadrilateral sage: U8.stratum() H_2(1^2) sage: U8.base_ring().polynomial().discriminant() 8 sage: U8.j_invariant() ( [4 0] (0), (0), [0 2] ) sage: U12.stratum() H_2(1^2) sage: U12.base_ring().polynomial().discriminant() 12 sage: U12.j_invariant() ( [6 0] (0), (0), [0 2] ) """ w = ZZ(w) h = ZZ(h) t = ZZ(t) e = ZZ(e) g = w.gcd(h) if ( w <= 0 or h <= 0 or t < 0 or t >= g or not g.gcd(t).gcd(e).is_one() or e + h >= w ): raise ValueError("invalid parameters") x = polygen(QQ) poly = x**2 - e * x - w * h if poly.is_irreducible(): if base_ring is None: emb = AA.polynomial_root(poly, RIF(0, w)) K = NumberField(poly, "l", embedding=emb) λ = K.gen() else: K = base_ring roots = poly.roots(K, multiplicities=False) if len(roots) != 2: raise ValueError("invalid base ring") roots.sort(key=lambda x: x.numerical_approx()) assert roots[0] < 0 and roots[0] > 0 λ = roots[1] else: if base_ring is None: K = QQ else: K = base_ring D = e**2 + 4 * w * h d = D.sqrt() λ = (e + d) / 2 try: rel = K(rel) except TypeError: K = get_coercion_model().common_parent(K, parent(rel)) λ = K(λ) rel = K(rel) # (lambda,lambda) square on top # twisted (w,0), (t,h) s = MutableOrientedSimilaritySurface(K) if rel: if rel < 0 or rel > w - λ: raise ValueError("invalid rel argument") s.add_polygon( Polygon(vertices=[(0, 0), (λ, 0), (λ + rel, λ), (rel, λ)], base_ring=K) ) s.add_polygon( Polygon( vertices=[ (0, 0), (rel, 0), (rel + λ, 0), (w, 0), (w + t, h), (λ + rel + t, h), (t + λ, h), (t, h), ], base_ring=K, ) ) s.glue((0, 1), (0, 3)) s.glue((0, 0), (1, 6)) s.glue((0, 2), (1, 1)) s.glue((1, 2), (1, 4)) s.glue((1, 3), (1, 7)) s.glue((1, 0), (1, 5)) else: s.add_polygon( Polygon(vertices=[(0, 0), (λ, 0), (λ, λ), (0, λ)], base_ring=K) ) s.add_polygon( Polygon( vertices=[(0, 0), (λ, 0), (w, 0), (w + t, h), (λ + t, h), (t, h)], base_ring=K, ) ) s.glue((0, 1), (0, 3)) s.glue((0, 0), (1, 4)) s.glue((0, 2), (1, 0)) s.glue((1, 1), (1, 3)) s.glue((1, 2), (1, 5)) s.set_immutable() return s
[docs] @staticmethod def lanneau_nguyen_genus3_prototype(w, h, t, e): r""" Return the Lanneau--Ngyuen prototype in the stratum H(4) with parameters ``w``, ``h``, ``t``, and ``e``. The associated discriminant is `e^2 + 8 wh`. INPUT: - ``w`` -- a positive integer - ``h`` -- a positive integer - ``t`` -- a non-negative integer strictly less than the gcd of ``w`` and ``h`` - ``e`` -- an integer strictly less than ``w - 2h`` EXAMPLES:: sage: from flatsurf import translation_surfaces sage: T = translation_surfaces.lanneau_nguyen_genus3_prototype(2,1,0,-1) sage: T.stratum() H_3(4) REFERENCES: - Erwan Lanneau, and Duc-Manh Nguyen, "Teichmüller curves generated by Weierstrass Prym eigenforms in genus 3 and genus 4", Journal of Topology 7.2, 2014, pp.475-522 """ from sage.all import gcd w = ZZ(w) h = ZZ(h) t = ZZ(t) e = ZZ(e) if not w > 0: raise ValueError("w must be positive") if not h > 0: raise ValueError("h must be positive") if not e + 2 * h < w: raise ValueError("e + 2h < w must hold") if not t >= 0: raise ValueError("t must be non-negative") if not t < gcd(w, h): raise ValueError("t must be smaller than the gcd of w and h") if not gcd([w, h, t, e]) == 1: raise ValueError("w, h, t, e must be coprime") K = QQ D = K(e**2 + 8 * w * h) if not D.is_square(): from sage.all import QuadraticField K = QuadraticField(D) D = K(D) d = D.sqrt() λ = (e + d) / 2 # (λ, λ) square in the middle # twisted (w, 0), (t, h) on top and bottom s = MutableOrientedSimilaritySurface(K) from flatsurf import Polygon s.add_polygon( Polygon( vertices=[(0, 0), (λ, 0), (w, 0), (w + t, h), (λ + t, h), (t, h)], base_ring=K, ) ) s.add_polygon(Polygon(vertices=[(0, 0), (λ, 0), (λ, λ), (0, λ)], base_ring=K)) s.add_polygon( Polygon( vertices=[ (0, 0), (w - λ, 0), (w, 0), (w + t, h), (w - λ + t, h), (t, h), ], base_ring=K, ) ) s.glue((0, 0), (2, 3)) s.glue((0, 1), (0, 3)) s.glue((0, 2), (0, 5)) s.glue((0, 4), (1, 0)) s.glue((1, 1), (1, 3)) s.glue((1, 2), (2, 1)) s.glue((2, 0), (2, 4)) s.glue((2, 2), (2, 5)) s.set_immutable() return s
[docs] @staticmethod def lanneau_nguyen_genus4_prototype(w, h, t, e): r""" Return the Lanneau--Ngyuen prototype in the stratum H(6) with parameters ``w``, ``h``, ``t``, and ``e``. The associated discriminant is `D = e^2 + 4 wh`. INPUT: - ``w`` -- a positive integer - ``h`` -- a positive integer - ``t`` -- a non-negative integer strictly smaller than the gcd of ``w`` and ``h`` - ``e`` -- a positive integer strictly smaller than `2w - \sqrt{D}` EXAMPLES:: sage: from flatsurf import translation_surfaces sage: T = translation_surfaces.lanneau_nguyen_genus4_prototype(1,1,0,-2) sage: T.stratum() H_4(6) REFERENCES: - Erwan Lanneau, and Duc-Manh Nguyen, "Weierstrass Prym eigenforms in genus four", Journal of the Institute of Mathematics of Jussieu, 19.6, 2020, pp.2045-2085 """ from sage.all import gcd w = ZZ(w) h = ZZ(h) t = ZZ(t) e = ZZ(e) if not w > 0: raise ValueError("w must be positive") if not h > 0: raise ValueError("h must be positive") if not t >= 0: raise ValueError("t must be non-negative") if not t < gcd(w, h): raise ValueError("t must be smaller than the gcd of w and h") if not gcd([w, h, t, e]) == 1: raise ValueError("w, h, t, e must be coprime") K = QQ D = K(e**2 + 4 * w * h) if not D.is_square(): from sage.all import QuadraticField K = QuadraticField(D) D = K(D) d = D.sqrt() λ = (e + d) / 2 if not λ < w: raise ValueError("λ must be smaller than w") if λ == w / 2: raise ValueError("λ and w/2 must be distinct") # (λ/2, λ/2) squares on top and bottom # twisted (w/2, 0), (t/2, h/2) in the middle s = MutableOrientedSimilaritySurface(K) from flatsurf import Polygon s.add_polygon( Polygon( vertices=[(0, 0), (λ / 2, 0), (λ / 2, λ / 2), (0, λ / 2)], base_ring=K ) ) s.add_polygon( Polygon( vertices=[ (0, 0), (w / 2 - λ, 0), (w / 2 - λ / 2, 0), (w / 2, 0), (w / 2 + t / 2, h / 2), (w / 2 - λ + t / 2, h / 2), (t / 2, h / 2), ], base_ring=K, ) ) s.add_polygon( Polygon( vertices=[ (0, 0), (λ, 0), (w / 2, 0), (w / 2 + t / 2, h / 2), (λ + t / 2, h / 2), (λ / 2 + t / 2, h / 2), (t / 2, h / 2), ], base_ring=K, ) ) s.add_polygon( Polygon( vertices=[(0, 0), (λ / 2, 0), (λ / 2, λ / 2), (0, λ / 2)], base_ring=K ) ) s.glue((0, 0), (2, 4)) s.glue((0, 1), (0, 3)) s.glue((0, 2), (1, 2)) s.glue((1, 0), (1, 5)) s.glue((1, 1), (3, 2)) s.glue((1, 3), (1, 6)) s.glue((1, 4), (2, 0)) s.glue((2, 1), (2, 3)) s.glue((2, 2), (2, 6)) s.glue((2, 5), (3, 0)) s.glue((3, 1), (3, 3)) s.set_immutable() return s
[docs] @staticmethod def mcmullen_L(l1, l2, l3, l4): r""" Return McMullen's L shaped surface with parameters l1, l2, l3, l4. Polygon labels and lengths are marked below:: +-----+ | | | 1 |l1 | | | | l4 +-----+---------+ | | | | 0 | 2 |l2 | | | +-----+---------+ l3 EXAMPLES:: sage: from flatsurf import translation_surfaces sage: s = translation_surfaces.mcmullen_L(1,1,1,1) sage: s Translation Surface in H_2(2) built from 3 squares sage: TestSuite(s).run() TESTS:: sage: from flatsurf import translation_surfaces sage: L = translation_surfaces.mcmullen_L(1r, 1r, 1r, 1r) sage: from flatsurf.geometry.categories import TranslationSurfaces sage: L in TranslationSurfaces() True """ field = Sequence([l1, l2, l3, l4]).universe() if isinstance(field, type): field = py_scalar_parent(field) if not field.is_field(): field = field.fraction_field() s = MutableOrientedSimilaritySurface(field) s.add_polygon( Polygon(edges=[(l3, 0), (0, l2), (-l3, 0), (0, -l2)], base_ring=field) ) s.add_polygon( Polygon(edges=[(l3, 0), (0, l1), (-l3, 0), (0, -l1)], base_ring=field) ) s.add_polygon( Polygon(edges=[(l4, 0), (0, l2), (-l4, 0), (0, -l2)], base_ring=field) ) s.glue((0, 0), (1, 2)) s.glue((0, 1), (2, 3)) s.glue((0, 2), (1, 0)) s.glue((0, 3), (2, 1)) s.glue((1, 1), (1, 3)) s.glue((2, 0), (2, 2)) s.set_immutable() return s
[docs] @staticmethod def ward(n): r""" Return the surface formed by gluing a regular 2n-gon to two regular n-gons. These surfaces have Veech's lattice property due to work of Ward. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: s = translation_surfaces.ward(3) sage: s Translation Surface in H_1(0^3) built from 2 equilateral triangles and a regular hexagon sage: TestSuite(s).run() sage: s = translation_surfaces.ward(7) sage: s Translation Surface in H_6(10) built from 2 regular heptagons and a regular tetradecagon sage: TestSuite(s).run() """ if n < 3: raise ValueError o = ZZ_2 * polygons.regular_ngon(2 * n) p1 = Polygon(edges=[o.edge((2 * i + n) % (2 * n)) for i in range(n)]) p2 = Polygon(edges=[o.edge((2 * i + n + 1) % (2 * n)) for i in range(n)]) s = MutableOrientedSimilaritySurface(o.base_ring()) s.add_polygon(o) s.add_polygon(p1) s.add_polygon(p2) for i in range(n): s.glue((1, i), (0, 2 * i)) s.glue((2, i), (0, 2 * i + 1)) s.set_immutable() return s
[docs] @staticmethod def octagon_and_squares(): r""" EXAMPLES:: sage: from flatsurf import translation_surfaces sage: os = translation_surfaces.octagon_and_squares() sage: os Translation Surface in H_3(4) built from 2 squares and a regular octagon sage: TestSuite(os).run() sage: from flatsurf.geometry.categories import TranslationSurfaces sage: os in TranslationSurfaces() True """ return translation_surfaces.ward(4)
[docs] @staticmethod def cathedral(a, b): r""" Return the cathedral surface with parameters ``a`` and ``b``. For any parameter ``a`` and ``b``, the cathedral surface belongs to the so-called Gothic locus described in McMullen, Mukamel, Wright "Cubic curves and totally geodesic subvarieties of moduli space" (2017):: 1 <---> /\ 2a / \ +------+ a b| | a / \ +----+ +---+ + | | | | | 1| P0 |P1 |P2 | P3 | | | | | | +----+ +---+ + b| | \ / \ / +------+ \/ If a and b satisfies .. MATH:: a = x + y \sqrt(d) \qquad b = -3x -3/2 + 3y \sqrt(d) for some rational x,y and d >= 0 then it is a Teichmüller curve. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: C = translation_surfaces.cathedral(1,2) sage: C Translation Surface in H_4(2^3) built from 2 squares, a hexagon with 4 marked vertices and an octagon sage: TestSuite(C).run() sage: from pyexactreal import ExactReals # optional: pyexactreal sage: K = QuadraticField(5, embedding=AA(5).sqrt()) sage: R = ExactReals(K) # optional: pyexactreal sage: C = translation_surfaces.cathedral(K.gen(), R.random_element([0.1, 0.2])) # optional: pyexactreal sage: C # optional: pyexactreal Translation Surface in H_4(2^3) built from 2 rectangles, a hexagon with 4 marked vertices and an octagon sage: C.stratum() # optional: pyexactreal H_4(2^3) sage: TestSuite(C).run() # long time (6s), optional: pyexactreal """ ring = Sequence([a, b]).universe() if isinstance(ring, type): ring = py_scalar_parent(ring) if not ring.has_coerce_map_from(QQ): ring = ring.fraction_field() a = ring(a) b = ring(b) s = MutableOrientedSimilaritySurface(ring) half = QQ((1, 2)) p0 = Polygon(base_ring=ring, vertices=[(0, 0), (a, 0), (a, 1), (0, 1)]) p1 = Polygon( base_ring=ring, vertices=[ (a, 0), (a, -b), (a + half, -b - half), (a + 1, -b), (a + 1, 0), (a + 1, 1), (a + 1, b + 1), (a + half, b + 1 + half), (a, b + 1), (a, 1), ], ) p2 = Polygon( base_ring=ring, vertices=[(a + 1, 0), (2 * a + 1, 0), (2 * a + 1, 1), (a + 1, 1)], ) p3 = Polygon( base_ring=ring, vertices=[ (2 * a + 1, 0), (2 * a + 1 + half, -half), (4 * a + 1 + half, -half), (4 * a + 2, 0), (4 * a + 2, 1), (4 * a + 1 + half, 1 + half), (2 * a + 1 + half, 1 + half), (2 * a + 1, 1), ], ) s.add_polygon(p0) s.add_polygon(p1) s.add_polygon(p2) s.add_polygon(p3) s.glue((0, 0), (0, 2)) s.glue((0, 1), (1, 9)) s.glue((0, 3), (3, 3)) s.glue((1, 0), (1, 3)) s.glue((1, 1), (3, 4)) s.glue((1, 2), (3, 6)) s.glue((1, 4), (2, 3)) s.glue((1, 5), (1, 8)) s.glue((1, 6), (3, 0)) s.glue((1, 7), (3, 2)) s.glue((2, 0), (2, 2)) s.glue((2, 1), (3, 7)) s.glue((3, 1), (3, 5)) s.set_immutable() return s
[docs] @staticmethod def arnoux_yoccoz(genus): r""" Construct the Arnoux-Yoccoz surface of genus 3 or greater. This presentation of the surface follows Section 2.3 of Joshua P. Bowman's paper "The Complete Family of Arnoux-Yoccoz Surfaces." EXAMPLES:: sage: from flatsurf import translation_surfaces sage: s = translation_surfaces.arnoux_yoccoz(4) sage: s Translation Surface in H_4(3^2) built from 16 triangles sage: TestSuite(s).run() sage: s.is_delaunay_decomposed() True sage: s = s.canonicalize() sage: s Translation Surface in H_4(3^2) built from 16 triangles sage: field=s.base_ring() sage: a = field.gen() sage: from sage.matrix.constructor import Matrix sage: m = Matrix([[a,0],[0,~a]]) sage: ss = m*s sage: ss = ss.canonicalize() sage: s.cmp(ss) == 0 True The Arnoux-Yoccoz pseudo-Anosov are known to have (minimal) invariant foliations with SAF=0:: sage: S3 = translation_surfaces.arnoux_yoccoz(3) sage: Jxx, Jyy, Jxy = S3.j_invariant() sage: Jxx.is_zero() and Jyy.is_zero() True sage: Jxy [ 0 2 0] [ 2 -2 0] [ 0 0 2] sage: S4 = translation_surfaces.arnoux_yoccoz(4) sage: Jxx, Jyy, Jxy = S4.j_invariant() sage: Jxx.is_zero() and Jyy.is_zero() True sage: Jxy [ 0 2 0 0] [ 2 -2 0 0] [ 0 0 2 2] [ 0 0 2 0] """ g = ZZ(genus) if g < 3: raise ValueError x = polygen(AA) p = sum([x**i for i in range(1, g + 1)]) - 1 cp = AA.common_polynomial(p) alpha_AA = AA.polynomial_root(cp, RIF(1 / 2, 1)) field = NumberField(alpha_AA.minpoly(), "alpha", embedding=alpha_AA) a = field.gen() V = VectorSpace(field, 2) p = [None for i in range(g + 1)] q = [None for i in range(g + 1)] p[0] = V(((1 - a**g) / 2, a**2 / (1 - a))) q[0] = V((-(a**g) / 2, a)) p[1] = V((-(a ** (g - 1) + a**g) / 2, (a - a**2 + a**3) / (1 - a))) p[g] = V((1 + (a - a**g) / 2, (3 * a - 1 - a**2) / (1 - a))) for i in range(2, g): p[i] = V(((a - a**i) / (1 - a), a / (1 - a))) for i in range(1, g + 1): q[i] = V( ( (2 * a - a**i - a ** (i + 1)) / (2 * (1 - a)), (a - a ** (g - i + 2)) / (1 - a), ) ) s = MutableOrientedSimilaritySurface(field) T = [None] * (2 * g + 1) Tp = [None] * (2 * g + 1) from sage.matrix.constructor import Matrix m = Matrix([[1, 0], [0, -1]]) for i in range(1, g + 1): # T_i is (P_0,Q_i,Q_{i-1}) T[i] = s.add_polygon( Polygon( base_ring=field, edges=[q[i] - p[0], q[i - 1] - q[i], p[0] - q[i - 1]], ) ) # T_{g+i} is (P_i,Q_{i-1},Q_{i}) T[g + i] = s.add_polygon( Polygon( base_ring=field, edges=[q[i - 1] - p[i], q[i] - q[i - 1], p[i] - q[i]], ) ) # T'_i is (P'_0,Q'_{i-1},Q'_i) Tp[i] = s.add_polygon(m * s.polygon(T[i])) # T'_{g+i} is (P'_i,Q'_i, Q'_{i-1}) Tp[g + i] = s.add_polygon(m * s.polygon(T[g + i])) for i in range(1, g): s.glue((T[i], 0), (T[i + 1], 2)) s.glue((Tp[i], 2), (Tp[i + 1], 0)) for i in range(1, g + 1): s.glue((T[i], 1), (T[g + i], 1)) s.glue((Tp[i], 1), (Tp[g + i], 1)) # P 0 Q 0 is paired with P' 0 Q' 0, ... s.glue((T[1], 2), (Tp[g], 2)) s.glue((Tp[1], 0), (T[g], 0)) # P1Q1 is paired with P'_g Q_{g-1} s.glue((T[g + 1], 2), (Tp[2 * g], 2)) s.glue((Tp[g + 1], 0), (T[2 * g], 0)) # P1Q0 is paired with P_{g-1} Q_{g-1} s.glue((T[g + 1], 0), (T[2 * g - 1], 2)) s.glue((Tp[g + 1], 2), (Tp[2 * g - 1], 0)) # PgQg is paired with Q1P2 s.glue((T[2 * g], 2), (T[g + 2], 0)) s.glue((Tp[2 * g], 0), (Tp[g + 2], 2)) for i in range(2, g - 1): # PiQi is paired with Q'_i P'_{i+1} s.glue((T[g + i], 2), (Tp[g + i + 1], 2)) s.glue((Tp[g + i], 0), (T[g + i + 1], 0)) s.set_immutable() return s
[docs] @staticmethod def from_flipper(h): r""" Build a (half-)translation surface from a flipper pseudo-Anosov. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: import flipper # optional - flipper A torus example:: sage: t1 = (0r,1r,2r) # optional - flipper sage: t2 = (~0r,~1r,~2r) # optional - flipper sage: T = flipper.create_triangulation([t1,t2]) # optional - flipper sage: L1 = T.lamination([1r,0r,1r]) # optional - flipper sage: L2 = T.lamination([0r,1r,1r]) # optional - flipper sage: h1 = L1.encode_twist() # optional - flipper sage: h2 = L2.encode_twist() # optional - flipper sage: h = h1*h2^(-1r) # optional - flipper sage: f = h.flat_structure() # optional - flipper sage: ts = translation_surfaces.from_flipper(h) # optional - flipper sage: ts # optional - flipper; computation of the stratum fails here, see #227 Half-Translation Surface built from 2 triangles sage: TestSuite(ts).run() # optional - flipper sage: from flatsurf.geometry.categories import HalfTranslationSurfaces # optional: flipper sage: ts in HalfTranslationSurfaces() # optional: flipper True A non-orientable example:: sage: T = flipper.load('SB_4') # optional - flipper sage: h = T.mapping_class('s_0S_1s_2S_3s_1S_2') # optional - flipper sage: h.is_pseudo_anosov() # optional - flipper True sage: S = translation_surfaces.from_flipper(h) # optional - flipper sage: TestSuite(S).run() # optional - flipper sage: len(S.polygons()) # optional - flipper 4 sage: from flatsurf.geometry.similarity_surface_generators import flipper_nf_element_to_sage sage: a = flipper_nf_element_to_sage(h.dilatation()) # optional - flipper """ f = h.flat_structure() x = next(iter(f.edge_vectors.values())).x K = flipper_nf_to_sage(x.field) V = VectorSpace(K, 2) edge_vectors = { i: V( (flipper_nf_element_to_sage(e.x, K), flipper_nf_element_to_sage(e.y, K)) ) for i, e in f.edge_vectors.items() } to_polygon_number = { k: (i, j) for i, t in enumerate(f.triangulation) for j, k in enumerate(t) } from flatsurf import MutableOrientedSimilaritySurface S = MutableOrientedSimilaritySurface(K) for i, t in enumerate(f.triangulation): try: poly = Polygon(base_ring=K, edges=[edge_vectors[i] for i in tuple(t)]) except ValueError: raise ValueError( "t = {}, edges = {}".format( t, [edge_vectors[i].n(digits=6) for i in t] ) ) S.add_polygon(poly) for i, t in enumerate(f.triangulation): for j, k in enumerate(t): S.glue((i, j), to_polygon_number[~k]) S.set_immutable() return S
[docs] @staticmethod def origami(r, u, rr=None, uu=None, domain=None): r""" Return the origami defined by the permutations ``r`` and ``u``. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = SymmetricGroup(3) sage: r = S('(1,2)') sage: u = S('(1,3)') sage: o = translation_surfaces.origami(r,u) sage: o Origami defined by r=(1,2) and u=(1,3) sage: o.stratum() H_2(2) sage: TestSuite(o).run() """ return Origami(r, u, rr, uu, domain)
[docs] @staticmethod def infinite_staircase(): r""" Return the infinite staircase built as an origami. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: S The infinite staircase sage: TestSuite(S).run() """ return TranslationSurfaceGenerators._InfiniteStaircase()
class _InfiniteStaircase(Origami): def __init__(self): super().__init__( self._vertical, self._horizontal, self._vertical, self._horizontal, domain=ZZ, root=ZZ(0), ) def is_compact(self): return False def _vertical(self, x): if x % 2: return x + 1 return x - 1 def _horizontal(self, x): if x % 2: return x - 1 return x + 1 def _position_function(self, n): from flatsurf.geometry.similarity import SimilarityGroup SG = SimilarityGroup(QQ) if n % 2 == 0: return SG((n // 2, n // 2)) else: return SG((n // 2, n // 2 + 1)) def __repr__(self): return "The infinite staircase" def __hash__(self): return 1337 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 == S True """ return isinstance(other, TranslationSurfaceGenerators._InfiniteStaircase) def graphical_surface(self, *args, **kwargs): default_position_function = kwargs.pop( "default_position_function", self._position_function ) graphical_surface = super().graphical_surface( *args, default_position_function=default_position_function, **kwargs ) graphical_surface.make_all_visible(limit=10) return graphical_surface def is_triangulated(self, limit=None): r""" Return whether this surface is triangulated, which it is not. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: 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 False def is_delaunay_triangulated(self, limit=None): r""" Return whether this surface is Delaunay triangulated, which it is not. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: S.is_delaunay_triangulated() False """ 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 False def is_delaunay_decomposed(self, limit=None): r""" Return whether this surface is made from Delaunay cells. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: S = translation_surfaces.infinite_staircase() sage: S.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] @staticmethod def t_fractal(w=ZZ_1, r=ZZ_2, h1=ZZ_1, h2=ZZ_1): r""" Return the T-fractal with parameters ``w``, ``r``, ``h1``, ``h2``. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: tf = translation_surfaces.t_fractal() # long time (.4s) sage: tf # long time (see above) The T-fractal surface with parameters w=1, r=2, h1=1, h2=1 sage: TestSuite(tf).run() # long time (see above) """ return tfractal_surface(w, r, h1, h2)
[docs] @staticmethod def e_infinity_surface(lambda_squared=None, field=None): r""" The translation surface based on the `E_\infinity` graph. The biparite graph is shown below, with edges numbered:: 0 1 2 -2 3 -3 4 -4 *---o---*---o---*---o---*---o---*... | |-1 o Here, black vertices are colored ``*``, and white ``o``. Black nodes represent vertical cylinders and white nodes represent horizontal cylinders. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: s = translation_surfaces.e_infinity_surface() sage: TestSuite(s).run() # long time (1s) """ return EInfinitySurface(lambda_squared, field)
[docs] @staticmethod def chamanara(alpha): r""" Return the Chamanara surface with parameter ``alpha``. EXAMPLES:: sage: from flatsurf import translation_surfaces sage: C = translation_surfaces.chamanara(1/2) sage: C Minimal Translation Cover of Chamanara surface with parameter 1/2 TESTS:: sage: TestSuite(C).run() sage: from flatsurf.geometry.categories import TranslationSurfaces sage: C in TranslationSurfaces() True """ from .chamanara import chamanara_surface return chamanara_surface(alpha)
translation_surfaces = TranslationSurfaceGenerators()