def fix_orientations(tri, angle, return_isom=False): """ Fix the orientations of the tetrahedra in triangulation so that they are consistently oriented. We choose how to flip each tetrahedron so that the angle structure list does not change. If return_isom, return the isomorphism from the original tri to the fixed orientations tri """ orig_tri = regina.Triangulation3(tri) old_orientations = find_orientations(tri) swaps = [] for i, orientation in enumerate(old_orientations): if orientation == -1: swaps.append( reverse_tet_orientation(tri, tri.tetrahedron(i), angle[i])) else: swaps.append(regina.Perm4()) ## identity if return_isom: out_isom = regina.Isomorphism3.identity(len(swaps)) for i, p in enumerate(swaps): out_isom.setFacetPerm(i, p) return out_isom ### Regina 6 didn't let us build the isom directly from perms in python. Regina 7 does, using setFacetPerm ### old: # all_isoms = orig_tri.findAllIsomorphisms(tri) ### we will be order two, so we dont care which way this goes # for isom in all_isoms: # if not moves_tetrahedra(isom): # for i in range(tri.countTetrahedra()): # assert swaps[i] == isom.facetPerm(i) # assert isom == out_isom # return isom assert False ## should never get here
def tet_to_face_data(tri, tet_num, face_num, vertices): ### vertices is list in order (trailing, pivot, leading) in numbering for the tet """given a tetrahedron, face_num and vertices in the labelling for the tetrahedron, convert to face_index and vertices in the labelling for the face. This is the correct format to feed into drill.""" assert face_num not in vertices # print('tet_num, face_num, vertices (trailing, pivot, leading)', tet_num, face_num, vertices) tet = tri.tetrahedron(tet_num) facemapping = tet.faceMapping(2,face_num) face = tet.triangle(face_num) new_vertices = facemapping.inverse() * regina.Perm4(vertices[0], vertices[1], vertices[2], face_num) return (face.index(), regina.Perm3(new_vertices[0], new_vertices[1], new_vertices[2]))
def vertex_correspondence_to_perm4(x, y): """ given a correspondence between a triangle of a 3D-triangulation and a triangle of a 2D-triangulation, returns the associated permutation on 0,1,2,3 """ vertex_to_vertex = [[x[i], y[i]] for i in range(3)] vertex_to_vertex.sort(key=lambda x: x[0]) missing_vertex = face_num_in_tet(x) if missing_vertex == 0: perm = regina.Perm4(3, vertex_to_vertex[0][1], vertex_to_vertex[1][1], vertex_to_vertex[2][1]) elif missing_vertex == 1: perm = regina.Perm4(vertex_to_vertex[0][1], 3, vertex_to_vertex[1][1], vertex_to_vertex[2][1]) elif missing_vertex == 2: perm = regina.Perm4(vertex_to_vertex[0][1], vertex_to_vertex[1][1], 3, vertex_to_vertex[2][1]) else: perm = regina.Perm4(vertex_to_vertex[0][1], vertex_to_vertex[1][1], vertex_to_vertex[2][1], 3) return perm
def reverse_tet_orientation(triangulation, tet, pi_location): # only need triangulation here for diagnostics """ Reglues tet into triangulation with reversed orientation. Returns which permutation of the tetrahedron was used. """ swaps = { 0: regina.Perm4(1, 0, 2, 3), 1: regina.Perm4(2, 1, 0, 3), 2: regina.Perm4(3, 1, 2, 0) } swap = swaps[pi_location] adjtets = [] oldadjgluings = [] adjgluings = [] self_faces = [] other_faces = [] for face in range(4): adjtet = tet.adjacentTetrahedron(face) adjtets.append(adjtet) oldadjgluing = tet.adjacentGluing(face) oldadjgluings.append(tet.adjacentGluing(face)) if adjtet != tet: adjgluings.append( oldadjgluing * swap) # swaps 0 with 1 in this tet, not the other tet other_faces.append(face) else: adjgluings.append(swap * oldadjgluing * swap) self_faces.append(face) tet.isolate() # undo all gluings for face in other_faces: tet.join(swap[face], adjtets[face], adjgluings[face]) if len(self_faces) > 0: # assume it must be two assert len(self_faces) == 2, "4 self faces, must be an error..." face = self_faces[0] # only need to make one of the identifications tet.join(swap[face], adjtets[face], adjgluings[face]) return swap
def excise_fans(tri, angle, fan_nums=None): vt = veering_triangulation(tri, angle) veering_colours = vt.veering_colours ## "red" or "blue" tet_types = vt.tet_types ### "toggle", "red" or "blue" tet_vert_coors = vt.coorientations if fan_nums == None: ## do all fans fan_nums = [ n for n in range(len(tet_types)) if tet_types[n] != "toggle" ] excisedAngle = angle[:] for fan_num in sorted(fan_nums, reverse=True): del excisedAngle[fan_num] minority_edge_pairs = [] for fan_num in fan_nums: assert tet_types[fan_num] != "toggle" tops, bottoms = get_top_and_bottom_nums(tet_vert_coors, fan_num) if 0 in tops: pi_pair = list(tops) else: pi_pair = list(bottoms) other = pi_pair[(pi_pair.index(0) + 1) % 2] for i in range(1, 4): if i != other: if vt.get_edge_between_verts_colour( fan_num, (0, i)) != tet_types[fan_num]: ### "red" or "blue" last = 6 - i - other ## the fourth vertex minority_edge_pairs.append([(0, i), (other, last)]) break excisedTri = regina.Triangulation3(tri) ### copy fan_tets = [excisedTri.tetrahedron(fan_num) for fan_num in fan_nums] for k, tet in enumerate(fan_tets): # print 'k', k, 'tet.index', tet.index() minority_edge_pair = minority_edge_pairs[k] # print minority_edge_pair ### record gluings for neighbours neighbours = [tet.adjacentSimplex(i) for i in range(4)] gluings = [tet.adjacentGluing(i) for i in range(4)] # print [neighbour.index() for neighbour in neighbours] # print gluings tet.isolate() ## now glue neighbours to each other if tet not in neighbours: to_glue = [0, 1, 2, 3] while to_glue != []: i = to_glue.pop() if i in minority_edge_pair[0]: j = minority_edge_pair[0][(minority_edge_pair[0].index(i) + 1) % 2] else: j = minority_edge_pair[1][(minority_edge_pair[1].index(i) + 1) % 2] teti = neighbours[i] tetj = neighbours[j] teti.join( gluings[i][i], tetj, gluings[j] * regina.Perm4(j, i) * (gluings[i].inverse())) to_glue.remove(j) # print i,j else: ### tet is glued to itself, which makes it trickier to remove ### first, find self-gluings self_gluings = [] self_gluings = [neighbours.index(tet)] self_gluings.append(neighbours.index(tet, self_gluings[0] + 1)) ### add second entry other_gluings = [0, 1, 2, 3] other_gluings.remove(self_gluings[0]) other_gluings.remove(self_gluings[1]) i, j = other_gluings teti = neighbours[i] tetj = neighbours[j] if i in minority_edge_pair[0]: p = minority_edge_pair[0][(minority_edge_pair[0].index(i) + 1) % 2] q = minority_edge_pair[1][(minority_edge_pair[1].index(j) + 1) % 2] else: p = minority_edge_pair[1][(minority_edge_pair[1].index(i) + 1) % 2] q = minority_edge_pair[0][(minority_edge_pair[0].index(j) + 1) % 2] teti.join( gluings[i][i], tetj, gluings[j] * regina.Perm4(j, q) * gluings[p] * regina.Perm4(p, i) * (gluings[i].inverse())) excisedTri.removeTetrahedron(tet) return excisedTri, excisedAngle
def threeTwoMove(tri, branch, edge_num, return_triangle=False): """Apply a 3-2 move to a triangulation with a branched surface, if possible. If perform = False, returns if the move is possible. modifies tri, returns (tri, branch) for the performed move""" ### note if this function does not return False, then there is only one ### possible branch so we just return it rather than a list # assert is_branched(tri, branch) assert has_non_sing_semiflow(tri, branch) edge = tri.edge(edge_num) if edge.degree() != 3: return False tets = [] tet_nums = [] vertices = [] for i in range(3): embed = edge.embedding(i) tets.append(embed.simplex()) tet_nums.append(tets[i].index()) vertices.append(embed.vertices()) if len(set([tet.index() for tet in tets])) != 3: return False ### tetrahedra must be distinct ### check we do the same as regina... tri2 = regina.Triangulation3(tri) ## make a copy tri2.pachner(tri2.edge(edge_num)) ## record the tetrahedra and gluings adjacent to the tets gluings = [] for i in range(3): tet_gluings = [] for j in range(2): tet_gluings.append([ tets[i].adjacentTetrahedron(vertices[i][j]), tets[i].adjacentGluing(vertices[i][j]) ]) gluings.append(tet_gluings) for i in range(3): assert tets[i].adjacentTetrahedron(vertices[i][2]) == tets[ (i + 1) % 3] ### The edge embeddings should be ordered this way... large_edges = [] ### record large edge info for the outer faces for i in range(3): this_tet_large_edges = [] for j in range(2): this_tet_large_edges.append( large_edge_of_face(branch[tets[i].index()], vertices[i][j])) large_edges.append(this_tet_large_edges) ### add new tetrahedra new_tets = [] for i in range(2): new_tets.append(tri.newTetrahedron()) ### glue across face new_tets[0].join(3, new_tets[1], regina.Perm4(0, 2, 1, 3)) ### replace mapping info with corresponding info for the 2 tet. Self gluings will be annoying... ### write vertices[i][j] as vij ### tets[0] new_tet1 ### _________ _________ ### ,'\`.v00,'/`. ,'\ /`. ### ,' \ `.' / `. ,' \ 3 / `. ### ,' v10\ | /v20 `. ,' \ / `. ### /|\ \|/ /|\ / \ \ / / \ ### / | \ * / | \ / \ * / \ ### v12/ | \..... | ...../ | \v23 / 1 _\..... | ...../_ 2 \ ### / ,' / * \ `. \ /_--"" / * \ ""--_\ ### \`.| /v03 /|\ v02\ |,'/ `. \`. 2 / / \ \ 1 ,'/ ### \ `./ / | \ \,' / ----} \ `./ / \ \,' / ### \|/`. / | \ ,'\|/ ,' \ /`. / 0 \ ,'\ / ### \ `. / | \ ,' / \ `. / \ ,' / ### \ * v13|v22 * / \ `---------' / ### \ \ | / / \ \ / / ### \ \ | / / \ \ 0 / / ### \ \ | / / \ \ / / ### tets[1] \ \|/ / tets[2] \ \ / / ### \ * / \ * / new_tet0 ### \..v01../ \...|.../ ### \`.|.'/ \ | / ### v11\ | /v21 \ 3 / ### \|/ \|/ ### * * # permutations taking the vertices for a face of the 2-tet ball to the # vertices of the same face for the 3-tet ball # these should be even in order to preserve orientability. # perms = [[regina.Perm4( vertices[0][0], vertices[0][2], vertices[0][3], vertices[0][1] ), ### opposite v00 # regina.Perm4( vertices[0][1], vertices[0][3], vertices[0][2], vertices[0][0] ) ### opposite v01 # ], # [regina.Perm4( vertices[1][3], vertices[1][0], vertices[1][2], vertices[1][1] ), ### opposite v10 # regina.Perm4( vertices[1][3], vertices[1][2], vertices[1][1], vertices[1][0] ) ### opposite v11 # ], # [regina.Perm4( vertices[2][2], vertices[2][3], vertices[2][0], vertices[2][1] ), ### opposite v20 # regina.Perm4( vertices[2][2], vertices[2][1], vertices[2][3], vertices[2][0] ) ### opposite v21 # ] # ] perms = [ [ vertices[0] * regina.Perm4(0, 2, 3, 1), ### opposite v00 vertices[0] * regina.Perm4(1, 3, 2, 0) ### opposite v01 ], [ vertices[1] * regina.Perm4(3, 0, 2, 1), ### opposite v10 vertices[1] * regina.Perm4(3, 2, 1, 0) ### opposite v11 ], [ vertices[2] * regina.Perm4(2, 3, 0, 1), ### opposite v20 vertices[2] * regina.Perm4(2, 1, 3, 0) ### opposite v21 ] ] for i in range(3): for j in range(2): gluing = gluings[i][j] if gluing != None: if gluing[0] not in tets: ### not a self gluing gluing[1] = gluing[1] * perms[i][j] else: i_other = tets.index(gluing[0]) otherfacenum = gluing[1][vertices[i][j]] j_other = [vertices[i_other][k] for k in range(4)].index(otherfacenum) assert gluings[i_other][j_other][0] == tets[i] assert gluings[i_other][j_other][1].inverse( ) == gluings[i][j][1] gluings[i_other][ j_other] = None ### only do a self gluing from one side gluing[0] = new_tets[ j_other] ### j refers to the vertex on the same 3 side gluing[1] = perms[i_other][j_other].inverse( ) * gluing[1] * perms[i][j] ### unglue three tetrahedra for tet in tets: tet.isolate() ### remove the tetrahedra for tet in tets: tri.removeSimplex(tet) ### make the gluings on the boundary of the new ball for i in range(3): for j in range(2): if gluings[i][j] != None: if j == 0 or i == 0: assert new_tets[j].adjacentTetrahedron( i) == None ## not glued assert gluings[i][j][0].adjacentTetrahedron( gluings[i][j][1][i]) == None new_tets[j].join(i, gluings[i][j][0], gluings[i][j][1]) else: assert new_tets[j].adjacentTetrahedron( 3 - i) == None ## not glued assert gluings[i][j][0].adjacentTetrahedron( gluings[i][j][1][3 - i]) == None new_tets[j].join(3 - i, gluings[i][j][0], gluings[i][j][1]) ## swap 1 and 2 assert tri.isIsomorphicTo(tri2) assert tri.isOriented() # ### update the branched surface tet_nums.sort() branch.pop(tet_nums[2]) branch.pop(tet_nums[1]) branch.pop(tet_nums[0]) ## remove from the list in the correct order! ### update the branched surface ### for each of the two new tetrahedra, figure out what their outer face train tracks are large_edges_new = [] ### record large edge info for the outer faces for i in range(3): this_tet_large_edges_new = [] for j in range(2): new_large_edge = perms[i][j].inverse()[large_edges[i][j]] # assert new_large_edge != i ?? this_tet_large_edges_new.append(new_large_edge) large_edges_new.append(this_tet_large_edges_new) large_edges_new_transposed = [list(i) for i in zip(*large_edges_new)] branch0 = determine_possible_branch_given_three_faces( [0, 1, 2], large_edges_new_transposed[0]) branch1 = determine_possible_branch_given_three_faces( [0, 2, 1], large_edges_new_transposed[1]) if branch0 == None or branch1 == None: return False large_edge_for_new_tet0 = large_edge_of_face(branch0, 3) large_edge_for_new_tet1 = large_edge_of_face(branch1, 3) if large_edge_for_new_tet0 == large_edge_for_new_tet1: if large_edge_for_new_tet0 != 0: return False else: if large_edge_for_new_tet0 + large_edge_for_new_tet1 != 3: ### one must be 1, one must be 2 return False branch.extend([branch0, branch1]) # if not is_branched(tri, branch): if not has_non_sing_semiflow(tri, branch): return False if not return_triangle: return (tri, branch) else: return (tri, branch, new_tets[0].triangle(3).index())
def twoThreeMove(tri, branch, face_num, perform=True, return_edge=False): """Apply a 2-3 move to a triangulation with a branched surface, if possible. If perform = False, returns if the move is possible. If perform = True, modifies tri, returns (tri, possible_branches) for the performed move""" ### possible_branches is a list # assert is_branched(tri, branch) assert has_non_sing_semiflow(tri, branch) face = tri.triangle(face_num) embed0 = face.embedding(0) tet0 = embed0.simplex() tet_num0 = tet0.index() tet_0_face_num = embed0.face() vertices0 = embed0.vertices( ) # Maps vertices (0,1,2) of face to the corresponding vertex numbers of tet0 embed1 = face.embedding(1) tet1 = embed1.simplex() tet_num1 = tet1.index() tet_1_face_num = embed1.face() vertices1 = embed1.vertices( ) # Maps vertices (0,1,2) of face to the corresponding vertex numbers of tet1 if tet0 == tet1: ### Cannot perform a 2-3 move across a self-gluing return False ### are all moves valid for the branched surface? ### for now, lets assume yes ### check we do the same as regina... tri2 = regina.Triangulation3(tri) ## make a copy tri2.pachner(tri2.triangle(face_num)) ### We have to implement twoThreeMove ourselves. e.g. we do a 2-3 move to canonical fig 8 knot complement triangulation. ### All of the original tetrahedra are removed. I don't see any way to carry the angle structure through without knowing ### exactly how Ben's implementation works. ## record the tetrahedra and gluings adjacent to tet0 and tet1 tets = [tet0, tet1] vertices = [vertices0, vertices1] # print('2-3 vertices signs') # print([v.sign() for v in vertices]) gluings = [] for i in range(2): tet_gluings = [] for j in range(3): tet_gluings.append([ tets[i].adjacentTetrahedron(vertices[i][j]), tets[i].adjacentGluing(vertices[i][j]) ]) # if tets[i].adjacentTetrahedron(vertices[i][j]) in tets: # print('self gluing') gluings.append(tet_gluings) large_edges = [] ### record large edge info for the outer faces for i in range(2): this_tet_large_edges = [] for j in range(3): this_tet_large_edges.append( large_edge_of_face(branch[tets[i].index()], vertices[i][j])) large_edges.append(this_tet_large_edges) ### add new tetrahedra new_tets = [] for i in range(3): new_tets.append(tri.newTetrahedron()) ### glue around degree 3 edge for i in range(3): new_tets[i].join(2, new_tets[(i + 1) % 3], regina.Perm4(0, 1, 3, 2)) ### replace mapping info with corresponding info for the 3 tet. Self gluings will be annoying... ### write verticesi[j] as vij ### tet0 new_tet0 ### _________ _________ ### ,'\ /`. ,'\`. ,'/`. ### ,' \ v03 / `. ,' \ `0' / `. ### ,' \ / `. ,' \ | / `. ### / \ \ / / \ /|\ \|/ /|\ ### /v02\ * /v01\ / | \ * / | \ ### / _\..... | ...../_ \ / | 3\..... | ...../2 | \ ### /_--"" / * \ ""--_\ /2 ,' / * \ `. 3\ ### \`.v12/ / \ \v11,'/ `. \`.| / /|\ \ |,'/ ### \ `./ / \ \,' / ----} \ `./ / | \ \,' / ### \ /`. / v00 \ ,'\ / ,' \|/`. / | \ ,'\|/ ### \ `. / \ ,' / \ `. / | \ ,' / ### \ `---------' / \ * 3 | 2 * / ### \ \ / / \ \ | / / ### \ \ v10 / / new_tet1 \ \ | / / new_tet2 ### \ \ / / \ \ | / / ### \ \ / / \ \|/ / ### \ * / \ * / ### tet1 \...|.../ \...|.../ ### \ | / \`.|.'/ ### \v13/ \ 1 / ### \|/ \|/ ### * * # permutations taking the vertices for a face of the 3-tet ball to the # vertices of the same face for the 2-tet ball # these should be even in order to preserve orientability. # exactly one of vertices[0] and vertices[1] is even, but it seems to depend on the face. # perms = [[regina.Perm4( vertices[0][3], vertices[0][0], vertices[0][1], vertices[0][2] ), ### opposite v00 # regina.Perm4( vertices[0][3], vertices[0][1], vertices[0][2], vertices[0][0] ), ### opposite v01 # regina.Perm4( vertices[0][3], vertices[0][2], vertices[0][0], vertices[0][1] ) ### opposite v02 # ], # [regina.Perm4( vertices[1][0], vertices[1][3], vertices[1][1], vertices[1][2] ), ### opposite v10 # regina.Perm4( vertices[1][1], vertices[1][3], vertices[1][2], vertices[1][0] ), ### opposite v11 # regina.Perm4( vertices[1][2], vertices[1][3], vertices[1][0], vertices[1][1] ) ### opposite v12 # ] # ] perms = [ [ vertices[0] * regina.Perm4(3, 0, 1, 2), ### opposite v00 vertices[0] * regina.Perm4(3, 1, 2, 0), ### opposite v01 vertices[0] * regina.Perm4(3, 2, 0, 1) ### opposite v02 ], [ vertices[1] * regina.Perm4(0, 3, 1, 2), ### opposite v10 vertices[1] * regina.Perm4(1, 3, 2, 0), ### opposite v11 vertices[1] * regina.Perm4(2, 3, 0, 1) ### opposite v12 ] ] flip = perms[0][0].sign() == -1 if flip: #then all of the signs are wrong, switch 0 and 1 on input perms = [[p * regina.Perm4(1, 0, 2, 3) for p in a] for a in perms] # print('flip') # else: # print('no flip') # print('2-3 perms signs') # print([[p.sign() for p in a] for a in perms]) for i in range(2): for j in range(3): gluing = gluings[i][j] if gluing != None: if gluing[0] not in tets: ### not a self gluing gluing[1] = gluing[1] * perms[i][j] else: i_other = tets.index(gluing[0]) otherfacenum = gluing[1][vertices[i][j]] j_other = [vertices[i_other][k] for k in range(4)].index(otherfacenum) assert gluings[i_other][j_other][0] == tets[i] assert gluings[i_other][j_other][1].inverse( ) == gluings[i][j][1] gluings[i_other][ j_other] = None ### only do a self gluing from one side gluing[0] = new_tets[j_other] gluing[1] = perms[i_other][j_other].inverse( ) * gluing[1] * perms[i][j] ### unglue two tetrahedra tet0.isolate() tet1.isolate() ### remove the tetrahedra tri.removeSimplex(tet0) tri.removeSimplex(tet1) ### make the gluings on the boundary of the new ball for i in range(2): for j in range(3): if gluings[i][j] != None: if flip: new_tets[j].join(i, gluings[i][j][0], gluings[i][j][1]) else: new_tets[j].join(1 - i, gluings[i][j][0], gluings[i][j][1]) assert tri.isIsomorphicTo(tri2) assert tri.isOriented() ### update the branched surface ### for each of the three new tetrahedra, figure out what their outer face train tracks are large_edges_new = [] ### record large edge info for the outer faces for i in range(2): this_tet_large_edges_new = [] for j in range(3): new_large_edge = perms[i][j].inverse()[large_edges[i][j]] if flip: assert new_large_edge != i ### the face number cannot be the large vertex for that face else: assert new_large_edge != 1 - i this_tet_large_edges_new.append(new_large_edge) large_edges_new.append(this_tet_large_edges_new) candidate_branches = [] for j in range(3): if flip: candidate_branches.append( determine_possible_branch_given_two_faces( (0, 1), (large_edges_new[0][j], large_edges_new[1][j]))) else: candidate_branches.append( determine_possible_branch_given_two_faces( (1, 0), (large_edges_new[0][j], large_edges_new[1][j]))) ### update the branch structure, many possible ways tet_indices = [tet_num0, tet_num1] tet_indices.sort() branch.pop(tet_indices[1]) branch.pop(tet_indices[0]) ## remove from the list in the correct order! out = [] for cand0 in candidate_branches[0]: for cand1 in candidate_branches[1]: for cand2 in candidate_branches[2]: candidate = branch[:] + [cand0, cand1, cand2] # print('candidate', candidate) # if is_branched(tri, candidate): if has_non_sing_semiflow(tri, candidate): out.append(candidate) # assert len(out) > 0 ### this works if we check is_branched three lines above, but not if we check has_non_sing_semiflow if len( out ) == 0: ### with has_non_sing_semiflow instead, we might not get any return False if not return_edge: return (tri, out) else: return (tri, out, new_tets[0].edge(0).index())
def drill(tri, loop, angle=None, branch=None, sig=None): # sig just for diagnostics """ Returns the new cusp formed by drilling """ if angle != None: face_coorientations = is_transverse_taut( tri, angle, return_type="face_coorientations") assert face_coorientations != False tet_vert_coorientations = is_transverse_taut( tri, angle, return_type="tet_vert_coorientations") assert tet_vert_coorientations != False original_tri = regina.Triangulation3(tri) original_countTetrahedra = tri.countTetrahedra() original_countBoundaryComponents = tri.countBoundaryComponents() ### add new tetrahedra new_0_tets = [] new_1_tets = [] ## both relative to regina's two embeddings for the face for i in range(len(loop)): new_0_tets.append(tri.newTetrahedron()) new_1_tets.append(tri.newTetrahedron()) ### we will glue tetrahedra together with a numbering that is convenient but ### unfortunately not oriented. We will orient later. # 1 pivot # /|\ # / | \ # / ,3. \ # /,' `.\ # 0---------2 leading # trailing for i in range(len(loop)): new_0_tets[i].join(1, new_1_tets[i], regina.Perm4(0, 1, 2, 3)) ### now glue along the loop path, need to worry about regina's embeddings for neighbouring triangles loop_face_tets0 = [] loop_face_vertices0 = [] loop_face_tets1 = [] loop_face_vertices1 = [ ] ### need to store these because they cannot be recomputed once we start ungluing faces for i in range(len(loop)): # print('collect info: i', i) face_data = loop[i] face_index = face_data[0] vert_nums = face_data[1] face = tri.triangles()[face_index] face_embed0 = face.embedding(0) face_tet = face_embed0.simplex() face_vertices = face_embed0.vertices() face_opposite_vert = face_vertices[3] face_other_non_edge_vert = face_vertices[ vert_nums[0]] ## opposite trailing vertex face_data_next = loop[(i + 1) % len(loop)] face_index_next = face_data_next[0] vert_nums_next = face_data_next[1] face_next = tri.triangles()[face_index_next] face_next_embed0 = face_next.embedding(0) face_next_tet = face_next_embed0.simplex() face_next_vertices = face_next_embed0.vertices() face_next_opposite_vert = face_next_vertices[3] face_next_other_non_edge_vert = face_next_vertices[ vert_nums_next[2]] ## opposite leading vertex ### things to store for later loop_face_tets0.append(face_tet) # store for later loop_face_vertices0.append(face_vertices) #store for later face_embed1 = face.embedding(1) loop_face_tets1.append(face_embed1.simplex()) loop_face_vertices1.append(face_embed1.vertices()) edge = face.edge(vert_nums[0]) ## opposite trailing vertex # print('edge index', edge.index()) assert edge == face_next.edge( vert_nums_next[2]) ## opposite leading vertex edgemapping = face.faceMapping(1, vert_nums[0]) next_edgemapping = face_next.faceMapping(1, vert_nums_next[2]) face_gluing_regina_numbering = next_edgemapping * ( edgemapping.inverse() ) ### maps vertices 0,1,2 on face to corresponding vertices on face_next assert face_gluing_regina_numbering[3] == 3 face_gluing_regina_numbering = regina.Perm3( face_gluing_regina_numbering[0], face_gluing_regina_numbering[1], face_gluing_regina_numbering[2]) assert face_gluing_regina_numbering[vert_nums[0]] == vert_nums_next[2] face_gluing = vert_nums_next.inverse( ) * face_gluing_regina_numbering * vert_nums assert face_gluing[0] == 2 signs = [] edge_embeddings = edge.embeddings() for embed in edge_embeddings: if embed.simplex() == face_tet: if set([embed.vertices()[2], embed.vertices()[3]]) == set( [face_opposite_vert, face_other_non_edge_vert]): # print('embed data face_tet', embed.simplex().index(), embed.vertices()) # print('embed edge vert nums', embed.vertices()[0], embed.vertices()[1]) signs.append(face_opposite_vert == embed.vertices()[2]) if embed.simplex() == face_next_tet: if set([embed.vertices()[2], embed.vertices()[3]]) == set([ face_next_opposite_vert, face_next_other_non_edge_vert ]): # print('embed data face_next_tet', embed.simplex().index(), embed.vertices()) # print('embed edge vert nums', embed.vertices()[0], embed.vertices()[1]) signs.append( face_next_opposite_vert == embed.vertices()[2]) # print('signs', signs) assert len(signs) == 2 if signs[0] == signs[ 1]: ### coorientations are same around the edge (not a transverse taut coorientation!) new_0_tets[i].join( 0, new_1_tets[(i + 1) % len(loop)], regina.Perm4(2, face_gluing[1], face_gluing[2], 3)) new_1_tets[i].join( 0, new_0_tets[(i + 1) % len(loop)], regina.Perm4(2, face_gluing[1], face_gluing[2], 3)) else: new_0_tets[i].join( 0, new_0_tets[(i + 1) % len(loop)], regina.Perm4(2, face_gluing[1], face_gluing[2], 3)) new_1_tets[i].join( 0, new_1_tets[(i + 1) % len(loop)], regina.Perm4(2, face_gluing[1], face_gluing[2], 3)) ### now unglue tri along the loop and glue in the new tetrahedra for i in range(len(loop)): # print('modify triangulation: i', i) vert_nums = loop[i][1] face_tet0 = loop_face_tets0[i] face_vertices0 = loop_face_vertices0[i] face_tet1 = loop_face_tets1[i] face_vertices1 = loop_face_vertices1[i] face_opposite_vert0 = face_vertices0[3] face_tet0.unjoin(face_opposite_vert0) ### glue torus shell to the old tetrahedra vert_nums_Perm4 = regina.Perm4(vert_nums[0], vert_nums[1], vert_nums[2], 3) new_0_tets[i].join(3, face_tet0, face_vertices0 * vert_nums_Perm4) new_1_tets[i].join(3, face_tet1, face_vertices1 * vert_nums_Perm4) assert tri.isValid() assert tri.countBoundaryComponents( ) == original_countBoundaryComponents + 1 if angle != None: for i in range(len(loop)): face_data = loop[i] face_index = face_data[0] face = original_tri.triangles()[face_index] vert_nums = face_data[1] face_embed0 = face.embedding(0) face_tet = face_embed0.simplex() face_vertices = face_embed0.vertices() face_opposite_vert = face_vertices[3] coor_points_out_of_tet0 = (tet_vert_coorientations[ face_tet.index()][face_opposite_vert] == +1) # if (flow_agrees_with_regina_numbers != face_cor_agrees_with_regina_numbers) != coor_points_out_of_tet0: if coor_points_out_of_tet0: angle.extend([0, 2]) else: angle.extend([2, 0]) # print(sig, loop, angle, is_taut(tri, angle)) assert is_taut(tri, angle) if branch != None: for i in range(len(loop)): branch.extend([1, 1]) assert is_branched(tri, branch) M = snappy.Manifold(tri) if M.volume() < 1.0: print('not hyperbolic', sig, loop, angle, M.volume()) assert False # print(M.verify_hyperbolicity()) ### very slow # print(M.volume()) # assert M.volume() > 1.0 ### ### now orient swaps = [regina.Perm4() ] * original_countTetrahedra ### identity permutations for i in range(len(loop)): if new_0_tets[i].adjacentGluing(3).sign() == 1: if angle != None: this_tet_angle = angle[original_countTetrahedra + 2 * i] else: this_tet_angle = 0 ### pi_location = 0 is an arbitrary choice swaps.append( reverse_tet_orientation(tri, new_0_tets[i], this_tet_angle)) else: swaps.append(regina.Perm4()) if new_1_tets[i].adjacentGluing(3).sign() == 1: if angle != None: this_tet_angle = angle[original_countTetrahedra + 2 * i + 1] else: this_tet_angle = 0 ### pi_location = 0 is an arbitrary choice swaps.append( reverse_tet_orientation(tri, new_1_tets[i], this_tet_angle)) else: swaps.append(regina.Perm4()) assert tri.isOriented() if angle != None: assert is_taut(tri, angle) if branch != None: # print('loop, branch, swaps', loop, branch, swaps) apply_swaps_to_branched_surface(branch, swaps) # print('loop, branch, swaps', loop, branch, swaps) assert is_branched(tri, branch) assert has_non_sing_semiflow(tri, branch) ### return the vertex of the triangulation corresponding to the drilled cusp drilled_cusp = new_0_tets[0].vertex(swaps[new_0_tets[0].index()][3]) assert drilled_cusp.degree() == len(new_0_tets) + len(new_1_tets) return drilled_cusp.index()
def veering_mobius_dehn_surgery(triangulation, angle_struct, face_num): tri = regina.Triangulation3(triangulation) # make a copy angle = list(angle_struct) # make a copy face = tri.triangle(face_num) assert face.isMobiusBand() # Note that dunce caps cannot appear in a veering triangulation # Find which vertex is on both copies of the identified edge of the face edges = [face.edge(i) for i in range(3)] # edge i is opposite vertex i, i in [0, 1, 2] for j in range(3): if edges[j] == edges[(j + 1) % 3]: B = (j + 2) % 3 break embed0 = face.embedding(0) embed1 = face.embedding(1) tet0 = embed0.tetrahedron() tet1 = embed1.tetrahedron() embed0_verts = embed0.vertices() embed1_verts = embed1.vertices() # In tet0: B gives "b". Let "c" be the edge sharing a # pi with "b". Let "d" be the vertex not meeting the given face. # Let "a" be the remaining vertex. b = embed0.vertices()[B] c = shares_pi_with(angle[tet0.index()], b) d = embed0.vertices()[3] # ... use the face index a = [i for i in [0, 1, 2, 3] if i not in [b, c, d]].pop() # ... whatever is left # similarly in tet1 - B gives "q". Let "r" be the edge sharing a # pi with "q". Let "s" be the vertex not meeting the given face. # Let "p" be the remaining vertex. q = embed1.vertices()[B] r = shares_pi_with(angle[tet1.index()], q) s = embed1.vertices()[3] # ... use the face index p = [i for i in [0, 1, 2, 3] if i not in [q, r, s]].pop() # ... whatever is left # get colour of mobius strip pair_a = [b, c] pair_a.sort() mob_edge_a = tet0.edge(vert_pair_to_edge_num[tuple(pair_a)]) pair_c = [a, b] pair_c.sort() mob_edge_c = tet0.edge(vert_pair_to_edge_num[tuple(pair_c)]) assert mob_edge_a == mob_edge_c veering_colours = is_veering(tri, angle, return_type="veering_colours") assert veering_colours != False # otherwise the triangulation is not veering mob_colour = veering_colours[mob_edge_a.index()] # Now actually do the surgery tet0.unjoin(d) # same as tet1.unjoin(s) tet_new = tri.newTetrahedron() if mob_colour == "red": tet_new.join(0, tet_new, regina.Perm4(3, 0, 1, 2)) tet_new.join(1, tet0, regina.Perm4(c, d, a, b)) tet_new.join(2, tet1, regina.Perm4(q, p, s, r)) else: tet_new.join(1, tet_new, regina.Perm4(1, 3, 0, 2)) tet_new.join(2, tet0, regina.Perm4(a, b, d, c)) tet_new.join(0, tet1, regina.Perm4(s, r, p, q)) angle.append(0) # this is the correct taut angle for our new tetrahedron assert is_taut(tri, angle) assert is_veering(tri, angle) return tri, angle, tet_new.triangle(3).index()
def twoThreeMove(tri, angle, face_num, perform = True, return_edge = False): """Apply a 2-3 move to a taut triangulation, if possible. If perform = False, returns if the move is possible. If perform = True, modifies tri, returns (tri, angle) for the performed move""" face = tri.triangle(face_num) embed0 = face.embedding(0) tet0 = embed0.simplex() tet_num0 = tet0.index() tet_0_face_num = embed0.face() vertices0 = embed0.vertices() # Maps vertices (0,1,2) of face to the corresponding vertex numbers of tet0 embed1 = face.embedding(1) tet1 = embed1.simplex() tet_num1 = tet1.index() tet_1_face_num = embed1.face() vertices1 = embed1.vertices() # Maps vertices (0,1,2) of face to the corresponding vertex numbers of tet1 if tet0 == tet1: ### Cannot perform a 2-3 move across a self-gluing return False ### taut 2-3 move is valid if the pis are on different edges of face ### this never happens if we start with a veering triangulation. ### for veering, the two-tetrahedron ball is always a continent. for i in range(3): j = (i+1) % 3 k = (i+2) % 3 if angle[tet_num0] == unsorted_vert_pair_to_edge_pair[(vertices0[j], vertices0[k])]: pi_num_0 = i if angle[tet_num1] == unsorted_vert_pair_to_edge_pair[(vertices1[j], vertices1[k])]: pi_num_1 = i if pi_num_0 == pi_num_1: return False if perform == False: return True ### check we do the same as regina... tri2 = regina.Triangulation3(tri) ## make a copy tri2.pachner(tri2.triangle(face_num)) ### We have to implement twoThreeMove ourselves. e.g. we do a 2-3 move to canonical fig 8 knot complement triangulation. ### All of the original tetrahedra are removed. I don't see any way to carry the angle structure through without knowing ### exactly how Ben's implementation works. ## record the tetrahedra and gluings adjacent to tet0 and tet1 tets = [tet0, tet1] vertices = [vertices0, vertices1] # print('2-3 vertices signs') # print([v.sign() for v in vertices]) gluings = [] for i in range(2): tet_gluings = [] for j in range(3): tet_gluings.append( [ tets[i].adjacentTetrahedron(vertices[i][j]), tets[i].adjacentGluing(vertices[i][j])] ) # if tets[i].adjacentTetrahedron(vertices[i][j]) in tets: # print('self gluing') gluings.append(tet_gluings) ### add new tetrahedra new_tets = [] for i in range(3): new_tets.append(tri.newTetrahedron()) ### glue around degree 3 edge for i in range(3): new_tets[i].join(2, new_tets[(i+1)%3], regina.Perm4(0,1,3,2)) ### replace mapping info with corresponding info for the 3 tet. Self gluings will be annoying... ### write verticesi[j] as vij ### tet0 new_tet0 ### _________ _________ ### ,'\ /`. ,'\`. ,'/`. ### ,' \ v03 / `. ,' \ `0' / `. ### ,' \ / `. ,' \ | / `. ### / \ \ / / \ /|\ \|/ /|\ ### /v02\ * /v01\ / | \ * / | \ ### / _\..... | ...../_ \ / | 3\..... | ...../2 | \ ### /_--"" / * \ ""--_\ /2 ,' / * \ `. 3\ ### \`.v12/ / \ \v11,'/ `. \`.| / /|\ \ |,'/ ### \ `./ / \ \,' / ----} \ `./ / | \ \,' / ### \ /`. / v00 \ ,'\ / ,' \|/`. / | \ ,'\|/ ### \ `. / \ ,' / \ `. / | \ ,' / ### \ `---------' / \ * 3 | 2 * / ### \ \ / / \ \ | / / ### \ \ v10 / / new_tet1 \ \ | / / new_tet2 ### \ \ / / \ \ | / / ### \ \ / / \ \|/ / ### \ * / \ * / ### tet1 \...|.../ \...|.../ ### \ | / \`.|.'/ ### \v13/ \ 1 / ### \|/ \|/ ### * * # permutations taking the vertices for a face of the 3-tet ball to the # vertices of the same face for the 2-tet ball # these should be even in order to preserve orientability. # exactly one of vertices[0] and vertices[1] is even, but it seems to depend on the face. # perms = [[regina.Perm4( vertices[0][3], vertices[0][0], vertices[0][1], vertices[0][2] ), ### opposite v00 # regina.Perm4( vertices[0][3], vertices[0][1], vertices[0][2], vertices[0][0] ), ### opposite v01 # regina.Perm4( vertices[0][3], vertices[0][2], vertices[0][0], vertices[0][1] ) ### opposite v02 # ], # [regina.Perm4( vertices[1][0], vertices[1][3], vertices[1][1], vertices[1][2] ), ### opposite v10 # regina.Perm4( vertices[1][1], vertices[1][3], vertices[1][2], vertices[1][0] ), ### opposite v11 # regina.Perm4( vertices[1][2], vertices[1][3], vertices[1][0], vertices[1][1] ) ### opposite v12 # ] # ] perms = [[vertices[0] * regina.Perm4( 3,0,1,2 ), ### opposite v00 vertices[0] * regina.Perm4( 3,1,2,0 ), ### opposite v01 vertices[0] * regina.Perm4( 3,2,0,1 ) ### opposite v02 ], [vertices[1] * regina.Perm4( 0,3,1,2 ), ### opposite v10 vertices[1] * regina.Perm4( 1,3,2,0 ), ### opposite v11 vertices[1] * regina.Perm4( 2,3,0,1 ) ### opposite v12 ] ] flip = perms[0][0].sign() == -1 if flip: #then all of the signs are wrong, switch 0 and 1 on input perms = [[p * regina.Perm4( 1,0,2,3 ) for p in a] for a in perms] # print('2-3 perms signs') # print([[p.sign() for p in a] for a in perms]) for i in range(2): for j in range(3): gluing = gluings[i][j] if gluing != None: if gluing[0] not in tets: ### not a self gluing gluing[1] = gluing[1] * perms[i][j] else: i_other = tets.index( gluing[0] ) otherfacenum = gluing[1][vertices[i][j]] j_other = [vertices[i_other][k] for k in range(4)].index(otherfacenum) assert gluings[i_other][j_other][0] == tets[i] assert gluings[i_other][j_other][1].inverse() == gluings[i][j][1] gluings[i_other][j_other] = None ### only do a self gluing from one side gluing[0] = new_tets[j_other] gluing[1] = perms[i_other][j_other].inverse() * gluing[1] * perms[i][j] ### unglue two tetrahedra tet0.isolate() tet1.isolate() ### remove the tetrahedra tri.removeSimplex(tet0) tri.removeSimplex(tet1) ### make the gluings on the boundary of the new ball for i in range(2): for j in range(3): if gluings[i][j] != None: if flip: new_tets[j].join(i, gluings[i][j][0], gluings[i][j][1]) else: new_tets[j].join(1 - i, gluings[i][j][0], gluings[i][j][1]) assert tri.isIsomorphicTo(tri2) assert tri.isOriented() ### update the angle structure tet_indices = [tet_num0, tet_num1] tet_indices.sort() angle.pop(tet_indices[1]) angle.pop(tet_indices[0]) ## remove from the list in the correct order! new_angle = [None, None, None] new_angle[pi_num_0] = 0 new_angle[pi_num_1] = 0 ### these two tetrahedra have their pi's on the new degree three edge third_index = 3 - (pi_num_0 + pi_num_1) if (pi_num_0 - third_index) % 3 == 1: new_angle[third_index] = 1 else: assert (pi_num_0 - third_index) % 3 == 2 new_angle[third_index] = 2 if flip: new_angle[third_index] = 3 - new_angle[third_index] angle.extend(new_angle) assert is_taut(tri, angle) if not return_edge: return [ tri, angle ] else: return [ tri, angle, new_tets[0].edge(0).index() ]
def threeTwoMove(tri, angle, edge_num, perform = True, return_triangle = False): """Apply a 3-2 move to a taut triangulation, if possible. If perform = False, returns if the move is possible. If perform = True, modifies tri, returns (tri, angle) for the performed move""" edge = tri.edge(edge_num) if edge.degree() != 3: return False tets = [] tet_nums = [] vertices = [] non_pi_tet_num = None for i in range(3): embed = edge.embedding(i) tets.append(embed.simplex()) tet_nums.append(tets[i].index()) vertices.append(embed.vertices()) if not there_is_a_pi_here(angle, embed): assert non_pi_tet_num == None non_pi_tet_num = embed.simplex().index() local_non_pi_tet_num = i if len(set([tet.index() for tet in tets])) != 3: return False ### tetrahedra must be distinct if not perform: return True ### taut 3-2 move is always possible if the 3-2 move is. ### record the "slope" of the pis on the non_pi_tet. This is a boolean non_pi_tet_positive = unsorted_vert_pair_to_edge_pair[ ( vertices[local_non_pi_tet_num][0], vertices[local_non_pi_tet_num][2] ) ] is_positive_slope = (angle[non_pi_tet_num] == non_pi_tet_positive) ### check we do the same as regina... tri2 = regina.Triangulation3(tri) ## make a copy tri2.pachner(tri2.edge(edge_num)) ## record the tetrahedra and gluings adjacent to the tets gluings = [] for i in range(3): tet_gluings = [] for j in range(2): tet_gluings.append( [ tets[i].adjacentTetrahedron(vertices[i][j]), tets[i].adjacentGluing(vertices[i][j])] ) gluings.append(tet_gluings) for i in range(3): assert tets[i].adjacentTetrahedron(vertices[i][2]) == tets[(i+1)%3] ### The edge embeddings should be ordered this way... ### add new tetrahedra new_tets = [] for i in range(2): new_tets.append(tri.newTetrahedron()) ### glue across face new_tets[0].join(3, new_tets[1], regina.Perm4(0,2,1,3)) ### replace mapping info with corresponding info for the 2 tet. Self gluings will be annoying... ### write vertices[i][j] as vij ### tets[0] new_tet1 ### _________ _________ ### ,'\`.v00,'/`. ,'\ /`. ### ,' \ `.' / `. ,' \ 3 / `. ### ,' v10\ | /v20 `. ,' \ / `. ### /|\ \|/ /|\ / \ \ / / \ ### / | \ * / | \ / \ * / \ ### v12/ | \..... | ...../ | \v23 / 1 _\..... | ...../_ 2 \ ### / ,' / * \ `. \ /_--"" / * \ ""--_\ ### \`.| /v03 /|\ v02\ |,'/ `. \`. 2 / / \ \ 1 ,'/ ### \ `./ / | \ \,' / ----} \ `./ / \ \,' / ### \|/`. / | \ ,'\|/ ,' \ /`. / 0 \ ,'\ / ### \ `. / | \ ,' / \ `. / \ ,' / ### \ * v13|v22 * / \ `---------' / ### \ \ | / / \ \ / / ### \ \ | / / \ \ 0 / / ### \ \ | / / \ \ / / ### tets[1] \ \|/ / tets[2] \ \ / / ### \ * / \ * / new_tet0 ### \..v01../ \...|.../ ### \`.|.'/ \ | / ### v11\ | /v21 \ 3 / ### \|/ \|/ ### * * # permutations taking the vertices for a face of the 2-tet ball to the # vertices of the same face for the 3-tet ball # these should be even in order to preserve orientability. # perms = [[regina.Perm4( vertices[0][0], vertices[0][2], vertices[0][3], vertices[0][1] ), ### opposite v00 # regina.Perm4( vertices[0][1], vertices[0][3], vertices[0][2], vertices[0][0] ) ### opposite v01 # ], # [regina.Perm4( vertices[1][3], vertices[1][0], vertices[1][2], vertices[1][1] ), ### opposite v10 # regina.Perm4( vertices[1][3], vertices[1][2], vertices[1][1], vertices[1][0] ) ### opposite v11 # ], # [regina.Perm4( vertices[2][2], vertices[2][3], vertices[2][0], vertices[2][1] ), ### opposite v20 # regina.Perm4( vertices[2][2], vertices[2][1], vertices[2][3], vertices[2][0] ) ### opposite v21 # ] # ] perms = [[vertices[0] * regina.Perm4( 0, 2, 3, 1 ), ### opposite v00 vertices[0] * regina.Perm4( 1, 3, 2, 0 ) ### opposite v01 ], [vertices[1] * regina.Perm4( 3, 0, 2, 1 ), ### opposite v10 vertices[1] * regina.Perm4( 3, 2, 1, 0 ) ### opposite v11 ], [vertices[2] * regina.Perm4( 2, 3, 0, 1 ), ### opposite v20 vertices[2] * regina.Perm4( 2, 1, 3, 0 ) ### opposite v21 ] ] for i in range(3): for j in range(2): gluing = gluings[i][j] if gluing != None: if gluing[0] not in tets: ### not a self gluing gluing[1] = gluing[1] * perms[i][j] else: i_other = tets.index( gluing[0] ) otherfacenum = gluing[1][vertices[i][j]] j_other = [vertices[i_other][k] for k in range(4)].index(otherfacenum) assert gluings[i_other][j_other][0] == tets[i] assert gluings[i_other][j_other][1].inverse() == gluings[i][j][1] gluings[i_other][j_other] = None ### only do a self gluing from one side gluing[0] = new_tets[j_other] ### j refers to the vertex on the same 3 side gluing[1] = perms[i_other][j_other].inverse() * gluing[1] * perms[i][j] ### unglue three tetrahedra for tet in tets: tet.isolate() ### remove the tetrahedra for tet in tets: tri.removeSimplex(tet) ### make the gluings on the boundary of the new ball for i in range(3): for j in range(2): if gluings[i][j] != None: if j == 0 or i == 0: assert new_tets[j].adjacentTetrahedron(i) == None ## not glued assert gluings[i][j][0].adjacentTetrahedron(gluings[i][j][1][i]) == None new_tets[j].join(i, gluings[i][j][0], gluings[i][j][1]) else: assert new_tets[j].adjacentTetrahedron(3 - i) == None ## not glued assert gluings[i][j][0].adjacentTetrahedron(gluings[i][j][1][3 - i]) == None new_tets[j].join(3 - i, gluings[i][j][0], gluings[i][j][1]) ## swap 1 and 2 assert tri.isIsomorphicTo(tri2) assert tri.isOriented() # ### update the angle structure tet_nums.sort() angle.pop(tet_nums[2]) angle.pop(tet_nums[1]) angle.pop(tet_nums[0]) ## remove from the list in the correct order! if local_non_pi_tet_num == 0: if is_positive_slope: new_angle = [0, 0] else: new_angle = [1, 1] elif local_non_pi_tet_num == 1: if is_positive_slope: new_angle = [2, 1] else: new_angle = [0, 2] else: assert local_non_pi_tet_num == 2 if is_positive_slope: new_angle = [1, 2] else: new_angle = [2, 0] angle.extend(new_angle) assert is_taut(tri, angle) if not return_triangle: return (tri, angle) else: return (tri, angle, new_tets[0].triangle(3).index())
def threeTwoMove(tri, edge_num, angle = None, branch = None, perform = True, return_triangle = False, return_vertex_perm = False): """Apply a 3-2 move to a triangulation with a taut structure and/or branched surface, if possible. If perform = False, returns if the move is possible. modifies tri, returns (tri, angle, branch) for the performed move. If return_vertex_perm, tells you how the vertices of the old triangulation correspond to the vertices of the new. If return_edge_consequences, tells you what happened to the edges: if an edge e survived then edge_consequences[e.index()] gives you the new index, if not then it returns None.""" ### perform = True isn't yet implemented for branch ### note if branch != None and this function does not return False, then there is only one possible branch # if branch != None: # assert has_non_sing_semiflow(tri, branch) ### we are checking this on the output of pachner moves so we don't need to check it here edge = tri.edge(edge_num) if edge.degree() != 3: return False tets = [] tet_nums = [] vertices = [] if angle != None: non_pi_tet_num = None for i in range(3): embed = edge.embedding(i) tets.append(embed.simplex()) tet_nums.append(tets[i].index()) vertices.append(embed.vertices()) if angle != None: if not there_is_a_pi_here(angle, embed): assert non_pi_tet_num == None non_pi_tet_num = embed.simplex().index() local_non_pi_tet_num = i tet_nums.sort() if len(set([tet.index() for tet in tets])) != 3: return False ### tetrahedra must be distinct if branch == None and not perform: return True ### taut 3-2 move is always possible if the 3-2 move is. if angle != None: ### record the "slope" of the pis on the non_pi_tet. This is a boolean non_pi_tet_positive = unsorted_vert_pair_to_edge_pair[ ( vertices[local_non_pi_tet_num][0], vertices[local_non_pi_tet_num][2] ) ] is_positive_slope = (angle[non_pi_tet_num] == non_pi_tet_positive) ### check we do the same as regina... tri2 = regina.Triangulation3(tri) ## make a copy tri2.pachner(tri2.edge(edge_num)) if return_vertex_perm: vertex_representatives = [] for c in tri.vertices(): embed = c.embedding(0) vertex_representatives.append((embed.simplex(), embed.face())) ### pair (tet, vert_num_of_that_tet) ### for testing: vertex_degrees = [v.degree() for v in tri.vertices()] ## record the tetrahedra and gluings adjacent to the tets gluings = [] for i in range(3): tet_gluings = [] for j in range(2): tet_gluings.append( [ tets[i].adjacentTetrahedron(vertices[i][j]), tets[i].adjacentGluing(vertices[i][j])] ) gluings.append(tet_gluings) for i in range(3): assert tets[i].adjacentTetrahedron(vertices[i][2]) == tets[(i+1)%3] ### The edge embeddings should be ordered this way... if branch != None: large_edges = [] ### record large edge info for the outer faces for i in range(3): this_tet_large_edges = [] for j in range(2): this_tet_large_edges.append(large_edge_of_face( branch[tets[i].index()], vertices[i][j] )) large_edges.append(this_tet_large_edges) ### add new tetrahedra new_tets = [] for i in range(2): new_tets.append(tri.newTetrahedron()) ### glue across face new_tets[0].join(3, new_tets[1], regina.Perm4(0,2,1,3)) ### replace mapping info with corresponding info for the 2 tet. Self gluings will be annoying... ### write vertices[i][j] as vij ### tets[0] new_tet1 ### _________ _________ ### ,'\`.v00,'/`. ,'\ /`. ### ,' \ `.' / `. ,' \ 3 / `. ### ,' v10\ | /v20 `. ,' \ / `. ### /|\ \|/ /|\ / \ \ / / \ ### / | \ * / | \ / \ * / \ ### v12/ | \..... | ...../ | \v23 / 1 _\..... | ...../_ 2 \ ### / ,' / * \ `. \ /_--"" / * \ ""--_\ ### \`.| /v03 /|\ v02\ |,'/ `. \`. 2 / / \ \ 1 ,'/ ### \ `./ / | \ \,' / ----} \ `./ / \ \,' / ### \|/`. / | \ ,'\|/ ,' \ /`. / 0 \ ,'\ / ### \ `. / | \ ,' / \ `. / \ ,' / ### \ * v13|v22 * / \ `---------' / ### \ \ | / / \ \ / / ### \ \ | / / \ \ 0 / / ### \ \ | / / \ \ / / ### tets[1] \ \|/ / tets[2] \ \ / / ### \ * / \ * / new_tet0 ### \..v01../ \...|.../ ### \`.|.'/ \ | / ### v11\ | /v21 \ 3 / ### \|/ \|/ ### * * # permutations taking the vertices for a face of the 2-tet ball to the # vertices of the same face for the 3-tet ball # these should be even in order to preserve orientability. # perms = [[regina.Perm4( vertices[0][0], vertices[0][2], vertices[0][3], vertices[0][1] ), ### opposite v00 # regina.Perm4( vertices[0][1], vertices[0][3], vertices[0][2], vertices[0][0] ) ### opposite v01 # ], # [regina.Perm4( vertices[1][3], vertices[1][0], vertices[1][2], vertices[1][1] ), ### opposite v10 # regina.Perm4( vertices[1][3], vertices[1][2], vertices[1][1], vertices[1][0] ) ### opposite v11 # ], # [regina.Perm4( vertices[2][2], vertices[2][3], vertices[2][0], vertices[2][1] ), ### opposite v20 # regina.Perm4( vertices[2][2], vertices[2][1], vertices[2][3], vertices[2][0] ) ### opposite v21 # ] # ] perms = [[vertices[0] * regina.Perm4( 0, 2, 3, 1 ), ### opposite v00 vertices[0] * regina.Perm4( 1, 3, 2, 0 ) ### opposite v01 ], [vertices[1] * regina.Perm4( 3, 0, 2, 1 ), ### opposite v10 vertices[1] * regina.Perm4( 3, 2, 1, 0 ) ### opposite v11 ], [vertices[2] * regina.Perm4( 2, 3, 0, 1 ), ### opposite v20 vertices[2] * regina.Perm4( 2, 1, 3, 0 ) ### opposite v21 ] ] for i in range(3): for j in range(2): gluing = gluings[i][j] if gluing != None: if gluing[0] not in tets: ### not a self gluing gluing[1] = gluing[1] * perms[i][j] else: i_other = tets.index( gluing[0] ) otherfacenum = gluing[1][vertices[i][j]] j_other = [vertices[i_other][k] for k in range(4)].index(otherfacenum) assert gluings[i_other][j_other][0] == tets[i] assert gluings[i_other][j_other][1].inverse() == gluings[i][j][1] gluings[i_other][j_other] = None ### only do a self gluing from one side gluing[0] = new_tets[j_other] ### j refers to the vertex on the same 3 side gluing[1] = perms[i_other][j_other].inverse() * gluing[1] * perms[i][j] if return_vertex_perm: new_vertex_representatives = [] for (tet, vert_num) in vertex_representatives: if tet in tets: which_tet = tets.index(tet) vert_num = vertices[which_tet].inverse()[vert_num] if vert_num == 0: new_vertex_representatives.append((new_tets[1], 3)) elif vert_num == 1: new_vertex_representatives.append((new_tets[0], 3)) elif vert_num == 2: new_vertex_representatives.append((new_tets[0], (which_tet + 1) % 3 )) elif vert_num == 3: new_vertex_representatives.append((new_tets[0], (which_tet - 1) % 3 )) else: new_vertex_representatives.append((tet, vert_num)) ### not changed by the move # ### for testing: polar_cusp_indices = [ tets[0].vertex( vertices[0][0] ).index(), tets[0].vertex( vertices[0][1] ).index() ] ### unglue three tetrahedra for tet in tets: tet.isolate() ### remove the tetrahedra for tet in tets: tri.removeSimplex(tet) ### make the gluings on the boundary of the new ball for i in range(3): for j in range(2): if gluings[i][j] != None: if j == 0 or i == 0: assert new_tets[j].adjacentTetrahedron(i) == None ## not glued assert gluings[i][j][0].adjacentTetrahedron(gluings[i][j][1][i]) == None new_tets[j].join(i, gluings[i][j][0], gluings[i][j][1]) else: assert new_tets[j].adjacentTetrahedron(3 - i) == None ## not glued assert gluings[i][j][0].adjacentTetrahedron(gluings[i][j][1][3 - i]) == None new_tets[j].join(3 - i, gluings[i][j][0], gluings[i][j][1]) ## swap 1 and 2 assert tri.isIsomorphicTo(tri2) assert tri.isOriented() if angle != None: ### update the angle structure angle.pop(tet_nums[2]) angle.pop(tet_nums[1]) angle.pop(tet_nums[0]) ## remove from the list in the correct order! if local_non_pi_tet_num == 0: if is_positive_slope: new_angle = [0, 0] else: new_angle = [1, 1] elif local_non_pi_tet_num == 1: if is_positive_slope: new_angle = [2, 1] else: new_angle = [0, 2] else: assert local_non_pi_tet_num == 2 if is_positive_slope: new_angle = [1, 2] else: new_angle = [2, 0] angle.extend(new_angle) assert is_taut(tri, angle) if branch != None: ### update the branched surface branch.pop(tet_nums[2]) branch.pop(tet_nums[1]) branch.pop(tet_nums[0]) ## remove from the list in the correct order! ### for each of the two new tetrahedra, figure out what their outer face train tracks are large_edges_new = [] ### record large edge info for the outer faces for i in range(3): this_tet_large_edges_new = [] for j in range(2): new_large_edge = perms[i][j].inverse()[ large_edges[i][j] ] this_tet_large_edges_new.append( new_large_edge ) large_edges_new.append(this_tet_large_edges_new) large_edges_new_transposed = [list(i) for i in zip(*large_edges_new)] branch0 = determine_possible_branch_given_three_faces([0,1,2], large_edges_new_transposed[0]) branch1 = determine_possible_branch_given_three_faces([0,2,1], large_edges_new_transposed[1]) if branch0 == None or branch1 == None: return False large_edge_for_new_tet0 = large_edge_of_face( branch0, 3 ) large_edge_for_new_tet1 = large_edge_of_face( branch1, 3 ) if large_edge_for_new_tet0 == large_edge_for_new_tet1: if large_edge_for_new_tet0 != 0: return False else: if large_edge_for_new_tet0 + large_edge_for_new_tet1 != 3: ### one must be 1, one must be 2 return False branch.extend([branch0, branch1]) assert is_branched(tri, branch) if not has_non_sing_semiflow(tri, branch): return False if return_vertex_perm: vertex_permutation = [] for (tet, vert_num) in new_vertex_representatives: vertex_permutation.append(tet.vertex(vert_num).index()) ### note that Regina's permutations can have up to 16 entries, lets just use lists for this ### for testing: new_vertex_degrees = [v.degree() for v in tri.vertices()] new_vertex_degrees_pulled_back = [new_vertex_degrees[vertex_permutation[i]] for i in range(len(vertex_degrees))] #if new_vertex_degrees != new_vertex_degrees_pulled_back: # print(vertex_permutation) # if vertex_permutation != list(range(len(new_vertex_representatives))): # print('3-2 permuted vertices', vertex_permutation, new_vertex_degrees, new_vertex_degrees_pulled_back, vertex_degrees) for i in polar_cusp_indices: vertex_degrees[i] -= 2 assert vertex_degrees == new_vertex_degrees_pulled_back # print(new_vertex_degrees_pulled_back) output = [tri] if angle != None: output.append(angle) if branch != None: output.append(branch) if return_triangle: output.append(new_tets[0].triangle(3).index()) if return_vertex_perm: output.append(vertex_permutation) return output
def twoThreeMove(tri, face_num, angle = None, branch = None, perform = True, return_edge = False, return_vertex_perm = False, return_edge_consequences = False): """Apply a 2-3 move to a triangulation with a taut structure and/or branched surface, if possible. If perform = False, returns if the move is possible. If perform = True, modifies tri, returns (tri, angle, possible_branches) for the performed move If return_edge, tells you the index of the newly created edge in the triangulation. If return_vertex_perm, tells you how the vertices of the old triangulation correspond to the vertices of the new. If return_edge_consequences, tells you what happened to the edges: edge_consequences[e.index()] gives you the new index.""" ### possible_branches is a list # if branch != None: # assert has_non_sing_semiflow(tri, branch) ### we are checking this on the output of pachner moves so we don't need to check it here ## Joe Christy says [p764, Branched surfaces and attractors I: Dynamic Branched Surfaces] that if the branched surface carries the stable lamination of a pseudo-Anosov flow then it has a non singular semi flow ## We hope that we can move to a veering triangulation through such branched surfaces face = tri.triangle(face_num) embed0 = face.embedding(0) tet0 = embed0.simplex() tet_num0 = tet0.index() tet_0_face_num = embed0.face() vertices0 = embed0.vertices() # Maps vertices (0,1,2) of face to the corresponding vertex numbers of tet0 embed1 = face.embedding(1) tet1 = embed1.simplex() tet_num1 = tet1.index() tet_1_face_num = embed1.face() vertices1 = embed1.vertices() # Maps vertices (0,1,2) of face to the corresponding vertex numbers of tet1 if tet0 == tet1: ### Cannot perform a 2-3 move across a self-gluing return False if angle != None: ### taut 2-3 move is valid if the pis are on different edges of face ### this never happens if we start with a veering triangulation. ### for veering, the two-tetrahedron ball is always a continent. for i in range(3): j = (i+1) % 3 k = (i+2) % 3 if angle[tet_num0] == unsorted_vert_pair_to_edge_pair[(vertices0[j], vertices0[k])]: pi_num_0 = i if angle[tet_num1] == unsorted_vert_pair_to_edge_pair[(vertices1[j], vertices1[k])]: pi_num_1 = i if pi_num_0 == pi_num_1: return False if perform == False: return True ### are all moves valid for the branched surface? ### for now, lets assume yes ### check we do the same as regina... tri2 = regina.Triangulation3(tri) ## make a copy tri2.pachner(tri2.triangle(face_num)) if return_vertex_perm: vertex_representatives = [] for c in tri.vertices(): embed = c.embedding(0) vertex_representatives.append((embed.simplex(), embed.face())) ### pair (tet, vert_num_of_that_tet) ### for testing: vertex_degrees = [v.degree() for v in tri.vertices()] if return_edge_consequences: edge_representatives = [] for e in tri.edges(): embed = e.embedding(0) ### HERE ##edge_representatives.append((embed.simplex(), embed.face())) ### pair (tet, vert_num_of_that_tet) ### We have to implement twoThreeMove ourselves. e.g. we do a 2-3 move to canonical fig 8 knot complement triangulation. ### All of the original tetrahedra are removed. I don't see any way to carry the angle structure through without knowing ### exactly how Ben's implementation works. ## record the tetrahedra and gluings adjacent to tet0 and tet1 tets = [tet0, tet1] vertices = [vertices0, vertices1] gluings = [] for i in range(2): tet_gluings = [] for j in range(3): tet_gluings.append( [ tets[i].adjacentTetrahedron(vertices[i][j]), tets[i].adjacentGluing(vertices[i][j])] ) # if tets[i].adjacentTetrahedron(vertices[i][j]) in tets: # print('self gluing') gluings.append(tet_gluings) if branch != None: large_edges = [] ### record large edge info for the outer faces for i in range(2): this_tet_large_edges = [] for j in range(3): this_tet_large_edges.append(large_edge_of_face( branch[tets[i].index()], vertices[i][j] )) large_edges.append(this_tet_large_edges) ### add new tetrahedra new_tets = [] for i in range(3): new_tets.append(tri.newTetrahedron()) ### glue around degree 3 edge for i in range(3): new_tets[i].join(2, new_tets[(i+1)%3], regina.Perm4(0,1,3,2)) ### replace mapping info with corresponding info for the 3 tet. Self gluings will be annoying... ### write verticesi[j] as vij ### tet0 new_tets[0] ### _________ _________ ### ,'\ /`. ,'\`. ,'/`. ### ,' \ v03 / `. ,' \ `0' / `. ### ,' \ / `. ,' \ | / `. ### / \ \ / / \ /|\ \|/ /|\ ### /v02\ * /v01\ / | \ * / | \ ### / _\..... | ...../_ \ / | 3\..... | ...../2 | \ ### /_--"" / * \ ""--_\ /2 ,' / * \ `. 3\ ### \`.v12/ / \ \v11,'/ `. \`.| / /|\ \ |,'/ ### \ `./ / \ \,' / ----} \ `./ / | \ \,' / ### \ /`. / v00 \ ,'\ / ,' \|/`. / | \ ,'\|/ ### \ `. / \ ,' / \ `. / | \ ,' / ### \ `---------' / \ * 3 | 2 * / ### \ \ / / \ \ | / / ### \ \ v10 / / new_tets[1] \ \ | / / new_tets[2] ### \ \ / / \ \ | / / ### \ \ / / \ \|/ / ### \ * / \ * / ### tet1 \...|.../ \...|.../ ### \ | / \`.|.'/ ### \v13/ \ 1 / ### \|/ \|/ ### * * # permutations taking the vertices for a face of the 3-tet ball to the # vertices of the same face for the 2-tet ball # these should be even in order to preserve orientability. # exactly one of vertices[0] and vertices[1] is even, but it seems to depend on the face. perms = [[vertices[0] * regina.Perm4( 3,0,1,2 ), ### opposite v00 vertices[0] * regina.Perm4( 3,1,2,0 ), ### opposite v01 vertices[0] * regina.Perm4( 3,2,0,1 ) ### opposite v02 ], [vertices[1] * regina.Perm4( 0,3,1,2 ), ### opposite v10 vertices[1] * regina.Perm4( 1,3,2,0 ), ### opposite v11 vertices[1] * regina.Perm4( 2,3,0,1 ) ### opposite v12 ] ] flip = perms[0][0].sign() == -1 if flip: #then all of the signs are wrong, switch 0 and 1 on input perms = [[p * regina.Perm4( 1,0,2,3 ) for p in a] for a in perms] for i in range(2): for j in range(3): gluing = gluings[i][j] if gluing != None: if gluing[0] not in tets: ### not a self gluing gluing[1] = gluing[1] * perms[i][j] else: i_other = tets.index( gluing[0] ) otherfacenum = gluing[1][vertices[i][j]] j_other = [vertices[i_other][k] for k in range(4)].index(otherfacenum) assert gluings[i_other][j_other][0] == tets[i] assert gluings[i_other][j_other][1].inverse() == gluings[i][j][1] gluings[i_other][j_other] = None ### only do a self gluing from one side gluing[0] = new_tets[j_other] gluing[1] = perms[i_other][j_other].inverse() * gluing[1] * perms[i][j] if return_vertex_perm: new_vertex_representatives = [] for (tet, vert_num) in vertex_representatives: if tet == tet0: triangle_vert_num = vertices[0].inverse()[vert_num] if triangle_vert_num == 3: if flip: new_vertex_representatives.append((new_tets[0], 1)) else: new_vertex_representatives.append((new_tets[0], 0)) else: new_vertex_representatives.append((new_tets[(triangle_vert_num + 1) % 3], 3)) elif tet == tet1: triangle_vert_num = vertices[1].inverse()[vert_num] if triangle_vert_num == 3: if flip: new_vertex_representatives.append((new_tets[0], 0)) else: new_vertex_representatives.append((new_tets[0], 1)) else: new_vertex_representatives.append((new_tets[(triangle_vert_num + 1) % 3], 3)) else: new_vertex_representatives.append((tet, vert_num)) ### not changed by the move ### for testing: polar_cusp_indices = [ tet0.vertex( vertices[0][3] ).index(), tet1.vertex( vertices[1][3] ).index() ] ### unglue two tetrahedra tet0.isolate() tet1.isolate() ### remove the tetrahedra tri.removeSimplex(tet0) tri.removeSimplex(tet1) ### make the gluings on the boundary of the new ball for i in range(2): for j in range(3): if gluings[i][j] != None: if flip: new_tets[j].join(i, gluings[i][j][0], gluings[i][j][1]) else: new_tets[j].join(1 - i, gluings[i][j][0], gluings[i][j][1]) assert tri.isIsomorphicTo(tri2) assert tri.isOriented() if angle != None: ### update the angle structure tet_indices = [tet_num0, tet_num1] tet_indices.sort() angle.pop(tet_indices[1]) angle.pop(tet_indices[0]) ## remove from the list in the correct order! new_angle = [None, None, None] new_angle[pi_num_0] = 0 new_angle[pi_num_1] = 0 ### these two tetrahedra have their pi's on the new degree three edge third_index = 3 - (pi_num_0 + pi_num_1) if (pi_num_0 - third_index) % 3 == 1: new_angle[third_index] = 1 else: assert (pi_num_0 - third_index) % 3 == 2 new_angle[third_index] = 2 if flip: new_angle[third_index] = 3 - new_angle[third_index] angle.extend(new_angle) assert is_taut(tri, angle) if branch != None: ### update the branched surface ### for each of the three new tetrahedra, figure out what their outer face train tracks are large_edges_new = [] ### record large edge info for the outer faces for i in range(2): this_tet_large_edges_new = [] for j in range(3): new_large_edge = perms[i][j].inverse()[ large_edges[i][j] ] if flip: assert new_large_edge != i ### the face number cannot be the large vertex for that face else: assert new_large_edge != 1 - i this_tet_large_edges_new.append( new_large_edge ) large_edges_new.append(this_tet_large_edges_new) candidate_branches = [] for j in range(3): if flip: candidate_branches.append( determine_possible_branch_given_two_faces((0,1), (large_edges_new[0][j], large_edges_new[1][j]) ) ) else: candidate_branches.append( determine_possible_branch_given_two_faces((1,0), (large_edges_new[0][j], large_edges_new[1][j]) ) ) ### update the branch structure, many possible ways tet_indices = [tet_num0, tet_num1] tet_indices.sort() branch.pop(tet_indices[1]) branch.pop(tet_indices[0]) ## remove from the list in the correct order! out_branches = [] for cand0 in candidate_branches[0]: for cand1 in candidate_branches[1]: for cand2 in candidate_branches[2]: candidate = branch[:] + [cand0, cand1, cand2] # print('candidate', candidate) # if is_branched(tri, candidate): if has_non_sing_semiflow(tri, candidate): out_branches.append(candidate) # assert len(out) > 0 ### this works if we check is_branched three lines above, but not if we check has_non_sing_semiflow if len(out_branches) == 0: ### with has_non_sing_semiflow instead, we might not get any return False if return_vertex_perm: vertex_permutation = [] for (tet, vert_num) in new_vertex_representatives: vertex_permutation.append(tet.vertex(vert_num).index()) ### note that Regina's permutations can have up to 16 entries, lets just use lists for this ### for testing: new_vertex_degrees = [v.degree() for v in tri.vertices()] new_vertex_degrees_pulled_back = [new_vertex_degrees[vertex_permutation[i]] for i in range(len(vertex_degrees))] # print(vertex_permutation) # if vertex_permutation != list(range(len(new_vertex_representatives))): # print('2-3 permuted vertices', vertex_permutation, new_vertex_degrees, new_vertex_degrees_pulled_back, vertex_degrees) for i in polar_cusp_indices: vertex_degrees[i] += 2 assert vertex_degrees == new_vertex_degrees_pulled_back # print(new_vertex_degrees_pulled_back) output = [tri] if angle != None: output.append(angle) if branch != None: output.append(out_branches) if return_edge: output.append(new_tets[0].edge(0).index()) if return_vertex_perm: output.append(vertex_permutation) return output
def surface_isom_to_regluing_pattern(tri, angle, weights, isom, tet_vert_coorientations=None): """ Translation between a combinatorial isomorphism of a carried surface and the associated mutation pattern. """ #isom must be a combinatorial isomorphism of a surface build using build_surface # 1. Get the top embeds to surface triangles map (if weights > 1 then the lowermost copy - the one which has a tet below, and not a 'prism'). # 2. Find images of triangles and permutations on vertices under the isomorphism. # 3. For every simpImage check if it is in the list obtained via surface_triangles_to_bottom triangles. If so, then it has a tet above (and not a prism), so the regluing pattern is # top embed --> surface triangle (lowermost over top embed) --isom--> surface triangle (uppermost over some tet) --> bottom face of the tet above # Otherwise, we look at the copy of the 3D-triangle in the surface which is above this surface triangle, apply isom again etc. if tet_vert_coorientations == None: tet_vert_coorientations = is_transverse_taut( tri, angle, return_type="tet_vert_coorientations") top_tri_to_surface_tri = top_triangles_to_surface_triangles( tri, angle, weights, tet_vert_coorientations) surface_tri_to_bottom_tri = surface_triangles_to_bottom_triangles( tri, angle, weights, tet_vert_coorientations) k = len(surface_tri_to_bottom_tri) auxiliary = [None] * k for i in range(k): if surface_tri_to_bottom_tri[i] != None: auxiliary[i] = surface_tri_to_bottom_tri[i][0] # auxiliary[k] is either None or an index of a surface triangle which has a tet above -- I want to keep Nones so that later I can find the index of the bottom triangle in surface_tri_to_bottom_tri by value regluing = [] for i in range(len(top_tri_to_surface_tri)): if top_tri_to_surface_tri[ i] != None: # this triangle is unglued in the cut manifold #print('top triangle to surface triangle', top_tri_to_surface_tri[i]) surface_tri_index = top_tri_to_surface_tri[i][2] tri_image = isom.simpImage(surface_tri_index) isom_perm = isom.facetPerm(surface_tri_index) #print('first tri image and perm', tri_image, isom_perm) if tri_image in auxiliary: index_in_surface_to_bottom_tri = auxiliary.index(tri_image) #print('surface triangle', tri_image, 'has a tet above') while tri_image not in auxiliary: #look at further images #print('tri_image does not have a tet above') triangle_above = tri_image + 1 tri_image = isom.simpImage( triangle_above) #look at the image of the upper copy isom_perm = isom.facetPerm(triangle_above) * isom_perm #print('next tri image and perm', tri_image, isom_perm) if tri_image in auxiliary: index_in_surface_to_bottom_tri = auxiliary.index(tri_image) #print('surface triangle', tri_image, 'has a tet above') top_to_surface_perm = vertex_correspondence_to_perm4( top_tri_to_surface_tri[i][1], top_tri_to_surface_tri[i][3]) #print('top tri to surface tri perm', top_to_surface_perm) isom_perm4 = regina.Perm4(isom_perm[0], isom_perm[1], isom_perm[2], 3) #print('4perm associated to isom', isom_perm4) bottom_to_surface_perm = vertex_correspondence_to_perm4( surface_tri_to_bottom_tri[index_in_surface_to_bottom_tri][3], surface_tri_to_bottom_tri[index_in_surface_to_bottom_tri][1]) #print('bottom tri to surface tri perm', bottom_to_surface_perm) surface_to_bottom_perm = bottom_to_surface_perm.inverse() #print('surface to bottom perm', surface_to_bottom_perm) perm = surface_to_bottom_perm * isom_perm4 * top_to_surface_perm #print('gluing perm', perm) tet_below_top_embed = top_tri_to_surface_tri[i][0] which_face = face_num_in_tet(top_tri_to_surface_tri[i][1]) tet_above_image = surface_tri_to_bottom_tri[ index_in_surface_to_bottom_tri][2] regluing.append( [tet_below_top_embed, which_face, tet_above_image, perm]) return regluing
n = 7 gems = surv.family_three_crystallisations(7, sphere1=[[1], [0, 4, 2], [3, 5, 6]]) idn = list(range(n)) mu = surv.mu_online(n) sigmas = surv.cycle_to_oneline(gems[0][0], n) taus = [surv.cycle_to_oneline(g[1]) for g in gems] triangulations = [] for tau in taus: tau = [] perms = [idn, mu, sigma, tau] T = regina.Triangulation3() simplices = [T.newTetrahedron() for i in range(2 * n)] id4 = regina.Perm4() for c in range(4): for i in range(n): simplices[i].join(c, simplices[perms[c][i] + n], id4) triangulations.append(T) # Example: computing each unique isomorphism signature for triangulations unique_sigs = [] for T in triangulations: s = T.isoSig() if s not in unique_sigs: unique_sigs.append(s) print "Distinct Isomorphism Signatures:\n" for s in unique_sigs:
def drill_midsurface_bdy(tri, angle): vt = veering_triangulation(tri, angle) veering_colours = vt.veering_colours ## "red" or "blue" tet_types = vt.tet_types ### "toggle", "red" or "blue" tet_vert_coors = vt.coorientations drilledTri = regina.Triangulation3() subtet_indices = [] subtet_addresses = [] num_toggles = tet_types.count("toggle") drilled_num_tet = 8 * num_toggles + 4 * (tri.countTetrahedra() - num_toggles) meridians = [] ### in each toggle, we record the meridians for the two boundary components we are drilling ### note that this repeats the same meridian many times - the construction here is purely local: it is much ### easier to figure out which are duplicates of which later for i in range(tri.countTetrahedra()): tet = tri.tetrahedron(i) first_subtet_index = drilledTri.countTetrahedra() if tet_types[i] == 'toggle': num_to_add = 8 else: num_to_add = 4 for j in range(num_to_add): drilledTri.newTetrahedron() subtet_indices.append( range(first_subtet_index, first_subtet_index + num_to_add)) ### glue sub-tetrahedra together. Note that some tetrahedra will be negatively oriented [(t0, t1), (b0, b1)] = get_top_and_bottom_nums(tet_vert_coors, i) this_tet_subtet_addresses = {} if tet_types[i] == 'toggle': ## first two subtetrahedra are top two, second two are bottom two, then the four side tetrahedra tet_t0, tet_t1 = drilledTri.tetrahedron( first_subtet_index), drilledTri.tetrahedron( first_subtet_index + 1) # tet_ti is opposite vertex bi tet_b0, tet_b1 = drilledTri.tetrahedron(first_subtet_index + 2), drilledTri.tetrahedron( first_subtet_index + 3) # tet_bi is opposite vertex ti tet_s00 = drilledTri.tetrahedron(first_subtet_index + 4) tet_s01 = drilledTri.tetrahedron(first_subtet_index + 5) tet_s10 = drilledTri.tetrahedron(first_subtet_index + 6) tet_s11 = drilledTri.tetrahedron(first_subtet_index + 7) # tet_sij meets vertices ti and bj ## keys are (vert not on face, vert not on edge), returns the subtet which meets the face, edge this_tet_subtet_addresses[(b0, b1)] = tet_t0 this_tet_subtet_addresses[(b1, b0)] = tet_t1 this_tet_subtet_addresses[(t0, t1)] = tet_b0 this_tet_subtet_addresses[(t1, t0)] = tet_b1 this_tet_subtet_addresses[(t1, b1)] = tet_s00 this_tet_subtet_addresses[(b1, t1)] = tet_s00 this_tet_subtet_addresses[(t1, b0)] = tet_s01 this_tet_subtet_addresses[(b0, t1)] = tet_s01 this_tet_subtet_addresses[(t0, b1)] = tet_s10 this_tet_subtet_addresses[(b1, t0)] = tet_s10 this_tet_subtet_addresses[(t0, b0)] = tet_s11 this_tet_subtet_addresses[(b0, t0)] = tet_s11 tet_t0.join(b0, tet_t1, regina.Perm4(b0, b1, b1, b0, t0, t0, t1, t1)) ## b0 <-> b1, t0 and t1 fixed tet_b0.join(t0, tet_b1, regina.Perm4(t0, t1, t1, t0, b0, b0, b1, b1)) ## t0 <-> t1, b0 and b1 fixed tet_s00.join(b0, tet_t1, regina.Perm4(t0, t0, b1, b1, t1, b0, b0, t1)) tet_s00.join(t0, tet_b1, regina.Perm4(b0, b0, t1, t1, b1, t0, t0, b1)) tet_s01.join(b1, tet_t0, regina.Perm4(t0, t0, b0, b0, t1, b1, b1, t1)) tet_s01.join(t0, tet_b1, regina.Perm4(b1, b1, t1, t1, b0, t0, t0, b0)) tet_s10.join(b0, tet_t1, regina.Perm4(t1, t1, b1, b1, t0, b0, b0, t0)) tet_s10.join(t1, tet_b0, regina.Perm4(b0, b0, t0, t0, b1, t1, t1, b1)) tet_s11.join(b1, tet_t0, regina.Perm4(t1, t1, b0, b0, t0, b1, b1, t0)) tet_s11.join(t1, tet_b0, regina.Perm4(b1, b1, t0, t0, b0, t1, t1, b0)) ### meridian around upper hole meridian = [0] * (3 * drilled_num_tet) meridian[3 * tet_t1.index() + unsorted_vert_pair_to_edge_pair[b0, t0]] = -1 meridian[3 * tet_s00.index() + unsorted_vert_pair_to_edge_pair[b1, t1]] = 1 meridian[3 * tet_b1.index() + unsorted_vert_pair_to_edge_pair[t0, t1]] = 1 meridian[3 * tet_s01.index() + unsorted_vert_pair_to_edge_pair[b0, t1]] = 1 meridian[3 * tet_t0.index() + unsorted_vert_pair_to_edge_pair[b1, t0]] = -1 meridians.append(meridian) ### meridian around lower hole - swap b with t everywhere. Note this also swaps s01 with s10 meridian = [0] * (3 * drilled_num_tet) meridian[3 * tet_b1.index() + unsorted_vert_pair_to_edge_pair[t0, b0]] = -1 meridian[3 * tet_s00.index() + unsorted_vert_pair_to_edge_pair[t1, b1]] = 1 meridian[3 * tet_t1.index() + unsorted_vert_pair_to_edge_pair[b0, b1]] = 1 meridian[3 * tet_s10.index() + unsorted_vert_pair_to_edge_pair[t0, b1]] = 1 meridian[3 * tet_b0.index() + unsorted_vert_pair_to_edge_pair[t1, b0]] = -1 meridians.append(meridian) else: ## fan # first two tetrahedra are the top and bottom tet_t, tet_b = drilledTri.tetrahedron( first_subtet_index), drilledTri.tetrahedron( first_subtet_index + 1) # second two tetrahedra are the sides, s0 meets vertex t0, s1 meets vertex t1 tet_s0, tet_s1 = drilledTri.tetrahedron(first_subtet_index + 2), drilledTri.tetrahedron( first_subtet_index + 3) # tet_types[i] == 'blue' this_tet_subtet_addresses[(b0, b1)] = tet_t this_tet_subtet_addresses[(b1, b0)] = tet_t this_tet_subtet_addresses[(t0, t1)] = tet_b this_tet_subtet_addresses[(t1, t0)] = tet_b ### if this is a blue fan tet then each side tet meet a blue edge # tet_types[i] could be 'blue' or 'red' ### find which bottom vertex, when linked to t0, gives an edge of the correct colour if vt.get_edge_between_verts_colour(i, (t0, b0)) == tet_types[i]: s0b = b0 ## bottom vert of s0 s1b = b1 ## bottom vert of s1 this_tet_subtet_addresses[(t0, b0)] = tet_s1 this_tet_subtet_addresses[(b0, t0)] = tet_s1 this_tet_subtet_addresses[(t1, b1)] = tet_s0 this_tet_subtet_addresses[(b1, t1)] = tet_s0 else: s0b = b1 ## bottom vert of s0 s1b = b0 ## bottom vert of s1 this_tet_subtet_addresses[(t0, b1)] = tet_s1 this_tet_subtet_addresses[(b1, t0)] = tet_s1 this_tet_subtet_addresses[(t1, b0)] = tet_s0 this_tet_subtet_addresses[(b0, t1)] = tet_s0 tet_s0.join(s0b, tet_t, regina.Perm4(t0, t0, s0b, t1, s1b, s1b, t1, s0b)) tet_s0.join(t0, tet_b, regina.Perm4(s0b, s0b, t0, s1b, t1, t1, s1b, t0)) tet_s1.join(s1b, tet_t, regina.Perm4(t1, t1, s1b, t0, s0b, s0b, t0, s1b)) tet_s1.join(t1, tet_b, regina.Perm4(s1b, s1b, t1, s0b, t0, t0, s0b, t1)) subtet_addresses.append(this_tet_subtet_addresses) ### now glue subtetrahedra from different original tetrahedra together ### fan tetrahedra only glue two of three subtetrahedra on each face. ### toggles glue one of their subfaces all the way through to the next toggle... unglued_flags = [] for f in range(tri.countTriangles()): for e in range(3): unglued_flags.append((f, e)) while unglued_flags != []: (f, e) = unglued_flags.pop() # print f,e face = tri.triangle(f) embed0 = face.embedding(0) embed1 = face.embedding(1) tet_index_0 = embed0.simplex().index() tet_index_1 = embed1.simplex().index() face0 = embed0.face() face1 = embed1.face() vertperm0 = embed0.vertices() vertperm1 = embed1.vertices() edge0 = vertperm0[e] edge1 = vertperm1[e] otherverts0 = [0, 1, 2, 3] otherverts0.remove(face0) otherverts0.remove(edge0) otherverts1 = [0, 1, 2, 3] otherverts1.remove(face1) otherverts1.remove(edge1) if (face0, edge0) in subtet_addresses[tet_index_0]: subtet0 = subtet_addresses[tet_index_0][(face0, edge0)] else: subtet0 = None if (face1, edge1) in subtet_addresses[tet_index_1]: subtet1 = subtet_addresses[tet_index_1][(face1, edge1)] else: subtet1 = None if subtet0 == None and subtet1 == None: pass ### both are fans, with no subtet, skip elif subtet0 != None and subtet1 != None: ### glue u, v = otherverts0 tet0perm = regina.Perm4(face0, edge0, edge0, face0, u, u, v, v) u, v = otherverts1 tet1perm = regina.Perm4(face1, edge1, edge1, face1, u, u, v, v) gluing = embed0.simplex().adjacentGluing(face0) subtet0.join(edge0, subtet1, tet1perm * gluing * tet0perm) ### perms act on left else: ### we have to walk around to find the right place to glue this toggle subtet, and remove the other unglued flag from the list if subtet1 == None: assert tet_types[tet_index_0] == 'toggle' toggle_tet_index = tet_index_0 toggle_face = face0 toggle_edge = edge0 subtet = subtet0 else: assert tet_types[tet_index_1] == 'toggle' toggle_tet_index = tet_index_1 toggle_face = face1 toggle_edge = edge1 subtet = subtet1 edge_verts = [0, 1, 2, 3] edge_verts.remove(toggle_edge) edge_verts.remove(toggle_face) toggle_e0, toggle_e1 = edge_verts e0, e1 = toggle_e0, toggle_e1 leading_vertex = toggle_edge trailing_vertex = toggle_face tet = tri.tetrahedron(toggle_tet_index) while True: gluing = tet.adjacentGluing(trailing_vertex) tet = tet.adjacentTetrahedron(trailing_vertex) e0, e1 = gluing[e0], gluing[e1] leading_vertex, trailing_vertex = gluing[ trailing_vertex], gluing[leading_vertex] if tet_types[tet.index()] == "toggle": break other_toggle_tet_index = tet.index() other_toggle_face = leading_vertex other_toggle_edge = trailing_vertex other_toggle_e0 = e0 other_toggle_e1 = e1 other_subtet = subtet_addresses[other_toggle_tet_index][( other_toggle_face, other_toggle_edge)] tetperm = regina.Perm4(toggle_face, toggle_edge, toggle_edge, toggle_face, toggle_e0, toggle_e0, toggle_e1, toggle_e1) other_tetperm = regina.Perm4(other_toggle_face, other_toggle_edge, other_toggle_edge, other_toggle_face, other_toggle_e0, other_toggle_e0, other_toggle_e1, other_toggle_e1) gluing = regina.Perm4(toggle_e0, other_toggle_e0, toggle_e1, other_toggle_e1, toggle_face, other_toggle_face, toggle_edge, other_toggle_edge) subtet.join(toggle_edge, other_subtet, other_tetperm * gluing * tetperm) return drilledTri, meridians