-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtwo_bridge.py
246 lines (153 loc) · 6.12 KB
/
two_bridge.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#! /pub/local/bin/python
#
# The location of your version of python should replace the above. On
# Unix, the command "which python" will tell you the correct location.
#
# This program computes the boundary slopes of a 2-bridge knot using
# the algorithm of Hatcher and Thurston, Invent. Math. 79, (1983).
# Below, Fig X refers to that figure in their paper.
#
# Written by Nathan Dunfield <nathand@math.uchicago.edu>
#
# Version 1.0. Dec 4, 1998.
# Version 1.3 Nov 14, 2020. Made work with Python 3.*
#
# The first function returns a list of the branched surfaces in the
# complement of the two-bridge knot p/q which carry an incompressible
# surface. Surfaces are stored as:
class surface:
expansion = []
slope = 0
# where expansion is the corresponding continued fraction expansion of
# p/q: [b1, b2, ... , bn] -> 1/b1 - 1/b2 + 1/b3 ...
def branched_surfaces(p, q):
# check input
if not (0 < p < q): raise ValueError("must have 0 < p < q")
# Branched surfaces carrying incompressible surfaces correspond to
# minimal paths in the heavy edges of the in Fig 5. The union of
# the heavy edges will be called D. The a_i in Fig. 5 are given
# by
a = positive_continued_fraction_expansion(p, q)
# Paths in D are kept track of by a list of the vertices. The
# vertices are labeled [-1, k] were i is vertex opposite a_i in
# Fig. 5 (I start a_i at a_0, not a_1, to be consistent with how
# python index arrays) . Thus vertices on the top edge of D are
# odd, and those on the bottom edge are even.
paths = minimal_edge_paths(a)
# Next, we compute the continued fraction expansion corresponding
# to each edge path, and create the corresponding branched
# surfaces.
surfaces = []
for path in paths:
surfaces.append(surface_from_path(path, a))
# We compute the slopes of the surfaces:
twist = seifert_twist(surfaces)
for s in surfaces:
compute_slope(s, twist)
return surfaces
def print_surfaces(surfaces):
for s in surfaces:
print(s.expansion, s.slope)
# returns the list of boundary slopes of surfaces with no repeats
def slopes(surfaces):
slopes = []
for s in surfaces:
slopes.append(s.slope)
slopes.sort()
unique_slopes = [slopes[0]]
for slope in slopes[1:]:
if slope != unique_slopes[-1]:
unique_slopes.append(slope)
return unique_slopes
# We will need the following
from gcd_tools import* #gcd, positive_continued_fraction_expansion
# see above for description
def minimal_edge_paths(a):
if len(a) < 1: raise(ValueError, "input trivial")
k = len(a)
# paths start at vertex -1 and end at vertex k
# there are two possible initial paths
paths = [ [-1, 1], [-1, 0] ]
final_paths = []
# create paths recursively
while len(paths) != 0:
new_paths = []
for path in paths:
# check to see if the path has reached the last vertex
if path[-1] == k:
final_paths.append(path)
continue
# otherwise, there are two possible continuations. The first is:
path1 = path[:] + [path[-1] + 1]
# We check if it is minimal. A path is minimal as long as
# it doesn't go from j to j + 1 to j + 2 with a_(j+1) = 1.
if not (path1[-3] + 1 == path1[-2] == path1[-1] - 1 and a[path1[-2]] == 1):
new_paths.append(path1)
# the other poss. continuation isn't always possible.
if path[-1] + 2 <= k:
path2 = path[:] + [path[-1] + 2]
if not (path2[-3] + 1 == path2[-2] == path2[-1] - 1 and a[path2[-2]] == 1):
new_paths.append(path2)
paths = new_paths[:]
return final_paths
# This function computes the continued fraction expansion associated
# to a minimal edge path, and creates a surface with that frac. expansion
def surface_from_path(path, a): # path, pos cont. frac. exp.
b = [] # this is the expansion (b_i) in the paper.
for i in range (1, len(path)):
x, y = path[i - 1], path[i]
# The associated cont. frac. exp. has one term for each vertex
# in the full diagram that the path passes through.
# First we add those b_i corresponding to vertices of the full
# diagram in the interior of the edge from x to y. Then we
# add the b_i corresponding to y.
# the sign of b_i is determined by whether the vertex is on
# the top or bottom edge of D.
if y %2 == 0:
sign = 1
else:
sign = -1
# there are only vertices on the interior of [x,y] if it is
# horizontal. each such vertex contributes +/- 2
if y - x == 2:
b = b + [sign*2]*(a[y - 1] - 1)
# now we consider the contribution of y, if y is not the last
# vertex in the path
if y != len(a):
z = path[i + 1]
# contrib of y is a[y] + c where c =
if y - x == 1 and z - y == 1:
c = 0
if y - x == 1 and z - y == 2:
c = 1
if y - x == 2 and z - y == 1:
c = 1
if y - x == 2 and z - y == 2:
c = 2
b.append(sign*(a[y] + c))
s = surface()
s.expansion = b[:]
return s
# for a surface s computes the n_plus - n_minus of Prop. 2
def twist(s):
n_plus, n_minus = 0, 0
for b in s.expansion:
if b > 0:
n_plus = n_plus + 1
else:
n_minus = n_minus + 1
return n_plus - n_minus
# The Seifert surfaces are carried by the unique branched surface where
# every term in the corresponding continued fraction expansion is even.
# this function returns the twist of that surface.
def seifert_twist(surfaces):
for s in surfaces:
all_even = 1
for b in s.expansion:
if b % 2 == 1:
all_even = 0
if all_even:
return twist(s)
# this function computes the slope using Prop. 2
def compute_slope(s, twist_of_seifert):
s.slope = 2 * (twist(s) - twist_of_seifert)