Skip to content

Visualizers

Jones Tail Visualizer

create_heatmap(u, v, j=10, qsub=2, trunc=50, tail=True)

Creates heatmap for the tail of the colored jones polynomial of a rational knot

Parameters:

Name Type Description Default
u int

Numerator.

required
v int

Denominator.

required
j int

Highest color to calculate.

10
qsub int

q specialization, 2 gives the Jones polynomial.

2
trunc int

Highest degree for each polynomial.

50
tail bool

True to compute tail, False to compute head.

True

Returns:

Name Type Description
None NoneType

Saves image to path.

Source code in visualizers\heatmap.py
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
def create_heatmap(u, v, j=10, qsub=2, trunc=50, tail=True):
    """Creates heatmap for the tail of the colored jones polynomial of a rational knot

    Args:
        u (int): Numerator.
        v (int): Denominator.
        j (int): Highest color to calculate.
        qsub (int): q specialization, 2 gives the Jones polynomial.
        trunc (int): Highest degree for each polynomial.
        tail (bool): True to compute tail, False to compute head.

    Returns:
        None (NoneType): Saves image to path.
    """

    polies = [] # create list of j colored jones polynomials
    for i in range(1, j+1):
        sub = substitute(u, v, i, qsub)
        if not tail:
            sub = sub.as_expr()
            sub = invert_and_renormalize(sub, q)
            if sub(0) < 0:
                sub = -sub
        polies.append(truncate(sub, trunc))

    data = []
    for idx, p in enumerate(polies):
        # p.as_dict() returns {(degree,): coeff}
        for (deg,), coeff in p.as_dict().items():
            data.append({'Poly_Index': idx + 1, 'Degree': deg, 'Coeff': int(coeff)})


    df = pd.DataFrame(data) # saves data in a data frame

    # Pivot so Degrees are columns and Poly_Index are rows
    pivot_df = df.pivot(index='Poly_Index', columns='Degree', values='Coeff').fillna(0)

    # Find the integer range of degrees for heatmap
    min_deg = pivot_df.columns.min()
    max_deg = pivot_df.columns.max()

    complete_range = range(min_deg, max_deg + 1)

    # Force Pandas to include all even columns, filling the missing ones with 0

    pivot_df_fixed = pivot_df.reindex(columns=complete_range, fill_value=0)
    even_columns = [c for c in pivot_df_fixed.columns if c % 2 == 0]
    pivot_df_even = pivot_df_fixed[even_columns]

    norm = HybridNormalize(vmin=pivot_df.values.min(), vmax=pivot_df.values.max())

    # Plot the heatmap
    plt.figure(figsize=(10, 6))
    ax = sns.heatmap(pivot_df_even, cmap=custom_cmap, norm=norm, annot=False, linewidths=0.5 ,fmt='.0f', cbar=True)

    # calculate shift in staircase
    shift = 0 
    _, A, _ = colored_homfly_vectors_and_quiver(u, v)
    if A.count(min(A)) == 1:
        shift = qsub - 2

    # plot staircase
    ax.plot([0,1+shift], [0,0], color='black', linewidth=2.5)
    for row_idx in range(len(pivot_df_even)):
        ax.plot([row_idx+1+shift, row_idx+2+shift], [row_idx, row_idx], color='black', linewidth=2.5)
        ax.plot([row_idx+2+shift, row_idx+2+shift], [row_idx, row_idx+1], color='black', linewidth=2.5)

    plt.title("Polynomial Coefficients Heatmap")
    plt.xlabel("Degree (q^d)")
    plt.ylabel("jth Colored Jones Polynomial")
    dir_path = Path("tails")
    dir_path.mkdir(parents=True, exist_ok=True)
    if tail:
        plt.savefig(f"tails/K_{u}_{v}_{qsub}")
    else:
        plt.savefig(f"tails/H_{u}_{v}_{qsub}")

create_heatmap_frac(u, v, j=10, qnum=2, qdenom=1, trunc=50, tail=True)

Creates heatmap for the tail of the colored HOMFLY-PT polynomial of a rational knot specialized to qnum/qdenom

Parameters:

Name Type Description Default
u int

Numerator.

required
v int

Denominator.

required
j int

Highest color to calculate.

10
qnum int

specializes a - > q^qnum

2
qdenom int

specializes q -> q^-qdenom

1
trunc int

Highest degree for each polynomial.

50
tail bool

True to compute tail, False to compute head.

True

Returns:

Name Type Description
None NoneType

Saves image to path.

Source code in visualizers\heatmap.py
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
def create_heatmap_frac(u, v, j=10, qnum=2, qdenom=1, trunc=50, tail=True):
    """Creates heatmap for the tail of the colored HOMFLY-PT polynomial of a rational knot specialized to qnum/qdenom

    Args:
        u (int): Numerator.
        v (int): Denominator.
        j (int): Highest color to calculate.
        qnum (int): specializes a - > q^qnum
        qdenom (int): specializes q -> q^-qdenom
        trunc (int): Highest degree for each polynomial.
        tail (bool): True to compute tail, False to compute head.

    Returns:
        None (NoneType): Saves image to path.
    """

    polies = [] # create list of j colored jones polynomials
    for i in range(1, j+1):
        sub = sub_frac(u, v, i, qnum, qdenom)
        if not tail:
            sub = sub.as_expr()
            sub = invert_and_renormalize(sub, q)
            if sub(0) < 0:
                sub = -sub
        polies.append(truncate(sub, trunc))

    data = []
    for idx, p in enumerate(polies):
        # p.as_dict() returns {(degree,): coeff}
        for (deg,), coeff in p.as_dict().items():
            data.append({'Poly_Index': idx + 1, 'Degree': deg, 'Coeff': int(coeff)})


    df = pd.DataFrame(data) # saves data in a data frame

    # Pivot so Degrees are columns and Poly_Index are rows
    pivot_df = df.pivot(index='Poly_Index', columns='Degree', values='Coeff').fillna(0)

    # Find the integer range of degrees for heatmap
    min_deg = pivot_df.columns.min()
    max_deg = pivot_df.columns.max()

    complete_range = range(min_deg, max_deg + 1)

    # Force Pandas to include all even columns, filling the missing ones with 0

    pivot_df_fixed = pivot_df.reindex(columns=complete_range, fill_value=0)
    even_columns = [c for c in pivot_df_fixed.columns if c % 2 == 0]
    pivot_df_even = pivot_df_fixed[even_columns]

    norm = HybridNormalize(vmin=pivot_df.values.min(), vmax=pivot_df.values.max())

    # Plot the heatmap
    plt.figure(figsize=(10, 6))
    ax = sns.heatmap(pivot_df_even, cmap=custom_cmap, norm=norm, annot=True, linewidths=0.5 ,fmt='.0f', cbar=True)

    # calculate shift in staircase
    shift = 0 
    _, A, _ = colored_homfly_vectors_and_quiver(u, v)
    #if A.count(min(A)) == 1:
    #    shift = qsub - 2

    # plot staircase
    ax.plot([0,1+shift], [0,0], color='black', linewidth=2.5)
    for row_idx in range(len(pivot_df_even)):
        ax.plot([row_idx+1+shift, row_idx+2+shift], [row_idx, row_idx], color='black', linewidth=2.5)
        ax.plot([row_idx+2+shift, row_idx+2+shift], [row_idx, row_idx+1], color='black', linewidth=2.5)

    plt.title("Polynomial Coefficients Heatmap")
    plt.xlabel("Degree (q^d)")
    plt.ylabel("jth Colored Jones Polynomial")
    dir_path = Path("tails")
    dir_path.mkdir(parents=True, exist_ok=True)
    if tail:
        plt.savefig(f"tails/K_{u}_{v}_{qnum}_{qdenom}")
    else:
        plt.savefig(f"tails/H_{u}_{v}_{qnum}_{qdenom}")

Quiver Table Generator

create_A_table(n, path='a_table')

Writes to text file A vectors of rational knots up to numerator n.

Source code in visualizers\quiver_tables.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def create_A_table(n, path="a_table"):
    """Writes to text file A vectors of rational knots up to numerator n."""
    with open(f"{path}.txt", 'w') as f:
        for i in range(1,n+1,2):
            for j in range(1,i):
                try:                   
                    S, A, Q = colored_homfly_vectors_and_quiver(i, j)
                    A = np.array(A)
                    f.write(f"A vector for K_{i}/{j}\n")
                    np.savetxt(f, [A], fmt="%3d", delimiter=" ")
                    f.write("\n")

                except:
                    pass    

create_S_table(n, path='s_table')

Writes to text file S vectors of rational knots up to numerator n.

Source code in visualizers\quiver_tables.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def create_S_table(n, path="s_table"):
    """Writes to text file S vectors of rational knots up to numerator n."""
    with open(f"{path}.txt", 'w') as f:
        for i in range(1,n+1,2):
            for j in range(1,i):
                try:                   
                    S, A, Q = colored_homfly_vectors_and_quiver(i, j)
                    S = np.array(S)
                    f.write(f"S vector for K_{i}/{j}\n")
                    np.savetxt(f, [S], fmt="%3d", delimiter=" ")
                    f.write("\n")

                except:
                    pass  

create_diag_table(n, path='diag_table')

Writes to text file all quiver diagonals of rational knots up to numerator n.

Source code in visualizers\quiver_tables.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def create_diag_table(n, path="diag_table"):
    """Writes to text file all quiver diagonals of rational knots up to numerator n."""
    with open(f"{path}.txt", 'w') as f:
        for i in range(1,n+1,2):
            for j in range(1,i):
                try:                   
                    S, A, Q = colored_homfly_vectors_and_quiver(i, j)
                    Q_numpy = np.diag(np.array(Q.tolist(), dtype=int))  
                    f.write(f"Q diagonal vector for K_{i}/{j}\n")
                    np.savetxt(f, [Q_numpy], fmt="%3d", delimiter=" ")
                    f.write("\n")

                except:
                    pass   

create_diff_table(n, path='diff_table')

Writes to text file all difference quivers of rational knots up to numerator n.

Source code in visualizers\quiver_tables.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def create_diff_table(n, path="diff_table"):
    """Writes to text file all difference quivers of rational knots up to numerator n."""
    with open(f"{path}.txt", 'w') as f:
        for i in range(1,n+1,2):
            for j in range(1,i):
                try:                   
                    Q = diff_quiv(i,j)
                    Q_numpy = np.array(Q.tolist(), dtype=int)
                    f.write(f"D matrix for K_{i}/{j}\n")
                    np.savetxt(f, Q_numpy, fmt="%3d", delimiter=" ")
                    f.write("\n")

                except:
                    pass   

create_head_table(n, path='head_table')

Writes to text file all head subquivers of rational knots up to numerator n.

Source code in visualizers\quiver_tables.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def create_head_table(n, path="head_table"):
    """Writes to text file all head subquivers of rational knots up to numerator n."""
    with open(f"{path}.txt", 'w') as f:
        for i in range(1,n+1,2):
            for j in range(1,i):
                try:                   
                    S, A, Q = colored_homfly_head_vectors_and_quiver(i,j)
                    S = np.array(S)
                    A = np.array(A)
                    Q_numpy = np.array(Q.tolist(), dtype=int)
                    f.write(f"Head subquiver data for K_{i}/{j}\n")
                    f.write("S:\n")
                    np.savetxt(f, [S], fmt="%3d", delimiter=" ")
                    f.write("\nA:\n")
                    np.savetxt(f, [A], fmt="%3d", delimiter=" ")
                    f.write("\nQ:\n")
                    np.savetxt(f, Q_numpy, fmt="%3d", delimiter=" ")
                    f.write("\n")

                except:
                    pass   

create_quiver_table(n, path='table')

Writes to text file all quivers of rational knots up to numerator n.

Source code in visualizers\quiver_tables.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def create_quiver_table(n, path="table"):
    """Writes to text file all quivers of rational knots up to numerator n."""
    with open(f"{path}.txt", 'w') as f:
        for i in range(1,n+1,2):
            for j in range(1,i):
                try:                   
                    S, A, Q = colored_homfly_vectors_and_quiver(i,j)
                    Q_numpy = np.array(Q.tolist(), dtype=int) 
                    f.write(f"Quiver for K_{i}/{j}\n")
                    np.savetxt(f, Q_numpy, fmt="%3d", delimiter=" ")
                    f.write("\n")
                except:
                    pass

create_tail_table(n, path='tail_table')

Writes to text file all tail subquivers of rational knots up to numerator n.

Source code in visualizers\quiver_tables.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def create_tail_table(n, path="tail_table"):
    """Writes to text file all tail subquivers of rational knots up to numerator n."""
    with open(f"{path}.txt", 'w') as f:
        for i in range(1,n+1,2):
            for j in range(1,i):
                try:                   
                    S, A, Q = colored_homfly_tail_vectors_and_quiver(i,j)
                    S = np.array(S)
                    A = np.array(A)
                    Q_numpy = np.array(Q.tolist(), dtype=int)
                    f.write(f"Tail subquiver data for K_{i}/{j}\n")
                    f.write("S:\n")
                    np.savetxt(f, [S], fmt="%3d", delimiter=" ")
                    f.write("\nA:\n")
                    np.savetxt(f, [A], fmt="%3d", delimiter=" ")
                    f.write("\nQ:\n")
                    np.savetxt(f, Q_numpy, fmt="%3d", delimiter=" ")
                    f.write("\n")

                except:
                    pass   

Homfly Polynomial Lattice

visualize_lattice(polynomial, path=None, show_plot=True, log_scale=True, edgecolor=None, thick_dots=False, cols='viridis')

Visualizes the colored Homfly polynomial of some rational knot

Source code in visualizers\visualize.py
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
def visualize_lattice(polynomial, path=None, show_plot=True, log_scale=True, edgecolor=None, thick_dots = False, cols = 'viridis'):
    """Visualizes the colored Homfly polynomial of some rational knot"""
    p_dict = polynomial.as_dict()
    keys = p_dict.keys()
    x, y = zip(*keys)
    x = np.array(x)
    y = np.array(y)
    z = p_dict.values()
    sizes = [float(np.clip(abs(c), 5, 35)) for c in z] if thick_dots else 10
    norm = mcolors.SymLogNorm(linthresh=1.0, linscale=1.0, vmin=min(z), vmax=max(z))
    if log_scale:
        if cols in ["black", "red", "blue", "green"]:
            scatter = plt.scatter(y, x, s = sizes, color=cols, norm=norm, edgecolor=edgecolor)
        else:
            scatter = plt.scatter(y, x, marker='o', c=z, s =10, cmap=cols, norm=norm, edgecolor=edgecolor)
            colorbar = plt.colorbar(scatter)
            colorbar.set_label('coefficient of monomial (log scale)')
    else:
        if cols in ["black", "red", "blue", "green"]:
            scatter = plt.scatter(y, x, s = sizes, color=cols, edgecolor=edgecolor)
        else:
            scatter = plt.scatter(y, x, marker='s', c=z, s = 600, cmap=cols, norm=mcolors.CenteredNorm(vcenter=0), edgecolor=edgecolor)
            colorbar = plt.colorbar(scatter)
            colorbar.set_label('coefficient of monomial')
    plt.xlabel('q powers')
    plt.ylabel('a powers')

    plt.title('Lattice points of Homfly Polynomial')
    if path is not None:
        plt.savefig(f"{path}.png", format="png")
        print(f"plot saved to {path}.png")
    if show_plot:
        plt.show()

visualize_lattice_colormap(polynomial, path=None, show_plot=True)

Visualizes the colored Homfly polynomial of some rational knot

Source code in visualizers\visualize.py
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def visualize_lattice_colormap(polynomial, path=None, show_plot=True):
    """Visualizes the colored Homfly polynomial of some rational knot"""
    coeff_dict = polynomial.as_dict()
    keys = coeff_dict.keys()
    x, y = zip(*keys)
    z = coeff_dict.values()
    sizes = 10
    #norm = colors.SymLogNorm(linthresh=1.0, linscale=1.0, vmin=min(z), vmax=max(z))
    norm = HybridNormalize(vmin=min(coeff_dict.values()), vmax=max(coeff_dict.values()))

    scatter = plt.scatter(y, x, c=z, s = sizes, cmap=custom_cmap, norm=norm)
    colorbar = plt.colorbar(scatter)
    colorbar.set_label('coefficient of monomial')


    plt.xlabel('q powers')
    plt.ylabel('a powers')

    plt.title('Lattice points of Homfly Polynomial')
    if path is not None:
        plt.savefig(f"{path}.svg", format="svg")
        print(f"plot saved to {path}.svg")
    if show_plot:
        plt.show()

Homfly Tail Visualizer

homfly_plots(u, v, trunc=100, dir='se')

Creates subplots for the first 10 colored Homfly tails of a knot.

Source code in visualizers\homfly_tail.py
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
def homfly_plots(u, v, trunc=100, dir="se"):
    """Creates subplots for the first 10 colored Homfly tails of a knot."""
    xs, ys, zs = homfly_tail(u, v, 10, trunc, dir)

    fig, ax = plt.subplots(2, 5, figsize=(20,12), sharex=True, sharey=True)

    num_plots = 10 

    all_z = [val for frame in zs for val in frame]
    global_min_z = min(all_z)
    global_max_z = max(all_z)

    all_unique_x = sorted(list(set([val for frame in xs for val in frame])))
    all_unique_y = sorted(list(set([val for frame in ys for val in frame])))

    for j in range(2):
        for frame_idx in range(5):
            x = xs[frame_idx + j * 5]
            y = ys[frame_idx + j * 5]
            z = zs[frame_idx + j * 5]

            df = pd.DataFrame({'x': x, 'y': y, 'z': z})
            matrix = df.pivot(index='x', columns='y', values='z')
            matrix = matrix.reindex(index=all_unique_x, columns=all_unique_y).fillna(0).astype(int)
            matrix = matrix.iloc[::-1]

            # Get the matrix for the current frame
            sns.heatmap(
                matrix, 
                ax=ax[j, frame_idx], 
                cmap='seismic', 
                vmin=global_min_z, 
                vmax=global_max_z, 
                cbar=False,
                center=0,
                annot=True, # Optional: Set to True if you want to see the 0s written out
                fmt="d"   # Text formatting for annotations
            )

            ax[j, frame_idx].set_title(f"j={j * 5 + frame_idx + 1}", fontweight="bold")
            ax[j, frame_idx].set_xlabel("")
            ax[j, frame_idx].set_ylabel("")

    mappable = plt.cm.ScalarMappable(
        norm=mcolors.Normalize(vmin=global_min_z, vmax=global_max_z), 
        cmap='seismic'
    )

        # --- ADD OUTLINES TO EACH SUBPLOT ---
    # If your axes array is 2D (e.g. 2x2 grid), use axes.flat to loop through all of them
    for axi in ax.flat:
        # 1. Force the spine borders to be visible (Seaborn heatmaps sometimes turn them off)
        for spine in axi.spines.values():
            spine.set_visible(True)
            spine.set_color('black')       # Set your outline color
            spine.set_linewidth(3.0)       # Set thickness of the outline

        # Optional: If you want a small gap between the heatmap and the border line
        # ax.set_frame_on(True)
    fig.supxlabel("q powers", fontweight="bold")
    fig.supylabel("a powers", fontweight="bold")
    fig.suptitle(f"Heatmaps for K_{u}/{v}", fontsize=16, fontweight='bold')
    # Passing ax=axes.tolist() spans the colorbar nicely across both subplots
    cbar = fig.colorbar(mappable, ax=ax, orientation='vertical', fraction=0.046, pad=0.04)
    cbar.set_label('Coefficient Value')


    output_path = f"homfly_tail_lattices/heatmap_subplots_{dir}_{u}_{v}.png"
    plt.savefig(output_path)

    print(f"Heatmap plots successfully saved to {output_path}")
    plt.close()

homfly_tail(u, v, j, trunc=10, dir='se')

Computes the first j colored Homfly polynomials of the rational knot \(K_{u/v}\).

Source code in visualizers\homfly_tail.py
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
def homfly_tail(u, v, j, trunc=10, dir="se"):
    r"""Computes the first j colored Homfly polynomials of the rational knot $K_{u/v}$."""
    if dir not in {"se", "sw", "nw", "ne"}:
        raise ValueError("direction is se, sw, nw, ne")
    S, A, Q = colored_homfly_vectors_and_quiver(u,v)
    Q_numpy = np.array(Q.tolist(), dtype=int)       
    polies = []
    xs, ys, zs = [], [], []

    for color in range(1,j+1):
        quiv_dict = evaluate_quiver_knot_dict(Q_numpy, S, A, u, v, color)
        sign = 1
        if dir == "se":
            min_qp = max(quiv_dict, key=lambda k: k[1]) # get key with highest q power
            min_ap = min(quiv_dict, key=lambda k: k[0]) # get key with smallest a power
        elif dir == "nw":
            min_qp = min(quiv_dict, key=lambda k: k[1]) # get key with smallest q power
            min_ap = max(quiv_dict, key=lambda k: k[0]) # get key with highest a power
        elif dir == "sw":
            min_qp = min(quiv_dict, key=lambda k: k[1]) # get key with smallest q power
            min_ap = min(quiv_dict, key=lambda k: k[0]) # get key with highest a power
        else:
            min_qp = max(quiv_dict, key=lambda k: k[1]) # get key with smallest q power
            min_ap = max(quiv_dict, key=lambda k: k[0]) # get key with highest a power
        sign = 1
        if quiv_dict[min_ap] < 0:
            sign = -1
        normalized = {
            (int(k[0] - min_ap[0]), int(k[1] - min_qp[1])): int(sign * val)
            for k, val in quiv_dict.items()
        }
        if dir == "se":
            sorted_items = sorted(normalized.items(), key=lambda item: (-item[0][1], item[0][0]))
        elif dir == "nw":
            sorted_items = sorted(normalized.items(), key=lambda item: (item[0][1], item[0][0]))
        elif dir == "sw":
            sorted_items = sorted(normalized.items(), key=lambda item: (item[0][1], -item[0][0]))
        else:
            sorted_items = sorted(normalized.items(), key=lambda item: (-item[0][1], -item[0][0]))
        trunced = dict(sorted_items[:trunc])
        polies.append(trunced)
        x, y = zip(*trunced)
        xs.append(x)
        ys.append(y)
        zs.append(list(trunced.values()))

    return xs, ys, zs

homfly_tail_animation(u, v, j, trunc)

Creates an animation for the tail of the colored Homfly Polynomial.

Source code in visualizers\homfly_tail.py
 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
def homfly_tail_animation(u, v, j, trunc):
    """Creates an animation for the tail of the colored Homfly Polynomial."""
    ys, xs, zs = homfly_tail(u, v, j, trunc)
    num_frames = len(xs)
    all_z = [val for frame in zs for val in frame]
    global_min_z = min(all_z)
    global_max_z = max(all_z)

    all_x = [val for frame in xs for val in frame]
    all_y = [val for frame in ys for val in frame]

    shared_norm = mcolors.SymLogNorm(linthresh=1.0, linscale=1.0, vmin=global_min_z, vmax=global_max_z)

    # --- 3. MATPLOTLIB CANVAS SETUP ---
    fig, ax = plt.subplots(figsize=(7, 6))

    # Lock limits to global min/max so the frame window doesn't vibrate/shake
    ax.set_xlim(min(xs[-1]) - 0.5, max(xs[-1]) + 0.5)
    ax.set_ylim(min(ys[-1]) - 0.5, max(ys[-1]) + 0.5)

    ax.set_xlabel('q powers')
    ax.set_ylabel('a powers')
    title = ax.set_title('j=1')

    scatter = ax.scatter([], [], c=[], cmap='viridis', norm=shared_norm, s=80, edgecolor=None)
    fig.colorbar(scatter, ax=ax, label='Coefficient Intensity')

    def update(frame_idx):
        # Get the x, y, and z lists for the current frame
        x = xs[frame_idx]
        y = ys[frame_idx]
        z = zs[frame_idx]

        # 1. Update the coordinates (requires an Nx2 numpy array shape)
        positions = np.column_stack((x, y))
        scatter.set_offsets(positions)

        # 2. Update the color mapping values
        z_numeric = np.array(z, dtype=float)
        scatter.set_array(z)

        # 3. Update the title text dynamically
        title.set_text(f'j={frame_idx+1}')

        return scatter, title
    ani = animation.FuncAnimation(fig, update, frames=num_frames, interval=500, blit=False)

    output_path = f"homfly_tail_lattices/lattice_evolution_{u}_{v}.mp4"
    ani.save(output_path, writer='ffmpeg')

    print(f"Successfully generated and saved {output_path}")
    plt.close()
    return True

homfly_tail_heatmap(u, v, j, trunc, dir='se')

Creates an animation for the tail of the colored Homfly polynomial visualized on a heatmap.

Source code in visualizers\homfly_tail.py
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
def homfly_tail_heatmap(u, v, j, trunc, dir="se"):
    """Creates an animation for the tail of the colored Homfly polynomial visualized on a heatmap."""
    xs, ys, zs = homfly_tail(u, v, j, trunc, dir)

    num_frames = j

    all_z = [val for frame in zs for val in frame]
    global_min_z = min(all_z)
    global_max_z = max(all_z)

    all_unique_x = sorted(list(set([val for frame in xs for val in frame])))
    all_unique_y = sorted(list(set([val for frame in ys for val in frame])))
    min_x = min(all_unique_y)
    max_a = max(all_unique_x)
    q_crop = min(ys[-1])
    cropped_q = [val for val in all_unique_y if q_crop <= val]

    custom_norm = HybridNormalize(vmin=global_min_z, vmax=global_max_z)

    fig, ax = plt.subplots(figsize=(15, 5))

    def update(frame_idx):
        # Clear the previous frame's heatmap and colorbar axis
        ax.clear()
        fdx = frame_idx + 1
        x = xs[frame_idx]
        y = ys[frame_idx]
        z = zs[frame_idx]

        df = pd.DataFrame({'x': x, 'y': y, 'z': z})
        matrix = df.pivot(index='x', columns='y', values='z')
        matrix = matrix.reindex(index=all_unique_x, columns=cropped_q).fillna(0).astype(int)
        matrix = matrix.iloc[::-1]

        # Get the matrix for the current frame
        sns.heatmap(
            matrix, 
            ax=ax, 
            cmap=custom_cmap, 
            norm=custom_norm,
            vmin=global_min_z, 
            vmax=global_max_z, 
            cbar=False,
            annot=True, # Optional: Set to True if you want to see the 0s written out
            fmt="d"   # Text formatting for annotations
        )

        if dir == "se":
            # plot stable zone
            line_color = 'black'
            _, A, _ = colored_homfly_vectors_and_quiver(u, v)
            if v == 1:
                shift = q_crop // 2 - 1
                yshift = max_a // 2 + 1
                ax.plot([-fdx - shift - 1,-shift], [yshift,yshift], color=line_color, linewidth=2.5)
                ax.plot([-fdx - shift - 1,-fdx - shift - 1], [yshift,yshift - 1], color=line_color, linewidth=2.5)
                ax.plot([-fdx - shift - 1,-fdx - shift], [yshift - 1,yshift - 1], color=line_color, linewidth=2.5)
                ax.plot([-fdx - shift,-fdx - shift], [yshift - 1,yshift - 2], color=line_color, linewidth=2.5)
                for i in range(2, yshift):
                    xl = -(fdx * i) - ((i)**2 + (i)) // 2 + 1 - shift + (i-1)
                    xr = -(fdx * (i-1)) - ((i-1)**2 + (i-1)) // 2 + 1 - shift + (i - 2)
                    ax.plot([xl,xr], [yshift - i,yshift - i], color=line_color, linewidth=2.5)
                    ax.plot([xl,xl], [yshift - i,yshift - i-1], color=line_color, linewidth=2.5)         
                    pass
            elif v % 2 == 0:
                shift = q_crop // 2 - 1
                yshift = max_a // 2 + 1
                if A.count(min(A)) == 1:
                    ax.plot([0,- shift], [yshift,yshift], color=line_color, linewidth=2.5)
                    #ax.plot([0,0], [yshift, yshift - 1], color=line_color, linewidth=2.5)
                    ax.plot([0,- fdx - shift], [yshift - 1,yshift - 1], color=line_color, linewidth=2.5)
                    #ax.plot([-fdx - shift,-fdx - shift], [yshift - 1,yshift - 2], color=line_color, linewidth=2.5)
                else:
                    ax.plot([-fdx - 2 - shift,- shift], [yshift,yshift], color=line_color, linewidth=2.5)
                    ax.plot([-fdx-2-shift,-fdx-2-shift], [yshift, yshift - 1], color=line_color, linewidth=2.5)
                    ax.plot([-fdx-2-shift,- fdx - shift], [yshift - 1,yshift - 1], color=line_color, linewidth=2.5)
                ax.plot([-fdx - shift,-fdx - shift], [yshift - 1,yshift - 2], color=line_color, linewidth=2.5)                
                ax.plot([-fdx- shift,-fdx+1 - shift], [yshift - 2,yshift - 2], color=line_color, linewidth=2.5)
                ax.plot([-fdx+1 - shift,-fdx+1- shift], [yshift - 2,yshift - 4], color=line_color, linewidth=2.5)
                for i in range(4, yshift):
                    xl = -fdx - ((i-3)**2 + (i-3)) // 2 + 1 - shift
                    xr = -fdx - ((i-4)**2 + (i-4)) // 2 + 1 - shift
                    ax.plot([xl,xr], [yshift - i,yshift - i], color=line_color, linewidth=2.5)
                    ax.plot([xl,xl], [yshift - i,yshift - i-1], color=line_color, linewidth=2.5)
                #ax.plot([-fdx - ((yshift - 4)**2 + (yshift - 4)) // 2 + 1 - shift, - shift],[0,0], color=line_color, linewidth=2.5)
            elif v % 2 == 1:
                shift = q_crop // 2 - 1
                yshift = max_a // 2 + 1
                ax.plot([-fdx - shift - 1,-shift], [yshift,yshift], color=line_color, linewidth=2.5)
                ax.plot([-fdx - shift - 1,-fdx - shift - 1], [yshift,yshift - 1], color=line_color, linewidth=2.5)
                ax.plot([-fdx - shift - 1,-fdx - shift], [yshift - 1,yshift - 1], color=line_color, linewidth=2.5)
                ax.plot([-fdx - shift,-fdx - shift], [yshift - 1,yshift - 3], color=line_color, linewidth=2.5)
                for i in range(3, yshift):
                    xl = -fdx - ((i-2)**2 + (i-2) + 1) + 1 - shift
                    xr = -fdx - ((i-3)**2 + (i-3) + 1) + 1 - shift
                    ax.plot([xl,xr], [yshift - i,yshift - i], color=line_color, linewidth=2.5)
                    ax.plot([xl,xl], [yshift - i,yshift - i-1], color=line_color, linewidth=2.5)
        ax.set_title(f"Lattice Density (j = {frame_idx + 1})")
        ax.set_xlabel("q powers")
        ax.set_ylabel("a powers")

    mappable = plt.cm.ScalarMappable(
        norm=custom_norm,
        cmap=custom_cmap
    )
    fig.colorbar(mappable, ax=ax, label='Coefficient Intensity')

    ani = animation.FuncAnimation(fig, update, frames=num_frames, interval=200, blit=False)

    output_path = f"homfly_tail_lattices/heatmap_evolution_{dir}_{u}_{v}.mp4"
    ani.save(output_path, writer='ffmpeg')

    print(f"Heatmap video successfully saved to {output_path}")
    plt.close()

plot_heatmap(coeff_dict, title='Coefficient heatmap', save_path=None)

coeff_dict: {(a_power, q_power): coefficient} Rows = a_power, Columns = q_power.

Source code in visualizers\homfly_head_torus.py
 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
def plot_heatmap(coeff_dict, title="Coefficient heatmap", save_path=None):
    """
    coeff_dict: {(a_power, q_power): coefficient}
    Rows = a_power, Columns = q_power.
    """
    max_q = max(k[1] for k in coeff_dict.keys())
    coeff_dict = {k: v for k,v in coeff_dict.items() if k[1] != max_q}
    a_powers = sorted(set(k[0] for k in coeff_dict))
    q_powers = sorted(set(k[1] for k in coeff_dict))

    a_index = {v: i for i, v in enumerate(a_powers)}
    q_index = {v: i for i, v in enumerate(q_powers)}

    grid = np.full((len(a_powers), len(q_powers)), np.nan)
    for (ap, qp), coeff in coeff_dict.items():
        grid[a_index[ap], q_index[qp]] = float(coeff)

    fig, ax = plt.subplots(figsize=(max(8, len(q_powers)*0.5),
                                     max(6, len(a_powers)*0.4)))

    norm = HybridNormalize(vmin=min(coeff_dict.values()), vmax=max(coeff_dict.values()))

    vmax = np.nanmax(np.abs(grid))
    im = ax.imshow(grid, cmap=custom_cmap, norm=norm, aspect="auto", origin="upper")

    ax.set_xticks(range(len(q_powers)))
    ax.set_xticklabels(q_powers)
    ax.set_yticks(range(len(a_powers)))
    ax.set_yticklabels(a_powers)
    ax.set_xlabel("power of q")
    ax.set_ylabel("power of a")
    ax.invert_yaxis()
    ax.set_title(title)

    for i in range(len(a_powers)):
        for j in range(len(q_powers)):
            val = grid[i, j]
            if not np.isnan(val):
                ax.text(j, i, f"{int(val)}", ha="center", va="center",
                         fontsize=7,
                         color="white" if abs(val) > vmax*0.6 else "black")

    fig.colorbar(im, ax=ax, label="coefficient")
    fig.tight_layout()

    if save_path:
        fig.savefig(save_path, dpi=150)
    return fig