1
|
|
|
# coding: utf8 |
2
|
|
|
|
3
|
|
|
# Copyright 2013-2015 Vincent Jacques <[email protected]> |
4
|
|
|
|
5
|
|
|
import collections |
6
|
|
|
import fractions |
7
|
|
|
import math |
8
|
|
|
|
9
|
|
|
|
10
|
|
|
End = collections.namedtuple("End", "theta, altitude") |
11
|
|
|
Segment = collections.namedtuple("Segment", "begin, end") |
12
|
|
|
Bridge = collections.namedtuple("Bridge", "before, after, tunnel") |
13
|
|
|
Tunnel = collections.namedtuple("Tunnel", "k, before, after") |
14
|
|
|
String = collections.namedtuple("String", "k, segments, bridges") |
15
|
|
|
|
16
|
|
|
|
17
|
|
|
class Knot(object): |
18
|
|
|
def __init__(self, p, q): |
19
|
|
|
self.p = p |
20
|
|
|
self.q = q |
21
|
|
|
self.d = fractions.gcd(p, q) |
22
|
|
|
self._ks = range(self.d) |
23
|
|
|
self.p_prime = p // self.d |
24
|
|
|
self.q_prime = q // self.d |
25
|
|
|
self.__max_theta = 2 * self.p * self.q_prime |
26
|
|
|
if self.q == 1: |
27
|
|
|
self.strings = [String(0, [Segment(End(0, 0), End(2 * self.p, 0))], [])] |
28
|
|
|
else: |
29
|
|
|
self.strings = list(self.__make_strings()) |
30
|
|
|
|
31
|
|
|
def __make_strings(self): |
32
|
|
|
intersections = list(self.__find_intersections()) |
33
|
|
|
tidied_intersections = self.__tidy_intersections(intersections) |
34
|
|
|
ends = list(self.__make_ends(tidied_intersections)) |
35
|
|
|
segments = list(self.__make_segments(ends)) |
36
|
|
|
bridges = list(self.__make_bridges(tidied_intersections, segments)) |
37
|
|
|
for k in self._ks: |
38
|
|
|
yield String(k, segments[k], bridges[k]) |
39
|
|
|
|
40
|
|
|
def __find_intersections(self): |
41
|
|
|
found = 0 |
42
|
|
|
for m in range(self.d): |
43
|
|
|
for n in range(m, self.d): |
44
|
|
|
for theta_1, theta_2 in self.__find_strings_intersections(m, n): |
45
|
|
|
assert 0 <= theta_1 < self.__max_theta |
46
|
|
|
assert 0 <= theta_2 < self.__max_theta |
47
|
|
|
assert m != n or theta_1 < theta_2 |
48
|
|
|
found += 1 |
49
|
|
|
yield m, n, theta_1, theta_2 |
50
|
|
|
assert found == self.p * (self.q - 1) |
51
|
|
|
|
52
|
|
|
def __find_strings_intersections(self, m, n): |
53
|
|
|
if m == n: |
54
|
|
|
min_a = 1 |
55
|
|
|
else: |
56
|
|
|
min_a = -self.q_prime + 1 |
57
|
|
|
max_a = self.q_prime |
58
|
|
|
for a in range(min_a, max_a): |
59
|
|
|
min_b = int(math.ceil(float(abs(a) * self.p - m - n) / self.q)) |
60
|
|
|
max_b = 2 * self.p_prime - int(math.floor(float(abs(a) * self.p + m + n) / self.q)) |
61
|
|
|
for b in range(min_b, max_b): |
62
|
|
|
theta_1 = b * self.q - a * self.p + m + n |
63
|
|
|
theta_2 = b * self.q + a * self.p + m + n |
64
|
|
|
yield theta_1, theta_2 |
65
|
|
|
|
66
|
|
|
def __tidy_intersections(self, intersections): |
67
|
|
|
tidied_intersections = [{} for k in self._ks] |
68
|
|
|
for m, n, theta_1, theta_2 in intersections: |
69
|
|
|
tidied_intersections[m][theta_1] = (n, theta_2) |
70
|
|
|
tidied_intersections[n][theta_2] = (m, theta_1) |
71
|
|
|
return tidied_intersections |
72
|
|
|
|
73
|
|
|
def __make_ends(self, tidied_intersections): |
74
|
|
|
ref_ends = list(self.__make_ref_ends(tidied_intersections)) |
75
|
|
|
yield ref_ends |
76
|
|
|
ref_ends = {e.theta: e for e in ref_ends} |
77
|
|
|
for k in range(1, self.d): |
78
|
|
|
yield list(self.__make_string_ends(ref_ends, tidied_intersections, k)) |
79
|
|
|
|
80
|
|
|
def __make_ref_ends(self, tidied_intersections): |
81
|
|
|
for (i, theta) in enumerate(sorted(tidied_intersections[0].iterkeys())): |
82
|
|
|
yield End(theta, (-1)**i) |
83
|
|
|
|
84
|
|
|
def __make_string_ends(self, ref_ends, tidied_intersections, k): |
85
|
|
|
for theta in tidied_intersections[k].iterkeys(): |
86
|
|
|
altitude = ref_ends[(theta - 2 * k) % (self.__max_theta)].altitude |
87
|
|
|
yield End(theta, altitude) |
88
|
|
|
|
89
|
|
|
def __make_segments(self, ends): |
90
|
|
|
for string_ends in ends: |
91
|
|
|
yield list(self.__make_string_segments(string_ends)) |
92
|
|
|
|
93
|
|
|
def __make_string_segments(self, string_ends): |
94
|
|
|
for begin, end in zip(string_ends, string_ends[1:]): |
95
|
|
|
yield Segment(begin, end) |
96
|
|
|
yield Segment( |
97
|
|
|
string_ends[-1], |
98
|
|
|
End(string_ends[0].theta + self.__max_theta, string_ends[0].altitude) |
99
|
|
|
) |
100
|
|
|
|
101
|
|
|
def __make_bridges(self, tidied_intersections, segments): |
102
|
|
|
segs_by_begin = [{s.begin.theta: s for s in string_segments} for string_segments in segments] |
103
|
|
|
segs_by_end = [{s.end.theta: s for s in string_segments} for string_segments in segments] |
104
|
|
|
for k, string_segments in enumerate(segments): |
105
|
|
|
yield list(self.__make_string_bridges(tidied_intersections, segs_by_begin, segs_by_end, k, string_segments)) |
106
|
|
|
|
107
|
|
|
def __make_string_bridges(self, tidied_intersections, segs_by_begin, segs_by_end, k, string_segments): |
108
|
|
|
assert len(string_segments) % 2 == 0 |
109
|
|
|
it = iter(string_segments) # Inspired by grouper in https://docs.python.org/2/library/itertools.html#recipes |
110
|
|
|
if string_segments[0].end.altitude != 1: |
111
|
|
|
after = it.next() |
112
|
|
|
before = self.__rotate_segment(string_segments[-1]) |
113
|
|
|
yield self.__make_bridge(tidied_intersections, segs_by_begin, segs_by_end, k, before, after) |
114
|
|
|
for before, after in zip(it, it): |
115
|
|
|
yield self.__make_bridge(tidied_intersections, segs_by_begin, segs_by_end, k, before, after) |
116
|
|
|
|
117
|
|
|
def __rotate_segment(self, segment): |
118
|
|
|
return Segment( |
119
|
|
|
End(segment.begin.theta - self.__max_theta, segment.begin.altitude), |
120
|
|
|
End(segment.end.theta - self.__max_theta, segment.end.altitude), |
121
|
|
|
) |
122
|
|
|
|
123
|
|
|
def __make_bridge(self, tidied_intersections, segs_by_begin, segs_by_end, k, before, after): |
124
|
|
|
assert before.end.theta == after.begin.theta |
125
|
|
|
assert before.end.altitude == after.begin.altitude == 1 |
126
|
|
|
n, theta_2 = tidied_intersections[k][before.end.theta] |
127
|
|
|
tunnel = self.__make_tunnel(segs_by_begin, segs_by_end, n, theta_2) |
128
|
|
|
bridge = Bridge(before, after, tunnel) |
129
|
|
|
if self.p < 3 or self.q < 3: |
130
|
|
|
return self.__shorten_bridge(bridge) |
131
|
|
|
else: |
132
|
|
|
return bridge |
133
|
|
|
|
134
|
|
|
def __make_tunnel(self, segs_by_begin, segs_by_end, n, theta_2): |
135
|
|
|
if theta_2 in segs_by_end[n]: |
136
|
|
|
bef_tunnel = segs_by_end[n][theta_2] |
137
|
|
|
else: |
138
|
|
|
bef_tunnel = self.__rotate_segment(segs_by_end[n][theta_2 + self.__max_theta]) |
139
|
|
|
aft_tunnel = segs_by_begin[n][theta_2] |
140
|
|
|
return Tunnel(n, bef_tunnel, aft_tunnel) |
141
|
|
|
|
142
|
|
|
def __shorten_bridge(self, bridge): |
143
|
|
|
return Bridge( |
144
|
|
|
Segment(End((bridge.before.begin.theta + bridge.before.end.theta) / 2, 0), bridge.before.end), |
145
|
|
|
Segment(bridge.after.begin, End((bridge.after.begin.theta + bridge.after.end.theta) / 2, 0)), |
146
|
|
|
Tunnel( |
147
|
|
|
bridge.tunnel.k, |
148
|
|
|
Segment( |
149
|
|
|
End((bridge.tunnel.before.begin.theta + bridge.tunnel.before.end.theta) / 2, 0), |
150
|
|
|
bridge.tunnel.before.end |
151
|
|
|
), |
152
|
|
|
Segment( |
153
|
|
|
bridge.tunnel.after.begin, |
154
|
|
|
End((bridge.tunnel.after.begin.theta + bridge.tunnel.after.end.theta) / 2, 0) |
155
|
|
|
), |
156
|
|
|
), |
157
|
|
|
) |
158
|
|
|
|