def splitting_sequence(self): ''' Return the splitting sequence associated to this mapping class. Assumes (and checks) that the mapping class is pseudo-Anosov. This encoding must be a mapping class. ''' # We get a list of all possible splitting sequences from # self.splitting_sequences(). From there we use the fact that each # of these differ by a periodic mapping class, which cannot be in the # Torelli subgroup and so acts non-trivially on H_1(S). # Thus we look for the map whose action on H_1(S) is conjugate to self # via splitting.preperiodic. # To do this we take sufficiently many curves (the key_curves() of the # underlying triangulation) and look for the splitting sequence in which # they are mapped to homologous curves by: # preperiodic . self and periodic . preperiodic. # Note that we no longer use self.inverse() as periodic now goes in the # same direction as self. homology_splittings = [splitting for splitting in self.splitting_sequences() if (splitting.preperiodic * self).is_homologous_to(splitting.mapping_class * splitting.preperiodic)] if len(homology_splittings) == 0: raise flipper.FatalError('Mapping class is not homologous to any splitting sequence.') elif len(homology_splittings) == 1: return homology_splittings[0] else: # len(homology_splittings) > 1: raise flipper.FatalError('Mapping class is homologous to multiple splitting sequences.')
def hitting_matrix(self): ''' Return the hitting matrix of the underlying train track. ''' # This can fail with an flipper.AssumptionError. h = self.canonical() M = flipper.kernel.id_matrix(h.zeta) lamination = h.invariant_lamination() # Lamination defines a train track with a bipod in each triangle. We # follow the sequence of folds (and isometries) which this train track # undergoes and track how the edges are mapped using M. for item in reversed(h.sequence): if isinstance(item, flipper.kernel.EdgeFlip): triangulation = lamination.triangulation a, b, c, d = triangulation.square_about_edge(item.edge_label) # Work out which way the train track is pointing. if lamination.is_bipod(triangulation.corner_of_edge(a.label)): assert lamination.is_bipod( triangulation.corner_of_edge(c.label)) M = M.elementary(item.edge_index, b.index) M = M.elementary(item.edge_index, d.index) elif lamination.is_bipod(triangulation.corner_of_edge( b.label)): assert lamination.is_bipod( triangulation.corner_of_edge(d.label)) M = M.elementary(item.edge_index, a.index) M = M.elementary(item.edge_index, c.index) else: raise flipper.FatalError('Incompatible bipod.') elif isinstance(item, flipper.kernel.Isometry): M = flipper.kernel.Permutation( [item.index_map[i] for i in range(h.zeta)]).matrix() * M else: raise flipper.FatalError('Unknown item in canonical sequence.') # Move the lamination onto the next triangulation. lamination = item(lamination) return M
def flat_structure(self): ''' Return the flat structure associated to self.canonical(). This is based off of code supplied by Shannon Horrigan. Assumes that this mapping class is pseudo-Anosov. This encoding must be a mapping class. ''' assert self.is_mapping_class() splitting_sequence = self.splitting_sequence() periodic_triangulation = splitting_sequence.triangulation # This is the triangulation we will build the flat structure on. stable_lamination = splitting_sequence.lamination # These give us x coordinates. unstable_lamination = splitting_sequence.mapping_class.inverse().invariant_lamination() # These give us y coordinates. edge_vectors = dict() for triangle in periodic_triangulation: # Find the sides with largest stable and unstable lengths. index_s = max(range(3), key=lambda i: stable_lamination(triangle.edges[i])) # pylint: disable=cell-var-from-loop index_u = max(range(3), key=lambda i: unstable_lamination(triangle.edges[i])) # pylint: disable=cell-var-from-loop # Get the edges of triangle relative to the index_s. edges = [triangle[(index_s + i) % 3] for i in range(3)] if (index_s + 1) % 3 == index_u: # If the longest stable side is followed by the longest unstable side. edge_vectors[edges[0]] = flipper.kernel.Vector2(+stable_lamination(edges[0]), -unstable_lamination(edges[0])) edge_vectors[edges[1]] = flipper.kernel.Vector2(-stable_lamination(edges[1]), +unstable_lamination(edges[1])) edge_vectors[edges[2]] = flipper.kernel.Vector2(-stable_lamination(edges[2]), -unstable_lamination(edges[2])) elif (index_s - 1) % 3 == index_u: # If the longest unstable side is followed by the longest stable side. edge_vectors[edges[0]] = flipper.kernel.Vector2(+stable_lamination(edges[0]), +unstable_lamination(edges[0])) edge_vectors[edges[1]] = flipper.kernel.Vector2(-stable_lamination(edges[1]), +unstable_lamination(edges[1])) edge_vectors[edges[2]] = flipper.kernel.Vector2(-stable_lamination(edges[2]), -unstable_lamination(edges[2])) else: # Longest stable and unstable sides are the same. raise flipper.FatalError('Longest stable and unstable edges are the same.') assert sum([edge_vectors[edge] for edge in triangle], flipper.kernel.Vector2(0, 0)) == flipper.kernel.Vector2(0, 0) return flipper.kernel.FlatStructure(periodic_triangulation, edge_vectors)
def bundle(self, veering=True, _safety=True): ''' Return the bundle associated to this mapping class. This method can be run in two different modes: If veering=True then the bundle returned is triangulated by a veering, layered triangulation and has at most 6g+5n-6 additional loops drilled from it, as described by Agol. These additional cusps are marked as fake cusps and can be dealt with by filling along their fibre slope. Assumes (and checks) that this mapping class is pseudo-Anosov. If veering=False then the bundle returned is triangulated by a layered triangulation obtained by stacking flat tetrahedra, one for each edge flip in self. Assumes (and checks) that the resulting triangulation is an ideal triangulation of a manifold and that the fibre surface immerses into the two skeleton. If _safety=True then this should always happen. This encoding must be a mapping class. ''' assert self.is_mapping_class() triangulation = self.source_triangulation if veering: # This can fail with an flipper.AssumptionError if self is not pseudo-Anosov. return self.canonical().bundle(veering=False, _safety=False) if _safety: # We should add enough flips to ensure the triangulation is a manifold. # Flipping and then unflipping every edge is certainly enough. # However, we still have to be careful as there may be non-flippable edges. # Start by adding a flip and unflip each flippable edge. safe_encoding = self for i in triangulation.flippable_edges(): extra = triangulation.encode_flip(i) safe_encoding = extra.inverse() * extra * safe_encoding # Then add a flip and unflip for each non-flippable edge. for i in triangulation.indices: if not triangulation.is_flippable(i): # To do this we must first flip the boundary edge. boundary_edge = triangulation.nonflippable_boundary(i) # This edge is always flippable and, after flipping it, i is too. extra = triangulation.encode([i, boundary_edge]) safe_encoding = extra.inverse() * extra * safe_encoding return safe_encoding.bundle(veering=False, _safety=False) id_perm3 = flipper.kernel.Permutation((0, 1, 2)) lower_triangulation, upper_triangulation = triangulation, triangulation triangulation3 = flipper.kernel.Triangulation3(self.flip_length()) # These are maps taking triangles of lower (respectively upper) triangulation to either: # - A pair (triangle, permutation) where triangle is in upper (resp. lower) triangulation, or # - A pair (tetrahedron, permutation) of triangulation3. # We start with no tetrahedra, so these maps are just the identity map between the two triangulations. lower_map = dict((triangleA, (triangleB, id_perm3)) for triangleA, triangleB in zip( lower_triangulation, upper_triangulation)) upper_map = dict((triangleB, (triangleA, id_perm3)) for triangleA, triangleB in zip( lower_triangulation, upper_triangulation)) # We also use these two functions to quickly tell what a triangle maps to. maps_to_triangle = lambda X: isinstance(X[0], flipper.kernel.Triangle) maps_to_tetrahedron = lambda X: not maps_to_triangle(X) tetra_count = 0 for item in reversed(self.sequence): assert item.source_triangulation == upper_triangulation try: tetra_count, upper_triangulation, upper_map, lower_map = \ item.extend_bundle(triangulation3, tetra_count, upper_triangulation, lower_triangulation, upper_map, lower_map) except AttributeError: # We have no way to handle any other type that appears. # Currently this means there was a LinearTransform and so this is not a mapping class. raise flipper.FatalError( 'Unknown move %s encountered while building bundle.' % item) # We're now back to the starting triangulation. assert lower_triangulation == upper_triangulation # This is a map which send each triangle of upper_triangulation via isometry to a pair: # (triangle, permutation) # where triangle in lower_triangulation and maps_to_tetrahedron(lower_map[triangle]). full_forwards = dict() for source_triangle in upper_triangulation: target_triangle, perm = source_triangle, id_perm3 c = 0 while maps_to_triangle(lower_map[target_triangle]): target_triangle, new_perm = lower_map[target_triangle] perm = new_perm * perm c += 1 assert c <= 3 * upper_triangulation.zeta full_forwards[source_triangle] = (target_triangle, perm) # Now close the bundle up. for source_triangle in upper_triangulation: if maps_to_tetrahedron(upper_map[source_triangle]): A, perm_A = upper_map[source_triangle] target_triangle, perm = full_forwards[source_triangle] B, perm_B = lower_map[target_triangle] A.glue(perm_A(3), B, perm_B * perm.embed(4) * perm_A.inverse()) # There are now no unglued faces. assert triangulation3.is_closed() # Install longitudes and meridians. This also calls Triangulation3.assign_cusp_indices(). triangulation3.install_peripheral_curves() # Construct an immersion of the fibre surface into the closed bundle. fibre_immersion = dict() for source_triangle in lower_triangulation: if maps_to_triangle(lower_map[source_triangle]): upper_triangle, upper_perm = lower_map[source_triangle] target_triangle, perm = full_forwards[upper_triangle] B, perm_B = lower_map[target_triangle] fibre_immersion[source_triangle] = ( B, perm_B * (perm * upper_perm).embed(4)) else: B, perm_B = lower_map[source_triangle] fibre_immersion[source_triangle] = lower_map[source_triangle] return flipper.kernel.Bundle(lower_triangulation, triangulation3, fibre_immersion)