-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmontesinos.py
636 lines (516 loc) · 21.5 KB
/
montesinos.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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
# This file contains a program to compute the boundary slopes of a
# Montesinos knot. It uses the algorithm of Hatcher and Oertel
# The code follows their article very closely. References to say Thm 2.5
# etc. below are to the Thm 2.5 below. This file uses the base classes
# in montesinos_base.py and also gcd_tools.py.
#
# For an example of the use of the main function in this file
# see the file montesinos_bdry.py (which is what you would
# use just to get output).
#
# Written by Nathan Dunfield <nathan@dunfield.info>
#
# Version 1.0. Dec 4, 1998.
# Version 1.1 Nov 24, 2002. Made to work with Python 2.*
# Version 1.2 Jan 2, 2008. Fixed bugs in the implementation of Props 2.5, 2.6(2) and 2.7(3).
# Version 1.3 Nov 14, 2020. Made work with Python 3.*
#
# This code is released into the public domain.
from montesinos_base import *
# Functions needed for calc_Seifert_twist()
# takes a path which is assumed to project to a single edge in the
# reduced diagram and continues it to the left edge of T.
def continue_path_wo_changing_in_reduced(path):
while path[0].u() != 0:
a, b = path[0].leftward_neighbors()
if a.reduced() == path[1].reduced():
path = [a] + path[ : ]
else:
path = [b] + path[ : ]
return path
def paths_to_infinity_using_only_one_edge_of_reduced_diagram(tangle):
cont = continue_path_wo_changing_in_reduced
v = vertex_of_D(tangle)
a, b = v.leftward_neighbors()
if tangle.b % 2 == 0:
a, b = v.leftward_neighbors()
return [ cont( [a, v] ) , cont( [b, v] ) ]
else:
if a.reduced() == frac(1,0):
return cont( [ a, v] )
else:
return cont( [ b, v] )
def to_left_edge_via_odd_denominators(tangle):
v = vertex_of_D(tangle)
a, b = v.leftward_neighbors()
if a.reduced().b == 1:
return continue_path_wo_changing_in_reduced( [a, v] )
else:
return continue_path_wo_changing_in_reduced( [b, v] )
# follows top of page 461
def comp_Seifert_twist(tangles):
n = len(tangles)
even_qi_exists = 0
for i in range(0, n):
if tangles[i].b % 2 == 0:
even_qi_exists = 1
break
if even_qi_exists:
# Seifert surface consists a edge paths which go all the way
# to <1/0> and use only one edge of the reduced diagram.
# There is a uniques such except of the ith tangle where qi is
# even
paths_to_infinity = []
for j in list(range(0, i)) + list(range(i+1, n)):
paths_to_infinity.append(
paths_to_infinity_using_only_one_edge_of_reduced_diagram(tangles[j]))
num_of_odd_penultimate_slopes = 0
for path in paths_to_infinity:
if path[0].p() % 2 == 1:
num_of_odd_penultimate_slopes = num_of_odd_penultimate_slopes + 1
for path in paths_to_infinity_using_only_one_edge_of_reduced_diagram(tangles[i]):
if (path[0].p() + num_of_odd_penultimate_slopes) % 2 == 0:
temp_paths = paths_to_infinity[ : i] + [path] + paths_to_infinity[i : ]
real_paths = []
for j in range(0, n):
real_paths.append(edgepath(j, temp_paths[j]) )
surface = branched_surface( real_paths, "III", frac(1,0) )
else: # all qi are odd.
paths = []
for i in range(0,n):
real_path = edgepath(i, to_left_edge_via_odd_denominators(tangles[i]))
paths.append(real_path)
surface = branched_surface(paths, "II", 0)
sum_of_endpoints = 0
for path in paths:
sum_of_endpoints = sum_of_endpoints + path[0].p()
surface.twist = surface.twist + 2* sum_of_endpoints
return surface.twist
# returns the trees T_i. Differs from paper in that if a tangle does
# not have maximum denominator, the root of the tree extends all the
# way back to u = 1. When the function first creates a node,
def build_trees(tangles, max_denominator):
trees = []
# create root and (sometimes) first edge
for t in tangles:
if t.b == max_denominator:
v = vertex_of_D(t)
n = node(v, None)
trees.append(n)
extend_tree(n)
else:
v = vertex_of_D(t)
n1 = node(v, None)
n2 = node(v, n1)
n1.leftward[0] = n2
trees.append(n1)
extend_tree(n2)
return trees
# takes node and extends tree recursively to left edge of T
def extend_tree(n):
if n.vertex.u() == 0:
return #allready to left edge of T
left = n.vertex.leftward_neighbors()
for i in [0,1]:
v = left[i]
#check for minimality
if n.back and n.back.vertex != n.vertex:
if joined_by_edge(v, n.back.vertex):
continue
# create new node
new_node = node(v, n)
n.leftward[i] = new_node
extend_tree(new_node)
def print_tree(n, level = 0):
if n:
print(level*"\t", n)
for i in [0,1]:
print_tree( n.leftward[i], level + 1)
# given a collection of edge created in create_type_I_surfaces,
# creates the associated linear equation:
#
# (sum of v cor over edges) = a * u + b = 0
#
# and returns (a,b)
def create_equation(edges):
a = b = 0
for edge in edges:
if edge[0].vertex == edge[1].vertex: #edge flat
b = b + edge[1].v()
else:
p , q = edge[0].p(), edge[0].q()
r, s = edge[1].p(), edge[1].q()
a = a + frac(p*s - q*r, q - s)
b = b + frac(p - r + q*r - p*s, q - s)
return a, b
# given one edge in each tree, and a value of u, constructs the
# associated surface.
def create_type_I(edges, u):
paths = []
for t in range(0, len(edges)):
edge = edges[t]
if u == edge[0].u():
path = [ edge[0].vertex ]
cur_node = edge[0]
elif u == edge[1].u():
path = [ edge[1].vertex ]
cur_node = edge[1]
else:
q = edge[0].q()
s = edge[1].q()
if edge[0].vertex == edge[1].vertex: # on a horizonal strip
km = q*(1 - u)
path = [ interior_of_edge_of_D( edge[0].v() , edge[1].v(), km ) ]
else:
# sometimes causes overflow, so use large_div
km = (1 + s * ( -1 + u)) / ( (s - q)*(-1 + u) )
path = [ interior_of_edge_of_D( edge[0].v() , edge[1].v(), km ), edge[1].vertex ]
cur_node = edge[1]
# add rest of path
while cur_node.back and cur_node.vertex != cur_node.back.vertex:
path.append(cur_node.back.vertex)
cur_node = cur_node.back
paths.append(edgepath(t, path))
return branched_surface(paths, "I", u)
def create_type_I_surfaces(tangles):
surfaces = []
max_denom = max( map( lambda x: x.b, tangles ))
trees = build_trees(tangles, max_denom)
# We look at systems whose ending point has u coordinate in
# [(k-2)/(k - 1), (k-1)/k] for k in [1, max_denom]. We keep a list
# edges, indexed by the tangles, so that edge[i] is a list of
# edges of the form [node, node] where the projection that edge
# onto the u axis contains [(k-2)/(k - 1), (k - 1)/k ]
edges = []
for tree in trees:
edges_for_this_tangle = []
if tree.q() == max_denom:
for i in [0,1]:
edges_for_this_tangle.append( [ tree.leftward[i], tree ] )
else: # flat initial segment
edges_for_this_tangle.append( [ tree.leftward[0], tree ] )
edges.append(edges_for_this_tangle)
for k in range(max_denom, 1, -1): # [max_denom, ... , 2]
for edge_choices in product_of_lists(edges):
a, b = create_equation(edge_choices) #(sum of v cor over edges) = a * u + b = 0
# if a == 0, there is either no solution, or a
# non-isolated one. In the latter case I will handle it
# with as small of k as possible (i.e. at the leftmost
# endpoint of the region of solution). There is a small
# point of concern here. I believe that you should just
# be able to ignore non-isolated solutions until they
# become isolated, but I'm not sure this is the case. At
# worst I'm reporting multiple copies of the same surface, most
# of which will be removed later.
if a == 0 and b == 0 and k != 2: # if k == 2, handle this as a type II solution
u = frac (k-2,k-1)
surface = create_type_I(edge_choices, u)
surface.carries_incompressible = test_when_no_vertical_edges(surface)
surfaces.append(surface)
surface.from_non_iso_solution = 1
if a != 0:
u = -b/a
if frac(k-2,k-1) < u <= frac(k - 1, k): # have solution in desired interval
surface = create_type_I(edge_choices, u)
surface.carries_incompressible = test_when_no_vertical_edges(surface)
surfaces.append(surface)
# change edges if necessary for decreasing k
new_edges = []
for edge_in_tangle in edges:
new_edges_in_tangle = []
for edge in edge_in_tangle:
if edge[0].q() == k - 1:
for i in [0,1]:
if edge[0].leftward[i]:
new_edges_in_tangle.append([edge[0].leftward[i], edge[0] ])
else:
new_edges_in_tangle.append(edge)
new_edges.append(new_edges_in_tangle)
edges = new_edges[:]
# weed out duplicates
final_surfaces = []
for surface in surfaces:
match_found = 0
for other_surface in final_surfaces:
if surface == other_surface:
match_found = 1
break
if not match_found:
final_surfaces.append(surface)
return final_surfaces
def create_all_edgepaths_to_left_edge_of_T(tangle_num, tangles):
paths = [ [ vertex_of_D( tangles[tangle_num] ) ] ]
completed_paths = []
while len(paths) > 0:
new_paths = []
for path in paths:
for v in path[0].leftward_neighbors():
# check if adding v to path would make a minimal path
if len(path) != 1 and joined_by_edge(v, path[1]): continue
if abs(v.q()) == 1: #have we reached left edge of T?
completed_paths.append( [v] + path )
else:
new_paths.append( [v] + path )
paths = new_paths[:]
final_paths = []
for path in completed_paths:
final_paths.append( edgepath(tangle_num, path))
return final_paths
# Cor. 2.4: A candidate surface is incompressible unless the cycle of
# r-values for the final edges is one of the following types:
# (0, r1, ..., rn), (1, ..., 1, rn) or (1, ... , 1, 2, rn).
#
# returns 0 if the test inconclusive, 1 if incompressible.
#
# if cycle of r-values is of the latter 2 types, rearranges
# paths so that the cycle is in the form shown. Note that
# the cycle is only determined up to rotations and reflection.
# Reflection may be needed in the last case.
def apply_cor_2_4(surface):
number_of_ones = 0
n = len(surface.edgepaths)
for path in surface.edgepaths:
r = path.r_value
if r == 0: return 0
if abs(r) == 1: number_of_ones = number_of_ones + 1
if number_of_ones == n:
return 0
if number_of_ones == n - 1:
for i in range(0, n):
if abs(surface[i].r_value ) != 1:
surface.cycle_paths_so_last(i)
return 0
if number_of_ones == n - 2:
for i in range(0,n):
# because of def of surface, below valid even if i = n - 1
if abs(surface[i].r_value ) == 2 and abs(surface[ i + 1 ].r_value) != 1:
surface.cycle_paths_so_last(i + 1)
return 0
if abs(surface[i].r_value ) == 2 and abs(surface[ i - 1 ].r_value) != 1:
surface.cycle_paths_so_last(i) # (1, ... ,1, r, 2)
surface.reflect() # (2, r, 1, ... 1)
surface.cycle_paths_so_last(1) # (1,...,1,2,r)
return 0
return 1
# needed functions for test_when_no_vertical_edges
#
# takes two edge paths and determines if they have the same ending
# point and the final edges lie on the same triangle.
def same_ending_point_and_triangle(path0, path1):
if path0[0] == path1[0]: #have same ending point
if joined_by_edge(path0[1],path1[1]) or path0[1] == path1[1]: #lie on same triangle
return 1
return 0
def pairs_of_ones_have_opp_signs(surface):
for i in range(0, len(surface.edgepaths)):
if surface[i].r_value * surface[i + 1].r_value == 1:
return 0
return 1
def all_signs_alternate(surface):
for i in range(0, len(surface.edgepaths)):
if surface[i].r_value * surface[i + 1].r_value > 0:
return 0
return 1
def ones_have_opp_sign_from_neighbors(surface):
for i in range(0, len(surface.edgepaths)):
if abs(surface[i].r_value) == 1:
if surface[i].r_value * surface[i + 1].r_value > 0:
return 0
if surface[i - 1].r_value * surface[i].r_value > 0:
return 0
return 1
# Uses Cor 2.4 and Prop. 2.6-7 to check compressiblity. Applys to
# systems contained in T with no vertical edges (type I and certain type II systems)
def test_when_no_vertical_edges(surface, fix = True):
n = len(surface.edgepaths)
# test for constant edgepaths
for i in range(0, n):
if len(surface[i].path) == 1:
return 1
if apply_cor_2_4(surface):
return 1
# if r-cycle is (1,...,1, r) apply Prop. 2.6
final_r = abs(surface[-1].r_value)
if abs(surface[-2].r_value) == 1:
if pairs_of_ones_have_opp_signs(surface):
if n % 2 == 1: # case (1)
if final_r % 2 == 0: # case (b)
if surface.num_reversible() >= n - 1 and surface[-1].completely_reversible:
return 0
else: # case(2) n even
# first check condition in (a) that the last edge of
# surface[-1] lies in the same triangle of T and has
# the same ending point, as an edge with r = 1. There
# is the poss that final_r ==1
if all_signs_alternate(surface):
if final_r == 1:
if surface.num_reversible() >= n - 2:
return 0
else:
for i in range(0, n-1):
if fix:
final_d = abs(surface[-1][0].q())
if final_r == (final_d + 1):
if surface.num_reversible(range(0, n-1)) >= n - 2:
return 0
else:
if same_ending_point_and_triangle(surface[i], surface[-1] ):
# case 2(b). Are n - 2 paths with r = 1 comp. reversible? Yes -> comp.
if surface.num_reversible(range(0, n-1)) >= n - 2:
return 0
else: # r-values are (1,...1,2,r) and we implement Prop 2.7
if ones_have_opp_sign_from_neighbors(surface):
if final_r == 2: # case (1)
if surface.num_reversible() >= n - 1: return 0 #(a)
elif final_r == 4: # case (2)
if surface.num_reversible() == n: return 0 # (b)
else: #case(3)
if fix:
final_d = abs(surface[-1][0].q())
if (final_r == final_d + 2 and
surface.num_reversible(range(0, n-1)) == n - 1):
return 0 # (b)
else:
if (same_ending_point_and_triangle(surface[-2], surface[-1]) and
surface.num_reversible(range(0, n-1)) == n - 1):
return 0 # (b)
return 1
# applys Prop 2.9 to the type II surface the sum of whose endpoints on
# the left edge of T is sum. Returns 1 if system carries an
# incompressible surface, 0 otherwise.
def apply_proposition_2_9(surface, sum):
e = sum//abs(sum) # +/- 1
# the system does not extend to one which carries an
# incompressible surface if the cycle of r-values contains at
# least one e and every e is separated from the next by e by
# r-values all but possibly one of which is a completely
# reversible e*2.
e_found = 0
for i in range(0, len(surface.edgepaths)):
if surface[i].r_value == e:
e_found = 1
if not e_found: return 1
for i in range(0, len(surface.edgepaths)):
if surface[i].r_value == e:
j = i + 1
non_comp_rev_e2s = 0
while surface[j].r_value != e:
if surface[j].r_value != e*2 or not surface[j].completely_reversible:
non_comp_rev_e2s = non_comp_rev_e2s + 1
j = j + 1
if non_comp_rev_e2s > 1:
return 1
return 0
def create_type_II_and_III_surfaces(tangles, fix = True):
path_poss = []
for i in range(0, len(tangles)):
path_poss.append( create_all_edgepaths_to_left_edge_of_T(i, tangles) )
# loop over all possible curve systems
surfaces = []
for paths in product_of_lists(path_poss):
# needed to determine incompressibility
sum_of_endpoints = 0
for path in paths:
sum_of_endpoints = sum_of_endpoints + path[0].p()
# Create corresponding type II surface
surface = branched_surface(paths, "II", 0)
# determine whether the branched surface carries an
# incompressible surface.
if(sum_of_endpoints == 0):
surface.carries_incompressible = test_when_no_vertical_edges(surface, fix)
else:
# have to adjust the twist in this case
surface.twist = surface.twist + 2* sum_of_endpoints
surface.carries_incompressible = apply_proposition_2_9(surface, sum_of_endpoints)
surfaces.append(surface)
#print surface
# Create corresponding type III surface
# (edgepaths continued to <1/0> )
#
# The original version of this program ignored the last
# segment to <1/0> in determining whether the path is
# completely reversible. I've hacked in something to fix
# this, but it's a little ugly.
if fix:
extended_paths = []
for path in paths:
new_path = path.clone()
# correct the path, and recompute reversibility
new_path.path = [vertex_of_D(1,0)] + path.path
new_path.completely_reversible = new_path.decide_reversibility()
# now restore the path, as other parts of the program, like computing the slope depend on it.
new_path.path = path.path
extended_paths.append(new_path)
else:
extended_paths = paths
surface = branched_surface(extended_paths, "III", frac(1,0))
# apply proposition 2.5 to determine whether the branched
# surface carries an incompressible surface
if abs(sum_of_endpoints) <= 1 and surface.num_reversible() >= len(tangles) - 2 :
surface.carries_incompressible = 0
else:
surface.carries_incompressible = 1
surfaces.append(surface)
return surfaces
# For checking if given tangle defines a knot
def count_even_qi(tangles):
count = 0
for t in tangles:
if t.b % 2== 0:
count = count + 1
return count
def count_odd_pi(tangles):
count = 0
for t in tangles:
if t.t % 2 == 1:
count = count + 1
return count
def defines_knot(tangles):
if count_even_qi(tangles) == 1:
return 1
if count_even_qi(tangles) == 0 and count_odd_pi(tangles) % 2 == 1:
return 1
return 0
# The program does not work with integer tangles.
def no_integer_tangles(tangles):
for tangle in tangles:
if tangle.b == 1:
return 0
return 1
# the main function:
def compute_surfaces(tangles, fix = True):
if not defines_knot(tangles):
raise ValueError("tangles give a link, not a knot")
if not no_integer_tangles(tangles):
raise ValueError("no integer tangles allowed")
surfaces = create_type_I_surfaces(tangles) + create_type_II_and_III_surfaces(tangles, fix)
seifert_twist = comp_Seifert_twist(tangles)
for surface in surfaces:
surface.comp_slope(seifert_twist)
if len(tangles) > 3:
surfaces.append(conway_sphere())
return surfaces
# functions below for printing out information about the surfaces
# returns the boundary slopes of all incompressible surfaces
def compute_boundary_slopes(surfaces):
slopes = []
for surface in surfaces:
if surface.carries_incompressible:
slopes.append(surface.slope)
slopes.sort()
unique_slopes = [slopes[0]]
for slope in slopes[1:]:
if slope != unique_slopes[-1]:
unique_slopes.append(slope)
return unique_slopes
# returns a list of tuples (slope, num_sheets, euler_char) about each
# incompressible surface.
def essential_info(surfaces):
info = []
for surface in surfaces:
if surface.carries_incompressible:
info.append((surface.slope, surface.num_sheets, surface.euler_char))
info.sort()
return info
if __name__ == "__main__":
None