diff --git a/main.pdf b/main.pdf index 085bed4..91de769 100644 Binary files a/main.pdf and b/main.pdf differ diff --git a/main.typ b/main.typ index c84ad95..bbd772f 100644 --- a/main.typ +++ b/main.typ @@ -623,8 +623,8 @@ Let's now recap the main formal algorithm for computing the Kauffman polynomial. $K$, $S_i K$, $E_i K$, $e_i K$, [ #set text(size: 9pt); _original_], [ #set text(size: 9pt); _switch_], - [ #set text(size: 9pt); _splice_], - [ #set text(size: 9pt); _splice_], + [ #set text(size: 9pt); _h-splice_], + [ #set text(size: 9pt); _v-splice_], ), ) ] @@ -634,9 +634,9 @@ Let now $K$ be an oriented link with $n$ components so $K = K_1 union ... union #definition[ Let $K$ abd $lambda = (lambda_n, ..., lambda_0)$ a sequence of indices of crossing of $K$ and let $i$ be an index of one of the crossings, let's define the following actions - - $A_i^lambda colon.eq E_i S_(lambda_i) ... S_(lambda_0)$ + - $A_i^lambda K colon.eq E_i S_(lambda_i) dots.c space S_(lambda_0) K$ - - $B_i^lambda colon.eq e_i S_(lambda_i) ... S_(lambda_0)$ + - $B_i^lambda K colon.eq e_i S_(lambda_i) dots.c space S_(lambda_0) K$ - Then let $lambda$ be a sequence of indices that bring $K$ to $hat(K)$ so that $hat(K)(lambda) colon.eq S_(lambda_n) dots.c space S_(lambda_0) K$ and define @@ -687,6 +687,176 @@ Let now $K$ be an oriented link with $n$ components so $K = K_1 union ... union $ ] + +== 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. + +This initial implementation uses `SignedGaussCodes` as they are easier to work with when working with crossing switches and splices but with some modifications the code could be adapted to work directly on `PDCode` provided of some efficient implementations of `splice_h` and `splice_v` methods. + +#pagebreak() + +=== Signed Gauss Codes + +We are now going to walk thorough the class that lets use work nicely with *Signed Gauss Codes*. The the classes we are going to use are all _frozen data-classes_ to ensure immutability and enforce a more functional programming style. + +#show raw.where(block: true): body => { + set text(size: 7pt) + set align(center) + + body +} + +```python +Sign = typing.Literal[+1, -1] + +@dataclass(frozen=True) +class SignedGaussCodeCrossing: + id: int + over_under: Sign + handedness: Sign + + def is_over(self) -> bool: ... + def is_under(self) -> bool: ... + def is_left(self) -> bool: ... + def is_right(self) -> bool: ... + def opposite(self) -> SignedGaussCodeCrossing: ... + + def __repr__(self): ... +``` + +```python +@dataclass(frozen=True) +class SignedGaussCode: + components: list[list[SignedGaussCodeCrossing]] + + def writhe(self): ... + def reverse(self): ... + def mirror(self): ... + def to_std_unknot(self) -> SignedGaussCode: ... + def std_unknot_switching_sequence(self) -> list[int]: ... + def apply_switching_sequence(self, seq: list[int]) -> SignedGaussCode: ... + def splice_h(self, id: int): ... + def splice_v(self, id: int): ... + def switch_crossing(self, id: int): ... + + def __repr__(self): ... +``` + +==== Writhe + +One of the first important things we need is to compute the *writhe* $w(K)$ of a link, this can easily be done with signed gauss codes as its a list of tuples where the second entry is the crossing sign. Let $L$ be an oriented link with components $C_1, ..., C_k$ each with crossings $c_(i, j)$ with $i = 1, ..., k$ and $j = 1, ..., |C_i|$. + +Let's notice that here each crossing appears twice, once as over-crossing and once as an under-crossing this is the reason for the $1 slash 2$ in the following formula. By $epsilon(c)$ we refer to the sign (or handedness) of the crossing at $c$. + +#[ + #set align(center) + #grid( + columns: 3, + gutter: 1.5em, + align: horizon, + [ + $ + w(L) = 1 / 2 sum_(c "crossing") epsilon(c) + $ + ], + [$ arrow.squiggly $], + [ + ```python + def writhe(self): + return sum( + c.handedness # => +1 or -1 + for component in self.components + for c in component + ) // 2 + ``` + ], + ) +] + +==== Standard Unknot + +The next building block for computing the Kauffman polynomial is detecting and computing the *standard unknot or unlink*. Formally this is done by taking the _planar shadow_ and a directed starting point on it. Then we can walk along the shadow and make each crossing an over-crossing when passing on it on the first time. + +On the other hand our algorithm directly works with switching sequences $lambda$ that bring a knot $K$ to its standard unknot $hat(K)$. We wrote methods to directly compute and apply these switching sequences. + +```python +def std_unknot_switching_sequence(self) -> list[int]: + visited_crossings: set[int] = set() + switched_crossings: list[int] = [] + + for component in self.components: + for crossing in component: + if crossing.id not in visited_crossings: + if crossing.is_under(): + switched_crossings.append(crossing.id) + visited_crossings.add(crossing.id) + + return switched_crossings +``` + +The `std_unknot_switching_sequence` method just walks along each component in its orientation marking what switches have to be made to bring that link to its standard unknot. + + +```python +def apply_switching_sequence(self, seq: list[int]) -> SignedGaussCode: + return SignedGaussCode( + [ + [ + crossing.opposite() if crossing.id in switching_sequence else crossing + for crossing in component + ] + for component in self.components + ] + ) +``` + +Applying a switching sequence is just a matter of walking along the crossings and flipping the crossings that are in the sequence. This is also how the `switch_crossing(id: int)` method works. + +==== Crossing Splices + +The splicing code is more involved due to the number of cases to analyze, let's first see formally what we need to do. + +We have all the following cases, first we can assume the _entering over strand_ is in the top left corner of a diagram (this can be done by applying locally a small isotopy). So we have $2$ cases for the crossing sign + +#{ + set align(center) + + grid( + columns: 4, + grid( + // + columns: 5, + gutter: 1.5em, + align: horizon, + + skein-generic(kind: "over", direction: (+1, +1)), + skein-generic(kind: "over", direction: (+1, -1)), + [$arrow.squiggly$], + skein.h, + [$arrow.squiggly$], + + skein-generic(kind: "over", direction: (+1, +1)), + skein-generic(kind: "over", direction: (+1, -1)), + [$arrow.squiggly$], + skein.v, + [$arrow.squiggly$], + ), + ) +} + + + +So the final code is just a conversion of all this cases to list slicing and re-joining with the appropriate crossings removed. + +```python +def splice_h(self, id: int): + raise NotImplementedError("Splicing not implemented yet") + +def splice_v(self, id: int): + raise NotImplementedError("Splicing not implemented yet") +``` + #pagebreak() = Appendix diff --git a/theme.typ b/theme.typ index a006d68..cd5294f 100644 --- a/theme.typ +++ b/theme.typ @@ -114,6 +114,7 @@ } else { set text(size: 10pt, fill: luma(20%)) v(normal-size * 2, weak: true) + [$thin diamond.medium space$] strong(it.body) v(normal-size * 1.5, weak: true) }