---
jupytext:
formats: ipynb,md:myst
text_representation:
extension: .md
format_name: myst
format_version: 0.13
jupytext_version: 1.16.3
kernelspec:
display_name: SageMath 9.7
language: sage
name: sagemath
---
# Defining Surfaces
## Built in surfaces
Veech's double n-gon surfaces:
```{code-cell}
from flatsurf import translation_surfaces
s = translation_surfaces.veech_double_n_gon(5)
s.plot()
```
The Arnoux-Yoccoz surface of arbitrary genus is built in:
```{code-cell}
s = translation_surfaces.arnoux_yoccoz(3)
s.plot()
```
Chamanara's infinite translation surface:
```{code-cell}
s = translation_surfaces.chamanara(1 / 2)
```
```{code-cell}
s.plot(polygon_labels=False, edge_labels=False)
```
```{code-cell}
s = translation_surfaces.infinite_staircase()
```
```{code-cell}
s.plot()
```
## Billiard tables
```{code-cell}
from flatsurf import similarity_surfaces, Polygon
s = similarity_surfaces.billiard(Polygon(vertices=[(0, 0), (3, 0), (0, 4)]))
```
```{code-cell}
s.plot()
```
## Minimal translation surface covers
Continuing the billiard example above, we get an infinite translation surface below:
```{code-cell}
ss = s.minimal_cover(cover_type="translation")
```
```{code-cell}
gs = ss.graphical_surface()
```
```{code-cell}
gs.make_all_visible(limit=12)
```
```{code-cell}
gs.plot()
```
## Building surfaces from polygons
This defines a regular 12-gon with algebraic real coordinates (AA) with first vector given by (1,0):
```{code-cell}
from flatsurf import polygons
p0 = polygons.regular_ngon(12, field=AA)
p1 = polygons.regular_ngon(3, field=AA)
```
```{code-cell}
p0.plot() + p1.plot()
```
The vertices of n-gons are numbered by $\{0,...,n-1\}$, with the $0$-th vertex at the origin. Edge $i$ joins vertex $i$ to vertex $i+1 \pmod{n}$.
We can act on polygon with $2 \times 2$ matrices. We define the rotation by $\frac{\pi}{6}$ below:
```{code-cell}
R = matrix(AA, [[cos(pi / 6), -sin(pi / 6)], [sin(pi / 6), cos(pi / 6)]])
show(R)
```
```{code-cell}
R * p1
```
Define a surface over the field AA
of algebraic reals.
```{code-cell}
from flatsurf import MutableOrientedSimilaritySurface
surface = MutableOrientedSimilaritySurface(AA)
```
Add two polygons to the surface with labels 0 and 1:
```{code-cell}
surface.add_polygon(p0, label=0)
```
```{code-cell}
surface.add_polygon(p1, label=1)
```
Glue the edges of polygon 0 to the parallel edges of polygon 1.
```{code-cell}
surface.glue((0, 6), (1, 0))
surface.glue((0, 10), (1, 1))
surface.glue((0, 2), (1, 2))
```
Add three more rotated triangles and glue them appropriately.
```{code-cell}
for i in range(1, 4):
surface.add_polygon((R**i) * p1, label=i + 1)
surface.glue((0, 6 + i), (i + 1, 0))
surface.glue((0, (10 + i) % 12), (i + 1, 1))
surface.glue((0, 2 + i), (i + 1, 2))
```
Now we have a closed surface. In fact this is a translation surface:
```{code-cell}
surface
```
Once we are done building the surface, it is recommended to make the surface immutable. This lets sage-flatsurf figure out of which nature this surface is, e.g., that it is a translation surface. This speeds up many operations on the surface and makes it possible to compute things that are only defined or implemented for some types of surfaces:
```{code-cell}
surface.set_immutable()
surface
```
If you want to compute things, such as the stratum without making a surface immutable, please refer to the details in the documentation of the ``flatsurf.geometry.categories`` module in the module reference.
We can plot the surface. Edges are labeled according to the polygon they are glued to.
```{code-cell}
surface.plot()
```
The field containing the vertices:
```{code-cell}
surface.base_ring()
```
Computations in the Algebraic Real Field (AA) are slow. It is better to use a NumberField. The following finds a smaller number field::
```{code-cell}
vertices = [surface.polygon(p).vertex(v) for (p, v) in surface.edges()]
vertices = [vertex[0] for vertex in vertices] + [vertex[1] for vertex in vertices]
base_ring = Sequence(
[coordinate.as_number_field_element()[1] for coordinate in vertices]
).universe()
ss = surface.change_ring(base_ring)
```
```{code-cell}
ss.base_ring()
```
## Getting a surface from Flipper
Flipper is a program written by Mark Bell which understands mapping classes and can compute the flat structure associated to a pseudo-Anosov mapping class. FlatSurf can import this structure.
This code below requires flipper to be installed. You can do this by running the shell within sage:
sage --sh
Then within the shell execute:
python -m pip install flipper --user --upgrade
More information including pitfalls are described in Flipper's installation instructions.
```{code-cell}
import flipper
```
```{code-cell}
T = flipper.load("SB_4")
```
```{code-cell}
h = T.mapping_class("s_0S_1s_2S_3s_1S_2")
```
```{code-cell}
h.is_pseudo_anosov()
```
```{code-cell}
s = translation_surfaces.from_flipper(h)
```
The surface s is actually a half translation surface
```{code-cell}
s
```
```{code-cell}
s.plot()
```
## From polyhedra
```{code-cell}
from flatsurf.geometry.polyhedra import platonic_dodecahedron
polyhedron, s, mapping = platonic_dodecahedron()
```
The surface $s$ is a Euclidean cone surface.
```{code-cell}
s
```
```{code-cell}
s.plot()
```
Sage has a built in polyhedron class. You can build a polyhedron as a convex hull of a list of vertices.
```{code-cell}
polyhedron = Polyhedron([(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)])
```
```{code-cell}
polyhedron.plot()
```
The following computes the boundary surface as a Euclidean cone surface. It also provides a map from the surface to the polyhedron.
```{code-cell}
from flatsurf.geometry.polyhedra import polyhedron_to_cone_surface
s, mapping = polyhedron_to_cone_surface(polyhedron)
s
```
```{code-cell}
s.plot()
```
## Defining an infinite surface from scratch
Finite surfaces can be built by gluing polygons into a ``MutableOrientedSimilaritySurface``. For an infinite surface, we need to subclass ``OrientedSimilaritySurface`` and implement a few methods ourselves:
```{code-cell}
from flatsurf.geometry.surface import OrientedSimilaritySurface
from flatsurf.geometry.categories import TranslationSurfaces
class ParabolaSurface(OrientedSimilaritySurface):
def __init__(self):
# For finite surfaces, the category can be determined automotatically
# but for infinite surfaces, we need to make an explicit choice here.
super().__init__(
QQ,
category=TranslationSurfaces().InfiniteType().WithoutBoundary().Connected(),
)
def __repr__(self):
r"""
Return a printable representation of this surface.
"""
return "ParabolaSurface()"
def roots(self):
r"""
Return a label for each connected component of the surface.
Iterating the polygons of the connected component starts at these labels.
"""
return (1,)
def is_mutable(self):
r"""
Return whether this surface can be modified by the user.
"""
return False
def is_compact(self):
r"""
Return whether this surface is a compact space.
"""
return False
def __eq__(self, other):
r"""
Return whether this surface is indistinguishable from ``other``.
"""
return isinstance(other, ParabolaSurface)
def __hash__(self):
r"""
Return a hash value for this surface that is compatible with ``__eq``.
"""
return hash(type(self))
def graphical_surface(self, **kwds):
r"""
Return a plottable representation of this surface.
"""
graphical_surface = super().graphical_surface(**kwds)
# Make the first six polygons of the surface visible by default when plotting.
graphical_surface.make_all_visible(limit=6)
return graphical_surface
def polygon(self, label):
r"""
Return the polygon making up this surface labeled ``label``.
"""
if label not in ZZ or label == 0:
raise ValueError(f"invalid label {label}")
if label < 0:
return matrix(QQ, [[-1, 0], [0, -1]]) * self.polygon(-label)
if label == 1:
return Polygon(vertices=[(0, 0), (1, 1), (-1, 1)], base_ring=QQ)
return Polygon(
vertices=[
(label - 1, (label - 1) ** 2),
(label, label**2),
(-label, label**2),
(-label + 1, (label - 1) ** 2),
],
base_ring=QQ,
)
def opposite_edge(self, label, e):
if label not in ZZ or label == 0:
raise ValueError(f"invalid label {label}")
if label in [-1, 1] and e not in [0, 1, 2]:
raise ValueError("no such edge")
if e not in [0, 1, 2, 3]:
raise ValueError("no such edge")
if label in [-1, 1] and e == 1:
return 2 * label, 3
if e in [0, 2]:
return -label, e
if e == 1:
return label + label.sign(), 3
return label - label.sign(), 1
```
```{code-cell}
s = ParabolaSurface()
s
```
```{code-cell}
s.plot()
```
We can run a test suite to ensure that we have implemented everything that is needed to make this a fully functional surface.
```{code-cell}
TestSuite(s).run(verbose=True)
```