Source code for flipper.surface


# Based on code by William Worden.

import flipper
from itertools import combinations

[docs]def LP(genus, num_punctures): g = int(genus) n = int(num_punctures) # The following functions make edge labelling easier. # In the picture of the triangulation in documentation, some edges are labelled with colored numbers. # The below function convert these coloured numbers to indices. # For example, if an edge of the triangulation is labelled 5 and colored green then its index is 5+2*g (or 2 if g = 1). green = lambda m: m + 2*g if g > 1 else 2 pink = lambda m: m + 4*g if g > 2 else 8 blue = lambda m: m + 5*g orange = lambda m: m + 6*g - 3 empty_lam = lambda: [0 for i in range(6*g+3*n-6)] if g == 0: triangles = [(3*i, 3*i+2, ~(3*((i+1) % (n-2)))) for i in range(n-2)] + [(3*i+1, ~(3*((i+1) % (n-2))+1), ~(3*i+2)) for i in range(n-2)] lams = dict() for i in range(n): lams['q'+str(i)] = empty_lam() for j in range(3, 3*n-8, 3) + [1, 2, 3*n-7]: lams['q0'][j] += 1 for j in range(3, 6) + [0, 1, 3*n-7]: lams['q1'][j] += 1 for i in range(0, n-4): for j in range(3*i+2, 3*i+5) + range(3*i+6, 3*i+9): lams['q'+str(i+2)][j] += 1 for j in range(1, 3*n-8, 3) + [3*n-10, 3*n-9, 3*n-7]: lams['q'+str(n-2)][j] += 1 for j in range(1, 3*n-7, 3) + range(0, 3*n-8, 3)+[3*n-7, 3*n-7]: lams['q'+str(n-1)][j] += 1 else: if g >= 1: triangles = [(k, k+1, green(k)) for k in range(0, 2*g-1, 2)] + [(~k, ~(k+1), ~green(k+1)) for k in range(0, 2*g-1, 2)] if g > 2: triangles += [(~green(k), green(k+1), ~pink(k/2)) for k in range(0, 2*g-1, 2)] if g == 2: triangles += [(~green(0), green(1), ~pink(0)), (~green(2), green(3), pink(0))] if g == 3: triangles += [(pink(0), pink(1), pink(2))] if g > 3: triangles += [(blue(k), pink(k+2), ~blue(k+1)) for k in range(0, g-4)] + [(pink(0), pink(1), ~blue(0)), (pink(g-2), pink(g-1), blue(g-4))] if n == 2: # For n == 2 we need a certain triangulation, so we can more easily compute the halftwist in terms of flips later. triangles.remove((2*g-2, 2*g-1, green(2*g-2))) triangles += [(orange(0), ~orange(2), orange(2)), (2*g-2, ~orange(0), orange(1)), (green(2*g-2), ~orange(1), 2*g-1)] if n > 2: triangles.remove((2*g-2, 2*g-1, green(2*g-2))) triangles += [(2*g-2, orange(0), orange(1)), (green(2*g-2), ~orange(3*n-5), orange(3*n-4)), (2*g-1, ~orange(3*n-4), ~orange(3*n-6))] triangles += [(~orange(k), orange(k+3), ~orange(k+2)) for k in range(0, 3*n-8, 3)] + [(~orange(k+1), orange(k+2), orange(k+4)) for k in range(0, 3*n-8, 3)] lams = dict() if g == 1: lams['a0'] = empty_lam() lams['a0'][1] += 1 lams['a0'][green(0)] += 1 lams['b0'] = empty_lam() lams['b0'][0] += 1 lams['b0'][green(0)] += 1 elif g > 1: for i in [0, 1]: lams['a'+str(i)] = empty_lam() for j in [2*i+1, green(2*i), green(2*i+1)]: lams['a'+str(i)][j] += 1 for i in range(g): lams['b'+str(i)] = empty_lam() for j in [2*i, green(2*i), green(2*i+1)]: lams['b'+str(i)][j] += 1 if g == 2: lams['c0'] = empty_lam() for j in [1, green(0), pink(0), green(2), 2, green(3), green(2), 3, 2, green(2), pink(0), green(1)]: lams['c0'][j] += 1 elif g > 2: for i in range(g-1): lams['c'+str(i)] = empty_lam() for j in [2*i+1, green(2*i), pink(i), pink(i+1), green(2*i+2), 2*i+2, green(2*i+3), green(2*i+2), 2*i+3, 2*i+2, green(2*i+2), pink(i+1), pink(i), green(2*i+1)]: lams['c'+str(i)][j] += 1 for i in range(1, g-2): lams['c'+str(i)][blue(i-1)] += 2 if n == 2: lams['b'+str(g-1)][orange(1)] += 1 if 'c'+str(g-2) in lams.keys(): lams['c'+str(g-2)][orange(1)] += 2 lams['p0'] = empty_lam() for j in [2*g-1, green(2*g-1), green(2*g-2), orange(0), orange(0), orange(1), orange(1), orange(2)]: lams['p0'][j] += 1 if g == 1: for i in range(n-1): lams['p'+str(i)][green(2*g-1)] -= 1 lams['q0'] = [2 for i in range(6*g+3*n-6)] lams['q0'][orange(0)] -= 2 lams['q0'][orange(2)] -= 2 if n > 2: if 'a'+str(g-1) in lams.keys(): lams['a'+str(g-1)][orange(3*n-4)] += 1 for j in [orange(3*k+1) for k in range(n-1)]: lams['b'+str(g-1)][j] += 1 if 'c'+str(g-2) in lams.keys(): for j in [orange(3*n-4)] + [orange(3*k+1) for k in range(n-1)] + [orange(3*k+1) for k in range(n-1)]: lams['c'+str(g-2)][j] += 1 lams['p0'] = empty_lam() for j in [2*g-1, green(2*g-1), green(2*g-2)]+[orange(3*k) for k in range(n-1)]+[orange(3*k+1) for k in range(n-1)]: lams['p0'][j] += 1 for i in range(1, n-1): lams['p'+str(i)] = empty_lam() for j in [2*g-1, green(2*g-1), green(2*g-2), orange(3*i-1)] + [orange(3*k) for k in range(i, n-1)] + [orange(3*k+1) for k in range(i, n-1)]: lams['p'+str(i)][j] += 1 if g == 1: for i in range(n-1): lams['p'+str(i)][green(2*g-1)] -= 1 lams['q0'] = [2 for i in range(6*g+3*n-6)] for j in [orange(3*k+2) for k in range(1, n-2)]+[orange(0)]: lams['q0'][j] -= 2 for j in [orange(3*k) for k in range(1, n-1)]+[orange(3*k+1) for k in range(1, n-1)]+[orange(2), orange(3*n-4)]: lams['q0'][j] -= 1 lams['q1'] = empty_lam() for j in [orange(0), orange(1), orange(3), orange(4), orange(5)]: lams['q1'][j] += 1 for i in range(2, n-1): lams['q'+str(i)] = empty_lam() for j in [orange(3*(i-1)-1), orange(3*(i-1)), orange(3*(i-1)+1), orange(3*(i-1)+3), orange(3*(i-1)+4), orange(3*(i-1)+5)]: lams['q'+str(i)][j] += 1 triangulation = flipper.create_triangulation(triangles) # Below we compute the halftwist around the isolating curve bounding the two punctures, for the n == 2 case, in terms of flips. # We then relable the edges, and re-create the triangulation, since we have altered the original one in the process of finding the flips. if n == 2: def _edges_around_vert(t, vert): """ Return the cyclically ordered list of edges around the given vertex. Vertex is input as an int corresponding to its index. That is, for vertex "Puncture 0", do edges_around_vert(t, 0). """ crns = t.corner_class_of_vertex(t.vertices[vert]) edges = [crns[i].edges[1].label for i in range(len(crns))] edges.reverse() return edges t = triangulation seq = [] vert = 0 if len(_edges_around_vert(t, 0)) > 1 else 1 edges = _edges_around_vert(t, vert) fixed_edge = orange(2) if orange(2) in edges else ~orange(2) ind = edges.index(fixed_edge) edges_0 = [edges[(ind+i) % len(edges)] for i in range(len(edges))] while len(edges) > 1: ind = (edges.index(fixed_edge) + 1) % len(edges) seq.append(edges[ind]) t = t.flip_edge(edges[ind]) edges = _edges_around_vert(t, vert) edges = _edges_around_vert(t, (vert+1) % 2) ind = edges.index(~fixed_edge) edges = [edges[(ind + i) % len(edges)] for i in range(len(edges))] isom = dict(zip(edges, edges_0)) seq.append(isom) seq.reverse() triangulation = flipper.create_triangulation(triangles) isolating_halftwist = triangulation.encode(seq) laminations = dict([(key, triangulation.lamination(value)) for key, value in lams.items()]) twists = dict() halftwists = dict() # If n == 2 then we have to add the halftwist (created above from flips) seperately. # It seems that the halftwist computed above is actually the right Dehn twist (based on the check below using chain relation). # So it is called 'Q0' and its inverse is 'q0' (lower case indicates left twists, upper case right twists). if n == 2: # halftwists['Q0'] = isolating_halftwist halftwists['q0'] = isolating_halftwist.inverse() for key in lams.keys(): if key == 'q0' and n == 2: pass elif key[0] == 'q': halftwists[key] = laminations[key].encode_halftwist(1) # halftwists[key.upper()] = laminations[key].encode_halftwist(-1) else: twists[key] = laminations[key].encode_twist(1) # twists[key.upper()] = laminations[key].encode_twist(-1) all_twists = dict([(key, value) for key, value in twists.items()] + [(key, value) for key, value in halftwists.items()]) return flipper.kernel.EquippedTriangulation(triangulation, laminations, all_twists)
def _check(surface): # Check to make sure the MCG generator curves have the correct geometric intersection properties. # This was used as a test to make sure there were no errors in the code above that creates the laminations. # This of course is not sufficient to guarantee that there are no errors, but its a good start. # Surfaces S_{g, n} for 1 <= g <= 5 and 1 <= n <= 20 have been checked. if surface.triangulation.num_vertices == 2: # Check the square of the halftwist against the chain relation. # This confirms that the computation of the halftwists in terms of flips was successful. # All surfaces S_{g, 2} for g < 23 have been tested. g = surface.triangulation.genus chain = surface.mapping_class('a0.b0.' + '.'.join('c%d.b%d' % (i, i+1) for i in range(g-1))) assert(chain**(4*g+2) == surface.mapping_class('q0.q0')) def intersection(l1, l2): return surface.lamination(l1).geometric_intersection(surface.lamination(l2)) keys = sorted(surface.laminations.keys()) intersections = dict([(tuple(sorted([l1, l2])), intersection(l1, l2)) for l1, l2 in combinations(keys, 2) if intersection(l1, l2) != 0]) if surface.triangulation.genus == 1: expected = dict( [(('a0', 'b0'), 1)] + [(('b0', 'p{}'.format(i)), 1) for i in range(surface.triangulation.num_vertices-1)] + [(('p{}'.format(i), 'q{}'.format(i)), 2) for i in range(surface.triangulation.num_vertices-1)] + [(tuple(sorted(['q{}'.format(i), 'q{}'.format(i+1)])), 2) for i in range(surface.triangulation.num_vertices-2)] ) else: expected = dict( [(('a0', 'b0'), 1), (('a1', 'b1'), 1), (('b0', 'c0'), 1)] + + [(('b{}'.format(i), 'c{}'.format(i-1)), 1) for i in range(1, surface.triangulation.genus)] + [(('b{}'.format(i), 'c{}'.format(i)), 1) for i in range(1, surface.triangulation.genus-1)] + [(('b{}'.format(surface.triangulation.genus-1), 'p{}'.format(i)), 1) for i in range(surface. triangulation.num_vertices-1)] + [(('p{}'.format(i), 'q{}'.format(i)), 2) for i in range(surface.triangulation.num_vertices-1)] + [(tuple(sorted(['q{}'.format(i), 'q{}'.format(i+1)])), 2) for i in range(surface.triangulation.num_vertices-2)] ) return expected == intersections