In this notebook we work with the infinite family of strongly invertible hyperbolic L-space knots K_n whose Watson invariant is thick. The first cell loads the necessary code.
import snappy
def FKP_bound(vol_surgery_link,lower_volume_bound):
'''
Returns the Futer-Kalfagianni-Purcell bound, that gives a lower bound on the normalized
length of a slope to ensure that a filling with such a slope on a link with volume = vol_surgery_link
has volume at least lower_volume_bound.
'''
return 2*3.14159/sqrt(1-(lower_volume_bound/vol_surgery_link)^(2/3))+0.1
def Bound(systole):
'''
Takes as input a systole and computes the bound from FPS19.
'''
B= math.sqrt(2*math.pi/systole +58)
if 10.1>B:
return 10.11
else:
return B+0.01
def better_systole(M,index=10):
'''
Computes the systole of a manifold.
It returns 'unclear' if SnapPy cannot compute the systole.
'''
w=False
randomizeCount=0
try:
systole=M.dual_curves()[0].complete_length.real()
w=True
except ValueError:
pass
except RuntimeError:
pass
except snappy.SnapPeaFatalError:
pass
except IndexError:
pass
if w==False:
while randomizeCount<index and w==False:
M.randomize()
randomizeCount=randomizeCount+1
try:
systole=M.length_spectrum()[0].length.real()
w=True
except ValueError:
pass
except RuntimeError:
pass
except snappy.SnapPeaFatalError:
pass
except IndexError:
pass
if w==True:
return systole
if w==False:
return 'unclear'
#
# dunfield.py
#
import regina
import snappy
import re
import networkx as nx
# This code has its origins in Dunfield's paper on exceptional slopes:
# https://dataverse.harvard.edu/file.xhtml?persistentId=doi:10.7910/DVN/6WNVG0/0X6FYV&version=1.0
# Some hacking has taken place. Most significantly, we have updated a lot of commands
# to interact with Regina 7.0 rather than 6.0.
################
# Code to prove a manifold is hyperbolic.
def all_positive(manifold):
return manifold.solution_type() == 'all tetrahedra positively oriented'
def find_positive_triangulation(manifold, tries = 3, verbose = 2):
M = snappy.Manifold(manifold)
for i in range(tries):
if all_positive(M):
return M
else:
M.randomize()
try: # this fails in verify_mt_action, sometimes
for d in M.dual_curves():
X = M.drill(d)
X = X.filled_triangulation()
X.dehn_fill((1,0),-1)
for i in range(tries):
if all_positive(X):
return X
X.randomize()
except snappy.SnapPeaFatalError:
print(M, "we failed to drill and fill... why?")
raise
# In the closed case, here is another trick.
if all(not c for c in M.cusp_info('is_complete')):
for i in range(tries):
# Drills out a random edge
X = M.__class__(M.filled_triangulation())
if all_positive(X):
return X
M.randomize()
# everything has failed
return None
def verify_hyperbolic_basic(manifold, tries = 3, verbose = 2):
M = find_positive_triangulation(manifold, tries = tries, verbose = verbose)
if M is not None:
for i in range(tries):
prec = 53*(2**i) # this used to go to 1000. Now it goes to 11.
try:
if M.verify_hyperbolicity(bits_prec=prec)[0]:
return True
except RuntimeError:
print(M, 'Treating exception in verify code as a failure')
return False
def verify_hyperbolic_basic_with_volume(manifold, tries = 3, verbose = 2):
M = find_positive_triangulation(manifold, tries = tries, verbose = verbose)
if M is not None:
for i in range(tries):
prec = 53*(2**i) # this used to go to 1000. Now it goes to 11.
try:
if M.verify_hyperbolicity(bits_prec = prec)[0]:
return (True, M.volume(verified = True, bits_prec = prec))
except RuntimeError:
print(M, 'Treating exception in verify code as a failure')
return (False, None)
def is_hyperbolic(manifold, tries = 10, verbose = 2):
if verify_hyperbolic_basic(manifold, tries = tries, verbose=verbose):
return True
else:
for d in range(2, min(tries, 8)):
for C in manifold.covers(d):
if verify_hyperbolic_basic(C, tries = tries, verbose=verbose):
return True
return False
def is_hyperbolic_with_volume(manifold, tries = 10, verbose = 2):
if verbose > 12:
print(manifold, "entering is_hyperbolic_with_volume")
is_hyp, vol = verify_hyperbolic_basic_with_volume(manifold, tries = tries, verbose = verbose)
if is_hyp:
if verbose > 12:
print(manifold, "verify_hyperbolic_basic_with_volume worked.")
return (is_hyp, vol)
else:
for d in range(2, min(tries, 8)):
for C in manifold.covers(d):
if verbose > 12:
print("trying cover of degree", d)
is_hyp, vol = verify_hyperbolic_basic_with_volume(C, tries = tries, verbose = verbose)
if is_hyp:
return (is_hyp, vol/d)
return (False, None)
###########################
# Provides functions for working with Regina (with a little
# help from SnapPy) to:
# 1. Give a standard name ("identify") manifolds, especially Seifert and
# graph manifolds.
# 2. Find essential tori.
# 3. Try to compute the JSJ decomposition.
def appears_hyperbolic(M):
acceptable = ['all tetrahedra positively oriented',
'contains negatively oriented tetrahedra']
return M.solution_type() in acceptable and M.volume() > 0
'''
The following seems to be unnecessary now that Regina 7.0 returns lists
def children(packet):
child = packet.firstChild()
while child:
yield child
child = child.nextSibling()
'''
def to_regina(data):
if hasattr(data, '_to_string'):
data = data._to_string()
if isinstance(data, str):
if data.find('(') > -1:
data = closed_isosigs(data)[0]
return regina.Triangulation3(data)
assert isinstance(data, regina.Triangulation3)
return data
def extract_vector(surface):
"""
Extract the raw vector of the (almost) normal surface in Regina's
NS_STANDARD coordinate system.
"""
S = surface
T = S.triangulation()
n = T.countTetrahedra()
ans = []
for i in range(n):
for j in range(4):
ans.append(S.triangles(i, j))
for j in range(3):
ans.append(S.quads(i, j))
A = regina.NormalSurface(T, regina.NS_STANDARD, ans)
assert A == S
return ans
def haken_sum(S1, S2):
T = S1.triangulation()
assert S1.locallyCompatible(S2)
v1, v2 = extract_vector(S1), extract_vector(S2)
sum_vec = [x1 + x2 for x1, x2 in zip(v1, v2)]
A = regina.NormalSurface(T, regina.NS_STANDARD, sum_vec)
assert S1.locallyCompatible(A) and S2.locallyCompatible(A)
assert S1.eulerChar() + S2.eulerChar() == A.eulerChar()
return A
def census_lookup(regina_tri):
"""
Should the input triangulation be in Regina's census, return the
name of the manifold, dropping the triangulation number.
"""
hits = regina.Census.lookup(regina_tri)
# The following is a Regina 6.0 command, deprecated in Regina 7.0
# hit = hits.first()
if len(hits) == 0:
return None
hit = hits[0]
if hit is not None:
name = hit.name()
match = re.search(r"(.*) : #\d+$", name)
if match:
return match.group(1)
else:
return match
def standard_lookup(regina_tri):
match = regina.StandardTriangulation.recognise(regina_tri)
if match:
return match.manifold()
def closed_isosigs(snappy_manifold, tries = 20, max_tets = 50):
"""
Generate a slew of 1-vertex triangulations of a closed manifold
using SnapPy.
>>> M = snappy.Manifold('m004(1,2)')
>>> len(closed_isosigs(M, tries=5)) > 0
True
"""
M = snappy.Manifold(snappy_manifold)
assert set(M.cusp_info('complete?')) == {False}
surgery_descriptions = [snappy.Manifold(M)]
try:
for curve in M.dual_curves():
N = M.drill(curve)
N.dehn_fill((1,0), 1)
surgery_descriptions.append(N.filled_triangulation([0]))
except snappy.SnapPeaFatalError:
pass
if len(surgery_descriptions) == 1:
# Try again, but unfill the cusp first to try to find more
# dual curves.
try:
filling = M.cusp_info(0).filling
N = snappy.Manifold(M)
N.dehn_fill((0, 0), 0)
N.randomize()
for curve in N.dual_curves():
D = N.drill(curve)
D.dehn_fill([filling, (1,0)])
surgery_descriptions.append(D.filled_triangulation([0]))
except snappy.SnapPeaFatalError:
pass
ans = set()
for N in surgery_descriptions:
for i in range(tries):
T = N.filled_triangulation()
if T._num_fake_cusps() == 1:
n = T.num_tetrahedra()
if n <= max_tets:
ans.add((n, T.triangulation_isosig(decorated=False)))
N.randomize()
return [iso for (n, iso) in sorted(ans)]
def best_match(matches):
"""
Prioritize the most concise description that Regina provides to
try to avoid things like the Seifert fibered space of a node being
a solid torus or having several nodes that can be condensed into a
single Seifert fibered piece.
"""
def score(m):
if isinstance(m, regina.SFSpace):
s = 0
elif isinstance(m, regina.GraphLoop):
s = 1
elif isinstance(m, regina.GraphPair):
s = 2
elif isinstance(m, regina.GraphTriple):
s = 3
elif m is None:
s = 10000
else:
s = 4
return (s, str(m))
return min(matches, key=score)
def identify_with_torus_boundary(regina_tri):
"""
Use the combined power of Regina and SnapPy to try to give a name
to the input manifold.
"""
kind, name = "unknown", None
P = regina.Triangulation3(regina_tri) # a clone of the triangulation
P.finiteToIdeal()
P.intelligentSimplify()
M = snappy.Manifold(P.isoSig())
M.simplify()
if appears_hyperbolic(M):
for i in range(100):
if M.solution_type() == 'all tetrahedra positively oriented':
break
M.randomize()
if not M.verify_hyperbolicity(bits_prec=100):
raise RuntimeError('Cannot prove hyperbolicity for ' +
M.triangulation_isosig())
kind = 'hyperbolic'
ids = M.identify()
if ids:
name = ids[0].name()
else:
match = standard_lookup(regina_tri)
if match is None:
Q = regina.Triangulation3(P)
Q.idealToFinite()
Q.intelligentSimplify()
match = standard_lookup(Q)
if match is not None:
kind = match.__class__.__name__
name = str(match)
else:
name = P.isoSig()
return kind, name
def is_toroidal(regina_tri):
"""
Checks for essential tori and returns the pieces of the
associated partial JSJ decomposition.
>>> T = to_regina('hLALAkbccfefgglpkusufk') # m004(4,1)
>>> is_toroidal(T)[0]
True
>>> T = to_regina('hvLAQkcdfegfggjwajpmpw') # m004(0,1)
>>> is_toroidal(T)[0]
True
>>> T = to_regina('nLLLLMLPQkcdgfihjlmmlkmlhshnrvaqtpsfnf') # 5_2(10,1)
>>> T.isHaken()
True
>>> is_toroidal(T)[0]
False
Note: currently checks all fundamental normal tori; possibly
the theory lets one just check *vertex* normal tori.
"""
T = regina_tri
assert T.isZeroEfficient()
# got rid of regina.NormalSurfaces.enumerate
surfaces = regina.NormalSurfaces(T, regina.NS_QUAD, regina.NS_FUNDAMENTAL)
for i in range(surfaces.size()):
S = surfaces.surface(i)
if S.eulerChar() == 0:
if not S.isOrientable():
S = S.doubleSurface()
assert S.isOrientable()
X = S.cutAlong()
X.intelligentSimplify()
# The following is Regina 6.0 code, deprecated in version 7.0
# X.splitIntoComponents()
# pieces = list(children(X))
pieces = X.triangulateComponents()
if all(not C.hasCompressingDisc() for C in pieces):
ids = [identify_with_torus_boundary(C) for C in pieces]
return (True, sorted(ids))
return (False, None)
def decompose_along_tori(regina_tri):
"""
First, finds all essential normal tori in the manifold associated
with fundamental normal surfaces. Then takes a maximal disjoint
collection of these tori, namely the one with the fewest tori
involved, and cuts the manifold open along it. It tries to
identify the pieces, removing any (torus x I) components.
Returns: (has essential torus, list of pieces)
Note: This may fail to be the true JSJ decomposition. There might be
unrecognized (torus x I)'s in the list of pieces and it might well be
possible to amalgamate some of the pieces into a single SFS.
"""
T = regina_tri
assert T.isZeroEfficient()
assert T.isConnected() # We will be counting components below
incompress_tori = []
# got rid of regina.NormalSurfaces.enumerate
surfaces = regina.NormalSurfaces(T, regina.NS_QUAD, regina.NS_FUNDAMENTAL)
for i in range(surfaces.size()):
S = surfaces.surface(i)
if S.eulerChar() == 0:
if not S.isOrientable():
S = S.doubleSurface()
assert S.isOrientable()
X = S.cutAlong()
X.intelligentSimplify()
# The following is Regina 6.0 code, deprecated in version 7.0
# X.splitIntoComponents()
# pieces = list(children(X))
pieces = X.triangulateComponents()
if all(not C.hasCompressingDisc() for C in pieces):
incompress_tori.append(S)
if incompress_tori == []:
return False, None
# Compute disjointness graph.
D = nx.Graph()
for a, A in enumerate(incompress_tori):
for b, B in enumerate(incompress_tori):
if a < b:
if A.disjoint(B):
D.add_edge(a, b)
# Build A, the union of a clique of disjoint tori such that any torus disjoint from A
# is parallel into A.
cliques = list(nx.find_cliques(D))
if len(cliques) == 0:
clique = [0]
else:
clique = min(cliques, key=len)
clique = [incompress_tori[c] for c in clique]
num_tori = len(clique)
A = clique[0]
for B in clique[1:]:
A = haken_sum(A, B)
X = A.cutAlong()
X.intelligentSimplify()
# The following is Regina 6.0 code, deprecated in version 7.0
# X.splitIntoComponents()
# pieces = list(children(X))
pieces = X.triangulateComponents()
ids = [identify_with_torus_boundary(C) for C in pieces]
# Count products. If this is less than the number of tori that we cut along,
# then at least one torus is not boundary-parallel
num_products = len([i for i in ids if i[1] in ('SFS [A: (1,1)]', 'A x S1')])
# The following is Regina 6.0 code, deprecated in version 7.0
# if len(T.boundaryComponents()) > 0 and (num_products >= num_tori):
if T.boundaryComponents().size() > 0 and (num_products >= num_tori):
# All tori are boundary-parallel
toroidal = False
else:
toroidal = True
# Remove products
pruned_ids = [i for i in ids if i[1] not in ('SFS [A: (1,1)]', 'A x S1')]
# But if nothing is left, then keep one product
if pruned_ids == []:
pruned_ids = ids[:1]
return (toroidal, sorted(pruned_ids))
def identify_with_bdy_from_isosig(data):
"""
Given the isosig of an ideal triangulation, use the combined
power of Regina and SnapPy to try to give a name to the input
manifold. Decompose along tori, if necessary.
Also works if handed a snappy Manifold.
"""
kind, name = "unknown", None
# If handed an isosig, throw away the boundary framing
if isinstance(data, str):
parts = iso.split("_")
data = parts[0]
P = to_regina(data)
P.intelligentSimplify()
M = snappy.Manifold(data)
M.simplify()
if appears_hyperbolic(M):
for i in range(100):
if M.solution_type() == 'all tetrahedra positively oriented':
break
M.randomize()
if not M.verify_hyperbolicity(bits_prec=100):
raise RuntimeError('Cannot prove hyperbolicity for ' +
M.triangulation_isosig())
kind = 'hyperbolic'
ids = M.identify()
if ids:
name = ids[0].name()
return kind, name
else:
match = standard_lookup(P)
if match is None:
P.idealToFinite()
P.intelligentSimplify()
match = standard_lookup(P)
if match is not None:
kind = match.__class__.__name__
name = str(match)
return kind, name
if match is None:
toroidal, pieces = decompose_along_tori(P)
if toroidal:
kind = 'toroidal'
name = str(pieces)
else:
# If atoroidal, there should be only one piece
assert len(pieces) == 1
kind = pieces[0][0]
name = pieces[0][1]
return kind, name
# At this point, we have failed. Return the only data available.
name = P.isoSig()
return kind, name
def regina_name(closed_snappy_manifold, tries = 100, max_tets = 25):
"""
>>> regina_name('m004(1,0)')
'S3'
>>> regina_name('s006(-2, 1)')
'SFS [A: (5,1)] / [ 0,-1 | -1,0 ]'
>>> regina_name('m010(-1, 1)')
'L(3,1) # RP3'
>>> regina_name('m022(-1,1)')
'SFS [S2: (3,2) (3,2) (4,-3)]'
>>> regina_name('v0004(0, 1)')
'SFS [S2: (2,1) (4,1) (15,-13)]'
>>> regina_name('m305(1, 0)')
'L(3,1) # RP3'
"""
M = snappy.Manifold(closed_snappy_manifold)
isosigs = closed_isosigs(M, tries = tries, max_tets = max_tets)
if len(isosigs) == 0:
return
T = to_regina(isosigs[0])
if T.isIrreducible():
if T.countTetrahedra() <= max_tets: # This used to be 11 - a magic number. We may regret changing it...
for i in range(3):
T.simplifyExhaustive(i)
name = census_lookup(T)
if name is not None:
return name
matches = [standard_lookup(to_regina(iso)) for iso in isosigs]
match = best_match(matches)
if match is not None:
return str(match)
else:
# The following is Regina 6.0 code, deprecated in version 7.0
# T.connectedSumDecomposition()
# pieces = list(children(T))
pieces = T.summands()
if len(pieces) == 1:
return census_lookup(pieces[0])
pieces = [regina_name(P) for P in pieces]
if None not in pieces:
return ' # '.join(sorted(pieces))
def better_symmetry_group(M,index=100,num_pos_triang=1):
'''
This function computes the symmetry group of the input manifold.
If the second entry is True it is proven to be the symmetry group.
'''
full=False
S='unclear'
try:
S=M.symmetry_group()
full=S.is_full_group()
if full:
return (S,full)
except (ValueError, RuntimeError, snappy.SnapPeaFatalError):
pass
pos_triang=find_positive_triangulations(M,tries=index,number=num_pos_triang)
if pos_triang==[]:
randomizeCount=0
while randomizeCount<index and full==False:
try:
S=M.symmetry_group()
full=S.is_full_group()
if full:
return (S,full)
M.randomize()
randomizeCount=randomizeCount+1
except (ValueError, RuntimeError, snappy.SnapPeaFatalError):
M.randomize()
randomizeCount=randomizeCount+1
for X in pos_triang:
randomizeCount=0
while randomizeCount<index and full==False:
try:
S=X.symmetry_group()
full=S.is_full_group()
if full:
return (S,full)
X.randomize()
randomizeCount=randomizeCount+1
except (ValueError, RuntimeError, snappy.SnapPeaFatalError):
X.randomize()
randomizeCount=randomizeCount+1
return (S,full)
def all_positive(manifold):
'''
Checks if the solution type of a triangulation is positive.
'''
return manifold.solution_type() == 'all tetrahedra positively oriented'
def find_positive_triangulations(manifold,number=1,tries=100):
'''
Searches for one triangulation with a positive solution type.
(Or if number is set to a different value also for different such triangulations.)
'''
M = manifold.copy()
pos_triangulations=[]
for i in range(tries):
if all_positive(M):
pos_triangulations.append(M)
if len(pos_triangulations)==number:
return pos_triangulations
break
M.randomize()
for d in M.dual_curves(max_segments=500):
X = M.drill(d)
X = X.filled_triangulation()
X.dehn_fill((1,0),-1)
for i in range(tries):
if all_positive(X):
pos_triangulations.append(X)
if len(pos_triangulations)==number:
return pos_triangulations
break
X.randomize()
# In the closed case, here is another trick.
if all(not c for c in M.cusp_info('is_complete')):
for i in range(tries):
# Drills out a random edge
X = M.__class__(M.filled_triangulation())
if all_positive(X):
pos_triangulations.append(X)
if len(pos_triangulations)==number:
return pos_triangulations
break
M.randomize()
return pos_triangulations
def Seifert_name(M, tries = 30):
"""
original version: identify_with_torus_boundary by Nathan Dunfield
Input: M - snappy.Manifold(); tries - int()
Output: None or regina name of M
returns Regina's canonical Seifert/standard name of a cusped (non-hyperbolic) exterior given by snappy. If not detected gives None.
Torus knot complements may need multiple tries.
"""
Mc = M.copy(); Mc.simplify()
i = tries
while i > 0:
Mc.randomize(); Mc.simplify()
T = regina.Triangulation3(Mc.triangulation_isosig(decorated=False))
T.idealToFinite() # ideal cusp -> real torus boundary (needed for SFS recognition)
T.intelligentSimplify()
T.intelligentSimplify()
s = regina.StandardTriangulation.recognise(T)
i += -1
if not (s is None):
return str(s.manifold())
return None
First we demostrate that these knots have a unique strong inversion. For that we consider their surgery description compute the symmetry group (of orientation-preserving diffeomorphism) and then check the possible symmetry-exceptional slopes.
L=snappy.Manifold('L12n1739')
L.symmetry_group().isometries()
[0 -> 0 1 -> 1 2 -> 2 [1 0] [1 0] [1 0] [0 1] [0 1] [0 1] Extends to link, 0 -> 0 1 -> 1 2 -> 2 [ 1 0] [-1 -4] [1 0] [-1 -1] [ 0 1] [0 -1] Does not extend to link, 0 -> 0 1 -> 1 2 -> 2 [-1 0] [-1 0] [-1 0] [ 0 -1] [ 0 -1] [ 0 -1] Extends to link, 0 -> 0 1 -> 1 2 -> 2 [-1 0] [1 4] [-1 0] [ 1 1] [0 -1] [ 0 1] Does not extend to link]
So this implies that there exist exactly one non-trivial orientation-preserving symmetry of the link which is given by a strong inversion. This strong inversion survives to a strong inversion after Dehn filling to K_n. But it could be that there exist for small values of the slope another exceptional symmetry. We exclude that by computing all short slopes and checking them directly.
systole=better_systole(L,10)
B=Bound(systole)
slopes=L.short_slopes(length=B*math.sqrt(L.cusp_areas()[0]))
for n in range(1,100):
if (1,2*n+2) in slopes[0]:
print(n)
if (-1,n+1) in slopes[2]:
print(n)
1 1 2 2 3 3 4 4 5 5 6 7 8
So the only possibilities of K_n that might have more than one symmetry are the ones for n<9. We check these directly.
for n in range(1,11):
L.dehn_fill([(1,2*n+3),(0,0),(-1,n+1)])
F=L.filled_triangulation()
print(n,F.symmetry_group())
1 Z/2 2 Z/2 3 Z/2 4 Z/2 5 Z/2 6 Z/2 7 Z/2 8 Z/2 9 Z/2 10 Z/2
Next, we compute the exceptional slopes of these. For that we first check which of the fillings on cusp 0 and 2 might be short.
L=snappy.Manifold('L12n1739')
short_slopes=L.short_slopes()
for n in range(1,100):
if (1,2*n+3) in short_slopes[0]:
print(n)
if (-1,n+1) in short_slopes[2]:
print(n)
1
So this might only occure for K_1 which is handled already in Baker-Kegel. The other ones we know to lie in short_slopes[1].
short_slopes[1]
[(1, 0), (-5, 1), (-4, 1), (-3, 1), (-2, 1), (-1, 1), (0, 1), (1, 1)]
Next, we compute for small values of n, the short slopes and check if the slope is exceptional for K_n.
for n in range(1,6):
L.dehn_fill([(1,2*n+3),(0,0),(-1,n+1)])
print('K_n:',n)
for x in short_slopes[1][1:]:
L.dehn_fill(x,1)
print(L)
F=L.filled_triangulation(cusps_to_fill=[0,2])
if is_hyperbolic(F)==False:
print(regina_name(F))
print('-------')
K_n: 1 L12n1739(1,5)(-5,1)(-1,2) L12n1739(1,5)(-4,1)(-1,2) L12n1739(1,5)(-3,1)(-1,2) L12n1739(1,5)(-2,1)(-1,2) SFS [S2: (2,1) (8,3) (11,-9)] L12n1739(1,5)(-1,1)(-1,2) L12n1739(1,5)(0,1)(-1,2) L12n1739(1,5)(1,1)(-1,2) ------- K_n: 2 L12n1739(1,7)(-5,1)(-1,3) L12n1739(1,7)(-4,1)(-1,3) L12n1739(1,7)(-3,1)(-1,3) L12n1739(1,7)(-2,1)(-1,3) SFS [S2: (2,1) (12,5) (15,-13)] L12n1739(1,7)(-1,1)(-1,3) L12n1739(1,7)(0,1)(-1,3) L12n1739(1,7)(1,1)(-1,3) ------- K_n: 3 L12n1739(1,9)(-5,1)(-1,4) L12n1739(1,9)(-4,1)(-1,4) L12n1739(1,9)(-3,1)(-1,4) L12n1739(1,9)(-2,1)(-1,4) SFS [S2: (2,1) (16,7) (19,-17)] L12n1739(1,9)(-1,1)(-1,4) L12n1739(1,9)(0,1)(-1,4) L12n1739(1,9)(1,1)(-1,4) ------- K_n: 4 L12n1739(1,11)(-5,1)(-1,5) L12n1739(1,11)(-4,1)(-1,5) L12n1739(1,11)(-3,1)(-1,5) L12n1739(1,11)(-2,1)(-1,5) SFS [S2: (2,1) (20,9) (23,-21)] L12n1739(1,11)(-1,1)(-1,5) L12n1739(1,11)(0,1)(-1,5) L12n1739(1,11)(1,1)(-1,5) ------- K_n: 5 L12n1739(1,13)(-5,1)(-1,6) L12n1739(1,13)(-4,1)(-1,6) L12n1739(1,13)(-3,1)(-1,6) L12n1739(1,13)(-2,1)(-1,6) SFS [S2: (2,1) (24,11) (27,-25)] L12n1739(1,13)(-1,1)(-1,6) L12n1739(1,13)(0,1)(-1,6) L12n1739(1,13)(1,1)(-1,6) -------
From Baker-Kegel we know already that the image of (-2,1) is a SFS slope for each knot. These calculations suggest that this is the only exceptional slope for each knot. (Excluding the (1,0) slope which gives S3.)
Below we first confirm that the image of (-2,1) is always exceptional for K_n, for that we show that L([(0,0),(-2,1),(0,0)] is a manifold obtained by gluing three Seifert fibered spaces over an annulus and thus each Dehn filling gives a non-hyperbolic manifolds.
Then we show that every other filling is hyperbolic.
L=snappy.Manifold('L12n1739')
L.dehn_fill((-2,1),1)
F=L.filled_triangulation()
F.splitting_surfaces()
[Orientable two-sided with euler = 0, Orientable two-sided with euler = 0]
A,B=F.split(1)
A.randomize()
B.randomize()
print(Seifert_name(A))
print(Seifert_name(B))
SFS [A: (2,1)] None
B.splitting_surfaces()
[Orientable two-sided with euler = 0]
C,D=B.split(0)
print(Seifert_name(C))
print(Seifert_name(D))
SFS [A: (2,1)] SFS [A: (2,1)]
To demonstrate that the other fillings are all hyperbolic, we show that L([(0,0),s,(0,0)] is hyperbolic for all other slopes s. For that it is enough to just check the short slopes. Since we fill along long slopes of the other cusps, it follows that all other slopes are hyperbolic.
for s in short_slopes[1]:
if s not in [(1,0),(-2,1)]:
L.dehn_fill(s,1)
H=L.filled_triangulation()
print(s,is_hyperbolic(H))
(-5, 1) True (-4, 1) True (-3, 1) True (-1, 1) True (0, 1) True (1, 1) True
Next, we classify the symmetry-exceptional slopes. We use the same strategy. First we compute the symmetry exceptional slopes of the K_n for n<9:
for n in range(1,9):
L.dehn_fill([(1,2*n+3),(0,0),(-1,n+1)])
print('K_n:',n)
for x in slopes[1][1:]:
if x!=(-2,1):
L.dehn_fill(x,1)
F=L.filled_triangulation(cusps_to_fill=[0,2])
F.randomize()
(S,w)=better_symmetry_group(F)
if w==False:
print(L)
print(S)
else:
if S.order()>2:
print(L)
print(S)
print('-------')
K_n: 1 L12n1739(1,5)(0,0)(-1,2) Z/2 + Z/2 L12n1739(1,5)(0,0)(-1,2) Z/2 + Z/2 ------- K_n: 2 L12n1739(1,7)(0,0)(-1,3) Z/2 + Z/2 L12n1739(1,7)(0,0)(-1,3) Z/2 + Z/2 ------- K_n: 3 L12n1739(1,9)(0,0)(-1,4) Z/2 + Z/2 L12n1739(1,9)(0,0)(-1,4) Z/2 + Z/2 ------- K_n: 4 L12n1739(1,11)(0,0)(-1,5) Z/2 + Z/2 L12n1739(1,11)(0,0)(-1,5) Z/2 + Z/2 ------- K_n: 5 L12n1739(1,13)(0,0)(-1,6) Z/2 + Z/2 L12n1739(1,13)(0,0)(-1,6) Z/2 + Z/2 ------- K_n: 6 L12n1739(1,15)(0,0)(-1,7) Z/2 + Z/2 L12n1739(1,15)(0,0)(-1,7) Z/2 + Z/2 ------- K_n: 7 L12n1739(1,17)(0,0)(-1,8) Z/2 + Z/2 L12n1739(1,17)(0,0)(-1,8) Z/2 + Z/2 ------- K_n: 8 L12n1739(1,19)(0,0)(-1,9) Z/2 + Z/2 L12n1739(1,19)(0,0)(-1,9) Z/2 + Z/2 -------
This suggest that the only symmetry-exceptional slopes are the images of -4 and 0. Both have two additional symmetries.
The slopes on the first cusp that are shorter than the FPS bound are:
print(slopes[1])
[(1, 0), (-15, 1), (-14, 1), (-13, 1), (-12, 1), (-11, 1), (-10, 1), (-9, 1), (-8, 1), (-7, 1), (-6, 1), (-5, 1), (-4, 1), (-3, 1), (-2, 1), (-1, 1), (0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1), (8, 1), (9, 1), (10, 1), (11, 1), (-15, 2), (-13, 2), (-11, 2), (-9, 2), (-7, 2), (-5, 2), (-3, 2), (-1, 2), (1, 2), (3, 2), (5, 2), (7, 2), (-17, 3), (-16, 3), (-14, 3), (-13, 3), (-11, 3), (-10, 3), (-8, 3), (-7, 3), (-5, 3), (-4, 3), (-2, 3), (-1, 3), (1, 3), (2, 3), (4, 3), (5, 3), (-19, 4), (-17, 4), (-15, 4), (-13, 4), (-11, 4), (-9, 4), (-7, 4), (-5, 4), (-3, 4), (-1, 4), (1, 4), (3, 4), (-19, 5), (-18, 5), (-17, 5), (-16, 5), (-14, 5), (-13, 5), (-12, 5), (-11, 5), (-9, 5), (-8, 5), (-7, 5), (-6, 5), (-4, 5), (-3, 5), (-2, 5), (-1, 5), (-19, 6), (-17, 6), (-13, 6), (-11, 6), (-7, 6), (-5, 6), (-19, 7), (-18, 7), (-17, 7), (-16, 7), (-15, 7), (-13, 7), (-12, 7), (-11, 7), (-10, 7), (-9, 7)]
We verify that (-4,1) and (0,1) fillings of L yield manifolds with symmetry group Z_2^2 and that all other slopes with length less than the FPS bound yield a manifold with symmetry group Z_2. Then we can conclude as in the case of the exceptional slopes.
L=snappy.Manifold('L12n1739')
for s in slopes[1]:
if s not in [(1,0),(-2,1),(-4,1),(0,1)]:
L.dehn_fill(s,1)
H=L.filled_triangulation()
print(s,better_symmetry_group(H))
(-15, 1) (Z/2, True) (-14, 1) (Z/2, True) (-13, 1) (Z/2, True) (-12, 1) (Z/2, True) (-11, 1) (Z/2, True) (-10, 1) (Z/2, True) (-9, 1) (Z/2, True) (-8, 1) (Z/2, True) (-7, 1) (Z/2, True) (-6, 1) (Z/2, True) (-5, 1) (Z/2, True) (-3, 1) (Z/2, True) (-1, 1) (Z/2, True) (1, 1) (Z/2, True) (2, 1) (Z/2, True) (3, 1) (Z/2, True) (4, 1) (Z/2, True) (5, 1) (Z/2, True) (6, 1) (Z/2, True) (7, 1) (Z/2, True) (8, 1) (Z/2, True) (9, 1) (Z/2, True) (10, 1) (Z/2, True) (11, 1) (Z/2, True) (-15, 2) (Z/2, True) (-13, 2) (Z/2, True) (-11, 2) (Z/2, True) (-9, 2) (Z/2, True) (-7, 2) (Z/2, True) (-5, 2) (Z/2, True) (-3, 2) (Z/2, True) (-1, 2) (Z/2, True) (1, 2) (Z/2, True) (3, 2) (Z/2, True) (5, 2) (Z/2, True) (7, 2) (Z/2, True) (-17, 3) (Z/2, True) (-16, 3) (Z/2, True) (-14, 3) (Z/2, True) (-13, 3) (Z/2, True) (-11, 3) (Z/2, True) (-10, 3) (Z/2, True) (-8, 3) (Z/2, True) (-7, 3) (Z/2, True) (-5, 3) (Z/2, True) (-4, 3) (Z/2, True) (-2, 3) (Z/2, True) (-1, 3) (Z/2, True) (1, 3) (Z/2, True) (2, 3) (Z/2, True) (4, 3) (Z/2, True) (5, 3) (Z/2, True) (-19, 4) (Z/2, True) (-17, 4) (Z/2, True) (-15, 4) (Z/2, True) (-13, 4) (Z/2, True) (-11, 4) (Z/2, True) (-9, 4) (Z/2, True) (-7, 4) (Z/2, True) (-5, 4) (Z/2, True) (-3, 4) (Z/2, True) (-1, 4) (Z/2, True) (1, 4) (Z/2, True) (3, 4) (Z/2, True) (-19, 5) (Z/2, True) (-18, 5) (Z/2, True) (-17, 5) (Z/2, True) (-16, 5) (Z/2, True) (-14, 5) (Z/2, True) (-13, 5) (Z/2, True) (-12, 5) (Z/2, True) (-11, 5) (Z/2, True) (-9, 5) (Z/2, True) (-8, 5) (Z/2, True) (-7, 5) (Z/2, True) (-6, 5) (Z/2, True) (-4, 5) (Z/2, True) (-3, 5) (Z/2, True) (-2, 5) (Z/2, True) (-1, 5) (Z/2, True) (-19, 6) (Z/2, True) (-17, 6) (Z/2, True) (-13, 6) (Z/2, True) (-11, 6) (Z/2, True) (-7, 6) (Z/2, True) (-5, 6) (Z/2, True) (-19, 7) (Z/2, True) (-18, 7) (Z/2, True) (-17, 7) (Z/2, True) (-16, 7) (Z/2, True) (-15, 7) (Z/2, True) (-13, 7) (Z/2, True) (-12, 7) (Z/2, True) (-11, 7) (Z/2, True) (-10, 7) (Z/2, True) (-9, 7) (Z/2, True)
L.dehn_fill((-4,1),1)
H=L.filled_triangulation()
better_symmetry_group(H)
(Z/2 + Z/2, True)
L.dehn_fill((0,1),1)
H=L.filled_triangulation()
better_symmetry_group(H)
(Z/2 + Z/2, True)