Source code for curver.kernel.arc
''' A module for representing (multi)arcs on triangulations. '''
from itertools import combinations
import networkx
import curver
from curver.kernel.lamination import IntegralLamination # Special import needed for subclassing.
from curver.kernel.decorators import memoize, topological_invariant # Special import needed for decorating.
[docs]class MultiArc(IntegralLamination):
''' An IntegralLamination in which every component is an Arc. '''
[docs] def is_short(self):
return all(weight <= 0 for weight in self)
[docs] def vertices(self):
''' Return set of vertices that the components of this MultiArc connects to. '''
return set(vertex for vertex in self.triangulation.vertices if any(self(edge) < 0 or self.left_weight(edge) < 0 for edge in vertex))
[docs] @topological_invariant
def has_distinct_endpoints(self):
''' Return whether this multiarc connects between distict vertices of its underlying triangulation. '''
return len(self.vertices()) == 2 * self.num_components()
[docs] def encode_halftwist(self, power=1):
''' Return an Encoding of a right half twist about a regular neighbourhood of this arc, raised to the given power.
This arc must have distinct endpoints. '''
if not self.has_distinct_endpoints(): # Check where it connects.
raise ValueError('Arc connects a vertex to itself')
if power == 0: # Boring case.
return self.triangulation.id_encoding()
short, conjugator = self.shorten()
h = curver.kernel.utilities.product(
[
curver.kernel.create.halftwist(arc, power).encode()
for arc, _ in short.components().items() # multiplicity must be 1.
],
start=short.triangulation.id_encoding()
)
return h.conjugate_by(conjugator)
[docs] def boundary(self):
''' Return the multicurve which is the boundary of a regular neighbourhood of this multiarc. '''
short, conjugator = self.shorten()
# short is a subset of the edges of the triangulation it is defined on.
# So its geometric vector is non-positive.
used = set(edge for edge in short.triangulation.edges if short(edge) < 0)
# Also use any edge that is in a triangle where two sides are used.
# Note that this does not change the boundary.
to_check = [triangle for triangle in short.triangulation if sum(1 for edge in triangle if edge in used) == 2]
while to_check:
triangle = to_check.pop()
for edge in triangle:
if edge not in used:
used.add(edge)
used.add(~edge)
neighbour = short.triangulation.triangle_lookup[~edge]
if sum(1 for edge in neighbour if edge in used) == 2:
to_check.append(neighbour)
break
# Now build each component by walking around the outside of the used edges.
passed_through = set()
geometric = [0] * short.zeta
for edge in short.triangulation.edges:
corner = short.triangulation.corner_lookup[edge]
if edge not in passed_through and edge not in used and corner[2] in used: # Start on a good edge.
while edge not in passed_through:
geometric[edge.index] += 1
passed_through.add(edge)
corner = short.triangulation.corner_lookup[edge]
if ~corner[2] not in used:
edge = ~corner[2]
else:
edge = ~corner[1]
boundary = short.triangulation(geometric)
return conjugator.inverse()(boundary)
[docs] @topological_invariant
def is_triangulation(self):
''' Return whether this MultiArc is a triangulation. '''
short, _ = self.shorten()
return all(weight == -1 for weight in short)
[docs] def explore_ball(self, radius):
''' Extend this MultiArc to a triangulation and return all triangulations within the ball of the given radius of that one.
Runs in exp(radius) time.
Note that this is only well-defined if this multiarc is filling. '''
assert self.is_filling()
short, conjugator = self.shorten()
triangulations = set()
for encoding in short.triangulation.all_encodings(radius):
T = encoding.target_triangulation.as_lamination()
triangulations.add(conjugator.inverse()(encoding.inverse()(T)))
return triangulations
[docs] def all_disjoint_arcs(self):
''' Yield all arcs that are disjoint from this multiarc.
Note that is only well defined if self is filling. '''
assert self.is_filling()
short, conjugator = self.shorten()
conjugator_inv = conjugator.inverse()
# All arcs that meet 0 edges.
for arc in short.triangulation.edge_arcs():
yield conjugator_inv(arc)
# All arcs that meet 1 edge.
for index in short.triangulation.indices:
if short(index) == 0:
arc = short.triangulation([1 if i == index else 0 for i in range(short.triangulation.zeta)])
yield conjugator_inv(arc)
# All arcs that meet at least two edges.
edges = [(short.triangulation.triangle_lookup[edge], short.triangulation.triangle_lookup[~edge]) for edge in short.triangulation.positive_edges if short(edge) == 0]
G = networkx.Graph(edges)
for t1, t2 in combinations(G.nodes(), r=2):
paths = networkx.all_simple_paths(G, t1, t2)
for path in paths:
if len(path) > 2: # Not covered by above case.
# Convert back to a sequence of edges.
cut_sequence = [edge.index for p1, p2 in zip(path, path[1:]) for edge in p1 if ~edge in p2]
arc = short.triangulation.lamination_from_cut_sequence(cut_sequence)
yield conjugator_inv(arc)
[docs] def all_disjoint_multiarcs(self):
''' Yield all multiarcs that are disjoint from this one.
Assumes that this multiarc is filling. '''
arcs = list(self.all_disjoint_arcs()) # Checks is filling.
G = networkx.Graph()
G.add_nodes_from(arcs)
G.add_edges_from([(a_1, a_2) for a_1, a_2 in combinations(arcs, r=2) if a_1.intersection(a_2) == 0])
for clique in networkx.enumerate_all_cliques(G):
yield self.triangulation.disjoint_sum(clique)
[docs] def all_disjoint_triangulations(self):
''' Yield all multiarcs that are triangulations that are disjoint from self.
Note that these must all therefore contain this multiarc.
Assumes that this multiarc is filling. '''
arcs = list(self.all_disjoint_arcs()) # Checks is filling.
G = networkx.Graph()
G.add_nodes_from(arcs)
G.add_edges_from([(a_1, a_2) for a_1, a_2 in combinations(arcs, r=2) if a_1.intersection(a_2) == 0])
for clique in networkx.find_cliques(G):
yield self.triangulation.disjoint_sum(clique)
[docs]class Arc(MultiArc):
''' A MultiArc with a single component. '''
[docs] @memoize
def components(self):
return {self: 1}
[docs] def parallel(self):
''' Return an edge that this arc is parallel to.
Note that this is only defined for short arcs. '''
assert self.is_short()
[(component, (multiplicity, edge))] = self.parallel_components().items()
assert component == self # Sanity.
assert multiplicity == 1 # Sanity.
return edge