From dd71d186f17792d442df9db89ab5247799f38d93 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 22 May 2023 08:19:12 +0200 Subject: [PATCH 1/2] change_pd_convention initial version --- src/sage/knots/knotinfo.py | 26 +-- src/sage/knots/link.py | 359 ++++++++++++++++++++----------------- 2 files changed, 204 insertions(+), 181 deletions(-) diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index c6c8f554fd5..19e2589af9d 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -31,12 +31,11 @@ lines of the examples, are unnecessary. Be aware that there are a couple of conventions used differently on KnotInfo as -in Sage, especially concerning the selection of the symmetry version of the link. +in Sage. -In this context you should note that the PD notation is recorded counter -clockwise in KnotInfo (see note in :meth:`KnotInfoBase.link`). In our transition -to Sage objects this is translated (by default) in order to avoid confusion about -exchanged mirror versions. +For different conventions regarding normalization of the polynomial invariants see +the according documentation of :meth:`KnotInfoBase.homfly_polynomial`, +:meth:`KnotInfoBase.jones_polynomial` and :meth:`KnotInfoBase.alexander_polynomial`. Also, note that the braid notation is used according to Sage, even thought in the source where it is taken from, the braid generators are assumed to have a @@ -44,10 +43,6 @@ 3 of :arxiv:`Gittings, T., "Minimum Braids: A Complete Invariant of Knots and Links" `). -For different conventions regarding normalization of the polynomial invariants see -the according documentation of :meth:`KnotInfoBase.homfly_polynomial`, -:meth:`KnotInfoBase.jones_polynomial` and :meth:`KnotInfoBase.alexander_polynomial`. - Furthermore, note that not all columns available in the database are visible on the web pages (see also the related note under :meth:`KnotInfoBase.khovanov_polynomial`). It is planned to remove non-visible columns from the database in the future (see @@ -1824,14 +1819,6 @@ def link(self, use_item=db.columns().pd_notation, snappy=False): coincides with the crossing number as a topological invariant. - But attention: The convention on how the edges are - listed are opposite to each other - - - KnotInfo: counter clockwise - - Sage: clockwise - - Therefore, we take the mirror version of the ``pd_notation``! - Furthermore, note that the mirror version may depend on the used KnotInfo-notation. For instance, regarding to the knot ``5_1`` the Gauss- and the DT-notation refer to @@ -1905,7 +1892,7 @@ def link(self, use_item=db.columns().pd_notation, snappy=False): sage: K4_1 = KnotInfo.K4_1 sage: K4_1.link().pd_code() - [[4, 1, 5, 2], [8, 5, 1, 6], [6, 4, 7, 3], [2, 8, 3, 7]] + [[4, 2, 5, 1], [8, 6, 1, 5], [6, 3, 7, 4], [2, 7, 3, 8]] sage: K4_1.pd_notation() [[4, 2, 5, 1], [8, 6, 1, 5], [6, 3, 7, 4], [2, 7, 3, 8]] @@ -1931,8 +1918,7 @@ def link(self, use_item=db.columns().pd_notation, snappy=False): from sage.knots.link import Link if use_item == self.items.pd_notation: - pd_code = [[a[0], a[3], a[2], a[1]] for a in self.pd_notation()] # take mirror version, see note above - return Link(pd_code) + return Link(self.pd_notation()) elif use_item == self.items.braid_notation: return Link(self.braid()) elif use_item == self.items.name and snappy: diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 4d845139d17..ed251cd9965 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -39,6 +39,8 @@ - Amit Jamadagni - Sebastian Oehms (October 2020, add :meth:`get_knotinfo` and :meth:`is_isotopic`) - Sebastian Oehms (May 2022): add :meth:`links_gould_polynomial` +- Sebastian Oehms (May 2023): change the convention about the ``pd_code`` from + clockwise to anti-clockwise. """ # **************************************************************************** @@ -127,7 +129,7 @@ class Link(SageObject): The diagram of the link is formed by segments that are adjacent to the crossings. Label each one of this segments with a positive number, and for each crossing, write down the four incident segments. The - order of these segments is clockwise, starting with the incoming + order of these segments is anti-clockwise, starting with the incoming undercrossing. There is no particular distinction between knots and links for @@ -189,9 +191,9 @@ class Link(SageObject): We construct the "monster" unknot using a planar code, and then construct the oriented Gauss code and braid representation:: - sage: L = Link([[3,1,2,4], [8,9,1,7], [5,6,7,3], [4,18,6,5], - ....: [17,19,8,18], [9,10,11,14], [10,12,13,11], - ....: [12,19,15,13], [20,16,14,15], [16,20,17,2]]) + sage: L = Link([[3,4,2,1], [8,7,1,9], [5,3,7,6], [4,5,6,18], + ....: [17,18,8,19], [9,14,11,10], [10,11,13,12], + ....: [12,13,15,19], [20,15,14,16], [16,2,17,20]]) sage: L.oriented_gauss_code() [[[1, -4, 3, -1, 10, -9, 6, -7, 8, 5, 4, -3, 2, -6, 7, -8, 9, -10, -5, -2]], [1, -1, 1, 1, 1, -1, -1, -1, -1, -1]] @@ -201,9 +203,9 @@ class Link(SageObject): .. PLOT:: :width: 300 px - L = Link([[3,1,2,4],[8,9,1,7],[5,6,7,3],[4,18,6,5], - [17,19,8,18],[9,10,11,14],[10,12,13,11], - [12,19,15,13],[20,16,14,15],[16,20,17,2]]) + L = Link([[3,4,2,1], [8,7,1,9], [5,3,7,6], [4,5,6,18], + [17,18,8,19], [9,14,11,10], [10,11,13,12], + [12,13,15,19], [20,15,14,16], [16,2,17,20]]) sphinx_plot(L.plot()) We construct the Ochiai unknot by using an oriented Gauss code:: @@ -212,10 +214,10 @@ class Link(SageObject): ....: -11,-16,4,3,-5,6,-9,7,-15,14,16,-10,8,9,-6,5]], ....: [-1,-1,1,1,1,1,-1,1,1,-1,1,-1,-1,-1,-1,-1]]) sage: L.pd_code() - [[10, 2, 11, 1], [2, 12, 3, 11], [3, 20, 4, 21], [12, 19, 13, 20], - [21, 32, 22, 1], [31, 22, 32, 23], [9, 25, 10, 24], [4, 29, 5, 30], - [23, 30, 24, 31], [28, 14, 29, 13], [17, 14, 18, 15], [5, 17, 6, 16], - [15, 7, 16, 6], [7, 27, 8, 26], [25, 9, 26, 8], [18, 28, 19, 27]] + [[10, 1, 11, 2], [2, 11, 3, 12], [3, 21, 4, 20], [12, 20, 13, 19], + [21, 1, 22, 32], [31, 23, 32, 22], [9, 24, 10, 25], [4, 30, 5, 29], + [23, 31, 24, 30], [28, 13, 29, 14], [17, 15, 18, 14], [5, 16, 6, 17], + [15, 6, 16, 7], [7, 26, 8, 27], [25, 8, 26, 9], [18, 27, 19, 28]] .. PLOT:: :width: 300 px @@ -426,14 +428,15 @@ def arcs(self, presentation='pd'): [[-1, -2], [2, 1]] """ if presentation == 'pd': + pd_code = self.pd_code() G = DiGraph() - for e in set(flatten(self.pd_code())): + for e in set(flatten(pd_code)): G.add_vertex(e) - for cr in zip(self.pd_code(), self.orientation()): + for cr in zip(pd_code, self.orientation()): if cr[1] == 1: - G.add_edge(cr[0][1], cr[0][3]) - else: G.add_edge(cr[0][3], cr[0][1]) + else: + G.add_edge(cr[0][1], cr[0][3]) res = [] for S in G.connected_components_subgraphs(): check = S.is_directed_acyclic(certificate=True) @@ -480,7 +483,7 @@ def fundamental_group(self, presentation='wirtinger'): EXAMPLES:: - sage: L = Link([[1, 2, 3, 4], [3, 2, 1, 4]]) + sage: L = Link([[1, 4, 3, 2], [3, 4, 1, 2]]) sage: L.fundamental_group() Finitely presented group < x0, x1, x2 | x1*x0^-1*x2^-1*x0, x2*x0*x1^-1*x0^-1 > sage: L.fundamental_group('braid') @@ -519,7 +522,7 @@ def fundamental_group(self, presentation='wirtinger'): rels = [] for crossing, orientation in zip(self.pd_code(), self.orientation()): a = arcs.index([i for i in arcs if crossing[0] in i][0]) - b = arcs.index([i for i in arcs if crossing[1] in i][0]) + b = arcs.index([i for i in arcs if crossing[3] in i][0]) c = arcs.index([i for i in arcs if crossing[2] in i][0]) ela = F.gen(a) elb = F.gen(b) @@ -621,13 +624,13 @@ def braid(self): EXAMPLES:: - sage: L = Link([[2, 3, 1, 4], [4, 1, 3, 2]]) + sage: L = Link([[2, 4, 1, 3], [4, 2, 3, 1]]) sage: L.braid() s^2 sage: L = Link([[[-1, 2, -3, 1, -2, 3]], [-1, -1, -1]]) sage: L.braid() s^-3 - sage: L = Link([[1,8,2,7], [8,4,9,5], [3,9,4,10], [10,1,7,6], [5,3,6,2]]) + sage: L = Link([[1,7,2,8], [8,5,9,4], [3,10,4,9], [10,6,7,1], [5,2,6,3]]) sage: L.braid() (s0*s1^-1)^2*s1^-1 @@ -670,6 +673,7 @@ def braid(self): B = BraidGroup(2) self._braid = B.one() return self._braid + seifert_circles = self.seifert_circles() newedge = max(flatten(pd_code)) + 1 for region in self.regions(): @@ -685,21 +689,45 @@ def braid(self): newPD = [list(vertex) for vertex in pd_code] if sign(a) == 1: + # ------------------------------------------------- + # Visualize insertion of the two new crossings D, E + # \ / + # a\ /b existing edges, a down, b up + # D + # n3/ \n0 newedge + 3, newedge + # \ / + # E + # n1/ \n2 newedge + 1, newedge + 2 + # / \ + # C1 C2 existing crossings + # ------------------------------------------------- C1 = newPD[newPD.index(heads[a])] C1[C1.index(a)] = newedge + 1 C2 = newPD[newPD.index(tails[b])] C2[C2.index(b)] = newedge + 2 - newPD.append([newedge + 3, a, b, newedge]) - newPD.append([newedge + 2, newedge + 1, newedge + 3, newedge]) + newPD.append([newedge + 3, newedge, b, a]) # D + newPD.append([newedge + 2, newedge, newedge + 3, newedge + 1]) # E self._braid = Link(newPD).braid() return self._braid else: + # ------------------------------------------------- + # Visualize insertion of the two new crossings D, E + # C1 C2 existing crossings + # \ / + # n1\ /n2 newedge + 1, newedge + 2 + # D + # n3/ \n0 newedge + 3, newedge + # \ / + # E + # a/ \b existing edges, a up, b down + # / \ + # ------------------------------------------------- C1 = newPD[newPD.index(heads[-a])] C1[C1.index(-a)] = newedge + 1 C2 = newPD[newPD.index(tails[-b])] C2[C2.index(-b)] = newedge + 2 - newPD.append([newedge + 2, newedge, newedge + 3, newedge + 1]) - newPD.append([newedge + 3, newedge, -b, -a]) + newPD.append([newedge + 2, newedge +1, newedge + 3, newedge]) # D + newPD.append([newedge + 3, -a, -b, newedge]) # E self._braid = Link(newPD).braid() return self._braid @@ -708,11 +736,11 @@ def braid(self): G.add_vertices([tuple(c) for c in seifert_circles]) for i,c in enumerate(pd_code): if self.orientation()[i] == 1: - a = [x for x in seifert_circles if c[1] in x][0] + a = [x for x in seifert_circles if c[3] in x][0] b = [x for x in seifert_circles if c[0] in x][0] else: a = [x for x in seifert_circles if c[0] in x][0] - b = [x for x in seifert_circles if c[3] in x][0] + b = [x for x in seifert_circles if c[1] in x][0] G.add_edge(tuple(a), tuple(b)) # Get a simple path from a source to a sink in the digraph @@ -733,10 +761,10 @@ def braid(self): if orientation[crossing_index] == 1: b = B([1]) status[0] = crossing[2] - status[1] = crossing[3] + status[1] = crossing[1] else: b = B([-1]) - status[0] = crossing[1] + status[0] = crossing[3] status[1] = crossing[2] counter = 0 while available_crossings: @@ -748,10 +776,10 @@ def braid(self): if orientation[pd_code.index(added)] == 1: b *= B([counter + 1]) status[counter] = added[2] - status[counter + 1] = added[3] + status[counter + 1] = added[1] else: b *= B([-counter - 1]) - status[counter] = added[1] + status[counter] = added[3] status[counter + 1] = added[2] if counter > 0: counter -= 1 @@ -773,50 +801,50 @@ def _directions_of_edges(self): EXAMPLES:: - sage: L = Link([[1, 3, 2, 4], [2, 3, 1, 4]]) + sage: L = Link([[1, 4, 2, 3], [2, 4, 1, 3]]) sage: tails, heads = L._directions_of_edges() sage: tails - {1: [2, 3, 1, 4], 2: [1, 3, 2, 4], 3: [1, 3, 2, 4], 4: [2, 3, 1, 4]} + {1: [2, 4, 1, 3], 2: [1, 4, 2, 3], 3: [1, 4, 2, 3], 4: [2, 4, 1, 3]} sage: heads - {1: [1, 3, 2, 4], 2: [2, 3, 1, 4], 3: [2, 3, 1, 4], 4: [1, 3, 2, 4]} + {1: [1, 4, 2, 3], 2: [2, 4, 1, 3], 3: [2, 4, 1, 3], 4: [1, 4, 2, 3]} :: - sage: L = Link([[1,5,2,4], [5,3,6,2], [3,1,4,6]]) + sage: L = Link([[1,4,2,5], [5,2,6,3], [3,6,4,1]]) sage: tails, heads = L._directions_of_edges() sage: tails - {1: [3, 1, 4, 6], - 2: [1, 5, 2, 4], - 3: [5, 3, 6, 2], - 4: [3, 1, 4, 6], - 5: [1, 5, 2, 4], - 6: [5, 3, 6, 2]} + {1: [3, 6, 4, 1], + 2: [1, 4, 2, 5], + 3: [5, 2, 6, 3], + 4: [3, 6, 4, 1], + 5: [1, 4, 2, 5], + 6: [5, 2, 6, 3]} sage: heads - {1: [1, 5, 2, 4], - 2: [5, 3, 6, 2], - 3: [3, 1, 4, 6], - 4: [1, 5, 2, 4], - 5: [5, 3, 6, 2], - 6: [3, 1, 4, 6]} + {1: [1, 4, 2, 5], + 2: [5, 2, 6, 3], + 3: [3, 6, 4, 1], + 4: [1, 4, 2, 5], + 5: [5, 2, 6, 3], + 6: [3, 6, 4, 1]} :: - sage: L = Link([[1,2,3,3], [2,4,5,5], [4,1,7,7]]) + sage: L = Link([[1,3,3,2], [2,5,5,4], [4,7,7,1]]) sage: tails, heads = L._directions_of_edges() sage: tails - {1: [4, 1, 7, 7], - 2: [1, 2, 3, 3], - 3: [1, 2, 3, 3], - 4: [2, 4, 5, 5], - 5: [2, 4, 5, 5], - 7: [4, 1, 7, 7]} + {1: [4, 7, 7, 1], + 2: [1, 3, 3, 2], + 3: [1, 3, 3, 2], + 4: [2, 5, 5, 4], + 5: [2, 5, 5, 4], + 7: [4, 7, 7, 1]} sage: heads - {1: [1, 2, 3, 3], - 2: [2, 4, 5, 5], - 3: [1, 2, 3, 3], - 4: [4, 1, 7, 7], - 5: [2, 4, 5, 5], - 7: [4, 1, 7, 7]} + {1: [1, 3, 3, 2], + 2: [2, 5, 5, 4], + 3: [1, 3, 3, 2], + 4: [4, 7, 7, 1], + 5: [2, 5, 5, 4], + 7: [4, 7, 7, 1]} """ tails = {} heads = {} @@ -832,10 +860,10 @@ def _directions_of_edges(self): tails[a] = D if D[0] == a: a = D[2] - elif D[1] == a: - a = D[3] - else: + elif D[3] == a: a = D[1] + else: + a = D[3] else: heads[a] = next_crossing[0] tails[a] = D @@ -887,7 +915,7 @@ def _enhanced_states(self): sage: K = Link([[[1,-2,3,-1,2,-3]],[-1,-1,-1]]) sage: K.pd_code() - [[4, 2, 5, 1], [2, 6, 3, 5], [6, 4, 1, 3]] + [[4, 1, 5, 2], [2, 5, 3, 6], [6, 3, 1, 4]] sage: K._enhanced_states() (((0, 0, 0), (((1, 4, 7), (4, 1, 9)), ((2, 5, 7), (5, 2, 8)), ((3, 6, 9), (6, 3, 8))), @@ -1053,16 +1081,16 @@ def _enhanced_states(self): n = nmax + j if not v[j]: # For negative crossings, we go from undercrossings to the left - G.add_edge((cr[3], cr[0], n), cr[0]) - G.add_edge((cr[3], cr[0], n), cr[3]) - G.add_edge((cr[1], cr[2], n), cr[2]) - G.add_edge((cr[1], cr[2], n), cr[1]) + G.add_edge((cr[1], cr[0], n), cr[0]) + G.add_edge((cr[1], cr[0], n), cr[1]) + G.add_edge((cr[3], cr[2], n), cr[2]) + G.add_edge((cr[3], cr[2], n), cr[3]) else: # positive crossings, from undercrossing to the right - G.add_edge((cr[0], cr[1], n), cr[0]) - G.add_edge((cr[0], cr[1], n), cr[1]) - G.add_edge((cr[2], cr[3], n), cr[2]) - G.add_edge((cr[2], cr[3], n), cr[3]) + G.add_edge((cr[0], cr[3], n), cr[0]) + G.add_edge((cr[0], cr[3], n), cr[3]) + G.add_edge((cr[2], cr[1], n), cr[2]) + G.add_edge((cr[2], cr[1], n), cr[1]) sm = set(tuple(sorted(x for x in b if isinstance(x, tuple))) for b in G.connected_components(sort=False)) iindex = (writhe - ncross + 2 * sum(v)) // 2 @@ -1265,10 +1293,10 @@ def oriented_gauss_code(self): EXAMPLES:: - sage: L = Link([[1, 11, 2, 10], [6, 2, 7, 3], [3, 12, 4, 9], [9, 5, 10, 6], [8, 1, 5, 4], [11, 8, 12, 7]]) + sage: L = Link([[1, 10, 2, 11], [6, 3, 7, 2], [3, 9, 4, 12], [9, 6, 10, 5], [8, 4, 5, 1], [11, 7, 12, 8]]) sage: L.oriented_gauss_code() [[[-1, 2, -3, 5], [4, -2, 6, -5], [-4, 1, -6, 3]], [-1, 1, 1, 1, -1, -1]] - sage: L = Link([[1, 4, 2, 3], [6, 1, 3, 2], [7, 4, 8, 5], [5, 8, 6, 7]]) + sage: L = Link([[1, 3, 2, 4], [6, 2, 3, 1], [7, 5, 8, 4], [5, 7, 6, 8]]) sage: L.oriented_gauss_code() [[[-1, 2], [-3, 4], [1, 3, -4, -2]], [-1, -1, 1, 1]] sage: B = BraidGroup(8) @@ -1295,10 +1323,10 @@ def oriented_gauss_code(self): for i, j in enumerate(pd): if orient[i] == -1: crossing_info[(j[0], -1, i + 1)] = j[2] - crossing_info[(j[3], 1, i + 1)] = j[1] + crossing_info[(j[1], 1, i + 1)] = j[3] elif orient[i] == 1: crossing_info[(j[0], -1, i + 1)] = j[2] - crossing_info[(j[1], 1, i + 1)] = j[3] + crossing_info[(j[3], 1, i + 1)] = j[1] edges = {} cross_number = {} for i, j in crossing_info.items(): @@ -1327,27 +1355,34 @@ def pd_code(self): The planar diagram is returned in the following format. We construct the crossing by starting with the entering component - of the undercrossing, move in the clockwise direction and then - generate the list. If the crossing is given by `[a, b, c, d]`, - then we interpret this information as: + of the undercrossing, move in the anti-clockwise direction (see the + note below) and then generate the list. If the crossing is given by + `[a, b, c, d]`, then we interpret this information as: 1. `a` is the entering component of the undercrossing; 2. `b, d` are the components of the overcrossing; 3. `c` is the leaving component of the undercrossing. + .. NOTE:: + + Until version 10.0 the convention to read the ``PD`` code has been + to list the components in clockwise direction. As of version 10.1 + the convention has changed, since it was opposite to the usage in + most other places. + EXAMPLES:: sage: L = Link([[[1, -2, 3, -4, 2, -1, 4, -3]], [1, 1, -1, -1]]) sage: L.pd_code() - [[6, 1, 7, 2], [2, 5, 3, 6], [8, 4, 1, 3], [4, 8, 5, 7]] + [[6, 2, 7, 1], [2, 6, 3, 5], [8, 3, 1, 4], [4, 7, 5, 8]] sage: B = BraidGroup(2) sage: b = B([1, 1, 1, 1, 1]) sage: L = Link(b) sage: L.pd_code() - [[2, 1, 3, 4], [4, 3, 5, 6], [6, 5, 7, 8], [8, 7, 9, 10], [10, 9, 1, 2]] + [[2, 4, 3, 1], [4, 6, 5, 3], [6, 8, 7, 5], [8, 10, 9, 7], [10, 2, 1, 9]] sage: L = Link([[[2, -1], [1, -2]], [1, 1]]) sage: L.pd_code() - [[2, 3, 1, 4], [4, 1, 3, 2]] + [[2, 4, 1, 3], [4, 2, 3, 1]] sage: L = Link([[1, 2, 3, 3], [2, 4, 5, 5], [4, 1, 7, 7]]) sage: L.pd_code() [[1, 2, 3, 3], [2, 4, 5, 5], [4, 1, 7, 7]] @@ -1380,11 +1415,11 @@ def pd_code(self): crossing_dic = {} for i,x in enumerate(oriented_gauss_code[1]): if x == -1: - crossing_dic[i + 1] = [d_dic[-(i + 1)][0], d_dic[i + 1][1], - d_dic[-(i + 1)][1], d_dic[i + 1][0]] - elif x == 1: crossing_dic[i + 1] = [d_dic[-(i + 1)][0], d_dic[i + 1][0], d_dic[-(i + 1)][1], d_dic[i + 1][1]] + elif x == 1: + crossing_dic[i + 1] = [d_dic[-(i + 1)][0], d_dic[i + 1][1], + d_dic[-(i + 1)][1], d_dic[i + 1][0]] elif len(oriented_gauss_code[0]) == 1: for i, j in enumerate(oriented_gauss_code[0][0]): d_dic[j] = [i + 1, i + 2] @@ -1392,11 +1427,11 @@ def pd_code(self): crossing_dic = {} for i, x in enumerate(oriented_gauss_code[1]): if x == -1: - crossing_dic[i + 1] = [d_dic[-(i + 1)][0], d_dic[i + 1][1], - d_dic[-(i + 1)][1], d_dic[i + 1][0]] - elif x == 1: crossing_dic[i + 1] = [d_dic[-(i + 1)][0], d_dic[i + 1][0], d_dic[-(i + 1)][1], d_dic[i + 1][1]] + elif x == 1: + crossing_dic[i + 1] = [d_dic[-(i + 1)][0], d_dic[i + 1][1], + d_dic[-(i + 1)][1], d_dic[i + 1][0]] else: crossing_dic = {} @@ -1412,10 +1447,10 @@ def pd_code(self): for i in b: if i > 0: pd.append( - [strings[i], strings[i - 1], strings_max + 1, strings_max + 2]) + [strings[i], strings_max + 2, strings_max + 1, strings[i - 1]]) else: pd.append( - [strings[abs(i) - 1], strings_max + 1, strings_max + 2, strings[abs(i)]]) + [strings[abs(i) - 1], strings[abs(i)], strings_max + 2, strings_max + 1]) strings[abs(i) - 1] = strings_max + 1 strings[abs(i)] = strings_max + 2 strings_max = strings_max + 2 @@ -1485,7 +1520,7 @@ def dowker_notation(self): """ pd = self.pd_code() orient = self.orientation() - dn = [(i[0], i[3]) if orient[j] == -1 else (i[0], i[1]) + dn = [(i[0], i[1]) if orient[j] == -1 else (i[0], i[3]) for j, i in enumerate(pd)] return dn @@ -1690,7 +1725,7 @@ def number_of_components(self): G.add_vertices(set(flatten(pd))) for c in pd: G.add_edge(c[0], c[2]) - G.add_edge(c[1], c[3]) + G.add_edge(c[3], c[1]) return G.connected_components_number() def is_knot(self): @@ -2130,24 +2165,24 @@ def orientation(self): EXAMPLES:: - sage: L = Link([[1, 4, 5, 2], [3, 5, 6, 7], [4, 8, 9, 6], [7, 9, 10, 11], [8, 1, 13, 10], [11, 13, 2, 3]]) + sage: L = Link([[1, 2, 5, 4], [3, 7, 6, 5], [4, 6, 9, 8], [7, 11, 10, 9], [8, 10, 13, 1], [11, 3, 2, 13]]) sage: L.orientation() [-1, 1, -1, 1, -1, 1] - sage: L = Link([[1, 7, 2, 6], [7, 3, 8, 2], [3, 11, 4, 10], [11, 5, 12, 4], [14, 5, 1, 6], [13, 9, 14, 8], [12, 9, 13, 10]]) + sage: L = Link([[1, 6, 2, 7], [7, 2, 8, 3], [3, 10, 4, 11], [11, 4, 12, 5], [14, 6, 1, 5], [13, 8, 14, 9], [12, 10, 13, 9]]) sage: L.orientation() [-1, -1, -1, -1, 1, -1, 1] - sage: L = Link([[1, 2, 3, 3], [2, 4, 5, 5], [4, 1, 7, 7]]) + sage: L = Link([[1, 3, 3, 2], [2, 5, 5, 4], [4, 7, 7, 1]]) sage: L.orientation() [-1, -1, -1] """ directions = self._directions_of_edges()[0] orientation = [] for C in self.pd_code(): - if C[0] == C[1] or C[2] == C[3]: + if C[0] == C[3] or C[2] == C[1]: orientation.append(-1) - elif C[1] == C[2] or C[0] == C[3]: + elif C[3] == C[2] or C[0] == C[1]: orientation.append(1) - elif directions[C[1]] == C: + elif directions[C[3]] == C: orientation.append(-1) else: orientation.append(1) @@ -2193,11 +2228,12 @@ def seifert_circles(self): sage: A.seifert_circles() [[3], [7], [1, 5], [2, 4], [6, 8]] """ - available_segments = set(flatten(self.pd_code())) + pd = self.pd_code() + available_segments = set(flatten(pd)) result = [] # detect looped segments. They must be their own seifert circles for a in available_segments: - if any(C.count(a) > 1 for C in self.pd_code()): + if any(C.count(a) > 1 for C in pd): result.append([a]) # remove the looped segments from the available for a in result: @@ -2212,11 +2248,11 @@ def seifert_circles(self): par = [] while a not in par: par.append(a) - posnext = C[(C.index(a) + 1) % 4] + posnext = C[(C.index(a) - 1) % 4] if tails[posnext] == C and not [posnext] in result: a = posnext else: - a = C[(C.index(a) - 1) % 4] + a = C[(C.index(a) + 1) % 4] if a in available_segments: available_segments.remove(a) C = heads[a] @@ -2251,7 +2287,7 @@ def regions(self): sage: L = Link([[[1, -2, 3, -4], [-1, 5, -3, 2, -5, 4]], [-1, 1, 1, -1, -1]]) sage: L.regions() [[10, -4, -7], [9, 7, -3], [8, 3], [6, -9, -2], [5, 2, -8, 4], [1, -5], [-1, -10, -6]] - sage: L = Link([[1, 2, 3, 3], [2, 5, 4, 4], [5, 7, 6, 6], [7, 1, 8, 8]]) + sage: L = Link([[1, 3, 3, 2], [2, 4, 4, 5], [5, 6, 6, 7], [7, 8, 8, 1]]) sage: L.regions() [[-3], [-4], [-6], [-8], [7, 1, 2, 5], [-1, 8, -7, 6, -5, 4, -2, 3]] @@ -2274,7 +2310,7 @@ def regions(self): raise NotImplementedError("can only have one isolated component") pd = self.pd_code() if len(pd) == 1: - if pd[0][0] == pd[0][1]: + if pd[0][0] == pd[0][3]: return [[-pd[0][2]], [pd[0][0]], [pd[0][2], -pd[0][0]]] else: return [[pd[0][2]], [-pd[0][0]], [-pd[0][2], pd[0][0]]] @@ -2288,7 +2324,7 @@ def regions(self): for edge in loops: cros = heads[edge] - if cros[1] == edge: + if cros[3] == edge: regions.append([edge]) else: regions.append([-edge]) @@ -2307,13 +2343,13 @@ def regions(self): else: cros = tails[-edge] ind = cros.index(-edge) - next_edge = cros[(ind + 1) % 4] + next_edge = cros[(ind - 1) % 4] if [next_edge] in regions: region.append(-next_edge) - next_edge = cros[(ind - 1) % 4] + next_edge = cros[(ind + 1) % 4] elif [-next_edge] in regions: region.append(next_edge) - next_edge = cros[(ind - 1) % 4] + next_edge = cros[(ind + 1) % 4] if tails[next_edge] == cros: edge = next_edge else: @@ -2357,9 +2393,9 @@ def mirror_image(self): sage: K2 = K.mirror_image(); K2 Knot represented by 3 crossings sage: K.pd_code() - [[4, 1, 5, 2], [2, 5, 3, 6], [6, 3, 1, 4]] - sage: K2.pd_code() [[4, 2, 5, 1], [2, 6, 3, 5], [6, 4, 1, 3]] + sage: K2.pd_code() + [[4, 1, 5, 2], [2, 5, 3, 6], [6, 3, 1, 4]] .. PLOT:: :width: 300 px @@ -2429,9 +2465,9 @@ def reverse(self): a non reversable knot:: - sage: K8_17 = Knot([[6, 2, 7, 1], [14, 8, 15, 7], [8, 3, 9, 4], - ....: [2, 13, 3, 14], [12, 5, 13, 6], [4, 9, 5, 10], - ....: [16, 12, 1, 11], [10, 16, 11, 15]]) + sage: K8_17 = Knot([[6, 1, 7, 2], [14, 7, 15, 8], [8, 4, 9, 3], + ....: [2, 14, 3, 13], [12, 6, 13, 5], [4, 10, 5, 9], + ....: [16, 11, 1, 12], [10, 15, 11, 16]]) sage: K8_17r = K8_17.reverse() sage: b = K8_17.braid(); b s0^2*s1^-1*(s1^-1*s0)^2*s1^-1 @@ -2708,7 +2744,7 @@ def _bracket(self): if not pd_code: return t.parent().one() if len(pd_code) == 1: - if pd_code[0][0] == pd_code[0][1]: + if pd_code[0][0] == pd_code[0][3]: return -t**(-3) else: return -t**3 @@ -2716,42 +2752,42 @@ def _bracket(self): cross = pd_code[0] rest = [list(vertex) for vertex in pd_code[1:]] [a, b, c, d] = cross - if a == b and c == d and len(rest) > 0: + if a == d and c == b and len(rest) > 0: return (~t + t**(-5)) * Link(rest)._bracket() - elif a == d and c == b and len(rest) > 0: + elif a == b and c == d and len(rest) > 0: return (t + t**5) * Link(rest)._bracket() - elif a == b: + elif a == d: for cross in rest: - if d in cross: - cross[cross.index(d)] = c + if b in cross: + cross[cross.index(b)] = c return -t**(-3) * Link(rest)._bracket() - elif a == d: + elif a == b: for cross in rest: if c in cross: - cross[cross.index(c)] = b - return -t**3 * Link(rest)._bracket() - elif c == b: - for cross in rest: - if d in cross: - cross[cross.index(d)] = a + cross[cross.index(c)] = d return -t**3 * Link(rest)._bracket() elif c == d: for cross in rest: if b in cross: cross[cross.index(b)] = a + return -t**3 * Link(rest)._bracket() + elif c == b: + for cross in rest: + if d in cross: + cross[cross.index(d)] = a return -t**(-3) * Link(rest)._bracket() else: rest_2 = [list(vertex) for vertex in rest] for cross in rest: - if d in cross: - cross[cross.index(d)] = a + if b in cross: + cross[cross.index(b)] = a if c in cross: - cross[cross.index(c)] = b + cross[cross.index(c)] = d for cross in rest_2: - if d in cross: - cross[cross.index(d)] = c if b in cross: - cross[cross.index(b)] = a + cross[cross.index(b)] = c + if d in cross: + cross[cross.index(d)] = a return t * Link(rest)._bracket() + ~t * Link(rest_2)._bracket() @cached_method @@ -2834,7 +2870,7 @@ def homfly_polynomial(self, var1=None, var2=None, normalization='lm'): The Hopf link:: - sage: L = Link([[1,3,2,4],[4,2,3,1]]) + sage: L = Link([[1,4,2,3],[4,1,3,2]]) sage: L.homfly_polynomial('x', 'y') -x^-1*y + x^-1*y^-1 + x^-3*y^-1 @@ -2842,16 +2878,16 @@ def homfly_polynomial(self, var1=None, var2=None, normalization='lm'): has been changed. Therefore we substitute `x \mapsto L^{-1}` and `y \mapsto M`:: - sage: L = Link([[1,4,2,3], [4,1,3,2]]) + sage: L = Link([[1,3,2,4], [4,2,3,1]]) sage: L.homfly_polynomial() L^3*M^-1 - L*M + L*M^-1 - sage: L = Link([[1,4,2,3], [4,1,3,2]]) + sage: L = Link([[1,3,2,4], [4,2,3,1]]) sage: L.homfly_polynomial(normalization='az') a^3*z^-1 - a*z - a*z^-1 The figure-eight knot:: - sage: L = Link([[2,1,4,5], [5,6,7,3], [6,4,1,9], [9,2,3,7]]) + sage: L = Link([[2,5,4,1], [5,3,7,6], [6,9,1,4], [9,7,3,2]]) sage: L.homfly_polynomial() -L^2 + M^2 - 1 - L^-2 sage: L.homfly_polynomial('a', 'z', 'az') @@ -2888,7 +2924,7 @@ def homfly_polynomial(self, var1=None, var2=None, normalization='lm'): This works with isolated components:: sage: L = Link([[[1, -1], [2, -2]], [1, 1]]) - sage: L2 = Link([[1, 3, 2, 4], [2, 3, 1, 4]]) + sage: L2 = Link([[1, 4, 2, 3], [2, 4, 1, 3]]) sage: L2.homfly_polynomial() -L*M^-1 - L^-1*M^-1 sage: L.homfly_polynomial() @@ -2989,7 +3025,7 @@ def links_gould_polynomial(self, varnames='t0, t1'): EXAMPLES:: - sage: Hopf = Link([[1, 4, 2, 3], [4, 1, 3, 2]]) + sage: Hopf = Link([[1, 3, 2, 4], [4, 2, 3, 1]]) sage: Hopf.links_gould_polynomial() -1 + t1^-1 + t0^-1 - t0^-1*t1^-1 """ @@ -3044,7 +3080,7 @@ def _coloring_matrix(self, n): crossing = crossings[i] for j in range(di): arc = arcs[j] - if crossing[1] in arc: + if crossing[3] in arc: M[i, j] += 2 if crossing[0] in arc: M[i, j] -= 1 @@ -3120,7 +3156,7 @@ def colorings(self, n): {(1, 2): 2, (3, 4): 0, (5, 6): 1}, {(1, 2): 2, (3, 4): 1, (5, 6): 0}] sage: K.pd_code() - [[4, 1, 5, 2], [2, 5, 3, 6], [6, 3, 1, 4]] + [[4, 2, 5, 1], [2, 6, 3, 5], [6, 4, 1, 3]] sage: K.arcs('pd') [[1, 2], [3, 4], [5, 6]] @@ -3321,8 +3357,9 @@ def plot(self, gap=0.1, component_gap=0.5, solver=None, sage: L.plot(solver='Gurobi') # optional - Gurobi Graphics object consisting of ... graphics primitives """ + pd_code = self.pd_code() if type(color) is not dict: - coloring = {int(i): color for i in set(flatten(self.pd_code()))} + coloring = {int(i): color for i in set(flatten(pd_code))} else: from sage.plot.colors import rainbow ncolors = len(set(color.values())) @@ -3359,7 +3396,7 @@ def plot(self, gap=0.1, component_gap=0.5, solver=None, from sage.plot.circle import circle # Special case for the unknot - if not self.pd_code(): + if not pd_code: return circle((0,0), ZZ(1)/ZZ(2), color=color, **kwargs) # The idea is the same followed in spherogram, but using MLP instead of @@ -3369,7 +3406,7 @@ def plot(self, gap=0.1, component_gap=0.5, solver=None, # with straight angles, and using the minimal number of bends. regions = sorted(self.regions(), key=len) regions = regions[:-1] - edges = list(set(flatten(self.pd_code()))) + edges = list(set(flatten(pd_code))) edges.sort() MLP = MixedIntegerLinearProgram(maximization=False, solver=solver) # v will be the list of variables in the MLP problem. There will be @@ -3493,8 +3530,8 @@ def plot(self, gap=0.1, component_gap=0.5, solver=None, v = MLP.get_values(v) lengths = {piece: sum(v[a] for a in pieces[piece]) for piece in pieces} image = line([], **kwargs) - crossings = {tuple(self.pd_code()[0]): (0, 0, 0)} - availables = self.pd_code()[1:] + crossings = {tuple(pd_code[0]): (0, 0, 0)} + availables = pd_code[1:] used_edges = [] ims = line([], **kwargs) while len(used_edges) < len(edges): @@ -3511,14 +3548,14 @@ def plot(self, gap=0.1, component_gap=0.5, solver=None, e = c[j] kwargs['color'] = coloring[e] used_edges.append(e) - direction = (crossings[c][2] - c.index(e)) % 4 - orien = self.orientation()[self.pd_code().index(list(c))] + direction = (crossings[c][2] + c.index(e)) % 4 + orien = self.orientation()[pd_code.index(list(c))] if s[edges.index(e)] < 0: turn = -1 else: turn = 1 lengthse = [lengths[(e,k)] for k in range(abs(s[edges.index(e)])+1)] - if c.index(e) == 0 or (c.index(e) == 1 and orien == 1) or (c.index(e) == 3 and orien == -1): + if c.index(e) == 0 or (c.index(e) == 3 and orien == 1) or (c.index(e) == 1 and orien == -1): turn = -turn lengthse.reverse() tailshort = (c.index(e) % 2 == 0) @@ -3546,8 +3583,8 @@ def plot(self, gap=0.1, component_gap=0.5, solver=None, c2 = [ee for ee in availables if e in ee] if len(c2) == 1: availables.remove(c2[0]) - crossings[tuple(c2[0])] = (x1, y1, (direction + c2[0].index(e) + 2) % 4) - c2 = [ee for ee in self.pd_code() if e in ee and ee != list(c)] + crossings[tuple(c2[0])] = (x1, y1, (direction - c2[0].index(e) + 2) % 4) + c2 = [ee for ee in pd_code if e in ee and ee != list(c)] if not c2: headshort = not tailshort else: @@ -3612,6 +3649,7 @@ def delta(u, v): ims += sum(line(a[0], **kwargs) for a in im) return image + def _markov_move_cmp(self, braid): r""" Return whether ``self`` can be transformed to the closure of ``braid`` @@ -3631,9 +3669,9 @@ def _markov_move_cmp(self, braid): EXAMPLES:: sage: b = BraidGroup(4)((1, 2, -3, 2, 2, 2, 2, 2, 2, -1, 2, 3, 2)) - sage: L = Link([[2, 1, 4, 5], [5, 4, 6, 7], [7, 6, 8, 9], [9, 8, 10, 11], - ....: [11, 10, 12, 13], [13, 12, 14, 15], [15, 14, 16, 17], - ....: [3, 17, 18, 19], [16, 1, 21, 18], [19, 21, 2, 3]]) + sage: L = Link([[2, 5, 4, 1], [5, 7, 6, 4], [7, 9, 8, 6], [9, 11, 10, 8], + ....: [11, 13, 12, 10], [13, 15, 14, 12], [15, 17, 16, 14], + ....: [3, 19, 18, 17], [16, 18, 21, 1], [19, 3, 2, 21]]) sage: L._markov_move_cmp(b) # both are isotopic to ``9_3`` True sage: bL = L.braid(); bL @@ -3711,7 +3749,8 @@ def _knotinfo_matching_list(self): """ from sage.knots.knotinfo import KnotInfoSeries - cr = len(self.pd_code()) + pd_code = self.pd_code() + cr = len(pd_code) co = self.number_of_components() # set the limits for the KnotInfoSeries @@ -3749,16 +3788,12 @@ def _knotinfo_matching_list(self): ln = Sn.lower_list(oriented=True, comp=co, det=det, homfly=Hp) l = sorted(list(set(la + ln))) - pdm = [[a[0], a[3], a[2], a[1]] for a in self.pd_code() ] br = self.braid() br_ind = br.strands() res = [] for L in l: - if L.pd_notation() == pdm: - # note that KnotInfo pd_notation works counter clockwise. Therefore, - # to compensate this we compare with the mirrored pd_code. See also, - # docstring of :meth:`link` of :class:`~sage.knots.knotinfo.KnotInfoBase`. + if L.pd_notation() == pd_code: return [L], True # pd_notation is unique in the KnotInfo database if L.braid_index() <= br_ind: @@ -3824,9 +3859,9 @@ def get_knotinfo(self, mirror_version=True, unique=True): EXAMPLES:: sage: from sage.knots.knotinfo import KnotInfo - sage: L = Link([[4,2,5,1], [10,3,11,4], [5,16,6,17], [7,12,8,13], - ....: [18,9,19,10], [2,11,3,12], [13,20,14,21], [15,6,16,7], - ....: [22,18,1,17], [8,19,9,20], [21,14,22,15]]) + sage: L = Link([[4,1,5,2], [10,4,11,3], [5,17,6,16], [7,13,8,12], + ....: [18,10,19,9], [2,12,3,11], [13,21,14,20], [15,7,16,6], + ....: [22,17,1,18], [8,20,9,19], [21,15,22,14]]) sage: L.get_knotinfo() # optional - database_knotinfo (, True) @@ -3845,7 +3880,7 @@ def get_knotinfo(self, mirror_version=True, unique=True): ... NotImplementedError: this knot having more than 12 crossings cannot be determined - sage: Link([[1, 5, 2, 4], [3, 1, 4, 8], [5, 3, 6, 2], [6, 9, 7, 10], [10, 7, 9, 8]]) + sage: Link([[1, 4, 2, 5], [3, 8, 4, 1], [5, 2, 6, 3], [6, 10, 7, 9], [10, 8, 9, 7]]) Link with 2 components represented by 5 crossings sage: _.get_knotinfo() Traceback (most recent call last): @@ -4082,6 +4117,7 @@ def answer_list(l): raise NotImplementedError('this link cannot be uniquely determined%s' %non_unique_hint) + self_m = self.mirror_image() ls, proved_s = self._knotinfo_matching_list() lm, proved_m = self_m._knotinfo_matching_list() @@ -4128,6 +4164,7 @@ def answer_list(l): return answer_list(l) + def is_isotopic(self, other): r""" Check whether ``self`` is isotopic to ``other``. From ff44af34b48fc447b4be591da407a660230899f9 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 22 May 2023 18:32:15 +0200 Subject: [PATCH 2/2] 35665: add some docs and fix pycodestyle --- src/sage/knots/link.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index ed251cd9965..260022d05b7 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -40,7 +40,7 @@ - Sebastian Oehms (October 2020, add :meth:`get_knotinfo` and :meth:`is_isotopic`) - Sebastian Oehms (May 2022): add :meth:`links_gould_polynomial` - Sebastian Oehms (May 2023): change the convention about the ``pd_code`` from - clockwise to anti-clockwise. + clockwise to anti-clockwise (see :trac:`35665`). """ # **************************************************************************** @@ -1368,7 +1368,10 @@ def pd_code(self): Until version 10.0 the convention to read the ``PD`` code has been to list the components in clockwise direction. As of version 10.1 the convention has changed, since it was opposite to the usage in - most other places. + most other places. + + Thus, if you use ``PD`` codes from former Sage releases with this + version you should check for the correct mirror type. EXAMPLES::