nearing the deadline, more implementation details

main
parent 8cda43d443
commit 6a199c236c

Binary file not shown.

@ -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

@ -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)
}

Loading…
Cancel
Save