This file contains the code we used to generate many of the figures in the paper Platonic solids and high genus covers of lattice surfaces.
Updated to work with Sage 9.0 (and prior versions) on Apr. 27, 2020.
Run the file code_from_the_article.ipynb
:
run code_from_the_article.ipynb
from flatsurf import *
Define ${\mathbb Q}(\sqrt{3})$ and the equilateral triangle.
x = var("x")
Q_adjoin_sqrt3.<sqrt3> = NumberField(x^2-3, embedding=AA(sqrt(3)))
triangle_top = 2*polygons.regular_ngon(3, field=Q_adjoin_sqrt3)
triangle_bottom = -1*triangle_top
show(triangle_top.plot())
show(triangle_bottom.plot())
Define the square:
square = polygons.square()
show(square.plot())
Define the field $F={\mathbb Q}(s)$ where $s=2 \sin(\frac{\pi}{5})$.
s_AA = AA(2*sin(pi/5))
F.<s> = NumberField(s_AA.minpoly(), embedding=s_AA)
pentagon_top = 2 * polygons.regular_ngon(5, field=F)
pentagon_bottom = (-1) * pentagon_top
show(pentagon_top.plot())
show(pentagon_bottom.plot())
octahedron_surface = Surface_dict(base_ring=Q_adjoin_sqrt3)
for label in range(8):
if label%2 == 0:
octahedron_surface.add_polygon(triangle_top, label=label)
else:
octahedron_surface.add_polygon(triangle_bottom, label=label)
octahedron_surface.change_base_label(0)
for label in range(8):
gluings = build_adj_oct(0,label)
for edge in range(3):
octahedron_surface.change_edge_gluing(label, edge, gluings[edge][1], (edge - gluings[edge][0]) % 3)
octahedron = ConeSurface(octahedron_surface)
gs = octahedron.graphical_surface()
gs.make_adjacent(0,1)
gs.make_adjacent(3,2)
gs.make_adjacent(2,0)
gs.make_adjacent(2,1)
gs.make_adjacent(5,0)
gs.make_adjacent(5,2)
gs.make_adjacent(4,1)
def apply_display_options(graphical_surface):
'''
Modify the graphical surface to make it display a prettier graphic.
'''
if 'color' in graphical_surface.polygon_options:
del graphical_surface.polygon_options['color']
graphical_surface.polygon_options['rgbcolor']="#E0EEFF"
graphical_surface.will_plot_edge_labels = False
graphical_surface.non_adjacent_edge_options['thickness']=0.25
graphical_surface.polygon_label_options['fontsize']=20
apply_display_options(gs)
octahedron.plot()
octahedron.plot().save_image("octahedron_net.pdf")
icosahedron_surface = Surface_dict(base_ring=Q_adjoin_sqrt3)
for label in range(20):
if label%2 == 0:
icosahedron_surface.add_polygon(triangle_top, label=label)
else:
icosahedron_surface.add_polygon(triangle_bottom, label=label)
icosahedron_surface.change_base_label(0)
for label in range(20):
gluings = build_adj_icosa(0,label)
for edge in range(3):
icosahedron_surface.change_edge_gluing(label, edge, gluings[edge][1], (edge - gluings[edge][0]) % 3)
icosahedron = ConeSurface(icosahedron_surface)
gs = icosahedron.graphical_surface()
for label in range(0,8,2):
gs.make_adjacent(label,0)
gs.make_adjacent(label,1)
gs.make_adjacent(label,2)
gs.make_adjacent(label+3,0)
gs.make_adjacent(label+3,1)
gs.make_adjacent(label+3,2)
gs.make_adjacent(1,0)
gs.make_adjacent(8,0)
apply_display_options(gs)
icosahedron.plot()
icosahedron.plot().save_image("icosahedron_net.pdf")
cube_surface = Surface_dict(base_ring=QQ)
for label in range(6):
cube_surface.add_polygon(square, label=label)
cube_surface.change_base_label(0)
for label in range(6):
gluings = build_adj_cube(0,label)
for edge in range(4):
cube_surface.change_edge_gluing(label, edge, gluings[edge][1], (edge + 2 - gluings[edge][0]) % 4)
cube = ConeSurface(cube_surface)
gs = cube.graphical_surface()
for edge in range(4):
gs.make_adjacent(1, edge)
gs.make_adjacent(2, 1)
apply_display_options(gs)
cube.plot()
cube.plot().save_image("cube_net.pdf")
Define the unfolded surface:
cube_unfolding_surface = Surface_dict(QQ)
Add polygons indexed by pairs $(sheet,sq)$.
for sheet in range(4):
for sq in range(6):
cube_unfolding_surface.add_polygon(square, label=(sheet,sq))
Set the base label:
cube_unfolding_surface.change_base_label((0,0))
Glue edges together:
for sheet in range(4):
for sq in range(6):
adj_list = build_adj_cube(sheet,sq)
for edge in range(4):
cube_unfolding_surface.change_edge_gluing((sheet,sq), edge, tuple(adj_list[edge]), (edge+2)%4)
Make it so that we can no longer change the surface.
cube_unfolding_surface.set_immutable()
We define cube_unfolding
to be the translation surface built as above.
cube_unfolding = TranslationSurface(cube_unfolding_surface)
gs = cube_unfolding.graphical_surface()
apply_display_options(gs)
gs.will_plot_polygon_labels = False
gs.non_adjacent_edge_options['thickness']=0.125
gs.adjacent_edge_options['thickness']=0.125
for label in cube_unfolding.label_iterator():
if gs.is_visible(label):
gs.hide(label)
rot_matrix = matrix(QQ,[[0,-1],[1,0]])
show(rot_matrix)
Layout the positions of squares with $sq=0$:
from flatsurf.geometry.similarity import SimilarityGroup
SG = SimilarityGroup(QQ)
for sheet in range(4):
gs.make_visible((sheet,0))
gp = gs.graphical_polygon((sheet,0))
gp.set_transformation(SG(rot_matrix^sheet*vector([-9/2,0])-vector([1/2,1/2])))
Layout the other squares:
for sheet in range(4):
gs.make_adjacent((sheet,0),(sheet+1)%4)
for edge in range(4):
gs.make_adjacent((sheet,1),edge)
gs.make_adjacent((sheet,2),(sheet+1)%4)
gs.edge_label_options['fontsize']=5
gs.edge_label_options['position']='inside'
gs.edge_label_options['t']=1/2
gs.plot()
plt = gs.plot()
gs.polygon_label_options['fontsize']=7
for sheet in range(4):
for sq in range(6):
label = (sheet, sq)
gp = gs.graphical_polygon(label)
plt += gp.plot_label(sq, **gs.polygon_label_options)
plt
code = ord('a')
for sq in range(6):
for e in range(4):
(sheet2,sq2),e2 = cube_unfolding.opposite_edge((0,sq),e)
if not gs.is_adjacent((0,sq),e) and sq2>sq:
for sheet in range(4):
gp = gs.graphical_polygon((sheet,sq))
plt += gp.plot_edge_label((e+sheet)%4, chr(code) + str(sheet), **gs.edge_label_options)
gp = gs.graphical_polygon(((sheet+sheet2)%4, sq2))
plt += gp.plot_edge_label((e2+sheet)%4, chr(code) + str(sheet), **gs.edge_label_options)
code = code + 1
plt
gs.polygon_label_options['fontsize']=10
for sheet in range(4):
gp = gs.graphical_polygon((sheet, 0))
v = (rot_matrix^sheet)*vector([-3/4,0])
plt += text(str(sheet), v, **gs.polygon_label_options)
gs.graphical_polygon((0,0))
plt
plt.save_image("cube_unfolding.pdf")
plt.save_image("cube_unfolding.svg")
# The double pentagon surface Pi5 was defined as part of Appendix C.
double_pentagon_surface = Surface_dict(base_ring=F)
double_pentagon_surface.add_polygon(pentagon_top, label=0)
double_pentagon_surface.add_polygon(pentagon_bottom, label=1)
for i in range(5):
double_pentagon_surface.change_edge_gluing(0,i,1,i)
double_pentagon_surface.change_base_label(0)
Pi5 = TranslationSurface(double_pentagon_surface)
gs = Pi5.graphical_surface(edge_labels="letter")
gs.make_adjacent(0,4)
apply_display_options(gs)
gs.will_plot_polygon_labels = False
gs.will_plot_edge_labels = True
gs.edge_label_options['t']=0.5
gs.edge_label_options['position']='outside'
gs.edge_label_options['fontsize']=20
gs.plot()
sigma0 = Pi5.tangent_vector(1,Pi5.polygon(1).vertex(2),(1,0)).straight_line_trajectory()
sigma1 = Pi5.tangent_vector(0,(0,0),(1,0)).straight_line_trajectory()
gt0 = sigma0.graphical_trajectory()
gp0 = gs.graphical_polygon(0)
gp1 = gs.graphical_polygon(1)
v0=gp0.transformed_vertex(0)
v0
v0=gp0.transformed_vertex
plt = gs.plot() + \
sigma0.plot(color="green", thickness="3") + \
sigma1.plot(color="red", thickness="3") + \
text("$\\sigma_0$", (-2*cos(pi/5), 0.05), color = 'black', fontsize=30, vertical_alignment="bottom") + \
text("$\\sigma_1$", (1, 0.05), color = 'black', fontsize=30, vertical_alignment="bottom") + \
point2d(gp0.transformed_vertex(0),size=60, color="black",zorder=10) + \
text("$v_0$", gp0.transformed_vertex(0), color = 'black', fontsize=30, vertical_alignment="top" , horizontal_alignment="left") + \
point2d(gp0.transformed_vertex(1),size=60, color="black",zorder=10) + \
text("$v_3$", gp0.transformed_vertex(1), color = 'black', fontsize=30, vertical_alignment="top" , horizontal_alignment="left") + \
point2d(gp0.transformed_vertex(4),size=60, color="black",zorder=10) + \
text("$v_4$", gp0.transformed_vertex(4)+vector((0,0.1)), color = 'black', fontsize=30, vertical_alignment="bottom" , horizontal_alignment="right") + \
point2d(gp1.transformed_vertex(2),size=60, color="black",zorder=10) + \
text("$v_2$", gp1.transformed_vertex(2), color = 'black', fontsize=30, vertical_alignment="top" , horizontal_alignment="right")
plt
plt.save_image("pi5sc.pdf")
gs.will_plot_edge_labels = False
x=[]
for i in range(4):
x.append(Pi5.tangent_vector(0,Pi5.polygon(0).vertex(4)/2,R^(i-1)*vector([1,0])).straight_line_trajectory())
x[i].flow(1)
plt = Pi5.plot()
for i in range(4):
plt = plt + x[i].plot(color="black", thickness="2")
plt
gp = gs.graphical_polygon(0)
label_options = {'t':0.5, 'position':'outside', 'fontsize':30, 'color':'black'}
for i in range(4):
plt += gp.plot_edge_label(i, "$x_"+str(i)+"$", **label_options)
plt
plt.save_image("pi5_fund_group.svg")
dodecahedron_surface = Surface_dict(base_ring=F)
for label in range(12):
dodecahedron_surface.add_polygon(pentagon_top, label=label)
dodecahedron_surface.change_base_label(0)
for label in range(12):
gluings = build_adj_dodec(0,label)
for edge in range(5):
dodecahedron_surface.change_edge_gluing(label, edge, gluings[edge][1], (edge + 2*gluings[edge][0]) % 5)
dodecahedron = ConeSurface(dodecahedron_surface)
gs = dodecahedron.graphical_surface()
for edge in range(5):
gs.make_adjacent(0,edge)
gs.make_adjacent(2,4)
gs.make_adjacent(8,1)
for edge in range(5):
gs.make_adjacent(6,edge)
apply_display_options(gs)
dodecahedron.plot()
dodecahedron.plot().save_image("dodecahedron_net.pdf")
We construct something useful for writing symbols on the edges of the unfolded dodecahedron below.
For each edge of the dodecahedron where two polygons meet that are not joined in the net, we will choose a letter. Also for each letter we will choose a favorite polygon label indident to this edge.
edge_to_letter = {}
favorite_label = {}
letter = 'a'
for label in range(12):
for edge in range(5):
if not gs.is_adjacent(label,edge):
if (label, edge) not in edge_to_letter:
edge_to_letter[(label, edge)] = letter
edge_to_letter[dodecahedron.opposite_edge(label, edge)] = letter
favorite_label[letter] = label
letter = chr(ord(letter)+1) # increment letter
gs = Dtilde.graphical_surface()
apply_display_options(gs)
gs.will_plot_polygon_labels = False
gs.non_adjacent_edge_options['thickness']=0.125
gs.adjacent_edge_options['thickness']=0.125
for label in Dtilde.label_iterator():
if gs.is_visible(label):
gs.hide(label)
from flatsurf.geometry.similarity import SimilarityGroup
SG = SimilarityGroup(Dtilde.base_ring())
for sheet in range(10):
gs.make_visible((sheet,0))
gp = gs.graphical_polygon((sheet,0))
if sheet%2 == 0:
gp.set_transformation(SG(R**sheet*vector((20,-5))-pentagon_bottom.circumscribing_circle().center()))
else:
gp.set_transformation(SG(R**sheet*vector((20,-5))-pentagon_top.circumscribing_circle().center()))
for sheet in range(10):
for edge in range(5):
gs.make_adjacent((sheet,0),edge)
gs.make_adjacent((sheet,2),(4+3*sheet)%5)
gs.make_adjacent((sheet,8),(1+3*sheet)%5)
for edge in range(5):
gs.make_adjacent((sheet,6),edge)
gs.edge_label_options['fontsize']=2.9
gs.edge_label_options['t']=1/2
gs.plot()
plt = gs.plot()
edge_vector_to_digit = {}
for i in range(10):
v = R^i*vector(F,[2,0])
v.set_immutable()
edge_vector_to_digit[v]=i
for sheet in range(10):
for label in range(12):
gp = gs.graphical_polygon((sheet,label))
for edge in range(5):
edge_in_dodecahedron = (edge + 2*sheet) % 5
if (label,edge_in_dodecahedron) in edge_to_letter:
letter = edge_to_letter[(label,edge_in_dodecahedron)]
label_glued = dodecahedron.opposite_edge(label,edge_in_dodecahedron)[0]
if label < label_glued:
v = Dtilde.polygon((sheet,label)).edge(edge)
else:
v = -Dtilde.polygon((sheet,label)).edge(edge)
v.set_immutable()
number = edge_vector_to_digit[v]
plt += gp.plot_edge_label(edge, letter + str(number), **gs.edge_label_options)
plt
gs.polygon_label_options['fontsize']=4
for label in Dtilde.label_iterator():
gp = gs.graphical_polygon(label)
plt += gp.plot_label(label[1], **gs.polygon_label_options)
plt
gs.polygon_label_options['fontsize']=6
for sheet in range(10):
gp = gs.graphical_polygon((sheet, 6))
if sheet%2 == 1:
v = gp.transform(-pentagon_top.circumscribing_circle().center())
else:
v = gp.transform(-pentagon_bottom.circumscribing_circle().center())
plt += text(str(sheet), v/1.6, **gs.polygon_label_options)
plt
plt.save_image("dodecahedron_unfolding.pdf")
from flatsurf.geometry.surface_objects import SaddleConnection
import itertools
gs = dodecahedron.graphical_surface()
gs.will_plot_polygon_labels = False
Below we quickly generate 31 pictures of saddle connections corresponding to the 31 lines in Table 3 of the paper.
for i in range(31):
print("Generating picture for saddle connection %s:" % (i+1))
holonomy_vector = sadd_conn_total_sort[i][1]
sc = SaddleConnection(dodecahedron, (0, 0), holonomy_vector)
plt = gs.plot() + sc.plot(color="black", thickness=0.5)
show(plt)
We found that we get nicer pictures by minimizing the number of breaks in the rendered saddle connection. Here a break is a segment that cuts across two non-adjacent polygons in the image. The following counts the number of breaks in a saddle connection.
def count_breaks(sc):
r""" Count the number of breaks in a saddle connection when drawn. """
count = 0
traj = sc.trajectory()
for k in range(traj.combinatorial_length()-1):
seg = traj.segment(k)
if not gs.is_adjacent( seg.polygon_label(), seg.end().position().get_edge() ):
count += 1
return count
Now we present optimized pictures where we minimize the number of breaks under all images of a saddle connection by an isometry of the dodecahedron. Unfortunately, we don't know a way to do this quickly, so it is a long computation.
for i in range(31):
print("Generating optimized picture for saddle connection %s:" % (i+1))
holonomy_vector1 = sadd_conn_total_sort[i][1]
holonomy_vector2 = R^3 * vector( [holonomy_vector1[0], -holonomy_vector1[1]] )
min_sc = None
for hol_vec in [holonomy_vector1, holonomy_vector2]:
for label in range(12):
for vertex in range(5):
w = R^(2*vertex) * hol_vec
sc = SaddleConnection(dodecahedron, (label, vertex), w)
start = sc.start_tangent_vector()
end = sc.end_tangent_vector()
assert dodecahedron.surface_point(start.polygon_label(), start.point()) == \
dodecahedron.surface_point(end.polygon_label(), end.point())
breaks = count_breaks(sc)
if min_sc is None or breaks < min_breaks:
min_sc = sc
min_breaks = breaks
plt = gs.plot() + min_sc.plot(color="black", thickness=0.5)
show(plt)
plt.save_image("closed_sc_"+str(i+1)+".pdf")
Below we generate pictures of saddle connections corresponding to the lines in Table 4 of the paper.
for i,data in shortest_table.items():
print("Generating picture for shortest saddle connection %s:" % (i+1))
holonomy_vector = data[1]
sc = SaddleConnection(dodecahedron, (0, 0), holonomy_vector)
plt = gs.plot() + sc.plot(color="black", thickness=0.5)
show(plt)
Now we generate optimized pictures.
for i,data in shortest_table.items():
print("Generating optimized picture for shortest saddle connection %s:" % (i+1))
holonomy_vector1 = data[1]
holonomy_vector2 = R^3 * vector( [holonomy_vector1[0], -holonomy_vector1[1]] )
min_sc = None
for hol_vec in [holonomy_vector1, holonomy_vector2]:
for label in range(12):
for vertex in range(5):
w = R^(2*vertex) * hol_vec
sc = SaddleConnection(dodecahedron, (label, vertex), w)
start = sc.start_tangent_vector()
end = sc.end_tangent_vector()
assert dodecahedron.surface_point(start.polygon_label(), start.point()) == \
dodecahedron.surface_point(end.polygon_label(), end.point())
breaks = count_breaks(sc)
if min_sc is None or breaks < min_breaks:
min_sc = sc
min_breaks = breaks
plt = gs.plot() + min_sc.plot(color="black", thickness=0.5)
show(plt)
plt.save_image("shortest_sc_"+str(i+1)+".pdf")