''' A module for representing basic ways of changing triangulations.
These moves can also track how laminations and homology classes move through those changes. '''
import curver
[docs]class Move(object):
''' A basic move from one triangulation to another. '''
def __init__(self, source_triangulation, target_triangulation):
assert isinstance(source_triangulation, curver.kernel.Triangulation)
assert isinstance(target_triangulation, curver.kernel.Triangulation)
self.source_triangulation = source_triangulation
self.target_triangulation = target_triangulation
self.zeta = self.source_triangulation.zeta
def __repr__(self):
return str(self)
def __invert__(self):
return self.inverse()
def __call__(self, other):
if isinstance(other, curver.kernel.Lamination):
return self.apply_lamination(other)
elif isinstance(other, curver.kernel.HomologyClass):
return self.apply_homology(other)
else:
raise TypeError('Unknown type %s' % other)
[docs] def encode(self):
''' Return the Encoding induced by this move. '''
return curver.kernel.Encoding([self])
[docs] def package(self):
''' Return a small amount of data such that self.source_triangulation.encode([data]) == self.encode(). '''
return self
[docs] def inverse(self): # pylint: disable=no-self-use
''' Return the inverse of this move. '''
return NotImplemented
[docs] def apply_lamination(self, lamination): # pylint: disable=no-self-use,unused-argument
''' Return the lamination obtained by mapping the given lamination through this move. '''
return NotImplemented
[docs] def apply_homology(self, homology_class): # pylint: disable=no-self-use,unused-argument
''' Return the homology class obtained by mapping the given homology class through this move. '''
return NotImplemented
[docs]class FlipGraphMove(Move):
''' A Move between two triangulations in the same flip graph. '''
[docs] def encode(self):
if self.source_triangulation != self.target_triangulation:
return curver.kernel.Mapping([self])
else:
return curver.kernel.MappingClass([self])
[docs] def flip_mapping(self): # pylint: disable=no-self-use
''' Return a Mapping equal to self.encoding() but that only uses EdgeFlips and Isometries. '''
return NotImplemented
[docs]class Isometry(FlipGraphMove):
''' This represents an isometry from one Triangulation to another.
Triangulations can create the isometries between themselves and this
is the standard way users are expected to create these. '''
def __init__(self, source_triangulation, target_triangulation, label_map):
''' This represents an isometry from source_triangulation to target_triangulation.
It is given by a map taking each edge label of source_triangulation to a label of target_triangulation.
Assumes that this map is defined on all labels. '''
super(Isometry, self).__init__(source_triangulation, target_triangulation)
assert isinstance(label_map, dict)
self.label_map = dict(label_map)
# Quick sanity check.
assert all(i in self.label_map for i in self.source_triangulation.labels)
self.index_map = dict((i, curver.kernel.norm(self.label_map[i])) for i in self.source_triangulation.indices)
# Store the inverses too while we're at it.
self.inverse_label_map = dict((self.label_map[label], label) for label in self.source_triangulation.labels)
self.inverse_index_map = dict((index, curver.kernel.norm(self.inverse_label_map[index])) for index in self.source_triangulation.indices)
def __str__(self):
return 'Isometry ' + str([curver.kernel.Edge(self.label_map[index]) for index in self.source_triangulation.indices])
def __reduce__(self):
return (self.__class__, (self.source_triangulation, self.target_triangulation, self.label_map))
[docs] def package(self):
if not all(self.label_map[i] == i for i in self.source_triangulation.indices): # If self is not the identity isometry.
return {i: self.label_map[i] for i in self.source_triangulation.labels}
else:
return None
def __eq__(self, other):
if isinstance(other, Isometry):
return self.source_triangulation == other.source_triangulation and self.target_triangulation == other.target_triangulation and self.label_map == other.label_map
else:
return NotImplemented
def __ne__(self, other):
return not self == other
[docs] def apply_lamination(self, lamination):
geometric = [lamination(self.inverse_index_map[index]) for index in self.source_triangulation.indices]
return lamination.__class__(self.target_triangulation, geometric) # Avoids promote.
[docs] def apply_homology(self, homology_class):
algebraic = [homology_class(self.inverse_label_map[index]) for index in self.source_triangulation.indices]
return curver.kernel.HomologyClass(self.target_triangulation, algebraic)
[docs] def inverse(self):
return Isometry(self.target_triangulation, self.source_triangulation, self.inverse_label_map)
[docs] def flip_mapping(self):
return self.encode()
[docs]class EdgeFlip(FlipGraphMove):
''' Represents the change to a curve caused by flipping an edge. '''
def __init__(self, source_triangulation, target_triangulation, edge):
super(EdgeFlip, self).__init__(source_triangulation, target_triangulation)
if isinstance(edge, curver.IntegerType): edge = curver.kernel.Edge(edge) # If given an integer instead.
self.edge = edge
assert self.source_triangulation.is_flippable(self.edge)
self.square = self.source_triangulation.square(self.edge)
def __str__(self):
return 'Flip %s' % self.edge
def __reduce__(self):
return (self.__class__, (self.source_triangulation, self.target_triangulation, self.edge))
[docs] def package(self):
return self.edge.label
def __eq__(self, other):
if isinstance(other, EdgeFlip):
return self.source_triangulation == other.source_triangulation and self.target_triangulation == other.target_triangulation and self.edge == other.edge
else:
return NotImplemented
def __ne__(self, other):
return not self == other
[docs] def apply_lamination(self, lamination):
L = lamination # Shorter name.
a, b, c, d, e = self.square
ai, bi, ci, di, ei = [max(L(edge), 0) for edge in self.square]
# Most of the new information matches the old, so we'll take a copy and modify the places that have changed.
geometric = list(L.geometric)
if L(e) >= ai + bi and ai >= di and bi >= ci: # CASE: A(ab)
geometric[e.index] = ai + bi - L(e)
elif L(e) >= ci + di and di >= ai and ci >= bi: # CASE: A(cd)
geometric[e.index] = ci + di - L(e)
elif L(e) <= 0 and ai >= bi and di >= ci: # CASE: D(ad)
geometric[e.index] = ai + di - L(e)
elif L(e) <= 0 and bi >= ai and ci >= di: # CASE: D(bc)
geometric[e.index] = bi + ci - L(e)
elif L(e) >= 0 and ai >= bi + L(e) and di >= ci + L(e): # CASE: N(ad)
geometric[e.index] = ai + di - 2*L(e)
elif L(e) >= 0 and bi >= ai + L(e) and ci >= di + L(e): # CASE: N(bc)
geometric[e.index] = bi + ci - 2*L(e)
elif ai + bi >= L(e) and bi + L(e) >= 2*ci + ai and ai + L(e) >= 2*di + bi: # CASE: N(ab)
geometric[e.index] = (ai + bi - L(e)) // 2
elif ci + di >= L(e) and di + L(e) >= 2*ai + ci and ci + L(e) >= 2*bi + di: # CASE: N(cd)
geometric[e.index] = (ci + di - L(e)) // 2
else:
geometric[e.index] = max(ai + ci, bi + di) - L(e)
return lamination.__class__(self.target_triangulation, geometric) # Avoids promote.
[docs] def apply_homology(self, homology_class):
a, b, c, d, e = self.square
algebraic = list(homology_class)
# Move the homology on e onto a & b.
algebraic[a.index] -= a.sign() * homology_class(e)
algebraic[b.index] -= b.sign() * homology_class(e)
algebraic[e.index] = 0
return curver.kernel.HomologyClass(self.target_triangulation, algebraic)
[docs] def inverse(self):
return EdgeFlip(self.target_triangulation, self.source_triangulation, ~self.edge)
[docs] def flip_mapping(self):
return self.encode()