Source code for curver.kernel.crush


''' A module for representing more advanced ways of changing triangulations. '''

import numpy as np

import curver
from curver.kernel.moves import Move  # Special import needed for subclassing.

[docs]class Crush(Move): ''' This represents the effect of crushing along a curve. ''' def __init__(self, source_triangulation, target_triangulation, curve): super().__init__(source_triangulation, target_triangulation) assert isinstance(curve, curver.kernel.Curve) assert not curve.is_peripheral() and curve.is_short() assert curve.triangulation == self.source_triangulation self.curve = curve def __str__(self): return 'Crush ' + str(self.curve) def __eq__(self, other): eq = super().__eq__(other) if eq in [NotImplemented, False]: return eq return self.curve == other.curve
[docs] def apply_lamination(self, lamination): geometric = list(lamination) # Get some edges. a = self.curve.parallel() _, b, e = self.source_triangulation.corner_lookup[a] v = self.curve.triangulation.vertex_lookup[a] # = self.triangulation.vertex_lookup[~a]. v_edges = curver.kernel.utilities.cyclic_slice(v, a, ~a) # The set of edges that come out of v from a round to ~a. around_v = curver.kernel.utilities.maximin([0], (lamination.left_weight(edgy) for edgy in v_edges)) out_v = sum(max(-lamination.left_weight(edge), 0) for edge in v_edges) + sum(max(-lamination(edge), 0) for edge in v_edges[1:]) # around_v > 0 ==> out_v == 0; out_v > 0 ==> around_v == 0. twisting = curver.kernel.utilities.maximin([0], (lamination.left_weight(edgy) - around_v for edgy in v_edges[1:-1])) # We could have initially removed the twisting via the fact that: # twisting == abs(self.curve.slope(lamination) * lamination(a)) # Computing around_v and twisting can be done more efficiently. # We work by manipulating the side weights around v. sides = dict((edge, lamination.left_weight(edge) - (self.curve.left_weight(edge)*twisting + around_v if edge in v_edges and lamination.left_weight(edge) >= 0 else 0)) for edge in self.source_triangulation.edges) parallels = dict((edge.index, max(-lamination(edge), 0)) for edge in v_edges) # TODO: 4) Add comments explaining what is going on in the next two blocks and how the different tightening cases work. # Tighten to the left. drop = max(sides[a], 0) + max(-sides[b], 0) for edge in v_edges[1:-1]: x, y, z = lamination.triangulation.corner_lookup[edge] if sides[x] >= 0 and sides[y] >= 0 and sides[z] >= 0: if drop <= sides[x]: sides[x] = sides[x] - drop else: # sides[x] < drop. sides[x], sides[y], drop = sides[x] - drop, sides[y] + sides[x] - drop, sides[x] elif sides[x] < 0: sides[x], sides[y], drop = sides[x] - drop, sides[y] - drop, 0 elif sides[y] < 0: sides[x] = sides[x] - drop else: # sides[z] < 0. if drop <= sides[x]: sides[x] = sides[x] - drop elif sides[x] < drop <= sides[x] - sides[z]: parallels[z.index] = parallels[z.index] + (drop - sides[x]) sides[x], sides[z], drop = 0, sides[z] + (drop - sides[x]), sides[x] else: # sides[x] - sides[z] < drop: parallels[z.index] = parallels[z.index] - sides[z] sides[x], sides[y], sides[z], drop = sides[x] - sides[z] - drop, sides[y] - (drop - sides[x] + sides[z]), 0, sides[x] if drop == 0: break # Stop early. # Tighten to the right. drop = max(-sides[a], 0) + max(sides[b], 0) for edge in reversed(v_edges[1:-1]): x, y, z = lamination.triangulation.corner_lookup[edge] if sides[x] >= 0 and sides[y] >= 0 and sides[z] >= 0: if drop <= sides[x]: sides[x] = sides[x] - drop else: # sides[x] < drop. sides[x], sides[z], drop = sides[x] - drop, sides[z] + sides[x] - drop, sides[x] elif sides[x] < 0: sides[x], sides[z], drop = sides[x] - drop, sides[z] - drop, 0 elif sides[y] < 0: if drop <= sides[x]: sides[x] = sides[x] - drop elif sides[x] < drop <= sides[x] - sides[y]: parallels[x.index] = parallels[x.index] + (drop - sides[x]) sides[x], sides[y], drop = 0, sides[y] + (drop - sides[x]), sides[x] else: # sides[x] - sides[y] < drop: parallels[x.index] = parallels[x.index] - sides[y] sides[x], sides[y], sides[z], drop = sides[x] - sides[y] - drop, 0, sides[z] - (drop - sides[x] + sides[y]), sides[x] else: # sides[z] < 0. sides[x] = sides[x] - drop if drop == 0: break # Stop early. # Now rebuild the intersection. for edge in v_edges: if edge not in (a, b, e, ~b, ~e): x, y, z = lamination.triangulation.corner_lookup[edge] if parallels[edge.index] > 0: geometric[edge.index] = -parallels[x.index] else: geometric[edge.index] = max(sides[x], 0) + max(sides[y], 0) + max(-sides[z], 0) # Sanity check: x2, y2, z2 = lamination.triangulation.corner_lookup[~edge] assert geometric[edge.index] == max(sides[x2], 0) + max(sides[y2], 0) + max(-sides[z2], 0) # We have to rebuild the ~e edge separately since it now pairs with ~b. x, y, z = lamination.triangulation.corner_lookup[~e] if parallels[e.index] + parallels[b.index] + max(-sides[e], 0) > 0: geometric[e.index] = -(parallels[e.index] + parallels[b.index] + max(-sides[e], 0)) else: geometric[e.index] = max(sides[x], 0) + max(sides[y], 0) + max(-sides[z], 0) # Sanity check: x2, y2, z2 = lamination.triangulation.corner_lookup[~b] assert geometric[e.index] == max(sides[x2], 0) + max(sides[y2], 0) + max(-sides[z2], 0) # And finally the b edge, which is now paired with e. # Since around_v > 0 ==> out_v == 0 & out_v > 0 ==> around_v == 0, this is equivalent to: around_v if around_v > 0 else -out_v geometric[b.index] = around_v - out_v return self.target_triangulation(geometric) # Have to promote.
[docs] def apply_homology(self, homology_class): return NotImplemented # I don't think we ever need this.
[docs] def package(self): return (self.curve.parallel(), 0)
class LinearTransformation(Move): ''' This represents a linear transformation between two triangulations. ''' def __init__(self, source_triangulation, target_triangulation, matrix): super().__init__(source_triangulation, target_triangulation) assert matrix.shape == (target_triangulation.zeta, source_triangulation.zeta) self.matrix = matrix def __str__(self): return f'LT to {self.target_triangulation}' def __eq__(self, other): eq = super().__eq__(other) if eq in [NotImplemented, False]: return eq return np.array_equal(self.matrix, other.matrix) def package(self): return (self.target_triangulation.sig(), self.matrix.tolist()) def apply_lamination(self, lamination): return self.target_triangulation(self.matrix.dot(lamination.geometric).tolist()) def apply_homology(self, homology_class): return NotImplemented # I don't think we ever need this.
[docs]class Lift(LinearTransformation): ''' This represents the inverse of crushing along a curve. ''' def __init__(self, source_triangulation, target_triangulation, matrix): super().__init__(source_triangulation, target_triangulation, matrix) # We need to use super again since we have not found the vertices needed so that we can call self yet. apply_lamination = super().apply_lamination self.vertices = [vertex for vertex in self.source_triangulation.vertices if apply_lamination(self.source_triangulation.curve_from_cut_sequence(vertex)).is_peripheral()] def __str__(self): return f'Lift to {self.target_triangulation}'
[docs] def apply_lamination(self, lamination): assert all(lamination(edge) >= 0 and lamination.left_weight(edge) >= 0 for vertex in self.vertices for edge in vertex) return super().apply_lamination(lamination)