Skip to content

Quivers

Quiver Computation

winding_tracker dataclass

Tracks the winding numbers for loops on \(\overline{\alpha_{u/v}}\).

Source code in quivers\knot_quiver.py
 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
@dataclass
class winding_tracker:
    r"""Tracks the winding numbers for loops on $\overline{\alpha_{u/v}}$.
    """
    num: int
    denom: int
    diag_winds: List[List[int]] = field(init = False)
    writhe_states: List[Tuple[int]] = field(init = False)
    curr_writhes: List[int] = field(init = False)
    intersection_path: List[int] = field(default_factory=list)
    has_hit: Set[int] = field(default_factory=set)
    permute: Tuple[int] = field(init = False)
    writhes_k_w: Tuple[int] = (0, 0, 0)

    def __post_init__(self):
        if self.num % 2 == 0:
            raise ValueError("num must be odd")
        self.diag_winds = [[0] * self.num for _ in range(self.num)]
        self.writhe_states = [[0, 0, 0] for _ in range(self.num)]
        self.curr_writhes = [0, 0, 0]
        orientation = get_state(self.num, self.denom)[0]
        if orientation == "RI":
            raise ValueError("RI orientation")
        self.permute = (1, 2 if orientation == "UP" else 0, 0 if orientation == "UP" else 2)
        self._trace_path()

    def _step(self, curr_point, next_point, path_type):
        """Given a loop of type R, T, L, or C, updates the writhes of each tracked
        path, and, if the loop hits an intersection point, begins tracking paths
        beginning at it and ends tracking paths ending at it

        Args:
            curr_point (int): starting point of loop
            next_point (int): ending point of loop
            path_type (str): type of loop
        """
        is_cw = not rotateCCW(self.num, self.denom, curr_point, next_point, path_type)
        intersect = intersection_index(self.num, self.denom, curr_point, next_point, path_type)
        match path_type:
            case "R":
                if is_cw: # CW loop goes around right point before hitting beta
                    self.curr_writhes[self.permute[2]] += 2
                # R loop passes under all the points to the right of intersect
                for j in self.has_hit:
                    for i in range(intersect + 1, self.num):
                        if i not in self.has_hit:
                            self.diag_winds[j][i] += 1 if is_cw else -1

                self.writhe_states[intersect] = tuple(self.curr_writhes)
                self.has_hit.add(intersect)
                self.intersection_path.append(intersect)
                if not is_cw: # R loop passes underneath rightward points after hitting intersection
                    for i in range(intersect + 1, self.num):
                        if i not in self.has_hit:
                            self.diag_winds[intersect][i] -= 1
                if not is_cw:  # CCW loop goes around right point after hitting beta 
                    self.curr_writhes[self.permute[2]] -= 2

            case "C":
                self.curr_writhes[self.permute[1]] += 1 if is_cw else -1
                for j in self.has_hit:
                    for i in range(intersect):
                        if i not in self.has_hit:
                            self.diag_winds[j][i] += 2 if is_cw else -2
                    if not is_cw: # If γ_{j,i} ends with a CCW center loop, the writhe ends up decreasing by 1 due to the square.
                        self.diag_winds[j][intersect] -= 1
                self.writhe_states[intersect] = tuple(self.curr_writhes)
                self.has_hit.add(intersect)
                self.intersection_path.append(intersect)
                for i in range(intersect):
                    if i not in self.has_hit:
                        self.diag_winds[intersect][i] += 1 if is_cw else -1 # loops starting at intersection point only get half the writhe
                self.curr_writhes[self.permute[1]] += 1 if is_cw else -1

            case "T":
                self.curr_writhes[self.permute[1]] += 1 if curr_point <= self.num else -1
                for j in self.has_hit:
                    for i in range(intersect): # If the T arc is shortened, it only affects writhe where i is less than the intersection point to the right
                        if i not in self.has_hit:
                            self.diag_winds[j][i] -= 1 if curr_point > self.num else -1
            case "L":
                self.curr_writhes[self.permute[0]] += 2 if is_cw else -2


    def _turn_around_CW(self): # Will always turn CW because we walk with RH to wall
        """The last type of "loop" to consider: path goes to X+, turns around, and returns back
        """
        if self.permute[2] == 0: # Path end (X+) is rightmost point
            self.curr_writhes[self.permute[2]] += 2
            self.writhes_k_w = self.writhe_states[self.num - 1] = tuple(self.curr_writhes)
            self.has_hit.add(self.num - 1)
            self.intersection_path.append(self.num - 1)
        else: # Path end (X+) is central point Z
            self.curr_writhes[self.permute[1]] += 1
            self.writhes_k_w = self.writhe_states[0] = tuple(self.curr_writhes)
            self.has_hit.add(0)
            self.intersection_path.append(0)
            self.curr_writhes[self.permute[1]] += 1


    def _trace_path(self):
        r"""Splits :math:`\overline{\\alpha_{u/v}}` into a composition of loops, and then \\
        progresses sequentially through them, updating writhes along the way
        """
        path, loops = findpathwithloops(self.num, self.denom)
        for curr_point, next_point, path_type in zip(path, path[1:], loops):
            self._step(curr_point, next_point, path_type)
        self._turn_around_CW()
        for curr_point, next_point, path_type in zip(reversed(path), reversed(path[:-1]), reversed(loops)):
            self._step(curr_point, next_point, path_type)

    def wind_diag(self, j, i):
        # j MUST come before i in intersection_path. Returns :math:`w_{\Delta}(\gamma_{j, i})`
        return self.diag_winds[j][i]
    def wind_x_plus(self, j, i):
        # j MUST come before i in intersection_path. Returns :math:`w_{\X+}(\gamma_{j, i})`
        return (self.writhe_states[i][0] - self.writhe_states[j][0]) // 2
    def homfly_vectors(self):
        # return s_vec, a_vec, q_diag
        mu1, mu2, mu3 = mu_func.mus(self.num, self.denom)
        s_vec = [0] * self.num
        a_vec = [0] * self.num
        q_diag = [0] * self.num
        for i in range(self.num): # u = len(writhes) = len(s_vec) = len(a_vec) = len(q_diag)
            s_vec[i] = mu1 + (
                    self.writhes_k_w[0] - self.writhe_states[i][0]
                    + self.writhes_k_w[1] - self.writhe_states[i][1]
                    + self.writhes_k_w[2] - self.writhe_states[i][2]
                ) // 2
            a_vec[i] = mu2 + self.writhes_k_w[0] - self.writhe_states[i][0]
            q_diag[i] = mu3 + (
                    -3 * (self.writhes_k_w[0] - self.writhe_states[i][0])
                    + self.writhes_k_w[1] - self.writhe_states[i][1]
                    + self.writhes_k_w[2] - self.writhe_states[i][2]
                ) // 2
        return s_vec, a_vec, q_diag

    def colored_jones_vectors(self):
        # returns h_vec, q_diag
        mu1, mu2, mu3 = mu_func.mus(self.num, self.denom)
        h_vec = [0] * self.num
        q_diag = [0] * self.num
        for i in range(self.num):
            wind_term = (
                3 * (self.writhes_k_w[0] - self.writhe_states[i][0])
                - self.writhes_k_w[1] + self.writhe_states[i][1]
                - self.writhes_k_w[2] + self.writhe_states[i][2]
            ) // 2
            h_vec[i] = wind_term + 2 * mu2 - mu1
            q_diag[i] = wind_term - mu3
        return h_vec, q_diag

colored_homfly_vectors_and_quiver(u, v)

Calculates the Homfly quiver data for the knot \(K_{u/v}\)

Parameters:

Name Type Description Default
u int

Numerator.

required
v int

Denominator.

required

Returns:

Name Type Description
S list

S vector of quiver data.

A list

A vector of quiver data.

Q Matrix

Homfly Quiver represented as an adjacency matrix.

Source code in quivers\knot_quiver.py
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
def colored_homfly_vectors_and_quiver(
    u: int, v: int) -> Tuple[List[int], List[int], sp.Matrix]:
    r"""Calculates the Homfly quiver data for the knot $K_{u/v}$

    Args:
        u (int): Numerator.
        v (int): Denominator.

    Returns:
        S (list): S vector of quiver data.
        A (list): A vector of quiver data.
        Q (sympy.Matrix): Homfly Quiver represented as an adjacency matrix.
    """

    if u < v:
        raise ValueError("U")
    winder = winding_tracker(u, v)
    Q = sp.Matrix.zeros(u, u)
    s_vec, a_vec, q_diag = winder.homfly_vectors()
    path = winder.intersection_path
    for idx_i in range(u):
        i = path[idx_i]
        Q[i, i] = q_diag[i]
        for idx_j in range(idx_i):
            j = path[idx_j]
            Q[i, j] = Q[i, i] + winder.wind_diag(j, i) - 2 * winder.wind_x_plus(j, i)
            Q[j, i] = Q[i, j] # = Q[j, j] + winddiag(ij) - 2winderwindxplus(ji)
    return s_vec, a_vec, Q

colored_jones_vector_and_quiver(u, v)

Calculates the Jones quiver data for the knot \(K_{u/v}\)

Parameters:

Name Type Description Default
u int

Numerator.

required
v int

Denominator.

required

Returns:

Name Type Description
H list

H vector of quiver data.

Q Matrix

Jones Quiver represented as an adjacency matrix.

Source code in quivers\knot_quiver.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def colored_jones_vector_and_quiver(
    u, v) -> Tuple[List[int], sp.Matrix]:
    r"""Calculates the Jones quiver data for the knot $K_{u/v}$

    Args:
        u (int): Numerator.
        v (int): Denominator.

    Returns:
        H (list): H vector of quiver data.
        Q (sympy.Matrix): Jones Quiver represented as an adjacency matrix.
    """
    winder = winding_tracker(u, v)
    Q = sp.Matrix.zeros(u, u)
    h_vec, q_diag = winder.colored_jones_vectors()
    path = winder.intersection_path
    for idx_i in range(u):
        i = path[idx_i]
        Q[i, i] = q_diag[i]
        for idx_j in range(idx_i):
            j = path[idx_j]
            Q[i, j] = Q[i, i] + 2 * winder.wind_x_plus(j, i) - winder.wind_diag(j, i)
            Q[j, i] = Q[i, j]
    return h_vec, Q

Polynomial Computation

colored_homfly_polynomial(u, v, j)

Computes the j colored Homfly polynomial for \(K_{u/v}\).

Parameters:

Name Type Description Default
u int

Numerator.

required
v int

Denominator.

required
j int

Color.

required

Returns:

Name Type Description
poly Poly

The colored Homfly polynomial for K.

Source code in quivers\evaluate_quiver.py
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
def colored_homfly_polynomial(u,v,j):
    r"""Computes the j colored Homfly polynomial for $K_{u/v}$.

    Args:
        u (int): Numerator.
        v (int): Denominator.
        j (int): Color.

    Returns:
        poly (sympy.polys.polytools.Poly): The colored Homfly polynomial for K.
    """
    S, A, Q = colored_homfly_vectors_and_quiver(u,v)
    Q = np.array(Q.tolist(), dtype=int)
    poly = evaluate_quiver_knot(Q, S, A, u, v, j)
    return poly

colored_jones_polynomial(u, v, j)

Computes the j colored Jones polynomial for \(K_{u/v}\).

Parameters:

Name Type Description Default
u int

Numerator.

required
v int

Denominator.

required
j int

Color.

required

Returns:

Name Type Description
poly Poly

The colored Jones polynomial for K.

Source code in quivers\evaluate_quiver.py
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
def colored_jones_polynomial(u,v,j):
    r"""Computes the j colored Jones polynomial for $K_{u/v}$.

    Args:
        u (int): Numerator.
        v (int): Denominator.
        j (int): Color.

    Returns:
        poly (sympy.polys.polytools.Poly): The colored Jones polynomial for K.
    """
    S, A, Q = colored_homfly_vectors_and_quiver(u,v)
    Q = np.array(Q.tolist(), dtype=int)
    poly = evaluate_quiver_jones_from_homfly(Q, S, A, u, v, j)
    return poly

colored_sln_polynomial(u, v, j, N)

Computes the j colored \(\mathfrak{sl}_N\) Jones polynomial for \(K_{u/v}\).

Parameters:

Name Type Description Default
u int

Numerator.

required
v int

Denominator.

required
j int

Color.

required
N int

q specialization.

required

Returns:

Name Type Description
poly Poly

The colored \(\mathfrak{sl}_N\) polynomial for K.

Source code in quivers\evaluate_quiver.py
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
def colored_sln_polynomial(u,v,j,N):
    r"""Computes the j colored $\mathfrak{sl}_N$ Jones polynomial for $K_{u/v}$.

    Args:
        u (int): Numerator.
        v (int): Denominator.
        j (int): Color.
        N (int): q specialization.

    Returns:
        poly (sympy.polys.polytools.Poly): The colored $\mathfrak{sl}_N$ polynomial for K.
    """

    # Calculate homfly quiver and then specialize q->q^-1 and a->q^N
    S, A, Q = colored_homfly_vectors_and_quiver(u, v)
    Q_numpy = np.array(Q.tolist(), dtype=int) 
    poly = evaluate_quiver_qsub(Q_numpy, S, A, u, v, j, N)

    # normalize so that the first term is positive
    if poly(0) < 0:
        return -poly
    return poly

get_tuples(k, n)

Yields all k-tuples of nonnegative integers adding up to n.

Source code in quivers\evaluate_quiver.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def get_tuples(k, n):
    """Yields all k-tuples of nonnegative integers adding up to n."""

    if k == 1:
        yield (n,)
        return

    for dividers in combinations(range(n + k - 1), k - 1):
        full_dividers = (-1,) + dividers + (n + k - 1,)

        tup = tuple(
            full_dividers[i + 1] - full_dividers[i] - 1
            for i in range(k)
        )

        yield tup