From 730833474e9d5e58848c7653930620ac8153f762 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 14 Dec 2023 19:00:32 +0100 Subject: [PATCH 1/8] link_braid_method_loops initial --- src/sage/knots/link.py | 103 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 10 deletions(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index f110091f20b..c2286cfa6f6 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -610,10 +610,17 @@ def __ne__(self, other): """ return not self.__eq__(other) - def braid(self): + def braid(self, remove_loops=False): r""" Return a braid representation of ``self``. + INPUT: + + - ``remove_loops`` -- boolean (default: ``False``). If set to ``True`` + loops will be removed first. This can reduce the number of strands + needed for an ambient isotopic braid closure. However, this can lead + to a loss of the regular isotopy. + OUTPUT: an element in the braid group .. WARNING:: @@ -634,6 +641,14 @@ def braid(self): sage: L.braid() (s0*s1^-1)^2*s1^-1 + using `remove_loops=True`:: + + sage: L = Link([[2, 7, 1, 1], [7, 3, 9, 2], [4, 11, 3, 9], [11, 5, 5, 4]]) + sage: L.braid() + s0*s1^-1*s2*s3^-1 + sage: L.braid(remove_loops=True) + 1 + TESTS:: sage: L = Link([]) @@ -648,7 +663,20 @@ def braid(self): sage: A = Link([[[1, 2, -2, -1, -3, -4, 4, 3]], [1, 1, 1, 1]]) sage: A.braid() s0*s1*s2*s3 + + Check that ??? is solved:: + + sage: L = Link([[1, 7, 2, 6], [3, 1, 4, 8], [5, 5, 6, 4], [7, 3, 8, 2]]) + sage: L.braid() + s0^3*s1*s0*s1^-1 + sage: L.braid(remove_loops=True) + s^3 """ + if remove_loops: + L = self.remove_loops() + if L != self: + return L.braid(remove_loops=remove_loops) + if self._braid is not None: return self._braid @@ -657,8 +685,8 @@ def braid(self): if len(comp) > 1: L1 = Link(comp[0]) L2 = Link(flatten(comp[1:], max_level=1)) - b1 = L1.braid() - b2 = L2.braid() + b1 = L1.braid(remove_loops=remove_loops) + b2 = L2.braid(remove_loops=remove_loops) n1 = b1.parent().strands() n2 = b2.parent().strands() t1 = list(b1.Tietze()) @@ -667,13 +695,26 @@ def braid(self): self._braid = B(t1 + t2) return self._braid - # look for possible Vogel moves, perform them and call recursively to the modified link pd_code = self.pd_code() if not pd_code: B = BraidGroup(2) self._braid = B.one() return self._braid + # look for possible Vogel moves, perform them and call recursively to the modified link + def idx(cross, edge): + r""" + Return the index of an edge in a crossing taking loops into acount. + A loop appears as an edge which occurs twice in the crossing. + In all cases the second occurence is the correct one needed in + the Vogel algorithm. + """ + i = cross.index(edge) + if cross.count(edge) > 1: + return cross.index(edge, i+1) + else: + return i + seifert_circles = self.seifert_circles() newedge = max(flatten(pd_code)) + 1 for region in self.regions(): @@ -702,12 +743,12 @@ def braid(self): # C1 C2 existing crossings # ------------------------------------------------- C1 = newPD[newPD.index(heads[a])] - C1[C1.index(a)] = newedge + 1 + C1[idx(C1, a)] = newedge + 1 C2 = newPD[newPD.index(tails[b])] - C2[C2.index(b)] = newedge + 2 + C2[idx(C2, b)] = newedge + 2 newPD.append([newedge + 3, newedge, b, a]) # D newPD.append([newedge + 2, newedge, newedge + 3, newedge + 1]) # E - self._braid = Link(newPD).braid() + self._braid = Link(newPD).braid(remove_loops=remove_loops) return self._braid else: # ------------------------------------------------- @@ -723,12 +764,12 @@ def braid(self): # / \ # ------------------------------------------------- C1 = newPD[newPD.index(heads[-a])] - C1[C1.index(-a)] = newedge + 1 + C1[idx(C1, -a)] = newedge + 1 C2 = newPD[newPD.index(tails[-b])] - C2[C2.index(-b)] = newedge + 2 + C2[idx(C2, -b)] = newedge + 2 newPD.append([newedge + 2, newedge + 1, newedge + 3, newedge]) # D newPD.append([newedge + 3, -a, -b, newedge]) # E - self._braid = Link(newPD).braid() + self._braid = Link(newPD).braid(remove_loops=remove_loops) return self._braid # We are in the case where no Vogel moves are necessary. @@ -2362,6 +2403,48 @@ def regions(self): regions.append(region) return regions + def remove_loops(self): + r""" + Return an ambient isotopic link in which all loops are removed. + + EXAMPLES:: + + sage: b = BraidGroup(4)((3, 2, -1, -1)) + sage: L = Link(b) + sage: L.remove_loops() + Link with 2 components represented by 2 crossings + sage: K4 = Link([[1, 7, 2, 6], [3, 1, 4, 8], [5, 5, 6, 4], [7, 3, 8, 2]]) + sage: K3 = K4.remove_loops() + sage: K3.pd_code() + [[1, 7, 2, 4], [3, 1, 4, 8], [7, 3, 8, 2]] + sage: U = Link([[1, 2, 2, 1]]) + sage: U.remove_loops() + Link with 1 component represented by 0 crossings + """ + pd = self.pd_code() + from copy import copy + new_pd = [copy(cr) for cr in pd if len(set(cr)) == 4] + if not new_pd: + # trivial knot + return type(self)([]) + elif pd == new_pd: + # no loops detected + return self + from sage.sets.set import Set + new_edges = Set(flatten(new_pd)) + for cr in Set(pd).difference(Set(new_pd)): + # cr is a loop crossing + rem = list(Set(cr).intersection(new_edges)) + if len(rem) == 2: + # put remaining edges together + a, b = sorted(rem) + for ncr in new_pd: + if b in ncr: + ncr[ncr.index(b)] = a + break + res = type(self)(new_pd) + return res.remove_loops() + @cached_method def mirror_image(self): r""" From 9e2ace6bb5fc5add319dd2b0805f3105484e5ebe Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 14 Dec 2023 19:55:23 +0100 Subject: [PATCH 2/8] add PR references --- src/sage/knots/link.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index c2286cfa6f6..ca4423f5493 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -664,7 +664,7 @@ def braid(self, remove_loops=False): sage: A.braid() s0*s1*s2*s3 - Check that ??? is solved:: + Check that `PR 36884 `__ is solved:: sage: L = Link([[1, 7, 2, 6], [3, 1, 4, 8], [5, 5, 6, 4], [7, 3, 8, 2]]) sage: L.braid() @@ -704,10 +704,10 @@ def braid(self, remove_loops=False): # look for possible Vogel moves, perform them and call recursively to the modified link def idx(cross, edge): r""" - Return the index of an edge in a crossing taking loops into acount. + Return the index of an edge in a crossing taking loops into account. A loop appears as an edge which occurs twice in the crossing. - In all cases the second occurence is the correct one needed in - the Vogel algorithm. + In all cases the second occurrence is the correct one needed in + the Vogel algorithm (see `PR 36884 `__). """ i = cross.index(edge) if cross.count(edge) > 1: From 2b7b778f44478cc3b7bd9c339f0ede6f16294bb1 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 15 Dec 2023 08:06:33 +0100 Subject: [PATCH 3/8] 36884: fix documentation --- src/sage/knots/link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index ca4423f5493..4e544c716cf 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -641,7 +641,7 @@ def braid(self, remove_loops=False): sage: L.braid() (s0*s1^-1)^2*s1^-1 - using `remove_loops=True`:: + using ``remove_loops=True``:: sage: L = Link([[2, 7, 1, 1], [7, 3, 9, 2], [4, 11, 3, 9], [11, 5, 5, 4]]) sage: L.braid() From 40934af440bb7d18c5270481b56ab99588cf160e Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Mon, 18 Dec 2023 09:14:00 +0100 Subject: [PATCH 4/8] 36884: issue reference Co-authored-by: Travis Scrimshaw --- src/sage/knots/link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 4e544c716cf..340f8010943 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -664,7 +664,7 @@ def braid(self, remove_loops=False): sage: A.braid() s0*s1*s2*s3 - Check that `PR 36884 `__ is solved:: + Check that :issue:`36884` is solved:: sage: L = Link([[1, 7, 2, 6], [3, 1, 4, 8], [5, 5, 6, 4], [7, 3, 8, 2]]) sage: L.braid() From 0cc64c18e569efa2ab8aec599a273eb31319c444 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Mon, 18 Dec 2023 09:16:01 +0100 Subject: [PATCH 5/8] 36884: doctest formatting Co-authored-by: Travis Scrimshaw --- src/sage/knots/link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 340f8010943..04a0c8c0c1b 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -2409,7 +2409,7 @@ def remove_loops(self): EXAMPLES:: - sage: b = BraidGroup(4)((3, 2, -1, -1)) + sage: b = BraidGroup(4)((3, 2, -1, -1)) sage: L = Link(b) sage: L.remove_loops() Link with 2 components represented by 2 crossings From 716e0e7a7324d566ff81b12677f763919903a4a2 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Mon, 18 Dec 2023 09:17:45 +0100 Subject: [PATCH 6/8] 36884: replace copy Co-authored-by: Travis Scrimshaw --- src/sage/knots/link.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 04a0c8c0c1b..c88a70cddd6 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -2422,8 +2422,7 @@ def remove_loops(self): Link with 1 component represented by 0 crossings """ pd = self.pd_code() - from copy import copy - new_pd = [copy(cr) for cr in pd if len(set(cr)) == 4] + new_pd = [list(cr) for cr in pd if len(set(cr)) == 4] if not new_pd: # trivial knot return type(self)([]) From e0b5e55a624c30a4f8d9582b8e9601ac7c93f749 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 18 Dec 2023 18:44:32 +0100 Subject: [PATCH 7/8] 36884: new list loop_crossings --- src/sage/knots/link.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index c88a70cddd6..a0ad819a9f7 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -707,7 +707,7 @@ def idx(cross, edge): Return the index of an edge in a crossing taking loops into account. A loop appears as an edge which occurs twice in the crossing. In all cases the second occurrence is the correct one needed in - the Vogel algorithm (see `PR 36884 `__). + the Vogel algorithm (see :issue:`36884`). """ i = cross.index(edge) if cross.count(edge) > 1: @@ -2422,18 +2422,21 @@ def remove_loops(self): Link with 1 component represented by 0 crossings """ pd = self.pd_code() - new_pd = [list(cr) for cr in pd if len(set(cr)) == 4] + new_pd = [] + loop_crossings = [] + for cr in pd: + if len(set(cr)) == 4: + new_pd.append(list(cr)) + else: + loop_crossings.append(cr) if not new_pd: # trivial knot return type(self)([]) - elif pd == new_pd: - # no loops detected + elif not loop_crossings: return self - from sage.sets.set import Set - new_edges = Set(flatten(new_pd)) - for cr in Set(pd).difference(Set(new_pd)): - # cr is a loop crossing - rem = list(Set(cr).intersection(new_edges)) + new_edges = flatten(new_pd) + for cr in loop_crossings: + rem = set([e for e in cr if e in new_edges]) if len(rem) == 2: # put remaining edges together a, b = sorted(rem) From f68c72a6daf89a53398589fff48fd74581ceb68a Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Tue, 19 Dec 2023 19:20:52 +0100 Subject: [PATCH 8/8] 36884: treat no loops first Co-authored-by: Travis Scrimshaw --- src/sage/knots/link.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index a0ad819a9f7..eb3d51a883d 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -2429,11 +2429,11 @@ def remove_loops(self): new_pd.append(list(cr)) else: loop_crossings.append(cr) + if not loop_crossings: + return self if not new_pd: # trivial knot return type(self)([]) - elif not loop_crossings: - return self new_edges = flatten(new_pd) for cr in loop_crossings: rem = set([e for e in cr if e in new_edges])