nearing the deadline, more implementation details
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user