From 679b9efa5ef25ebd487dd4acc3a82d6e64d4d8f3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 23 Mar 2024 13:54:07 +0100 Subject: [PATCH 1/8] fix_link_is_isotopic initial --- src/sage/knots/knotinfo.py | 72 ++++++- src/sage/knots/link.py | 423 +++++++++++++++++++++++++------------ 2 files changed, 350 insertions(+), 145 deletions(-) diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 18587bd8cf0..8deb3fbe37a 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -204,7 +204,7 @@ ....: [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.get_knotinfo() - (, None) + (, ) REFERENCES: @@ -335,6 +335,47 @@ def knotinfo_bool(string): return False raise ValueError('%s is not a KnotInfo boolean') + +class SymmetryMutant(Enum): + r""" + Enum to specify the symmetry mutant link of the prime link listed in the + KnotInfo and LinkInfo databases. From the KnotInfo description page: + + If a knot is viewed as the oriented diffeomorphism + class of an oriented pair, `K = (S_3, S_1)`, with `S_i` + diffeomorphic to `S^i`, there are four oriented knots + associated to any particular knot `K`. In addition to + `K` itself, there is the reverse, `K^r = (S_3, -S_1)`, + the concordance inverse, `-K = (-S_3, -S_1)`, and the + mirror image, `K^m = (-S_3, S_1)`. + """ + itself ='s' + reverse ='r' + concordance_inverse ='mr' + mirror_image ='m' + mixed = 'x' # to be used in connection with KnotInfoSeries + unknown ='?' + + def __gt__(self, other): + r""" + Implement comparison of different items in order to have ``sorted`` work. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import SymmetryMutant + sage: sorted(SymmetryMutant) # indirect doctest + [, + , + , + , + , + ] + """ + if self.__class__ is other.__class__: + return self.value < other.value + return NotImplemented + + # --------------------------------------------------------------------------------- # KnotInfoBase # --------------------------------------------------------------------------------- @@ -946,12 +987,22 @@ def is_reversible(self): sage: KnotInfo.K6_3.is_reversible() True """ - symmetry_type = self.symmetry_type() - if symmetry_type == 'reversible': - return True - if symmetry_type == 'fully amphicheiral': + if self.is_knot(): + symmetry_type = self.symmetry_type() + if symmetry_type == 'reversible': + return True + if symmetry_type == 'fully amphicheiral': + return True + return False + + # revert orientation + b = self.braid() + bt = list(b.Tietze()) + bt.reverse() + br = b.parent()(tuple(bt)) + if b.is_conjugated(br): return True - return False + return None @cached_method def is_amphicheiral(self, positive=False): @@ -2123,6 +2174,9 @@ def recover(mirror, braid): else: l = self.link() if mirror: + if self.is_amphicheiral(): + # no need to test again + return True l = l.mirror_image() def check_result(L, m): @@ -2131,12 +2185,10 @@ def check_result(L, m): """ if L != self: return False - if m is None or m == '?': - return True if mirror: - return m + return m is SymmetryMutant.mirror_image else: - return not m + return m is SymmetryMutant.itself try: L, m = l.get_knotinfo() diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index d39cb97c862..054f1c47eb0 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -2410,16 +2410,16 @@ def remove_loops(self): 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 + 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() new_pd = [] @@ -2579,7 +2579,8 @@ def reverse(self): if self._reverse: return self._reverse - if self._braid: + b = self._braid + if b and len(b.Tietze()) <= len(self.pd_code()): self._reverse = type(self)(self._braid.reverse()) self._reverse._reverse = self return self._reverse @@ -2992,7 +2993,7 @@ def homfly_polynomial(self, var1=None, var2=None, normalization='lm'): Comparison with KnotInfo:: sage: KI, m = K.get_knotinfo(); KI, m - (, False) + (, ) sage: K.homfly_polynomial(normalization='vz') == KI.homfly_polynomial() True @@ -3930,13 +3931,12 @@ def _knotinfo_matching_list(self): EXAMPLES:: - sage: from sage.knots.knotinfo import KnotInfo sage: KnotInfo.L5a1_0.inject() Defining L5a1_0 - sage: L5a1_0.link()._knotinfo_matching_list() - ([], True) - sage: Link(L5a1_0.braid())._knotinfo_matching_list() + sage: ML = L5a1_0.link()._knotinfo_matching_list(); ML ([, ], True) + sage: ML == Link(L5a1_0.braid())._knotinfo_matching_list() + True Care is needed for links having non irreducible HOMFLY-PT polynomials:: @@ -3993,7 +3993,9 @@ def _knotinfo_matching_list(self): res = [] for L in l: if L.pd_notation() == pd_code: - return [L], True # pd_notation is unique in the KnotInfo database + # pd_notation is unique in the KnotInfo database + res.append(L) + continue Lbraid = L.braid() if Lbraid.strands() <= br_ind: @@ -4005,6 +4007,45 @@ def _knotinfo_matching_list(self): return res, True return l, False + def _knotinfo_matching_dict(self): + r""" + Return a dictionary mapping items of the enum :class:`~sage.knots.knotinfo.SymmetryType` + to list of links from the KnotInfo and LinkInfo databases which match + the properties of the according symmetry mutant of ``self`` as much as + possible. + + OUTPUT: + + A pair (``match_lists, proves``) of dictionaries with keys from the + enum :class:`~sage.knots.knotinfo.SymmetryType`. The first dictionary maps these keys to + the corresponding matching list and ``proves`` maps them to booleans + telling if the entries of the corresponding ``match_lists`` are checked + to be isotopic to the symmetry mutant of ``self`` or not. + + EXAMPLES:: + + sage: KnotInfo.L4a1_0.inject() + Defining L4a1_0 + sage: L4a1_0.link()._knotinfo_matching_dict() + ({: [], + : [], + : [], + : []}, + {: True, + : True, + : False, + : False}) + """ + from sage.knots.knotinfo import SymmetryMutant + mutant = {} + mutant[SymmetryMutant.itself] = self + mutant[SymmetryMutant.mirror_image] = self.mirror_image() + mutant[SymmetryMutant.reverse] = self.reverse() + mutant[SymmetryMutant.concordance_inverse] = mutant[SymmetryMutant.mirror_image].reverse() + match_lists = {k: list(mutant[k]._knotinfo_matching_list()[0]) for k in mutant.keys()} + proves = {k: mutant[k]._knotinfo_matching_list()[1] for k in mutant.keys()} + return match_lists, proves + def get_knotinfo(self, mirror_version=True, unique=True): r""" Identify this link as an item of the KnotInfo database (if possible). @@ -4013,7 +4054,7 @@ def get_knotinfo(self, mirror_version=True, unique=True): - ``mirror_version`` -- boolean (default is ``True``). If set to ``False`` the result of the method will be just the instance of :class:`~sage.knots.knotinfo.KnotInfoBase` - (by default the result is a tuple of the instance and a boolean, see + (by default the result is a tuple of the instance and an enum, see explanation of the output below) - ``unique`` -- boolean (default is ``True``). This only affects the case @@ -4023,10 +4064,10 @@ def get_knotinfo(self, mirror_version=True, unique=True): OUTPUT: A tuple ``(K, m)`` where ``K`` is an instance of :class:`~sage.knots.knotinfo.KnotInfoBase` - and ``m`` a boolean (for chiral links) telling if ``self`` corresponds - to the mirrored version of ``K`` or not. The value of ``m`` is ``None`` - for amphicheiral links and ``?`` if it cannot be determined uniquely - and the keyword option ``unique=False`` is given. + and ``m`` an instance of :class:`~sage.knots.knotinfo.SymmetryMutant` + (for chiral links) specifying the symmetry mutant of ``K`` to which + ``self`` is isotopic. The value of ``m`` is ``unknown`` if it cannot + be determined uniquely and the keyword option ``unique=False`` is given. For proper links, if the orientation mutant cannot be uniquely determined, K will be a series of links gathering all links having the same unoriented @@ -4065,11 +4106,22 @@ def get_knotinfo(self, mirror_version=True, unique=True): ....: [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() - (, True) + (, ) sage: K = KnotInfo.K10_25 sage: l = K.link() sage: l.get_knotinfo() - (, False) + (, ) + sage: k11 = KnotInfo.K11n_82.link() + sage: k11m = k11.mirror_image() + sage: k11mr = k11m.reverse() + sage: k11mr.get_knotinfo() + (, ) + sage: k11r = k11.reverse() + sage: k11r.get_knotinfo() + (, ) + sage: k11rm = k11r.mirror_image() + sage: k11rm.get_knotinfo() + (, ) Knots with more than 13 and proper links having more than 11 crossings cannot be identified. In addition non prime links or even links whose @@ -4077,7 +4129,7 @@ def get_knotinfo(self, mirror_version=True, unique=True): sage: b, = BraidGroup(2).gens() sage: Link(b**13).get_knotinfo() # optional - database_knotinfo - (, False) + (, ) sage: Link(b**14).get_knotinfo() Traceback (most recent call last): ... @@ -4096,7 +4148,7 @@ def get_knotinfo(self, mirror_version=True, unique=True): ....: [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.get_knotinfo() - (, None) + (, ) Usage of option ``mirror_version``:: @@ -4113,17 +4165,10 @@ def get_knotinfo(self, mirror_version=True, unique=True): NotImplementedError: this link cannot be uniquely determined use keyword argument `unique` to obtain more details sage: l.get_knotinfo(unique=False) - [(, False), (, False)] - sage: k11 = KnotInfo.K11n_82.link() - sage: k11m = k11.mirror_image() - sage: k11mr = k11m.reverse() - sage: k11mr.get_knotinfo() - Traceback (most recent call last): - ... - NotImplementedError: mirror type of this link cannot be uniquely determined - use keyword argument `unique` to obtain more details - sage: k11mr.get_knotinfo(unique=False) - [(, '?')] + [(, ), + (, ), + (, ), + (, )] sage: t = (1, -2, 1, 1, -2, 1, -2, -2) sage: l8 = Link(BraidGroup(3)(t)) sage: l8.get_knotinfo() @@ -4132,8 +4177,8 @@ def get_knotinfo(self, mirror_version=True, unique=True): NotImplementedError: this link cannot be uniquely determined use keyword argument `unique` to obtain more details sage: l8.get_knotinfo(unique=False) - [(, None), - (, None)] + [(, ), + (, )] sage: t = (2, -3, -3, -2, 3, 3, -2, 3, 1, -2, -2, 1) sage: l12 = Link(BraidGroup(5)(t)) sage: l12.get_knotinfo() @@ -4142,10 +4187,14 @@ def get_knotinfo(self, mirror_version=True, unique=True): NotImplementedError: this link having more than 11 crossings cannot be uniquely determined use keyword argument `unique` to obtain more details sage: l12.get_knotinfo(unique=False) - [(, '?'), - (, None), - (, None), - (, None)] + [(, ), + (, ), + (, ), + (, + ), + (, ), + (, ), + (, )] Furthermore, if the result is a complete series of oriented links having the same unoriented name (according to the note above) the option can be @@ -4153,33 +4202,39 @@ def get_knotinfo(self, mirror_version=True, unique=True): sage: L2a1 = Link(b**2) sage: L2a1.get_knotinfo() - (Series of links L2a1, None) + (Series of links L2a1, ) sage: L2a1.get_knotinfo(unique=False) - [(, True), (, False)] + [(, ), + (, ), + (, ), + (, )] sage: KnotInfo.L5a1_0.inject() Defining L5a1_0 sage: l5 = Link(L5a1_0.braid()) sage: l5.get_knotinfo() - (Series of links L5a1, False) + (Series of links L5a1, ) sage: _[0].inject() Defining L5a1 sage: list(L5a1) [, ] sage: l5.get_knotinfo(unique=False) - [(, False), (, False)] + [(, ), + (, ), + (, ), + (, )] Clarifying the series around the Perko pair (:wikipedia:`Perko_pair`):: sage: for i in range(160, 166): # optional - database_knotinfo ....: K = Knots().from_table(10, i) ....: print('%s_%s' %(10, i), '--->', K.get_knotinfo()) - 10_160 ---> (, False) - 10_161 ---> (, True) - 10_162 ---> (, False) - 10_163 ---> (, False) - 10_164 ---> (, False) - 10_165 ---> (, True) + 10_160 ---> (, ) + 10_161 ---> (, ) + 10_162 ---> (, ) + 10_163 ---> (, ) + 10_164 ---> (, ) + 10_165 ---> (, ) Clarifying ther Perko series against `SnapPy `__:: @@ -4197,27 +4252,29 @@ def get_knotinfo(self, mirror_version=True, unique=True): ....: K = K10(i) ....: k = K.link(K.items.name, snappy=True) ....: print(k, '--->', k.sage_link().get_knotinfo()) - ---> (, False) - ---> (, True) - ---> (, False) - ---> (, False) - ---> (, False) - ---> (, False) + ---> (, ) + ---> (, ) + ---> (, ) + ---> (, ) + ---> (, ) + ---> (, ) sage: snappy.Link('10_166') sage: _.sage_link().get_knotinfo() - (, True) + (, ) Another pair of confusion (see the corresponding `Warning `__):: - sage: # optional - database_knotinfo snappy - sage: Ks10_86 = snappy.Link('10_86') - sage: Ks10_83 = snappy.Link('10_83') - sage: Ks10_86.sage_link().get_knotinfo() - (, True) - sage: Ks10_83.sage_link().get_knotinfo() - (, False) + sage: # optional - database_knotinfo snappy + sage: Ks10_86 = snappy.Link('10_86') + sage: Ks10_83 = snappy.Link('10_83') + sage: Ks10_86.sage_link().get_knotinfo(unique=False) + [(, ), + (, )] + sage: Ks10_83.sage_link().get_knotinfo(unique=False) + [(, ), + (, )] TESTS:: @@ -4225,15 +4282,30 @@ def get_knotinfo(self, mirror_version=True, unique=True): sage: L = KnotInfo.L10a171_1_1_0 sage: l = L.link(L.items.braid_notation) sage: l.get_knotinfo(unique=False) - [(, True), - (, True), - (, False), - (, False)] + [(, + ), + (, + ), + (, + ), + (, + ), + (, ), + (, ), + (, ), + (, )] + + sage: L = KnotInfo.L6a2_0 + sage: L1 = L.link() + sage: L2 = L.link(L.items.braid_notation) + sage: L1.get_knotinfo() == L2.get_knotinfo() + True """ # ToDo: extension to non prime links in which case an element of the monoid # over :class:`KnotInfo` should be returned non_unique_hint = '\nuse keyword argument `unique` to obtain more details' + from sage.knots.knotinfo import SymmetryMutant def answer(L): r""" @@ -4246,33 +4318,45 @@ def answer(L): chiral = True ach = L.is_amphicheiral() achp = L.is_amphicheiral(positive=True) - if ach is None and achp is None: + rev = L.is_reversible() + if ach is None and achp is None and rev is None: if unique: raise NotImplementedError('this link cannot be uniquely determined (unknown chirality)%s' % non_unique_hint) - elif L.is_amphicheiral() or L.is_amphicheiral(positive=True): + chiral = None + elif ach and achp: chiral = False - if not chiral: - mirrored = None - elif proved_m and not proved_s and L in lm: - mirrored = True - elif proved_s and not proved_m and L in l: - mirrored = False + sym_mut = None + if chiral is None: + sym_mut = SymmetryMutant.unknown + elif not chiral: + sym_mut = SymmetryMutant.itself else: - # nothing proved - if L in ls and L in lm: - # In case of a chiral link this means that the HOMFLY-PT - # polynomial does not distinguish mirror images (see the above - # example ``k11m``). - if unique: - raise NotImplementedError('mirror type of this link cannot be uniquely determined%s' % non_unique_hint) - mirrored = '?' - elif L in lm: - mirrored = True - else: - mirrored = False + for k in match_lists.keys(): + lk = match_lists[k] + if proves[k] and L in lk: + lk.remove(L) + sym_mut = k + break + + if not sym_mut: + for k in match_lists.keys(): + lk = match_lists[k] + if L in lk: + lk.remove(L) + sym_mut = k + break + + if not sym_mut: + # In case of a chiral link this means that the HOMFLY-PT + # polynomial does not distinguish mirror images (see the above + # example ``L10n36_0``). + sym_mut = SymmetryMutant.unknown - return L, mirrored + if unique and sym_mut is SymmetryMutant.unknown: + raise NotImplementedError('symmetry mutant of this link cannot be uniquely determined%s' % non_unique_hint) + + return L, sym_mut def answer_unori(S): r""" @@ -4282,21 +4366,18 @@ def answer_unori(S): if not mirror_version: return S - mirrored = [answer(L)[1] for L in S] - if all(mirrored): + sym_mut = [answer(L)[1] for L in S] + if all(i is SymmetryMutant.mirror_image for i in sym_mut): # all matching links are mirrored to self - return S, True - if any(i == '?' for i in mirrored): + return S, SymmetryMutant.mirror_image + if all(i is SymmetryMutant.itself for i in sym_mut): + # all matching links are self itself + return S, SymmetryMutant.itself + if any(i is SymmetryMutant.unknown for i in sym_mut): # unknown chirality for a matching link - return S, '?' - if any(i is None for i in mirrored): - # an amphicheiral link matches - return S, None - if not any(mirrored): - # no matching link is mirrored to self - return S, False - # finally both mirror types match - return S, None + return S, SymmetryMutant.unknown + # finally several mirror types match + return S, SymmetryMutant.mixed def answer_list(l): r""" @@ -4304,9 +4385,9 @@ def answer_list(l): argument ``unique``. """ if not unique: - return sorted([answer(L) for L in l]) + return sorted(set([answer(L) for L in l])) - if len(l) == 1: + if len(set(l)) == 1: return answer(l[0]) if not l[0].is_knot(): @@ -4316,23 +4397,28 @@ 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() - l = list(set(ls + lm)) + match_lists, proves = self._knotinfo_matching_dict() + + # first add only proved matching lists + proved = any(proves[k] for k in proves.keys()) + + l = [] + if proved and unique and self.is_knot(): + for k in match_lists.keys(): + if proves[k]: + l += match_lists[k] + else: + # for multi-component links there could regularily be more than one + # matching entry + for k in match_lists.keys(): + l += match_lists[k] if l and not unique: return answer_list(l) - if proved_s and proved_m: + if proved: return answer_list(l) - if proved_s: - return answer_list(ls) - - if proved_m: - return answer_list(lm) - # here we come if we cannot be sure about the found result uniq_txt = ('', '') @@ -4386,7 +4472,6 @@ def is_isotopic(self, other): False sage: # optional - database_knotinfo - sage: from sage.knots.knotinfo import KnotInfo sage: L = KnotInfo.L7a7_0_0 sage: L.series(oriented=True).inject() Defining L7a7 @@ -4401,6 +4486,37 @@ def is_isotopic(self, other): True sage: l.is_isotopic(L7a7(3).link()) False + + Using verbosity:: + + sage: set_verbose(1) + sage: l1.is_isotopic(l2) + verbose 1 (... link.py, is_isotopic) identified by KnotInfo (KnotInfo.K7_2, SymmetryMutant.mirror_image) + True + sage: l1.is_isotopic(l3) + verbose 1 (... link.py, is_isotopic) different Homfly-PT polynomials + False + sage: set_verbose(0) + + TESTS:: + + sage: L = KnotInfo.L6a2_0 + sage: L1 = L.link() + sage: L2 = L.link(L.items.braid_notation) + sage: set_verbose(1) + sage: L1.is_isotopic(L2) + verbose 1 (... link.py, is_isotopic) identified by KnotInfo uniquely (KnotInfo.L6a2_0, SymmetryMutant.itself) + True + + sage: # optional - database_knotinfo + sage: K = KnotInfo.K10_67 + sage: K1 = K.link() + sage: K1r = K.link().reverse() + sage: K1.is_isotopic(K1r) + verbose 1 (... link.py, is_isotopic) unidentified by KnotInfo ([], SymmetryMutant.itself != [], SymmetryMutant.reverse) + False + sage: set_verbose(0) + """ from sage.misc.verbose import verbose if not isinstance(other, Link): @@ -4412,6 +4528,11 @@ def is_isotopic(self, other): verbose('identified by representation') return True + if self.number_of_components() != other.number_of_components(): + # surely non isotopic + verbose('different number of components') + return False + if self.homfly_polynomial() != other.homfly_polynomial(): # surely non isotopic verbose('different Homfly-PT polynomials') @@ -4422,21 +4543,53 @@ def is_isotopic(self, other): verbose('identified via Markov moves') return True - try: - ki, m = self.get_knotinfo() - verbose('KnotInfo self: %s mirrored %s' % (ki, m)) - try: - if ki.is_unique(): - try: - kio = other.get_knotinfo() - verbose('KnotInfo other: %s mirrored %s' % kio) - return (ki, m) == kio - except NotImplementedError: - pass - except AttributeError: - # ki is a series - pass - except NotImplementedError: - pass + slists, sproves = self._knotinfo_matching_dict() + olists, oproves = other._knotinfo_matching_dict() + proved_s = None + proved_o = None + for k in slists.keys(): + sl = slists[k] + ol = olists[k] + sp = sproves[k] + op = oproves[k] + if sp and op: + if sorted(sl) == sorted(ol): + if len(sl) == 1: + verbose('identified by KnotInfo uniquely (%s, %s)' % (sl[0], k)) + return True + elif not self.is_knot(): + if len(set([l[0].series(oriented=True) for l in sl])) == 1: + # all matches are orientation mutants of each other + verbose('identified by KnotInfoSeries (%s, %s)' % (sl, k)) + return True + else: + verbose('KnotInfoSeries non-unique (%s, %s)' % (sl, k)) + else: + verbose('KnotInfo non-unique (%s, %s)' % (sl, k)) + else: + common = [l for l in sl if l in ol] + if common: + # better don't trust + verbose('KnotInfo common: %s' % common) + else: + verbose('unidentified by KnotInfo (%s != %s, %s)' % (sl, ol, k)) + return False + elif sp: + proved_s = (sl, k) + elif op: + proved_o = (ol, k) + if proved_s and proved_o: + sl, sk = proved_s + ol, ok = proved_o + verbose('unidentified by KnotInfo (%s, %s != %s, %s)' % (sl, sk, ol, ok)) + return False + + for k in slists.keys(): + # second loop without provings + sl = slists[k] + ol = olists[k] + if sorted(sl) == sorted(ol) and len(sl) == 1: + verbose('identified by KnotInfo (%s, %s)' % (sl[0], k)) + return True raise NotImplementedError('comparison not possible!') From 73fbbc2ec58077e005db1e2f939846d7121a4eaa Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 25 Mar 2024 08:19:44 +0100 Subject: [PATCH 2/8] 37668: add link to PR --- src/sage/knots/link.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 054f1c47eb0..34fc52a7400 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -4498,7 +4498,9 @@ def is_isotopic(self, other): False sage: set_verbose(0) - TESTS:: + TESTS: + + check that :issue:`37668` is fixed:: sage: L = KnotInfo.L6a2_0 sage: L1 = L.link() From c55e0d33a0d9813c5cfba29de5385a6a3a466d58 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 26 Mar 2024 21:51:58 +0100 Subject: [PATCH 3/8] 37668: Linter and Codecov fixes --- src/sage/knots/knotinfo.py | 20 ++++++++++++-------- src/sage/knots/link.py | 23 +++++++++++++++++++++-- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 8deb3fbe37a..019f56ba6c2 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -349,12 +349,12 @@ class of an oriented pair, `K = (S_3, S_1)`, with `S_i` the concordance inverse, `-K = (-S_3, -S_1)`, and the mirror image, `K^m = (-S_3, S_1)`. """ - itself ='s' - reverse ='r' - concordance_inverse ='mr' - mirror_image ='m' + itself = 's' + reverse = 'r' + concordance_inverse = 'mr' + mirror_image = 'm' mixed = 'x' # to be used in connection with KnotInfoSeries - unknown ='?' + unknown = '?' def __gt__(self, other): r""" @@ -371,9 +371,7 @@ def __gt__(self, other): , ] """ - if self.__class__ is other.__class__: - return self.value < other.value - return NotImplemented + return self.value < other.value # --------------------------------------------------------------------------------- @@ -986,6 +984,12 @@ def is_reversible(self): sage: KnotInfo.K6_3.is_reversible() True + + TESTS:: + + sage: KnotInfo.K10_67.is_reversible() # optional - database_knotinfo + False + sage: KnotInfo.L7a4_0.is_reversible() # optional - database_knotinfo """ if self.is_knot(): symmetry_type = self.symmetry_type() diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 34fc52a7400..681499655de 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -4294,6 +4294,16 @@ def get_knotinfo(self, mirror_version=True, unique=True): (, ), (, ), (, )] + sage: KnotInfo.L10a151_0_0.link().get_knotinfo() + Traceback (most recent call last): + ... + NotImplementedError: this link cannot be uniquely determined (unknown chirality) + use keyword argument `unique` to obtain more details + sage: KnotInfo.L10a151_0_0.link().get_knotinfo(unique=False) + [(, ), + (, ), + (, ), + (, )] sage: L = KnotInfo.L6a2_0 sage: L1 = L.link() @@ -4509,6 +4519,9 @@ def is_isotopic(self, other): sage: L1.is_isotopic(L2) verbose 1 (... link.py, is_isotopic) identified by KnotInfo uniquely (KnotInfo.L6a2_0, SymmetryMutant.itself) True + sage: KnotInfo.K0_1.link().is_isotopic(KnotInfo.L2a1_0.link()) + verbose 1 (... link.py, is_isotopic) different number of components + False sage: # optional - database_knotinfo sage: K = KnotInfo.K10_67 @@ -4517,6 +4530,12 @@ def is_isotopic(self, other): sage: K1.is_isotopic(K1r) verbose 1 (... link.py, is_isotopic) unidentified by KnotInfo ([], SymmetryMutant.itself != [], SymmetryMutant.reverse) False + sage: KnotInfo.K10_25.link().is_isotopic(KnotInfo.K10_56.link()) + verbose 1 (... link.py, is_isotopic) unidentified by KnotInfo ([] != [], SymmetryMutant.itself) + False + sage: KnotInfo.L8n2_0.link().is_isotopic(KnotInfo.L8n2_1.link()) + verbose 1 (... link.py, is_isotopic) identified by KnotInfoSeries ([, ], SymmetryMutant.reverse) + True sage: set_verbose(0) """ @@ -4560,7 +4579,7 @@ def is_isotopic(self, other): verbose('identified by KnotInfo uniquely (%s, %s)' % (sl[0], k)) return True elif not self.is_knot(): - if len(set([l[0].series(oriented=True) for l in sl])) == 1: + if len(set([l.series(oriented=True) for l in sl])) == 1: # all matches are orientation mutants of each other verbose('identified by KnotInfoSeries (%s, %s)' % (sl, k)) return True @@ -4569,7 +4588,7 @@ def is_isotopic(self, other): else: verbose('KnotInfo non-unique (%s, %s)' % (sl, k)) else: - common = [l for l in sl if l in ol] + common = [l for l in sl if l in ol] if common: # better don't trust verbose('KnotInfo common: %s' % common) From f85804e4d475224901bcfc6c06d9fffc389c606c Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:46:42 +0100 Subject: [PATCH 4/8] 37668: codestyle fix 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 681499655de..c5da2dc06cc 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -4342,7 +4342,7 @@ def answer(L): elif not chiral: sym_mut = SymmetryMutant.itself else: - for k in match_lists.keys(): + for k in match_lists: lk = match_lists[k] if proves[k] and L in lk: lk.remove(L) From b053315a4865dbd7fb8dfbdec5e08c82afccc677 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:47:21 +0100 Subject: [PATCH 5/8] 37668: codestyle fix second occurence 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 c5da2dc06cc..0417d2f1bf7 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -4350,7 +4350,7 @@ def answer(L): break if not sym_mut: - for k in match_lists.keys(): + for k in match_lists: lk = match_lists[k] if L in lk: lk.remove(L) From 9312a36e06700dd9f96bb603443d52253be3cce2 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:48:06 +0100 Subject: [PATCH 6/8] 37668: codestyle fix whitespace 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 0417d2f1bf7..7a3aa6dfcc3 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -4377,7 +4377,7 @@ def answer_unori(S): return S sym_mut = [answer(L)[1] for L in S] - if all(i is SymmetryMutant.mirror_image for i in sym_mut): + if all(i is SymmetryMutant.mirror_image for i in sym_mut): # all matching links are mirrored to self return S, SymmetryMutant.mirror_image if all(i is SymmetryMutant.itself for i in sym_mut): From f98266e90fe26ecbcb87df050ce72f5f8bd76e29 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:54:40 +0100 Subject: [PATCH 7/8] 37668: doctest-style 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 7a3aa6dfcc3..7ddbe5879c3 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -4510,7 +4510,7 @@ def is_isotopic(self, other): TESTS: - check that :issue:`37668` is fixed:: + Check that :issue:`37668` is fixed:: sage: L = KnotInfo.L6a2_0 sage: L1 = L.link() From b8757d814ed5a339ce250e5df0c5c1a8b10baaba Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 28 Mar 2024 18:34:05 +0100 Subject: [PATCH 8/8] 37668: add a comment about the order of SymmetryMutant --- src/sage/knots/knotinfo.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 019f56ba6c2..d30fd678735 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -371,6 +371,8 @@ def __gt__(self, other): , ] """ + # We use the reversal of the alphabetical order of the values so that + # `itself` occurs before the mirrored cases return self.value < other.value