@ -788,14 +790,21 @@ Let's notice that here each crossing appears twice, once as over-crossing and on
==== 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.
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 each component of the link. Then we can walk along the shadow of each component in order 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.
#figure(
image("assets/standard-unlink-construction.png"),
caption: [Standard unknot construction for a link, in blue are highlighted all the crossings in the resulting switching sequence],
)
On the other hand our algorithm directly works with switching sequences $lambda$ that bring a link $L$ to its standard unlink $hat(L)$. We wrote methods to directly compute and apply these switching sequences.
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.
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.
Now we need to be careful when applying these switches as they do not preserve the writhe as we can see in the following figure
First we wrote two functions one called `switch` that creates a new switched crossing from another crossing and then another one for `SGCode` that does the _two_ switches.
#align(
center,
grid(
columns: 2,
gutter: 1em,
align: center + horizon,
[
```py
def switch(self: SGCodeCrossing):
return SGCodeCrossing(
self.id,
-self.over_under,
-self.handedness
)
```
],
[
```py
def switch_crossing(self: SGCode, id: int):
return SGCode([
[
crossing.switch()
if crossing.id == id else crossing
for crossing in component
]
for component in self.components
])
```
],
),
)
With this infrastructure in place now applying a switching sequence is just a matter of applying all switches in a given sequence, this can be done in a single pass with the following function.
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 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.
@ -840,7 +887,7 @@ Then we have the following cases
So we have a total of $2 times 2 times 2 = 8$ cases to analyze. The following diagram shows all the possible cases for horizontal splicing for _signed gauss codes_.
The code for the *vertical splices* is omitted as the cases are the same as for the horizontal splices with a minor change. All the cases for vertical splices are shown below in the following diagram and we can see that the output lists are the same as in the previous splice case just switched based on the crossing sign.
#figure(
image("assets/splice-v-cases.png"),
image("assets/operations-splice-v-cases.png"),
caption: [Cases for vertical splicing],
)
@ -904,6 +951,64 @@ So the final code is just a conversion of all this cases to list slicing and re-
= Appendix
== Enhancing SGCode rejoining code
The initial code to do horizontal and vertical splices looked something like the following
```py
...
if handedness == HANDED_LEFT:
return SGCode([
*(component[:]
for i, component in enumerate(self.components)
if i != component_index),
[
*l1,
*l3,
],
l2,
])
else:
return SGCode([
*(component[:]
for i, component in enumerate(self.components)
if i != component_index),
[
*l1,
*l2[::-1],
*l3,
],
])
```
This is actually wrong as this is not correcting the signs of all crossing from the reversed strand. The corrected code for the second case is not very readable
```py
over_crossing_ids = set(c.id for c in l2 if c.is_over())
under_crossing_ids = set(c.id for c in l2 if c.is_under())