3-manifolds / Spherogram

Spherogram is a Python module for dealing with the kind of planar diagrams that arise in 3-dimensional topology, such as link and Heegaard diagrams. It a component of the larger SnapPy project.
https://snappy.math.uic.edu/spherogram.html
19 stars 8 forks source link

Bug in constructing 2-strand components from PD code #35

Closed sophiatev closed 1 year ago

sophiatev commented 2 years ago

It seems to me there's a bug in how Spherogram builds a link from its PD code for two-strand components. If I pass in a PD code for a Hopf link with two positive crossings, [(1, 2, 0, 3), (2, 1, 3, 0)] (pictured on left), and then call the PD_code() method on the constructed link, the PD code I get back is [(1, 2, 0, 3), (3, 0, 2, 1)], which corresponds to a Hopf link with two negative crossings (pictured on right). I also double checked that each crossing of the constructed link has a crossing.sign of -1, confirming that the wrong link is constructed.

image

However, if I instead pass in the PD code [(1, 3, 0, 2), (3, 1, 2, 0)], which corresponds to the same Hopf link with two positive crossings, simply with relabeled strands (see below), I get back the correct PD code. In this case, the crossing.sign of each crossing is correct: +1.

image

I believe this is due to the logic of this else statement in the method _component_starts of links_base.py. As I understand it, this logic seems to assume that in the case a component is made up of just two strands, the lowest-labeled strand of the component must be the incoming overstrand of a crossing. It extracts the two crossings that the strand "glues" together, and checks for which of those two the lowest-labeled strand (called m) is an overstrand. It then orients the component to point along the strand two indices away from m in that crossing's PD tuple. This only works if that strand is an outgoing strand, which is true only if min is an incoming strand.

This is the case for the second image that Spherogram processes correctly. For the left component, we see that the lowest-labeled strand is m = 0, and in this case it is indeed the incoming overstrand of crossing c2. Similarly, the lowest-labeled strand for the right component is m = 2, and this once more is the incoming overstrand of crossing c1. So the top crossing is oriented with strand 1 as the outgoing strand, and the bottom crossing is oriented with strand 3 as the outgoing strand, which is correct. In the first image I posted, the logic remains the same for the left component (0 is still the incoming overstrand of the top crossing), so the orientation is set correctly for that component. However, for the right component, 2 is no longer the incoming overstrand but rather the incoming understrand of crossing c2. As such, the else statement decides that we should orient the component to point along strand 3 moving from the bottom crossing, c1. It does this under the assumption that since 2 wasn't the incoming overstrand for c2, it must be for c1, and hence strand 3 must be the outgoing strand. We wrongly reverse the orientation of the second component and end up with an inequivalent link with two negative crossings, pictured in the first image on the right.

The comment in the else statement seems to imply that we are using the fact that the first element of a crossing's PD code is the incoming understrand. But this doesn't imply that the lowest labeled-strand of a two-strand component will be the incoming overstrand of some crossing. I think the correct logic would rather look like

else:
    next_label = l1
    # l1 is the incoming understrand of c2, so it must be the outgoing strand of c1 at index j1
    # or lowest-labeled strand is incoming understrand of c1, so l1 is the outgoing understrand at index j1
    if code[c2][0] == l1 or code[c1][0] == m:
        direction = (c1, j1)
    # l1 is the incoming understrand of c1, so it must be the outgoing strand of c2 at index j2
    # or lowest-labeled strand is incoming understrand of c2, so l1 is the outgoing understrand at index j2
    # or both crossings are under- or over-, in which case orientation can be arbitrary anyway 
    else:
        direction = (c2, j2)

This is of course assuming that component with just two strands should appear as the understrand in one crossing and as the overstrand in another. Otherwise it's just an unknot lying on top of or below the link diagram, in which case the orientation doesn't matter anyway.

NathanDunfield commented 2 years ago

Many thanks for this very detailed report and proposed solution, definitely something isn't right here! I will try to look into it in detail in the next few days.

sophiatev commented 2 years ago

Thank you! FYI, I noticed a bug in my initial proposed solution. The updated post has my new proposed solution which works with what I've tested thus far.

NathanDunfield commented 2 years ago

I incorporated your fix in 0e30e0e99fd0f0d. I checked that the link constructed under permuting the PD labels.

Thanks for both pointing this out and solving the underlying problem!

fchapoton commented 1 year ago

Maybe this issue can be closed then ?

NathanDunfield commented 1 year ago

Closed by 0e30e0e99fd0f0dd1547a8e0.