minor updates

main
parent f85200887a
commit 56df1e849d

Binary file not shown.

@ -146,7 +146,7 @@
),
),
abstract: [
In this project we write implement from scratch the Kauffman polynomial in Python. First we introduce problems in computational knot theory and describe various representations of knots and links and find a good representation to use for the algorithm. We then describe in-depth the algorithm for computing the Kauffman polynomial and how to implement it in Python. Finally we try the algorithm on various knots and links and compare the results with the ones from the KnotInfo Database.
In this project we write implement from scratch the Kauffman polynomial in Python. We start with a brief detour in computational knot theory and describe various representations of knots and links and find a good representation to use for the algorithm. We then describe in-depth the algorithm for computing the Kauffman polynomial and how to implement it in Python. Finally we try the algorithm on various knots and links and compare the results with the ones from the KnotInfo Database, finding an error for the knot $10_125$.
],
)
@ -200,21 +200,17 @@ For each component we walk along it from the starting point in the component dir
#figure(image("assets/pd-code-crossing-ordering.svg"))
*Algorithm*:
*Algorithm*: The input is an oriented link diagram with starting points on each component and the output is a list of 4-tuples.
```
Input: An oriented link diagram with starting points on each component
Output: List<(Nat, Nat, Nat, Nat)>
1. Choose an ordering for the components and starting point for each component
- Choose an ordering for the components and starting point for each component
- Label each arc with a number
- For each component:
- Walk along it from the starting point in its orientation
- At each crossing, when at an over-crossing for the current strand
- Write down a 4-uple of counter-clockwise numbers for the arc incident
to the crossing starting from the entering under-crossing arc
2. Label each arc with a number
```
3. For each component:
1. Walk along it from the starting point in its orientation
2. At each crossing, when at an over-crossing for the current strand: write down a 4-uple of counter-clockwise numbers for the arc incident to the crossing starting from the entering under-crossing arc
Let's see an example of how to construct the PD code for the following link diagram
@ -333,7 +329,7 @@ We will now see how SG codes are better suited for the manipulations (switching
- *PD codes* -- This can be approached in various ways more or less performant.
One can add a meaning to pairs $(i, j)$ symbols to the original sequence of 4-uples called "path" elements (the KnotTheory package has this extension of the PD notation with the `P[i, j]` element) that tell we joined the arc $i$ with the arc $j$. This gives an heterogeneous list of elements that is more complex to handle efficiently in classical programming languages different from Mathematica.
One can add a meaning to pairs $(i, j)$ symbols to the original sequence of 4-tuples called "path" elements (the KnotTheory package has this extension of the PD notation with the `P[i, j]` element) that tell we joined the arc $i$ with the arc $j$. This gives an heterogeneous list of elements that is more complex to handle efficiently in classical programming languages different from Mathematica.
Another approach to keep list homogeneous is to manually splice the crossing and do a relabelling of all the arcs at each step. This causes in the worst case a continuos relabelling of all the crossings in the link at every splice and is not very efficient as our algorithm needs to be able to do splicing as fast as possible.
@ -620,7 +616,7 @@ Another approach used by #link("https://knotfol.io/")[KnotFolio] is based on #li
This condition that every point is the average of its neighbors can be easily expressed as a system of linear equations where some points on a chosen outer face have been fixed. When the graph is planar and 3-vertex-connected the linear system is non degenerate and has a unique solution.
= Computing the Kauffman Polynomial
= Computing the Polynomial
Let's now recap the main formal algorithm for computing the Kauffman polynomial.
@ -711,9 +707,7 @@ Let now $K$ be an oriented link with $n$ components so $K = K_1 union dotss unio
=== Second Approach
After a first implementation of the algorithm we noticed that it is not very efficient to compute using this closed form algorithm. In this case the naive implementation of the algorithm of just applying the rules recursively is more efficient and we only use the closed form for handling the base cases and the case of multiple components.
The final algorithm we implemented is the following:
After a first implementation of the algorithm we noticed that it is not very efficient to compute using this closed form algorithm. In this case the naive implementation of the algorithm of just applying the rules recursively is more efficient and we only use the closed form for handling the base cases and the case of multiple components. The final algorithm we implemented is the following:
*Algorithm*: We apply the following rules recursively
@ -725,7 +719,7 @@ The final algorithm we implemented is the following:
kL(K_1 union.sq dotss union.sq K_n) = d^(n-1) kL(K_1) dotss kL(K_n)
$
3. If $K$ is a linked component then we find $hat(K)$, the standard unlink for $K$, and pick the first available crossing $c$ to switch, then we have the following cases based on the crossing over/under type and handedness that can be reduced to the following two cases
3. If $K$ is a linked component then we find $hat(K)$, the standard unlink for $K$, and pick the first available crossing $c$ to switch, then we have some cases based on the crossing over/under type and handedness that can be reduced to the following two cases
$
kL(skein.over) = z (kL(skein.h) + kL(skein.v)) - kL(skein.under)
@ -748,7 +742,7 @@ The final algorithm we implemented is the following:
= Python Implementation
The approach has been a mix of bottom-up and top-down. First we defined a couple of classed `SignedGaussCode` and `PDCode` to work with these codes and easily convert between each other.
The approach has been a mix of bottom-up and top-down. First we defined a couple of classed `SGCode` and `PDCode` to work with these codes and easily convert between each other.
== SG Codes
@ -761,9 +755,9 @@ We are now going to walk thorough the class that lets use work nicely with *SG c
body
}
// @dataclass(frozen=True)
```python
class SignedGaussCodeCrossing:
@dataclass(frozen=True)
class SGCodeCrossing:
id: int
over_under: typing.Literal[+1, -1]
handedness: typing.Literal[+1, -1]
@ -778,15 +772,11 @@ class SignedGaussCodeCrossing:
```
// @dataclass(frozen=True)
```python
@dataclass(frozen=True)
class SGCode:
components: list[list[SignedGaussCodeCrossing]]
# static methods
def from_tuples(self, components: list[list[tuple[int, int]]]) -> SignedGaussCode:
def from_pd(pd_code: PDCode) -> SGCode:
def relabel(self) -> SGCode:
def to_minimal(self) -> SGCode:
@ -810,6 +800,10 @@ class SGCode:
def switch_crossing(self, id: int) -> SGCode:
def splice_h(self, id: int, orthogonal: Sign = +1) -> SGCode:
def splice_v(self, id: int) -> SGCode:
# static methods
def from_tuples(self, components: list[list[tuple[int, int]]]) -> SignedGaussCode:
def from_pd(pd_code: PDCode) -> SGCode:
```
Let's now walk through the most important methods of this class.
@ -1178,17 +1172,17 @@ The code (omitted from above, see the github repository for the full code) has a
- `relabel`: After minimal rotation we apply a relabelling to increase the chances of hitting the cache decorator.
These two optimization decrease the number of total recursive calls by almost an order of magnitude, for example for the case of knot `12n_888` we have the following optimization lattice with number of calls in blue and time of execution in green.
These two optimization decrease the number of calls by almost an order of magnitude, for example for the case of knot `12n_888` we have the following optimizations lattice, the number of total recursive calls for the function `kauffman_polynomial` is in blue and time of execution in green.
#figure(image("assets/optimizations-lattice-calls.jpg", width: 45%))
As we can see the most important optimization is the `to_minimal` one that by its own reduces the total number of calls from $29"k"$ to $8"k"$ but relabelling is still able to help a bit more.
As we can see the most important optimization is the `to_minimal` one that by its own reduces the total number of calls from $29"k"$ to $8"k"$ but relabelling is still able to help.
All optimizations are enabled by default in the final program and debugging traces are disabled to not impact the speed.
== Experiments
The main achievement of this project was to check all (normalized) Kauffman polynomials present in the #link("https://knotinfo.math.indiana.edu/")[KnotInfo database]. Using the python package `database_knotinfo` we loaded all the data present in the database and wrote a script called `./check_knotinfo.py` with the following options
The main achievement of this project was to check all (normalized) Kauffman polynomials present in the #link("https://knotinfo.math.indiana.edu/")[KnotInfo database]. Using the python package `database_knotinfo` we loaded all the data and wrote a script called `./check_knotinfo.py` with the following options
```
usage: check_knotinfo.py [-h] [--knots] [--links] [-c COUNT]

@ -157,6 +157,8 @@
it,
)
show raw.where(block: false): set text(size: 7.25pt, fill: luma(7%))
// Configure citation and bibliography styles.
set std.bibliography(style: "springer-mathphys", title: [References])

Loading…
Cancel
Save