Links: planar diagrams and invariants¶
Tutorial¶
SnapPy includes the Spherogram module which allows one to create links programmatically. The graphical conventions used are summarized here.
First, here is the figure8 knot assembled manually from four crossings, with conventions similar to those used by KnotTheory:
>>> a, b, c, d = [Crossing(x) for x in 'abcd']
>>> a[0], a[1], a[2], a[3] = c[1], d[0], b[1], b[0]
>>> b[2], b[3] = d[3], c[2]
>>> c[3], c[0] = d[2], d[1]
>>> L = Link([a,b,c,d])
>>> E = L.exterior()
>>> E.volume()
2.029883212819
>>> Manifold('4_1').is_isometric_to(E)
True
We can also give the same knot as a rational tangle:
>>> L = RationalTangle(3,5).denominator_closure()
>>> L.PD_code()
[[6, 3, 7, 4], [4, 2, 5, 1], [0, 6, 1, 5], [2, 7, 3, 0]]
>>> L.DT_code(True)
'DT[dadCDAB]'
The natural algebra of tangles shown here all works. For instance, we can build the (2, 3, 7) pretzel knot by adding together three rational tangles:
>>> T = RationalTangle(1, 2) + RationalTangle(1, 3) + RationalTangle(1, 7)
>>> L = T.numerator_closure()
>>> Manifold('m016').is_isometric_to(L.exterior())
True
To create the figure8 knot as a closed braid, we first mash tangles together horizontally using “” to make the standard braid generators; then multiplication in the braid group is just tangle multiplication:
>>> C, Id = RationalTangle(1), IdentityBraid(1)
>>> x = sigma_1 = C  Id
>>> y = sigma_2_inverse = Id  C
>>> L = (x*y*x*y).denominator_closure()
>>> E = L.exterior()
>>> Manifold('4_1').is_isometric_to(E)
True
Here’s the minimallytwisted five chain from Figure 2 of this paper:
def twisted_chain(n, k):
T = RationalTangle(1, 2)
m = (n+1)//2
base = (m*[T, T])[:n]
tangles = base + [RationalTangle(k)]
return sum(tangles, RationalTangle(0) ).bridge_closure()
>>> L = twisted_chain(5, 1)
>>> L.exterior().volume()
10.14941606410
Spherogram includes ways to create very large random links, see below. When used inside Sage, one can compute many basic link invariants, including the Jones polynomial. See the complete list of Link methods below.
Random Links¶

spherogram.
random_link
(crossings, num_components='any', initial_map_gives_link=False, alternating=False, consistent_twist_regions=False, simplify='basic', prime_decomposition=True, return_all_pieces=False, max_tries=100)¶ Generates a random link from a model that starts with a random 4valent planar graph sampled with the uniform distribution by Schaeffer’s PlanarMap program.
The
crossings
argument specifies the number of vertices of the initial planar graph G; the number of crossing in the returned knot will typically be less. The meanings of the optional arguments are as follows:num_components
: The number of components of the returned link. The link naively associated to G may have too few or too many components. The former situation is resolved by picking another G, and the latter by either Taking the sublink consisting of the components with the largest selfcrossing numbers.
 Resampling G until the desired number of components is achieved; this can take a very long time as the expected number of components associated to G grows linearly in the number of vertices.
When the argument
initial_map_gives_link
isFalse
the program does (a) and this is the default behavior. If you want (b) set this argument toTrue
.To get the entire link associated to G, set
num_components
to`any`
, which is also the default.The 4valent vertices of G are turned into crossings by flipping a fair coin. If you want the unique alternating diagram associated to G, pass
alternating = True
. If you want there to be no obvious Type II Reidemeister moves, passconsistent_twist_regions = False
.simplify
: Whether and how to try to reduce the number of crossings of the link via Reidemeister moves using the methodLink.simplify
. For no simplification, setsimplify = None
; otherwise setsimplify
to be the appropriate mode forLink.simplify
, for examplebasic
(the default),level
, orglobal
.prime_decomposition
: The initial link generated from G may not be prime (and typically isn’t ifinitial_map_gives_link
isFalse
). When set (the default), the program undoes any connect sums that are “diagrammatic obvious”, simplifies the result, and repeats until pieces are “diagrammatically prime”. Ifreturn_all_pieces
isFalse
(the default) then only the largest (apparently) prime component is returned; otherwise all summands are returned as a list.
Some examples:
>>> K = random_link(25, num_components=1, initial_map_gives_link=True, alternating=True) >>> K <Link: 1 comp; 25 cross>
>>> L= random_link(30, consistent_twist_regions=True, simplify = 'global') >>> type(random_link(30, return_all_pieces=True)) <type 'list'>
The Link class¶

class
spherogram.
Link
(crossings, check_planarity=True, build=True)¶ Links are made from Crossings. The general model is that of the PD diagrams used in KnotTheory.
See the file “doc.pdf” for the conventions, which can be accessed via “spherogram.pdf_docs()”, and the Spherogram tutorial for some examples of creating links.
Here are two ways of creating the figure8 knot, first via a PD code
>>> K1 = Link([[8,3,1,4],[2,6,3,5],[6,2,7,1],[4,7,5,8]])
and by directly gluing up Crossings:
>>> a, b, c, d = [Crossing(x) for x in 'abcd'] >>> a[0], a[1], a[2], a[3] = c[1], d[0], b[1], b[0] >>> b[2], b[3] = d[3], c[2] >>> c[3], c[0] = d[2], d[1] >>> K2 = Link([a,b,c,d])
Some families of named links are available, such a torus knots
>>> Link('T(4, 2)') <Link: 2 comp; 6 cross>
and if you have SnapPy installed also the Rolfsen and HosteThistlethwaite tables:
>>> Link('8_20') <Link 8_20: 1 comp; 8 cross> >>> Link('K12a123') <Link K12a123: 1 comp; 12 cross> >>> Link('L12n123') <Link L12n123: 2 comp; 12 cross>

DT_code
(DT_alpha=False)¶ The DowkerThistlethwaite code for the link in either numerical or alphabetical form.

KLPProjection
()¶

PD_code
(KnotTheory=False, min_strand_index=0)¶ The planar diagram code for the link.

alexander_matrix
(mv=True)¶ Returns the Alexander matrix of the link:
sage: L = Link('3_1') sage: L.alexander_matrix() ([ 1 1/t + 1 1/t] [ 1/t 1 1/t + 1] [1/t + 1 1/t 1], [t, t, t]) sage: L = Link([(4,1,3,2),(1,4,2,3)]) sage: L.alexander_matrix() #doctest: +SKIP ([ t1  1 t2 + 1] [t1 + 1 t2  1], [t2, t1])

alexander_poly
(multivar=True, v='no', method='wirt', norm=True)¶ Calculates the Alexander polynomial of the link. For links with one component, can evaluate the alexander polynomial at v:
sage: K = Link('4_1') sage: K.alexander_poly() t + 1/t  3 sage: K.alexander_poly(v=[4]) 5/4 sage: K = Link('L7n1') sage: K.alexander_poly(norm=False) (t1*t2^3 + 1)/(t1*t2^4)

all_crossings_oriented
()¶

alternating
()¶ Returns the alternating link with the same planar graph. No attempt is made to preserve the order of the link components or ensure that the DT code of the result has all positive entries (as opposed to all negative).
>>> L = Link('L14n12345') >>> A = L.alternating() >>> A.exterior().identify() [L14a5150(0,0)(0,0)]

backtrack
(steps=10)¶ Performs a sequence of Reidemeister moves which increase or maintain the number of crossings in a diagram. The number of such moves is the parameter steps. The diagram is modified in place.
>>> K = Link('L14a7689') >>> K <Link L14a7689: 2 comp; 14 cross> >>> K.backtrack(steps = 50) >>> len(K.crossings) > 20 True

black_graph
()¶ Returns the black graph of K. If the black graph is disconnected (which can only happen for a split link diagram), returns one connected component. The edges are labeled by the crossings they correspond to. Example:
sage: K=Link('5_1') sage: K.black_graph() Subgraph of (): Multigraph on 2 vertices

connected_sum
(other_knot)¶ Returns the connected sum of two knots.
>>> K = Link('4_1') >>> K.connected_sum(K) <Link: 1 comp; 8 cross>

copy
()¶ Returns a copy of the link.
>>> K=Link('4_1') >>> copy=K.copy() >>> K.PD_code() == copy.PD_code() True
If the link is very large (100s of crossings), you may get a recursion limit exception; to get around this, call e.g.
sys.setrecursionlimit(10000)
.

crossing_entries
()¶

crossing_strands
()¶

deconnect_sum
(destroy_original=False)¶ Undoes all connect sums that are diagramatically obvious, i.e. those where there is a circle which meets the projection in two points.
>>> K = Link('5_2') >>> L = K.connected_sum(K); L <Link: 1 comp; 10 cross> >>> L.deconnect_sum() [<Link: 1 comp; 5 cross>, <Link: 1 comp; 5 cross>]

determinant
(method='goeritz')¶ Returns the determinant of the link, a nonnegative integer.
Possible methods are ‘wirt’, using the Wirtinger presentation; ‘goeritz’, using the Goeritz matrix, and ‘color’, using the ‘colorability matrix’, or anything else, to compute the Alexander polynomial at 1. Example:
sage: K = Link( [(4,1,5,2),(6,4,7,3),(8,5,1,6),(2,8,3,7)] ) # Figure 8 knot sage: K.determinant() 5

digraph
()¶ The underlying directed graph for the link diagram.

dual_graph
()¶ The dual graph to a link diagram D, whose vertices correspond to complementary regions (faces) of D and whose edges are dual to the edges of D.

exterior
(with_hyperbolic_stucture=True)¶ The exterior or complement of the link L, that is, S^3 minus L.
>>> K = Link('4_1') >>> M = K.exterior() >>> M.volume() 2.02988321

faces
()¶ The faces are the complementary regions of the link diagram. Each face is given as a list of corners of crossings as one goes around clockwise. These corners are recorded as CrossingStrands, where CrossingStrand(c, j) denotes the corner of the face abutting crossing c between strand j and j + 1.
Alternatively, the sequence of CrossingStrands can be regarded as the heads of the oriented edges of the face.

goeritz_matrix
()¶ Finds the black graph of a knot, and from that, returns the Goeritz matrix of that knot:
sage: K=Link('4_1') sage: K.goeritz_matrix().det() 5

is_planar
()¶ Whether the 4valent graph underlying the link projection is planar. Should always be True for any actual Link.
>>> L = Link('5^2_1') >>> L.is_planar() True >>> c = Crossing() >>> c[0], c[1] = c[2], c[3] # Punctured torus gluing >>> bad = Link([c], check_planarity=False) >>> bad.is_planar() False

jones_poly
(variable=None)¶ Returns the Jones polynomial of the link:
sage: L = Link('8_5') sage: L.jones_poly() q^8  2*q^7 + 3*q^6  4*q^5 + 3*q^4  3*q^3 + 3*q^2  q + 1

knot_group
()¶ Computes the knot group using the Wirtinger presentation. Returns a finitely presented group:
sage: K = Link('3_1') sage: G = K.knot_group() sage: type(G) <class 'sage.groups.finitely_presented.FinitelyPresentedGroup_with_category'>

linking_matrix
()¶ Calcluates the linking number for each pair of link components. Returns a linking matrix, in which the (i,j)th component is the linking number of the ith and jth link components.

linking_number
()¶ Returns the linking number of self if self has two components; or the sum of the linking numbers of all pairs of components in general.

mirror
()¶ Returns the mirror image of the link, preserving link orientations and component order.

morse_diagram
()¶ Returns a MorseLinkDiagram of this link diagram, that is a choice of height function which realizes the Morse number:
sage: L = Link('L8n2') sage: D = L.morse_diagram() sage: D.morse_number == L.morse_number() True sage: D.is_bridge() True sage: B = D.bridge() sage: len(B.bohua_code()) 64

morse_number
(solver='GLPK')¶ The Morse number of a planar link diagram D is
m(D) = min { # of maxima of h on D }where h is a height function on R^2 which is generic on D; alternatively, this is the minimum number of cups/caps in a MorseLink presentation of the diagram D. The Morse number is very closely related to the more traditional bridge number. Examples:
sage: K = Link('5_2') sage: K.morse_number() 2 sage: Link('6^3_2').morse_number() 3

optimize_overcrossings
()¶ Minimizes the number of crossings of a strand which crosses entirely above the diagram by finding the path crossing over the diagram with the least number of overcrossings. It begins with the longest overcrossing, and continues with smaller ones until it successfully reduces the number of crossings.

overcrossings
()¶ Returns a list of the sequences of overcrossings of the link, sorted in descending order of length

peer_code
()¶

pieces
()¶ Auxiliary function used by knot_group. Constructs the strands of the knot from undercrossing to undercrossing. Needed for the Wirtinger Presentation.

signature
()¶ Returns the signature of the link. Examples:
sage: K = Link('4a1') sage: K.signature() 0 sage: L = Link('9^3_12') sage: Lbar = L.mirror() sage: L.signature() + Lbar.signature() 0

simplify
(mode='basic', type_III_limit=100)¶ Tries to simplify the link projection. Returns whether it succeeded in reducing the number of crossings. Modifies the link in place, and unknot components which are also unlinked may be silently discarded. The ordering of
link_components
is not always preserved.The following strategies can be employed.
 In the default
basic
mode, it does Reidemeister I and II moves until none are possible.  In
level
mode, it does random Reidemeister III moves, reducing the number of crossings via type I and II moves whenever possible. The process stops when it has donetype_III_limit
consecutive type III moves without any simplification.  In
pickup
mode, it also minimizes the number of crossings of strands which cross entirely above (or below) the diagram by finding the path crossing over the diagram with the least number of overcrossings (or undercrossings); this has the effect of doing “picking up” strands and putting them down elsewhere.  Finally, the
global
mode is the combination of 3 and 4.
Some examples:
>>> K = Link([(13,10,14,11),(11,5,12,4),(3,13,4,12), (9,14,10,1),(1,7,2,6),(2,7,3,8),(5,9,6,8)]) >>> K <Link: 1 comp; 7 cross> >>> K.simplify('basic') True >>> K <Link: 1 comp; 4 cross> >>> K.simplify('basic') # Already done all it can False
>>> L = Link([(5,0,6,1), (14,5,15,4), (10,2,11,3), (7,12,8,11), (17,0,14,9), (12,9,13,8), (3,13,4,10), (1,16,2,15), (16,6,17,7)]) >>> L <Link: 3 comp; 9 cross> >>> L.simplify('basic') False >>> L.simplify('level') True >>> L # Trivial unlinked component has been discarded! <Link: 2 comp; 2 cross>
>>> K = Link('K14n2345') >>> K.backtrack(30) >>> K.simplify('global') True >>> K <Link: 1 comp; 14 cross>
 In the default

split_link_diagram
(destroy_original=False)¶ Breaks the given link diagram into pieces, one for each connected component of the underlying 4valent graph.
>>> L = Link([(2,1,1,2),(4,3,3,4)], check_planarity=False) >>> L.split_link_diagram() [<Link: 1 comp; 1 cross>, <Link: 1 comp; 1 cross>]

sublink
(components)¶ Returns the sublink consisting of the specified components; see the example below for the various accepted forms.
Warnings: Components in the sublink that are both unknotted and unlinked may be silently thrown away. The order of the components in the sublink need not correspond to their order in the original link.
>>> L = Link('L14n64110') >>> L <Link L14n64110: 5 comp; 14 cross> >>> L.sublink([1,2,3,4]) <Link: 4 comp; 10 cross> >>> comps = L.link_components >>> L.sublink([comps[0], comps[1]]) <Link: 2 comp; 2 cross>
If you just want one component you can do this:
>>> L = Link('L11a127') >>> L.sublink(1) <Link: 1 comp; 7 cross> >>> L.sublink(L.link_components[0]) <Link: 0 comp; 0 cross>
The last answer is because the second component is unknotted and so thown away.

view
(link_editor=None)¶ Opens a Plink link editor window with displaying the current link. The strands of the links are unions of edges in the standard integer grid, following the work of Tamassia and Bridgeman et. al.

writhe
()¶ Finds the writhe of a knot.
>>> K = Link( [(4,1,5,2),(6,4,7,3),(8,5,1,6),(2,8,3,7)] ) # Figure 8 knot >>> K.writhe() 0
