A Python module to calculate cobordism maps induced on Khovanov homology.
Written by Zsombor Fehér, 2024.
Example: Distinguishing two ribbon disks of 61, given as two band diagrams on the same link diagram.
-
Make sure you have SciPy (and NumPy) installed and updated to the latest version:
pip install --upgrade scipy
(Some methods require Sage to be installed, but tasks like this example do not.)
-
Start a Python session, copy the file khovanov.py to your working folder, and import that module:
from khovanov import *
-
Obtain a PD code of the link, and determine how the PD code corresponds to the diagram.
Label each crossing in your diagram with its corresponding index in the PD code. Mark the 0th strand of each crossing with a dot, corresponding to the 0th element of its PD code entry. (Since this module does not have a graphical interface yet, you might find it useful to draw the link in SnapPy's Plink Editor, and enable Info → DT labels and Info → PD code for getting this correspondence.)
-
Create a
Linkobject using the PD code:L = Link([(9, 4, 10, 5), (5, 8, 6, 9), (11, 2, 12, 3), (3, 10, 4, 11), (1, 7, 2, 6), (7, 1, 8, 12)])
You can verify that your diagram is marked correctly using
print(L). -
Create the two
Cobordismobjects based on the band diagrams and your marking of crossings.S0 = Cobordism(L) S0.band_move(-1, (0, 0), (2, 1)) S0.finish() S1 = Cobordism(L) S1.band_move(-1, (1, 2), (3, 3)) S1.finish()
For example,
S1.band_move(-1, (1, 2), (3, 3))represents a (-1)-twisted band, connecting the 1st crossing's 2nd strand to the 3rd crossing's 3rd strand, positioned on the band's left side.You can check the resulting movie of the cobordism with
print(S0). -
Calculate the Khovanov-Jacobsson classes (
CKhElementobjects) and compare them in homology:compare(S0.KJ_class(), S1.KJ_class())
-
If this fails to distinguish the surfaces, try mirroring the cobordisms:
compare(S0.mirror().KJ_class(), S1.mirror().KJ_class())
We provide a brief overview of the program’s classes, highlighting key properties and methods that might be useful to users. Further details and documentation can be found in the source code.
The program builds on SnapPy's Link class, with modifications. This class represents a link diagram combinatorially as a graph in which each vertex has degree 4 or 2.
A Crossing represents a crossing in the link diagram, modelled as a degree-4 vertex. The strands are labelled 0, 1, 2, and 3 in positive orientation around the vertex, with 0 and 2 corresponding to the understrand. Its properties:
adjacentspecifies the connection for each strand.labelis used to identify a crossing within aLink(usually an integer).
For example, a = Crossing(7) creates a Crossing object with label 7, and connecting the strand (a, 3) to (b, 0) can be achieved by writing a[3] = b[0].
A Strand is similar to a Crossing, but represents a degree-2 vertex. While SnapPy automatically fuses these and removes unknotted unlinked components, we retain them as they are essential for representing cobordism movies.
A Link represents a link diagram with an enumeration of its crossings. Its property:
crossingsprovides a list ofCrossingandStrandobjects that form the vertices of the graph.
Links can be created using PD codes, braid closures, or a list of Crossing and Strand objects, e.g. L = Link([a, b, c]). Its methods:
-
L.orient(cs0, cs1, ...): if the link$L$ has multiple components, it may be necessary to define its orientation to set itsn_plusandn_minusproperties (representing the number of positive and negative crossings). -
L.differential_matrix(h, q)returns a matrix of the differential map$d$ in grading$(h,q)\to (h+1,q)$ . -
L.homology_with_generators(h, q)calculates the Khovanov homology$\mathrm{Kh}^{h,q}(L)$ , expressing it as a direct sum of cyclic groups and returning chain representatives for the generators of each group (this method needs Sage).
For computing Khovanov homology without generators, significantly faster programs exist, such as KnotJob, which implements Bar-Natan's efficient algorithm.
A CrossingStrand represents one of the four strands around a Crossing (or one of the two strands around a Strand). Its properties:
crossingreferences the associatedCrossingorStrandobject.strand_indexindicates the strand's position, e.g. 0, 1, 2, or 3.
For example, cs = CrossingStrand(a, 2) sets cs.crossing to the object a, and cs.strand_index to 2.
CrossingStrand objects are crucial for defining cobordisms, as they allow precise specification of strands involved in an elementary move. Several methods are available to navigate within a Link:
cs + 1,cs.next(), andcs - 1give the adjacent strands around the same crossing.cs.opposite()returns the strand located at the opposite end of the edge connected tocs.
It is important to understand that while cs and cs.opposite() lie on the same edge of the underlying graph of the link diagram, they represent distinct portions of the edge. Care must be taken to use the appropriate one when specifying strands for defining an elementary move.
A SmoothLink represents a smoothing of a link diagram. Its properties:
linkrefers to the underlyingLink.smoothingis a dictionary mapping eachCrossingto 0 or 1.loopsis a list of loops, where each loop is represented as a tuple ofCrossingStrandobjects (two at each crossing) traversed along the loop.
A LabelledSmoothing represents a labelled smoothing with a coefficient. Its properties:
smooth_linkrefers to the underlyingSmoothLink.labelsis a dictionary mapping each loop to"1"or"x".coefficientis an integer that will represent the coefficient in a linear combination.
There are several methods for examining the relationship between a LabelledSmoothing LS and the image of the differential map (without computing the entire differential matrix):
LS.differential()returns its differential as a list ofLabelledSmoothingobjects, interpreted as a linear combination.LS.is_outside()checks if every 1-smoothed crossing connects two distinct 1-labelled loops. If so, thenLSlies outside the image of the differential.LS.row_size()andLS.reverse_differential()give how many and which elements containLSin their differential.
A CKhElement represents a chain element of LabelledSmoothing objects, interpreted as a linear combination using their coefficient properties.
Each of the elementary moves can be applied to a CKhElement CKH by calling the corresponding method and specifying the location with appropriate CrossingStrand objects (see the source code for more details and pictures):
CKH.morse_birth()adds a new loop.CKH.morse_death(cs)removes a loop containingcs.CKH.morse_saddle(cs0, cs1)adds a saddle move to connectcs0withcs1.CKH.reidemeister_1(cs)removes a twist containingcs.CKH.reidemeister_1_up(cs, True)adds a positive twist to the left ofcs.CKH.reidemeister_2(cs0, cs1)removes the common crossing ofcs0andcs1, and the common crossing ofcs0.opposite()andcs1.opposite().CKH.reidemeister_2_up(cs0, cs1)movescs0to the right and overcs1.CKH.reidemeister_3(cs0, cs1, cs2)movescs0overcs1,cs2, and the common crossing ofcs1.opposite()andcs2.opposite().
These change the underlying LabelledSmoothing, SmoothLink, and Link objects as well by the elementary move. Further methods:
CKH.fuse(strand)fuses aStrand, combining the two edges from it.CKH.differential()returns the differential.CKH.reorder_crossings(new_order)reorders the crossings.CKH.replace_link(link, flipping)might be useful ifCKHis on a different copy of the same link.CKH.simplify()simplifies the linear combination by combining equal terms and removing 0-coefficient elements.CKH0 + CKH1andCKH0 - CKH1calculate the sum and difference of two chain elements and simplify the result.compare(CKH0, CKH1)returns whether two chain elements are the same up to sign in homology.
A Cobordism represents a movie of a cobordism between two links. Its properties:
moviecontains a list of elementary moves and their locations.linksgives the list of links corresponding to each stage of the movie.
A new cobordism C = Cobordism(L), and moves can be added by subsequent calls of methods. Crossing labels can also be used in arguments instead of CrossingStrand objects, e.g. C.morse_death((7, 1)). In addition to the above methods of CKhElement, there are two convenient methods for adding multiple elementary moves at once: band_move(...) and finish().
-
C.band_move(n, cs0, (cs1, True), (cs2, False), ..., csk)adds a band with$n$ half-twists, starting atcs0, passing overcs1, undercs2, and so on, before ending atcsk. -
C.finish()tries to simplify the last link using Reidemeister 1–3 moves (similar to SnapPy'ssimplify("level")algorithm), then does a Morse death on each crossingless component. -
C.reverse()returns the reversal of the cobordism. -
C.mirror()returns the mirror of the cobordism. -
C.chi()returns the Euler characteristic of the cobordism. -
C.map(CKH)applies the map induced by the cobordism toCKHand changes it in place. -
C.KJ_class()returns the Khovanov–Jacobsson class ofCorC.reverse(), if one of the ends ofCis the empty link. -
C.matrix(h, q)calculates the cobordism map induced on homology in grading$(h,q)\to (h,q+\chi(C))$ in matrix form (this method needs Sage).
All classes support the standard print(...) method to display information about an object. For instance, print(L) outputs the adjacency structure of a Link, print(C) displays the sequence of moves in a Cobordism movie, and print(CKH) represents a CKhElement by showing the labels of each loop in every element of the linear combination. Additionally, CKH.print_short() provides a concise representation that omits printing the loops.
Link class based on SnapPy's implementation by Nathan Dunfield.
Additional ideas by Alan Du and Gary Dunkerley.
